initial version of VSAP 34/25334/2
authorYu Ping <[email protected]>
Fri, 21 Feb 2020 14:36:20 +0000 (22:36 +0800)
committerFlorin Coras <[email protected]>
Sat, 22 Feb 2020 00:30:48 +0000 (00:30 +0000)
Signed-off-by: Yu Ping <[email protected]>
Change-Id: I04d9150f0c7607ba20de9096b452476eff1622fc

407 files changed:
README.md [new file with mode: 0644]
configs/README [new file with mode: 0644]
configs/nginx.conf [new file with mode: 0644]
configs/startup.conf [new file with mode: 0644]
configs/vcl.conf [new file with mode: 0644]
configs/vppenvset [new file with mode: 0644]
nginx/CHANGES [new file with mode: 0644]
nginx/CHANGES.ru [new file with mode: 0644]
nginx/LICENSE [new file with mode: 0644]
nginx/README [new file with mode: 0644]
nginx/auto/cc/acc [new file with mode: 0644]
nginx/auto/cc/bcc [new file with mode: 0644]
nginx/auto/cc/ccc [new file with mode: 0644]
nginx/auto/cc/clang [new file with mode: 0644]
nginx/auto/cc/conf [new file with mode: 0644]
nginx/auto/cc/gcc [new file with mode: 0644]
nginx/auto/cc/icc [new file with mode: 0644]
nginx/auto/cc/msvc [new file with mode: 0644]
nginx/auto/cc/name [new file with mode: 0644]
nginx/auto/cc/owc [new file with mode: 0644]
nginx/auto/cc/sunc [new file with mode: 0644]
nginx/auto/define [new file with mode: 0644]
nginx/auto/endianness [new file with mode: 0644]
nginx/auto/feature [new file with mode: 0644]
nginx/auto/have [new file with mode: 0644]
nginx/auto/have_headers [new file with mode: 0644]
nginx/auto/headers [new file with mode: 0644]
nginx/auto/include [new file with mode: 0644]
nginx/auto/init [new file with mode: 0644]
nginx/auto/install [new file with mode: 0644]
nginx/auto/lib/conf [new file with mode: 0644]
nginx/auto/lib/geoip/conf [new file with mode: 0644]
nginx/auto/lib/google-perftools/conf [new file with mode: 0644]
nginx/auto/lib/libatomic/conf [new file with mode: 0644]
nginx/auto/lib/libatomic/make [new file with mode: 0644]
nginx/auto/lib/libgd/conf [new file with mode: 0644]
nginx/auto/lib/libxslt/conf [new file with mode: 0644]
nginx/auto/lib/make [new file with mode: 0644]
nginx/auto/lib/openssl/conf [new file with mode: 0644]
nginx/auto/lib/openssl/make [new file with mode: 0644]
nginx/auto/lib/openssl/makefile.bcc [new file with mode: 0644]
nginx/auto/lib/openssl/makefile.msvc [new file with mode: 0644]
nginx/auto/lib/pcre/conf [new file with mode: 0644]
nginx/auto/lib/pcre/make [new file with mode: 0644]
nginx/auto/lib/pcre/makefile.bcc [new file with mode: 0644]
nginx/auto/lib/pcre/makefile.msvc [new file with mode: 0644]
nginx/auto/lib/pcre/makefile.owc [new file with mode: 0644]
nginx/auto/lib/perl/conf [new file with mode: 0644]
nginx/auto/lib/perl/make [new file with mode: 0644]
nginx/auto/lib/zlib/conf [new file with mode: 0644]
nginx/auto/lib/zlib/make [new file with mode: 0644]
nginx/auto/lib/zlib/makefile.bcc [new file with mode: 0644]
nginx/auto/lib/zlib/makefile.msvc [new file with mode: 0644]
nginx/auto/lib/zlib/makefile.owc [new file with mode: 0644]
nginx/auto/make [new file with mode: 0644]
nginx/auto/module [new file with mode: 0644]
nginx/auto/modules [new file with mode: 0644]
nginx/auto/nohave [new file with mode: 0644]
nginx/auto/options [new file with mode: 0644]
nginx/auto/os/conf [new file with mode: 0644]
nginx/auto/os/darwin [new file with mode: 0644]
nginx/auto/os/freebsd [new file with mode: 0644]
nginx/auto/os/linux [new file with mode: 0644]
nginx/auto/os/solaris [new file with mode: 0644]
nginx/auto/os/win32 [new file with mode: 0644]
nginx/auto/sources [new file with mode: 0644]
nginx/auto/stubs [new file with mode: 0644]
nginx/auto/summary [new file with mode: 0644]
nginx/auto/threads [new file with mode: 0644]
nginx/auto/types/sizeof [new file with mode: 0644]
nginx/auto/types/typedef [new file with mode: 0644]
nginx/auto/types/uintptr_t [new file with mode: 0644]
nginx/auto/types/value [new file with mode: 0644]
nginx/auto/unix [new file with mode: 0644]
nginx/conf/fastcgi.conf [new file with mode: 0644]
nginx/conf/fastcgi_params [new file with mode: 0644]
nginx/conf/koi-utf [new file with mode: 0644]
nginx/conf/koi-win [new file with mode: 0644]
nginx/conf/mime.types [new file with mode: 0644]
nginx/conf/nginx.conf [new file with mode: 0644]
nginx/conf/scgi_params [new file with mode: 0644]
nginx/conf/uwsgi_params [new file with mode: 0644]
nginx/conf/win-utf [new file with mode: 0644]
nginx/configure [new file with mode: 0755]
nginx/contrib/README [new file with mode: 0644]
nginx/contrib/geo2nginx.pl [new file with mode: 0644]
nginx/contrib/unicode2nginx/koi-utf [new file with mode: 0644]
nginx/contrib/unicode2nginx/unicode-to-nginx.pl [new file with mode: 0755]
nginx/contrib/unicode2nginx/win-utf [new file with mode: 0644]
nginx/contrib/vim/ftdetect/nginx.vim [new file with mode: 0644]
nginx/contrib/vim/ftplugin/nginx.vim [new file with mode: 0644]
nginx/contrib/vim/indent/nginx.vim [new file with mode: 0644]
nginx/contrib/vim/syntax/nginx.vim [new file with mode: 0644]
nginx/html/50x.html [new file with mode: 0644]
nginx/html/index.html [new file with mode: 0644]
nginx/man/nginx.8 [new file with mode: 0644]
nginx/src/core/nginx.c [new file with mode: 0644]
nginx/src/core/nginx.h [new file with mode: 0644]
nginx/src/core/ngx_array.c [new file with mode: 0644]
nginx/src/core/ngx_array.h [new file with mode: 0644]
nginx/src/core/ngx_buf.c [new file with mode: 0644]
nginx/src/core/ngx_buf.h [new file with mode: 0644]
nginx/src/core/ngx_conf_file.c [new file with mode: 0644]
nginx/src/core/ngx_conf_file.h [new file with mode: 0644]
nginx/src/core/ngx_config.h [new file with mode: 0644]
nginx/src/core/ngx_connection.c [new file with mode: 0644]
nginx/src/core/ngx_connection.h [new file with mode: 0644]
nginx/src/core/ngx_core.h [new file with mode: 0644]
nginx/src/core/ngx_cpuinfo.c [new file with mode: 0644]
nginx/src/core/ngx_crc.h [new file with mode: 0644]
nginx/src/core/ngx_crc32.c [new file with mode: 0644]
nginx/src/core/ngx_crc32.h [new file with mode: 0644]
nginx/src/core/ngx_crypt.c [new file with mode: 0644]
nginx/src/core/ngx_crypt.h [new file with mode: 0644]
nginx/src/core/ngx_cycle.c [new file with mode: 0644]
nginx/src/core/ngx_cycle.h [new file with mode: 0644]
nginx/src/core/ngx_file.c [new file with mode: 0644]
nginx/src/core/ngx_file.h [new file with mode: 0644]
nginx/src/core/ngx_hash.c [new file with mode: 0644]
nginx/src/core/ngx_hash.h [new file with mode: 0644]
nginx/src/core/ngx_inet.c [new file with mode: 0644]
nginx/src/core/ngx_inet.h [new file with mode: 0644]
nginx/src/core/ngx_list.c [new file with mode: 0644]
nginx/src/core/ngx_list.h [new file with mode: 0644]
nginx/src/core/ngx_log.c [new file with mode: 0644]
nginx/src/core/ngx_log.h [new file with mode: 0644]
nginx/src/core/ngx_md5.c [new file with mode: 0644]
nginx/src/core/ngx_md5.h [new file with mode: 0644]
nginx/src/core/ngx_module.c [new file with mode: 0644]
nginx/src/core/ngx_module.h [new file with mode: 0644]
nginx/src/core/ngx_murmurhash.c [new file with mode: 0644]
nginx/src/core/ngx_murmurhash.h [new file with mode: 0644]
nginx/src/core/ngx_open_file_cache.c [new file with mode: 0644]
nginx/src/core/ngx_open_file_cache.h [new file with mode: 0644]
nginx/src/core/ngx_output_chain.c [new file with mode: 0644]
nginx/src/core/ngx_palloc.c [new file with mode: 0644]
nginx/src/core/ngx_palloc.h [new file with mode: 0644]
nginx/src/core/ngx_parse.c [new file with mode: 0644]
nginx/src/core/ngx_parse.h [new file with mode: 0644]
nginx/src/core/ngx_parse_time.c [new file with mode: 0644]
nginx/src/core/ngx_parse_time.h [new file with mode: 0644]
nginx/src/core/ngx_proxy_protocol.c [new file with mode: 0644]
nginx/src/core/ngx_proxy_protocol.h [new file with mode: 0644]
nginx/src/core/ngx_queue.c [new file with mode: 0644]
nginx/src/core/ngx_queue.h [new file with mode: 0644]
nginx/src/core/ngx_radix_tree.c [new file with mode: 0644]
nginx/src/core/ngx_radix_tree.h [new file with mode: 0644]
nginx/src/core/ngx_rbtree.c [new file with mode: 0644]
nginx/src/core/ngx_rbtree.h [new file with mode: 0644]
nginx/src/core/ngx_regex.c [new file with mode: 0644]
nginx/src/core/ngx_regex.h [new file with mode: 0644]
nginx/src/core/ngx_resolver.c [new file with mode: 0644]
nginx/src/core/ngx_resolver.h [new file with mode: 0644]
nginx/src/core/ngx_rwlock.c [new file with mode: 0644]
nginx/src/core/ngx_rwlock.h [new file with mode: 0644]
nginx/src/core/ngx_sha1.c [new file with mode: 0644]
nginx/src/core/ngx_sha1.h [new file with mode: 0644]
nginx/src/core/ngx_shmtx.c [new file with mode: 0644]
nginx/src/core/ngx_shmtx.h [new file with mode: 0644]
nginx/src/core/ngx_slab.c [new file with mode: 0644]
nginx/src/core/ngx_slab.h [new file with mode: 0644]
nginx/src/core/ngx_spinlock.c [new file with mode: 0644]
nginx/src/core/ngx_string.c [new file with mode: 0644]
nginx/src/core/ngx_string.h [new file with mode: 0644]
nginx/src/core/ngx_syslog.c [new file with mode: 0644]
nginx/src/core/ngx_syslog.h [new file with mode: 0644]
nginx/src/core/ngx_thread_pool.c [new file with mode: 0644]
nginx/src/core/ngx_thread_pool.h [new file with mode: 0644]
nginx/src/core/ngx_times.c [new file with mode: 0644]
nginx/src/core/ngx_times.h [new file with mode: 0644]
nginx/src/event/modules/ngx_devpoll_module.c [new file with mode: 0644]
nginx/src/event/modules/ngx_epoll_module.c [new file with mode: 0644]
nginx/src/event/modules/ngx_eventport_module.c [new file with mode: 0644]
nginx/src/event/modules/ngx_kqueue_module.c [new file with mode: 0644]
nginx/src/event/modules/ngx_poll_module.c [new file with mode: 0644]
nginx/src/event/modules/ngx_select_module.c [new file with mode: 0644]
nginx/src/event/modules/ngx_win32_select_module.c [new file with mode: 0644]
nginx/src/event/ngx_event.c [new file with mode: 0644]
nginx/src/event/ngx_event.h [new file with mode: 0644]
nginx/src/event/ngx_event_accept.c [new file with mode: 0644]
nginx/src/event/ngx_event_connect.c [new file with mode: 0644]
nginx/src/event/ngx_event_connect.h [new file with mode: 0644]
nginx/src/event/ngx_event_openssl.c [new file with mode: 0644]
nginx/src/event/ngx_event_openssl.h [new file with mode: 0644]
nginx/src/event/ngx_event_openssl_stapling.c [new file with mode: 0644]
nginx/src/event/ngx_event_pipe.c [new file with mode: 0644]
nginx/src/event/ngx_event_pipe.h [new file with mode: 0644]
nginx/src/event/ngx_event_posted.c [new file with mode: 0644]
nginx/src/event/ngx_event_posted.h [new file with mode: 0644]
nginx/src/event/ngx_event_timer.c [new file with mode: 0644]
nginx/src/event/ngx_event_timer.h [new file with mode: 0644]
nginx/src/http/modules/ngx_http_access_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_addition_filter_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_auth_basic_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_auth_request_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_autoindex_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_browser_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_charset_filter_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_chunked_filter_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_dav_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_degradation_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_empty_gif_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_fastcgi_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_flv_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_geo_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_geoip_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_grpc_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_gunzip_filter_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_gzip_filter_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_gzip_static_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_headers_filter_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_image_filter_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_index_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_limit_conn_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_limit_req_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_log_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_map_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_memcached_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_mirror_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_mp4_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_not_modified_filter_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_proxy_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_random_index_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_range_filter_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_realip_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_referer_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_rewrite_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_scgi_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_secure_link_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_slice_filter_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_split_clients_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_ssi_filter_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_ssi_filter_module.h [new file with mode: 0644]
nginx/src/http/modules/ngx_http_ssl_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_ssl_module.h [new file with mode: 0644]
nginx/src/http/modules/ngx_http_static_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_stub_status_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_sub_filter_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_try_files_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_upstream_hash_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_upstream_ip_hash_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_upstream_keepalive_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_upstream_least_conn_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_upstream_zone_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_userid_filter_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_uwsgi_module.c [new file with mode: 0644]
nginx/src/http/modules/ngx_http_xslt_filter_module.c [new file with mode: 0644]
nginx/src/http/modules/perl/Makefile.PL [new file with mode: 0644]
nginx/src/http/modules/perl/nginx.pm [new file with mode: 0644]
nginx/src/http/modules/perl/nginx.xs [new file with mode: 0644]
nginx/src/http/modules/perl/ngx_http_perl_module.c [new file with mode: 0644]
nginx/src/http/modules/perl/ngx_http_perl_module.h [new file with mode: 0644]
nginx/src/http/modules/perl/typemap [new file with mode: 0644]
nginx/src/http/ngx_http.c [new file with mode: 0644]
nginx/src/http/ngx_http.h [new file with mode: 0644]
nginx/src/http/ngx_http_cache.h [new file with mode: 0644]
nginx/src/http/ngx_http_config.h [new file with mode: 0644]
nginx/src/http/ngx_http_copy_filter_module.c [new file with mode: 0644]
nginx/src/http/ngx_http_core_module.c [new file with mode: 0644]
nginx/src/http/ngx_http_core_module.h [new file with mode: 0644]
nginx/src/http/ngx_http_file_cache.c [new file with mode: 0644]
nginx/src/http/ngx_http_header_filter_module.c [new file with mode: 0644]
nginx/src/http/ngx_http_parse.c [new file with mode: 0644]
nginx/src/http/ngx_http_postpone_filter_module.c [new file with mode: 0644]
nginx/src/http/ngx_http_request.c [new file with mode: 0644]
nginx/src/http/ngx_http_request.h [new file with mode: 0644]
nginx/src/http/ngx_http_request_body.c [new file with mode: 0644]
nginx/src/http/ngx_http_script.c [new file with mode: 0644]
nginx/src/http/ngx_http_script.h [new file with mode: 0644]
nginx/src/http/ngx_http_special_response.c [new file with mode: 0644]
nginx/src/http/ngx_http_upstream.c [new file with mode: 0644]
nginx/src/http/ngx_http_upstream.h [new file with mode: 0644]
nginx/src/http/ngx_http_upstream_round_robin.c [new file with mode: 0644]
nginx/src/http/ngx_http_upstream_round_robin.h [new file with mode: 0644]
nginx/src/http/ngx_http_variables.c [new file with mode: 0644]
nginx/src/http/ngx_http_variables.h [new file with mode: 0644]
nginx/src/http/ngx_http_write_filter_module.c [new file with mode: 0644]
nginx/src/http/v2/ngx_http_v2.c [new file with mode: 0644]
nginx/src/http/v2/ngx_http_v2.h [new file with mode: 0644]
nginx/src/http/v2/ngx_http_v2_encode.c [new file with mode: 0644]
nginx/src/http/v2/ngx_http_v2_filter_module.c [new file with mode: 0644]
nginx/src/http/v2/ngx_http_v2_huff_decode.c [new file with mode: 0644]
nginx/src/http/v2/ngx_http_v2_huff_encode.c [new file with mode: 0644]
nginx/src/http/v2/ngx_http_v2_module.c [new file with mode: 0644]
nginx/src/http/v2/ngx_http_v2_module.h [new file with mode: 0644]
nginx/src/http/v2/ngx_http_v2_table.c [new file with mode: 0644]
nginx/src/mail/ngx_mail.c [new file with mode: 0644]
nginx/src/mail/ngx_mail.h [new file with mode: 0644]
nginx/src/mail/ngx_mail_auth_http_module.c [new file with mode: 0644]
nginx/src/mail/ngx_mail_core_module.c [new file with mode: 0644]
nginx/src/mail/ngx_mail_handler.c [new file with mode: 0644]
nginx/src/mail/ngx_mail_imap_handler.c [new file with mode: 0644]
nginx/src/mail/ngx_mail_imap_module.c [new file with mode: 0644]
nginx/src/mail/ngx_mail_imap_module.h [new file with mode: 0644]
nginx/src/mail/ngx_mail_parse.c [new file with mode: 0644]
nginx/src/mail/ngx_mail_pop3_handler.c [new file with mode: 0644]
nginx/src/mail/ngx_mail_pop3_module.c [new file with mode: 0644]
nginx/src/mail/ngx_mail_pop3_module.h [new file with mode: 0644]
nginx/src/mail/ngx_mail_proxy_module.c [new file with mode: 0644]
nginx/src/mail/ngx_mail_smtp_handler.c [new file with mode: 0644]
nginx/src/mail/ngx_mail_smtp_module.c [new file with mode: 0644]
nginx/src/mail/ngx_mail_smtp_module.h [new file with mode: 0644]
nginx/src/mail/ngx_mail_ssl_module.c [new file with mode: 0644]
nginx/src/mail/ngx_mail_ssl_module.h [new file with mode: 0644]
nginx/src/misc/ngx_cpp_test_module.cpp [new file with mode: 0644]
nginx/src/misc/ngx_google_perftools_module.c [new file with mode: 0644]
nginx/src/os/unix/ngx_alloc.c [new file with mode: 0644]
nginx/src/os/unix/ngx_alloc.h [new file with mode: 0644]
nginx/src/os/unix/ngx_atomic.h [new file with mode: 0644]
nginx/src/os/unix/ngx_channel.c [new file with mode: 0644]
nginx/src/os/unix/ngx_channel.h [new file with mode: 0644]
nginx/src/os/unix/ngx_daemon.c [new file with mode: 0644]
nginx/src/os/unix/ngx_darwin.h [new file with mode: 0644]
nginx/src/os/unix/ngx_darwin_config.h [new file with mode: 0644]
nginx/src/os/unix/ngx_darwin_init.c [new file with mode: 0644]
nginx/src/os/unix/ngx_darwin_sendfile_chain.c [new file with mode: 0644]
nginx/src/os/unix/ngx_dlopen.c [new file with mode: 0644]
nginx/src/os/unix/ngx_dlopen.h [new file with mode: 0644]
nginx/src/os/unix/ngx_errno.c [new file with mode: 0644]
nginx/src/os/unix/ngx_errno.h [new file with mode: 0644]
nginx/src/os/unix/ngx_file_aio_read.c [new file with mode: 0644]
nginx/src/os/unix/ngx_files.c [new file with mode: 0644]
nginx/src/os/unix/ngx_files.h [new file with mode: 0644]
nginx/src/os/unix/ngx_freebsd.h [new file with mode: 0644]
nginx/src/os/unix/ngx_freebsd_config.h [new file with mode: 0644]
nginx/src/os/unix/ngx_freebsd_init.c [new file with mode: 0644]
nginx/src/os/unix/ngx_freebsd_sendfile_chain.c [new file with mode: 0644]
nginx/src/os/unix/ngx_gcc_atomic_amd64.h [new file with mode: 0644]
nginx/src/os/unix/ngx_gcc_atomic_ppc.h [new file with mode: 0644]
nginx/src/os/unix/ngx_gcc_atomic_sparc64.h [new file with mode: 0644]
nginx/src/os/unix/ngx_gcc_atomic_x86.h [new file with mode: 0644]
nginx/src/os/unix/ngx_linux.h [new file with mode: 0644]
nginx/src/os/unix/ngx_linux_aio_read.c [new file with mode: 0644]
nginx/src/os/unix/ngx_linux_config.h [new file with mode: 0644]
nginx/src/os/unix/ngx_linux_init.c [new file with mode: 0644]
nginx/src/os/unix/ngx_linux_sendfile_chain.c [new file with mode: 0644]
nginx/src/os/unix/ngx_os.h [new file with mode: 0644]
nginx/src/os/unix/ngx_posix_config.h [new file with mode: 0644]
nginx/src/os/unix/ngx_posix_init.c [new file with mode: 0644]
nginx/src/os/unix/ngx_process.c [new file with mode: 0644]
nginx/src/os/unix/ngx_process.h [new file with mode: 0644]
nginx/src/os/unix/ngx_process_cycle.c [new file with mode: 0644]
nginx/src/os/unix/ngx_process_cycle.h [new file with mode: 0644]
nginx/src/os/unix/ngx_readv_chain.c [new file with mode: 0644]
nginx/src/os/unix/ngx_recv.c [new file with mode: 0644]
nginx/src/os/unix/ngx_send.c [new file with mode: 0644]
nginx/src/os/unix/ngx_setaffinity.c [new file with mode: 0644]
nginx/src/os/unix/ngx_setaffinity.h [new file with mode: 0644]
nginx/src/os/unix/ngx_setproctitle.c [new file with mode: 0644]
nginx/src/os/unix/ngx_setproctitle.h [new file with mode: 0644]
nginx/src/os/unix/ngx_shmem.c [new file with mode: 0644]
nginx/src/os/unix/ngx_shmem.h [new file with mode: 0644]
nginx/src/os/unix/ngx_socket.c [new file with mode: 0644]
nginx/src/os/unix/ngx_socket.h [new file with mode: 0644]
nginx/src/os/unix/ngx_solaris.h [new file with mode: 0644]
nginx/src/os/unix/ngx_solaris_config.h [new file with mode: 0644]
nginx/src/os/unix/ngx_solaris_init.c [new file with mode: 0644]
nginx/src/os/unix/ngx_solaris_sendfilev_chain.c [new file with mode: 0644]
nginx/src/os/unix/ngx_sunpro_amd64.il [new file with mode: 0644]
nginx/src/os/unix/ngx_sunpro_atomic_sparc64.h [new file with mode: 0644]
nginx/src/os/unix/ngx_sunpro_sparc64.il [new file with mode: 0644]
nginx/src/os/unix/ngx_sunpro_x86.il [new file with mode: 0644]
nginx/src/os/unix/ngx_thread.h [new file with mode: 0644]
nginx/src/os/unix/ngx_thread_cond.c [new file with mode: 0644]
nginx/src/os/unix/ngx_thread_id.c [new file with mode: 0644]
nginx/src/os/unix/ngx_thread_mutex.c [new file with mode: 0644]
nginx/src/os/unix/ngx_time.c [new file with mode: 0644]
nginx/src/os/unix/ngx_time.h [new file with mode: 0644]
nginx/src/os/unix/ngx_udp_recv.c [new file with mode: 0644]
nginx/src/os/unix/ngx_udp_send.c [new file with mode: 0644]
nginx/src/os/unix/ngx_udp_sendmsg_chain.c [new file with mode: 0644]
nginx/src/os/unix/ngx_user.c [new file with mode: 0644]
nginx/src/os/unix/ngx_user.h [new file with mode: 0644]
nginx/src/os/unix/ngx_writev_chain.c [new file with mode: 0644]
nginx/src/stream/ngx_stream.c [new file with mode: 0644]
nginx/src/stream/ngx_stream.h [new file with mode: 0644]
nginx/src/stream/ngx_stream_access_module.c [new file with mode: 0644]
nginx/src/stream/ngx_stream_core_module.c [new file with mode: 0644]
nginx/src/stream/ngx_stream_geo_module.c [new file with mode: 0644]
nginx/src/stream/ngx_stream_geoip_module.c [new file with mode: 0644]
nginx/src/stream/ngx_stream_handler.c [new file with mode: 0644]
nginx/src/stream/ngx_stream_limit_conn_module.c [new file with mode: 0644]
nginx/src/stream/ngx_stream_log_module.c [new file with mode: 0644]
nginx/src/stream/ngx_stream_map_module.c [new file with mode: 0644]
nginx/src/stream/ngx_stream_proxy_module.c [new file with mode: 0644]
nginx/src/stream/ngx_stream_realip_module.c [new file with mode: 0644]
nginx/src/stream/ngx_stream_return_module.c [new file with mode: 0644]
nginx/src/stream/ngx_stream_script.c [new file with mode: 0644]
nginx/src/stream/ngx_stream_script.h [new file with mode: 0644]
nginx/src/stream/ngx_stream_split_clients_module.c [new file with mode: 0644]
nginx/src/stream/ngx_stream_ssl_module.c [new file with mode: 0644]
nginx/src/stream/ngx_stream_ssl_module.h [new file with mode: 0644]
nginx/src/stream/ngx_stream_ssl_preread_module.c [new file with mode: 0644]
nginx/src/stream/ngx_stream_upstream.c [new file with mode: 0644]
nginx/src/stream/ngx_stream_upstream.h [new file with mode: 0644]
nginx/src/stream/ngx_stream_upstream_hash_module.c [new file with mode: 0644]
nginx/src/stream/ngx_stream_upstream_least_conn_module.c [new file with mode: 0644]
nginx/src/stream/ngx_stream_upstream_round_robin.c [new file with mode: 0644]
nginx/src/stream/ngx_stream_upstream_round_robin.h [new file with mode: 0644]
nginx/src/stream/ngx_stream_upstream_zone_module.c [new file with mode: 0644]
nginx/src/stream/ngx_stream_variables.c [new file with mode: 0644]
nginx/src/stream/ngx_stream_variables.h [new file with mode: 0644]
nginx/src/stream/ngx_stream_write_filter_module.c [new file with mode: 0644]
nginx_patches/0001-ngxvcl.patch [new file with mode: 0644]
vpp_patches/common/0001-session-pinning.patch [new file with mode: 0644]
vpp_patches/ldp/0001-LDP-remove-lock.patch [new file with mode: 0644]
vpp_patches/vcl/0001-ngxvcl-api.patch [new file with mode: 0644]

diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..5c1c892
--- /dev/null
+++ b/README.md
@@ -0,0 +1,154 @@
+# 1 Introduction
+This repository is to provide an optimized NGINX based on VPP host stack.
+We provide two ways of VPP host stack integration, i.e. LDP and VCL.
+LDP is basically un-modified NGINX with VPP via LD_PRELOAD, while VCL NGINX is
+to integrate VPP host stack directly with NGINX code change.
+
+# 2 Repository Layout
+**configs**: configuration files for VPP, NGINX and VCL
+
+**nginx**: NGINX/VCL code library based on version 1.14.2
+
+**nginx_patches**: VCL patches for NGINX
+
+**vpp_patches**: lock-free LDP and pinned-VPP patches
+
+# 3 Building on top of distinct patches
+
+## 3.0 Basic patch
+
+### 3.0.1 Application Worker Partition
+**Functionality**
+
+For both VCL and LDP, it requires to add a patch to app worker optimization.
+
+**Instructions**
+
+```bash
+$ cd /path/to/vpp
+$ patch -p1 < /path/to/this/repo/vpp_patches/common/0001-session-pinning.patch
+$ make build && make build-release
+```
+
+## 3.1 VCL NGINX
+
+### 3.1.1 VPP side setting
+VCL NGINX integration requires a patch inside VPP first.
+
+```bash
+$ cd /path/to/vpp
+$ patch -p1 < /path/to/this/repo/vpp_patches/vcl/0001-ngxvcl-api.patch
+$ make build && make build-release
+```
+
+### 3.1.2 NGINX side setting
+
+Our repo has provided the modified code based on NGINX 1.14.2. You can either directly use our modified Nginx version or patch NGINX 1.14.2 by the provided patch.
+
+```bash
+$ cd /path/to/this/repo
+$ cd nginx
+```
+
+or
+
+```bash
+$ cd path/to/your/own/nginx-1.14.2
+$ patch -p2 < path/to/this/repo/nginx_patches/0001-ngxvcl.patch
+```
+
+Now the original NGINX code has been modified to VCL-supporting code.
+
+Then you can configure and build NGINX.
+
+```bash
+$ ./configure --with-vcl --vpp-lib-path=/path/to/vpp/build-root/install-vpp-native/vpp/lib --vpp-src-path=/path/to/vpp/src
+$ sudo make install
+```
+
+### 3.1.3 Run NgxVCL
+- Run VPP first
+
+  - Refer to startup.conf provided in "configs" to start VPP. (learn how to use startup.conf in section 4.1.1)
+
+  ```bash
+  ./vpp -c /path/to/startup.conf
+  ```
+
+  Start NGINX
+
+  - refer to vcl.conf and nginx.conf provided under "configs"
+
+  ```
+  # export VCL_CONFIG=/path/to/vcl.conf
+  # export LD_LIBRARY_PATH=/path/to/vpp/build-root/install-vpp-native/vpp/lib
+  # /usr/local/nginx/sbin/nginx -c /path/to/nginx.conf
+  ```
+
+## 3.2 LDP NGINX
+
+### 3.2.1 Removing VLS Locks
+**Functionality**
+
+This patch removes VLS session locks for saving approximately 100% CPU cycles one core of applications, especially for the application which is CPU-intensive.
+
+**Instructions**
+
+You may need root privilege.
+
+```bash
+$ cd /path/to/vpp
+$ patch -p1 < /path/to/this/repo/vpp_patches/ldp/0001-LDP-remove-lock.patch
+$ make build && make build-release
+```
+**Start NGINX**
+
+```bash
+$ export VCL_CONFIG=path/to/vcl.conf
+$ LD_PRELOAD=path/to/vpp/build-root/install-vpp-native/vpp/lib/libvcl_ldpreload.so /usr/local/nginx/sbin/nginx -c path/to/nginx.conf
+```
+
+## 3.3 Enable VPP TLS
+
+### 3.3.1 Enable VPP TLS for VCL NGINX
+If TLS is supproted, then before you run VCL NGINX, export following environment variables.
+
+```bash
+$ export NGXVCL_TLS_ON=1
+$ export NGXVCL_TLS_CERT=/path/to/this/repo/configs/tls-test-cert
+$ export NGXVCL_TLS_KEY=/path/to/this/repo/configs/tls-test-key
+```
+
+### 3.3.2 Enable VPP TLS for LDP NGINX
+
+Before you run LDP NGINX, export following environment variables.
+
+```bash
+$ export LDP_TRANSPARENT_TLS=1
+$ export LDP_TLS_CERT_FILE=/path/to/this/repo/configs/tls-test-cert
+$ export LDP_TLS_KEY_FILE=/path/to/this/repo/configs/tls-test-key
+```
+
+# 4 Instances
+
+## 4.1 NGINX + VPP
+
+### 4.1.1 Used startup.conf
+Inside startup.conf, you need to configure the following several directives:
+- pci-address: the pci address of NIC. Refer to [dpdk_bind_driver](http://doc.dpdk.org/guides/linux_gsg/linux_drivers.html) to bind NIC to the vfio-pci driver at first.
+- socket-name path/to/vpp-api.sock: socket which would be used to connect VPP and NGINX. This sock should also be configured in vcl.conf.
+
+### 4.1.2 Notes
+Please ensure that the processes of NGINX and VPP run on same NUMA node as the used NIC.
+
+**VPP**
+
+The core number could be selected via two arguments:
+
+```bash
+main-core 0             ##set vpp master on core 0
+corelist-workers 1-4    ##set the four vpp worker threads on core 1-4,
+                        ##If start 8 VPP worker, the value should be 1-8 or x-(x+7)
+num-rx-queues 4         ##Assign each VPP worker one rx queue.
+```
+
diff --git a/configs/README b/configs/README
new file mode 100644 (file)
index 0000000..3f1f218
--- /dev/null
@@ -0,0 +1,6 @@
+VPP uses startup.conf and please remember to modify 'exec /path/to/configs/vppenvset' to your own setting.
+
+As for NGINX, if you just want to test small files, nginx.conf is a good choice. It supports 64B, 1KB and 2KB JSON files embedded in nginx.conf, meaning NGINX sends files from memory rather than disk to avoid file read cost.
+
+vcl.conf is used for both LDP NGINX and VCL NGINX. Note the rx-fifo-size and tx-fifo-size should be larger than file size you want to test. 'rx-fifo-size 5000' means the size of rx fifo of an app session is 5000 bytes.
+
diff --git a/configs/nginx.conf b/configs/nginx.conf
new file mode 100644 (file)
index 0000000..0466aeb
--- /dev/null
@@ -0,0 +1,80 @@
+user  xxx;
+worker_processes 1;
+
+master_process on;
+daemon off;
+
+worker_rlimit_nofile 10240;
+events {
+    use epoll;
+    worker_connections  10240;
+    accept_mutex       off;
+    multi_accept       off;
+}
+
+
+http {
+    access_log    off;
+    include       mime.types;
+    default_type  application/octet-stream;
+    sendfile        on;
+
+
+    ##RPS test
+    keepalive_timeout 300s;
+    keepalive_requests 1000000;
+
+    server {
+        listen 8000;
+        root   /path/to/root/;
+        index  index.html index.htm;
+        location /return {
+                return 204;
+           }
+        location /64B.json {
+                return 200 '{"status":"success","result":"this is a 64Byte json file test!"}';
+          }
+        location /1KB.json{
+               return 201 '{"status":"success","result":"\
+                           Nanchang, which was the capital of Yuzhang Prefecture during the HanDynasty, \
+                           now falls under the jurisdiction of Hongzhou. It straddles the borderof the \
+                           influence of the Ye and Zhen constellations , and is adjacent to theHeng \
+                           and the Lu mountains . The three rivers enfold it like the frontpart \
+                           of a garment and the five lakes encircle it like a girdle. Itcontrols \
+                           the savage Jing area and connects Ou and Yue, and itsproducts are \
+                           nature’s jewels. The radiance of its legendary sword shootsdirectly upward \
+                           between the constellations Niu and Dou. Its talented peopleare outstanding,\
+                           and the spirit of intelligence pervades the place. This wasthe place where Xu \
+                           Ru spent the night on his visit to Chen Fan (10). The mightyHongzhou spreads \
+                           out immensely amid the fog, and the intellectual luminariesare as numerous as\
+                           meteors chasing one another. \
+                           "}';
+          }
+        location /2KB.json{
+               return 202 '{"status":"success","result":"\
+                           Hello from  NGINX, 2KB test\
+                           Nanchang, which was the capital of Yuzhang Prefecture during the HanDynasty, \
+                           now falls under the jurisdiction of Hongzhou. It straddles the borderof the \
+                           influence of the Ye and Zhen constellations , and is adjacent to theHeng \
+                           and the Lu mountains . The three rivers enfold it like the frontpart \
+                           of a garment and the five lakes encircle it like a girdle. Itcontrols \
+                           the savage Jing area and connects Ou and Yue, and itsproducts are \
+                           nature’s jewels. The radiance of its legendary sword shootsdirectly upward \
+                           between the constellations Niu and Dou. Its talented peopleare outstanding,\
+                           and the spirit of intelligence pervades the place. This wasthe place where Xu \
+                           Ru spent the night on his visit to Chen Fan (10). The mightyHongzhou spreads \
+                           out immensely amid the fog, and the intellectual luminariesare as numerous as\
+                           meteors chasing one another.\
+                           of a garment and the five lakes encircle it like a girdle. Itcontrols \
+                           the savage Jing area and connects Ou and Yue, and itsproducts are \
+                           nature’s jewels. The radiance of its legendary sword shootsdirectly upward \
+                           between the constellations Niu and Dou. Its talented peopleare outstanding,\
+                           and the spirit of intelligence pervades the place. This wasthe place where Xu \
+                           Ru spent the night on his visit to Chen Fan (10). The mightyHongzhou spreads \
+                           out immensely amid the fog, and the intellectual luminariesare as numerous as\
+                           meteors chasing one another. \
+                           "}';
+          }
+
+        }
+    }
diff --git a/configs/startup.conf b/configs/startup.conf
new file mode 100644 (file)
index 0000000..b9260b4
--- /dev/null
@@ -0,0 +1,45 @@
+unix {
+  interactive
+  log /var/log/vpp/vpp.log
+  full-coredump
+  cli-listen /run/vpp/cli-vpp1.sock
+  exec /path/to/configs/vppenvset
+}
+
+api-trace {
+  on
+}
+
+session { 
+       evt_qs_memfd_seg  
+}
+
+socksvr {
+       socket-name /path/to/vpp-api.sock
+}
+
+cpu {
+       main-core 0
+        ##Start 4 VPP workers
+       #corelist-workers 1-4
+        ##start 1 VPP worker  
+        corelist-workers 1
+}
+
+tcp {cc-algo cubic}
+
+dpdk {
+       dev <pci-address> {
+               name eth0
+               
+                ##the value of num-rx-queues should be the same as the number
+               ##of VPP worker
+               ##If 1
+               num-rx-queues 1
+               ##If 4
+               #num-rx-queues 4
+                num-rx-desc 512
+                num-tx-desc 512
+
+       }
+}
diff --git a/configs/vcl.conf b/configs/vcl.conf
new file mode 100644 (file)
index 0000000..c6e7da9
--- /dev/null
@@ -0,0 +1,17 @@
+vcl {
+##File size is no smaller than 1MB
+#rx-fifo-size 2200000
+#tx-fifo-size 2200000
+##File size is smaller than 1MB
+rx-fifo-size 5000
+tx-fifo-size 5000
+app-scope-local
+app-scope-global
+api-socket-name /path/to/vpp-api.sock
+event-queue-size 1000000
+segment-size 1024000000
+add-segment-size 1024000000
+vpp-api-q-length 65536
+max-workers 36
+#use-mq-eventfd
+}
diff --git a/configs/vppenvset b/configs/vppenvset
new file mode 100644 (file)
index 0000000..286434d
--- /dev/null
@@ -0,0 +1,3 @@
+set int state eth0 up
+set int ip address eth0 10.0.0.1/24
+session enable
diff --git a/nginx/CHANGES b/nginx/CHANGES
new file mode 100644 (file)
index 0000000..597c270
--- /dev/null
@@ -0,0 +1,8115 @@
+
+Changes with nginx 1.14.2                                        04 Dec 2018
+
+    *) Bugfix: nginx could not be built by gcc 8.1.
+
+    *) Bugfix: nginx could not be built on Fedora 28 Linux.
+
+    *) Bugfix: in handling of client addresses when using unix domain listen
+       sockets to work with datagrams on Linux.
+
+    *) Change: the logging level of the "http request", "https proxy
+       request", "unsupported protocol", "version too low", "no suitable key
+       share", and "no suitable signature algorithm" SSL errors has been
+       lowered from "crit" to "info".
+
+    *) Bugfix: when using OpenSSL 1.1.0 or newer it was not possible to
+       switch off "ssl_prefer_server_ciphers" in a virtual server if it was
+       switched on in the default server.
+
+    *) Bugfix: nginx could not be built with LibreSSL 2.8.0.
+
+    *) Bugfix: if nginx was built with OpenSSL 1.1.0 and used with OpenSSL
+       1.1.1, the TLS 1.3 protocol was always enabled.
+
+    *) Bugfix: sending a disk-buffered request body to a gRPC backend might
+       fail.
+
+    *) Bugfix: connections with some gRPC backends might not be cached when
+       using the "keepalive" directive.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       ngx_http_mp4_module was used on 32-bit platforms.
+
+
+Changes with nginx 1.14.1                                        06 Nov 2018
+
+    *) Security: when using HTTP/2 a client might cause excessive memory
+       consumption (CVE-2018-16843) and CPU usage (CVE-2018-16844).
+
+    *) Security: processing of a specially crafted mp4 file with the
+       ngx_http_mp4_module might result in worker process memory disclosure
+       (CVE-2018-16845).
+
+    *) Bugfix: working with gRPC backends might result in excessive memory
+       consumption.
+
+
+Changes with nginx 1.14.0                                        17 Apr 2018
+
+    *) 1.14.x stable branch.
+
+
+Changes with nginx 1.13.12                                       10 Apr 2018
+
+    *) Bugfix: connections with gRPC backends might be closed unexpectedly
+       when returning a large response.
+
+
+Changes with nginx 1.13.11                                       03 Apr 2018
+
+    *) Feature: the "proxy_protocol" parameter of the "listen" directive now
+       supports the PROXY protocol version 2.
+
+    *) Bugfix: nginx could not be built with OpenSSL 1.1.1 statically on
+       Linux.
+
+    *) Bugfix: in the "http_404", "http_500", etc. parameters of the
+       "proxy_next_upstream" directive.
+
+
+Changes with nginx 1.13.10                                       20 Mar 2018
+
+    *) Feature: the "set" parameter of the "include" SSI directive now
+       allows writing arbitrary responses to a variable; the
+       "subrequest_output_buffer_size" directive defines maximum response
+       size.
+
+    *) Feature: now nginx uses clock_gettime(CLOCK_MONOTONIC) if available,
+       to avoid timeouts being incorrectly triggered on system time changes.
+
+    *) Feature: the "escape=none" parameter of the "log_format" directive.
+       Thanks to Johannes Baiter and Calin Don.
+
+    *) Feature: the $ssl_preread_alpn_protocols variable in the
+       ngx_stream_ssl_preread_module.
+
+    *) Feature: the ngx_http_grpc_module.
+
+    *) Bugfix: in memory allocation error handling in the "geo" directive.
+
+    *) Bugfix: when using variables in the "auth_basic_user_file" directive
+       a null character might appear in logs.
+       Thanks to Vadim Filimonov.
+
+
+Changes with nginx 1.13.9                                        20 Feb 2018
+
+    *) Feature: HTTP/2 server push support; the "http2_push" and
+       "http2_push_preload" directives.
+
+    *) Bugfix: "header already sent" alerts might appear in logs when using
+       cache; the bug had appeared in 1.9.13.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       "ssl_verify_client" directive was used and no SSL certificate was
+       specified in a virtual server.
+
+    *) Bugfix: in the ngx_http_v2_module.
+
+    *) Bugfix: in the ngx_http_dav_module.
+
+
+Changes with nginx 1.13.8                                        26 Dec 2017
+
+    *) Feature: now nginx automatically preserves the CAP_NET_RAW capability
+       in worker processes when using the "transparent" parameter of the
+       "proxy_bind", "fastcgi_bind", "memcached_bind", "scgi_bind", and
+       "uwsgi_bind" directives.
+
+    *) Feature: improved CPU cache line size detection.
+       Thanks to Debayan Ghosh.
+
+    *) Feature: new directives in vim syntax highlighting scripts.
+       Thanks to Gena Makhomed.
+
+    *) Bugfix: binary upgrade refused to work if nginx was re-parented to a
+       process with PID different from 1 after its parent process has
+       finished.
+
+    *) Bugfix: the ngx_http_autoindex_module incorrectly handled requests
+       with bodies.
+
+    *) Bugfix: in the "proxy_limit_rate" directive when used with the
+       "keepalive" directive.
+
+    *) Bugfix: some parts of a response might be buffered when using
+       "proxy_buffering off" if the client connection used SSL.
+       Thanks to Patryk Lesiewicz.
+
+    *) Bugfix: in the "proxy_cache_background_update" directive.
+
+    *) Bugfix: it was not possible to start a parameter with a variable in
+       the "${name}" form with the name in curly brackets without enclosing
+       the parameter into single or double quotes.
+
+
+Changes with nginx 1.13.7                                        21 Nov 2017
+
+    *) Bugfix: in the $upstream_status variable.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if a
+       backend returned a "101 Switching Protocols" response to a
+       subrequest.
+
+    *) Bugfix: a segmentation fault occurred in a master process if a shared
+       memory zone size was changed during a reconfiguration and the
+       reconfiguration failed.
+
+    *) Bugfix: in the ngx_http_fastcgi_module.
+
+    *) Bugfix: nginx returned the 500 error if parameters without variables
+       were specified in the "xslt_stylesheet" directive.
+
+    *) Workaround: "gzip filter failed to use preallocated memory" alerts
+       appeared in logs when using a zlib library variant from Intel.
+
+    *) Bugfix: the "worker_shutdown_timeout" directive did not work when
+       using mail proxy and when proxying WebSocket connections.
+
+
+Changes with nginx 1.13.6                                        10 Oct 2017
+
+    *) Bugfix: switching to the next upstream server in the stream module
+       did not work when using the "ssl_preread" directive.
+
+    *) Bugfix: in the ngx_http_v2_module.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: nginx did not support dates after the year 2038 on 32-bit
+       platforms with 64-bit time_t.
+
+    *) Bugfix: in handling of dates prior to the year 1970 and after the
+       year 10000.
+
+    *) Bugfix: in the stream module timeouts waiting for UDP datagrams from
+       upstream servers were not logged or logged at the "info" level
+       instead of "error".
+
+    *) Bugfix: when using HTTP/2 nginx might return the 400 response without
+       logging the reason.
+
+    *) Bugfix: in processing of corrupted cache files.
+
+    *) Bugfix: cache control headers were ignored when caching errors
+       intercepted by error_page.
+
+    *) Bugfix: when using HTTP/2 client request body might be corrupted.
+
+    *) Bugfix: in handling of client addresses when using unix domain
+       sockets.
+
+    *) Bugfix: nginx hogged CPU when using the "hash ... consistent"
+       directive in the upstream block if large weights were used and all or
+       most of the servers were unavailable.
+
+
+Changes with nginx 1.13.5                                        05 Sep 2017
+
+    *) Feature: the $ssl_client_escaped_cert variable.
+
+    *) Bugfix: the "ssl_session_ticket_key" directive and the "include"
+       parameter of the "geo" directive did not work on Windows.
+
+    *) Bugfix: incorrect response length was returned on 32-bit platforms
+       when requesting more than 4 gigabytes with multiple ranges.
+
+    *) Bugfix: the "expires modified" directive and processing of the
+       "If-Range" request header line did not use the response last
+       modification time if proxying without caching was used.
+
+
+Changes with nginx 1.13.4                                        08 Aug 2017
+
+    *) Feature: the ngx_http_mirror_module.
+
+    *) Bugfix: client connections might be dropped during configuration
+       testing when using the "reuseport" parameter of the "listen"
+       directive on Linux.
+
+    *) Bugfix: request body might not be available in subrequests if it was
+       saved to a file and proxying was used.
+
+    *) Bugfix: cleaning cache based on the "max_size" parameter did not work
+       on Windows.
+
+    *) Bugfix: any shared memory allocation required 4096 bytes on Windows.
+
+    *) Bugfix: nginx worker might be terminated abnormally when using the
+       "zone" directive inside the "upstream" block on Windows.
+
+
+Changes with nginx 1.13.3                                        11 Jul 2017
+
+    *) Security: a specially crafted request might result in an integer
+       overflow and incorrect processing of ranges in the range filter,
+       potentially resulting in sensitive information leak (CVE-2017-7529).
+
+
+Changes with nginx 1.13.2                                        27 Jun 2017
+
+    *) Change: nginx now returns 200 instead of 416 when a range starting
+       with 0 is requested from an empty file.
+
+    *) Feature: the "add_trailer" directive.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: nginx could not be built on Cygwin and NetBSD; the bug had
+       appeared in 1.13.0.
+
+    *) Bugfix: nginx could not be built under MSYS2 / MinGW 64-bit.
+       Thanks to Orgad Shaneh.
+
+    *) Bugfix: a segmentation fault might occur in a worker process when
+       using SSI with many includes and proxy_pass with variables.
+
+    *) Bugfix: in the ngx_http_v2_module.
+       Thanks to Piotr Sikora.
+
+
+Changes with nginx 1.13.1                                        30 May 2017
+
+    *) Feature: now a hostname can be used as the "set_real_ip_from"
+       directive parameter.
+
+    *) Feature: vim syntax highlighting scripts improvements.
+
+    *) Feature: the "worker_cpu_affinity" directive now works on DragonFly
+       BSD.
+       Thanks to Sepherosa Ziehau.
+
+    *) Bugfix: SSL renegotiation on backend connections did not work when
+       using OpenSSL before 1.1.0.
+
+    *) Workaround: nginx could not be built with Oracle Developer Studio
+       12.5.
+
+    *) Workaround: now cache manager ignores long locked cache entries when
+       cleaning cache based on the "max_size" parameter.
+
+    *) Bugfix: client SSL connections were immediately closed if deferred
+       accept and the "proxy_protocol" parameter of the "listen" directive
+       were used.
+
+    *) Bugfix: in the "proxy_cache_background_update" directive.
+
+    *) Workaround: now the "tcp_nodelay" directive sets the TCP_NODELAY
+       option before an SSL handshake.
+
+
+Changes with nginx 1.13.0                                        25 Apr 2017
+
+    *) Change: SSL renegotiation is now allowed on backend connections.
+
+    *) Feature: the "rcvbuf" and "sndbuf" parameters of the "listen"
+       directives of the mail proxy and stream modules.
+
+    *) Feature: the "return" and "error_page" directives can now be used to
+       return 308 redirections.
+       Thanks to Simon Leblanc.
+
+    *) Feature: the "TLSv1.3" parameter of the "ssl_protocols" directive.
+
+    *) Feature: when logging signals nginx now logs PID of the process which
+       sent the signal.
+
+    *) Bugfix: in memory allocation error handling.
+
+    *) Bugfix: if a server in the stream module listened on a wildcard
+       address, the source address of a response UDP datagram could differ
+       from the original datagram destination address.
+
+
+Changes with nginx 1.11.13                                       04 Apr 2017
+
+    *) Feature: the "http_429" parameter of the "proxy_next_upstream",
+       "fastcgi_next_upstream", "scgi_next_upstream", and
+       "uwsgi_next_upstream" directives.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: in memory allocation error handling.
+
+    *) Bugfix: requests might hang when using the "sendfile" and
+       "timer_resolution" directives on Linux.
+
+    *) Bugfix: requests might hang when using the "sendfile" and "aio_write"
+       directives with subrequests.
+
+    *) Bugfix: in the ngx_http_v2_module.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: a segmentation fault might occur in a worker process when
+       using HTTP/2.
+
+    *) Bugfix: requests might hang when using the "limit_rate",
+       "sendfile_max_chunk", "limit_req" directives, or the $r->sleep()
+       embedded perl method with subrequests.
+
+    *) Bugfix: in the ngx_http_slice_module.
+
+
+Changes with nginx 1.11.12                                       24 Mar 2017
+
+    *) Bugfix: nginx might hog CPU; the bug had appeared in 1.11.11.
+
+
+Changes with nginx 1.11.11                                       21 Mar 2017
+
+    *) Feature: the "worker_shutdown_timeout" directive.
+
+    *) Feature: vim syntax highlighting scripts improvements.
+       Thanks to Wei-Ko Kao.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       $limit_rate variable was set to an empty string.
+
+    *) Bugfix: the "proxy_cache_background_update",
+       "fastcgi_cache_background_update", "scgi_cache_background_update",
+       and "uwsgi_cache_background_update" directives might work incorrectly
+       if the "if" directive was used.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if
+       number of large_client_header_buffers in a virtual server was
+       different from the one in the default server.
+
+    *) Bugfix: in the mail proxy server.
+
+
+Changes with nginx 1.11.10                                       14 Feb 2017
+
+    *) Change: cache header format has been changed, previously cached
+       responses will be invalidated.
+
+    *) Feature: support of "stale-while-revalidate" and "stale-if-error"
+       extensions in the "Cache-Control" backend response header line.
+
+    *) Feature: the "proxy_cache_background_update",
+       "fastcgi_cache_background_update", "scgi_cache_background_update",
+       and "uwsgi_cache_background_update" directives.
+
+    *) Feature: nginx is now able to cache responses with the "Vary" header
+       line up to 128 characters long (instead of 42 characters in previous
+       versions).
+
+    *) Feature: the "build" parameter of the "server_tokens" directive.
+       Thanks to Tom Thorogood.
+
+    *) Bugfix: "[crit] SSL_write() failed" messages might appear in logs
+       when handling requests with the "Expect: 100-continue" request header
+       line.
+
+    *) Bugfix: the ngx_http_slice_module did not work in named locations.
+
+    *) Bugfix: a segmentation fault might occur in a worker process when
+       using AIO after an "X-Accel-Redirect" redirection.
+
+    *) Bugfix: reduced memory consumption for long-lived requests using
+       gzipping.
+
+
+Changes with nginx 1.11.9                                        24 Jan 2017
+
+    *) Bugfix: nginx might hog CPU when using the stream module; the bug had
+       appeared in 1.11.5.
+
+    *) Bugfix: EXTERNAL authentication mechanism in mail proxy was accepted
+       even if it was not enabled in the configuration.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       "ssl_verify_client" directive of the stream module was used.
+
+    *) Bugfix: the "ssl_verify_client" directive of the stream module might
+       not work.
+
+    *) Bugfix: closing keepalive connections due to no free worker
+       connections might be too aggressive.
+       Thanks to Joel Cunningham.
+
+    *) Bugfix: an incorrect response might be returned when using the
+       "sendfile" directive on FreeBSD and macOS; the bug had appeared in
+       1.7.8.
+
+    *) Bugfix: a truncated response might be stored in cache when using the
+       "aio_write" directive.
+
+    *) Bugfix: a socket leak might occur when using the "aio_write"
+       directive.
+
+
+Changes with nginx 1.11.8                                        27 Dec 2016
+
+    *) Feature: the "absolute_redirect" directive.
+
+    *) Feature: the "escape" parameter of the "log_format" directive.
+
+    *) Feature: client SSL certificates verification in the stream module.
+
+    *) Feature: the "ssl_session_ticket_key" directive supports AES256
+       encryption of TLS session tickets when used with 80-byte keys.
+
+    *) Feature: vim-commentary support in vim scripts.
+       Thanks to Armin Grodon.
+
+    *) Bugfix: recursion when evaluating variables was not limited.
+
+    *) Bugfix: in the ngx_stream_ssl_preread_module.
+
+    *) Bugfix: if a server in an upstream in the stream module failed, it
+       was considered alive only when a test connection sent to it after
+       fail_timeout was closed; now a successfully established connection is
+       enough.
+
+    *) Bugfix: nginx/Windows could not be built with 64-bit Visual Studio.
+
+    *) Bugfix: nginx/Windows could not be built with OpenSSL 1.1.0.
+
+
+Changes with nginx 1.11.7                                        13 Dec 2016
+
+    *) Change: now in case of a client certificate verification error the
+       $ssl_client_verify variable contains a string with the failure
+       reason, for example, "FAILED:certificate has expired".
+
+    *) Feature: the $ssl_ciphers, $ssl_curves, $ssl_client_v_start,
+       $ssl_client_v_end, and $ssl_client_v_remain variables.
+
+    *) Feature: the "volatile" parameter of the "map" directive.
+
+    *) Bugfix: dependencies specified for a module were ignored while
+       building dynamic modules.
+
+    *) Bugfix: when using HTTP/2 and the "limit_req" or "auth_request"
+       directives client request body might be corrupted; the bug had
+       appeared in 1.11.0.
+
+    *) Bugfix: a segmentation fault might occur in a worker process when
+       using HTTP/2; the bug had appeared in 1.11.3.
+
+    *) Bugfix: in the ngx_http_mp4_module.
+       Thanks to Congcong Hu.
+
+    *) Bugfix: in the ngx_http_perl_module.
+
+
+Changes with nginx 1.11.6                                        15 Nov 2016
+
+    *) Change: format of the $ssl_client_s_dn and $ssl_client_i_dn variables
+       has been changed to follow RFC 2253 (RFC 4514); values in the old
+       format are available in the $ssl_client_s_dn_legacy and
+       $ssl_client_i_dn_legacy variables.
+
+    *) Change: when storing temporary files in a cache directory they will
+       be stored in the same subdirectories as corresponding cache files
+       instead of a separate subdirectory for temporary files.
+
+    *) Feature: EXTERNAL authentication mechanism support in mail proxy.
+       Thanks to Robert Norris.
+
+    *) Feature: WebP support in the ngx_http_image_filter_module.
+
+    *) Feature: variables support in the "proxy_method" directive.
+       Thanks to Dmitry Lazurkin.
+
+    *) Feature: the "http2_max_requests" directive in the
+       ngx_http_v2_module.
+
+    *) Feature: the "proxy_cache_max_range_offset",
+       "fastcgi_cache_max_range_offset", "scgi_cache_max_range_offset", and
+       "uwsgi_cache_max_range_offset" directives.
+
+    *) Bugfix: graceful shutdown of old worker processes might require
+       infinite time when using HTTP/2.
+
+    *) Bugfix: in the ngx_http_mp4_module.
+
+    *) Bugfix: "ignore long locked inactive cache entry" alerts might appear
+       in logs when proxying WebSocket connections with caching enabled.
+
+    *) Bugfix: nginx did not write anything to log and returned a response
+       with code 502 instead of 504 when a timeout occurred during an SSL
+       handshake to a backend.
+
+
+Changes with nginx 1.11.5                                        11 Oct 2016
+
+    *) Change: the --with-ipv6 configure option was removed, now IPv6
+       support is configured automatically.
+
+    *) Change: now if there are no available servers in an upstream, nginx
+       will not reset number of failures of all servers as it previously
+       did, but will wait for fail_timeout to expire.
+
+    *) Feature: the ngx_stream_ssl_preread_module.
+
+    *) Feature: the "server" directive in the "upstream" context supports
+       the "max_conns" parameter.
+
+    *) Feature: the --with-compat configure option.
+
+    *) Feature: "manager_files", "manager_threshold", and "manager_sleep"
+       parameters of the "proxy_cache_path", "fastcgi_cache_path",
+       "scgi_cache_path", and "uwsgi_cache_path" directives.
+
+    *) Bugfix: flags passed by the --with-ld-opt configure option were not
+       used while building perl module.
+
+    *) Bugfix: in the "add_after_body" directive when used with the
+       "sub_filter" directive.
+
+    *) Bugfix: in the $realip_remote_addr variable.
+
+    *) Bugfix: the "dav_access", "proxy_store_access",
+       "fastcgi_store_access", "scgi_store_access", and "uwsgi_store_access"
+       directives ignored permissions specified for user.
+
+    *) Bugfix: unix domain listen sockets might not be inherited during
+       binary upgrade on Linux.
+
+    *) Bugfix: nginx returned the 400 response on requests with the "-"
+       character in the HTTP method.
+
+
+Changes with nginx 1.11.4                                        13 Sep 2016
+
+    *) Feature: the $upstream_bytes_received variable.
+
+    *) Feature: the $bytes_received, $session_time, $protocol, $status,
+       $upstream_addr, $upstream_bytes_sent, $upstream_bytes_received,
+       $upstream_connect_time, $upstream_first_byte_time, and
+       $upstream_session_time variables in the stream module.
+
+    *) Feature: the ngx_stream_log_module.
+
+    *) Feature: the "proxy_protocol" parameter of the "listen" directive,
+       the $proxy_protocol_addr and $proxy_protocol_port variables in the
+       stream module.
+
+    *) Feature: the ngx_stream_realip_module.
+
+    *) Bugfix: nginx could not be built with the stream module and the
+       ngx_http_ssl_module, but without ngx_stream_ssl_module; the bug had
+       appeared in 1.11.3.
+
+    *) Feature: the IP_BIND_ADDRESS_NO_PORT socket option was not used; the
+       bug had appeared in 1.11.2.
+
+    *) Bugfix: in the "ranges" parameter of the "geo" directive.
+
+    *) Bugfix: an incorrect response might be returned when using the "aio
+       threads" and "sendfile" directives; the bug had appeared in 1.9.13.
+
+
+Changes with nginx 1.11.3                                        26 Jul 2016
+
+    *) Change: now the "accept_mutex" directive is turned off by default.
+
+    *) Feature: now nginx uses EPOLLEXCLUSIVE on Linux.
+
+    *) Feature: the ngx_stream_geo_module.
+
+    *) Feature: the ngx_stream_geoip_module.
+
+    *) Feature: the ngx_stream_split_clients_module.
+
+    *) Feature: variables support in the "proxy_pass" and "proxy_ssl_name"
+       directives in the stream module.
+
+    *) Bugfix: socket leak when using HTTP/2.
+
+    *) Bugfix: in configure tests.
+       Thanks to Piotr Sikora.
+
+
+Changes with nginx 1.11.2                                        05 Jul 2016
+
+    *) Change: now nginx always uses internal MD5 and SHA1 implementations;
+       the --with-md5 and --with-sha1 configure options were canceled.
+
+    *) Feature: variables support in the stream module.
+
+    *) Feature: the ngx_stream_map_module.
+
+    *) Feature: the ngx_stream_return_module.
+
+    *) Feature: a port can be specified in the "proxy_bind", "fastcgi_bind",
+       "memcached_bind", "scgi_bind", and "uwsgi_bind" directives.
+
+    *) Feature: now nginx uses the IP_BIND_ADDRESS_NO_PORT socket option
+       when available.
+
+    *) Bugfix: a segmentation fault might occur in a worker process when
+       using HTTP/2 and the "proxy_request_buffering" directive.
+
+    *) Bugfix: the "Content-Length" request header line was always added to
+       requests passed to backends, including requests without body, when
+       using HTTP/2.
+
+    *) Bugfix: "http request count is zero" alerts might appear in logs when
+       using HTTP/2.
+
+    *) Bugfix: unnecessary buffering might occur when using the "sub_filter"
+       directive; the issue had appeared in 1.9.4.
+
+
+Changes with nginx 1.11.1                                        31 May 2016
+
+    *) Security: a segmentation fault might occur in a worker process while
+       writing a specially crafted request body to a temporary file
+       (CVE-2016-4450); the bug had appeared in 1.3.9.
+
+
+Changes with nginx 1.11.0                                        24 May 2016
+
+    *) Feature: the "transparent" parameter of the "proxy_bind",
+       "fastcgi_bind", "memcached_bind", "scgi_bind", and "uwsgi_bind"
+       directives.
+
+    *) Feature: the $request_id variable.
+
+    *) Feature: the "map" directive supports combinations of multiple
+       variables as resulting values.
+
+    *) Feature: now nginx checks if EPOLLRDHUP events are supported by
+       kernel, and optimizes connection handling accordingly if the "epoll"
+       method is used.
+
+    *) Feature: the "ssl_certificate" and "ssl_certificate_key" directives
+       can be specified multiple times to load certificates of different
+       types (for example, RSA and ECDSA).
+
+    *) Feature: the "ssl_ecdh_curve" directive now allows specifying a list
+       of curves when using OpenSSL 1.0.2 or newer; by default a list built
+       into OpenSSL is used.
+
+    *) Change: to use DHE ciphers it is now required to specify parameters
+       using the "ssl_dhparam" directive.
+
+    *) Feature: the $proxy_protocol_port variable.
+
+    *) Feature: the $realip_remote_port variable in the
+       ngx_http_realip_module.
+
+    *) Feature: the ngx_http_realip_module is now able to set the client
+       port in addition to the address.
+
+    *) Change: the "421 Misdirected Request" response now used when
+       rejecting requests to a virtual server different from one negotiated
+       during an SSL handshake; this improves interoperability with some
+       HTTP/2 clients when using client certificates.
+
+    *) Change: HTTP/2 clients can now start sending request body
+       immediately; the "http2_body_preread_size" directive controls size of
+       the buffer used before nginx will start reading client request body.
+
+    *) Bugfix: cached error responses were not updated when using the
+       "proxy_cache_bypass" directive.
+
+
+Changes with nginx 1.9.15                                        19 Apr 2016
+
+    *) Bugfix: "recv() failed" errors might occur when using HHVM as a
+       FastCGI server.
+
+    *) Bugfix: when using HTTP/2 and the "limit_req" or "auth_request"
+       directives a timeout or a "client violated flow control" error might
+       occur while reading client request body; the bug had appeared in
+       1.9.14.
+
+    *) Workaround: a response might not be shown by some browsers if HTTP/2
+       was used and client request body was not fully read; the bug had
+       appeared in 1.9.14.
+
+    *) Bugfix: connections might hang when using the "aio threads"
+       directive.
+       Thanks to Mindaugas Rasiukevicius.
+
+
+Changes with nginx 1.9.14                                        05 Apr 2016
+
+    *) Feature: OpenSSL 1.1.0 compatibility.
+
+    *) Feature: the "proxy_request_buffering", "fastcgi_request_buffering",
+       "scgi_request_buffering", and "uwsgi_request_buffering" directives
+       now work with HTTP/2.
+
+    *) Bugfix: "zero size buf in output" alerts might appear in logs when
+       using HTTP/2.
+
+    *) Bugfix: the "client_max_body_size" directive might work incorrectly
+       when using HTTP/2.
+
+    *) Bugfix: of minor bugs in logging.
+
+
+Changes with nginx 1.9.13                                        29 Mar 2016
+
+    *) Change: non-idempotent requests (POST, LOCK, PATCH) are no longer
+       passed to the next server by default if a request has been sent to a
+       backend; the "non_idempotent" parameter of the "proxy_next_upstream"
+       directive explicitly allows retrying such requests.
+
+    *) Feature: the ngx_http_perl_module can be built dynamically.
+
+    *) Feature: UDP support in the stream module.
+
+    *) Feature: the "aio_write" directive.
+
+    *) Feature: now cache manager monitors number of elements in caches and
+       tries to avoid cache keys zone overflows.
+
+    *) Bugfix: "task already active" and "second aio post" alerts might
+       appear in logs when using the "sendfile" and "aio" directives with
+       subrequests.
+
+    *) Bugfix: "zero size buf in output" alerts might appear in logs if
+       caching was used and a client closed a connection prematurely.
+
+    *) Bugfix: connections with clients might be closed needlessly if
+       caching was used.
+       Thanks to Justin Li.
+
+    *) Bugfix: nginx might hog CPU if the "sendfile" directive was used on
+       Linux or Solaris and a file being sent was changed during sending.
+
+    *) Bugfix: connections might hang when using the "sendfile" and "aio
+       threads" directives.
+
+    *) Bugfix: in the "proxy_pass", "fastcgi_pass", "scgi_pass", and
+       "uwsgi_pass" directives when using variables.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: in the ngx_http_sub_filter_module.
+
+    *) Bugfix: if an error occurred in a cached backend connection, the
+       request was passed to the next server regardless of the
+       proxy_next_upstream directive.
+
+    *) Bugfix: "CreateFile() failed" errors when creating temporary files on
+       Windows.
+
+
+Changes with nginx 1.9.12                                        24 Feb 2016
+
+    *) Feature: Huffman encoding of response headers in HTTP/2.
+       Thanks to Vlad Krasnov.
+
+    *) Feature: the "worker_cpu_affinity" directive now supports more than
+       64 CPUs.
+
+    *) Bugfix: compatibility with 3rd party C++ modules; the bug had
+       appeared in 1.9.11.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: nginx could not be built statically with OpenSSL on Linux;
+       the bug had appeared in 1.9.11.
+
+    *) Bugfix: the "add_header ... always" directive with an empty value did
+       not delete "Last-Modified" and "ETag" header lines from error
+       responses.
+
+    *) Workaround: "called a function you should not call" and "shutdown
+       while in init" messages might appear in logs when using OpenSSL
+       1.0.2f.
+
+    *) Bugfix: invalid headers might be logged incorrectly.
+
+    *) Bugfix: socket leak when using HTTP/2.
+
+    *) Bugfix: in the ngx_http_v2_module.
+
+
+Changes with nginx 1.9.11                                        09 Feb 2016
+
+    *) Feature: TCP support in resolver.
+
+    *) Feature: dynamic modules.
+
+    *) Bugfix: the $request_length variable did not include size of request
+       headers when using HTTP/2.
+
+    *) Bugfix: in the ngx_http_v2_module.
+
+
+Changes with nginx 1.9.10                                        26 Jan 2016
+
+    *) Security: invalid pointer dereference might occur during DNS server
+       response processing if the "resolver" directive was used, allowing an
+       attacker who is able to forge UDP packets from the DNS server to
+       cause segmentation fault in a worker process (CVE-2016-0742).
+
+    *) Security: use-after-free condition might occur during CNAME response
+       processing if the "resolver" directive was used, allowing an attacker
+       who is able to trigger name resolution to cause segmentation fault in
+       a worker process, or might have potential other impact
+       (CVE-2016-0746).
+
+    *) Security: CNAME resolution was insufficiently limited if the
+       "resolver" directive was used, allowing an attacker who is able to
+       trigger arbitrary name resolution to cause excessive resource
+       consumption in worker processes (CVE-2016-0747).
+
+    *) Feature: the "auto" parameter of the "worker_cpu_affinity" directive.
+
+    *) Bugfix: the "proxy_protocol" parameter of the "listen" directive did
+       not work with IPv6 listen sockets.
+
+    *) Bugfix: connections to upstream servers might be cached incorrectly
+       when using the "keepalive" directive.
+
+    *) Bugfix: proxying used the HTTP method of the original request after
+       an "X-Accel-Redirect" redirection.
+
+
+Changes with nginx 1.9.9                                         09 Dec 2015
+
+    *) Bugfix: proxying to unix domain sockets did not work when using
+       variables; the bug had appeared in 1.9.8.
+
+
+Changes with nginx 1.9.8                                         08 Dec 2015
+
+    *) Feature: pwritev() support.
+
+    *) Feature: the "include" directive inside the "upstream" block.
+
+    *) Feature: the ngx_http_slice_module.
+
+    *) Bugfix: a segmentation fault might occur in a worker process when
+       using LibreSSL; the bug had appeared in 1.9.6.
+
+    *) Bugfix: nginx could not be built on OS X in some cases.
+
+
+Changes with nginx 1.9.7                                         17 Nov 2015
+
+    *) Feature: the "nohostname" parameter of logging to syslog.
+
+    *) Feature: the "proxy_cache_convert_head" directive.
+
+    *) Feature: the $realip_remote_addr variable in the
+       ngx_http_realip_module.
+
+    *) Bugfix: the "expires" directive might not work when using variables.
+
+    *) Bugfix: a segmentation fault might occur in a worker process when
+       using HTTP/2; the bug had appeared in 1.9.6.
+
+    *) Bugfix: if nginx was built with the ngx_http_v2_module it was
+       possible to use the HTTP/2 protocol even if the "http2" parameter of
+       the "listen" directive was not specified.
+
+    *) Bugfix: in the ngx_http_v2_module.
+
+
+Changes with nginx 1.9.6                                         27 Oct 2015
+
+    *) Bugfix: a segmentation fault might occur in a worker process when
+       using HTTP/2.
+       Thanks to Piotr Sikora and Denis Andzakovic.
+
+    *) Bugfix: the $server_protocol variable was empty when using HTTP/2.
+
+    *) Bugfix: backend SSL connections in the stream module might be timed
+       out unexpectedly.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if
+       different ssl_session_cache settings were used in different virtual
+       servers.
+
+    *) Bugfix: nginx/Windows could not be built with MinGW gcc; the bug had
+       appeared in 1.9.4.
+       Thanks to Kouhei Sutou.
+
+    *) Bugfix: time was not updated when the timer_resolution directive was
+       used on Windows.
+
+    *) Miscellaneous minor fixes and improvements.
+       Thanks to Markus Linnala, Kurtis Nusbaum and Piotr Sikora.
+
+
+Changes with nginx 1.9.5                                         22 Sep 2015
+
+    *) Feature: the ngx_http_v2_module (replaces ngx_http_spdy_module).
+       Thanks to Dropbox and Automattic for sponsoring this work.
+
+    *) Change: now the "output_buffers" directive uses two buffers by
+       default.
+
+    *) Change: now nginx limits subrequests recursion, not simultaneous
+       subrequests.
+
+    *) Change: now nginx checks the whole cache key when returning a
+       response from cache.
+       Thanks to Gena Makhomed and Sergey Brester.
+
+    *) Bugfix: "header already sent" alerts might appear in logs when using
+       cache; the bug had appeared in 1.7.5.
+
+    *) Bugfix: "writev() failed (4: Interrupted system call)" errors might
+       appear in logs when using CephFS and the "timer_resolution" directive
+       on Linux.
+
+    *) Bugfix: in invalid configurations handling.
+       Thanks to Markus Linnala.
+
+    *) Bugfix: a segmentation fault occurred in a worker process if the
+       "sub_filter" directive was used at http level; the bug had appeared
+       in 1.9.4.
+
+
+Changes with nginx 1.9.4                                         18 Aug 2015
+
+    *) Change: the "proxy_downstream_buffer" and "proxy_upstream_buffer"
+       directives of the stream module are replaced with the
+       "proxy_buffer_size" directive.
+
+    *) Feature: the "tcp_nodelay" directive in the stream module.
+
+    *) Feature: multiple "sub_filter" directives can be used simultaneously.
+
+    *) Feature: variables support in the search string of the "sub_filter"
+       directive.
+
+    *) Workaround: configuration testing might fail under Linux OpenVZ.
+       Thanks to Gena Makhomed.
+
+    *) Bugfix: old worker processes might hog CPU after reconfiguration with
+       a large number of worker_connections.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       "try_files" and "alias" directives were used inside a location given
+       by a regular expression; the bug had appeared in 1.7.1.
+
+    *) Bugfix: the "try_files" directive inside a nested location given by a
+       regular expression worked incorrectly if the "alias" directive was
+       used in the outer location.
+
+    *) Bugfix: in hash table initialization error handling.
+
+    *) Bugfix: nginx could not be built with Visual Studio 2015.
+
+
+Changes with nginx 1.9.3                                         14 Jul 2015
+
+    *) Change: duplicate "http", "mail", and "stream" blocks are now
+       disallowed.
+
+    *) Feature: connection limiting in the stream module.
+
+    *) Feature: data rate limiting in the stream module.
+
+    *) Bugfix: the "zone" directive inside the "upstream" block did not work
+       on Windows.
+
+    *) Bugfix: compatibility with LibreSSL in the stream module.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: in the "--builddir" configure parameter.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: the "ssl_stapling_file" directive did not work; the bug had
+       appeared in 1.9.2.
+       Thanks to Faidon Liambotis and Brandon Black.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       "ssl_stapling" directive was used; the bug had appeared in 1.9.2.
+       Thanks to Matthew Baldwin.
+
+
+Changes with nginx 1.9.2                                         16 Jun 2015
+
+    *) Feature: the "backlog" parameter of the "listen" directives of the
+       mail proxy and stream modules.
+
+    *) Feature: the "allow" and "deny" directives in the stream module.
+
+    *) Feature: the "proxy_bind" directive in the stream module.
+
+    *) Feature: the "proxy_protocol" directive in the stream module.
+
+    *) Feature: the -T switch.
+
+    *) Feature: the REQUEST_SCHEME parameter added to the fastcgi.conf,
+       fastcgi_params, scgi_params, and uwsgi_params standard configuration
+       files.
+
+    *) Bugfix: the "reuseport" parameter of the "listen" directive of the
+       stream module did not work.
+
+    *) Bugfix: OCSP stapling might return an expired OCSP response in some
+       cases.
+
+
+Changes with nginx 1.9.1                                         26 May 2015
+
+    *) Change: now SSLv3 protocol is disabled by default.
+
+    *) Change: some long deprecated directives are not supported anymore.
+
+    *) Feature: the "reuseport" parameter of the "listen" directive.
+       Thanks to Yingqi Lu at Intel and Sepherosa Ziehau.
+
+    *) Feature: the $upstream_connect_time variable.
+
+    *) Bugfix: in the "hash" directive on big-endian platforms.
+
+    *) Bugfix: nginx might fail to start on some old Linux variants; the bug
+       had appeared in 1.7.11.
+
+    *) Bugfix: in IP address parsing.
+       Thanks to Sergey Polovko.
+
+
+Changes with nginx 1.9.0                                         28 Apr 2015
+
+    *) Change: obsolete aio and rtsig event methods have been removed.
+
+    *) Feature: the "zone" directive inside the "upstream" block.
+
+    *) Feature: the stream module.
+
+    *) Feature: byte ranges support in the ngx_http_memcached_module.
+       Thanks to Martin Mlynář.
+
+    *) Feature: shared memory can now be used on Windows versions with
+       address space layout randomization.
+       Thanks to Sergey Brester.
+
+    *) Feature: the "error_log" directive can now be used on mail and server
+       levels in mail proxy.
+
+    *) Bugfix: the "proxy_protocol" parameter of the "listen" directive did
+       not work if not specified in the first "listen" directive for a
+       listen socket.
+
+
+Changes with nginx 1.7.12                                        07 Apr 2015
+
+    *) Feature: now the "tcp_nodelay" directive works with backend SSL
+       connections.
+
+    *) Feature: now thread pools can be used to read cache file headers.
+
+    *) Bugfix: in the "proxy_request_buffering" directive.
+
+    *) Bugfix: a segmentation fault might occur in a worker process when
+       using thread pools on Linux.
+
+    *) Bugfix: in error handling when using the "ssl_stapling" directive.
+       Thanks to Filipe da Silva.
+
+    *) Bugfix: in the ngx_http_spdy_module.
+
+
+Changes with nginx 1.7.11                                        24 Mar 2015
+
+    *) Change: the "sendfile" parameter of the "aio" directive is
+       deprecated; now nginx automatically uses AIO to pre-load data for
+       sendfile if both "aio" and "sendfile" directives are used.
+
+    *) Feature: experimental thread pools support.
+
+    *) Feature: the "proxy_request_buffering", "fastcgi_request_buffering",
+       "scgi_request_buffering", and "uwsgi_request_buffering" directives.
+
+    *) Feature: request body filters experimental API.
+
+    *) Feature: client SSL certificates support in mail proxy.
+       Thanks to Sven Peter, Franck Levionnois, and Filipe Da Silva.
+
+    *) Feature: startup speedup when using the "hash ... consistent"
+       directive in the upstream block.
+       Thanks to Wai Keen Woon.
+
+    *) Feature: debug logging into a cyclic memory buffer.
+
+    *) Bugfix: in hash table handling.
+       Thanks to Chris West.
+
+    *) Bugfix: in the "proxy_cache_revalidate" directive.
+
+    *) Bugfix: SSL connections might hang if deferred accept or the
+       "proxy_protocol" parameter of the "listen" directive were used.
+       Thanks to James Hamlin.
+
+    *) Bugfix: the $upstream_response_time variable might contain a wrong
+       value if the "image_filter" directive was used.
+
+    *) Bugfix: in integer overflow handling.
+       Thanks to Régis Leroy.
+
+    *) Bugfix: it was not possible to enable SSLv3 with LibreSSL.
+
+    *) Bugfix: the "ignoring stale global SSL error ... called a function
+       you should not call" alerts appeared in logs when using LibreSSL.
+
+    *) Bugfix: certificates specified by the "ssl_client_certificate" and
+       "ssl_trusted_certificate" directives were inadvertently used to
+       automatically construct certificate chains.
+
+
+Changes with nginx 1.7.10                                        10 Feb 2015
+
+    *) Feature: the "use_temp_path" parameter of the "proxy_cache_path",
+       "fastcgi_cache_path", "scgi_cache_path", and "uwsgi_cache_path"
+       directives.
+
+    *) Feature: the $upstream_header_time variable.
+
+    *) Workaround: now on disk overflow nginx tries to write error logs once
+       a second only.
+
+    *) Bugfix: the "try_files" directive did not ignore normal files while
+       testing directories.
+       Thanks to Damien Tournoud.
+
+    *) Bugfix: alerts "sendfile() failed" if the "sendfile" directive was
+       used on OS X; the bug had appeared in 1.7.8.
+
+    *) Bugfix: alerts "sem_post() failed" might appear in logs.
+
+    *) Bugfix: nginx could not be built with musl libc.
+       Thanks to James Taylor.
+
+    *) Bugfix: nginx could not be built on Tru64 UNIX.
+       Thanks to Goetz T. Fischer.
+
+
+Changes with nginx 1.7.9                                         23 Dec 2014
+
+    *) Feature: variables support in the "proxy_cache", "fastcgi_cache",
+       "scgi_cache", and "uwsgi_cache" directives.
+
+    *) Feature: variables support in the "expires" directive.
+
+    *) Feature: loading of secret keys from hardware tokens with OpenSSL
+       engines.
+       Thanks to Dmitrii Pichulin.
+
+    *) Feature: the "autoindex_format" directive.
+
+    *) Bugfix: cache revalidation is now only used for responses with 200
+       and 206 status codes.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: the "TE" client request header line was passed to backends
+       while proxying.
+
+    *) Bugfix: the "proxy_pass", "fastcgi_pass", "scgi_pass", and
+       "uwsgi_pass" directives might not work correctly inside the "if" and
+       "limit_except" blocks.
+
+    *) Bugfix: the "proxy_store" directive with the "on" parameter was
+       ignored if the "proxy_store" directive with an explicitly specified
+       file path was used on a previous level.
+
+    *) Bugfix: nginx could not be built with BoringSSL.
+       Thanks to Lukas Tribus.
+
+
+Changes with nginx 1.7.8                                         02 Dec 2014
+
+    *) Change: now the "If-Modified-Since", "If-Range", etc. client request
+       header lines are passed to a backend while caching if nginx knows in
+       advance that the response will not be cached (e.g., when using
+       proxy_cache_min_uses).
+
+    *) Change: now after proxy_cache_lock_timeout nginx sends a request to a
+       backend with caching disabled; the new directives
+       "proxy_cache_lock_age", "fastcgi_cache_lock_age",
+       "scgi_cache_lock_age", and "uwsgi_cache_lock_age" specify a time
+       after which the lock will be released and another attempt to cache a
+       response will be made.
+
+    *) Change: the "log_format" directive can now be used only at http
+       level.
+
+    *) Feature: the "proxy_ssl_certificate", "proxy_ssl_certificate_key",
+       "proxy_ssl_password_file", "uwsgi_ssl_certificate",
+       "uwsgi_ssl_certificate_key", and "uwsgi_ssl_password_file"
+       directives.
+       Thanks to Piotr Sikora.
+
+    *) Feature: it is now possible to switch to a named location using
+       "X-Accel-Redirect".
+       Thanks to Toshikuni Fukaya.
+
+    *) Feature: now the "tcp_nodelay" directive works with SPDY connections.
+
+    *) Feature: new directives in vim syntax highliting scripts.
+       Thanks to Peter Wu.
+
+    *) Bugfix: nginx ignored the "s-maxage" value in the "Cache-Control"
+       backend response header line.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: in the ngx_http_spdy_module.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: in the "ssl_password_file" directive when using OpenSSL
+       0.9.8zc, 1.0.0o, 1.0.1j.
+
+    *) Bugfix: alerts "header already sent" appeared in logs if the
+       "post_action" directive was used; the bug had appeared in 1.5.4.
+
+    *) Bugfix: alerts "the http output chain is empty" might appear in logs
+       if the "postpone_output 0" directive was used with SSI includes.
+
+    *) Bugfix: in the "proxy_cache_lock" directive with SSI subrequests.
+       Thanks to Yichun Zhang.
+
+
+Changes with nginx 1.7.7                                         28 Oct 2014
+
+    *) Change: now nginx takes into account the "Vary" header line in a
+       backend response while caching.
+
+    *) Feature: the "proxy_force_ranges", "fastcgi_force_ranges",
+       "scgi_force_ranges", and "uwsgi_force_ranges" directives.
+
+    *) Feature: the "proxy_limit_rate", "fastcgi_limit_rate",
+       "scgi_limit_rate", and "uwsgi_limit_rate" directives.
+
+    *) Feature: the "Vary" parameter of the "proxy_ignore_headers",
+       "fastcgi_ignore_headers", "scgi_ignore_headers", and
+       "uwsgi_ignore_headers" directives.
+
+    *) Bugfix: the last part of a response received from a backend with
+       unbufferred proxy might not be sent to a client if "gzip" or "gunzip"
+       directives were used.
+
+    *) Bugfix: in the "proxy_cache_revalidate" directive.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: in error handling.
+       Thanks to Yichun Zhang and Daniil Bondarev.
+
+    *) Bugfix: in the "proxy_next_upstream_tries" and
+       "proxy_next_upstream_timeout" directives.
+       Thanks to Feng Gu.
+
+    *) Bugfix: nginx/Windows could not be built with MinGW-w64 gcc.
+       Thanks to Kouhei Sutou.
+
+
+Changes with nginx 1.7.6                                         30 Sep 2014
+
+    *) Change: the deprecated "limit_zone" directive is not supported
+       anymore.
+
+    *) Feature: the "limit_conn_zone" and "limit_req_zone" directives now
+       can be used with combinations of multiple variables.
+
+    *) Bugfix: request body might be transmitted incorrectly when retrying a
+       FastCGI request to the next upstream server.
+
+    *) Bugfix: in logging to syslog.
+
+
+Changes with nginx 1.7.5                                         16 Sep 2014
+
+    *) Security: it was possible to reuse SSL sessions in unrelated contexts
+       if a shared SSL session cache or the same TLS session ticket key was
+       used for multiple "server" blocks (CVE-2014-3616).
+       Thanks to Antoine Delignat-Lavaud.
+
+    *) Change: now the "stub_status" directive does not require a parameter.
+
+    *) Feature: the "always" parameter of the "add_header" directive.
+
+    *) Feature: the "proxy_next_upstream_tries",
+       "proxy_next_upstream_timeout", "fastcgi_next_upstream_tries",
+       "fastcgi_next_upstream_timeout", "memcached_next_upstream_tries",
+       "memcached_next_upstream_timeout", "scgi_next_upstream_tries",
+       "scgi_next_upstream_timeout", "uwsgi_next_upstream_tries", and
+       "uwsgi_next_upstream_timeout" directives.
+
+    *) Bugfix: in the "if" parameter of the "access_log" directive.
+
+    *) Bugfix: in the ngx_http_perl_module.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: the "listen" directive of the mail proxy module did not allow
+       to specify more than two parameters.
+
+    *) Bugfix: the "sub_filter" directive did not work with a string to
+       replace consisting of a single character.
+
+    *) Bugfix: requests might hang if resolver was used and a timeout
+       occurred during a DNS request.
+
+    *) Bugfix: in the ngx_http_spdy_module when using with AIO.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       "set" directive was used to change the "$http_...", "$sent_http_...",
+       or "$upstream_http_..." variables.
+
+    *) Bugfix: in memory allocation error handling.
+       Thanks to Markus Linnala and Feng Gu.
+
+
+Changes with nginx 1.7.4                                         05 Aug 2014
+
+    *) Security: pipelined commands were not discarded after STARTTLS
+       command in SMTP proxy (CVE-2014-3556); the bug had appeared in 1.5.6.
+       Thanks to Chris Boulton.
+
+    *) Change: URI escaping now uses uppercase hexadecimal digits.
+       Thanks to Piotr Sikora.
+
+    *) Feature: now nginx can be build with BoringSSL and LibreSSL.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: requests might hang if resolver was used and a DNS server
+       returned a malformed response; the bug had appeared in 1.5.8.
+
+    *) Bugfix: in the ngx_http_spdy_module.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: the $uri variable might contain garbage when returning errors
+       with code 400.
+       Thanks to Sergey Bobrov.
+
+    *) Bugfix: in error handling in the "proxy_store" directive and the
+       ngx_http_dav_module.
+       Thanks to Feng Gu.
+
+    *) Bugfix: a segmentation fault might occur if logging of errors to
+       syslog was used; the bug had appeared in 1.7.1.
+
+    *) Bugfix: the $geoip_latitude, $geoip_longitude, $geoip_dma_code, and
+       $geoip_area_code variables might not work.
+       Thanks to Yichun Zhang.
+
+    *) Bugfix: in memory allocation error handling.
+       Thanks to Tatsuhiko Kubo and Piotr Sikora.
+
+
+Changes with nginx 1.7.3                                         08 Jul 2014
+
+    *) Feature: weak entity tags are now preserved on response
+       modifications, and strong ones are changed to weak.
+
+    *) Feature: cache revalidation now uses If-None-Match header if
+       possible.
+
+    *) Feature: the "ssl_password_file" directive.
+
+    *) Bugfix: the If-None-Match request header line was ignored if there
+       was no Last-Modified header in a response returned from cache.
+
+    *) Bugfix: "peer closed connection in SSL handshake" messages were
+       logged at "info" level instead of "error" while connecting to
+       backends.
+
+    *) Bugfix: in the ngx_http_dav_module module in nginx/Windows.
+
+    *) Bugfix: SPDY connections might be closed prematurely if caching was
+       used.
+
+
+Changes with nginx 1.7.2                                         17 Jun 2014
+
+    *) Feature: the "hash" directive inside the "upstream" block.
+
+    *) Feature: defragmentation of free shared memory blocks.
+       Thanks to Wandenberg Peixoto and Yichun Zhang.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       default value of the "access_log" directive was used; the bug had
+       appeared in 1.7.0.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: trailing slash was mistakenly removed from the last parameter
+       of the "try_files" directive.
+
+    *) Bugfix: nginx could not be built on OS X in some cases.
+
+    *) Bugfix: in the ngx_http_spdy_module.
+
+
+Changes with nginx 1.7.1                                         27 May 2014
+
+    *) Feature: the "$upstream_cookie_..." variables.
+
+    *) Feature: the $ssl_client_fingerprint variable.
+
+    *) Feature: the "error_log" and "access_log" directives now support
+       logging to syslog.
+
+    *) Feature: the mail proxy now logs client port on connect.
+
+    *) Bugfix: memory leak if the "ssl_stapling" directive was used.
+       Thanks to Filipe da Silva.
+
+    *) Bugfix: the "alias" directive used inside a location given by a
+       regular expression worked incorrectly if the "if" or "limit_except"
+       directives were used.
+
+    *) Bugfix: the "charset" directive did not set a charset to encoded
+       backend responses.
+
+    *) Bugfix: a "proxy_pass" directive without URI part might use original
+       request after the $args variable was set.
+       Thanks to Yichun Zhang.
+
+    *) Bugfix: in the "none" parameter in the "smtp_auth" directive; the bug
+       had appeared in 1.5.6.
+       Thanks to Svyatoslav Nikolsky.
+
+    *) Bugfix: if sub_filter and SSI were used together, then responses
+       might be transferred incorrectly.
+
+    *) Bugfix: nginx could not be built with the --with-file-aio option on
+       Linux/aarch64.
+
+
+Changes with nginx 1.7.0                                         24 Apr 2014
+
+    *) Feature: backend SSL certificate verification.
+
+    *) Feature: support for SNI while working with SSL backends.
+
+    *) Feature: the $ssl_server_name variable.
+
+    *) Feature: the "if" parameter of the "access_log" directive.
+
+
+Changes with nginx 1.5.13                                        08 Apr 2014
+
+    *) Change: improved hash table handling; the default values of the
+       "variables_hash_max_size" and "types_hash_bucket_size" were changed
+       to 1024 and 64 respectively.
+
+    *) Feature: the ngx_http_mp4_module now supports the "end" argument.
+
+    *) Feature: byte ranges support in the ngx_http_mp4_module and while
+       saving responses to cache.
+
+    *) Bugfix: alerts "ngx_slab_alloc() failed: no memory" no longer logged
+       when using shared memory in the "ssl_session_cache" directive and in
+       the ngx_http_limit_req_module.
+
+    *) Bugfix: the "underscores_in_headers" directive did not allow
+       underscore as a first character of a header.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: cache manager might hog CPU on exit in nginx/Windows.
+
+    *) Bugfix: nginx/Windows terminated abnormally if the
+       "ssl_session_cache" directive was used with the "shared" parameter.
+
+    *) Bugfix: in the ngx_http_spdy_module.
+
+
+Changes with nginx 1.5.12                                        18 Mar 2014
+
+    *) Security: a heap memory buffer overflow might occur in a worker
+       process while handling a specially crafted request by
+       ngx_http_spdy_module, potentially resulting in arbitrary code
+       execution (CVE-2014-0133).
+       Thanks to Lucas Molas, researcher at Programa STIC, Fundación Dr.
+       Manuel Sadosky, Buenos Aires, Argentina.
+
+    *) Feature: the "proxy_protocol" parameters of the "listen" and
+       "real_ip_header" directives, the $proxy_protocol_addr variable.
+
+    *) Bugfix: in the "fastcgi_next_upstream" directive.
+       Thanks to Lucas Molas.
+
+
+Changes with nginx 1.5.11                                        04 Mar 2014
+
+    *) Security: memory corruption might occur in a worker process on 32-bit
+       platforms while handling a specially crafted request by
+       ngx_http_spdy_module, potentially resulting in arbitrary code
+       execution (CVE-2014-0088); the bug had appeared in 1.5.10.
+       Thanks to Lucas Molas, researcher at Programa STIC, Fundación Dr.
+       Manuel Sadosky, Buenos Aires, Argentina.
+
+    *) Feature: the $ssl_session_reused variable.
+
+    *) Bugfix: the "client_max_body_size" directive might not work when
+       reading a request body using chunked transfer encoding; the bug had
+       appeared in 1.3.9.
+       Thanks to Lucas Molas.
+
+    *) Bugfix: a segmentation fault might occur in a worker process when
+       proxying WebSocket connections.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       ngx_http_spdy_module was used on 32-bit platforms; the bug had
+       appeared in 1.5.10.
+
+    *) Bugfix: the $upstream_status variable might contain wrong data if the
+       "proxy_cache_use_stale" or "proxy_cache_revalidate" directives were
+       used.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if
+       errors with code 400 were redirected to a named location using the
+       "error_page" directive.
+
+    *) Bugfix: nginx/Windows could not be built with Visual Studio 2013.
+
+
+Changes with nginx 1.5.10                                        04 Feb 2014
+
+    *) Feature: the ngx_http_spdy_module now uses SPDY 3.1 protocol.
+       Thanks to Automattic and MaxCDN for sponsoring this work.
+
+    *) Feature: the ngx_http_mp4_module now skips tracks too short for a
+       seek requested.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       $ssl_session_id variable was used in logs; the bug had appeared in
+       1.5.9.
+
+    *) Bugfix: the $date_local and $date_gmt variables used wrong format
+       outside of the ngx_http_ssi_filter_module.
+
+    *) Bugfix: client connections might be immediately closed if deferred
+       accept was used; the bug had appeared in 1.3.15.
+
+    *) Bugfix: alerts "getsockopt(TCP_FASTOPEN) ... failed" appeared in logs
+       during binary upgrade on Linux; the bug had appeared in 1.5.8.
+       Thanks to Piotr Sikora.
+
+
+Changes with nginx 1.5.9                                         22 Jan 2014
+
+    *) Change: now nginx expects escaped URIs in "X-Accel-Redirect" headers.
+
+    *) Feature: the "ssl_buffer_size" directive.
+
+    *) Feature: the "limit_rate" directive can now be used to rate limit
+       responses sent in SPDY connections.
+
+    *) Feature: the "spdy_chunk_size" directive.
+
+    *) Feature: the "ssl_session_tickets" directive.
+       Thanks to Dirkjan Bussink.
+
+    *) Bugfix: the $ssl_session_id variable contained full session
+       serialized instead of just a session id.
+       Thanks to Ivan Ristić.
+
+    *) Bugfix: nginx incorrectly handled escaped "?" character in the
+       "include" SSI command.
+
+    *) Bugfix: the ngx_http_dav_module did not unescape destination URI of
+       the COPY and MOVE methods.
+
+    *) Bugfix: resolver did not understand domain names with a trailing dot.
+       Thanks to Yichun Zhang.
+
+    *) Bugfix: alerts "zero size buf in output" might appear in logs while
+       proxying; the bug had appeared in 1.3.9.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       ngx_http_spdy_module was used.
+
+    *) Bugfix: proxied WebSocket connections might hang right after
+       handshake if the select, poll, or /dev/poll methods were used.
+
+    *) Bugfix: the "xclient" directive of the mail proxy module incorrectly
+       handled IPv6 client addresses.
+
+
+Changes with nginx 1.5.8                                         17 Dec 2013
+
+    *) Feature: IPv6 support in resolver.
+
+    *) Feature: the "listen" directive supports the "fastopen" parameter.
+       Thanks to Mathew Rodley.
+
+    *) Feature: SSL support in the ngx_http_uwsgi_module.
+       Thanks to Roberto De Ioris.
+
+    *) Feature: vim syntax highlighting scripts were added to contrib.
+       Thanks to Evan Miller.
+
+    *) Bugfix: a timeout might occur while reading client request body in an
+       SSL connection using chunked transfer encoding.
+
+    *) Bugfix: the "master_process" directive did not work correctly in
+       nginx/Windows.
+
+    *) Bugfix: the "setfib" parameter of the "listen" directive might not
+       work.
+
+    *) Bugfix: in the ngx_http_spdy_module.
+
+
+Changes with nginx 1.5.7                                         19 Nov 2013
+
+    *) Security: a character following an unescaped space in a request line
+       was handled incorrectly (CVE-2013-4547); the bug had appeared in
+       0.8.41.
+       Thanks to Ivan Fratric of the Google Security Team.
+
+    *) Change: a logging level of auth_basic errors about no user/password
+       provided has been lowered from "error" to "info".
+
+    *) Feature: the "proxy_cache_revalidate", "fastcgi_cache_revalidate",
+       "scgi_cache_revalidate", and "uwsgi_cache_revalidate" directives.
+
+    *) Feature: the "ssl_session_ticket_key" directive.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: the directive "add_header Cache-Control ''" added a
+       "Cache-Control" response header line with an empty value.
+
+    *) Bugfix: the "satisfy any" directive might return 403 error instead of
+       401 if auth_request and auth_basic directives were used.
+       Thanks to Jan Marc Hoffmann.
+
+    *) Bugfix: the "accept_filter" and "deferred" parameters of the "listen"
+       directive were ignored for listen sockets created during binary
+       upgrade.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: some data received from a backend with unbufferred proxy
+       might not be sent to a client immediately if "gzip" or "gunzip"
+       directives were used.
+       Thanks to Yichun Zhang.
+
+    *) Bugfix: in error handling in ngx_http_gunzip_filter_module.
+
+    *) Bugfix: responses might hang if the ngx_http_spdy_module was used
+       with the "auth_request" directive.
+
+    *) Bugfix: memory leak in nginx/Windows.
+
+
+Changes with nginx 1.5.6                                         01 Oct 2013
+
+    *) Feature: the "fastcgi_buffering" directive.
+
+    *) Feature: the "proxy_ssl_protocols" and "proxy_ssl_ciphers"
+       directives.
+       Thanks to Piotr Sikora.
+
+    *) Feature: optimization of SSL handshakes when using long certificate
+       chains.
+
+    *) Feature: the mail proxy supports SMTP pipelining.
+
+    *) Bugfix: in the ngx_http_auth_basic_module when using "$apr1$"
+       password encryption method.
+       Thanks to Markus Linnala.
+
+    *) Bugfix: in MacOSX, Cygwin, and nginx/Windows incorrect location might
+       be used to process a request if locations were given using characters
+       in different cases.
+
+    *) Bugfix: automatic redirect with appended trailing slash for proxied
+       locations might not work.
+
+    *) Bugfix: in the mail proxy server.
+
+    *) Bugfix: in the ngx_http_spdy_module.
+
+
+Changes with nginx 1.5.5                                         17 Sep 2013
+
+    *) Change: now nginx assumes HTTP/1.0 by default if it is not able to
+       detect protocol reliably.
+
+    *) Feature: the "disable_symlinks" directive now uses O_PATH on Linux.
+
+    *) Feature: now nginx uses EPOLLRDHUP events to detect premature
+       connection close by clients if the "epoll" method is used.
+
+    *) Bugfix: in the "valid_referers" directive if the "server_names"
+       parameter was used.
+
+    *) Bugfix: the $request_time variable did not work in nginx/Windows.
+
+    *) Bugfix: in the "image_filter" directive.
+       Thanks to Lanshun Zhou.
+
+    *) Bugfix: OpenSSL 1.0.1f compatibility.
+       Thanks to Piotr Sikora.
+
+
+Changes with nginx 1.5.4                                         27 Aug 2013
+
+    *) Change: the "js" extension MIME type has been changed to
+       "application/javascript"; default value of the "charset_types"
+       directive was changed accordingly.
+
+    *) Change: now the "image_filter" directive with the "size" parameter
+       returns responses with the "application/json" MIME type.
+
+    *) Feature: the ngx_http_auth_request_module.
+
+    *) Bugfix: a segmentation fault might occur on start or during
+       reconfiguration if the "try_files" directive was used with an empty
+       parameter.
+
+    *) Bugfix: memory leak if relative paths were specified using variables
+       in the "root" or "auth_basic_user_file" directives.
+
+    *) Bugfix: the "valid_referers" directive incorrectly executed regular
+       expressions if a "Referer" header started with "https://".
+       Thanks to Liangbin Li.
+
+    *) Bugfix: responses might hang if subrequests were used and an SSL
+       handshake error happened during subrequest processing.
+       Thanks to Aviram Cohen.
+
+    *) Bugfix: in the ngx_http_autoindex_module.
+
+    *) Bugfix: in the ngx_http_spdy_module.
+
+
+Changes with nginx 1.5.3                                         30 Jul 2013
+
+    *) Change in internal API: now u->length defaults to -1 if working with
+       backends in unbuffered mode.
+
+    *) Change: now after receiving an incomplete response from a backend
+       server nginx tries to send an available part of the response to a
+       client, and then closes client connection.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       ngx_http_spdy_module was used with the "client_body_in_file_only"
+       directive.
+
+    *) Bugfix: the "so_keepalive" parameter of the "listen" directive might
+       be handled incorrectly on DragonFlyBSD.
+       Thanks to Sepherosa Ziehau.
+
+    *) Bugfix: in the ngx_http_xslt_filter_module.
+
+    *) Bugfix: in the ngx_http_sub_filter_module.
+
+
+Changes with nginx 1.5.2                                         02 Jul 2013
+
+    *) Feature: now several "error_log" directives can be used.
+
+    *) Bugfix: the $r->header_in() embedded perl method did not return value
+       of the "Cookie" and "X-Forwarded-For" request header lines; the bug
+       had appeared in 1.3.14.
+
+    *) Bugfix: in the ngx_http_spdy_module.
+       Thanks to Jim Radford.
+
+    *) Bugfix: nginx could not be built on Linux with x32 ABI.
+       Thanks to Serguei Ivantsov.
+
+
+Changes with nginx 1.5.1                                         04 Jun 2013
+
+    *) Feature: the "ssi_last_modified", "sub_filter_last_modified", and
+       "xslt_last_modified" directives.
+       Thanks to Alexey Kolpakov.
+
+    *) Feature: the "http_403" parameter of the "proxy_next_upstream",
+       "fastcgi_next_upstream", "scgi_next_upstream", and
+       "uwsgi_next_upstream" directives.
+
+    *) Feature: the "allow" and "deny" directives now support unix domain
+       sockets.
+
+    *) Bugfix: nginx could not be built with the ngx_mail_ssl_module, but
+       without ngx_http_ssl_module; the bug had appeared in 1.3.14.
+
+    *) Bugfix: in the "proxy_set_body" directive.
+       Thanks to Lanshun Zhou.
+
+    *) Bugfix: in the "lingering_time" directive.
+       Thanks to Lanshun Zhou.
+
+    *) Bugfix: the "fail_timeout" parameter of the "server" directive in the
+       "upstream" context might not work if "max_fails" parameter was used;
+       the bug had appeared in 1.3.0.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       "ssl_stapling" directive was used.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: in the mail proxy server.
+       Thanks to Filipe Da Silva.
+
+    *) Bugfix: nginx/Windows might stop accepting connections if several
+       worker processes were used.
+
+
+Changes with nginx 1.5.0                                         07 May 2013
+
+    *) Security: a stack-based buffer overflow might occur in a worker
+       process while handling a specially crafted request, potentially
+       resulting in arbitrary code execution (CVE-2013-2028); the bug had
+       appeared in 1.3.9.
+       Thanks to Greg MacManus, iSIGHT Partners Labs.
+
+
+Changes with nginx 1.4.0                                         24 Apr 2013
+
+    *) Bugfix: nginx could not be built with the ngx_http_perl_module if the
+       --with-openssl option was used; the bug had appeared in 1.3.16.
+
+    *) Bugfix: in a request body handling in the ngx_http_perl_module; the
+       bug had appeared in 1.3.9.
+
+
+Changes with nginx 1.3.16                                        16 Apr 2013
+
+    *) Bugfix: a segmentation fault might occur in a worker process if
+       subrequests were used; the bug had appeared in 1.3.9.
+
+    *) Bugfix: the "tcp_nodelay" directive caused an error if a WebSocket
+       connection was proxied into a unix domain socket.
+
+    *) Bugfix: the $upstream_response_length variable has an incorrect value
+       "0" if buffering was not used.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: in the eventport and /dev/poll methods.
+
+
+Changes with nginx 1.3.15                                        26 Mar 2013
+
+    *) Change: opening and closing a connection without sending any data in
+       it is no longer logged to access_log with error code 400.
+
+    *) Feature: the ngx_http_spdy_module.
+       Thanks to Automattic for sponsoring this work.
+
+    *) Feature: the "limit_req_status" and "limit_conn_status" directives.
+       Thanks to Nick Marden.
+
+    *) Feature: the "image_filter_interlace" directive.
+       Thanks to Ian Babrou.
+
+    *) Feature: $connections_waiting variable in the
+       ngx_http_stub_status_module.
+
+    *) Feature: the mail proxy module now supports IPv6 backends.
+
+    *) Bugfix: request body might be transmitted incorrectly when retrying a
+       request to the next upstream server; the bug had appeared in 1.3.9.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: in the "client_body_in_file_only" directive; the bug had
+       appeared in 1.3.9.
+
+    *) Bugfix: responses might hang if subrequests were used and a DNS error
+       happened during subrequest processing.
+       Thanks to Lanshun Zhou.
+
+    *) Bugfix: in backend usage accounting.
+
+
+Changes with nginx 1.3.14                                        05 Mar 2013
+
+    *) Feature: $connections_active, $connections_reading, and
+       $connections_writing variables in the ngx_http_stub_status_module.
+
+    *) Feature: support of WebSocket connections in the
+       ngx_http_uwsgi_module and ngx_http_scgi_module.
+
+    *) Bugfix: in virtual servers handling with SNI.
+
+    *) Bugfix: new sessions were not always stored if the "ssl_session_cache
+       shared" directive was used and there was no free space in shared
+       memory.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: multiple X-Forwarded-For headers were handled incorrectly.
+       Thanks to Neal Poole for sponsoring this work.
+
+    *) Bugfix: in the ngx_http_mp4_module.
+       Thanks to Gernot Vormayr.
+
+
+Changes with nginx 1.3.13                                        19 Feb 2013
+
+    *) Change: a compiler with name "cc" is now used by default.
+
+    *) Feature: support for proxying of WebSocket connections.
+       Thanks to Apcera and CloudBees for sponsoring this work.
+
+    *) Feature: the "auth_basic_user_file" directive supports "{SHA}"
+       password encryption method.
+       Thanks to Louis Opter.
+
+
+Changes with nginx 1.3.12                                        05 Feb 2013
+
+    *) Feature: variables support in the "proxy_bind", "fastcgi_bind",
+       "memcached_bind", "scgi_bind", and "uwsgi_bind" directives.
+
+    *) Feature: the $pipe, $request_length, $time_iso8601, and $time_local
+       variables can now be used not only in the "log_format" directive.
+       Thanks to Kiril Kalchev.
+
+    *) Feature: IPv6 support in the ngx_http_geoip_module.
+       Thanks to Gregor Kališnik.
+
+    *) Bugfix: in the "proxy_method" directive.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if
+       resolver was used with the poll method.
+
+    *) Bugfix: nginx might hog CPU during SSL handshake with a backend if
+       the select, poll, or /dev/poll methods were used.
+
+    *) Bugfix: the "[crit] SSL_write() failed (SSL:)" error.
+
+    *) Bugfix: in the "client_body_in_file_only" directive; the bug had
+       appeared in 1.3.9.
+
+    *) Bugfix: in the "fastcgi_keep_conn" directive.
+
+
+Changes with nginx 1.3.11                                        10 Jan 2013
+
+    *) Bugfix: a segmentation fault might occur if logging was used; the bug
+       had appeared in 1.3.10.
+
+    *) Bugfix: the "proxy_pass" directive did not work with IP addresses
+       without port specified; the bug had appeared in 1.3.10.
+
+    *) Bugfix: a segmentation fault occurred on start or during
+       reconfiguration if the "keepalive" directive was specified more than
+       once in a single upstream block.
+
+    *) Bugfix: parameter "default" of the "geo" directive did not set
+       default value for IPv6 addresses.
+
+
+Changes with nginx 1.3.10                                        25 Dec 2012
+
+    *) Change: domain names specified in configuration file are now resolved
+       to IPv6 addresses as well as IPv4 ones.
+
+    *) Change: now if the "include" directive with mask is used on Unix
+       systems, included files are sorted in alphabetical order.
+
+    *) Change: the "add_header" directive adds headers to 201 responses.
+
+    *) Feature: the "geo" directive now supports IPv6 addresses in CIDR
+       notation.
+
+    *) Feature: the "flush" and "gzip" parameters of the "access_log"
+       directive.
+
+    *) Feature: variables support in the "auth_basic" directive.
+
+    *) Bugfix: nginx could not be built with the ngx_http_perl_module in
+       some cases.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       ngx_http_xslt_module was used.
+
+    *) Bugfix: nginx could not be built on MacOSX in some cases.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: the "limit_rate" directive with high rates might result in
+       truncated responses on 32-bit platforms.
+       Thanks to Alexey Antropov.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       "if" directive was used.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: a "100 Continue" response was issued with "413 Request Entity
+       Too Large" responses.
+
+    *) Bugfix: the "image_filter", "image_filter_jpeg_quality" and
+       "image_filter_sharpen" directives might be inherited incorrectly.
+       Thanks to Ian Babrou.
+
+    *) Bugfix: "crypt_r() failed" errors might appear if the "auth_basic"
+       directive was used on Linux.
+
+    *) Bugfix: in backup servers handling.
+       Thanks to Thomas Chen.
+
+    *) Bugfix: proxied HEAD requests might return incorrect response if the
+       "gzip" directive was used.
+
+
+Changes with nginx 1.3.9                                         27 Nov 2012
+
+    *) Feature: support for chunked transfer encoding while reading client
+       request body.
+
+    *) Feature: the $request_time and $msec variables can now be used not
+       only in the "log_format" directive.
+
+    *) Bugfix: cache manager and cache loader processes might not be able to
+       start if more than 512 listen sockets were used.
+
+    *) Bugfix: in the ngx_http_dav_module.
+
+
+Changes with nginx 1.3.8                                         30 Oct 2012
+
+    *) Feature: the "optional_no_ca" parameter of the "ssl_verify_client"
+       directive.
+       Thanks to Mike Kazantsev and Eric O'Connor.
+
+    *) Feature: the $bytes_sent, $connection, and $connection_requests
+       variables can now be used not only in the "log_format" directive.
+       Thanks to Benjamin Grössing.
+
+    *) Feature: the "auto" parameter of the "worker_processes" directive.
+
+    *) Bugfix: "cache file ... has md5 collision" alert.
+
+    *) Bugfix: in the ngx_http_gunzip_filter_module.
+
+    *) Bugfix: in the "ssl_stapling" directive.
+
+
+Changes with nginx 1.3.7                                         02 Oct 2012
+
+    *) Feature: OCSP stapling support.
+       Thanks to Comodo, DigiCert and GlobalSign for sponsoring this work.
+
+    *) Feature: the "ssl_trusted_certificate" directive.
+
+    *) Feature: resolver now randomly rotates addresses returned from cache.
+       Thanks to Anton Jouline.
+
+    *) Bugfix: OpenSSL 0.9.7 compatibility.
+
+
+Changes with nginx 1.3.6                                         12 Sep 2012
+
+    *) Feature: the ngx_http_gunzip_filter_module.
+
+    *) Feature: the "memcached_gzip_flag" directive.
+
+    *) Feature: the "always" parameter of the "gzip_static" directive.
+
+    *) Bugfix: in the "limit_req" directive; the bug had appeared in 1.1.14.
+       Thanks to Charles Chen.
+
+    *) Bugfix: nginx could not be built by gcc 4.7 with -O2 optimization if
+       the --with-ipv6 option was used.
+
+
+Changes with nginx 1.3.5                                         21 Aug 2012
+
+    *) Change: the ngx_http_mp4_module module no longer skips tracks in
+       formats other than H.264 and AAC.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       "map" directive was used with variables as values.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       "geo" directive was used with the "ranges" parameter but without the
+       "default" parameter; the bug had appeared in 0.8.43.
+       Thanks to Zhen Chen and Weibin Yao.
+
+    *) Bugfix: in the -p command-line parameter handling.
+
+    *) Bugfix: in the mail proxy server.
+
+    *) Bugfix: of minor potential bugs.
+       Thanks to Coverity.
+
+    *) Bugfix: nginx/Windows could not be built with Visual Studio 2005
+       Express.
+       Thanks to HAYASHI Kentaro.
+
+
+Changes with nginx 1.3.4                                         31 Jul 2012
+
+    *) Change: the "ipv6only" parameter is now turned on by default for
+       listening IPv6 sockets.
+
+    *) Feature: the Clang compiler support.
+
+    *) Bugfix: extra listening sockets might be created.
+       Thanks to Roman Odaisky.
+
+    *) Bugfix: nginx/Windows might hog CPU if a worker process failed to
+       start.
+       Thanks to Ricardo Villalobos Guevara.
+
+    *) Bugfix: the "proxy_pass_header", "fastcgi_pass_header",
+       "scgi_pass_header", "uwsgi_pass_header", "proxy_hide_header",
+       "fastcgi_hide_header", "scgi_hide_header", and "uwsgi_hide_header"
+       directives might be inherited incorrectly.
+
+
+Changes with nginx 1.3.3                                         10 Jul 2012
+
+    *) Feature: entity tags support and the "etag" directive.
+
+    *) Bugfix: trailing dot in a source value was not ignored if the "map"
+       directive was used with the "hostnames" parameter.
+
+    *) Bugfix: incorrect location might be used to process a request if a
+       URI was changed via a "rewrite" directive before an internal redirect
+       to a named location.
+
+
+Changes with nginx 1.3.2                                         26 Jun 2012
+
+    *) Change: the "single" parameter of the "keepalive" directive is now
+       ignored.
+
+    *) Change: SSL compression is now disabled when using all versions of
+       OpenSSL, including ones prior to 1.0.0.
+
+    *) Feature: it is now possible to use the "ip_hash" directive to balance
+       IPv6 clients.
+
+    *) Feature: the $status variable can now be used not only in the
+       "log_format" directive.
+
+    *) Bugfix: a segmentation fault might occur in a worker process on
+       shutdown if the "resolver" directive was used.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       ngx_http_mp4_module was used.
+
+    *) Bugfix: in the ngx_http_mp4_module.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if
+       conflicting wildcard server names were used.
+
+    *) Bugfix: nginx might be terminated abnormally on a SIGBUS signal on
+       ARM platform.
+
+    *) Bugfix: an alert "sendmsg() failed (9: Bad file number)" on HP-UX
+       while reconfiguration.
+
+
+Changes with nginx 1.3.1                                         05 Jun 2012
+
+    *) Security: now nginx/Windows ignores trailing dot in URI path
+       component, and does not allow URIs with ":$" in it.
+       Thanks to Vladimir Kochetkov, Positive Research Center.
+
+    *) Feature: the "proxy_pass", "fastcgi_pass", "scgi_pass", "uwsgi_pass"
+       directives, and the "server" directive inside the "upstream" block,
+       now support IPv6 addresses.
+
+    *) Feature: the "resolver" directive now supports IPv6 addresses and an
+       optional port specification.
+
+    *) Feature: the "least_conn" directive inside the "upstream" block.
+
+    *) Feature: it is now possible to specify a weight for servers while
+       using the "ip_hash" directive.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       "image_filter" directive was used; the bug had appeared in 1.3.0.
+
+    *) Bugfix: nginx could not be built with ngx_cpp_test_module; the bug
+       had appeared in 1.1.12.
+
+    *) Bugfix: access to variables from SSI and embedded perl module might
+       not work after reconfiguration.
+       Thanks to Yichun Zhang.
+
+    *) Bugfix: in the ngx_http_xslt_filter_module.
+       Thanks to Kuramoto Eiji.
+
+    *) Bugfix: memory leak if $geoip_org variable was used.
+       Thanks to Denis F. Latypoff.
+
+    *) Bugfix: in the "proxy_cookie_domain" and "proxy_cookie_path"
+       directives.
+
+
+Changes with nginx 1.3.0                                         15 May 2012
+
+    *) Feature: the "debug_connection" directive now supports IPv6 addresses
+       and the "unix:" parameter.
+
+    *) Feature: the "set_real_ip_from" directive and the "proxy" parameter
+       of the "geo" directive now support IPv6 addresses.
+
+    *) Feature: the "real_ip_recursive", "geoip_proxy", and
+       "geoip_proxy_recursive" directives.
+
+    *) Feature: the "proxy_recursive" parameter of the "geo" directive.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       "resolver" directive was used.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       "fastcgi_pass", "scgi_pass", or "uwsgi_pass" directives were used and
+       backend returned incorrect response.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       "rewrite" directive was used and new request arguments in a
+       replacement used variables.
+
+    *) Bugfix: nginx might hog CPU if the open file resource limit was
+       reached.
+
+    *) Bugfix: nginx might loop infinitely over backends if the
+       "proxy_next_upstream" directive with the "http_404" parameter was
+       used and there were backup servers specified in an upstream block.
+
+    *) Bugfix: adding the "down" parameter of the "server" directive might
+       cause unneeded client redistribution among backend servers if the
+       "ip_hash" directive was used.
+
+    *) Bugfix: socket leak.
+       Thanks to Yichun Zhang.
+
+    *) Bugfix: in the ngx_http_fastcgi_module.
+
+
+Changes with nginx 1.2.0                                         23 Apr 2012
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       "try_files" directive was used; the bug had appeared in 1.1.19.
+
+    *) Bugfix: response might be truncated if there were more than IOV_MAX
+       buffers used.
+
+    *) Bugfix: in the "crop" parameter of the "image_filter" directive.
+       Thanks to Maxim Bublis.
+
+
+Changes with nginx 1.1.19                                        12 Apr 2012
+
+    *) Security: specially crafted mp4 file might allow to overwrite memory
+       locations in a worker process if the ngx_http_mp4_module was used,
+       potentially resulting in arbitrary code execution (CVE-2012-2089).
+       Thanks to Matthew Daley.
+
+    *) Bugfix: nginx/Windows might be terminated abnormally.
+       Thanks to Vincent Lee.
+
+    *) Bugfix: nginx hogged CPU if all servers in an upstream were marked as
+       "backup".
+
+    *) Bugfix: the "allow" and "deny" directives might be inherited
+       incorrectly if they were used with IPv6 addresses.
+
+    *) Bugfix: the "modern_browser" and "ancient_browser" directives might
+       be inherited incorrectly.
+
+    *) Bugfix: timeouts might be handled incorrectly on Solaris/SPARC.
+
+    *) Bugfix: in the ngx_http_mp4_module.
+
+
+Changes with nginx 1.1.18                                        28 Mar 2012
+
+    *) Change: keepalive connections are no longer disabled for Safari by
+       default.
+
+    *) Feature: the $connection_requests variable.
+
+    *) Feature: $tcpinfo_rtt, $tcpinfo_rttvar, $tcpinfo_snd_cwnd and
+       $tcpinfo_rcv_space variables.
+
+    *) Feature: the "worker_cpu_affinity" directive now works on FreeBSD.
+
+    *) Feature: the "xslt_param" and "xslt_string_param" directives.
+       Thanks to Samuel Behan.
+
+    *) Bugfix: in configure tests.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: in the ngx_http_xslt_filter_module.
+
+    *) Bugfix: nginx could not be built on Debian GNU/Hurd.
+
+
+Changes with nginx 1.1.17                                        15 Mar 2012
+
+    *) Security: content of previously freed memory might be sent to a
+       client if backend returned specially crafted response.
+       Thanks to Matthew Daley.
+
+    *) Bugfix: in the embedded perl module if used from SSI.
+       Thanks to Matthew Daley.
+
+    *) Bugfix: in the ngx_http_uwsgi_module.
+
+
+Changes with nginx 1.1.16                                        29 Feb 2012
+
+    *) Change: the simultaneous subrequest limit has been raised to 200.
+
+    *) Feature: the "from" parameter of the "disable_symlinks" directive.
+
+    *) Feature: the "return" and "error_page" directives can now be used to
+       return 307 redirections.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       "resolver" directive was used and there was no "error_log" directive
+       specified at global level.
+       Thanks to Roman Arutyunyan.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if the
+       "proxy_http_version 1.1" or "fastcgi_keep_conn on" directives were
+       used.
+
+    *) Bugfix: memory leaks.
+       Thanks to Lanshun Zhou.
+
+    *) Bugfix: in the "disable_symlinks" directive.
+
+    *) Bugfix: on ZFS filesystem disk cache size might be calculated
+       incorrectly; the bug had appeared in 1.0.1.
+
+    *) Bugfix: nginx could not be built by the icc 12.1 compiler.
+
+    *) Bugfix: nginx could not be built by gcc on Solaris; the bug had
+       appeared in 1.1.15.
+
+
+Changes with nginx 1.1.15                                        15 Feb 2012
+
+    *) Feature: the "disable_symlinks" directive.
+
+    *) Feature: the "proxy_cookie_domain" and "proxy_cookie_path"
+       directives.
+
+    *) Bugfix: nginx might log incorrect error "upstream prematurely closed
+       connection" instead of correct "upstream sent too big header" one.
+       Thanks to Feibo Li.
+
+    *) Bugfix: nginx could not be built with the ngx_http_perl_module if the
+       --with-openssl option was used.
+
+    *) Bugfix: the number of internal redirects to named locations was not
+       limited.
+
+    *) Bugfix: calling $r->flush() multiple times might cause errors in the
+       ngx_http_gzip_filter_module.
+
+    *) Bugfix: temporary files might be not removed if the "proxy_store"
+       directive was used with SSI includes.
+
+    *) Bugfix: in some cases non-cacheable variables (such as the $args
+       variable) returned old empty cached value.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if too
+       many SSI subrequests were issued simultaneously; the bug had appeared
+       in 0.7.25.
+
+
+Changes with nginx 1.1.14                                        30 Jan 2012
+
+    *) Feature: multiple "limit_req" limits may be used simultaneously.
+
+    *) Bugfix: in error handling while connecting to a backend.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: in AIO error handling on FreeBSD.
+
+    *) Bugfix: in the OpenSSL library initialization.
+
+    *) Bugfix: the "proxy_redirect" directives might be inherited
+       incorrectly.
+
+    *) Bugfix: memory leak during reconfiguration if the "pcre_jit"
+       directive was used.
+
+
+Changes with nginx 1.1.13                                        16 Jan 2012
+
+    *) Feature: the "TLSv1.1" and "TLSv1.2" parameters of the
+       "ssl_protocols" directive.
+
+    *) Bugfix: the "limit_req" directive parameters were not inherited
+       correctly; the bug had appeared in 1.1.12.
+
+    *) Bugfix: the "proxy_redirect" directive incorrectly processed
+       "Refresh" header if regular expression were used.
+
+    *) Bugfix: the "proxy_cache_use_stale" directive with "error" parameter
+       did not return answer from cache if there were no live upstreams.
+
+    *) Bugfix: the "worker_cpu_affinity" directive might not work.
+
+    *) Bugfix: nginx could not be built on Solaris; the bug had appeared in
+       1.1.12.
+
+    *) Bugfix: in the ngx_http_mp4_module.
+
+
+Changes with nginx 1.1.12                                        26 Dec 2011
+
+    *) Change: a "proxy_pass" directive without URI part now uses changed
+       URI after redirection with the "error_page" directive.
+       Thanks to Lanshun Zhou.
+
+    *) Feature: the "proxy/fastcgi/scgi/uwsgi_cache_lock",
+       "proxy/fastcgi/scgi/uwsgi_cache_lock_timeout" directives.
+
+    *) Feature: the "pcre_jit" directive.
+
+    *) Feature: the "if" SSI command supports captures in regular
+       expressions.
+
+    *) Bugfix: the "if" SSI command did not work inside the "block" command.
+
+    *) Bugfix: the "limit_conn_log_level" and "limit_req_log_level"
+       directives might not work.
+
+    *) Bugfix: the "limit_rate" directive did not allow to use full
+       throughput, even if limit value was very high.
+
+    *) Bugfix: the "sendfile_max_chunk" directive did not work, if the
+       "limit_rate" directive was used.
+
+    *) Bugfix: a "proxy_pass" directive without URI part always used
+       original request URI if variables were used.
+
+    *) Bugfix: a "proxy_pass" directive without URI part might use original
+       request after redirection with the "try_files" directive.
+       Thanks to Lanshun Zhou.
+
+    *) Bugfix: in the ngx_http_scgi_module.
+
+    *) Bugfix: in the ngx_http_mp4_module.
+
+    *) Bugfix: nginx could not be built on Solaris; the bug had appeared in
+       1.1.9.
+
+
+Changes with nginx 1.1.11                                        12 Dec 2011
+
+    *) Feature: the "so_keepalive" parameter of the "listen" directive.
+       Thanks to Vsevolod Stakhov.
+
+    *) Feature: the "if_not_empty" parameter of the
+       "fastcgi/scgi/uwsgi_param" directives.
+
+    *) Feature: the $https variable.
+
+    *) Feature: the "proxy_redirect" directive supports variables in the
+       first parameter.
+
+    *) Feature: the "proxy_redirect" directive supports regular expressions.
+
+    *) Bugfix: the $sent_http_cache_control variable might contain a wrong
+       value if the "expires" directive was used.
+       Thanks to Yichun Zhang.
+
+    *) Bugfix: the "read_ahead" directive might not work combined with
+       "try_files" and "open_file_cache".
+
+    *) Bugfix: a segmentation fault might occur in a worker process if small
+       time was used in the "inactive" parameter of the "proxy_cache_path"
+       directive.
+
+    *) Bugfix: responses from cache might hang.
+
+
+Changes with nginx 1.1.10                                        30 Nov 2011
+
+    *) Bugfix: a segmentation fault occurred in a worker process if AIO was
+       used on Linux; the bug had appeared in 1.1.9.
+
+
+Changes with nginx 1.1.9                                         28 Nov 2011
+
+    *) Change: now double quotes are encoded in an "echo" SSI-command
+       output.
+       Thanks to Zaur Abasmirzoev.
+
+    *) Feature: the "valid" parameter of the "resolver" directive. By
+       default TTL returned by a DNS server is used.
+       Thanks to Kirill A. Korinskiy.
+
+    *) Bugfix: nginx might hang after a worker process abnormal termination.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if SNI
+       was used; the bug had appeared in 1.1.2.
+
+    *) Bugfix: in the "keepalive_disable" directive; the bug had appeared in
+       1.1.8.
+       Thanks to Alexander Usov.
+
+    *) Bugfix: SIGWINCH signal did not work after first binary upgrade; the
+       bug had appeared in 1.1.1.
+
+    *) Bugfix: backend responses with length not matching "Content-Length"
+       header line are no longer cached.
+
+    *) Bugfix: in the "scgi_param" directive, if complex parameters were
+       used.
+
+    *) Bugfix: in the "epoll" event method.
+       Thanks to Yichun Zhang.
+
+    *) Bugfix: in the ngx_http_flv_module.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: in the ngx_http_mp4_module.
+
+    *) Bugfix: IPv6 addresses are now handled properly in a request line and
+       in a "Host" request header line.
+
+    *) Bugfix: "add_header" and "expires" directives did not work if a
+       request was proxied and response status code was 206.
+
+    *) Bugfix: nginx could not be built on FreeBSD 10.
+
+    *) Bugfix: nginx could not be built on AIX.
+
+
+Changes with nginx 1.1.8                                         14 Nov 2011
+
+    *) Change: the ngx_http_limit_zone_module was renamed to the
+       ngx_http_limit_conn_module.
+
+    *) Change: the "limit_zone" directive was superseded by the
+       "limit_conn_zone" directive with a new syntax.
+
+    *) Feature: support for multiple "limit_conn" limits on the same level.
+
+    *) Feature: the "image_filter_sharpen" directive.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if
+       resolver got a big DNS response.
+       Thanks to Ben Hawkes.
+
+    *) Bugfix: in cache key calculation if internal MD5 implementation was
+       used; the bug had appeared in 1.0.4.
+
+    *) Bugfix: the "If-Modified-Since", "If-Range", etc. client request
+       header lines might be passed to backend while caching; or not passed
+       without caching if caching was enabled in another part of the
+       configuration.
+
+    *) Bugfix: the module ngx_http_mp4_module sent incorrect
+       "Content-Length" response header line if the "start" argument was
+       used.
+       Thanks to Piotr Sikora.
+
+
+Changes with nginx 1.1.7                                         31 Oct 2011
+
+    *) Feature: support of several DNS servers in the "resolver" directive.
+       Thanks to Kirill A. Korinskiy.
+
+    *) Bugfix: a segmentation fault occurred on start or during
+       reconfiguration if the "ssl" directive was used at http level and
+       there was no "ssl_certificate" defined.
+
+    *) Bugfix: reduced memory consumption while proxying big files if they
+       were buffered to disk.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if
+       "proxy_http_version 1.1" directive was used.
+
+    *) Bugfix: in the "expires @time" directive.
+
+
+Changes with nginx 1.1.6                                         17 Oct 2011
+
+    *) Change in internal API: now module context data are cleared while
+       internal redirect to named location.
+       Requested by Yichun Zhang.
+
+    *) Change: if a server in an upstream failed, only one request will be
+       sent to it after fail_timeout; the server will be considered alive if
+       it will successfully respond to the request.
+
+    *) Change: now the 0x7F-0xFF characters are escaped as \xXX in an
+       access_log.
+
+    *) Feature: "proxy/fastcgi/scgi/uwsgi_ignore_headers" directives support
+       the following additional values: X-Accel-Limit-Rate,
+       X-Accel-Buffering, X-Accel-Charset.
+
+    *) Feature: decrease of memory consumption if SSL is used.
+
+    *) Bugfix: some UTF-8 characters were processed incorrectly.
+       Thanks to Alexey Kuts.
+
+    *) Bugfix: the ngx_http_rewrite_module directives specified at "server"
+       level were executed twice if no matching locations were defined.
+
+    *) Bugfix: a socket leak might occurred if "aio sendfile" was used.
+
+    *) Bugfix: connections with fast clients might be closed after
+       send_timeout if file AIO was used.
+
+    *) Bugfix: in the ngx_http_autoindex_module.
+
+    *) Bugfix: the module ngx_http_mp4_module did not support seeking on
+       32-bit platforms.
+
+
+Changes with nginx 1.1.5                                         05 Oct 2011
+
+    *) Feature: the "uwsgi_buffering" and "scgi_buffering" directives.
+       Thanks to Peter Smit.
+
+    *) Bugfix: non-cacheable responses might be cached if
+       "proxy_cache_bypass" directive was used.
+       Thanks to John Ferlito.
+
+    *) Bugfix: in HTTP/1.1 support in the ngx_http_proxy_module.
+
+    *) Bugfix: cached responses with an empty body were returned
+       incorrectly; the bug had appeared in 0.8.31.
+
+    *) Bugfix: 201 responses of the ngx_http_dav_module were incorrect; the
+       bug had appeared in 0.8.32.
+
+    *) Bugfix: in the "return" directive.
+
+    *) Bugfix: the "ssl_session_cache builtin" directive caused segmentation
+       fault; the bug had appeared in 1.1.1.
+
+
+Changes with nginx 1.1.4                                         20 Sep 2011
+
+    *) Feature: the ngx_http_upstream_keepalive module.
+
+    *) Feature: the "proxy_http_version" directive.
+
+    *) Feature: the "fastcgi_keep_conn" directive.
+
+    *) Feature: the "worker_aio_requests" directive.
+
+    *) Bugfix: if nginx was built --with-file-aio it could not be run on
+       Linux kernel which did not support AIO.
+
+    *) Bugfix: in Linux AIO error processing.
+       Thanks to Hagai Avrahami.
+
+    *) Bugfix: reduced memory consumption for long-lived requests.
+
+    *) Bugfix: the module ngx_http_mp4_module did not support 64-bit MP4
+       "co64" atom.
+
+
+Changes with nginx 1.1.3                                         14 Sep 2011
+
+    *) Feature: the module ngx_http_mp4_module.
+
+    *) Bugfix: in Linux AIO combined with open_file_cache.
+
+    *) Bugfix: open_file_cache did not update file info on retest if file
+       was not atomically changed.
+
+    *) Bugfix: nginx could not be built on MacOSX 10.7.
+
+
+Changes with nginx 1.1.2                                         05 Sep 2011
+
+    *) Change: now if total size of all ranges is greater than source
+       response size, then nginx disables ranges and returns just the source
+       response.
+
+    *) Feature: the "max_ranges" directive.
+
+    *) Bugfix: the "ssl_verify_client", "ssl_verify_depth", and
+       "ssl_prefer_server_ciphers" directives might work incorrectly if SNI
+       was used.
+
+    *) Bugfix: in the "proxy/fastcgi/scgi/uwsgi_ignore_client_abort"
+       directives.
+
+
+Changes with nginx 1.1.1                                         22 Aug 2011
+
+    *) Change: now cache loader processes either as many files as specified
+       by "loader_files" parameter or works no longer than time specified by
+       the "loader_threshold" parameter during each iteration.
+
+    *) Change: now SIGWINCH signal works only in daemon mode.
+
+    *) Feature: now shared zones and caches use POSIX semaphores on Solaris.
+       Thanks to Den Ivanov.
+
+    *) Feature: accept filters are now supported on NetBSD.
+
+    *) Bugfix: nginx could not be built on Linux 3.0.
+
+    *) Bugfix: nginx did not use gzipping in some cases; the bug had
+       appeared in 1.1.0.
+
+    *) Bugfix: request body might be processed incorrectly if client used
+       pipelining.
+
+    *) Bugfix: in the "request_body_in_single_buf" directive.
+
+    *) Bugfix: in "proxy_set_body" and "proxy_pass_request_body" directives
+       if SSL connection to backend was used.
+
+    *) Bugfix: nginx hogged CPU if all servers in an upstream were marked as
+       "down".
+
+    *) Bugfix: a segmentation fault might occur during reconfiguration if
+       ssl_session_cache was defined but not used in previous configuration.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if many
+       backup servers were used in an upstream.
+
+    *) Bugfix: a segmentation fault might occur in a worker process if
+       "fastcgi/scgi/uwsgi_param" directives were used with values starting
+       with "HTTP_"; the bug had appeared in 0.8.40.
+
+
+Changes with nginx 1.1.0                                         01 Aug 2011
+
+    *) Feature: cache loader run time decrease.
+
+    *) Feature: "loader_files", "loader_sleep", and "loader_threshold"
+       options of the "proxy/fastcgi/scgi/uwsgi_cache_path" directives.
+
+    *) Feature: loading time decrease of configuration with large number of
+       HTTPS sites.
+
+    *) Feature: now nginx supports ECDHE key exchange ciphers.
+       Thanks to Adrian Kotelba.
+
+    *) Feature: the "lingering_close" directive.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: in closing connection for pipelined requests.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: nginx did not disable gzipping if client sent "gzip;q=0" in
+       "Accept-Encoding" request header line.
+
+    *) Bugfix: in timeout in unbuffered proxied mode.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: memory leaks when a "proxy_pass" directive contains variables
+       and proxies to an HTTPS backend.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: in parameter validation of a "proxy_pass" directive with
+       variables.
+       Thanks to Lanshun Zhou.
+
+    *) Bugfix: SSL did not work on QNX.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: SSL modules could not be built by gcc 4.6 without
+       --with-debug option.
+
+
+Changes with nginx 1.0.5                                         19 Jul 2011
+
+    *) Change: now default SSL ciphers are "HIGH:!aNULL:!MD5".
+       Thanks to Rob Stradling.
+
+    *) Feature: the "referer_hash_max_size" and "referer_hash_bucket_size"
+       directives.
+       Thanks to Witold Filipczyk.
+
+    *) Feature: $uid_reset variable.
+
+    *) Bugfix: a segmentation fault might occur in a worker process, if a
+       caching was used.
+       Thanks to Lanshun Zhou.
+
+    *) Bugfix: worker processes may got caught in an endless loop during
+       reconfiguration, if a caching was used; the bug had appeared in
+       0.8.48.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: "stalled cache updating" alert.
+       Thanks to Maxim Dounin.
+
+
+Changes with nginx 1.0.4                                         01 Jun 2011
+
+    *) Change: now regular expressions case sensitivity in the "map"
+       directive is given by prefixes "~" or "~*".
+
+    *) Feature: now shared zones and caches use POSIX semaphores on Linux.
+       Thanks to Denis F. Latypoff.
+
+    *) Bugfix: "stalled cache updating" alert.
+
+    *) Bugfix: nginx could not be built --without-http_auth_basic_module;
+       the bug had appeared in 1.0.3.
+
+
+Changes with nginx 1.0.3                                         25 May 2011
+
+    *) Feature: the "auth_basic_user_file" directive supports "$apr1",
+       "{PLAIN}", and "{SSHA}" password encryption methods.
+       Thanks to Maxim Dounin.
+
+    *) Feature: the "geoip_org" directive and $geoip_org variable.
+       Thanks to Alexander Uskov, Arnaud Granal, and Denis F. Latypoff.
+
+    *) Feature: ngx_http_geo_module and ngx_http_geoip_module support IPv4
+       addresses mapped to IPv6 addresses.
+
+    *) Bugfix: a segmentation fault occurred in a worker process during
+       testing IPv4 address mapped to IPv6 address, if access or deny rules
+       were defined only for IPv6; the bug had appeared in 0.8.22.
+
+    *) Bugfix: a cached response may be broken if "proxy/fastcgi/scgi/
+       uwsgi_cache_bypass" and "proxy/fastcgi/scgi/uwsgi_no_cache" directive
+       values were different; the bug had appeared in 0.8.46.
+
+
+Changes with nginx 1.0.2                                         10 May 2011
+
+    *) Feature: now shared zones and caches use POSIX semaphores.
+
+    *) Bugfix: in the "rotate" parameter of the "image_filter" directive.
+       Thanks to Adam Bocim.
+
+    *) Bugfix: nginx could not be built on Solaris; the bug had appeared in
+       1.0.1.
+
+
+Changes with nginx 1.0.1                                         03 May 2011
+
+    *) Change: now the "split_clients" directive uses MurmurHash2 algorithm
+       because of better distribution.
+       Thanks to Oleg Mamontov.
+
+    *) Change: now long strings starting with zero are not considered as
+       false values.
+       Thanks to Maxim Dounin.
+
+    *) Change: now nginx uses a default listen backlog value 511 on Linux.
+
+    *) Feature: the $upstream_... variables may be used in the SSI and perl
+       modules.
+
+    *) Bugfix: now nginx limits better disk cache size.
+       Thanks to Oleg Mamontov.
+
+    *) Bugfix: a segmentation fault might occur while parsing incorrect IPv4
+       address; the bug had appeared in 0.9.3.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: nginx could not be built by gcc 4.6 without --with-debug
+       option.
+
+    *) Bugfix: nginx could not be built on Solaris 9 and earlier; the bug
+       had appeared in 0.9.3.
+       Thanks to Dagobert Michelsen.
+
+    *) Bugfix: $request_time variable had invalid values if subrequests were
+       used; the bug had appeared in 0.8.47.
+       Thanks to Igor A. Valcov.
+
+
+Changes with nginx 1.0.0                                         12 Apr 2011
+
+    *) Bugfix: a cache manager might hog CPU after reload.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: an "image_filter crop" directive worked incorrectly coupled
+       with an "image_filter rotate 180" directive.
+
+    *) Bugfix: a "satisfy any" directive disabled custom 401 error page.
+
+
+Changes with nginx 0.9.7                                         04 Apr 2011
+
+    *) Feature: now keepalive connections may be closed premature, if there
+       are no free worker connections.
+       Thanks to Maxim Dounin.
+
+    *) Feature: the "rotate" parameter of the "image_filter" directive.
+       Thanks to Adam Bocim.
+
+    *) Bugfix: a case when a backend in "fastcgi_pass", "scgi_pass", or
+       "uwsgi_pass" directives is given by expression and refers to a
+       defined upstream.
+
+
+Changes with nginx 0.9.6                                         21 Mar 2011
+
+    *) Feature: the "map" directive supports regular expressions as value of
+       the first parameter.
+
+    *) Feature: $time_iso8601 access_log variable.
+       Thanks to Michael Lustfield.
+
+
+Changes with nginx 0.9.5                                         21 Feb 2011
+
+    *) Change: now nginx uses a default listen backlog value -1 on Linux.
+       Thanks to Andrei Nigmatulin.
+
+    *) Feature: the "utf8" parameter of "geoip_country" and "geoip_city"
+       directives.
+       Thanks to Denis F. Latypoff.
+
+    *) Bugfix: in a default "proxy_redirect" directive if "proxy_pass"
+       directive has no URI part.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: an "error_page" directive did not work with nonstandard error
+       codes; the bug had appeared in 0.8.53.
+       Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.9.4                                         21 Jan 2011
+
+    *) Feature: the "server_name" directive supports the $hostname variable.
+
+    *) Feature: 494 code for "Request Header Too Large" error.
+
+
+Changes with nginx 0.9.3                                         13 Dec 2010
+
+    *) Bugfix: if there was a single server for given IPv6 address:port
+       pair, then captures in regular expressions in a "server_name"
+       directive did not work.
+
+    *) Bugfix: nginx could not be built on Solaris; the bug had appeared in
+       0.9.0.
+
+
+Changes with nginx 0.9.2                                         06 Dec 2010
+
+    *) Feature: the "If-Unmodified-Since" client request header line
+       support.
+
+    *) Workaround: fallback to accept() syscall if accept4() was not
+       implemented; the issue had appeared in 0.9.0.
+
+    *) Bugfix: nginx could not be built on Cygwin; the bug had appeared in
+       0.9.0.
+
+    *) Bugfix: for OpenSSL vulnerability CVE-2010-4180.
+       Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.9.1                                         30 Nov 2010
+
+    *) Bugfix: "return CODE message" directives did not work; the bug had
+       appeared in 0.9.0.
+
+
+Changes with nginx 0.9.0                                         29 Nov 2010
+
+    *) Feature: the "keepalive_disable" directive.
+
+    *) Feature: the "map" directive supports variables as value of a defined
+       variable.
+
+    *) Feature: the "map" directive supports empty strings as value of the
+       first parameter.
+
+    *) Feature: the "map" directive supports expressions as the first
+       parameter.
+
+    *) Feature: nginx(8) manual page.
+       Thanks to Sergey Osokin.
+
+    *) Feature: Linux accept4() support.
+       Thanks to Simon Liu.
+
+    *) Workaround: elimination of Linux linker warning about "sys_errlist"
+       and "sys_nerr"; the warning had appeared in 0.8.35.
+
+    *) Bugfix: a segmentation fault might occur in a worker process, if the
+       "auth_basic" directive was used.
+       Thanks to Michail Laletin.
+
+    *) Bugfix: compatibility with ngx_http_eval_module; the bug had appeared
+       in 0.8.42.
+
+
+Changes with nginx 0.8.53                                        18 Oct 2010
+
+    *) Feature: now the "error_page" directive allows to change a status
+       code in a redirect.
+
+    *) Feature: the "gzip_disable" directive supports special "degradation"
+       mask.
+
+    *) Bugfix: a socket leak might occurred if file AIO was used.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: if the first server had no "listen" directive and there was
+       no explicit default server, then a next server with a "listen"
+       directive became the default server; the bug had appeared in 0.8.21.
+
+
+Changes with nginx 0.8.52                                        28 Sep 2010
+
+    *) Bugfix: nginx used SSL mode for a listen socket if any listen option
+       was set; the bug had appeared in 0.8.51.
+
+
+Changes with nginx 0.8.51                                        27 Sep 2010
+
+    *) Change: the "secure_link_expires" directive has been canceled.
+
+    *) Change: a logging level of resolver errors has been lowered from
+       "alert" to "error".
+
+    *) Feature: now a listen socket "ssl" parameter may be set several
+       times.
+
+
+Changes with nginx 0.8.50                                        02 Sep 2010
+
+    *) Feature: the "secure_link", "secure_link_md5", and
+       "secure_link_expires" directives of the ngx_http_secure_link_module.
+
+    *) Feature: the -q switch.
+       Thanks to Gena Makhomed.
+
+    *) Bugfix: worker processes may got caught in an endless loop during
+       reconfiguration, if a caching was used; the bug had appeared in
+       0.8.48.
+
+    *) Bugfix: in the "gzip_disable" directive.
+       Thanks to Derrick Petzold.
+
+    *) Bugfix: nginx/Windows could not send stop, quit, reopen, and reload
+       signals to a process run in other session.
+
+
+Changes with nginx 0.8.49                                        09 Aug 2010
+
+    *) Feature: the "image_filter_jpeg_quality" directive supports
+       variables.
+
+    *) Bugfix: a segmentation fault might occur in a worker process, if the
+       $geoip_region_name variables was used; the bug had appeared in
+       0.8.48.
+
+    *) Bugfix: errors intercepted by error_page were cached only for next
+       request; the bug had appeared in 0.8.48.
+
+
+Changes with nginx 0.8.48                                        03 Aug 2010
+
+    *) Change: now the "server_name" directive default value is an empty
+       name "".
+       Thanks to Gena Makhomed.
+
+    *) Change: now the "server_name_in_redirect" directive default value is
+       "off".
+
+    *) Feature: the $geoip_dma_code, $geoip_area_code, and
+       $geoip_region_name variables.
+       Thanks to Christine McGonagle.
+
+    *) Bugfix: the "proxy_pass", "fastcgi_pass", "uwsgi_pass", and
+       "scgi_pass" directives were not inherited inside "limit_except"
+       blocks.
+
+    *) Bugfix: the "proxy_cache_min_uses", "fastcgi_cache_min_uses"
+       "uwsgi_cache_min_uses", and "scgi_cache_min_uses" directives did not
+       work; the bug had appeared in 0.8.46.
+
+    *) Bugfix: the "fastcgi_split_path_info" directive used incorrectly
+       captures, if only parts of an URI were captured.
+       Thanks to Yuriy Taraday and Frank Enderle.
+
+    *) Bugfix: the "rewrite" directive did not escape a ";" character during
+       copying from URI to query string.
+       Thanks to Daisuke Murase.
+
+    *) Bugfix: the ngx_http_image_filter_module closed a connection, if an
+       image was larger than "image_filter_buffer" size.
+
+
+Changes with nginx 0.8.47                                        28 Jul 2010
+
+    *) Bugfix: $request_time variable had invalid values for subrequests.
+
+    *) Bugfix: errors intercepted by error_page could not be cached.
+
+    *) Bugfix: a cache manager process may got caught in an endless loop, if
+       max_size parameter was used; the bug had appeared in 0.8.46.
+
+
+Changes with nginx 0.8.46                                        19 Jul 2010
+
+    *) Change: now the "proxy_no_cache", "fastcgi_no_cache",
+       "uwsgi_no_cache", and "scgi_no_cache" directives affect on a cached
+       response saving only.
+
+    *) Feature: the "proxy_cache_bypass", "fastcgi_cache_bypass",
+       "uwsgi_cache_bypass", and "scgi_cache_bypass" directives.
+
+    *) Bugfix: nginx did not free memory in cache keys zones if there was an
+       error during working with backend: the memory was freed only after
+       inactivity time or on memory low condition.
+
+
+Changes with nginx 0.8.45                                        13 Jul 2010
+
+    *) Feature: ngx_http_xslt_filter improvements.
+       Thanks to Laurence Rowe.
+
+    *) Bugfix: SSI response might be truncated after include with
+       wait="yes"; the bug had appeared in 0.7.25.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: the "listen" directive did not support the "setfib=0"
+       parameter.
+
+
+Changes with nginx 0.8.44                                        05 Jul 2010
+
+    *) Change: now nginx does not cache by default backend responses, if
+       they have a "Set-Cookie" header line.
+
+    *) Feature: the "listen" directive supports the "setfib" parameter.
+       Thanks to Andrew Filonov.
+
+    *) Bugfix: the "sub_filter" directive might change character case on
+       partial match.
+
+    *) Bugfix: compatibility with HP/UX.
+
+    *) Bugfix: compatibility with AIX xlC_r compiler.
+
+    *) Bugfix: nginx treated large SSLv2 packets as plain requests.
+       Thanks to Miroslaw Jaworski.
+
+
+Changes with nginx 0.8.43                                        30 Jun 2010
+
+    *) Feature: large geo ranges base loading speed-up.
+
+    *) Bugfix: an error_page redirection to "location /zero {return 204;}"
+       without changing status code kept the error body; the bug had
+       appeared in 0.8.42.
+
+    *) Bugfix: nginx might close IPv6 listen socket during reconfiguration.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: the $uid_set variable may be used at any request processing
+       stage.
+
+
+Changes with nginx 0.8.42                                        21 Jun 2010
+
+    *) Change: now nginx tests locations given by regular expressions, if
+       request was matched exactly by a location given by a prefix string.
+       The previous behavior has been introduced in 0.7.1.
+
+    *) Feature: the ngx_http_scgi_module.
+       Thanks to Manlio Perillo.
+
+    *) Feature: a text answer may be added to a "return" directive.
+
+
+Changes with nginx 0.8.41                                        15 Jun 2010
+
+    *) Security: nginx/Windows worker might be terminated abnormally if a
+       requested file name has invalid UTF-8 encoding.
+
+    *) Change: now nginx allows to use spaces in a request line.
+
+    *) Bugfix: the "proxy_redirect" directive changed incorrectly a backend
+       "Refresh" response header line.
+       Thanks to Andrey Andreew and Max Sogin.
+
+    *) Bugfix: nginx did not support path without host name in "Destination"
+       request header line.
+
+
+Changes with nginx 0.8.40                                        07 Jun 2010
+
+    *) Security: now nginx/Windows ignores default file stream name.
+       Thanks to Jose Antonio Vazquez Gonzalez.
+
+    *) Feature: the ngx_http_uwsgi_module.
+       Thanks to Roberto De Ioris.
+
+    *) Feature: a "fastcgi_param" directive with value starting with "HTTP_"
+       overrides a client request header line.
+
+    *) Bugfix: the "If-Modified-Since", "If-Range", etc. client request
+       header lines were passed to FastCGI-server while caching.
+
+    *) Bugfix: listen unix domain socket could not be changed during
+       reconfiguration.
+       Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.8.39                                        31 May 2010
+
+    *) Bugfix: an inherited "alias" directive worked incorrectly in
+       inclusive location.
+
+    *) Bugfix: in "alias" with variables and "try_files" directives
+       combination.
+
+    *) Bugfix: listen unix domain and IPv6 sockets did not inherit while
+       online upgrade.
+       Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.8.38                                        24 May 2010
+
+    *) Feature: the "proxy_no_cache" and "fastcgi_no_cache" directives.
+
+    *) Feature: now the "rewrite" directive does a redirect automatically if
+       the $scheme variable is used.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: now "limit_req" delay directive conforms to the described
+       algorithm.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: the $uid_got variable might not be used in the SSI and perl
+       modules.
+
+
+Changes with nginx 0.8.37                                        17 May 2010
+
+    *) Feature: the ngx_http_split_clients_module.
+
+    *) Feature: the "map" directive supports keys more than 255 characters.
+
+    *) Bugfix: nginx ignored the "private" and "no-store" values in the
+       "Cache-Control" backend response header line.
+
+    *) Bugfix: a "stub" parameter of an "include" SSI directive was not
+       used, if empty response has 200 status code.
+
+    *) Bugfix: if a proxied or FastCGI request was internally redirected to
+       another proxied or FastCGI location, then a segmentation fault might
+       occur in a worker process; the bug had appeared in 0.8.33.
+       Thanks to Yichun Zhang.
+
+    *) Bugfix: IMAP connections may hang until they timed out while talking
+       to Zimbra server.
+       Thanks to Alan Batie.
+
+
+Changes with nginx 0.8.36                                        22 Apr 2010
+
+    *) Bugfix: the ngx_http_dav_module handled incorrectly the DELETE, COPY,
+       and MOVE methods for symlinks.
+
+    *) Bugfix: values of the $query_string, $arg_..., etc. variables cached
+       in main request were used by the SSI module in subrequests.
+
+    *) Bugfix: a variable value was repeatedly encoded after each an "echo"
+       SSI-command output; the bug had appeared in 0.6.14.
+
+    *) Bugfix: a worker process hung if a FIFO file was requested.
+       Thanks to Vicente Aguilar and Maxim Dounin.
+
+    *) Bugfix: OpenSSL-1.0.0 compatibility on 64-bit Linux.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: nginx could not be built --without-http-cache; the bug had
+       appeared in 0.8.35.
+
+
+Changes with nginx 0.8.35                                        01 Apr 2010
+
+    *) Change: now the charset filter runs before the SSI filter.
+
+    *) Feature: the "chunked_transfer_encoding" directive.
+
+    *) Bugfix: an "&" character was not escaped when it was copied in
+       arguments part in a rewrite rule.
+
+    *) Bugfix: nginx might be terminated abnormally while a signal
+       processing or if the directive "timer_resolution" was used on
+       platforms which do not support kqueue or eventport notification
+       methods.
+       Thanks to George Xie and Maxim Dounin.
+
+    *) Bugfix: if temporary files and permanent storage area resided at
+       different file systems, then permanent file modification times were
+       incorrect.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: ngx_http_memcached_module might issue the error message
+       "memcached sent invalid trailer".
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: nginx could not built zlib-1.2.4 library using the library
+       sources.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: a segmentation fault occurred in a worker process, if there
+       was large stderr output before FastCGI response; the bug had appeared
+       in 0.8.34.
+       Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.8.34                                        03 Mar 2010
+
+    *) Bugfix: nginx did not support all ciphers and digests used in client
+       certificates.
+       Thanks to Innocenty Enikeew.
+
+    *) Bugfix: nginx cached incorrectly FastCGI responses if there was large
+       stderr output before response.
+
+    *) Bugfix: nginx did not support HTTPS referrers.
+
+    *) Bugfix: nginx/Windows might not find file if path in configuration
+       was given in other character case; the bug had appeared in 0.8.33.
+
+    *) Bugfix: the $date_local variable has an incorrect value, if the "%s"
+       format was used.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: if ssl_session_cache was not set or was set to "none", then
+       during client certificate verify the error "session id context
+       uninitialized" might occur; the bug had appeared in 0.7.1.
+
+    *) Bugfix: a geo range returned default value if the range included two
+       or more /16 networks and did not begin at /16 network boundary.
+
+    *) Bugfix: a block used in a "stub" parameter of an "include" SSI
+       directive was output with "text/plain" MIME type.
+
+    *) Bugfix: $r->sleep() did not work; the bug had appeared in 0.8.11.
+
+
+Changes with nginx 0.8.33                                        01 Feb 2010
+
+    *) Security: now nginx/Windows ignores trailing spaces in URI.
+       Thanks to Dan Crowley, Core Security Technologies.
+
+    *) Security: now nginx/Windows ignores short files names.
+       Thanks to Dan Crowley, Core Security Technologies.
+
+    *) Change: now keepalive connections after POST requests are not
+       disabled for MSIE 7.0+.
+       Thanks to Adam Lounds.
+
+    *) Workaround: now keepalive connections are disabled for Safari.
+       Thanks to Joshua Sierles.
+
+    *) Bugfix: if a proxied or FastCGI request was internally redirected to
+       another proxied or FastCGI location, then $upstream_response_time
+       variable may have abnormally large value; the bug had appeared in
+       0.8.7.
+
+    *) Bugfix: a segmentation fault might occur in a worker process, while
+       discarding a request body; the bug had appeared in 0.8.11.
+
+
+Changes with nginx 0.8.32                                        11 Jan 2010
+
+    *) Bugfix: UTF-8 encoding usage in the ngx_http_autoindex_module.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: regular expression named captures worked for two names only.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: now the "localhost" name is used in the "Host" request header
+       line, if an unix domain socket is defined in the "auth_http"
+       directive.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: nginx did not support chunked transfer encoding for 201
+       responses.
+       Thanks to Julian Reich.
+
+    *) Bugfix: if the "expires modified" set date in the past, then a
+       negative number was set in the "Cache-Control" response header line.
+       Thanks to Alex Kapranoff.
+
+
+Changes with nginx 0.8.31                                        23 Dec 2009
+
+    *) Feature: now the "error_page" directive may redirect the 301 and 302
+       responses.
+
+    *) Feature: the $geoip_city_continent_code, $geoip_latitude, and
+       $geoip_longitude variables.
+       Thanks to Arvind Sundararajan.
+
+    *) Feature: now the ngx_http_image_filter_module deletes always EXIF and
+       other application specific data if the data consume more than 5% of a
+       JPEG file.
+
+    *) Bugfix: nginx closed a connection if a cached response had an empty
+       body.
+       Thanks to Piotr Sikora.
+
+    *) Bugfix: nginx might not be built by gcc 4.x if the -O2 or higher
+       optimization option was used.
+       Thanks to Maxim Dounin and Denis F. Latypoff.
+
+    *) Bugfix: regular expressions in location were always tested in
+       case-sensitive mode; the bug had appeared in 0.8.25.
+
+    *) Bugfix: nginx cached a 304 response if there was the "If-None-Match"
+       header line in a proxied request.
+       Thanks to Tim Dettrick and David Kostal.
+
+    *) Bugfix: nginx/Windows tried to delete a temporary file twice if the
+       file should replace an already existent file.
+
+
+Changes with nginx 0.8.30                                        15 Dec 2009
+
+    *) Change: now the default buffer size of the
+       "large_client_header_buffers" directive is 8K.
+       Thanks to Andrew Cholakian.
+
+    *) Feature: the conf/fastcgi.conf for simple FastCGI configurations.
+
+    *) Bugfix: nginx/Windows tried to rename a temporary file twice if the
+       file should replace an already existent file.
+
+    *) Bugfix: of "double free or corruption" error issued if host could not
+       be resolved; the bug had appeared in 0.8.22.
+       Thanks to Konstantin Svist.
+
+    *) Bugfix: in libatomic usage on some platforms.
+       Thanks to W-Mark Kubacki.
+
+
+Changes with nginx 0.8.29                                        30 Nov 2009
+
+    *) Change: now the "009" status code is written to an access log for
+       proxied HTTP/0.9 responses.
+
+    *) Feature: the "addition_types", "charset_types", "gzip_types",
+       "ssi_types", "sub_filter_types", and "xslt_types" directives support
+       an "*" parameter.
+
+    *) Feature: GCC 4.1+ built-in atomic operations usage.
+       Thanks to W-Mark Kubacki.
+
+    *) Feature: the --with-libatomic[=DIR] option in the configure.
+       Thanks to W-Mark Kubacki.
+
+    *) Bugfix: listen unix domain socket had limited access rights.
+
+    *) Bugfix: cached HTTP/0.9 responses were handled incorrectly.
+
+    *) Bugfix: regular expression named captures given by "?P<...>" did not
+       work in a "server_name" directive.
+       Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.8.28                                        23 Nov 2009
+
+    *) Bugfix: nginx could not be built with the --without-pcre parameter;
+       the bug had appeared in 0.8.25.
+
+
+Changes with nginx 0.8.27                                        17 Nov 2009
+
+    *) Bugfix: regular expressions did not work in nginx/Windows; the bug
+       had appeared in 0.8.25.
+
+
+Changes with nginx 0.8.26                                        16 Nov 2009
+
+    *) Bugfix: in captures usage in "rewrite" directive; the bug had
+       appeared in 0.8.25.
+
+    *) Bugfix: nginx could not be built without the --with-debug option; the
+       bug had appeared in 0.8.25.
+
+
+Changes with nginx 0.8.25                                        16 Nov 2009
+
+    *) Change: now no message is written in an error log if a variable is
+       not found by $r->variable() method.
+
+    *) Feature: the ngx_http_degradation_module.
+
+    *) Feature: regular expression named captures.
+
+    *) Feature: now URI part is not required a "proxy_pass" directive if
+       variables are used.
+
+    *) Feature: now the "msie_padding" directive works for Chrome too.
+
+    *) Bugfix: a segmentation fault occurred in a worker process on low
+       memory condition; the bug had appeared in 0.8.18.
+
+    *) Bugfix: nginx sent gzipped responses to clients those do not support
+       gzip, if "gzip_static on" and "gzip_vary off"; the bug had appeared
+       in 0.8.16.
+
+
+Changes with nginx 0.8.24                                        11 Nov 2009
+
+    *) Bugfix: nginx always added "Content-Encoding: gzip" response header
+       line in 304 responses sent by ngx_http_gzip_static_module.
+
+    *) Bugfix: nginx could not be built without the --with-debug option; the
+       bug had appeared in 0.8.23.
+
+    *) Bugfix: the "unix:" parameter of the "set_real_ip_from" directive
+       inherited incorrectly from previous level.
+
+    *) Bugfix: in resolving empty name.
+
+
+Changes with nginx 0.8.23                                        11 Nov 2009
+
+    *) Security: now SSL/TLS renegotiation is disabled.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: listen unix domain socket did not inherit while online
+       upgrade.
+
+    *) Bugfix: the "unix:" parameter of the "set_real_ip_from" directive did
+       not without yet another directive with any IP address.
+
+    *) Bugfix: segmentation fault and infinite looping in resolver.
+
+    *) Bugfix: in resolver.
+       Thanks to Artem Bokhan.
+
+
+Changes with nginx 0.8.22                                        03 Nov 2009
+
+    *) Feature: the "proxy_bind", "fastcgi_bind", and "memcached_bind"
+       directives.
+
+    *) Feature: the "access" and the "deny" directives support IPv6.
+
+    *) Feature: the "set_real_ip_from" directive supports IPv6 addresses in
+       request headers.
+
+    *) Feature: the "unix:" parameter of the "set_real_ip_from" directive.
+
+    *) Bugfix: nginx did not delete unix domain socket after configuration
+       testing.
+
+    *) Bugfix: nginx deleted unix domain socket while online upgrade.
+
+    *) Bugfix: the "!-x" operator did not work.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: a segmentation fault might occur in a worker process, if
+       limit_rate was used in HTTPS server.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: a segmentation fault might occur in a worker process while
+       $limit_rate logging.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: a segmentation fault might occur in a worker process, if
+       there was no "listen" directive in "server" block; the bug had
+       appeared in 0.8.21.
+
+
+Changes with nginx 0.8.21                                        26 Oct 2009
+
+    *) Feature: now the "-V" switch shows TLS SNI support.
+
+    *) Feature: the "listen" directive of the HTTP module supports unix
+       domain sockets.
+       Thanks to Hongli Lai.
+
+    *) Feature: the "default_server" parameter of the "listen" directive.
+
+    *) Feature: now a "default" parameter is not required to set listen
+       socket options.
+
+    *) Bugfix: nginx did not support dates in 2038 year on 32-bit platforms;
+
+    *) Bugfix: socket leak; the bug had appeared in 0.8.11.
+
+
+Changes with nginx 0.8.20                                        14 Oct 2009
+
+    *) Change: now default SSL ciphers are "HIGH:!ADH:!MD5".
+
+    *) Bugfix: the ngx_http_autoindex_module did not show the trailing slash
+       in links to a directory; the bug had appeared in 0.7.15.
+
+    *) Bugfix: nginx did not close a log file set by the --error-log-path
+       configuration option; the bug had appeared in 0.7.53.
+
+    *) Bugfix: nginx did not treat a comma as separator in the
+       "Cache-Control" backend response header line.
+
+    *) Bugfix: nginx/Windows might not create temporary file, a cache file,
+       or "proxy/fastcgi_store"d file if a worker had no enough access
+       rights for top level directories.
+
+    *) Bugfix: the "Set-Cookie" and "P3P" FastCGI response header lines were
+       not hidden while caching if no "fastcgi_hide_header" directives were
+       used with any parameters.
+
+    *) Bugfix: nginx counted incorrectly disk cache size.
+
+
+Changes with nginx 0.8.19                                        06 Oct 2009
+
+    *) Change: now SSLv2 protocol is disabled by default.
+
+    *) Change: now default SSL ciphers are "ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM".
+
+    *) Bugfix: a "limit_req" directive did not work; the bug had appeared in
+       0.8.18.
+
+
+Changes with nginx 0.8.18                                        06 Oct 2009
+
+    *) Feature: the "read_ahead" directive.
+
+    *) Feature: now several "perl_modules" directives may be used.
+
+    *) Feature: the "limit_req_log_level" and "limit_conn_log_level"
+       directives.
+
+    *) Bugfix: now "limit_req" directive conforms to the leaky bucket
+       algorithm.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: nginx did not work on Linux/sparc.
+       Thanks to Marcus Ramberg.
+
+    *) Bugfix: nginx sent '\0' in a "Location" response header line on MKCOL
+       request.
+       Thanks to Xie Zhenye.
+
+    *) Bugfix: zero status code was logged instead of 499 status code; the
+       bug had appeared in 0.8.11.
+
+    *) Bugfix: socket leak; the bug had appeared in 0.8.11.
+
+
+Changes with nginx 0.8.17                                        28 Sep 2009
+
+    *) Security: now "/../" are disabled in "Destination" request header
+       line.
+
+    *) Change: now $host variable value is always low case.
+
+    *) Feature: the $ssl_session_id variable.
+
+    *) Bugfix: socket leak; the bug had appeared in 0.8.11.
+
+
+Changes with nginx 0.8.16                                        22 Sep 2009
+
+    *) Feature: the "image_filter_transparency" directive.
+
+    *) Bugfix: "addition_types" directive was incorrectly named
+       "addtion_types".
+
+    *) Bugfix: resolver cache poisoning.
+       Thanks to Matthew Dempsky.
+
+    *) Bugfix: memory leak in resolver.
+       Thanks to Matthew Dempsky.
+
+    *) Bugfix: invalid request line in $request variable was written in
+       access_log only if error_log was set to "info" or "debug" level.
+
+    *) Bugfix: in PNG alpha-channel support in the
+       ngx_http_image_filter_module.
+
+    *) Bugfix: nginx always added "Vary: Accept-Encoding" response header
+       line, if both "gzip_static" and "gzip_vary" were on.
+
+    *) Bugfix: in UTF-8 encoding support by "try_files" directive in
+       nginx/Windows.
+
+    *) Bugfix: in "post_action" directive usage; the bug had appeared in
+       0.8.11.
+       Thanks to Igor Artemiev.
+
+
+Changes with nginx 0.8.15                                        14 Sep 2009
+
+    *) Security: a segmentation fault might occur in worker process while
+       specially crafted request handling.
+       Thanks to Chris Ries.
+
+    *) Bugfix: if names .domain.tld, .sub.domain.tld, and .domain-some.tld
+       were defined, then the name .sub.domain.tld was matched by
+       .domain.tld.
+
+    *) Bugfix: in transparency support in the ngx_http_image_filter_module.
+
+    *) Bugfix: in file AIO.
+
+    *) Bugfix: in X-Accel-Redirect usage; the bug had appeared in 0.8.11.
+
+    *) Bugfix: in embedded perl module; the bug had appeared in 0.8.11.
+
+
+Changes with nginx 0.8.14                                        07 Sep 2009
+
+    *) Bugfix: an expired cached response might stick in the "UPDATING"
+       state.
+
+    *) Bugfix: a segmentation fault might occur in worker process, if
+       error_log was set to info or debug level.
+       Thanks to Sergey Bochenkov.
+
+    *) Bugfix: in embedded perl module; the bug had appeared in 0.8.11.
+
+    *) Bugfix: an "error_page" directive did not redirect a 413 error; the
+       bug had appeared in 0.6.10.
+
+
+Changes with nginx 0.8.13                                        31 Aug 2009
+
+    *) Bugfix: in the "aio sendfile" directive; the bug had appeared in
+       0.8.12.
+
+    *) Bugfix: nginx could not be built without the --with-file-aio option
+       on FreeBSD; the bug had appeared in 0.8.12.
+
+
+Changes with nginx 0.8.12                                        31 Aug 2009
+
+    *) Feature: the "sendfile" parameter in the "aio" directive on FreeBSD.
+
+    *) Bugfix: in try_files; the bug had appeared in 0.8.11.
+
+    *) Bugfix: in memcached; the bug had appeared in 0.8.11.
+
+
+Changes with nginx 0.8.11                                        28 Aug 2009
+
+    *) Change: now directive "gzip_disable msie6" does not disable gzipping
+       for MSIE 6.0 SV1.
+
+    *) Feature: file AIO support on FreeBSD and Linux.
+
+    *) Feature: the "directio_alignment" directive.
+
+
+Changes with nginx 0.8.10                                        24 Aug 2009
+
+    *) Bugfix: memory leaks if GeoIP City database was used.
+
+    *) Bugfix: in copying temporary files to permanent storage area; the bug
+       had appeared in 0.8.9.
+
+
+Changes with nginx 0.8.9                                         17 Aug 2009
+
+    *) Feature: now the start cache loader runs in a separate process; this
+       should improve large caches handling.
+
+    *) Feature: now temporary files and permanent storage area may reside at
+       different file systems.
+
+
+Changes with nginx 0.8.8                                         10 Aug 2009
+
+    *) Bugfix: in handling FastCGI headers split in records.
+
+    *) Bugfix: a segmentation fault occurred in worker process, if a request
+       was handled in two proxied or FastCGIed locations and a caching was
+       enabled in the first location; the bug had appeared in 0.8.7.
+
+
+Changes with nginx 0.8.7                                         27 Jul 2009
+
+    *) Change: minimum supported OpenSSL version is 0.9.7.
+
+    *) Change: the "ask" parameter of the "ssl_verify_client" directive was
+       changed to the "optional" parameter and now it checks a client
+       certificate if it was offered.
+       Thanks to Brice Figureau.
+
+    *) Feature: the $ssl_client_verify variable.
+       Thanks to Brice Figureau.
+
+    *) Feature: the "ssl_crl" directive.
+       Thanks to Brice Figureau.
+
+    *) Feature: the "proxy" parameter of the "geo" directive.
+
+    *) Feature: the "image_filter" directive supports variables for setting
+       size.
+
+    *) Bugfix: the $ssl_client_cert variable usage corrupted memory; the bug
+       had appeared in 0.7.7.
+       Thanks to Sergey Zhuravlev.
+
+    *) Bugfix: "proxy_pass_header" and "fastcgi_pass_header" directives did
+       not pass to a client the "X-Accel-Redirect", "X-Accel-Limit-Rate",
+       "X-Accel-Buffering", and "X-Accel-Charset" lines from backend
+       response header.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: in handling "Last-Modified" and "Accept-Ranges" backend
+       response header lines; the bug had appeared in 0.7.44.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: the "[alert] zero size buf" error if subrequest returns an
+       empty response; the bug had appeared in 0.8.5.
+
+
+Changes with nginx 0.8.6                                         20 Jul 2009
+
+    *) Feature: the ngx_http_geoip_module.
+
+    *) Bugfix: XSLT filter may fail with message "not well formed XML
+       document" for valid XML document.
+       Thanks to Kuramoto Eiji.
+
+    *) Bugfix: now in MacOSX, Cygwin, and nginx/Windows locations given by a
+       regular expression are always tested in case insensitive mode.
+
+    *) Bugfix: now nginx/Windows ignores trailing dots in URI.
+       Thanks to Hugo Leisink.
+
+    *) Bugfix: name of file specified in --conf-path was not honored during
+       installation; the bug had appeared in 0.6.6.
+       Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.8.5                                         13 Jul 2009
+
+    *) Bugfix: now nginx allows underscores in a request method.
+
+    *) Bugfix: a 500 error code was returned for invalid login/password
+       while HTTP Basic authentication on Windows.
+
+    *) Bugfix: ngx_http_perl_module responses did not work in subrequests.
+
+    *) Bugfix: in ngx_http_limit_req_module.
+       Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.8.4                                         22 Jun 2009
+
+    *) Bugfix: nginx could not be built --without-http-cache; the bug had
+       appeared in 0.8.3.
+
+
+Changes with nginx 0.8.3                                         19 Jun 2009
+
+    *) Feature: the $upstream_cache_status variable.
+
+    *) Bugfix: nginx could not be built on MacOSX 10.6.
+
+    *) Bugfix: nginx could not be built --without-http-cache; the bug had
+       appeared in 0.8.2.
+
+    *) Bugfix: a segmentation fault occurred in worker process, if a backend
+       401 error was intercepted and the backend did not set the
+       "WWW-Authenticate" response header line.
+       Thanks to Eugene Mychlo.
+
+
+Changes with nginx 0.8.2                                         15 Jun 2009
+
+    *) Bugfix: in open_file_cache and proxy/fastcgi cache interaction on
+       start up.
+
+    *) Bugfix: open_file_cache might cache open file descriptors too long;
+       the bug had appeared in 0.7.4.
+
+
+Changes with nginx 0.8.1                                         08 Jun 2009
+
+    *) Feature: the "updating" parameter in "proxy_cache_use_stale" and
+       "fastcgi_cache_use_stale" directives.
+
+    *) Bugfix: the "If-Modified-Since", "If-Range", etc. client request
+       header lines were passed to backend while caching if no
+       "proxy_set_header" directive was used with any parameters.
+
+    *) Bugfix: the "Set-Cookie" and "P3P" response header lines were not
+       hidden while caching if no "proxy_hide_header/fastcgi_hide_header"
+       directives were used with any parameters.
+
+    *) Bugfix: the ngx_http_image_filter_module did not support GIF87a
+       format.
+       Thanks to Denis Ilyinyh.
+
+    *) Bugfix: nginx could not be built modules on Solaris 10 and early; the
+       bug had appeared in 0.7.56.
+
+
+Changes with nginx 0.8.0                                         02 Jun 2009
+
+    *) Feature: the "keepalive_requests" directive.
+
+    *) Feature: the "limit_rate_after" directive.
+       Thanks to Ivan Debnar.
+
+    *) Bugfix: XLST filter did not work in subrequests.
+
+    *) Bugfix: in relative paths handling in nginx/Windows.
+
+    *) Bugfix: in proxy_store, fastcgi_store, proxy_cache, and fastcgi_cache
+       in nginx/Windows.
+
+    *) Bugfix: in memory allocation error handling.
+       Thanks to Maxim Dounin and Kirill A. Korinskiy.
+
+
+Changes with nginx 0.7.59                                        25 May 2009
+
+    *) Feature: the "proxy_cache_methods" and "fastcgi_cache_methods"
+       directives.
+
+    *) Bugfix: socket leak; the bug had appeared in 0.7.25.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: a segmentation fault occurred in worker process, if a request
+       had no body and the $request_body variable was used;
+       the bug had appeared in 0.7.58.
+
+    *) Bugfix: the SSL modules might not built on Solaris and Linux;
+       the bug had appeared in 0.7.56.
+
+    *) Bugfix: ngx_http_xslt_filter_module responses were not handled by
+       SSI, charset, and gzip filters.
+
+    *) Bugfix: a "charset" directive did not set a charset to
+       ngx_http_gzip_static_module responses.
+
+
+Changes with nginx 0.7.58                                        18 May 2009
+
+    *) Feature: a "listen" directive of the mail proxy module supports IPv6.
+
+    *) Feature: the "image_filter_jpeg_quality" directive.
+
+    *) Feature: the "client_body_in_single_buffer" directive.
+
+    *) Feature: the $request_body variable.
+
+    *) Bugfix: in ngx_http_autoindex_module in file name links having a ":"
+       symbol in the name.
+
+    *) Bugfix: "make upgrade" procedure did not work; the bug had appeared
+       in 0.7.53.
+       Thanks to Denis F. Latypoff.
+
+
+Changes with nginx 0.7.57                                        12 May 2009
+
+    *) Bugfix: a floating-point fault occurred in worker process, if the
+       ngx_http_image_filter_module errors were redirected to named
+       location; the bug had appeared in 0.7.56.
+
+
+Changes with nginx 0.7.56                                        11 May 2009
+
+    *) Feature: nginx/Windows supports IPv6 in a "listen" directive of the
+       HTTP module.
+
+    *) Bugfix: in ngx_http_image_filter_module.
+
+
+Changes with nginx 0.7.55                                        06 May 2009
+
+    *) Bugfix: the http_XXX parameters in "proxy_cache_use_stale" and
+       "fastcgi_cache_use_stale" directives did not work.
+
+    *) Bugfix: fastcgi cache did not cache header only responses.
+
+    *) Bugfix: of "select() failed (9: Bad file descriptor)" error in
+       nginx/Unix and "select() failed (10038: ...)" error in nginx/Windows.
+
+    *) Bugfix: a segmentation fault might occur in worker process, if an
+       "debug_connection" directive was used; the bug had appeared in
+       0.7.54.
+
+    *) Bugfix: fix ngx_http_image_filter_module building errors.
+
+    *) Bugfix: the files bigger than 2G could not be transferred using
+       $r->sendfile.
+       Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.7.54                                        01 May 2009
+
+    *) Feature: the ngx_http_image_filter_module.
+
+    *) Feature: the "proxy_ignore_headers" and "fastcgi_ignore_headers"
+       directives.
+
+    *) Bugfix: a segmentation fault might occur in worker process, if an
+       "open_file_cache_errors off" directive was used; the bug had appeared
+       in 0.7.53.
+
+    *) Bugfix: the "port_in_redirect off" directive did not work; the bug
+       had appeared in 0.7.39.
+
+    *) Bugfix: improve handling of "select" method errors.
+
+    *) Bugfix: of "select() failed (10022: ...)" error in nginx/Windows.
+
+    *) Bugfix: in error text descriptions in nginx/Windows; the bug had
+       appeared in 0.7.53.
+
+
+Changes with nginx 0.7.53                                        27 Apr 2009
+
+    *) Change: now a log set by --error-log-path is created from the very
+       start-up.
+
+    *) Feature: now the start up errors and warnings are outputted to an
+       error_log and stderr.
+
+    *) Feature: the empty --prefix= configure parameter forces nginx to use
+       a directory where it was run as prefix.
+
+    *) Feature: the -p switch.
+
+    *) Feature: the -s switch on Unix platforms.
+
+    *) Feature: the -? and -h switches.
+       Thanks to Jerome Loyet.
+
+    *) Feature: now switches may be set in condensed form.
+
+    *) Bugfix: nginx/Windows did not work if configuration file was given by
+       the -c switch.
+
+    *) Bugfix: temporary files might be not removed if the "proxy_store",
+       "fastcgi_store", "proxy_cache", or "fastcgi_cache" were used.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: an incorrect value was passed to mail proxy authentication
+       server in "Auth-Method" header line; the bug had appeared
+       in 0.7.34.
+       Thanks to Simon Lecaille.
+
+    *) Bugfix: system error text descriptions were not logged on Linux;
+       the bug had appeared in 0.7.45.
+
+    *) Bugfix: the "fastcgi_cache_min_uses" directive did not work.
+       Thanks to Andrew Vorobyoff.
+
+
+Changes with nginx 0.7.52                                        20 Apr 2009
+
+    *) Feature: the first native Windows binary release.
+
+    *) Bugfix: in processing HEAD method while caching.
+
+    *) Bugfix: in processing the "If-Modified-Since", "If-Range", etc.
+       client request header lines while caching.
+
+    *) Bugfix: now the "Set-Cookie" and "P3P" header lines are hidden in
+       cacheable responses.
+
+    *) Bugfix: if nginx was built with the ngx_http_perl_module and with a
+       perl which supports threads, then during a master process exit the
+       message "panic: MUTEX_LOCK" might be issued.
+
+    *) Bugfix: nginx could not be built --without-http-cache; the bug had
+       appeared in 0.7.48.
+
+    *) Bugfix: nginx could not be built on platforms different from i386,
+       amd64, sparc, and ppc; the bug had appeared in 0.7.42.
+
+
+Changes with nginx 0.7.51                                        12 Apr 2009
+
+    *) Feature: the "try_files" directive supports a response code in the
+       fallback parameter.
+
+    *) Feature: now any response code can be used in the "return" directive.
+
+    *) Bugfix: the "error_page" directive made an external redirect without
+       query string; the bug had appeared in 0.7.44.
+
+    *) Bugfix: if servers listened on several defined explicitly addresses,
+       then virtual servers might not work; the bug had appeared in 0.7.39.
+
+
+Changes with nginx 0.7.50                                        06 Apr 2009
+
+    *) Bugfix: the $arg_... variables did not work; the bug had appeared in
+       0.7.49.
+
+
+Changes with nginx 0.7.49                                        06 Apr 2009
+
+    *) Bugfix: a segmentation fault might occur in worker process, if the
+       $arg_... variables were used; the bug had appeared in 0.7.48.
+
+
+Changes with nginx 0.7.48                                        06 Apr 2009
+
+    *) Feature: the "proxy_cache_key" directive.
+
+    *) Bugfix: now nginx takes into account the "X-Accel-Expires",
+       "Expires", and "Cache-Control" header lines in a backend response.
+
+    *) Bugfix: now nginx caches responses for the GET requests only.
+
+    *) Bugfix: the "fastcgi_cache_key" directive was not inherited.
+
+    *) Bugfix: the $arg_... variables did not work with SSI subrequests.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: nginx could not be built with uclibc library.
+       Thanks to Timothy Redaelli.
+
+    *) Bugfix: nginx could not be built on OpenBSD; the bug had
+       appeared in 0.7.46.
+
+
+Changes with nginx 0.7.47                                        01 Apr 2009
+
+    *) Bugfix: nginx could not be built on FreeBSD 6 and early versions; the
+       bug had appeared in 0.7.46.
+
+    *) Bugfix: nginx could not be built on MacOSX; the bug had
+       appeared in 0.7.46.
+
+    *) Bugfix: if the "max_size" parameter was set, then the cache manager
+       might purge a whole cache; the bug had appeared in 0.7.46.
+
+    *) Change: a segmentation fault might occur in worker process, if the
+       "proxy_cache"/"fastcgi_cache" and the "proxy_cache_valid"/
+       "fastcgi_cache_valid" were set on different levels; the bug had
+       appeared in 0.7.46.
+
+    *) Bugfix: a segmentation fault might occur in worker process, if a
+       request was redirected to a proxied or FastCGI server via error_page
+       or try_files; the bug had appeared in 0.7.44.
+
+
+Changes with nginx 0.7.46                                        30 Mar 2009
+
+    *) Bugfix: the previous release tarball was incorrect.
+
+
+Changes with nginx 0.7.45                                        30 Mar 2009
+
+    *) Change: now the "proxy_cache" and the "proxy_cache_valid" directives
+       can be set on different levels.
+
+    *) Change: the "clean_time" parameter of the "proxy_cache_path"
+       directive is canceled.
+
+    *) Feature: the "max_size" parameter of the "proxy_cache_path"
+       directive.
+
+    *) Feature: the ngx_http_fastcgi_module preliminary cache support.
+
+    *) Feature: now on shared memory allocation errors directive and zone
+       names are logged.
+
+    *) Bugfix: the directive "add_header last-modified ''" did not delete a
+       "Last-Modified" response header line; the bug had appeared in 0.7.44.
+
+    *) Bugfix: a relative path in the "auth_basic_user_file" directive given
+       without variables did not work; the bug had appeared in 0.7.44.
+       Thanks to Jerome Loyet.
+
+    *) Bugfix: in an "alias" directive given using variables without
+       references to captures of regular expressions; the bug had appeared
+       in 0.7.42.
+
+
+Changes with nginx 0.7.44                                        23 Mar 2009
+
+    *) Feature: the ngx_http_proxy_module preliminary cache support.
+
+    *) Feature: the --with-pcre option in the configure.
+
+    *) Feature: the "try_files" directive is now allowed on the server block
+       level.
+
+    *) Bugfix: the "try_files" directive handled incorrectly a query string
+       in a fallback parameter.
+
+    *) Bugfix: the "try_files" directive might test incorrectly directories.
+
+    *) Bugfix: if there was a single server for given address:port pair,
+       then captures in regular expressions in a "server_name" directive did
+       not work.
+
+
+Changes with nginx 0.7.43                                        18 Mar 2009
+
+    *) Bugfix: a request was handled incorrectly, if a "root" directive used
+       variables; the bug had appeared in 0.7.42.
+
+    *) Bugfix: if a server listened on wildcard address, then the
+       $server_addr variable value was "0.0.0.0"; the bug had appeared in
+       0.7.36.
+
+
+Changes with nginx 0.7.42                                        16 Mar 2009
+
+    *) Change: now the "Invalid argument" error returned by
+       setsockopt(TCP_NODELAY) on Solaris, is ignored.
+
+    *) Change: now if a file specified in a "auth_basic_user_file" directive
+       is absent, then the 403 error is returned instead of the 500 one.
+
+    *) Feature: the "auth_basic_user_file" directive supports variables.
+       Thanks to Kirill A. Korinskiy.
+
+    *) Feature: the "listen" directive supports the "ipv6only" parameter.
+       Thanks to Zhang Hua.
+
+    *) Bugfix: in an "alias" directive with references to captures of
+       regular expressions; the bug had appeared in 0.7.40.
+
+    *) Bugfix: compatibility with Tru64 UNIX.
+       Thanks to Dustin Marquess.
+
+    *) Bugfix: nginx could not be built without PCRE library; the bug had
+       appeared in 0.7.41.
+
+
+Changes with nginx 0.7.41                                        11 Mar 2009
+
+    *) Bugfix: a segmentation fault might occur in worker process, if a
+       "server_name" or a "location" directives had captures in regular
+       expressions; the issue had appeared in 0.7.40.
+       Thanks to Vladimir Sopot.
+
+
+Changes with nginx 0.7.40                                        09 Mar 2009
+
+    *) Feature: the "location" directive supports captures in regular
+       expressions.
+
+    *) Feature: an "alias" directive with capture references may be used
+       inside a location given by a regular expression with captures.
+
+    *) Feature: the "server_name" directive supports captures in regular
+       expressions.
+
+    *) Workaround: the ngx_http_autoindex_module did not show the trailing
+       slash in directories on XFS filesystem; the issue had appeared in
+       0.7.15.
+       Thanks to Dmitry Kuzmenko.
+
+
+Changes with nginx 0.7.39                                        02 Mar 2009
+
+    *) Bugfix: large response with SSI might hang, if gzipping was enabled;
+       the bug had appeared in 0.7.28.
+       Thanks to Artem Bokhan.
+
+    *) Bugfix: a segmentation fault might occur in worker process, if short
+       static variants are used in a "try_files" directive.
+
+
+Changes with nginx 0.7.38                                        23 Feb 2009
+
+    *) Feature: authentication failures logging.
+
+    *) Bugfix: name/password in auth_basic_user_file were ignored after odd
+       number of empty lines.
+       Thanks to Alexander Zagrebin.
+
+    *) Bugfix: a segmentation fault occurred in a master process, if long
+       path was used in unix domain socket; the bug had appeared in 0.7.36.
+
+
+Changes with nginx 0.7.37                                        21 Feb 2009
+
+    *) Bugfix: directives using upstreams did not work; the bug had appeared
+       in 0.7.36.
+
+
+Changes with nginx 0.7.36                                        21 Feb 2009
+
+    *) Feature: a preliminary IPv6 support; the "listen" directive of the
+       HTTP module supports IPv6.
+
+    *) Bugfix: the $ancient_browser variable did not work for browsers
+       preset by a "modern_browser" directives.
+
+
+Changes with nginx 0.7.35                                        16 Feb 2009
+
+    *) Bugfix: a "ssl_engine" directive did not use a SSL-accelerator for
+       asymmetric ciphers.
+       Thanks to Marcin Gozdalik.
+
+    *) Bugfix: a "try_files" directive set MIME type depending on an
+       original request extension.
+
+    *) Bugfix: "*domain.tld" names were handled incorrectly in
+       "server_name", "valid_referers", and "map" directives, if
+       ".domain.tld" and ".subdomain.domain.tld" wildcards were used;
+       the bug had appeared in 0.7.9.
+
+
+Changes with nginx 0.7.34                                        10 Feb 2009
+
+    *) Feature: the "off" parameter of the "if_modified_since" directive.
+
+    *) Feature: now nginx sends an HELO/EHLO command after a XCLIENT
+       command.
+       Thanks to Maxim Dounin.
+
+    *) Feature: Microsoft specific "AUTH LOGIN with User Name" mode support
+       in mail proxy server.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: in a redirect rewrite directive original arguments were
+       concatenated with new arguments by a "?" rather than an "&";
+       the bug had appeared in 0.1.18.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: nginx could not be built on AIX.
+
+
+Changes with nginx 0.7.33                                        02 Feb 2009
+
+    *) Bugfix: a double response might be returned if the epoll or rtsig
+       methods are used and a redirect was returned to a request with body.
+       Thanks to Eden Li.
+
+    *) Bugfix: the $sent_http_location variable was empty for some redirects
+       types.
+
+    *) Bugfix: a segmentation fault might occur in worker process if
+       "resolver" directive was used in SMTP proxy.
+
+
+Changes with nginx 0.7.32                                        26 Jan 2009
+
+    *) Feature: now a directory existence testing can be set explicitly in
+       the "try_files" directive.
+
+    *) Bugfix: fastcgi_store stored files not always.
+
+    *) Bugfix: in geo ranges.
+
+    *) Bugfix: in shared memory allocations if nginx was built without
+       debugging.
+       Thanks to Andrey Kvasov.
+
+
+Changes with nginx 0.7.31                                        19 Jan 2009
+
+    *) Change: now the "try_files" directive tests files only and ignores
+       directories.
+
+    *) Feature: the "fastcgi_split_path_info" directive.
+
+    *) Bugfixes in an "Expect" request header line support.
+
+    *) Bugfixes in geo ranges.
+
+    *) Bugfix: in a miss case ngx_http_memcached_module returned the "END"
+       line as response body instead of default 404 page body; the bug had
+       appeared in 0.7.18.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: while SMTP proxying nginx issued message "250 2.0.0 OK"
+       instead of "235 2.0.0 OK"; the bug had appeared in 0.7.22.
+       Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.7.30                                        24 Dec 2008
+
+    *) Bugfix: a segmentation fault occurred in worker process, if variables
+       were used in the "fastcgi_pass" or "proxy_pass" directives and host
+       name must be resolved; the bug had appeared in 0.7.29.
+
+
+Changes with nginx 0.7.29                                        24 Dec 2008
+
+    *) Bugfix: the "fastcgi_pass" and "proxy_pass" directives did not
+       support variables if unix domain sockets were used.
+
+    *) Bugfixes in subrequest processing; the bugs had appeared in 0.7.25.
+
+    *) Bugfix: a "100 Continue" response was issued for HTTP/1.0 requests;
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: in memory allocation in the ngx_http_gzip_filter_module on
+       Cygwin.
+
+
+Changes with nginx 0.7.28                                        22 Dec 2008
+
+    *) Change: in memory allocation in the ngx_http_gzip_filter_module.
+
+    *) Change: the default "gzip_buffers" directive values have been changed
+       to 32 4k or 16 8k from 4 4k/8k.
+
+
+Changes with nginx 0.7.27                                        15 Dec 2008
+
+    *) Feature: the "try_files" directive.
+
+    *) Feature: variables support in the "fastcgi_pass" directive.
+
+    *) Feature: now the $geo variable may get an address from a variable.
+       Thanks to Andrei Nigmatulin.
+
+    *) Feature: now a location's modifier may be used without space before
+       name.
+
+    *) Feature: the $upstream_response_length variable.
+
+    *) Bugfix: now a "add_header" directive does not add an empty value.
+
+    *) Bugfix: if zero length static file was requested, then nginx just
+       closed connection; the bug had appeared in 0.7.25.
+
+    *) Bugfix: a MOVE method could not move file in non-existent directory.
+
+    *) Bugfix: a segmentation fault occurred in worker process, if no one
+       named location was defined in server, but some one was used in an
+       error_page directive.
+       Thanks to Sergey Bochenkov.
+
+
+Changes with nginx 0.7.26                                        08 Dec 2008
+
+    *) Bugfix: in subrequest processing; the bug had appeared in 0.7.25.
+
+
+Changes with nginx 0.7.25                                        08 Dec 2008
+
+    *) Change: in subrequest processing.
+
+    *) Change: now POSTs without "Content-Length" header line are allowed.
+
+    *) Bugfix: now the "limit_req" and "limit_conn" directives log a
+       prohibition reason.
+
+    *) Bugfix: in the "delete" parameter of the "geo" directive.
+
+
+Changes with nginx 0.7.24                                        01 Dec 2008
+
+    *) Feature: the "if_modified_since" directive.
+
+    *) Bugfix: nginx did not process a FastCGI server response, if the
+       server send too many messages to stderr before response.
+
+    *) Bugfix: the "$cookie_..." variables did not work in the SSI and the
+       perl module.
+
+
+Changes with nginx 0.7.23                                        27 Nov 2008
+
+    *) Feature: the "delete" and "ranges" parameters in the "geo" directive.
+
+    *) Feature: speeding up loading of geo base with large number of values.
+
+    *) Feature: decrease of memory required for geo base load.
+
+
+Changes with nginx 0.7.22                                        20 Nov 2008
+
+    *) Feature: the "none" parameter in the "smtp_auth" directive.
+       Thanks to Maxim Dounin.
+
+    *) Feature: the "$cookie_..." variables.
+
+    *) Bugfix: the "directio" directive did not work in XFS filesystem.
+
+    *) Bugfix: the resolver did not understand big DNS responses.
+       Thanks to Zyb.
+
+
+Changes with nginx 0.7.21                                        11 Nov 2008
+
+    *) Changes in the ngx_http_limit_req_module.
+
+    *) Feature: the EXSLT support in the ngx_http_xslt_module.
+       Thanks to Denis F. Latypoff.
+
+    *) Workaround: compatibility with glibc 2.3.
+       Thanks to Eric Benson and Maxim Dounin.
+
+    *) Bugfix: nginx could not run on MacOSX 10.4 and earlier; the bug had
+       appeared in 0.7.6.
+
+
+Changes with nginx 0.7.20                                        10 Nov 2008
+
+    *) Changes in the ngx_http_gzip_filter_module.
+
+    *) Feature: the ngx_http_limit_req_module.
+
+    *) Bugfix: worker processes might exit on a SIGBUS signal on sparc and
+       ppc platforms; the bug had appeared in 0.7.3.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: the "proxy_pass http://host/some:uri" directives did not
+       work; the bug had appeared in 0.7.12.
+
+    *) Bugfix: in HTTPS mode requests might fail with the "bad write retry"
+       error.
+
+    *) Bugfix: the ngx_http_secure_link_module did not work inside
+       locations, whose names are less than 3 characters.
+
+    *) Bugfix: $server_addr variable might have no value.
+
+
+Changes with nginx 0.7.19                                        13 Oct 2008
+
+    *) Bugfix: version number update.
+
+
+Changes with nginx 0.7.18                                        13 Oct 2008
+
+    *) Change: the "underscores_in_headers" directive; now nginx does not
+       allows underscores in a client request header line names.
+
+    *) Feature: the ngx_http_secure_link_module.
+
+    *) Feature: the "real_ip_header" directive supports any header.
+
+    *) Feature: the "log_subrequest" directive.
+
+    *) Feature: the $realpath_root variable.
+
+    *) Feature: the "http_502" and "http_504" parameters of the
+       "proxy_next_upstream" directive.
+
+    *) Bugfix: the "http_503" parameter of the "proxy_next_upstream" or
+       "fastcgi_next_upstream" directives did not work.
+
+    *) Bugfix: nginx might send a "Transfer-Encoding: chunked" header line
+       for HEAD requests.
+
+    *) Bugfix: now accept threshold depends on worker_connections.
+
+
+Changes with nginx 0.7.17                                        15 Sep 2008
+
+    *) Feature: now the "directio" directive works on Linux.
+
+    *) Feature: the $pid variable.
+
+    *) Bugfix: the "directio" optimization that had appeared in 0.7.15 did
+       not work with open_file_cache.
+
+    *) Bugfix: the "access_log" with variables did not work on Linux; the
+       bug had appeared in 0.7.7.
+
+    *) Bugfix: the ngx_http_charset_module did not understand quoted charset
+       name received from backend.
+
+
+Changes with nginx 0.7.16                                        08 Sep 2008
+
+    *) Bugfix: nginx could not be built on 64-bit platforms; the bug had
+       appeared in 0.7.15.
+
+
+Changes with nginx 0.7.15                                        08 Sep 2008
+
+    *) Feature: the ngx_http_random_index_module.
+
+    *) Feature: the "directio" directive has been optimized for file
+       requests starting from arbitrary position.
+
+    *) Feature: the "directio" directive turns off sendfile if it is
+       necessary.
+
+    *) Feature: now nginx allows underscores in a client request header line
+       names.
+
+
+Changes with nginx 0.7.14                                        01 Sep 2008
+
+    *) Change: now the ssl_certificate and ssl_certificate_key directives
+       have no default values.
+
+    *) Feature: the "listen" directive supports the "ssl" parameter.
+
+    *) Feature: now nginx takes into account a time zone change while
+       reconfiguration on FreeBSD and Linux.
+
+    *) Bugfix: the "listen" directive parameters such as "backlog",
+       "rcvbuf", etc. were not set, if a default server was not the first
+       one.
+
+    *) Bugfix: if URI part captured by a "rewrite" directive was used as a
+       query string, then the query string was not escaped.
+
+    *) Bugfix: configuration file validity test improvements.
+
+
+Changes with nginx 0.7.13                                        26 Aug 2008
+
+    *) Bugfix: nginx could not be built on Linux and Solaris; the bug had
+       appeared in 0.7.12.
+
+
+Changes with nginx 0.7.12                                        26 Aug 2008
+
+    *) Feature: the "server_name" directive supports empty name "".
+
+    *) Feature: the "gzip_disable" directive supports special "msie6" mask.
+
+    *) Bugfix: if the "max_fails=0" parameter was used in upstream with
+       several servers, then a worker process exited on a SIGFPE signal.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: a request body was dropped while redirection via an
+       "error_page" directive.
+
+    *) Bugfix: a full response was returned for request method HEAD while
+       redirection via an "error_page" directive.
+
+    *) Bugfix: the $r->header_in() method did not return value of the
+       "Host", "User-Agent", and "Connection" request header lines; the bug
+       had appeared in 0.7.0.
+
+
+Changes with nginx 0.7.11                                        18 Aug 2008
+
+    *) Change: now ngx_http_charset_module does not work by default with
+       text/css MIME type.
+
+    *) Feature: now nginx returns the 405 status code for POST method
+       requesting a static file only if the file exists.
+
+    *) Feature: the "proxy_ssl_session_reuse" directive.
+
+    *) Bugfix: a "proxy_pass" directive without URI part might use original
+       request after the "X-Accel-Redirect" redirection was used.
+
+    *) Bugfix: if a directory has search only rights and the first index
+       file was absent, then nginx returned the 500 status code.
+
+    *) Bugfix: in inclusive locations; the bugs had appeared in 0.7.1.
+
+
+Changes with nginx 0.7.10                                        13 Aug 2008
+
+    *) Bugfix: in the "addition_types", "charset_types", "gzip_types",
+       "ssi_types", "sub_filter_types", and "xslt_types" directives; the
+       bugs had appeared in 0.7.9.
+
+    *) Bugfix: of recursive error_page for 500 status code.
+
+    *) Bugfix: now the ngx_http_realip_module sets address not for whole
+       keepalive connection, but for each request passed via the connection.
+
+
+Changes with nginx 0.7.9                                         12 Aug 2008
+
+    *) Change: now ngx_http_charset_module works by default with following
+       MIME types: text/html, text/css, text/xml, text/plain,
+       text/vnd.wap.wml, application/x-javascript, and application/rss+xml.
+
+    *) Feature: the "charset_types" and "addition_types" directives.
+
+    *) Feature: now the "gzip_types", "ssi_types", and "sub_filter_types"
+       directives use hash.
+
+    *) Feature: the ngx_cpp_test_module.
+
+    *) Feature: the "expires" directive supports daily time.
+
+    *) Feature: the ngx_http_xslt_module improvements and bug fixing.
+       Thanks to Denis F. Latypoff and Maxim Dounin.
+
+    *) Bugfix: the "log_not_found" directive did not work for index files
+       tests.
+
+    *) Bugfix: HTTPS connections might hang, if kqueue, epoll, rtsig, or
+       eventport methods were used; the bug had appeared in 0.7.7.
+
+    *) Bugfix: if the "server_name", "valid_referers", and "map" directives
+       used an "*.domain.tld" wildcard and exact name "domain.tld" was not
+       set, then the exact name was matched by the wildcard; the bug had
+       appeared in 0.3.18.
+
+
+Changes with nginx 0.7.8                                         04 Aug 2008
+
+    *) Feature: the ngx_http_xslt_module.
+
+    *) Feature: the "$arg_..." variables.
+
+    *) Feature: Solaris directio support.
+       Thanks to Ivan Debnar.
+
+    *) Bugfix: now if FastCGI server sends a "Location" header line without
+       status line, then nginx uses 302 status code.
+       Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.7.7                                         30 Jul 2008
+
+    *) Change: now the EAGAIN error returned by connect() is not considered
+       as temporary error.
+
+    *) Change: now the $ssl_client_cert variable value is a certificate with
+       TAB character intended before each line except first one; an
+       unchanged certificate is available in the $ssl_client_raw_cert
+       variable.
+
+    *) Feature: the "ask" parameter in the "ssl_verify_client" directive.
+
+    *) Feature: byte-range processing improvements.
+       Thanks to Maxim Dounin.
+
+    *) Feature: the "directio" directive.
+       Thanks to Jiang Hong.
+
+    *) Feature: MacOSX 10.5 sendfile() support.
+
+    *) Bugfix: now in MacOSX and Cygwin locations are tested in case
+       insensitive mode; however, the compare is provided by single-byte
+       locales only.
+
+    *) Bugfix: mail proxy SSL connections hanged, if select, poll, or
+       /dev/poll methods were used.
+
+    *) Bugfix: UTF-8 encoding usage in the ngx_http_autoindex_module.
+
+
+Changes with nginx 0.7.6                                         07 Jul 2008
+
+    *) Bugfix: now if variables are used in the "access_log" directive a
+       request root existence is always tested.
+
+    *) Bugfix: the ngx_http_flv_module did not support several values in a
+       query string.
+
+
+Changes with nginx 0.7.5                                         01 Jul 2008
+
+    *) Bugfixes in variables support in the "access_log" directive; the bugs
+       had appeared in 0.7.4.
+
+    *) Bugfix: nginx could not be built --without-http_gzip_module; the bug
+       had appeared in 0.7.3.
+       Thanks to Kirill A. Korinskiy.
+
+    *) Bugfix: if sub_filter and SSI were used together, then responses
+       might were transferred incorrectly.
+
+
+Changes with nginx 0.7.4                                         30 Jun 2008
+
+    *) Feature: variables support in the "access_log" directive.
+
+    *) Feature: the "open_log_file_cache" directive.
+
+    *) Feature: the -g switch.
+
+    *) Feature: the "Expect" request header line support.
+
+    *) Bugfix: large SSI inclusions might be truncated.
+
+
+Changes with nginx 0.7.3                                         23 Jun 2008
+
+    *) Change: the "rss" extension MIME type has been changed to
+       "application/rss+xml".
+
+    *) Change: now the "gzip_vary" directive turned on issues a
+       "Vary: Accept-Encoding" header line for uncompressed responses too.
+
+    *) Feature: now the "rewrite" directive does a redirect automatically if
+       the "https://" protocol is used.
+
+    *) Bugfix: the "proxy_pass" directive did not work with the HTTPS
+       protocol; the bug had appeared in 0.6.9.
+
+
+Changes with nginx 0.7.2                                         16 Jun 2008
+
+    *) Feature: now nginx supports EDH key exchange ciphers.
+
+    *) Feature: the "ssl_dhparam" directive.
+
+    *) Feature: the $ssl_client_cert variable.
+       Thanks to Manlio Perillo.
+
+    *) Bugfix: after changing URI via a "rewrite" directive nginx did not
+       search a new location; the bug had appeared in 0.7.1.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: nginx could not be built without PCRE library; the bug had
+       appeared in 0.7.1.
+
+    *) Bugfix: when a request to a directory was redirected with the slash
+       added, nginx dropped a query string from the original request.
+
+
+Changes with nginx 0.7.1                                         26 May 2008
+
+    *) Change: now locations are searched in a tree.
+
+    *) Change: the "optimize_server_names" directive was canceled due to the
+       "server_name_in_redirect" directive introduction.
+
+    *) Change: some long deprecated directives are not supported anymore.
+
+    *) Change: the "none" parameter in the "ssl_session_cache" directive;
+       now this is default parameter.
+       Thanks to Rob Mueller.
+
+    *) Bugfix: worker processes might not catch reconfiguration and log
+       rotation signals.
+
+    *) Bugfix: nginx could not be built on latest Fedora 9 Linux.
+       Thanks to Roxis.
+
+
+Changes with nginx 0.7.0                                         19 May 2008
+
+    *) Change: now the 0x00-0x1F, '"' and '\' characters are escaped as \xXX
+       in an access_log.
+       Thanks to Maxim Dounin.
+
+    *) Change: now nginx allows several "Host" request header line.
+
+    *) Feature: the "modified" flag in the "expires" directive.
+
+    *) Feature: the $uid_got and $uid_set variables may be used at any
+       request processing stage.
+
+    *) Feature: the $hostname variable.
+       Thanks to Andrei Nigmatulin.
+
+    *) Feature: DESTDIR support.
+       Thanks to Todd A. Fisher and Andras Voroskoi.
+
+    *) Bugfix: a segmentation fault might occur in worker process on Linux,
+       if keepalive was enabled.
+
+
+Changes with nginx 0.6.31                                        12 May 2008
+
+    *) Bugfix: nginx did not process FastCGI response if header was at the
+       end of FastCGI record; the bug had appeared in 0.6.2.
+       Thanks to Sergey Serov.
+
+    *) Bugfix: a segmentation fault might occur in worker process if a file
+       was deleted and the "open_file_cache_errors" directive was off.
+
+
+Changes with nginx 0.6.30                                        29 Apr 2008
+
+    *) Change: now if an "include" directive pattern does not match any
+       file, then nginx does not issue an error.
+
+    *) Feature: now the time in directives may be specified without spaces,
+       for example, "1h50m".
+
+    *) Bugfix: memory leaks if the "ssl_verify_client" directive was on.
+       Thanks to Chavelle Vincent.
+
+    *) Bugfix: the "sub_filter" directive might set text to change into
+       output.
+
+    *) Bugfix: the "error_page" directive did not take into account
+       arguments in redirected URI.
+
+    *) Bugfix: now nginx always opens files in binary mode under Cygwin.
+
+    *) Bugfix: nginx could not be built on OpenBSD; the bug had appeared in
+       0.6.15.
+
+
+Changes with nginx 0.6.29                                        18 Mar 2008
+
+    *) Feature: the ngx_google_perftools_module.
+
+    *) Bugfix: the ngx_http_perl_module could not be built on 64-bit
+       platforms; the bug had appeared in 0.6.27.
+
+
+Changes with nginx 0.6.28                                        13 Mar 2008
+
+    *) Bugfix: the rtsig method could not be built; the bug had appeared in
+       0.6.27.
+
+
+Changes with nginx 0.6.27                                        12 Mar 2008
+
+    *) Change: now by default the rtsig method is not built on
+       Linux 2.6.18+.
+
+    *) Change: now a request method is not changed while redirection to a
+       named location via an "error_page" directive.
+
+    *) Feature: the "resolver" and "resolver_timeout" directives in SMTP
+       proxy.
+
+    *) Feature: the "post_action" directive supports named locations.
+
+    *) Bugfix: a segmentation fault occurred in worker process, if a request
+       was redirected from proxy, FastCGI, or memcached location to static
+       named locations.
+
+    *) Bugfix: browsers did not repeat SSL handshake if there is no valid
+       client certificate in first handshake.
+       Thanks to Alexander V. Inyukhin.
+
+    *) Bugfix: if response code 495-497 was redirected via an "error_page"
+       directive without code change, then nginx tried to allocate too many
+       memory.
+
+    *) Bugfix: memory leak in long-lived non buffered connections.
+
+    *) Bugfix: memory leak in resolver.
+
+    *) Bugfix: a segmentation fault occurred in worker process, if a request
+       was redirected from proxy, FastCGI, or memcached location to static
+       named locations.
+
+    *) Bugfix: in the $proxy_host and $proxy_port variables caching.
+       Thanks to Sergey Bochenkov.
+
+    *) Bugfix: a "proxy_pass" directive with variables used incorrectly the
+       same port as in another "proxy_pass" directive with the same host
+       name and without variables.
+       Thanks to Sergey Bochenkov.
+
+    *) Bugfix: an alert "sendmsg() failed (9: Bad file descriptor)" on some
+       64-bit platforms while reconfiguration.
+
+    *) Bugfix: a segmentation fault occurred in worker process, if empty
+       stub block was used second time in SSI.
+
+    *) Bugfix: in copying URI part contained escaped symbols into arguments.
+
+
+Changes with nginx 0.6.26                                        11 Feb 2008
+
+    *) Bugfix: the "proxy_store" and "fastcgi_store" directives did not
+       check a response length.
+
+    *) Bugfix: a segmentation fault occurred in worker process, if big value
+       was used in a "expires" directive.
+       Thanks to Joaquin Cuenca Abela.
+
+    *) Bugfix: nginx incorrectly detected cache line size on Pentium 4.
+       Thanks to Gena Makhomed.
+
+    *) Bugfix: in proxied or FastCGI subrequests a client original method
+       was used instead of the GET method.
+
+    *) Bugfix: socket leak in HTTPS mode if deferred accept was used.
+       Thanks to Ben Maurer.
+
+    *) Bugfix: nginx issued the bogus error message "SSL_shutdown() failed
+       (SSL: )"; the bug had appeared in 0.6.23.
+
+    *) Bugfix: in HTTPS mode requests might fail with the "bad write retry"
+       error; the bug had appeared in 0.6.23.
+
+
+Changes with nginx 0.6.25                                        08 Jan 2008
+
+    *) Change: now the "server_name_in_redirect" directive is used instead
+       of the "server_name" directive's special "*" parameter.
+
+    *) Change: now wildcard and regex names can be used as main name in a
+       "server_name" directive.
+
+    *) Change: the "satisfy_any" directive was replaced by the "satisfy"
+       directive.
+
+    *) Workaround: old worker processes might hog CPU after reconfiguration
+       if they was run under Linux OpenVZ.
+
+    *) Feature: the "min_delete_depth" directive.
+
+    *) Bugfix: the COPY and MOVE methods did not work with single files.
+
+    *) Bugfix: the ngx_http_gzip_static_module did not allow the
+       ngx_http_dav_module to work; the bug had appeared in 0.6.23.
+
+    *) Bugfix: socket leak in HTTPS mode if deferred accept was used.
+       Thanks to Ben Maurer.
+
+    *) Bugfix: nginx could not be built without PCRE library; the bug had
+       appeared in 0.6.23.
+
+
+Changes with nginx 0.6.24                                        27 Dec 2007
+
+    *) Bugfix: a segmentation fault might occur in worker process if HTTPS
+       was used; the bug had appeared in 0.6.23.
+
+
+Changes with nginx 0.6.23                                        27 Dec 2007
+
+    *) Change: the "off" parameter in the "ssl_session_cache" directive; now
+       this is default parameter.
+
+    *) Change: the "open_file_cache_retest" directive was renamed to the
+       "open_file_cache_valid".
+
+    *) Feature: the "open_file_cache_min_uses" directive.
+
+    *) Feature: the ngx_http_gzip_static_module.
+
+    *) Feature: the "gzip_disable" directive.
+
+    *) Feature: the "memcached_pass" directive may be used inside the "if"
+       block.
+
+    *) Bugfix: a segmentation fault occurred in worker process, if the
+       "memcached_pass" and "if" directives were used in the same location.
+
+    *) Bugfix: if a "satisfy_any on" directive was used and not all access
+       and auth modules directives were set, then other given access and
+       auth directives were not tested;
+
+    *) Bugfix: regex parameters in a "valid_referers" directive were not
+       inherited from previous level.
+
+    *) Bugfix: a "post_action" directive did run if a request was completed
+       with 499 status code.
+
+    *) Bugfix: optimization of 16K buffer usage in a SSL connection.
+       Thanks to Ben Maurer.
+
+    *) Bugfix: the STARTTLS in SMTP mode did not work.
+       Thanks to Oleg Motienko.
+
+    *) Bugfix: in HTTPS mode requests might fail with the "bad write retry"
+       error; the bug had appeared in 0.5.13.
+
+
+Changes with nginx 0.6.22                                        19 Dec 2007
+
+    *) Change: now all ngx_http_perl_module methods return values copied to
+       perl's allocated memory.
+
+    *) Bugfix: if nginx was built with ngx_http_perl_module, the perl before
+       5.8.6 was used, and perl supported threads, then during
+       reconfiguration the master process aborted; the bug had appeared in
+       0.5.9.
+       Thanks to Boris Zhmurov.
+
+    *) Bugfix: the ngx_http_perl_module methods may get invalid values of
+       the regex captures.
+
+    *) Bugfix: a segmentation fault occurred in worker process, if the
+       $r->has_request_body() method was called for a request whose small
+       request body was already received.
+
+    *) Bugfix: large_client_header_buffers did not freed before going to
+       keep-alive state.
+       Thanks to Olexander Shtepa.
+
+    *) Bugfix: the last address was missed in the $upstream_addr variable;
+       the bug had appeared in 0.6.18.
+
+    *) Bugfix: the "fastcgi_catch_stderr" directive did return error code;
+       now it returns 502 code, that can be rerouted to a next server using
+       the "fastcgi_next_upstream invalid_header" directive.
+
+    *) Bugfix: a segmentation fault occurred in master process if the
+       "fastcgi_catch_stderr" directive was used; the bug had appeared in
+       0.6.10.
+       Thanks to Manlio Perillo.
+
+
+Changes with nginx 0.6.21                                        03 Dec 2007
+
+    *) Change: if variable values used in a "proxy_pass" directive contain
+       IP-addresses only, then a "resolver" directive is not mandatory.
+
+    *) Bugfix: a segmentation fault might occur in worker process if a
+       "proxy_pass" directive with URI-part was used; the bug had appeared
+       in 0.6.19.
+
+    *) Bugfix: if resolver was used on platform that does not support
+       kqueue, then nginx issued an alert "name is out of response".
+       Thanks to Andrei Nigmatulin.
+
+    *) Bugfix: if the $server_protocol was used in FastCGI parameters and a
+       request line length was near to the "client_header_buffer_size"
+       directive value, then nginx issued an alert "fastcgi: the request
+       record is too big".
+
+    *) Bugfix: if a plain text HTTP/0.9 version request was made to HTTPS
+       server, then nginx returned usual response.
+
+
+Changes with nginx 0.6.20                                        28 Nov 2007
+
+    *) Bugfix: a segmentation fault might occur in worker process if a
+       "proxy_pass" directive with URI-part was used; the bug had appeared
+       in 0.6.19.
+
+
+Changes with nginx 0.6.19                                        27 Nov 2007
+
+    *) Bugfix: the 0.6.18 version could not be built.
+
+
+Changes with nginx 0.6.18                                        27 Nov 2007
+
+    *) Change: now the ngx_http_userid_module adds start time microseconds
+       to the cookie field contains a pid value.
+
+    *) Change: now the full request line instead of URI only is written to
+       error_log.
+
+    *) Feature: variables support in the "proxy_pass" directive.
+
+    *) Feature: the "resolver" and "resolver_timeout" directives.
+
+    *) Feature: now the directive "add_header last-modified ''" deletes a
+       "Last-Modified" response header line.
+
+    *) Bugfix: the "limit_rate" directive did not allow to use full
+       throughput, even if limit value was very high.
+
+
+Changes with nginx 0.6.17                                        15 Nov 2007
+
+    *) Feature: the "If-Range" request header line support.
+       Thanks to Alexander V. Inyukhin.
+
+    *) Bugfix: URL double escaping in a redirect of the "msie_refresh"
+       directive; the bug had appeared in 0.6.4.
+
+    *) Bugfix: the "autoindex" directive did not work with the "alias /"
+       directive.
+
+    *) Bugfix: a segmentation fault might occur in worker process if
+       subrequests were used.
+
+    *) Bugfix: the big responses may be transferred truncated if SSL and
+       gzip were used.
+
+    *) Bugfix: the $status variable was equal to 0 if a proxied server
+       returned response in HTTP/0.9 version.
+
+
+Changes with nginx 0.6.16                                        29 Oct 2007
+
+    *) Change: now the uname(2) is used on Linux instead of procfs.
+       Thanks to Ilya Novikov.
+
+    *) Bugfix: if the "?" character was in a "error_page" directive, then it
+       was escaped in a proxied request; the bug had appeared in 0.6.11.
+
+    *) Bugfix: compatibility with mget.
+
+
+Changes with nginx 0.6.15                                        22 Oct 2007
+
+    *) Feature: Cygwin compatibility.
+       Thanks to Vladimir Kutakov.
+
+    *) Feature: the "merge_slashes" directive.
+
+    *) Feature: the "gzip_vary" directive.
+
+    *) Feature: the "server_tokens" directive.
+
+    *) Bugfix: nginx did not unescape URI in the "include" SSI command.
+
+    *) Bugfix: the segmentation fault was occurred on start or while
+       reconfiguration if variable was used in the "charset" or
+       "source_charset" directives.
+
+    *) Bugfix: nginx returned the 400 response on requests like
+       "GET http://www.domain.com HTTP/1.0".
+       Thanks to James Oakley.
+
+    *) Bugfix: if request with request body was redirected using the
+       "error_page" directive, then nginx tried to read the request body
+       again; the bug had appeared in 0.6.7.
+
+    *) Bugfix: a segmentation fault occurred in worker process if no
+       server_name was explicitly defined for server processing request; the
+       bug had appeared in 0.6.7.
+
+
+Changes with nginx 0.6.14                                        15 Oct 2007
+
+    *) Change: now by default the "echo" SSI command uses entity encoding.
+
+    *) Feature: the "encoding" parameter in the "echo" SSI command.
+
+    *) Feature: the "access_log" directive may be used inside the
+       "limit_except" block.
+
+    *) Bugfix: if all upstream servers were failed, then all servers had got
+       weight the was equal one until servers became alive; the bug had
+       appeared in 0.6.6.
+
+    *) Bugfix: a segmentation fault occurred in worker process if
+       $date_local and $date_gmt were used outside the
+       ngx_http_ssi_filter_module.
+
+    *) Bugfix: a segmentation fault might occur in worker process if debug
+       log was enabled.
+       Thanks to Andrei Nigmatulin.
+
+    *) Bugfix: ngx_http_memcached_module did not set
+       $upstream_response_time.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: a worker process may got caught in an endless loop, if the
+       memcached was used.
+
+    *) Bugfix: nginx supported low case only "close" and "keep-alive" values
+       in the "Connection" request header line; the bug had appeared in
+       0.6.11.
+
+    *) Bugfix: sub_filter did not work with empty substitution.
+
+    *) Bugfix: in sub_filter parsing.
+
+
+Changes with nginx 0.6.13                                        24 Sep 2007
+
+    *) Bugfix: nginx did not close directory file on HEAD request if
+       autoindex was used.
+       Thanks to Arkadiusz Patyk.
+
+
+Changes with nginx 0.6.12                                        21 Sep 2007
+
+    *) Change: mail proxy was split on three modules: pop3, imap and smtp.
+
+    *) Feature: the --without-mail_pop3_module, --without-mail_imap_module,
+       and --without-mail_smtp_module configuration parameters.
+
+    *) Feature: the "smtp_greeting_delay" and "smtp_client_buffer"
+       directives of the ngx_mail_smtp_module.
+
+    *) Bugfix: the trailing wildcards did not work; the bug had appeared in
+       0.6.9.
+
+    *) Bugfix: nginx could not start on Solaris if the shared PCRE library
+       located in non-standard place was used.
+
+    *) Bugfix: the "proxy_hide_header" and "fastcgi_hide_header" directives
+       did not hide response header lines whose name was longer than 32
+       characters.
+       Thanks to Manlio Perillo.
+
+
+Changes with nginx 0.6.11                                        11 Sep 2007
+
+    *) Bugfix: active connection counter always increased if mail proxy was
+       used.
+
+    *) Bugfix: if backend returned response header only using non-buffered
+       proxy, then nginx closed backend connection on timeout.
+
+    *) Bugfix: nginx did not support several "Connection" request header
+       lines.
+
+    *) Bugfix: if the "max_fails" was set for upstream server, then after
+       first failure server weight was always one; the bug had appeared in
+       0.6.6.
+
+
+Changes with nginx 0.6.10                                        03 Sep 2007
+
+    *) Feature: the "open_file_cache", "open_file_cache_retest", and
+       "open_file_cache_errors" directives.
+
+    *) Bugfix: socket leak; the bug had appeared in 0.6.7.
+
+    *) Bugfix: a charset set by the "charset" directive was not appended to
+       the "Content-Type" header set by $r->send_http_header().
+
+    *) Bugfix: a segmentation fault might occur in worker process if
+       /dev/poll method was used.
+
+
+Changes with nginx 0.6.9                                         28 Aug 2007
+
+    *) Bugfix: a worker process may got caught in an endless loop, if the
+       HTTPS protocol was used; the bug had appeared in 0.6.7.
+
+    *) Bugfix: if server listened on two addresses or ports and trailing
+       wildcard was used, then nginx did not run.
+
+    *) Bugfix: the "ip_hash" directive might incorrectly mark servers as
+       down.
+
+    *) Bugfix: nginx could not be built on amd64; the bug had appeared in
+       0.6.8.
+
+
+Changes with nginx 0.6.8                                         20 Aug 2007
+
+    *) Change: now nginx tries to set the "worker_priority",
+       "worker_rlimit_nofile", "worker_rlimit_core", and
+       "worker_rlimit_sigpending" without super-user privileges.
+
+    *) Change: now nginx escapes space and "%" in request to a mail proxy
+       authentication server.
+
+    *) Change: now nginx escapes "%" in $memcached_key variable.
+
+    *) Bugfix: nginx used path relative to configuration prefix for
+       non-absolute configuration file path specified in the "-c" key; the
+       bug had appeared in 0.6.6.
+
+    *) Bugfix: nginx did not work on FreeBSD/sparc64.
+
+
+Changes with nginx 0.6.7                                         15 Aug 2007
+
+    *) Change: now the paths specified in the "include",
+       "auth_basic_user_file", "perl_modules", "ssl_certificate",
+       "ssl_certificate_key", and "ssl_client_certificate" directives are
+       relative to directory of nginx configuration file nginx.conf, but not
+       to nginx prefix directory.
+
+    *) Change: the --sysconfdir=PATH option in configure was canceled.
+
+    *) Change: the special make target "upgrade1" was defined for online
+       upgrade of 0.1.x versions.
+
+    *) Feature: the "server_name" and "valid_referers" directives support
+       regular expressions.
+
+    *) Feature: the "server" directive in the "upstream" context supports
+       the "backup" parameter.
+
+    *) Feature: the ngx_http_perl_module supports the
+       $r->discard_request_body.
+
+    *) Feature: the "add_header Last-Modified ..." directive changes the
+       "Last-Modified" response header line.
+
+    *) Bugfix: if a response different than 200 was returned to a request
+       with body and connection went to the keep-alive state after the
+       request, then nginx returned 400 for the next request.
+
+    *) Bugfix: a segmentation fault occurred in worker process if invalid
+       address was set in the "auth_http" directive.
+
+    *) Bugfix: now nginx uses default listen backlog value 511 on all
+       platforms except FreeBSD.
+       Thanks to Jiang Hong.
+
+    *) Bugfix: a worker process may got caught in an endless loop, if a
+       "server" inside "upstream" block was marked as "down"; the bug had
+       appeared in 0.6.6.
+
+    *) Bugfix: now Solaris sendfilev() is not used to transfer the client
+       request body to FastCGI-server via the unix domain socket.
+
+
+Changes with nginx 0.6.6                                         30 Jul 2007
+
+    *) Feature: the --sysconfdir=PATH option in configure.
+
+    *) Feature: named locations.
+
+    *) Feature: the $args variable can be set with the "set" directive.
+
+    *) Feature: the $is_args variable.
+
+    *) Bugfix: fair big weight upstream balancer.
+
+    *) Bugfix: if a client has closed connection to mail proxy then nginx
+       might not close connection to backend.
+
+    *) Bugfix: if the same host without specified port was used as backend
+       for HTTP and HTTPS, then nginx used only one port - 80 or 443.
+
+    *) Bugfix: fix building on Solaris/amd64 by Sun Studio 11 and early
+       versions; the bug had appeared in 0.6.4.
+
+
+Changes with nginx 0.6.5                                         23 Jul 2007
+
+    *) Feature: $nginx_version variable.
+       Thanks to Nick S. Grechukh.
+
+    *) Feature: the mail proxy supports AUTHENTICATE in IMAP mode.
+       Thanks to Maxim Dounin.
+
+    *) Feature: the mail proxy supports STARTTLS in SMTP mode.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: now nginx escapes space in $memcached_key variable.
+
+    *) Bugfix: nginx was incorrectly built by Sun Studio on Solaris/amd64.
+       Thanks to Jiang Hong.
+
+    *) Bugfix: of minor potential bugs.
+       Thanks to Coverity's Scan.
+
+
+Changes with nginx 0.6.4                                         17 Jul 2007
+
+    *) Security: the "msie_refresh" directive allowed XSS.
+       Thanks to Maxim Boguk.
+
+    *) Change: the "proxy_store" and "fastcgi_store" directives were
+       changed.
+
+    *) Feature: the "proxy_store_access" and "fastcgi_store_access"
+       directives.
+
+    *) Bugfix: nginx did not work on Solaris/sparc64 if it was built by Sun
+       Studio.
+       Thanks to Andrei Nigmatulin.
+
+    *) Workaround: for Sun Studio 12.
+       Thanks to Jiang Hong.
+
+
+Changes with nginx 0.6.3                                         12 Jul 2007
+
+    *) Feature: the "proxy_store" and "fastcgi_store" directives.
+
+    *) Bugfix: a segmentation fault might occur in worker process if the
+       "auth_http_header" directive was used.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: a segmentation fault occurred in worker process if the
+       CRAM-MD5 authentication method was used, but it was not enabled.
+
+    *) Bugfix: a segmentation fault might occur in worker process when the
+       HTTPS protocol was used in the "proxy_pass" directive.
+
+    *) Bugfix: a segmentation fault might occur in worker process if the
+       eventport method was used.
+
+    *) Bugfix: the "proxy_ignore_client_abort" and
+       "fastcgi_ignore_client_abort" directives did not work; the bug had
+       appeared in 0.5.13.
+
+
+Changes with nginx 0.6.2                                         09 Jul 2007
+
+    *) Bugfix: if the FastCGI header was split in records, then nginx passed
+       garbage in the header to a client.
+
+
+Changes with nginx 0.6.1                                         17 Jun 2007
+
+    *) Bugfix: in SSI parsing.
+
+    *) Bugfix: if remote SSI subrequest was used, then posterior local file
+       subrequest might transferred to client in wrong order.
+
+    *) Bugfix: large SSI inclusions buffered in temporary files were
+       truncated.
+
+    *) Bugfix: the perl $$ variable value in ngx_http_perl_module was equal
+       to the master process identification number.
+
+
+Changes with nginx 0.6.0                                         14 Jun 2007
+
+    *) Feature: the "server_name", "map", and "valid_referers" directives
+       support the "www.example.*" wildcards.
+
+
+Changes with nginx 0.5.25                                        11 Jun 2007
+
+    *) Bugfix: nginx could not be built with the
+       --without-http_rewrite_module parameter; the bug had appeared in
+       0.5.24.
+
+
+Changes with nginx 0.5.24                                        06 Jun 2007
+
+    *) Security: the "ssl_verify_client" directive did not work if request
+       was made using HTTP/0.9.
+
+    *) Bugfix: a part of response body might be passed uncompressed if gzip
+       was used; the bug had appeared in 0.5.23.
+
+
+Changes with nginx 0.5.23                                        04 Jun 2007
+
+    *) Feature: the ngx_http_ssl_module supports Server Name Indication TLS
+       extension.
+
+    *) Feature: the "fastcgi_catch_stderr" directive.
+       Thanks to Nick S. Grechukh, OWOX project.
+
+    *) Bugfix: a segmentation fault occurred in master process if two
+       virtual servers should bind() to the overlapping ports.
+
+    *) Bugfix: if nginx was built with ngx_http_perl_module and perl
+       supported threads, then during second reconfiguration the error
+       messages "panic: MUTEX_LOCK" and "perl_parse() failed" were issued.
+
+    *) Bugfix: in the HTTPS protocol in the "proxy_pass" directive.
+
+
+Changes with nginx 0.5.22                                        29 May 2007
+
+    *) Bugfix: a big request body might not be passed to backend; the bug
+       had appeared in 0.5.21.
+
+
+Changes with nginx 0.5.21                                        28 May 2007
+
+    *) Bugfix: if server has more than about ten locations, then regex
+       locations might be chosen not in that order as they were specified.
+
+    *) Bugfix: a worker process may got caught in an endless loop on 64-bit
+       platform, if the 33-rd or next in succession backend has failed.
+       Thanks to Anton Povarov.
+
+    *) Bugfix: a bus error might occur on Solaris/sparc64 if the PCRE
+       library was used.
+       Thanks to Andrei Nigmatulin.
+
+    *) Bugfix: in the HTTPS protocol in the "proxy_pass" directive.
+
+
+Changes with nginx 0.5.20                                        07 May 2007
+
+    *) Feature: the "sendfile_max_chunk" directive.
+
+    *) Feature: the "$http_...", "$sent_http_...", and "$upstream_http_..."
+       variables may be changed using the "set" directive.
+
+    *) Bugfix: a segmentation fault might occur in worker process if the SSI
+       command 'if expr="$var = /"' was used.
+
+    *) Bugfix: trailing boundary of multipart range response was transferred
+       incorrectly.
+       Thanks to Evan Miller.
+
+    *) Bugfix: nginx did not work on Solaris/sparc64 if it was built by Sun
+       Studio.
+       Thanks to Andrei Nigmatulin.
+
+    *) Bugfix: the ngx_http_perl_module could not be built by Solaris make.
+       Thanks to Andrei Nigmatulin.
+
+
+Changes with nginx 0.5.19                                        24 Apr 2007
+
+    *) Change: now the $request_time variable has millisecond precision.
+
+    *) Change: the method $r->rflush of ngx_http_perl_module was renamed to
+       the $r->flush.
+
+    *) Feature: the $upstream_addr variable.
+
+    *) Feature: the "proxy_headers_hash_max_size" and
+       "proxy_headers_hash_bucket_size" directives.
+       Thanks to Volodymyr Kostyrko.
+
+    *) Bugfix: the files more than 2G could not be transferred using
+       sendfile and limit_rate on 64-bit platforms.
+
+    *) Bugfix: the files more than 2G could not be transferred using
+       sendfile on 64-bit Linux.
+
+
+Changes with nginx 0.5.18                                        19 Apr 2007
+
+    *) Feature: the ngx_http_sub_filter_module.
+
+    *) Feature: the "$upstream_http_..." variables.
+
+    *) Feature: now the $upstream_status and $upstream_response_time
+       variables keep data about all upstreams before X-Accel-Redirect.
+
+    *) Bugfix: a segmentation fault occurred in master process after first
+       reconfiguration and receiving any signal if nginx was built with
+       ngx_http_perl_module and perl did not support multiplicity; the bug
+       had appeared in 0.5.9.
+
+    *) Bugfix: if perl did not support multiplicity, then after
+       reconfiguration perl code did not work; the bug had appeared in
+       0.3.38.
+
+
+Changes with nginx 0.5.17                                        02 Apr 2007
+
+    *) Change: now nginx always returns the 405 status for the TRACE method.
+
+    *) Feature: now nginx supports the "include" directive inside the
+       "types" block.
+
+    *) Bugfix: the $document_root variable usage in the "root" and "alias"
+       directives is disabled: this caused recursive stack overflow.
+
+    *) Bugfix: in the HTTPS protocol in the "proxy_pass" directive.
+
+    *) Bugfix: in some cases non-cacheable variables (such as $uri variable)
+       returned old cached value.
+
+
+Changes with nginx 0.5.16                                        26 Mar 2007
+
+    *) Bugfix: the C-class network was not used as hash key in the "ip_hash"
+       directive.
+       Thanks to Pavel Yarkovoy.
+
+    *) Bugfix: a segmentation fault might occur in worker process if a
+       charset was set in the "Content-Type" header line and the line has
+       trailing ";"; the bug had appeared in 0.3.50.
+
+    *) Bugfix: the "[alert] zero size buf" error when FastCGI server was
+       used and a request body written in a temporary file was multiple of
+       32K.
+
+    *) Bugfix: nginx could not be built on Solaris without the --with-debug
+       option; the bug had appeared in 0.5.15.
+
+
+Changes with nginx 0.5.15                                        19 Mar 2007
+
+    *) Feature: the mail proxy supports authenticated SMTP proxying and the
+       "smtp_auth", "smtp_capabilities", and "xclient" directives.
+       Thanks to Anton Yuzhaninov and Maxim Dounin.
+
+    *) Feature: now the keep-alive connections are closed just after
+       receiving the reconfiguration signal.
+
+    *) Change: the "imap" and "auth" directives were renamed to the "mail"
+       and "pop3_auth" directives.
+
+    *) Bugfix: a segmentation fault occurred in worker process if the
+       CRAM-MD5 authentication method was used and the APOP method was
+       disabled.
+
+    *) Bugfix: if the "starttls only" directive was used in POP3 protocol,
+       then nginx allowed authentication without switching to the SSL mode.
+
+    *) Bugfix: worker processes did not exit after reconfiguration and did
+       not rotate logs if the eventport method was used.
+
+    *) Bugfix: a worker process may got caught in an endless loop, if the
+       "ip_hash" directive was used.
+
+    *) Bugfix: now nginx does not log some alerts if eventport or /dev/poll
+       methods are used.
+
+
+Changes with nginx 0.5.14                                        23 Feb 2007
+
+    *) Bugfix: nginx ignored superfluous closing "}" in the end of
+       configuration file.
+
+
+Changes with nginx 0.5.13                                        19 Feb 2007
+
+    *) Feature: the COPY and MOVE methods.
+
+    *) Bugfix: the ngx_http_realip_module set garbage for requests passed
+       via keep-alive connection.
+
+    *) Bugfix: nginx did not work on big-endian 64-bit Linux.
+       Thanks to Andrei Nigmatulin.
+
+    *) Bugfix: now when IMAP/POP3 proxy receives too long command it closes
+       the connection right away, but not after timeout.
+
+    *) Bugfix: if the "epoll" method was used and a client closed a
+       connection prematurely, then nginx closed the connection after a send
+       timeout only.
+
+    *) Bugfix: nginx could not be built on platforms different from i386,
+       amd64, sparc, and ppc; the bug had appeared in 0.5.8.
+
+
+Changes with nginx 0.5.12                                        12 Feb 2007
+
+    *) Bugfix: nginx could not be built on platforms different from i386,
+       amd64, sparc, and ppc; the bug had appeared in 0.5.8.
+
+    *) Bugfix: a segmentation fault might occur in worker process if the
+       temporary files were used while working with FastCGI server; the bug
+       had appeared in 0.5.8.
+
+    *) Bugfix: a segmentation fault might occur in worker process if the
+       $fastcgi_script_name variable was logged.
+
+    *) Bugfix: ngx_http_perl_module could not be built on Solaris.
+
+
+Changes with nginx 0.5.11                                        05 Feb 2007
+
+    *) Feature: now configure detects system PCRE library in MacPorts.
+       Thanks to Chris McGrath.
+
+    *) Bugfix: the response was incorrect if several ranges were requested;
+       the bug had appeared in 0.5.6.
+
+    *) Bugfix: the "create_full_put_path" directive could not create the
+       intermediate directories if no "dav_access" directive was set.
+       Thanks to Evan Miller.
+
+    *) Bugfix: the "0" response code might be logged in the access_log
+       instead of the "400" and "408" error codes.
+
+    *) Bugfix: a segmentation fault might occur in worker process if nginx
+       was built with -O2 optimization.
+
+
+Changes with nginx 0.5.10                                        26 Jan 2007
+
+    *) Bugfix: while online executable file upgrade the new master process
+       did not inherit the listening sockets; the bug had appeared in 0.5.9.
+
+    *) Bugfix: a segmentation fault might occur in worker process if nginx
+       was built with -O2 optimization; the bug had appeared in 0.5.1.
+
+
+Changes with nginx 0.5.9                                         25 Jan 2007
+
+    *) Change: now the ngx_http_memcached_module uses the $memcached_key
+       variable value as a key.
+
+    *) Feature: the $memcached_key variable.
+
+    *) Feature: the "clean" parameter in the "client_body_in_file_only"
+       directive.
+
+    *) Feature: the "env" directive.
+
+    *) Feature: the "sendfile" directive is available inside the "if" block.
+
+    *) Feature: now on failure of the writing to access nginx logs a message
+       to error_log, but not more often than once a minute.
+
+    *) Bugfix: the "access_log off" directive did not always turn off the
+       logging.
+
+
+Changes with nginx 0.5.8                                         19 Jan 2007
+
+    *) Bugfix: a segmentation fault might occur if
+       "client_body_in_file_only on" was used and a request body was small.
+
+    *) Bugfix: a segmentation fault occurred if
+       "client_body_in_file_only on" and "proxy_pass_request_body off" or
+       "fastcgi_pass_request_body off" directives were used, and nginx
+       switched to a next upstream.
+
+    *) Bugfix: if the "proxy_buffering off" directive was used and a client
+       connection was non-active, then the connection was closed after send
+       timeout; the bug had appeared in 0.4.7.
+
+    *) Bugfix: if the "epoll" method was used and a client closed a
+       connection prematurely, then nginx closed the connection after a send
+       timeout only.
+
+    *) Bugfix: the "[alert] zero size buf" error when FastCGI server was
+       used.
+
+    *) Bugfixes in the "limit_zone" directive.
+
+
+Changes with nginx 0.5.7                                         15 Jan 2007
+
+    *) Feature: the ssl_session_cache storage optimization.
+
+    *) Bugfixes in the "ssl_session_cache" and "limit_zone" directives.
+
+    *) Bugfix: the segmentation fault was occurred on start or while
+       reconfiguration if the "ssl_session_cache" or "limit_zone" directives
+       were used on 64-bit platforms.
+
+    *) Bugfix: a segmentation fault occurred if the "add_before_body" or
+       "add_after_body" directives were used and there was no "Content-Type"
+       header line in response.
+
+    *) Bugfix: the OpenSSL library was always built with the threads
+       support.
+       Thanks to Den Ivanov.
+
+    *) Bugfix: the PCRE-6.5+ library and the icc compiler compatibility.
+
+
+Changes with nginx 0.5.6                                         09 Jan 2007
+
+    *) Change: now the ngx_http_index_module ignores all methods except the
+       GET, HEAD, and POST methods.
+
+    *) Feature: the ngx_http_limit_zone_module.
+
+    *) Feature: the $binary_remote_addr variable.
+
+    *) Feature: the "ssl_session_cache" directives of the
+       ngx_http_ssl_module and ngx_imap_ssl_module.
+
+    *) Feature: the DELETE method supports recursive removal.
+
+    *) Bugfix: the byte-ranges were transferred incorrectly if the
+       $r->sendfile() was used.
+
+
+Changes with nginx 0.5.5                                         24 Dec 2006
+
+    *) Change: the -v switch does not show compiler information any more.
+
+    *) Feature: the -V switch.
+
+    *) Feature: the "worker_rlimit_core" directive supports size in K, M,
+       and G.
+
+    *) Bugfix: the nginx.pm module now could be installed by an unprivileged
+       user.
+
+    *) Bugfix: a segmentation fault might occur if the $r->request_body or
+       $r->request_body_file methods were used.
+
+    *) Bugfix: the ppc platform specific bugs.
+
+
+Changes with nginx 0.5.4                                         15 Dec 2006
+
+    *) Feature: the "perl" directive may be used inside the "limit_except"
+       block.
+
+    *) Bugfix: the ngx_http_dav_module required the "Date" request header
+       line for the DELETE method.
+
+    *) Bugfix: if one only parameter was used in the "dav_access" directive,
+       then nginx might report about configuration error.
+
+    *) Bugfix: a segmentation fault might occur if the $host variable was
+       used; the bug had appeared in 0.4.14.
+
+
+Changes with nginx 0.5.3                                         13 Dec 2006
+
+    *) Feature: the ngx_http_perl_module supports the $r->status,
+       $r->log_error, and $r->sleep methods.
+
+    *) Feature: the $r->variable method supports variables that do not exist
+       in nginx configuration.
+
+    *) Bugfix: the $r->has_request_body method did not work.
+
+
+Changes with nginx 0.5.2                                         11 Dec 2006
+
+    *) Bugfix: if the "proxy_pass" directive used the name of the "upstream"
+       block, then nginx tried to resolve the name; the bug had appeared in
+       0.5.1.
+
+
+Changes with nginx 0.5.1                                         11 Dec 2006
+
+    *) Bugfix: the "post_action" directive might not run after a
+       unsuccessful completion of a request.
+
+    *) Workaround: for Eudora for Mac; the bug had appeared in 0.4.11.
+       Thanks to Bron Gondwana.
+
+    *) Bugfix: if the "upstream" name was used in the "fastcgi_pass", then
+       the message "no port in upstream" was issued; the bug had appeared in
+       0.5.0.
+
+    *) Bugfix: if the "proxy_pass" and "fastcgi_pass" directives used the
+       same servers but different ports, then these directives uses the
+       first described port; the bug had appeared in 0.5.0.
+
+    *) Bugfix: if the "proxy_pass" and "fastcgi_pass" directives used the
+       unix domain sockets, then these directives used first described
+       socket; the bug had appeared in 0.5.0.
+
+    *) Bugfix: ngx_http_auth_basic_module ignored the user if it was in the
+       last line in the password file and there was no the carriage return,
+       the line feed, or the ":" symbol after the password.
+
+    *) Bugfix: the $upstream_response_time variable might be equal to
+       "0.000", although response time was more than 1 millisecond.
+
+
+Changes with nginx 0.5.0                                         04 Dec 2006
+
+    *) Change: the parameters in the "%name" form in the "log_format"
+       directive are not supported anymore.
+
+    *) Change: the "proxy_upstream_max_fails",
+       "proxy_upstream_fail_timeout", "fastcgi_upstream_max_fails",
+       "fastcgi_upstream_fail_timeout", "memcached_upstream_max_fails", and
+       "memcached_upstream_fail_timeout" directives are not supported
+       anymore.
+
+    *) Feature: the "server" directive in the "upstream" context supports
+       the "max_fails", "fail_timeout", and "down" parameters.
+
+    *) Feature: the "ip_hash" directive inside the "upstream" block.
+
+    *) Feature: the WAIT status in the "Auth-Status" header line of the
+       IMAP/POP3 proxy authentication server response.
+
+    *) Bugfix: nginx could not be built on 64-bit platforms; the bug had
+       appeared in 0.4.14.
+
+
+Changes with nginx 0.4.14                                        27 Nov 2006
+
+    *) Feature: the "proxy_pass_error_message" directive in IMAP/POP3 proxy.
+
+    *) Feature: now configure detects system PCRE library on FreeBSD, Linux,
+       and NetBSD.
+
+    *) Bugfix: ngx_http_perl_module did not work with perl built with the
+       threads support; the bug had appeared in 0.3.38.
+
+    *) Bugfix: ngx_http_perl_module did not work if perl was called
+       recursively.
+
+    *) Bugfix: nginx ignored a host name in a request line.
+
+    *) Bugfix: a worker process may got caught in an endless loop, if a
+       FastCGI server sent too many data to the stderr.
+
+    *) Bugfix: the $upstream_response_time variable may be negative if the
+       system time was changed backward.
+
+    *) Bugfix: the "Auth-Login-Attempt" parameter was not sent to IMAP/POP3
+       proxy authentication server when POP3 was used.
+
+    *) Bugfix: a segmentation fault might occur if connect to IMAP/POP3
+       proxy authentication server failed.
+
+
+Changes with nginx 0.4.13                                        15 Nov 2006
+
+    *) Feature: the "proxy_pass" directive may be used inside the
+       "limit_except" block.
+
+    *) Feature: the "limit_except" directive supports all WebDAV methods.
+
+    *) Bugfix: if the "add_before_body" directive was used without the
+       "add_after_body" directive, then a response did not transferred
+       complete.
+
+    *) Bugfix: a large request body did not receive if the epoll method and
+       the deferred accept() were used.
+
+    *) Bugfix: a charset could not be set for ngx_http_autoindex_module
+       responses; the bug had appeared in 0.3.50.
+
+    *) Bugfix: the "[alert] zero size buf" error when FastCGI server was
+       used;
+
+    *) Bugfix: the --group= configuration parameter was ignored.
+       Thanks to Thomas Moschny.
+
+    *) Bugfix: the 50th subrequest in SSI response did not work; the bug had
+       appeared in 0.3.50.
+
+
+Changes with nginx 0.4.12                                        31 Oct 2006
+
+    *) Feature: the ngx_http_perl_module supports the $r->variable method.
+
+    *) Bugfix: if a big static file was included using SSI in a response,
+       then the response may be transferred incomplete.
+
+    *) Bugfix: nginx did not omit the "#fragment" part in URI.
+
+
+Changes with nginx 0.4.11                                        25 Oct 2006
+
+    *) Feature: the POP3 proxy supports the AUTH LOGIN PLAIN and CRAM-MD5.
+
+    *) Feature: the ngx_http_perl_module supports the $r->allow_ranges
+       method.
+
+    *) Bugfix: if the APOP was enabled in the POP3 proxy, then the USER/PASS
+       commands might not work; the bug had appeared in 0.4.10.
+
+
+Changes with nginx 0.4.10                                        23 Oct 2006
+
+    *) Feature: the POP3 proxy supports the APOP command.
+
+    *) Bugfix: if the select, poll or /dev/poll methods were used, then
+       while waiting authentication server response the IMAP/POP3 proxy
+       hogged CPU.
+
+    *) Bugfix: a segmentation fault might occur if the $server_addr variable
+       was used in the "map" directive.
+
+    *) Bugfix: the ngx_http_flv_module did not support the byte ranges for
+       full responses; the bug had appeared in 0.4.7.
+
+    *) Bugfix: nginx could not be built on Debian amd64; the bug had
+       appeared in 0.4.9.
+
+
+Changes with nginx 0.4.9                                         13 Oct 2006
+
+    *) Feature: the "set" parameter in the "include" SSI command.
+
+    *) Feature: the ngx_http_perl_module now tests the nginx.pm module
+       version.
+
+
+Changes with nginx 0.4.8                                         11 Oct 2006
+
+    *) Bugfix: if an "include" SSI command were before another "include" SSI
+       command with a "wait" parameter, then the "wait" parameter might not
+       work.
+
+    *) Bugfix: the ngx_http_flv_module added the FLV header to the full
+       responses.
+       Thanks to Alexey Kovyrin.
+
+
+Changes with nginx 0.4.7                                         10 Oct 2006
+
+    *) Feature: the ngx_http_flv_module.
+
+    *) Feature: the $request_body_file variable.
+
+    *) Feature: the "charset" and "source_charset" directives support the
+       variables.
+
+    *) Bugfix: if an "include" SSI command were before another "include" SSI
+       command with a "wait" parameter, then the "wait" parameter might not
+       work.
+
+    *) Bugfix: if the "proxy_buffering off" directive was used or while
+       working with memcached the connections might not be closed on
+       timeout.
+
+    *) Bugfix: nginx did not run on 64-bit platforms except amd64, sparc64,
+       and ppc64.
+
+
+Changes with nginx 0.4.6                                         06 Oct 2006
+
+    *) Bugfix: nginx did not run on 64-bit platforms except amd64, sparc64,
+       and ppc64.
+
+    *) Bugfix: nginx sent the chunked response for HTTP/1.1 request,
+       if its length was set by text string in the
+       $r->headers_out("Content-Length", ...) method.
+
+    *) Bugfix: after redirecting error by an "error_page" directive any
+       ngx_http_rewrite_module directive returned this error code; the bug
+       had appeared in 0.4.4.
+
+
+Changes with nginx 0.4.5                                         02 Oct 2006
+
+    *) Bugfix: nginx could not be built on Linux and Solaris; the bug had
+       appeared in 0.4.4.
+
+
+Changes with nginx 0.4.4                                         02 Oct 2006
+
+    *) Feature: the $scheme variable.
+
+    *) Feature: the "expires" directive supports the "max" parameter.
+
+    *) Feature: the "include" directive supports the "*" mask.
+       Thanks to Jonathan Dance.
+
+    *) Bugfix: the "return" directive always overrode the "error_page"
+       response code redirected by the "error_page" directive.
+
+    *) Bugfix: a segmentation fault occurred if zero-length body was in PUT
+       method.
+
+    *) Bugfix: the redirect was changed incorrectly if the variables were
+       used in the "proxy_redirect" directive.
+
+
+Changes with nginx 0.4.3                                         26 Sep 2006
+
+    *) Change: now the 499 error could not be redirected using an
+       "error_page" directive.
+
+    *) Feature: the Solaris 10 event ports support.
+
+    *) Feature: the ngx_http_browser_module.
+
+    *) Bugfix: a segmentation fault may occur while redirecting the 400
+       error to the proxied server using a "proxy_pass" directive.
+
+    *) Bugfix: a segmentation fault occurred if an unix domain socket was
+       used in a "proxy_pass" directive; the bug had appeared in 0.3.47.
+
+    *) Bugfix: SSI did work with memcached and nonbuffered responses.
+
+    *) Workaround: of the Sun Studio PAUSE hardware capability bug.
+
+
+Changes with nginx 0.4.2                                         14 Sep 2006
+
+    *) Bugfix: the O_NOATIME flag support on Linux was canceled; the bug had
+       appeared in 0.4.1.
+
+
+Changes with nginx 0.4.1                                         14 Sep 2006
+
+    *) Bugfix: the DragonFlyBSD compatibility.
+       Thanks to Pavel Nazarov.
+
+    *) Workaround: of bug in 64-bit Linux sendfile(), when file is more than
+       2G.
+
+    *) Feature: now on Linux nginx uses O_NOATIME flag for static requests.
+       Thanks to Yusuf Goolamabbas.
+
+
+Changes with nginx 0.4.0                                         30 Aug 2006
+
+    *) Change in internal API: the HTTP modules initialization was moved
+       from the init module phase to the HTTP postconfiguration phase.
+
+    *) Change: now the request body is not read beforehand for the
+       ngx_http_perl_module: it's required to start the reading using the
+       $r->has_request_body method.
+
+    *) Feature: the ngx_http_perl_module supports the DECLINED return code.
+
+    *) Feature: the ngx_http_dav_module supports the incoming "Date" header
+       line for the PUT method.
+
+    *) Feature: the "ssi" directive is available inside the "if" block.
+
+    *) Bugfix: a segmentation fault occurred if there was an "index"
+       directive with variables and the first index name was without
+       variables; the bug had appeared in 0.1.29.
+
+
+Changes with nginx 0.3.61                                        28 Aug 2006
+
+    *) Change: now the "tcp_nodelay" directive is turned on by default.
+
+    *) Feature: the "msie_refresh" directive.
+
+    *) Feature: the "recursive_error_pages" directive.
+
+    *) Bugfix: the "rewrite" directive returned incorrect redirect, if the
+       redirect had the captured escaped symbols from original URI.
+
+
+Changes with nginx 0.3.60                                        18 Aug 2006
+
+    *) Bugfix: a worker process may got caught in an endless loop while an
+       error redirection; the bug had appeared in 0.3.59.
+
+
+Changes with nginx 0.3.59                                        16 Aug 2006
+
+    *) Feature: now is possible to do several redirection using the
+       "error_page" directive.
+
+    *) Bugfix: the "dav_access" directive did not support three parameters.
+
+    *) Bugfix: the "error_page" directive did not changes the "Content-Type"
+       header line after the "X-Accel-Redirect" was used; the bug had
+       appeared in 0.3.58.
+
+
+Changes with nginx 0.3.58                                        14 Aug 2006
+
+    *) Feature: the "error_page" directive supports the variables.
+
+    *) Change: now the procfs interface instead of sysctl is used on Linux.
+
+    *) Change: now the "Content-Type" header line is inherited from first
+       response when the "X-Accel-Redirect" was used.
+
+    *) Bugfix: the "error_page" directive did not redirect the 413 error.
+
+    *) Bugfix: the trailing "?" did not remove old arguments if no new
+       arguments were added to a rewritten URI.
+
+    *) Bugfix: nginx could not run on 64-bit FreeBSD 7.0-CURRENT.
+
+
+Changes with nginx 0.3.57                                        09 Aug 2006
+
+    *) Feature: the $ssl_client_serial variable.
+
+    *) Bugfix: in the "!-e" operator of the "if" directive.
+       Thanks to Andrian Budanstov.
+
+    *) Bugfix: while a client certificate verification nginx did not send to
+       a client the required certificates information.
+
+    *) Bugfix: the $document_root variable did not support the variables in
+       the "root" directive.
+
+
+Changes with nginx 0.3.56                                        04 Aug 2006
+
+    *) Feature: the "dav_access" directive.
+
+    *) Feature: the "if" directive supports the "-d", "!-d", "-e", "!-e",
+       "-x", and "!-x" operators.
+
+    *) Bugfix: a segmentation fault occurred if a request returned a
+       redirect and some sent to client header lines were logged in the
+       access log.
+
+
+Changes with nginx 0.3.55                                        28 Jul 2006
+
+    *) Feature: the "stub" parameter in the "include" SSI command.
+
+    *) Feature: the "block" SSI command.
+
+    *) Feature: the unicode2nginx script was added to contrib.
+
+    *) Bugfix: if a "root" was specified by variable only, then the root was
+       relative to a server prefix.
+
+    *) Bugfix: if the request contained "//" or "/./" and escaped symbols
+       after them, then the proxied request was sent unescaped.
+
+    *) Bugfix: the $r->header_in("Cookie") of the ngx_http_perl_module now
+       returns all "Cookie" header lines.
+
+    *) Bugfix: a segmentation fault occurred if
+       "client_body_in_file_only on" was used and nginx switched to a next
+       upstream.
+
+    *) Bugfix: on some condition while reconfiguration character codes
+       inside the "charset_map" may be treated invalid; the bug had appeared
+       in 0.3.50.
+
+
+Changes with nginx 0.3.54                                        11 Jul 2006
+
+    *) Feature: nginx now logs the subrequest information to the error log.
+
+    *) Feature: the "proxy_next_upstream", "fastcgi_next_upstream", and
+       "memcached_next_upstream" directives support the "off" parameter.
+
+    *) Feature: the "debug_connection" directive supports the CIDR address
+       form.
+
+    *) Bugfix: if a response of proxied server or FastCGI server was
+       converted from UTF-8 or back, then it may be transferred incomplete.
+
+    *) Bugfix: the $upstream_response_time variable had the time of the
+       first request to a backend only.
+
+    *) Bugfix: nginx could not be built on amd64 platform; the bug had
+       appeared in 0.3.53.
+
+
+Changes with nginx 0.3.53                                        07 Jul 2006
+
+    *) Change: the "add_header" directive adds the string to 204, 301, and
+       302 responses.
+
+    *) Feature: the "server" directive in the "upstream" context supports
+       the "weight" parameter.
+
+    *) Feature: the "server_name" directive supports the "*" wildcard.
+
+    *) Feature: nginx supports the request body size more than 2G.
+
+    *) Bugfix: if a client was successfully authorized using "satisfy_any
+       on", then anyway the message "access forbidden by rule" was written
+       in the log.
+
+    *) Bugfix: the "PUT" method may erroneously not create a file and return
+       the 409 code.
+
+    *) Bugfix: if the IMAP/POP3 backend returned an error, then nginx
+       continued proxying anyway.
+
+
+Changes with nginx 0.3.52                                        03 Jul 2006
+
+    *) Change: the ngx_http_index_module behavior for the "POST /" requests
+       is reverted to the 0.3.40 version state: the module now does not
+       return the 405 error.
+
+    *) Bugfix: the worker process may got caught in an endless loop if the
+       limit rate was used; the bug had appeared in 0.3.37.
+
+    *) Bugfix: ngx_http_charset_module logged "unknown charset" alert, even
+       if the recoding was not needed; the bug had appeared in 0.3.50.
+
+    *) Bugfix: if a code response of the PUT request was 409, then a
+       temporary file was not removed.
+
+
+Changes with nginx 0.3.51                                        30 Jun 2006
+
+    *) Bugfix: the "<" symbols might disappeared some conditions in the SSI;
+       the bug had appeared in 0.3.50.
+
+
+Changes with nginx 0.3.50                                        28 Jun 2006
+
+    *) Change: the "proxy_redirect_errors" and "fastcgi_redirect_errors"
+       directives was renamed to the "proxy_intercept_errors" and
+       "fastcgi_intercept_errors" directives.
+
+    *) Feature: the ngx_http_charset_module supports the recoding from the
+       single byte encodings to the UTF-8 encoding and back.
+
+    *) Feature: the "X-Accel-Charset" response header line is supported in
+       proxy and FastCGI mode.
+
+    *) Bugfix: the "\" escape symbol in the "\"" and "\'" pairs in the SSI
+       command was removed only if the command also has the "$" symbol.
+
+    *) Bugfix: the "<!--" string might be added on some conditions in the
+       SSI after inclusion.
+
+    *) Bugfix: if the "Content-Length: 0" header line was in response, then
+       in nonbuffered proxying mode the client connection was not closed.
+
+
+Changes with nginx 0.3.49                                        31 May 2006
+
+    *) Bugfix: in the "set" directive.
+
+    *) Bugfix: if two or more FastCGI subrequests was in SSI, then first
+       subrequest output was included instead of second and following
+       subrequests.
+
+
+Changes with nginx 0.3.48                                        29 May 2006
+
+    *) Change: now the ngx_http_charset_module works for subrequests, if the
+       response has no "Content-Type" header line.
+
+    *) Bugfix: if the "proxy_pass" directive has no URI part, then the
+       "proxy_redirect default" directive add the unnecessary slash in start
+       of the rewritten redirect.
+
+    *) Bugfix: the internal redirect always transform client's HTTP method
+       to GET, now the transformation is made for the "X-Accel-Redirect"
+       redirects only and if the method is not HEAD; the bug had appeared in
+       0.3.42.
+
+    *) Bugfix: the ngx_http_perl_module could not be built, if the perl was
+       built with the threads support; the bug had appeared in 0.3.46.
+
+
+Changes with nginx 0.3.47                                        23 May 2006
+
+    *) Feature: the "upstream" directive.
+
+    *) Change: now the "\" escape symbol in the "\"" and "\'" pairs in the
+       SSI command is always removed.
+
+
+Changes with nginx 0.3.46                                        11 May 2006
+
+    *) Feature: the "proxy_hide_header", "proxy_pass_header",
+       "fastcgi_hide_header", and "fastcgi_pass_header" directives.
+
+    *) Change: the "proxy_pass_x_powered_by", "fastcgi_x_powered_by", and
+       "proxy_pass_server" directives were canceled.
+
+    *) Feature: the "X-Accel-Buffering" response header line is supported in
+       proxy mode.
+
+    *) Bugfix: the reconfiguration bug and memory leaks in the
+       ngx_http_perl_module.
+
+
+Changes with nginx 0.3.45                                        06 May 2006
+
+    *) Feature: the "ssl_verify_client", "ssl_verify_depth", and
+       "ssl_client_certificate" directives.
+
+    *) Change: the $request_method variable now returns the main request
+       method.
+
+    *) Change: the &deg; symbol codes were changed in koi-win conversion
+       table.
+
+    *) Feature: the euro and N symbols were added to koi-win conversion
+       table.
+
+    *) Bugfix: if nginx distributed the requests among several backends and
+       some backend failed, then requests intended for this backend was
+       directed to one live backend only instead of being distributed among
+       the rest.
+
+
+Changes with nginx 0.3.44                                        04 May 2006
+
+    *) Feature: the "wait" parameter in the "include" SSI command.
+
+    *) Feature: the Ukrainian and Byelorussian characters were added to
+       koi-win conversion table.
+
+    *) Bugfix: in the SSI.
+
+
+Changes with nginx 0.3.43                                        26 Apr 2006
+
+    *) Bugfix: in the SSI.
+
+
+Changes with nginx 0.3.42                                        26 Apr 2006
+
+    *) Feature: the "bind" option of the "listen" directive in IMAP/POP3
+       proxy.
+
+    *) Bugfix: if the same capture in the "rewrite" directive was used more
+       then once.
+
+    *) Bugfix: the $sent_http_content_type, $sent_http_content_length,
+       $sent_http_last_modified, $sent_http_connection,
+       $sent_http_keep_alive, and $sent_http_transfer_encoding variables
+       were not written to access log.
+
+    *) Bugfix: the $sent_http_cache_control returned value of the single
+       "Cache-Control" response header line.
+
+
+Changes with nginx 0.3.41                                        21 Apr 2006
+
+    *) Feature: the -v switch.
+
+    *) Bugfix: the segmentation fault may occurred if the SSI page has
+       remote subrequests.
+
+    *) Bugfix: in FastCGI handling.
+
+    *) Bugfix: if the perl modules path was not set using
+       --with-perl_modules_path=PATH or the "perl_modules", then the
+       segmentation fault was occurred.
+
+
+Changes with nginx 0.3.40                                        19 Apr 2006
+
+    *) Feature: the ngx_http_dav_module supports the MKCOL method.
+
+    *) Feature: the "create_full_put_path" directive.
+
+    *) Feature: the "$limit_rate" variable.
+
+
+Changes with nginx 0.3.39                                        17 Apr 2006
+
+    *) Feature: the "uninitialized_variable_warn" directive; the logging
+       level of the "uninitialized variable" message was lowered from
+       "alert" to "warn".
+
+    *) Feature: the "override_charset" directive.
+
+    *) Change: now if the unknown variable is used in the "echo" and "if
+       expr='$name'" SSI-commands, then the "unknown variable" message is
+       not logged.
+
+    *) Bugfix: the active connection counter increased on the exceeding of
+       the connection limit specified by the "worker_connections" directive;
+       the bug had appeared in 0.2.0.
+
+    *) Bugfix: the limit rate might not work on some condition; the bug had
+       appeared in 0.3.38.
+
+
+Changes with nginx 0.3.38                                        14 Apr 2006
+
+    *) Feature: the ngx_http_dav_module.
+
+    *) Change: the ngx_http_perl_module optimizations.
+       Thanks to Sergey Skvortsov.
+
+    *) Feature: the ngx_http_perl_module supports the $r->request_body_file
+       method.
+
+    *) Feature: the "client_body_in_file_only" directive.
+
+    *) Workaround: now on disk overflow nginx tries to write access logs
+       once a second only.
+       Thanks to Anton Yuzhaninov and Maxim Dounin.
+
+    *) Bugfix: now the "limit_rate" directive more precisely limits rate if
+       rate is more than 100 Kbyte/s.
+       Thanks to ForJest.
+
+    *) Bugfix: now the IMAP/POP3 proxy escapes the "\r" and "\n" symbols in
+       login and password to pass authorization server.
+       Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.3.37                                        07 Apr 2006
+
+    *) Feature: the "limit_except" directive.
+
+    *) Feature: the "if" directive supports the "!~", "!~*", "-f", and "!-f"
+       operators.
+
+    *) Feature: the ngx_http_perl_module supports the $r->request_body
+       method.
+
+    *) Bugfix: in the ngx_http_addition_filter_module.
+
+
+Changes with nginx 0.3.36                                        05 Apr 2006
+
+    *) Feature: the ngx_http_addition_filter_module.
+
+    *) Feature: the "proxy_pass" and "fastcgi_pass" directives may be used
+       inside the "if" block.
+
+    *) Feature: the "proxy_ignore_client_abort" and
+       "fastcgi_ignore_client_abort" directives.
+
+    *) Feature: the "$request_completion" variable.
+
+    *) Feature: the ngx_http_perl_module supports the $r->request_method and
+       $r->remote_addr.
+
+    *) Feature: the ngx_http_ssi_module supports the "elif" command.
+
+    *) Bugfix: the "\/" string in the expression of the "if" command of the
+       ngx_http_ssi_module was treated incorrectly.
+
+    *) Bugfix: in the regular expressions in the "if" command of the
+       ngx_http_ssi_module.
+
+    *) Bugfix: if the relative path was specified in the
+       "client_body_temp_path", "proxy_temp_path", "fastcgi_temp_path", and
+       "perl_modules" directives, then the directory was used relatively to
+       a current path but not to a server prefix.
+
+
+Changes with nginx 0.3.35                                        22 Mar 2006
+
+    *) Bugfix: the accept-filter and the TCP_DEFER_ACCEPT option were set
+       for first "listen" directive only; the bug had appeared in 0.3.31.
+
+    *) Bugfix: in the "proxy_pass" directive without the URI part in a
+       subrequest.
+
+
+Changes with nginx 0.3.34                                        21 Mar 2006
+
+    *) Feature: the "add_header" directive supports the variables.
+
+
+Changes with nginx 0.3.33                                        15 Mar 2006
+
+    *) Feature: the "http_503" parameter of the "proxy_next_upstream" or
+       "fastcgi_next_upstream" directives.
+
+    *) Bugfix: ngx_http_perl_module did not work with inlined in the
+       configuration code, if it was not started with the "sub" word.
+
+    *) Bugfix: in the "post_action" directive.
+
+
+Changes with nginx 0.3.32                                        11 Mar 2006
+
+    *) Bugfix: the debug logging on startup and reconfiguration time was
+       removed; the bug had appeared in 0.3.31.
+
+
+Changes with nginx 0.3.31                                        10 Mar 2006
+
+    *) Change: now nginx passes the malformed proxied backend responses.
+
+    *) Feature: the "listen" directives support the address in the "*:port"
+       form.
+
+    *) Feature: the EVFILER_TIMER support in MacOSX 10.4.
+
+    *) Workaround: for MacOSX 64-bit kernel kqueue millisecond timeout bug.
+       Thanks to Andrei Nigmatulin.
+
+    *) Bugfix: if there were several "listen" directives listening one
+       various addresses inside one server, then server names like
+       "*.domain.tld" worked for first address only; the bug had appeared in
+       0.3.18.
+
+    *) Bugfix: if the HTTPS protocol was used in the "proxy_pass" directive
+       and the request body was in temporary file then the request was not
+       transferred.
+
+    *) Bugfix: perl 5.8.8 compatibility.
+
+
+Changes with nginx 0.3.30                                        22 Feb 2006
+
+    *) Change: the ECONNABORTED error log level was changed to "error" from
+       "crit".
+
+    *) Bugfix: the ngx_http_perl_module could not be build without the
+       ngx_http_ssi_filter_module.
+
+    *) Bugfix: nginx could not be built on i386 platform, if the PIC was
+       used; the bug had appeared in 0.3.27.
+
+
+Changes with nginx 0.3.29                                        20 Feb 2006
+
+    *) Feature: now nginx uses less memory, if PHP in FastCGI mode sends
+       many warnings before the response.
+
+    *) Bugfix: the "Transfer-Encoding: chunked" header line was issued in
+       the 204 responses for the HTTP/1.1 requests.
+
+    *) Bugfix: nginx returned the 502 response, if the complete response
+       header lines were transferred in a separate FastCGI records.
+
+    *) Bugfix: if the proxied URI was specified in the "post_action"
+       directive, then it ran only after a successful completion of a
+       request.
+
+
+Changes with nginx 0.3.28                                        16 Feb 2006
+
+    *) Feature: the "restrict_host_names" directive was canceled.
+
+    *) Feature: the --with-cpu-opt=ppc64 configuration parameter.
+
+    *) Bugfix: on some condition the proxied connection with a client was
+       terminated prematurely.
+       Thanks to Vladimir Shutoff.
+
+    *) Bugfix: the "X-Accel-Limit-Rate" header line was not taken into
+       account if the request was redirected using the "X-Accel-Redirect"
+       header line.
+
+    *) Bugfix: the "post_action" directive ran only after a successful
+       completion of a request.
+
+    *) Bugfix: the proxied response body generated by the "post_action"
+       directive was transferred to a client.
+
+
+Changes with nginx 0.3.27                                        08 Feb 2006
+
+    *) Change: the "variables_hash_max_size" and
+       "variables_hash_bucket_size" directives.
+
+    *) Feature: the $body_bytes_sent variable can be used not only in the
+       "log_format" directive.
+
+    *) Feature: the $ssl_protocol and $ssl_cipher variables.
+
+    *) Feature: the cache line size detection for widespread CPUs at start
+       time.
+
+    *) Feature: now the "accept_mutex" directive is supported using fcntl(2)
+       on platforms different from i386, amd64, sparc64, and ppc.
+
+    *) Feature: the "lock_file" directive and the --with-lock-path=PATH
+       autoconfiguration directive.
+
+    *) Bugfix: if the HTTPS protocol was used in the "proxy_pass" directive
+       then the requests with the body was not transferred.
+
+
+Changes with nginx 0.3.26                                        03 Feb 2006
+
+    *) Change: the "optimize_host_names" directive was renamed to the
+       "optimize_server_names".
+
+    *) Bugfix: if in the "proxy_pass" directive was no the URI part, then
+       the main request URI was transferred to a backend while proxying the
+       SSI subrequest.
+
+
+Changes with nginx 0.3.25                                        01 Feb 2006
+
+    *) Bugfix: the segmentation fault was occurred on start or while
+       reconfiguration if there was invalid configuration; the bug had
+       appeared in 0.3.24.
+
+
+Changes with nginx 0.3.24                                        01 Feb 2006
+
+    *) Workaround: for bug in FreeBSD kqueue.
+
+    *) Bugfix: now a response generated by the "post_action" directive is
+       not transferred to a client.
+
+    *) Bugfix: the memory leaks were occurring if many log files were used.
+
+    *) Bugfix: the first "proxy_redirect" directive was working inside one
+       location.
+
+    *) Bugfix: on 64-bit platforms segmentation fault may occurred on start
+       if the many names were used in the "server_name" directives; the bug
+       had appeared in 0.3.18.
+
+
+Changes with nginx 0.3.23                                        24 Jan 2006
+
+    *) Feature: the "optimize_host_names" directive.
+
+    *) Bugfix: in using of the variables in the "path" and "alias"
+       directives.
+
+    *) Bugfix: the ngx_http_perl_module was incorrectly built on Linux and
+       Solaris.
+
+
+Changes with nginx 0.3.22                                        17 Jan 2006
+
+    *) Feature: the ngx_http_perl_module supports the $r->args and
+       $r->unescape methods.
+
+    *) Feature: the method $r->query_string of ngx_http_perl_module was
+       canceled.
+
+    *) Bugfix: segmentation fault was occurred if the "none" or "blocked"
+       values was specified in the "valid_referers" directive; the bug had
+       appeared in 0.3.18.
+
+
+Changes with nginx 0.3.21                                        16 Jan 2006
+
+    *) Feature: the ngx_http_perl_module.
+
+    *) Change: the "valid_referers" directive allows the referrers without
+       URI part.
+
+
+Changes with nginx 0.3.20                                        11 Jan 2006
+
+    *) Bugfix: in SSI handling.
+
+    *) Bugfix: the ngx_http_memcached_module did not support the keys in the
+       "/usr?args" form.
+
+
+Changes with nginx 0.3.19                                        28 Dec 2005
+
+    *) Feature: the "path" and "alias" directives support the variables.
+
+    *) Change: now the "valid_referers" directive again checks the URI part.
+
+    *) Bugfix: in SSI handling.
+
+
+Changes with nginx 0.3.18                                        26 Dec 2005
+
+    *) Feature: the "server_names" directive supports the ".domain.tld"
+       names.
+
+    *) Feature: the "server_names" directive uses the hash for the
+       "*.domain.tld" names and more effective hash for usual names.
+
+    *) Change: the "server_names_hash_max_size" and
+       "server_names_hash_bucket_size" directives.
+
+    *) Change: the "server_names_hash" and "server_names_hash_threshold"
+       directives were canceled.
+
+    *) Feature: the "valid_referers" directive uses the hash site names.
+
+    *) Change: now the "valid_referers" directive checks the site names only
+       without the URI part.
+
+    *) Bugfix: some ".domain.tld" names incorrectly processed by the
+       ngx_http_map_module.
+
+    *) Bugfix: segmentation fault was occurred if configuration file did not
+       exist; the bug had appeared in 0.3.12.
+
+    *) Bugfix: on 64-bit platforms segmentation fault may occurred on start;
+       the bug had appeared in 0.3.16.
+
+
+Changes with nginx 0.3.17                                        18 Dec 2005
+
+    *) Change: now on Linux configure checks the presence of epoll and
+       sendfile64() in kernel.
+
+    *) Feature: the "map" directive supports domain names in the
+       ".domain.tld" form.
+
+    *) Bugfix: the timeouts were not used in SSL handshake; the bug had
+       appeared in 0.2.4.
+
+    *) Bugfix: in the HTTPS protocol in the "proxy_pass" directive.
+
+    *) Bugfix: when the HTTPS protocol was used in the "proxy_pass"
+       directive the port 80 was used by default.
+
+
+Changes with nginx 0.3.16                                        16 Dec 2005
+
+    *) Feature: the ngx_http_map_module.
+
+    *) Feature: the "types_hash_max_size" and "types_hash_bucket_size"
+       directives.
+
+    *) Feature: the "ssi_value_length" directive.
+
+    *) Feature: the "worker_rlimit_core" directive.
+
+    *) Workaround: the connection number in logs was always 1 if nginx was
+       built by the icc 8.1 or 9.0 compilers with optimization for
+       Pentium 4.
+
+    *) Bugfix: the "config timefmt" SSI command set incorrect time format.
+
+    *) Bugfix: nginx did not close connection to IMAP/POP3 backend for the
+       SSL connections; the bug had appeared in 0.3.13.
+       Thanks to Rob Mueller.
+
+    *) Bugfix: segmentation fault may occurred in at SSL shutdown; the bug
+       had appeared in 0.3.13.
+
+
+Changes with nginx 0.3.15                                        07 Dec 2005
+
+    *) Feature: the new 444 code of the "return" directive to close
+       connection.
+
+    *) Feature: the "so_keepalive" directive in IMAP/POP3 proxy.
+
+    *) Bugfix: if there are unclosed connection nginx now calls abort() only
+       on graceful quit and active "debug_points" directive.
+
+
+Changes with nginx 0.3.14                                        05 Dec 2005
+
+    *) Bugfix: in the 304 response the body was transferred; the bug had
+       appeared in 0.3.13.
+
+
+Changes with nginx 0.3.13                                        05 Dec 2005
+
+    *) Feature: the IMAP/POP3 proxy supports STARTTLS and STLS.
+
+    *) Bugfix: the IMAP/POP3 proxy did not work with the select, poll, and
+       /dev/poll methods.
+
+    *) Bugfix: in SSI handling.
+
+    *) Bugfix: now Solaris sendfilev() is not used to transfer the client
+       request body to FastCGI-server via the unix domain socket.
+
+    *) Bugfix: the "auth_basic" directive did not disable the authorization;
+       the bug had appeared in 0.3.11.
+
+
+Changes with nginx 0.3.12                                        26 Nov 2005
+
+    *) Security: if nginx was built with the ngx_http_realip_module and the
+       "satisfy_any on" directive was used, then access and authorization
+       directives did not work. The ngx_http_realip_module was not built and
+       is not built by default.
+
+    *) Change: the "$time_gmt" variable name was changed to "$time_local".
+
+    *) Change: the "proxy_header_buffer_size" and
+       "fastcgi_header_buffer_size" directives was renamed to the
+       "proxy_buffer_size" and "fastcgi_buffer_size" directives.
+
+    *) Feature: the ngx_http_memcached_module.
+
+    *) Feature: the "proxy_buffering" directive.
+
+    *) Bugfix: the changes in accept mutex handling when the "rtsig" method
+       was used; the bug had appeared in 0.3.0.
+
+    *) Bugfix: if the client sent the "Transfer-Encoding: chunked" header
+       line, then nginx returns the 411 error.
+
+    *) Bugfix: if the "auth_basic" directive was inherited from the http
+       level, then the realm in the "WWW-Authenticate" header line was
+       without the "Basic realm" text.
+
+    *) Bugfix: if the "combined" format was explicitly specified in the
+       "access_log" directive, then the empty lines was written to the log;
+       the bug had appeared in 0.3.8.
+
+    *) Bugfix: nginx did not run on the sparc platform under any OS except
+       Solaris.
+
+    *) Bugfix: now it is not necessary to place space between the quoted
+       string and closing bracket in the "if" directive.
+
+
+Changes with nginx 0.3.11                                        15 Nov 2005
+
+    *) Bugfix: nginx did not pass the client request headers and body while
+       proxying; the bug had appeared in 0.3.10.
+
+
+Changes with nginx 0.3.10                                        15 Nov 2005
+
+    *) Change: the "valid_referers" directive and the "$invalid_referer"
+       variable were moved to the new ngx_http_referer_module from the
+       ngx_http_rewrite_module.
+
+    *) Change: the "$apache_bytes_sent" variable name was changed to
+       "$body_bytes_sent".
+
+    *) Feature: the "$sent_http_..." variables.
+
+    *) Feature: the "if" directive supports the "=" and "!=" operations.
+
+    *) Feature: the "proxy_pass" directive supports the HTTPS protocol.
+
+    *) Feature: the "proxy_set_body" directive.
+
+    *) Feature: the "post_action" directive.
+
+    *) Feature: the ngx_http_empty_gif_module.
+
+    *) Feature: the "worker_cpu_affinity" directive for Linux.
+
+    *) Bugfix: the "rewrite" directive did not unescape URI part in
+       redirect, now it is unescaped except the %00-%25 and %7F-%FF
+       characters.
+
+    *) Bugfix: nginx could not be built by the icc 9.0 compiler.
+
+    *) Bugfix: if the SSI was enabled for zero size static file, then the
+       chunked response was encoded incorrectly.
+
+
+Changes with nginx 0.3.9                                         10 Nov 2005
+
+    *) Bugfix: nginx considered URI as unsafe if two any symbols was between
+       two slashes; the bug had appeared in 0.3.8.
+
+
+Changes with nginx 0.3.8                                         09 Nov 2005
+
+    *) Security: nginx now checks URI got from a backend in
+       "X-Accel-Redirect" header line or in SSI file for the "/../" paths
+       and zeroes.
+
+    *) Change: nginx now does not treat the empty user name in the
+       "Authorization" header line as valid one.
+
+    *) Feature: the "ssl_session_timeout" directives of the
+       ngx_http_ssl_module and ngx_imap_ssl_module.
+
+    *) Feature: the "auth_http_header" directive of the
+       ngx_imap_auth_http_module.
+
+    *) Feature: the "add_header" directive.
+
+    *) Feature: the ngx_http_realip_module.
+
+    *) Feature: the new variables to use in the "log_format" directive:
+       $bytes_sent, $apache_bytes_sent, $status, $time_gmt, $uri,
+       $request_time, $request_length, $upstream_status,
+       $upstream_response_time, $gzip_ratio, $uid_got, $uid_set,
+       $connection, $pipe, and $msec. The parameters in the "%name" form
+       will be canceled soon.
+
+    *) Change: now the false variable values in the "if" directive are the
+       empty string "" and string starting with "0".
+
+    *) Bugfix: while using proxied or FastCGI-server nginx may leave
+       connections and temporary files with client requests in open state.
+
+    *) Bugfix: the worker processes did not flush the buffered logs on
+       graceful exit.
+
+    *) Bugfix: if the request URI was changes by the "rewrite" directive and
+       the request was proxied in location given by regular expression, then
+       the incorrect request was transferred to backend; the bug had
+       appeared in 0.2.6.
+
+    *) Bugfix: the "expires" directive did not remove the previous "Expires"
+       header.
+
+    *) Bugfix: nginx may stop to accept requests if the "rtsig" method and
+       several worker processes were used.
+
+    *) Bugfix: the "\"" and "\'" escape symbols were incorrectly handled in
+       SSI commands.
+
+    *) Bugfix: if the response was ended just after the SSI command and
+       gzipping was used, then the response did not transferred complete or
+       did not transferred at all.
+
+
+Changes with nginx 0.3.7                                         27 Oct 2005
+
+    *) Feature: the "access_log" supports the "buffer=" parameter.
+
+    *) Bugfix: nginx could not be built on platforms different from i386,
+       amd64, sparc, and ppc; the bug had appeared in 0.3.2.
+
+
+Changes with nginx 0.3.6                                         24 Oct 2005
+
+    *) Change: now the IMAP/POP3 proxy do not send the empty login to
+       authorization server.
+
+    *) Feature: the "log_format" supports the variables in the $name form.
+
+    *) Bugfix: if at least in one server was no the "listen" directive, then
+       nginx did not listen on the 80 port; the bug had appeared in 0.3.3.
+
+    *) Bugfix: if the URI part is omitted in "proxy_pass" directive, the 80
+       port was always used.
+
+
+Changes with nginx 0.3.5                                         21 Oct 2005
+
+    *) Bugfix: the segmentation fault may occurred if the IMAP/POP3 login
+       was changed by authorization server; the bug had appeared in 0.2.2.
+
+    *) Bugfix: the accept mutex did not work and all connections were
+       handled by one process; the bug had appeared in 0.3.3.
+
+    *) Bugfix: the timeout did not work if the "rtsig" method and the
+       "timer_resolution" directive were used.
+
+
+Changes with nginx 0.3.4                                         19 Oct 2005
+
+    *) Bugfix: nginx could not be built on Linux 2.4+ and MacOS X; the bug
+       had appeared in 0.3.3.
+
+
+Changes with nginx 0.3.3                                         19 Oct 2005
+
+    *) Change: the "bl" and "af" parameters of the "listen" directive was
+       renamed to the "backlog" and "accept_filter".
+
+    *) Feature: the "rcvbuf" and "sndbuf" parameters of the "listen"
+       directive.
+
+    *) Change: the "$msec" log parameter does not require now the additional
+       the gettimeofday() system call.
+
+    *) Feature: the -t switch now tests the "listen" directives.
+
+    *) Bugfix: if the invalid address was specified in the "listen"
+       directive, then after the -HUP signal nginx left an open socket in
+       the CLOSED state.
+
+    *) Bugfix: the mime type may be incorrectly set to default value for
+       index file with variable in the name; the bug had appeared in 0.3.0.
+
+    *) Feature: the "timer_resolution" directive.
+
+    *) Feature: the millisecond "$upstream_response_time" log parameter.
+
+    *) Bugfix: a temporary file with client request body now is removed just
+       after the response header was transferred to a client.
+
+    *) Bugfix: OpenSSL 0.9.6 compatibility.
+
+    *) Bugfix: the SSL certificate and key file paths could not be relative.
+
+    *) Bugfix: the "ssl_prefer_server_ciphers" directive did not work in the
+       ngx_imap_ssl_module.
+
+    *) Bugfix: the "ssl_protocols" directive allowed to specify the single
+       protocol only.
+
+
+Changes with nginx 0.3.2                                         12 Oct 2005
+
+    *) Feature: the Sun Studio 10 C compiler support.
+
+    *) Feature: the "proxy_upstream_max_fails",
+       "proxy_upstream_fail_timeout", "fastcgi_upstream_max_fails", and
+       "fastcgi_upstream_fail_timeout" directives.
+
+
+Changes with nginx 0.3.1                                         10 Oct 2005
+
+    *) Bugfix: the segmentation fault occurred when the signal queue
+       overflowed if the "rtsig" method was used; the bug had appeared in
+       0.2.0.
+
+    *) Change: correct handling of the "\\", "\"", "\'", and "\$" pairs in
+       SSI.
+
+
+Changes with nginx 0.3.0                                         07 Oct 2005
+
+    *) Change: the 10-days live time limit of worker process was eliminated.
+       The limit was introduced because of millisecond timers overflow.
+
+
+Changes with nginx 0.2.6                                         05 Oct 2005
+
+    *) Change: while using load-balancing the time before the failed backend
+       retry was decreased from 60 to 10 seconds.
+
+    *) Change: the "proxy_pass_unparsed_uri" was canceled, the original URI
+       now passed, if the URI part is omitted in "proxy_pass" directive.
+
+    *) Feature: the "error_page" directive supports redirects and allows
+       more flexible to change an error code.
+
+    *) Change: the charset in the "Content-Type" header line now is ignored
+       in proxied subrequests.
+
+    *) Bugfix: if the URI was changed in the "if" block and request did not
+       found new configuration, then the ngx_http_rewrite_module rules ran
+       again.
+
+    *) Bugfix: if the "set" directive set the ngx_http_geo_module variable
+       in some configuration part, the this variable was not available in
+       other configuration parts and the "using uninitialized variable"
+       error was occurred; the bug had appeared in 0.2.2.
+
+
+Changes with nginx 0.2.5                                         04 Oct 2005
+
+    *) Change: the duplicate value of the ngx_http_geo_module variable now
+       causes the warning and changes old value.
+
+    *) Feature: the ngx_http_ssi_module supports the "set" command.
+
+    *) Feature: the ngx_http_ssi_module supports the "file" parameter in the
+       "include" command.
+
+    *) Feature: the ngx_http_ssi_module supports the variable value
+       substitutions in expressions of the "if" command.
+
+
+Changes with nginx 0.2.4                                         03 Oct 2005
+
+    *) Feature: the ngx_http_ssi_module supports "$var=text", "$var!=text",
+       "$var=/text/", and "$var!=/text/" expressions in the "if" command.
+
+    *) Bugfix: in proxying location without trailing slash; the bug had
+       appeared in 0.1.44.
+
+    *) Bugfix: the segmentation fault may occurred if the "rtsig" method was
+       used; the bug had appeared in 0.2.0.
+
+
+Changes with nginx 0.2.3                                         30 Sep 2005
+
+    *) Bugfix: nginx could not be built without the --with-debug option; the
+       bug had appeared in 0.2.2.
+
+
+Changes with nginx 0.2.2                                         30 Sep 2005
+
+    *) Feature: the "config errmsg" command of the ngx_http_ssi_module.
+
+    *) Change: the ngx_http_geo_module variables can be overridden by the
+       "set" directive.
+
+    *) Feature: the "ssl_protocols" and "ssl_prefer_server_ciphers"
+       directives of the ngx_http_ssl_module and ngx_imap_ssl_module.
+
+    *) Bugfix: the ngx_http_autoindex_module did not show correctly the long
+       file names;
+
+    *) Bugfix: the ngx_http_autoindex_module now do not show the files
+       starting by dot.
+
+    *) Bugfix: if the SSL handshake failed then another connection may be
+       closed too.
+       Thanks to Rob Mueller.
+
+    *) Bugfix: the export versions of MSIE 5.x could not connect via HTTPS.
+
+
+Changes with nginx 0.2.1                                         23 Sep 2005
+
+    *) Bugfix: if all backend using in load-balancing failed after one
+       error, then nginx may got caught in an endless loop; the bug had
+       appeared in 0.2.0.
+
+
+Changes with nginx 0.2.0                                         23 Sep 2005
+
+    *) The pid-file names used during online upgrade was changed and now is
+       not required a manual rename operation. The old master process adds
+       the ".oldbin" suffix to its pid-file and executes a new binary file.
+       The new master process creates usual pid-file without the ".newbin"
+       suffix. If the master process exits, then old master process renames
+       back its pid-file with the ".oldbin" suffix to the pid-file without
+       suffix.
+
+    *) Change: the "worker_connections" directive, new name of the
+       "connections" directive; now the directive specifies maximum number
+       of connections, but not maximum socket descriptor number.
+
+    *) Feature: SSL supports the session cache inside one worker process.
+
+    *) Feature: the "satisfy_any" directive.
+
+    *) Change: the ngx_http_access_module and ngx_http_auth_basic_module do
+       not run for subrequests.
+
+    *) Feature: the "worker_rlimit_nofile" and "worker_rlimit_sigpending"
+       directives.
+
+    *) Bugfix: if all backend using in load-balancing failed after one
+       error, then nginx did not try do connect to them during 60 seconds.
+
+    *) Bugfix: in IMAP/POP3 command argument parsing.
+       Thanks to Rob Mueller.
+
+    *) Bugfix: errors while using SSL in IMAP/POP3 proxy.
+
+    *) Bugfix: errors while using SSI and gzipping.
+
+    *) Bugfix: the "Expires" and "Cache-Control" header lines were omitted
+       from the 304 responses.
+       Thanks to Alexandr Kukushkin.
+
+
+Changes with nginx 0.1.45                                        08 Sep 2005
+
+    *) Change: the "ssl_engine" directive was canceled in the
+       ngx_http_ssl_module and now is introduced at global level.
+
+    *) Bugfix: the responses with SSI subrequests did not transferred via
+       SSL connection.
+
+    *) Various bug fixes in the IMAP/POP3 proxy.
+
+
+Changes with nginx 0.1.44                                        06 Sep 2005
+
+    *) Feature: the IMAP/POP3 proxy supports SSL.
+
+    *) Feature: the "proxy_timeout" directive of the ngx_imap_proxy_module.
+
+    *) Feature: the "userid_mark" directive.
+
+    *) Feature: the $remote_user variable value is determined independently
+       of authorization use.
+
+
+Changes with nginx 0.1.43                                        30 Aug 2005
+
+    *) Feature: the listen(2) backlog in the "listen" directive can be
+       changed using the -HUP signal.
+
+    *) Feature: the geo2nginx.pl script was added to contrib.
+
+    *) Change: the FastCGI parameters with the empty values now are passed
+       to a server.
+
+    *) Bugfix: the segmentation fault occurred or the worker process may got
+       caught in an endless loop if the proxied or FastCGI server sent the
+       "Cache-Control" header line and the "expires" directive was used; in
+       the proxied mode the bug had appeared in 0.1.29.
+
+
+Changes with nginx 0.1.42                                        23 Aug 2005
+
+    *) Bugfix: if the request URI had a zero length after the processing in
+       the ngx_http_proxy_module, then the segmentation fault or bus error
+       occurred in the ngx_http_proxy_module.
+
+    *) Bugfix: the "limit_rate" directive did not work inside the "if"
+       block; the bug had appeared in 0.1.38.
+
+
+Changes with nginx 0.1.41                                        25 Jul 2005
+
+    *) Bugfix: if the variable was used in the configuration file, then it
+       can not be used in SSI.
+
+
+Changes with nginx 0.1.40                                        22 Jul 2005
+
+    *) Bugfix: if a client sent too long header line, then the request
+       information did not logged in the error log.
+
+    *) Bugfix: the "Set-Cookie" header line was not transferred when the
+       "X-Accel-Redirect" was used; the bug had appeared in 0.1.39.
+
+    *) Bugfix: the "Content-Disposition" header line was not transferred
+       when the "X-Accel-Redirect" was used.
+
+    *) Bugfix: the master process did not close the listen socket on the
+       SIGQUIT signal.
+
+    *) Bugfix: after on-line upgrade on Linux and Solaris the process name
+       became shorter in the "ps" command.
+
+
+Changes with nginx 0.1.39                                        14 Jul 2005
+
+    *) The changes in the ngx_http_charset_module: the "default_charset"
+       directive was canceled; the "charset" directive sets the response
+       charset; the "source_charset" directive sets the source charset only.
+
+    *) Bugfix: the backend "WWW-Authenticate" header line did not
+       transferred while the 401 response code redirecting.
+
+    *) Bugfix: the ngx_http_proxy_module and ngx_http_fastcgi_module may
+       close a connection before anything was transferred to a client; the
+       bug had appeared in 0.1.38.
+
+    *) Workaround: the Linux glibc crypt_r() initialization bug.
+
+    *) Bugfix: the ngx_http_ssi_module did not support the relative URI in
+       the "include virtual" command.
+
+    *) Bugfix: if the backend response had the "Location" header line and
+       nginx should not rewrite this line, then the 500 code response body
+       was transferred; the bug had appeared in 0.1.29.
+
+    *) Bugfix: some directives of the ngx_http_proxy_module and
+       ngx_http_fastcgi_module were not inherited from the server to the
+       location level; the bug had appeared in 0.1.29.
+
+    *) Bugfix: the ngx_http_ssl_module did not support the certificate
+       chain.
+
+    *) Bugfix: the ngx_http_autoindex_module did not show correctly the long
+       file names; the bug had appeared in 0.1.38.
+
+    *) Bugfixes in IMAP/POP3 proxy in interaction with a backend at the
+       login state.
+
+
+Changes with nginx 0.1.38                                        08 Jul 2005
+
+    *) Feature: the "limit_rate" directive is supported in proxy and FastCGI
+       mode.
+
+    *) Feature: the "X-Accel-Limit-Rate" response header line is supported
+       in proxy and FastCGI mode.
+
+    *) Feature: the "break" directive.
+
+    *) Feature: the "log_not_found" directive.
+
+    *) Bugfix: the response status code was not changed when request was
+       redirected by the ""X-Accel-Redirect" header line.
+
+    *) Bugfix: the variables set by the "set" directive could not be used in
+       SSI.
+
+    *) Bugfix: the segmentation fault may occurred if the SSI page has more
+       than one remote subrequest.
+
+    *) Bugfix: nginx treated the backend response as invalid if the status
+       line in the header was transferred in two packets; the bug had
+       appeared in 0.1.29.
+
+    *) Feature: the "ssi_types" directive.
+
+    *) Feature: the "autoindex_exact_size" directive.
+
+    *) Bugfix: the ngx_http_autoindex_module did not support the long file
+       names in UTF-8.
+
+    *) Feature: the IMAP/POP3 proxy.
+
+
+Changes with nginx 0.1.37                                        23 Jun 2005
+
+    *) Change: now the "\n" is added to the end of the "nginx.pid" file.
+
+    *) Bugfix: the responses may be transferred not completely, if many
+       parts or the big parts were included by SSI.
+
+    *) Bugfix: if all backends had returned the 404 response and the
+       "http_404" parameter of the "proxy_next_upstream" or
+       "fastcgi_next_upstream" directives was used, then nginx started to
+       request all backends again.
+
+
+Changes with nginx 0.1.36                                        15 Jun 2005
+
+    *) Change: if the request header has duplicate the "Host", "Connection",
+       "Content-Length", or "Authorization" lines, then nginx now returns
+       the 400 error.
+
+    *) Change: the "post_accept_timeout" directive was canceled.
+
+    *) Feature: the "default", "af=", "bl=", "deferred", and "bind"
+       parameters of the "listen" directive.
+
+    *) Feature: the FreeBSD accept filters support.
+
+    *) Feature: the Linux TCP_DEFER_ACCEPT support.
+
+    *) Bugfix: the ngx_http_autoindex_module did not support the file names
+       in UTF-8.
+
+    *) Bugfix: the new log file can be rotated by the -USR1 signal only if
+       the reconfiguration by the -HUP signal was made twice.
+
+
+Changes with nginx 0.1.35                                        07 Jun 2005
+
+    *) Feature: the "working_directory" directive.
+
+    *) Feature: the "port_in_redirect" directive.
+
+    *) Bugfix: the segmentation fault was occurred if the backend response
+       header was in several packets; the bug had appeared in 0.1.29.
+
+    *) Bugfix: if more than 10 servers were configured or some server did
+       not use the "listen" directive, then the segmentation fault was
+       occurred on the start.
+
+    *) Bugfix: the segmentation fault might occur if the response was bigger
+       than the temporary file.
+
+    *) Bugfix: nginx returned the 400 response on requests like
+       "GET http://www.domain.com/uri HTTP/1.0"; the bug had appeared in
+       0.1.28.
+
+
+Changes with nginx 0.1.34                                        26 May 2005
+
+    *) Bugfix: the worker process may got caught in an endless loop if the
+       big response part were include by SSI.
+
+    *) Bugfix: the variables set by the "set" directive were not available
+       in SSI.
+
+    *) Feature: the "autoindex_localtime" directive.
+
+    *) Bugfix: the empty value of the "proxy_set_header" directive forbids
+       the client request header line passing.
+
+
+Changes with nginx 0.1.33                                        23 May 2005
+
+    *) Bugfix: nginx could not be built with the --without-pcre parameter;
+       the bug had appeared in 0.1.29.
+
+    *) Bugfix: 3, 4, 7, and 8 the "proxy_set_header" directives in one level
+       cause the bus fault on start up.
+
+    *) Bugfix: the HTTP protocol was specified in the HTTPS redirects.
+
+    *) Bugfix: if the "rewrite" directive used the captures inside the "if"
+       directive, then the 500 error code was returned.
+
+
+Changes with nginx 0.1.32                                        19 May 2005
+
+    *) Bugfix: the arguments were omitted in the redirects, issued by the
+       "rewrite" directive; the bug had appeared in 0.1.29.
+
+    *) Feature: the "if" directive supports the captures in regular
+       expressions.
+
+    *) Feature: the "set" directive supports the variables and the captures
+       of regular expressions.
+
+    *) Feature: the "X-Accel-Redirect" response header line is supported in
+       proxy and FastCGI mode.
+
+
+Changes with nginx 0.1.31                                        16 May 2005
+
+    *) Bugfix: the response encrypted by SSL may not transferred complete.
+
+    *) Bugfix: errors while processing FastCGI response by SSI.
+
+    *) Bugfix: errors while using SSI and gzipping.
+
+    *) Bugfix: the redirect with the 301 code was transferred without
+       response body; the bug had appeared in 0.1.30.
+
+
+Changes with nginx 0.1.30                                        14 May 2005
+
+    *) Bugfix: the worker process may got caught in an endless loop if the
+       SSI was used.
+
+    *) Bugfix: the response encrypted by SSL may not transferred complete.
+
+    *) Bugfix: if the length of the response part received at once from
+       proxied or FastCGI server was equal to 500, then nginx returns the
+       500 response code; in proxy mode the bug had appeared in 0.1.29 only.
+
+    *) Bugfix: nginx did not consider the directives with 8 or 9 parameters
+       as invalid.
+
+    *) Feature: the "return" directive can return the 204 response code.
+
+    *) Feature: the "ignore_invalid_headers" directive.
+
+
+Changes with nginx 0.1.29                                        12 May 2005
+
+    *) Feature: the ngx_http_ssi_module supports "include virtual" command.
+
+    *) Feature: the ngx_http_ssi_module supports the condition command like
+       'if expr="$NAME"' and "else" and "endif" commands. Only one nested
+       level is supported.
+
+    *) Feature: the ngx_http_ssi_module supports the DATE_LOCAL and DATE_GMT
+       variables and "config timefmt" command.
+
+    *) Feature: the "ssi_ignore_recycled_buffers" directive.
+
+    *) Bugfix: the "echo" command did not show the default value for the
+       empty QUERY_STRING variable.
+
+    *) Change: the ngx_http_proxy_module was rewritten.
+
+    *) Feature: the "proxy_redirect", "proxy_pass_request_headers",
+       "proxy_pass_request_body", and "proxy_method" directives.
+
+    *) Feature: the "proxy_set_header" directive. The "proxy_x_var" was
+       canceled and must be replaced with the proxy_set_header directive.
+
+    *) Change: the "proxy_preserve_host" is canceled and must be replaced
+       with the "proxy_set_header Host $host" and the "proxy_redirect off"
+       directives, the "proxy_set_header Host $host:$proxy_port" directive
+       and the appropriate proxy_redirect directives.
+
+    *) Change: the "proxy_set_x_real_ip" is canceled and must be replaced
+       with the "proxy_set_header X-Real-IP $remote_addr" directive.
+
+    *) Change: the "proxy_add_x_forwarded_for" is canceled and must be
+       replaced with
+       the "proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for"
+       directive.
+
+    *) Change: the "proxy_set_x_url" is canceled and must be replaced with
+       the "proxy_set_header X-URL http://$host:$server_port$request_uri"
+       directive.
+
+    *) Feature: the "fastcgi_param" directive.
+
+    *) Change: the "fastcgi_root", "fastcgi_set_var" and "fastcgi_params"
+       directive are canceled and must be replaced with the fastcgi_param
+       directives.
+
+    *) Feature: the "index" directive can use the variables.
+
+    *) Feature: the "index" directive can be used at http and server levels.
+
+    *) Change: the last index only in the "index" directive can be absolute.
+
+    *) Feature: the "rewrite" directive can use the variables.
+
+    *) Feature: the "internal" directive.
+
+    *) Feature: the CONTENT_LENGTH, CONTENT_TYPE, REMOTE_PORT, SERVER_ADDR,
+       SERVER_PORT, SERVER_PROTOCOL, DOCUMENT_ROOT, SERVER_NAME,
+       REQUEST_METHOD, REQUEST_URI, and REMOTE_USER variables.
+
+    *) Change: nginx now passes the invalid lines in a client request
+       headers or a backend response header.
+
+    *) Bugfix: if the backend did not transfer response for a long time and
+       the "send_timeout" was less than "proxy_read_timeout", then nginx
+       returned the 408 response.
+
+    *) Bugfix: the segmentation fault was occurred if the backend sent an
+       invalid line in response header; the bug had appeared in 0.1.26.
+
+    *) Bugfix: the segmentation fault may occurred in FastCGI fault
+       tolerance configuration.
+
+    *) Bugfix: the "expires" directive did not remove the previous "Expires"
+       and "Cache-Control" headers.
+
+    *) Bugfix: nginx did not take into account trailing dot in "Host" header
+       line.
+
+    *) Bugfix: the ngx_http_auth_module did not work under Linux.
+
+    *) Bugfix: the rewrite directive worked incorrectly, if the arguments
+       were in a request.
+
+    *) Bugfix: nginx could not be built on MacOS X.
+
+
+Changes with nginx 0.1.28                                        08 Apr 2005
+
+    *) Bugfix: nginx hogs CPU while proxying the huge files.
+
+    *) Bugfix: nginx could not be built by gcc 4.0 on Linux.
+
+
+Changes with nginx 0.1.27                                        28 Mar 2005
+
+    *) Feature: the "blocked" parameter of the "valid_referers" directive.
+
+    *) Change: the errors while handling the request header now logged at
+       "info" level. The server name and the "Host" and "Referer" header
+       lines also logged.
+
+    *) Change: the "Host" header line is also logged in error log.
+
+    *) Feature: the proxy_pass_unparsed_uri directive. The special handling
+       of the "://" symbols in URI, appeared in 0.1.11 version, now is
+       canceled.
+
+    *) Bugfix: nginx could not be built on FreeBSD and Linux, if the
+       --without-ngx_http_auth_basic_module configuration parameter was
+       used.
+
+
+Changes with nginx 0.1.26                                        22 Mar 2005
+
+    *) Change: the invalid client header lines are now ignored and logged at
+       the info level.
+
+    *) Change: the server name is also logged in error log.
+
+    *) Feature: the ngx_http_auth_basic_module module and the auth_basic and
+       auth_basic_user_file directives.
+
+
+Changes with nginx 0.1.25                                        19 Mar 2005
+
+    *) Bugfix: nginx did run on Linux parisc.
+
+    *) Feature: nginx now does not start under FreeBSD if the sysctl
+       kern.ipc.somaxconn value is too big.
+
+    *) Bugfix: if a request was internally redirected by the
+       ngx_http_index_module module to the ngx_http_proxy_module or
+       ngx_http_fastcgi_module modules, then the index file was not closed
+       after request completion.
+
+    *) Feature: the "proxy_pass" can be used in location with regular
+       expression.
+
+    *) Feature: the ngx_http_rewrite_filter_module module supports the
+       condition like "if ($HTTP_USER_AGENT ~ MSIE)".
+
+    *) Bugfix: nginx started too slow if the large number of addresses and
+       text values were used in the "geo" directive.
+
+    *) Change: a variable name must be declared as "$name" in the "geo"
+       directive. The previous variant without "$" is still supported, but
+       will be removed soon.
+
+    *) Feature: the "%{VARIABLE}v" logging parameter.
+
+    *) Feature: the "set $name value" directive.
+
+    *) Bugfix: gcc 4.0 compatibility.
+
+    *) Feature: the --with-openssl-opt=OPTIONS autoconfiguration directive.
+
+
+Changes with nginx 0.1.24                                        04 Mar 2005
+
+    *) Feature: the ngx_http_ssi_filter_module supports the QUERY_STRING and
+       DOCUMENT_URI variables.
+
+    *) Bugfix: the ngx_http_autoindex_module may some times return the 404
+       response for existent directory, if this directory was used in
+       "alias" directive.
+
+    *) Bugfix: the ngx_http_ssi_filter_module ran incorrectly for large
+       responses.
+
+    *) Bugfix: the lack of the "Referer" header line was always accounted as
+       valid referrer.
+
+
+Changes with nginx 0.1.23                                        01 Mar 2005
+
+    *) Feature: the ngx_http_ssi_filter_module and the ssi,
+       ssi_silent_errors, and ssi_min_file_chunk directives. The 'echo
+       var="HTTP_..." default=""' and 'echo var="REMOTE_ADDR"' commands are
+       supported.
+
+    *) Feature: the %request_time log parameter.
+
+    *) Feature: if the request has no the "Host" header line, then the
+       "proxy_preserve_host" directive set this header line to the first
+       server name of the "server_name" directive.
+
+    *) Bugfix: nginx could not be built on platforms different from i386,
+       amd64, sparc, and ppc; the bug had appeared in 0.1.22.
+
+    *) Bugfix: the ngx_http_autoindex_module now shows the information not
+       about the symlink, but about file or directory it points to.
+
+    *) Bugfix: the %apache_length parameter logged the negative length of
+       the response header if the no response was transferred to a client.
+
+
+Changes with nginx 0.1.22                                        22 Feb 2005
+
+    *) Bugfix: the ngx_http_stub_status_module showed incorrect handled
+       connections statistics if the proxying or FastCGI server were used.
+
+    *) Bugfix: the installation paths were incorrectly quoted on Linux and
+       Solaris; the bug had appeared in 0.1.21.
+
+
+Changes with nginx 0.1.21                                        22 Feb 2005
+
+    *) Bugfix: the ngx_http_stub_status_module showed incorrect statistics
+       if "rtsig" method was used or if several worker process ran on SMP.
+
+    *) Bugfix: nginx could not be built by the icc compiler on Linux or if
+       the zlib-1.2.x library was building from sources.
+
+    *) Bugfix: nginx could not be built on NetBSD 2.0.
+
+
+Changes with nginx 0.1.20                                        17 Feb 2005
+
+    *) Feature: the new "script_filename" and "remote_port" parameters of
+       the fastcgi_params directive.
+
+    *) Bugfix: the FastCGI stderr stream was handled incorrectly.
+
+
+Changes with nginx 0.1.19                                        16 Feb 2005
+
+    *) Bugfix: now, if request contains the zero, then the 404 error is
+       returned for the local requests.
+
+    *) Bugfix: nginx could not be built on NetBSD 2.0.
+
+    *) Bugfix: the timeout may occur while reading of the client request
+       body via SSL connections.
+
+
+Changes with nginx 0.1.18                                        09 Feb 2005
+
+    *) Workaround: the default values of the devpoll_events and the
+       devpoll_changes directives changed from 512 to 32 to be compatible
+       with Solaris 10.
+
+    *) Bugfix: the proxy_set_x_var and fastcgi_set_var directives were not
+       inherited.
+
+    *) Bugfix: in a redirect rewrite directive arguments were concatenated
+       with URI by an "&" rather than a "?".
+
+    *) Bugfix: the lines without trailing ";" in the file being included by
+       the ngx_http_geo_module were silently ignored.
+
+    *) Feature: the ngx_http_stub_status_module.
+
+    *) Bugfix: the unknown log format in the access_log directive caused the
+       segmentation fault.
+
+    *) Feature: the new "document_root" parameter of the fastcgi_params
+       directive.
+
+    *) Feature: the fastcgi_redirect_errors directive.
+
+    *) Feature: the new "break" modifier of the "rewrite" directive allows
+       to stop the rewrite/location cycle and sets the current configuration
+       to the request.
+
+
+Changes with nginx 0.1.17                                        03 Feb 2005
+
+    *) Change: the ngx_http_rewrite_module was rewritten from the scratch.
+       Now it is possible to redirect, to return the error codes, to check
+       the variables and referrers. The directives can be used inside
+       locations. The redirect directive was canceled.
+
+    *) Feature: the ngx_http_geo_module.
+
+    *) Feature: the proxy_set_x_var and fastcgi_set_var directives.
+
+    *) Bugfix: the location configuration with "=" modifier may be used in
+       another location.
+
+    *) Bugfix: the correct content type was set only for requests that use
+       small caps letters in extension.
+
+    *) Bugfix: if the proxy_pass or fastcgi_pass directives were set in the
+       location, and access was denied, and the error was redirected to a
+       static page, then the segmentation fault occurred.
+
+    *) Bugfix: if in a proxied "Location" header was a relative URL, then a
+       host name and a slash were added to them; the bug had appeared in
+       0.1.14.
+
+    *) Bugfix: the system error message was not logged on Linux.
+
+
+Changes with nginx 0.1.16                                        25 Jan 2005
+
+    *) Bugfix: if the response were transferred by chunks, then on the HEAD
+       request the final chunk was issued.
+
+    *) Bugfix: the "Connection: keep-alive" header were issued, even if the
+       keepalive_timeout directive forbade the keep-alive use.
+
+    *) Bugfix: the errors in the ngx_http_fastcgi_module caused the
+       segmentation faults.
+
+    *) Bugfix: the compressed response encrypted by SSL may not transferred
+       complete.
+
+    *) Bugfix: the TCP-specific TCP_NODELAY, TCP_NOPUSH, and TCP_CORK
+       options, are not used for the unix domain sockets.
+
+    *) Feature: the rewrite directive supports the arguments rewriting.
+
+    *) Bugfix: the response code 400 was returned for the POST request with
+       the "Content-Length: 0" header; the bug had appeared in 0.1.14.
+
+
+Changes with nginx 0.1.15                                        19 Jan 2005
+
+    *) Bugfix: the error while the connecting to the FastCGI server caused
+       segmentation fault.
+
+    *) Bugfix: the correct handling of the regular expression, that has
+       different number of the captures and substitutions.
+
+    *) Feature: the location, that is passed to the FastCGI server, can be
+       regular expression.
+
+    *) Bugfix: the FastCGI's parameter REQUEST_URI is now passed with the
+       arguments and in the original state.
+
+    *) Bugfix: the ngx_http_rewrite_module module was required to be built
+       to use the regular expressions in locations.
+
+    *) Bugfix: the directive "proxy_preserve_host on" adds port 80 to the
+       "Host" headers, if upstream listen on port 80; the bug had appeared
+       in 0.1.14.
+
+    *) Bugfix: the same paths in autoconfiguration parameters
+       --http-client-body-temp-path=PATH and --http-proxy-temp-path=PATH, or
+       --http-client-body-temp-path=PATH and --http-fastcgi-temp-path=PATH
+       caused segmentation fault.
+
+
+Changes with nginx 0.1.14                                        18 Jan 2005
+
+    *) Feature: the autoconfiguration directives:
+       --http-client-body-temp-path=PATH, --http-proxy-temp-path=PATH, and
+       --http-fastcgi-temp-path=PATH
+
+    *) Change: the directory name for the temporary files with the client
+       request body is specified by directive client_body_temp_path, by
+       default it is <prefix>/client_body_temp.
+
+    *) Feature: the ngx_http_fastcgi_module and the directives:
+       fastcgi_pass, fastcgi_root, fastcgi_index, fastcgi_params,
+       fastcgi_connect_timeout, fastcgi_send_timeout, fastcgi_read_timeout,
+       fastcgi_send_lowat, fastcgi_header_buffer_size, fastcgi_buffers,
+       fastcgi_busy_buffers_size, fastcgi_temp_path,
+       fastcgi_max_temp_file_size, fastcgi_temp_file_write_size,
+       fastcgi_next_upstream, and fastcgi_x_powered_by.
+
+    *) Bugfix: the "[alert] zero size buf" error; the bug had appeared in
+       0.1.3.
+
+    *) Change: the URI must be specified after the host name in the
+       proxy_pass directive.
+
+    *) Change: the %3F symbol in the URI was considered as the argument
+       string start.
+
+    *) Feature: the unix domain sockets support in the
+       ngx_http_proxy_module.
+
+    *) Feature: the ssl_engine and ssl_ciphers directives.
+       Thanks to Sergey Skvortsov for SSL-accelerator.
+
+
+Changes with nginx 0.1.13                                        21 Dec 2004
+
+    *) Feature: the server_names_hash and server_names_hash_threshold
+       directives.
+
+    *) Bugfix: the *.domain.tld names in the "server_name" directive did not
+       work.
+
+    *) Bugfix: the %request_length log parameter logged the incorrect
+       length.
+
+
+Changes with nginx 0.1.12                                        06 Dec 2004
+
+    *) Feature: the %request_length log parameter.
+
+    *) Bugfix: when using the /dev/poll, select and poll on the platforms,
+       where these methods may do the false reports, there may be the long
+       delay when the request was passed via the keep-alive connection. It
+       may be at least on Solaris when using the /dev/poll.
+
+    *) Bugfix: the send_lowat directive is ignored on Linux because Linux
+       does not support the SO_SNDLOWAT option.
+
+
+Changes with nginx 0.1.11                                        02 Dec 2004
+
+    *) Feature: the worker_priority directive.
+
+    *) Change: both tcp_nopush and tcp_nodelay directives affect the
+       transferred response.
+
+    *) Bugfix: nginx did not call initgroups().
+       Thanks to Andrew Sitnikov and Andrei Nigmatulin.
+
+    *) Change: now the ngx_http_autoindex_module shows the file size in the
+       bytes.
+
+    *) Bugfix: the ngx_http_autoindex_module returned the 500 error if the
+       broken symlink was in a directory.
+
+    *) Bugfix: the files bigger than 4G could not be transferred using
+       sendfile.
+
+    *) Bugfix: if the backend was resolved to several backends and there was
+       an error while the response waiting then process may got caught in an
+       endless loop.
+
+    *) Bugfix: the worker process may exit with the "unknown cycle" message
+       when the /dev/poll method was used.
+
+    *) Bugfix: "close() channel failed" errors.
+
+    *) Bugfix: the autodetection of the "nobody" and "nogroup" groups.
+
+    *) Bugfix: the send_lowat directive did not work on Linux.
+
+    *) Bugfix: the segmentation fault occurred if there was no events
+       section in configuration.
+
+    *) Bugfix: nginx could not be built on OpenBSD.
+
+    *) Bugfix: the double slashes in "://" in the URI were converted to
+       ":/".
+
+
+Changes with nginx 0.1.10                                        26 Nov 2004
+
+    *) Bugfix: if the request without arguments contains "//", "/./", "/../"
+       or "%XX" then the last character in the request line was lost; the
+       bug had appeared in 0.1.9.
+
+    *) Bugfix: the fix in 0.1.9 for the files bigger than 2G on Linux did
+       not work.
+
+
+Changes with nginx 0.1.9                                         25 Nov 2004
+
+    *) Bugfix: the proxied request was sent without arguments if the request
+       contains "//", "/./", "/../" or "%XX".
+
+    *) Bugfix: the large compressed responses may be transferred not
+       completely.
+
+    *) Bugfix: the files bigger than 2G was not transferred on Linux that
+       does not support sendfile64().
+
+    *) Bugfix: while the build configuration on Linux the --with-poll_module
+       parameter was required; the bug had appeared in 0.1.8.
+
+
+Changes with nginx 0.1.8                                         20 Nov 2004
+
+    *) Bugfix: in the ngx_http_autoindex_module if the long file names were
+       in the listing.
+
+    *) Feature: the "^~" modifier in the location directive.
+
+    *) Feature: the proxy_max_temp_file_size directive.
+
+
+Changes with nginx 0.1.7                                         12 Nov 2004
+
+    *) Bugfix: on FreeBSD the segmentation fault may occur if the size of
+       the transferred file was changed; the bug had appeared in 0.1.5.
+
+
+Changes with nginx 0.1.6                                         11 Nov 2004
+
+    *) Bugfix: some location directive combinations with the regular
+       expressions caused the wrong configuration choose.
+
+
+Changes with nginx 0.1.5                                         11 Nov 2004
+
+    *) Bugfix: on Solaris and Linux there may be too many "recvmsg()
+       returned not enough data" alerts.
+
+    *) Bugfix: there were the "writev() failed (22: Invalid argument)"
+       errors on Solaris in proxy mode without sendfile. On other platforms
+       that do not support sendfile at all the process got caught in an
+       endless loop.
+
+    *) Bugfix: segmentation fault on Solaris in proxy mode and using
+       sendfile.
+
+    *) Bugfix: segmentation fault on Solaris.
+
+    *) Bugfix: on-line upgrade did not work on Linux.
+
+    *) Bugfix: the ngx_http_autoindex_module module did not escape the
+       spaces, the quotes, and the percent signs in the directory listing.
+
+    *) Change: the decrease of the copy operations.
+
+    *) Feature: the userid_p3p directive.
+
+
+Changes with nginx 0.1.4                                         26 Oct 2004
+
+    *) Bugfix: in the ngx_http_autoindex_module.
+
+
+Changes with nginx 0.1.3                                         25 Oct 2004
+
+    *) Feature: the ngx_http_autoindex_module and the autoindex directive.
+
+    *) Feature: the proxy_set_x_url directive.
+
+    *) Bugfix: proxy module may get caught in an endless loop when sendfile
+       is not used.
+
+
+Changes with nginx 0.1.2                                         21 Oct 2004
+
+    *) Feature: the --user=USER, --group=GROUP, and --with-ld-opt=OPTIONS
+       options in configure.
+
+    *) Feature: the server_name directive supports *.domain.tld.
+
+    *) Bugfix: the portability improvements.
+
+    *) Bugfix: if configuration file was set in command line, the
+       reconfiguration was impossible; the bug had appeared in 0.1.1.
+
+    *) Bugfix: proxy module may get caught in an endless loop when sendfile
+       is not used.
+
+    *) Bugfix: with sendfile the response was not recoded according to the
+       charset module directives; the bug had appeared in 0.1.1.
+
+    *) Bugfix: very seldom bug in the kqueue processing.
+
+    *) Bugfix: the gzip module compressed the proxied responses that was
+       already compressed.
+
+
+Changes with nginx 0.1.1                                         11 Oct 2004
+
+    *) Feature: the gzip_types directive.
+
+    *) Feature: the tcp_nodelay directive.
+
+    *) Feature: the send_lowat directive is working not only on OSes that
+       support kqueue NOTE_LOWAT, but also on OSes that support SO_SNDLOWAT.
+
+    *) Feature: the setproctitle() emulation for Linux and Solaris.
+
+    *) Bugfix: the "Location" header rewrite bug fixed while the proxying.
+
+    *) Bugfix: the ngx_http_chunked_module module may get caught in an
+       endless loop.
+
+    *) Bugfix: the /dev/poll module bugs fixed.
+
+    *) Bugfix: the responses were corrupted when the temporary files were
+       used while the proxying.
+
+    *) Bugfix: the unescaped requests were passed to the backend.
+
+    *) Bugfix: while the build configuration on Linux 2.4 the
+       --with-poll_module parameter was required.
+
+
+Changes with nginx 0.1.0                                         04 Oct 2004
+
+    *) The first public version.
+
diff --git a/nginx/CHANGES.ru b/nginx/CHANGES.ru
new file mode 100644 (file)
index 0000000..8c260fe
--- /dev/null
@@ -0,0 +1,8252 @@
+
+Изменения в nginx 1.14.2                                          04.12.2018
+
+    *) Исправление: nginx не собирался gcc 8.1.
+
+    *) Исправление: nginx не собирался на Fedora 28 Linux.
+
+    *) Исправление: в обработке адресов клиентов при использовании unix
+       domain listen-сокетов для работы с датаграммами на Linux.
+
+    *) Изменение: уровень логгирования ошибок SSL "http request", "https
+       proxy request", "unsupported protocol", "version too low", "no
+       suitable key share" и "no suitable signature algorithm" понижен с
+       уровня crit до info.
+
+    *) Исправление: при использовании OpenSSL 1.1.0 и новее директиву
+       ssl_prefer_server_ciphers нельзя было выключить в виртуальном
+       сервере, если она была включена в сервере по умолчанию.
+
+    *) Исправление: nginx не собирался с LibreSSL 2.8.0.
+
+    *) Исправление: если nginx был собран с OpenSSL 1.1.0, а использовался с
+       OpenSSL 1.1.1, протокол TLS 1.3 всегда был разрешён.
+
+    *) Исправление: при отправке сохранённого на диск тела запроса на
+       gRPC-бэкенд могли возникать ошибки.
+
+    *) Исправление: соединения к некоторым gRPC-бэкендам могли не
+       кэшироваться при использовании директивы keepalive.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовался модуль ngx_http_mp4_module на 32-битных
+       платформах.
+
+
+Изменения в nginx 1.14.1                                          06.11.2018
+
+    *) Безопасность: при использовании HTTP/2 клиент мог вызвать чрезмерное
+       потреблению памяти (CVE-2018-16843) и ресурсов процессора
+       (CVE-2018-16844).
+
+    *) Безопасность: при обработке специально созданного mp4-файла модулем
+       ngx_http_mp4_module содержимое памяти рабочего процесса могло быть
+       отправлено клиенту (CVE-2018-16845).
+
+    *) Исправление: при работе с gRPC-бэкендами могло расходоваться большое
+       количество памяти.
+
+
+Изменения в nginx 1.14.0                                          17.04.2018
+
+    *) Стабильная ветка 1.14.x.
+
+
+Изменения в nginx 1.13.12                                         10.04.2018
+
+    *) Исправление: при возврате большого ответа соединения с gRPC-бэкендами
+       могли неожиданно закрываться.
+
+
+Изменения в nginx 1.13.11                                         03.04.2018
+
+    *) Добавление: параметр proxy_protocol директивы listen теперь
+       поддерживает протокол PROXY версии 2.
+
+    *) Исправление: nginx не собирался с OpenSSL 1.1.1 статически на Linux.
+
+    *) Исправление: в параметрах http_404, http_500 и им подобных директивы
+       proxy_next_upstream.
+
+
+Изменения в nginx 1.13.10                                         20.03.2018
+
+    *) Добавление: теперь параметр set в SSI-директиве include позволяет
+       сохранять в переменную любые ответы; максимальный размер ответа
+       задаётся директивой subrequest_output_buffer_size.
+
+    *) Добавление: теперь nginx использует вызов
+       clock_gettime(CLOCK_MONOTONIC), если он доступен, что позволяет
+       избежать некорректного срабатывания таймаутов при изменениях
+       системного времени.
+
+    *) Добавление: параметр "escape=none" директивы log_format.
+       Спасибо Johannes Baiter и Calin Don.
+
+    *) Добавление: переменная $ssl_preread_alpn_protocols в модуле
+       ngx_stream_ssl_preread_module.
+
+    *) Добавление: модуль ngx_http_grpc_module.
+
+    *) Исправление: в обработке ошибок выделения памяти в директиве geo.
+
+    *) Исправление: при использовании переменных в директиве
+       auth_basic_user_file в лог мог выводиться символ '\0'.
+       Спасибо Вадиму Филимонову.
+
+
+Изменения в nginx 1.13.9                                          20.02.2018
+
+    *) Добавление: поддержка HTTP/2 server push; директивы http2_push и
+       http2_push_preload.
+
+    *) Исправление: при использовании кэша в логах могли появляться
+       сообщения "header already sent"; ошибка появилась в 1.9.13.
+
+    *) Исправление: при использовании директивы ssl_verify_client в рабочем
+       процессе мог произойти segmentation fault, если в виртуальном сервере
+       не был указан SSL-сертификат.
+
+    *) Исправление: в модуле ngx_http_v2_module.
+
+    *) Исправление: в модуле ngx_http_dav_module.
+
+
+Изменения в nginx 1.13.8                                          26.12.2017
+
+    *) Добавление: теперь при использовании параметра transparent директив
+       proxy_bind, fastcgi_bind, memcached_bind, scgi_bind и uwsgi_bind
+       nginx автоматически сохраняет capability CAP_NET_RAW в рабочих
+       процессах.
+
+    *) Добавление: улучшения в определении размера строки кэша процессора.
+       Спасибо Debayan Ghosh.
+
+    *) Добавление: новые директивы в скриптах подсветки синтаксиса для vim.
+       Спасибо Геннадию Махомеду.
+
+    *) Исправление: процедура обновления исполняемого файла не работала,
+       если после завершения родительского процесса новым родительским
+       процессом nginx'а становился процесс с PID, отличным от 1.
+
+    *) Исправление: модуль ngx_http_autoindex_module неправильно обрабатывал
+       запросы с телом.
+
+    *) Исправление: в директиве proxy_limit_rate при использовании с
+       директивой keepalive.
+
+    *) Исправление: при использовании "proxy_buffering off" часть ответа
+       могла буферизироваться, если клиентское соединение использовало SSL.
+       Спасибо Patryk Lesiewicz.
+
+    *) Исправление: в директиве proxy_cache_background_update.
+
+    *) Исправление: переменную вида "${name}" с именем в фигурных скобках
+       нельзя было использовать в начале параметра не заключив весь параметр
+       в кавычки.
+
+
+Изменения в nginx 1.13.7                                          21.11.2017
+
+    *) Исправление: в переменной $upstream_status.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если бэкенд возвращал ответ "101 Switching Protocols" на подзапрос.
+
+    *) Исправление: если при переконфигурации изменялся размер зоны
+       разделяемой памяти и переконфигурация завершалась неудачно, то в
+       главном процессе происходил segmentation fault.
+
+    *) Исправление: в модуле ngx_http_fastcgi_module.
+
+    *) Исправление: nginx возвращал ошибку 500, если в директиве
+       xslt_stylesheet были заданы параметры без использования переменных.
+
+    *) Изменение: при использовании варианта библиотеки zlib от Intel в лог
+       писались сообщения "gzip filter failed to use preallocated memory".
+
+    *) Исправление: директива worker_shutdown_timeout не работала при
+       использовании почтового прокси-сервера и при проксировании
+       WebSocket-соединений.
+
+
+Изменения в nginx 1.13.6                                          10.10.2017
+
+    *) Исправление: при использовании директивы ssl_preread в модуле stream
+       не работало переключение на следующий бэкенд.
+
+    *) Исправление: в модуле ngx_http_v2_module.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: nginx не поддерживал даты после 2038 года на 32-битных
+       платформах с 64-битным time_t.
+
+    *) Исправление: в обработке дат до 1970 года и после 10000 года.
+
+    *) Исправление: в модуле stream таймауты ожидания UDP-пакетов от
+       бэкендов не логгировались или логгировались на уровне info вместо
+       error.
+
+    *) Исправление: при использовании HTTP/2 nginx мог вернуть ошибку 400,
+       не указав в логе причину.
+
+    *) Исправление: в обработке повреждённых файлов кэша.
+
+    *) Исправление: при кэшировании ошибок, перехваченных error_page, не
+       учитывались заголовки управления кэшированием.
+
+    *) Исправление: при использовании HTTP/2 тело запроса могло быть
+       повреждено.
+
+    *) Исправление: в обработке адресов клиентов при использовании unix
+       domain сокетов.
+
+    *) Исправление: при использовании директивы "hash ... consistent" в
+       блоке upstream nginx нагружал процессор, если использовались большие
+       веса и все или почти все бэкенды были недоступны.
+
+
+Изменения в nginx 1.13.5                                          05.09.2017
+
+    *) Добавление: переменная $ssl_client_escaped_cert.
+
+    *) Исправление: директива ssl_session_ticket_key и параметр include
+       директивы geo не работали на Windows.
+
+    *) Исправление: на 32-битных платформах при запросе более 4 гигабайт с
+       помощью нескольких диапазонов возвращалась некорректная длина ответа.
+
+    *) Исправление: директива "expires modified" и обработка строки If-Range
+       заголовка запроса не учитывали время последнего изменения ответа,
+       если использовалось проксирование без кэширования.
+
+
+Изменения в nginx 1.13.4                                          08.08.2017
+
+    *) Добавление: модуль ngx_http_mirror_module.
+
+    *) Исправление: клиентские соединения могли сбрасываться при
+       тестировании конфигурации, если использовался параметр reuseport
+       директивы listen на Linux.
+
+    *) Исправление: тело запроса могло быть недоступно в подзапросах, если
+       оно было сохранено в файл и использовалось проксирование.
+
+    *) Исправление: очистка кэша по max_size не работала на Windows.
+
+    *) Исправление: любое выделение разделяемой памяти на Windows требовало
+       4096 байт памяти.
+
+    *) Исправление: при использовании директивы zone в блоке upstream на
+       Windows рабочий процесс мог завершаться аварийно.
+
+
+Изменения в nginx 1.13.3                                          11.07.2017
+
+    *) Безопасность: специально созданный запрос мог вызвать целочисленное
+       переполнение в range-фильтре и последующую некорректную обработку
+       запрошенных диапазонов, что потенциально могло привести к утечке
+       конфиденциальной информации (CVE-2017-7529).
+
+
+Изменения в nginx 1.13.2                                          27.06.2017
+
+    *) Изменение: теперь при запросе диапазона, начинающегося с 0, из
+       пустого файла nginx возвращает ответ 200 вместо 416.
+
+    *) Добавление: директива add_trailer.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: nginx не собирался под Cygwin и NetBSD; ошибка появилась
+       в 1.13.0.
+
+    *) Исправление: nginx не собирался под MSYS2 / MinGW 64-bit.
+       Спасибо Orgad Shaneh.
+
+    *) Исправление: при использовании SSI с большим количеством подзапросов
+       и proxy_pass с переменными в рабочем процессе мог произойти
+       segmentation fault.
+
+    *) Исправление: в модуле ngx_http_v2_module.
+       Спасибо Piotr Sikora.
+
+
+Изменения в nginx 1.13.1                                          30.05.2017
+
+    *) Добавление: теперь в качестве параметра директивы set_real_ip_from
+       можно указывать имя хоста.
+
+    *) Добавление: улучшения в скриптах подсветки синтаксиса для vim.
+
+    *) Добавление: директива worker_cpu_affinity теперь работает на
+       DragonFly BSD.
+       Спасибо Sepherosa Ziehau.
+
+    *) Исправление: SSL renegotiation в соединениях к бэкендам не работал
+       при использовании OpenSSL до 1.1.0.
+
+    *) Изменение: nginx не собирался с Oracle Developer Studio 12.5.
+
+    *) Изменение: теперь cache manager пропускает заблокированные записи при
+       очистке кэша по max_size.
+
+    *) Исправление: клиентские SSL-соединения сразу закрывались, если
+       использовался отложенный accept и параметр proxy_protocol директивы
+       listen.
+
+    *) Исправление: в директиве proxy_cache_background_update.
+
+    *) Изменение: теперь директива tcp_nodelay устанавливает опцию
+       TCP_NODELAY перед SSL handshake.
+
+
+Изменения в nginx 1.13.0                                          25.04.2017
+
+    *) Изменение: теперь SSL renegotiation допускается в соединениях к
+       бэкендам.
+
+    *) Добавление: параметры rcvbuf и sndbuf директив listen в почтовом
+       прокси-сервере и модуле stream.
+
+    *) Добавление: директивы return и error_page теперь могут использоваться
+       для возврата перенаправлений с кодом 308.
+       Спасибо Simon Leblanc.
+
+    *) Добавление: параметр TLSv1.3 в директиве ssl_protocols.
+
+    *) Добавление: при логгировании сигналов теперь указывается PID
+       отправившего сигнал процесса.
+
+    *) Исправление: в обработке ошибок выделения памяти.
+
+    *) Исправление: если сервер в модуле stream слушал на wildcard-адресе,
+       исходящий адрес ответного UDP-пакета мог отличаться от адреса
+       назначения исходного пакета.
+
+
+Изменения в nginx 1.11.13                                         04.04.2017
+
+    *) Добавление: параметр http_429 в директивах proxy_next_upstream,
+       fastcgi_next_upstream, scgi_next_upstream и uwsgi_next_upstream.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: в обработке ошибок выделения памяти.
+
+    *) Исправление: при использовании директив sendfile и timer_resolution
+       на Linux запросы могли зависать.
+
+    *) Исправление: при использовании с подзапросами директив sendfile и
+       aio_write запросы могли зависать.
+
+    *) Исправление: в модуле ngx_http_v2_module.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: при использовании HTTP/2 в рабочем процессе мог
+       произойти segmentation fault.
+
+    *) Исправление: запросы могли зависать при использовании с подзапросами
+       директив limit_rate, sendfile_max_chunk, limit_req или метода
+       $r->sleep() встроенного перла.
+
+    *) Исправление: в модуле ngx_http_slice_module.
+
+
+Изменения в nginx 1.11.12                                         24.03.2017
+
+    *) Исправление: nginx мог нагружать процессор; ошибка появилась в
+       1.11.11.
+
+
+Изменения в nginx 1.11.11                                         21.03.2017
+
+    *) Добавление: директива worker_shutdown_timeout.
+
+    *) Добавление: улучшения в скриптах подсветки синтаксиса для vim.
+       Спасибо Wei-Ko Kao.
+
+    *) Исправление: при попытке установить переменную $limit_rate в пустую
+       строку в рабочем процессе мог произойти segmentation fault.
+
+    *) Исправление: директивы proxy_cache_background_update,
+       fastcgi_cache_background_update, scgi_cache_background_update и
+       uwsgi_cache_background_update могли работать некорректно, если
+       использовалась директива if.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если количество large_client_header_buffers в виртуальном сервере
+       отличалось от такового в сервере по умолчанию.
+
+    *) Исправление: в почтовом прокси-сервере.
+
+
+Изменения в nginx 1.11.10                                         14.02.2017
+
+    *) Изменение: формат заголовка кэша был изменен, ранее закэшированные
+       ответы будут загружены заново.
+
+    *) Добавление: поддержка расширений stale-while-revalidate и
+       stale-if-error в строке "Cache-Control" в заголовке ответа бэкенда.
+
+    *) Добавление: директивы proxy_cache_background_update,
+       fastcgi_cache_background_update, scgi_cache_background_update и
+       uwsgi_cache_background_update.
+
+    *) Добавление: теперь nginx может кэшировать ответы со строкой Vary
+       заголовка длиной до 128 символов (вместо 42 символов в предыдущих
+       версиях).
+
+    *) Добавление: параметр build директивы server_tokens.
+       Спасибо Tom Thorogood.
+
+    *) Исправление: при обработке запросов со строкой "Expect: 100-continue"
+       в заголовке запроса в логах могли появляться сообщения "[crit]
+       SSL_write() failed".
+
+    *) Исправление: модуль ngx_http_slice_module не работал в именованных
+       location'ах.
+
+    *) Исправление: при использовании AIO после перенаправления запроса с
+       помощью X-Accel-Redirect в рабочем процессе мог произойти
+       segmentation fault.
+
+    *) Исправление: уменьшено потребление памяти для долгоживущих запросов,
+       использующих сжатие.
+
+
+Изменения в nginx 1.11.9                                          24.01.2017
+
+    *) Исправление: при использовании модуля stream nginx мог нагружать
+       процессор; ошибка появилась в 1.11.5.
+
+    *) Исправление: метод аутентификации EXTERNAL в почтовом прокси-сервере
+       можно было использовать, даже если он не был разрешён в конфигурации.
+
+    *) Исправление: при использовании директивы ssl_verify_client модуля
+       stream в рабочем процессе мог произойти segmentation fault.
+
+    *) Исправление: директива ssl_verify_client модуля stream могла не
+       работать.
+
+    *) Исправление: при исчерпании рабочим процессом свободных соединений
+       keepalive-соединения могли закрываться излишне агрессивно.
+       Спасибо Joel Cunningham.
+
+    *) Исправление: при использовании директивы sendfile на FreeBSD и macOS
+       мог возвращаться некорректный ответ; ошибка появилась в 1.7.8.
+
+    *) Исправление: при использовании директивы aio_write ответ мог
+       сохраняться в кэш не полностью.
+
+    *) Исправление: при использовании директивы aio_write могла происходить
+       утечка сокетов.
+
+
+Изменения в nginx 1.11.8                                          27.12.2016
+
+    *) Добавление: директива absolute_redirect.
+
+    *) Добавление: параметр escape директивы log_format.
+
+    *) Добавление: проверка клиентских SSL-сертификатов в модуле stream.
+
+    *) Добавление: директива ssl_session_ticket_key поддерживает шифрование
+       TLS session tickets с помощью AES256 при использовании с 80-байтными
+       ключами.
+
+    *) Добавление: поддержка vim-commentary в скриптах для vim.
+       Спасибо Armin Grodon.
+
+    *) Исправление: рекурсия при получении значений переменных не
+       ограничивалась.
+
+    *) Исправление: в модуле ngx_stream_ssl_preread_module.
+
+    *) Исправление: если сервер, описанный в блоке upstream в модуле stream,
+       был признан неработающим, то после истечения fail_timeout он
+       признавался работающим только после завершения тестового соединения;
+       теперь достаточно, чтобы соединение было успешно установлено.
+
+    *) Исправление: nginx/Windows не собирался с 64-битным Visual Studio.
+
+    *) Исправление: nginx/Windows не собирался с OpenSSL 1.1.0.
+
+
+Изменения в nginx 1.11.7                                          13.12.2016
+
+    *) Изменение: переменная $ssl_client_verify теперь в случае ошибки
+       проверки клиентского сертификата содержит строку с описанием ошибки,
+       например, "FAILED:certificate has expired".
+
+    *) Добавление: переменные $ssl_ciphers, $ssl_curves,
+       $ssl_client_v_start, $ssl_client_v_end и $ssl_client_v_remain.
+
+    *) Добавление: параметр volatile директивы map.
+
+    *) Исправление: при сборке динамических модулей не учитывались заданные
+       для модуля зависимости.
+
+    *) Исправление: при использовании HTTP/2 и директив limit_req или
+       auth_request тело запроса могло быть повреждено; ошибка появилась в
+       1.11.0.
+
+    *) Исправление: при использовании HTTP/2 в рабочем процессе мог
+       произойти segmentation fault; ошибка появилась в 1.11.3.
+
+    *) Исправление: в модуле ngx_http_mp4_module.
+       Спасибо Congcong Hu.
+
+    *) Исправление: в модуле ngx_http_perl_module.
+
+
+Изменения в nginx 1.11.6                                          15.11.2016
+
+    *) Изменение: формат переменных $ssl_client_s_dn и $ssl_client_i_dn
+       изменён на соответствующий RFC 2253 (RFC 4514); значения в старом
+       формате доступны через переменные $ssl_client_s_dn_legacy и
+       $ssl_client_i_dn_legacy.
+
+    *) Изменение: при сохранении временных файлов в каталоге кэша они теперь
+       располагаются не в отдельном подкаталоге для временных файлов, а в
+       том же подкаталоге, что и соответствующие файлы в кэше.
+
+    *) Добавление: поддержка метода аутентификации EXTERNAL в почтовом
+       прокси-сервере.
+       Спасибо Robert Norris.
+
+    *) Добавление: поддержка WebP в модуле ngx_http_image_filter_module.
+
+    *) Добавление: директива proxy_method поддерживает переменные.
+       Спасибо Дмитрию Лазуркину.
+
+    *) Добавление: директива http2_max_requests в модуле ngx_http_v2_module.
+
+    *) Добавление: директивы proxy_cache_max_range_offset,
+       fastcgi_cache_max_range_offset, scgi_cache_max_range_offset и
+       uwsgi_cache_max_range_offset.
+
+    *) Исправление: плавное завершение старых рабочих процессов могло
+       занимать бесконечное время при использовании HTTP/2.
+
+    *) Исправление: в модуле ngx_http_mp4_module.
+
+    *) Исправление: при проксировании WebSocket-соединений и включённом
+       кэшировании в логах могли появляться сообщения "ignore long locked
+       inactive cache entry".
+
+    *) Исправление: если во время SSL handshake с бэкендом происходил
+       таймаут, nginx ничего не писал в лог и возвращал ответ с кодом 502
+       вместо 504.
+
+
+Изменения в nginx 1.11.5                                          11.10.2016
+
+    *) Изменение: параметр configure --with-ipv6 упразднён, поддержка IPv6
+       теперь собирается автоматически.
+
+    *) Изменение: теперь, если в блоке upstream не оказалось доступных
+       серверов, nginx не сбрасывает статистику ошибок всех серверов, как
+       делал ранее, а ожидает истечения fail_timeout.
+
+    *) Добавление: модуль ngx_stream_ssl_preread_module.
+
+    *) Добавление: директива server в блоке upstream поддерживает параметр
+       max_conns.
+
+    *) Добавление: параметр configure --with-compat.
+
+    *) Добавление: параметры manager_files, manager_threshold и
+       manager_sleep директив proxy_cache_path, fastcgi_cache_path,
+       scgi_cache_path и uwsgi_cache_path.
+
+    *) Исправление: при сборке perl-модуля не использовались флаги, заданные
+       с помощью параметра configure --with-ld-opt.
+
+    *) Исправление: в директиве add_after_body при использовании совместно с
+       директивой sub_filter.
+
+    *) Исправление: в переменной $realip_remote_addr.
+
+    *) Исправление: директивы dav_access, proxy_store_access,
+       fastcgi_store_access, scgi_store_access и uwsgi_store_access
+       игнорировали права, заданные для пользователя.
+
+    *) Исправление: unix domain listen-сокеты могли не наследоваться при
+       обновлении исполняемого файла на Linux.
+
+    *) Исправление: nginx возвращал ошибку 400 на запросы с символом "-" в
+       HTTP-методе.
+
+
+Изменения в nginx 1.11.4                                          13.09.2016
+
+    *) Добавление: переменная $upstream_bytes_received.
+
+    *) Добавление: переменные $bytes_received, $session_time, $protocol,
+       $status, $upstream_addr, $upstream_bytes_sent,
+       $upstream_bytes_received, $upstream_connect_time,
+       $upstream_first_byte_time и $upstream_session_time в модуле stream.
+
+    *) Добавление: модуль ngx_stream_log_module.
+
+    *) Добавление: параметр proxy_protocol в директиве listen, переменные
+       $proxy_protocol_addr и $proxy_protocol_port в модуле stream.
+
+    *) Добавление: модуль ngx_stream_realip_module.
+
+    *) Исправление: nginx не собирался с модулем stream и модулем
+       ngx_http_ssl_module, но без модуля ngx_stream_ssl_module; ошибка
+       появилась в 1.11.3.
+
+    *) Добавление: опция сокета IP_BIND_ADDRESS_NO_PORT не использовалась;
+       ошибка появилась в 1.11.2.
+
+    *) Исправление: в параметре ranges директивы geo.
+
+    *) Исправление: при использовании директив "aio threads" и sendfile мог
+       возвращаться некорректный ответ; ошибка появилась в 1.9.13.
+
+
+Изменения в nginx 1.11.3                                          26.07.2016
+
+    *) Изменение: теперь accept_mutex по умолчанию выключен.
+
+    *) Добавление: теперь nginx использует EPOLLEXCLUSIVE на Linux.
+
+    *) Добавление: модуль ngx_stream_geo_module.
+
+    *) Добавление: модуль ngx_stream_geoip_module.
+
+    *) Добавление: модуль ngx_stream_split_clients_module.
+
+    *) Добавление: директивы proxy_pass и proxy_ssl_name в модуле stream
+       поддерживают переменные.
+
+    *) Исправление: утечки сокетов при использовании HTTP/2.
+
+    *) Исправление: в configure.
+       Спасибо Piotr Sikora.
+
+
+Изменения в nginx 1.11.2                                          05.07.2016
+
+    *) Изменение: теперь nginx всегда использует внутренние реализации MD5 и
+       SHA1; параметры configure --with-md5 и --with-sha1 упразднены.
+
+    *) Добавление: поддержка переменных в модуле stream.
+
+    *) Добавление: модуль ngx_stream_map_module.
+
+    *) Добавление: модуль ngx_stream_return_module.
+
+    *) Добавление: в директивах proxy_bind, fastcgi_bind, memcached_bind,
+       scgi_bind и uwsgi_bind теперь можно указывать порт.
+
+    *) Добавление: теперь nginx использует опцию сокета
+       IP_BIND_ADDRESS_NO_PORT, если она доступна.
+
+    *) Исправление: при использовании HTTP/2 и директивы
+       proxy_request_buffering в рабочем процессе мог произойти segmentation
+       fault.
+
+    *) Исправление: при использовании HTTP/2 к запросам, передаваемым на
+       бэкенд, всегда добавлялась строка заголовка "Content-Length", даже
+       если у запроса не было тела.
+
+    *) Исправление: при использовании HTTP/2 в логах могли появляться
+       сообщения "http request count is zero".
+
+    *) Исправление: при использовании директивы sub_filter могло
+       буферизироваться больше данных, чем это необходимо; проблема
+       появилась в 1.9.4.
+
+
+Изменения в nginx 1.11.1                                          31.05.2016
+
+    *) Безопасность: при записи тела специально созданного запроса во
+       временный файл в рабочем процессе мог происходить segmentation fault
+       (CVE-2016-4450); ошибка появилась в 1.3.9.
+
+
+Изменения в nginx 1.11.0                                          24.05.2016
+
+    *) Добавление: параметр transparent директив proxy_bind, fastcgi_bind,
+       memcached_bind, scgi_bind и uwsgi_bind.
+
+    *) Добавление: переменная $request_id.
+
+    *) Добавление: директива map поддерживает комбинации нескольких
+       переменных в качестве результирующих значений.
+
+    *) Добавление: теперь при использовании метода epoll nginx проверяет,
+       поддерживает ли ядро события EPOLLRDHUP, и соответственно
+       оптимизирует обработку соединений.
+
+    *) Добавление: директивы ssl_certificate и ssl_certificate_key теперь
+       можно указывать несколько раз для загрузки сертификатов разных типов
+       (например, RSA и ECDSA).
+
+    *) Добавление: при использовании OpenSSL 1.0.2 и новее с помощью
+       директивы ssl_ecdh_curve теперь можно задать список кривых; по
+       умолчанию используется встроенный в OpenSSL список кривых.
+
+    *) Изменение: для использования DHE-шифров теперь надо явно задавать
+       файл параметров с помощью директивы ssl_dhparam.
+
+    *) Добавление: переменная $proxy_protocol_port.
+
+    *) Добавление: переменная $realip_remote_port в модуле
+       ngx_http_realip_module.
+
+    *) Добавление: модуль ngx_http_realip_module теперь позволяет
+       устанавливать не только адрес, но и порт клиента.
+
+    *) Изменение: при попытке запросить виртуальный сервер, отличающийся от
+       согласованного в процессе SSL handshake, теперь возвращается ответ
+       "421 Misdirected Request"; это улучшает совместимость с некоторыми
+       HTTP/2-клиентами в случае использования клиентских сертификатов.
+
+    *) Изменение: HTTP/2-клиенты теперь могут сразу присылать тело запроса;
+       директива http2_body_preread_size позволяет указать размер буфера,
+       который будет использоваться до того, как nginx начнёт читать тело.
+
+    *) Исправление: при использовании директивы proxy_cache_bypass не
+       обновлялись закэшированные ошибочные ответы.
+
+
+Изменения в nginx 1.9.15                                          19.04.2016
+
+    *) Исправление: при использовании HHVM в качестве FastCGI-сервера могли
+       возникать ошибки "recv() failed".
+
+    *) Исправление: при использовании HTTP/2 и директив limit_req или
+       auth_request при чтении тела запроса мог произойти таймаут или ошибка
+       "client violated flow control"; ошибка появилась в 1.9.14.
+
+    *) Изменение: при использовании HTTP/2 ответ мог не показываться
+       некоторыми браузерами, если тело запроса было прочитано не целиком;
+       ошибка появилась в 1.9.14.
+
+    *) Исправление: при использовании директивы "aio threads" соединения
+       могли зависать.
+       Спасибо Mindaugas Rasiukevicius.
+
+
+Изменения в nginx 1.9.14                                          05.04.2016
+
+    *) Добавление: совместимость с OpenSSL 1.1.0.
+
+    *) Добавление: директивы proxy_request_buffering,
+       fastcgi_request_buffering, scgi_request_buffering и
+       uwsgi_request_buffering теперь работают при использовании HTTP/2.
+
+    *) Исправление: при использовании HTTP/2 в логах могли появляться
+       сообщения "zero size buf in output".
+
+    *) Исправление: при использовании HTTP/2 директива client_max_body_size
+       могла работать неверно.
+
+    *) Исправление: незначительных ошибок логгирования.
+
+
+Изменения в nginx 1.9.13                                          29.03.2016
+
+    *) Изменение: неидемпотентные запросы (POST, LOCK, PATCH) теперь по
+       умолчанию не передаются на другой сервер, если запрос уже был
+       отправлен на бэкенд; параметр non_idempotent директивы
+       proxy_next_upstream явно разрешает повторять такие запросы.
+
+    *) Добавление: модуль ngx_http_perl_module теперь можно собрать
+       динамически.
+
+    *) Добавление: поддержка UDP в модуле stream.
+
+    *) Добавление: директива aio_write.
+
+    *) Добавление: теперь cache manager следит за количеством элементов в
+       кэше и старается не допускать переполнений зоны разделяемой памяти.
+
+    *) Исправление: при использовании директив sendfile и aio с подзапросами
+       в логах могли появляться сообщения "task already active" и "second
+       aio post".
+
+    *) Исправление: при использовании кэширования в логах могли появляться
+       сообщения "zero size buf in output", если клиент закрывал соединение
+       преждевременно.
+
+    *) Исправление: при использовании кэширования соединения с клиентами
+       могли закрываться без необходимости.
+       Спасибо Justin Li.
+
+    *) Исправление: nginx мог нагружать процессор при использовании
+       директивы sendfile на Linux и Solaris, если отправляемый файл был
+       изменён в процессе отправки.
+
+    *) Исправление: при использовании директив sendfile и "aio threads"
+       соединения могли зависать.
+
+    *) Исправление: в директивах proxy_pass, fastcgi_pass, scgi_pass и
+       uwsgi_pass при использовании переменных.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: в модуле ngx_http_sub_filter_module.
+
+    *) Исправление: если в закэшированном соединении к бэкенду происходила
+       ошибка, запрос передавался на другой сервер без учёта директивы
+       proxy_next_upstream.
+
+    *) Исправление: ошибки "CreateFile() failed" при создании временных
+       файлов на Windows.
+
+
+Изменения в nginx 1.9.12                                          24.02.2016
+
+    *) Добавление: кодирование Хаффмана заголовков ответов в HTTP/2.
+       Спасибо Владу Краснову.
+
+    *) Добавление: директива worker_cpu_affinity теперь поддерживает более
+       64 процессоров.
+
+    *) Исправление: совместимость со сторонними модулями на C++; ошибка
+       появилась в 1.9.11.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: nginx не собирался статически с OpenSSL на Linux; ошибка
+       появилась в 1.9.11.
+
+    *) Исправление: директива "add_header ... always" с пустым значением не
+       удаляла из заголовков ошибочных ответов строки Last-Modified и ETag.
+
+    *) Изменение: при использовании OpenSSL 1.0.2f в логах могли появляться
+       сообщения "called a function you should not call" и "shutdown while
+       in init".
+
+    *) Исправление: ошибочные заголовки могли логгироваться некорректно.
+
+    *) Исправление: утечки сокетов при использовании HTTP/2.
+
+    *) Исправление: в модуле ngx_http_v2_module.
+
+
+Изменения в nginx 1.9.11                                          09.02.2016
+
+    *) Добавление: теперь resolver поддерживает TCP.
+
+    *) Добавление: динамические модули.
+
+    *) Исправление: при использовании HTTP/2 переменная $request_length не
+       учитывала размер заголовков запроса.
+
+    *) Исправление: в модуле ngx_http_v2_module.
+
+
+Изменения в nginx 1.9.10                                          26.01.2016
+
+    *) Безопасность: при использовании директивы resolver во время обработки
+       ответов DNS-сервера могло происходить разыменование некорректного
+       адреса, что позволяло атакующему, имеющему возможность подделывать
+       UDP-пакеты от DNS-сервера, вызвать segmentation fault в рабочем
+       процессе (CVE-2016-0742).
+
+    *) Безопасность: при использовании директивы resolver во время обработки
+       CNAME-записей могло произойти обращение к ранее освобождённой памяти,
+       что позволяло атакующему, имеющему возможность инициировать
+       преобразование произвольных имён в адреса, вызвать segmentation fault
+       в рабочем процессе, а также потенциально могло иметь другие
+       последствия (CVE-2016-0746).
+
+    *) Безопасность: при использовании директивы resolver во время обработки
+       CNAME-записей не во всех случаях проверялось ограничение на
+       максимальное количество записей в цепочке, что позволяло атакующему,
+       имеющему возможность инициировать преобразование произвольных имён в
+       адреса, вызвать чрезмерное потребление ресурсов рабочими процессами
+       (CVE-2016-0747).
+
+    *) Добавление: параметр auto директивы worker_cpu_affinity.
+
+    *) Исправление: параметр proxy_protocol директивы listen не работал с
+       IPv6 listen-сокетами.
+
+    *) Исправление: при использовании директивы keepalive соединения к
+       бэкендам могли кэшироваться некорректно.
+
+    *) Исправление: после перенаправления запроса с помощью X-Accel-Redirect
+       при проксировании использовался HTTP-метод оригинального запроса.
+
+
+Изменения в nginx 1.9.9                                           09.12.2015
+
+    *) Исправление: проксирование в unix domain сокеты не работало при
+       использовании переменных; ошибка появилась в 1.9.8.
+
+
+Изменения в nginx 1.9.8                                           08.12.2015
+
+    *) Добавление: поддержка pwritev().
+
+    *) Добавление: директива include в блоке upstream.
+
+    *) Добавление: модуль ngx_http_slice_module.
+
+    *) Исправление: при использовании LibreSSL в рабочем процессе мог
+       произойти segmentation fault; ошибка появилась в 1.9.6.
+
+    *) Исправление: nginx мог не собираться на OS X.
+
+
+Изменения в nginx 1.9.7                                           17.11.2015
+
+    *) Добавление: параметр nohostname логгирования в syslog.
+
+    *) Добавление: директива proxy_cache_convert_head.
+
+    *) Добавление: переменная $realip_remote_addr в модуле
+       ngx_http_realip_module.
+
+    *) Исправление: директива expires могла не срабатывать при использовании
+       переменных.
+
+    *) Исправление: при использовании HTTP/2 в рабочем процессе мог
+       произойти segmentation fault; ошибка появилась в 1.9.6.
+
+    *) Исправление: если nginx был собран с модулем ngx_http_v2_module,
+       протокол HTTP/2 мог быть использован клиентом, даже если не был
+       указан параметр http2 директивы listen.
+
+    *) Исправление: в модуле ngx_http_v2_module.
+
+
+Изменения в nginx 1.9.6                                           27.10.2015
+
+    *) Исправление: при использовании HTTP/2 в рабочем процессе мог
+       произойти segmentation fault.
+       Спасибо Piotr Sikora и Denis Andzakovic.
+
+    *) Исправление: при использовании HTTP/2 переменная $server_protocol
+       была пустой.
+
+    *) Исправление: SSL-соединения к бэкендам в модуле stream могли
+       неожиданно завершаться по таймауту.
+
+    *) Исправление: при использовании различных настроек ssl_session_cache в
+       разных виртуальных серверах в рабочем процессе мог произойти
+       segmentation fault.
+
+    *) Исправление: nginx/Windows не собирался с MinGW gcc; ошибка появилась
+       в 1.9.4.
+       Спасибо Kouhei Sutou.
+
+    *) Исправление: при использовании директивы timer_resolution на Windows
+       время не обновлялось.
+
+    *) Незначительные исправления и улучшения.
+       Спасибо Markus Linnala, Kurtis Nusbaum и Piotr Sikora.
+
+
+Изменения в nginx 1.9.5                                           22.09.2015
+
+    *) Добавление: модуль ngx_http_v2_module (заменяет модуль
+       ngx_http_spdy_module).
+       Спасибо Dropbox и Automattic за спонсирование разработки.
+
+    *) Изменение: теперь по умолчанию директива output_buffers использует
+       два буфера.
+
+    *) Изменение: теперь nginx ограничивает максимальную вложенность
+       подзапросов, а не количество одновременных подзапросов.
+
+    *) Изменение: теперь при возврате ответов из кэша nginx проверяет ключ
+       полностью.
+       Спасибо Геннадию Махомеду и Сергею Брестеру.
+
+    *) Исправление: при использовании кэша в логах могли появляться
+       сообщения "header already sent"; ошибка появилась в 1.7.5.
+
+    *) Исправление: при использовании CephFS и директивы timer_resolution на
+       Linux в логах могли появляться сообщения "writev() failed (4:
+       Interrupted system call)".
+
+    *) Исправление: в обработке ошибок конфигурации.
+       Спасибо Markus Linnala.
+
+    *) Исправление: при использовании директивы sub_filter на уровне http в
+       рабочем процессе происходил segmentation fault; ошибка появилась в
+       1.9.4.
+
+
+Изменения в nginx 1.9.4                                           18.08.2015
+
+    *) Изменение: директивы proxy_downstream_buffer и proxy_upstream_buffer
+       в модуле stream заменены директивой proxy_buffer_size.
+
+    *) Добавление: директива tcp_nodelay в модуле stream.
+
+    *) Добавление: теперь можно указать несколько директив sub_filter
+       одновременно.
+
+    *) Добавление: директива sub_filter поддерживает переменные в строке
+       поиска.
+
+    *) Изменение: тестирование конфигурации могло не работать под Linux
+       OpenVZ.
+       Спасибо Геннадию Махомеду.
+
+    *) Исправление: после переконфигурации старые рабочие процессы могли
+       сильно нагружать процессор при больших значениях worker_connections.
+
+    *) Исправление: при совместном использовании директив try_files и alias
+       внутри location'а, заданного регулярным выражением, в рабочем
+       процессе мог произойти segmentation fault; ошибка появилась в 1.7.1.
+
+    *) Исправление: директива try_files внутри вложенного location'а,
+       заданного регулярным выражением, работала неправильно, если во
+       внешнем location'е использовалась директива alias.
+
+    *) Исправление: в обработке ошибок при построении хэш-таблиц.
+
+    *) Исправление: nginx не собирался с Visual Studio 2015.
+
+
+Изменения в nginx 1.9.3                                           14.07.2015
+
+    *) Изменение: дублирующиеся блоки http, mail и stream теперь запрещены.
+
+    *) Добавление: ограничение количества соединений в модуле stream.
+
+    *) Добавление: ограничение скорости в модуле stream.
+
+    *) Исправление: директива zone в блоке upstream не работала на Windows.
+
+    *) Исправление: совместимость с LibreSSL в модуле stream.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: в параметре --builddir в configure.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: директива ssl_stapling_file не работала; ошибка
+       появилась в 1.9.2.
+       Спасибо Faidon Liambotis и Brandon Black.
+
+    *) Исправление: при использовании директивы ssl_stapling в рабочем
+       процессе мог произойти segmentation fault; ошибка появилась в 1.9.2.
+       Спасибо Matthew Baldwin.
+
+
+Изменения в nginx 1.9.2                                           16.06.2015
+
+    *) Добавление: параметр backlog директивы listen в почтовом
+       прокси-сервере и модуле stream.
+
+    *) Добавление: директивы allow и deny в модуле stream.
+
+    *) Добавление: директива proxy_bind в модуле stream.
+
+    *) Добавление: директива proxy_protocol в модуле stream.
+
+    *) Добавление: ключ -T.
+
+    *) Добавление: параметр REQUEST_SCHEME добавлен в стандартные
+       конфигурационные файлы fastcgi.conf, fastcgi_params, scgi_params и
+       uwsgi_params.
+
+    *) Исправление: параметр reuseport директивы listen в модуле stream не
+       работал.
+
+    *) Исправление: OCSP stapling в некоторых случаях мог вернуть устаревший
+       OCSP-ответ.
+
+
+Изменения в nginx 1.9.1                                           26.05.2015
+
+    *) Изменение: теперь протокол SSLv3 по умолчанию запрещён.
+
+    *) Изменение: некоторые давно устаревшие директивы больше не
+       поддерживаются.
+
+    *) Добавление: параметр reuseport директивы listen.
+       Спасибо Yingqi Lu из Intel и Sepherosa Ziehau.
+
+    *) Добавление: переменная $upstream_connect_time.
+
+    *) Исправление: в директиве hash на big-endian платформах.
+
+    *) Исправление: nginx мог не запускаться на некоторых старых версиях
+       Linux; ошибка появилась в 1.7.11.
+
+    *) Исправление: в парсинге IP-адресов.
+       Спасибо Сергею Половко.
+
+
+Изменения в nginx 1.9.0                                           28.04.2015
+
+    *) Изменение: устаревшие методы обработки соединений aio и rtsig больше
+       не поддерживаются.
+
+    *) Добавление: директива zone в блоке upstream.
+
+    *) Добавление: модуль stream.
+
+    *) Добавление: поддержка byte ranges для ответов модуля
+       ngx_http_memcached_module.
+       Спасибо Martin Mlynář.
+
+    *) Добавление: разделяемую память теперь можно использовать на версиях
+       Windows с рандомизацией адресного пространства.
+       Спасибо Сергею Брестеру.
+
+    *) Добавление: директиву error_log теперь можно использовать на уровнях
+       mail и server в почтовом прокси-сервере.
+
+    *) Исправление: параметр proxy_protocol директивы listen не работал,
+       если не был указан в первой директиве listen для данного
+       listen-сокета.
+
+
+Изменения в nginx 1.7.12                                          07.04.2015
+
+    *) Добавление: теперь директива tcp_nodelay работает для SSL-соединений
+       с бэкендами.
+
+    *) Добавление: теперь потоки могут использоваться для чтения заголовков
+       файлов в кэше.
+
+    *) Исправление: в директиве proxy_request_buffering.
+
+    *) Исправление: при использовании потоков на Linux в рабочем процессе
+       мог произойти segmentation fault.
+
+    *) Исправление: в обработке ошибок при использовании директивы
+       ssl_stapling.
+       Спасибо Filipe da Silva.
+
+    *) Исправление: в модуле ngx_http_spdy_module.
+
+
+Изменения в nginx 1.7.11                                          24.03.2015
+
+    *) Изменение: параметр sendfile директивы aio более не нужен; теперь
+       nginx автоматически использует AIO для подгрузки данных для sendfile,
+       если одновременно используются директивы aio и sendfile.
+
+    *) Добавление: экспериментальная поддержка потоков.
+
+    *) Добавление: директивы proxy_request_buffering,
+       fastcgi_request_buffering, scgi_request_buffering и
+       uwsgi_request_buffering.
+
+    *) Добавление: экспериментальное API для обработки тела запроса.
+
+    *) Добавление: проверка клиентских SSL-сертификатов в почтовом
+       прокси-сервере.
+       Спасибо Sven Peter, Franck Levionnois и Filipe Da Silva.
+
+    *) Добавление: уменьшение времени запуска при использовании директивы
+       "hash ... consistent" в блоке upstream.
+       Спасибо Wai Keen Woon.
+
+    *) Добавление: отладочное логгирование в кольцевой буфер в памяти.
+
+    *) Исправление: в обработке хэш-таблиц.
+       Спасибо Chris West.
+
+    *) Исправление: в директиве proxy_cache_revalidate.
+
+    *) Исправление: SSL-соединения могли зависать, если использовался
+       отложенный accept или параметр proxy_protocol директивы listen.
+       Спасибо James Hamlin.
+
+    *) Исправление: переменная $upstream_response_time могла содержать
+       неверное значение при использовании директивы image_filter.
+
+    *) Исправление: в обработке целочисленных переполнений.
+       Спасибо Régis Leroy.
+
+    *) Исправление: при использовании LibreSSL было невозможно включить
+       поддержку SSLv3.
+
+    *) Исправление: при использовании LibreSSL в логах появлялись сообщения
+       "ignoring stale global SSL error ... called a function you should not
+       call".
+
+    *) Исправление: сертификаты, указанные в директивах
+       ssl_client_certificate и ssl_trusted_certificate, использовались для
+       автоматического построения цепочек сертификатов.
+
+
+Изменения в nginx 1.7.10                                          10.02.2015
+
+    *) Добавление: параметр use_temp_path директив proxy_cache_path,
+       fastcgi_cache_path, scgi_cache_path и uwsgi_cache_path.
+
+    *) Добавление: переменная $upstream_header_time.
+
+    *) Изменение: теперь при переполнении диска nginx пытается писать
+       error_log'и только раз в секунду.
+
+    *) Исправление: директива try_files при тестировании каталогов не
+       игнорировала обычные файлы.
+       Спасибо Damien Tournoud.
+
+    *) Исправление: при использовании директивы sendfile на OS X возникали
+       ошибки "sendfile() failed"; ошибка появилась в nginx 1.7.8.
+
+    *) Исправление: в лог могли писаться сообщения "sem_post() failed".
+
+    *) Исправление: nginx не собирался с musl libc.
+       Спасибо James Taylor.
+
+    *) Исправление: nginx не собирался на Tru64 UNIX.
+       Спасибо Goetz T. Fischer.
+
+
+Изменения в nginx 1.7.9                                           23.12.2014
+
+    *) Добавление: директивы proxy_cache, fastcgi_cache, scgi_cache и
+       uwsgi_cache поддерживают переменные.
+
+    *) Добавление: директива expires поддерживает переменные.
+
+    *) Добавление: возможность загрузки секретных ключей с аппаратных
+       устройств с помощью OpenSSL engines.
+       Спасибо Дмитрию Пичулину.
+
+    *) Добавление: директива autoindex_format.
+
+    *) Исправление: ревалидация элементов кэша теперь используется только
+       для ответов с кодами 200 и 206.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: строка "TE" заголовка запроса клиента передавалась на
+       бэкенд при проксировании.
+
+    *) Исправление: директивы proxy_pass, fastcgi_pass, scgi_pass и
+       uwsgi_pass могли неправильно работать внутри блоков if и
+       limit_except.
+
+    *) Исправление: директива proxy_store с параметром "on" игнорировалась,
+       если на предыдущем уровне использовалась директива proxy_store с явно
+       заданным путём к файлам.
+
+    *) Исправление: nginx не собирался с BoringSSL.
+       Спасибо Lukas Tribus.
+
+
+Изменения в nginx 1.7.8                                           02.12.2014
+
+    *) Изменение: теперь строки "If-Modified-Since", "If-Range" и им
+       подобные в заголовке запроса клиента передаются бэкенду при
+       включённом кэшировании, если nginx заранее знает, что не будет
+       кэшировать ответ (например, при использовании proxy_cache_min_uses).
+
+    *) Изменение: теперь после истечения proxy_cache_lock_timeout nginx
+       отправляет запрос на бэкенд без кэширования; новые директивы
+       proxy_cache_lock_age, fastcgi_cache_lock_age, scgi_cache_lock_age и
+       uwsgi_cache_lock_age позволяют указать, через какое время блокировка
+       будет принудительно снята и будет сделана ещё одна попытка
+       закэшировать ответ.
+
+    *) Изменение: директива log_format теперь может использоваться только на
+       уровне http.
+
+    *) Добавление: директивы proxy_ssl_certificate,
+       proxy_ssl_certificate_key, proxy_ssl_password_file,
+       uwsgi_ssl_certificate, uwsgi_ssl_certificate_key и
+       uwsgi_ssl_password_file.
+       Спасибо Piotr Sikora.
+
+    *) Добавление: теперь с помощью X-Accel-Redirect можно перейти в
+       именованный location.
+       Спасибо Toshikuni Fukaya.
+
+    *) Добавление: теперь директива tcp_nodelay работает для
+       SPDY-соединений.
+
+    *) Добавление: новые директивы в скриптах подсветки синтаксиса для vim.
+       Спасибо Peter Wu.
+
+    *) Исправление: nginx игнорировал значение "s-maxage" в строке
+       "Cache-Control" в заголовке ответа бэкенда.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: в модуле ngx_http_spdy_module.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: в директиве ssl_password_file при использовании OpenSSL
+       0.9.8zc, 1.0.0o, 1.0.1j.
+
+    *) Исправление: при использовании директивы post_action в лог писались
+       сообщения "header already sent"; ошибка появилась в nginx 1.5.4.
+
+    *) Исправление: при использовании директивы "postpone_output 0" с
+       SSI-подзапросами в лог могли писаться сообщения "the http output
+       chain is empty".
+
+    *) Исправление: в директиве proxy_cache_lock при использовании
+       SSI-подзапросов.
+       Спасибо Yichun Zhang.
+
+
+Изменения в nginx 1.7.7                                           28.10.2014
+
+    *) Изменение: теперь nginx учитывает при кэшировании строку "Vary" в
+       заголовке ответа бэкенда.
+
+    *) Добавление: директивы proxy_force_ranges, fastcgi_force_ranges,
+       scgi_force_ranges и uwsgi_force_ranges.
+
+    *) Добавление: директивы proxy_limit_rate, fastcgi_limit_rate,
+       scgi_limit_rate и uwsgi_limit_rate.
+
+    *) Добавление: параметр Vary директив proxy_ignore_headers,
+       fastcgi_ignore_headers, scgi_ignore_headers и uwsgi_ignore_headers.
+
+    *) Исправление: последняя часть ответа, полученного от бэкенда при
+       небуферизированном проксировании, могла не отправляться клиенту, если
+       использовались директивы gzip или gunzip.
+
+    *) Исправление: в директиве proxy_cache_revalidate.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: в обработке ошибок.
+       Спасибо Yichun Zhang и Даниилу Бондареву.
+
+    *) Исправление: в директивах proxy_next_upstream_tries и
+       proxy_next_upstream_timeout.
+       Спасибо Feng Gu.
+
+    *) Исправление: nginx/Windows не собирался с MinGW-w64 gcc.
+       Спасибо Kouhei Sutou.
+
+
+Изменения в nginx 1.7.6                                           30.09.2014
+
+    *) Изменение: устаревшая директива limit_zone больше не поддерживается.
+
+    *) Добавление: в директивах limit_conn_zone и limit_req_zone теперь
+       можно использовать комбинации нескольких переменных.
+
+    *) Исправление: при повторной отправке FastCGI-запроса на бэкенд тело
+       запроса могло передаваться неправильно.
+
+    *) Исправление: в логгировании в syslog.
+
+
+Изменения в nginx 1.7.5                                           16.09.2014
+
+    *) Безопасность: при использовании общего для нескольких блоков server
+       разделяемого кэша SSL-сессий или общего ключа для шифрования TLS
+       session tickets было возможно повторно использовать SSL-сессию в
+       контексте другого блока server (CVE-2014-3616).
+       Спасибо Antoine Delignat-Lavaud.
+
+    *) Изменение: директиву stub_status теперь можно указывать без
+       параметров.
+
+    *) Добавление: параметр always директивы add_header.
+
+    *) Добавление: директивы proxy_next_upstream_tries,
+       proxy_next_upstream_timeout, fastcgi_next_upstream_tries,
+       fastcgi_next_upstream_timeout, memcached_next_upstream_tries,
+       memcached_next_upstream_timeout, scgi_next_upstream_tries,
+       scgi_next_upstream_timeout, uwsgi_next_upstream_tries и
+       uwsgi_next_upstream_timeout.
+
+    *) Исправление: в параметре if директивы access_log.
+
+    *) Исправление: в модуле ngx_http_perl_module.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: директива listen почтового прокси-сервера не позволяла
+       указать более двух параметров.
+
+    *) Исправление: директива sub_filter не работала с заменяемой строкой из
+       одного символа.
+
+    *) Исправление: запросы могли зависать, если использовался resolver и в
+       процессе обращения к DNS-серверу происходил таймаут.
+
+    *) Исправление: в модуле ngx_http_spdy_module при использовании
+       совместно с AIO.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если с помощью директивы set изменялись переменные "$http_...",
+       "$sent_http_..." или "$upstream_http_...".
+
+    *) Исправление: в обработке ошибок выделения памяти.
+       Спасибо Markus Linnala и Feng Gu.
+
+
+Изменения в nginx 1.7.4                                           05.08.2014
+
+    *) Безопасность: pipelined-команды не отбрасывались после команды
+       STARTTLS в SMTP прокси-сервере (CVE-2014-3556); ошибка появилась в
+       1.5.6.
+       Спасибо Chris Boulton.
+
+    *) Изменение: экранирование символов в URI теперь использует
+       шестнадцатеричные цифры в верхнем регистре.
+       Спасибо Piotr Sikora.
+
+    *) Добавление: теперь nginx можно собрать с BoringSSL и LibreSSL.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: запросы могли зависать, если использовался resolver и
+       DNS-сервер возвращал некорректный ответ; ошибка появилась в 1.5.8.
+
+    *) Исправление: в модуле ngx_http_spdy_module.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: переменная $uri могла содержать мусор при возврате
+       ошибок с кодом 400.
+       Спасибо Сергею Боброву.
+
+    *) Исправление: в обработке ошибок в директиве proxy_store и в модуле
+       ngx_http_dav_module.
+       Спасибо Feng Gu.
+
+    *) Исправление: при логгировании ошибок в syslog мог происходить
+       segmentation fault; ошибка появилась в 1.7.1.
+
+    *) Исправление: переменные $geoip_latitude, $geoip_longitude,
+       $geoip_dma_code и $geoip_area_code могли не работать.
+       Спасибо Yichun Zhang.
+
+    *) Исправление: в обработке ошибок выделения памяти.
+       Спасибо Tatsuhiko Kubo и Piotr Sikora.
+
+
+Изменения в nginx 1.7.3                                           08.07.2014
+
+    *) Добавление: weak entity tags теперь не удаляются при изменениях
+       ответа, а strong entity tags преобразуются в weak.
+
+    *) Добавление: ревалидация элементов кэша теперь, если это возможно,
+       использует заголовок If-None-Match.
+
+    *) Добавление: директива ssl_password_file.
+
+    *) Исправление: при возврате ответа из кэша заголовок запроса
+       If-None-Match игнорировался, если в ответе не было заголовка
+       Last-Modified.
+
+    *) Исправление: сообщения "peer closed connection in SSL handshake" при
+       соединении с бэкендами логгировались на уровне info вместо error.
+
+    *) Исправление: в модуле ngx_http_dav_module в nginx/Windows.
+
+    *) Исправление: SPDY-соединения могли неожиданно закрываться, если
+       использовалось кэширование.
+
+
+Изменения в nginx 1.7.2                                           17.06.2014
+
+    *) Добавление: директива hash в блоке upstream.
+
+    *) Добавление: дефрагментация свободных блоков разделяемой памяти.
+       Спасибо Wandenberg Peixoto и Yichun Zhang.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовалось значение access_log по умолчанию; ошибка
+       появилась в 1.7.0.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: завершающий слэш ошибочно удалялся из последнего
+       параметра директивы try_files.
+
+    *) Исправление: nginx мог не собираться на OS X.
+
+    *) Исправление: в модуле ngx_http_spdy_module.
+
+
+Изменения в nginx 1.7.1                                           27.05.2014
+
+    *) Добавление: переменные "$upstream_cookie_...".
+
+    *) Добавление: переменная $ssl_client_fingerprint.
+
+    *) Добавление: директивы error_log и access_log теперь поддерживают
+       логгирование в syslog.
+
+    *) Добавление: почтовый прокси-сервер теперь логгирует порт клиента при
+       соединении.
+
+    *) Исправление: утечки памяти при использовании директивы
+       "ssl_stapling".
+       Спасибо Filipe da Silva.
+
+    *) Исправление: директива alias внутри location'а, заданного регулярным
+       выражением, работала неправильно, если использовались директивы if
+       или limit_except.
+
+    *) Исправление: директива charset не ставила кодировку для сжатых
+       ответов бэкендов.
+
+    *) Исправление: директива proxy_pass без URI могла использовать
+       оригинальный запрос после установки переменной $args.
+       Спасибо Yichun Zhang.
+
+    *) Исправление: в работе параметра none директивы smtp_auth; ошибка
+       появилась в 1.5.6.
+       Спасибо Святославу Никольскому.
+
+    *) Исправление: при совместном использовании sub_filter и SSI ответы
+       могли передаваться неверно.
+
+    *) Исправление: nginx не собирался с параметром --with-file-aio на
+       Linux/aarch64.
+
+
+Изменения в nginx 1.7.0                                           24.04.2014
+
+    *) Добавление: проверка SSL-сертификатов бэкендов.
+
+    *) Добавление: поддержка SNI при работе с бэкендами по SSL.
+
+    *) Добавление: переменная $ssl_server_name.
+
+    *) Добавление: параметр if директивы access_log.
+
+
+Изменения в nginx 1.5.13                                          08.04.2014
+
+    *) Изменение: улучшена обработка хэш-таблиц; в директивах
+       variables_hash_max_size и types_hash_bucket_size значения по
+       умолчанию изменены на 1024 и 64 соответственно.
+
+    *) Добавление: модуль ngx_http_mp4_module теперь понимает аргумент end.
+
+    *) Добавление: поддержка byte ranges модулем ngx_http_mp4_module и при
+       сохранении ответов в кэш.
+
+    *) Исправление: теперь nginx не пишет в лог сообщения "ngx_slab_alloc()
+       failed: no memory" при использовании разделяемой памяти в
+       ssl_session_cache и в модуле ngx_http_limit_req_module.
+
+    *) Исправление: директива underscores_in_headers не разрешала
+       подчёркивание в первом символе заголовка.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: cache manager мог нагружать процессор при выходе в
+       nginx/Windows.
+
+    *) Исправление: при использовании ssl_session_cache с параметром shared
+       рабочий процесс nginx/Windows завершался аварийно.
+
+    *) Исправление: в модуле ngx_http_spdy_module.
+
+
+Изменения в nginx 1.5.12                                          18.03.2014
+
+    *) Безопасность: при обработке специально созданного запроса модулем
+       ngx_http_spdy_module могло происходить переполнение буфера в рабочем
+       процессе, что потенциально могло приводить к выполнению произвольного
+       кода (CVE-2014-0133).
+       Спасибо Lucas Molas из Programa STIC, Fundación Dr. Manuel Sadosky,
+       Buenos Aires, Argentina.
+
+    *) Добавление: параметр proxy_protocol в директивах listen и
+       real_ip_header, переменная $proxy_protocol_addr.
+
+    *) Исправление: в директиве fastcgi_next_upstream.
+       Спасибо Lucas Molas.
+
+
+Изменения в nginx 1.5.11                                          04.03.2014
+
+    *) Безопасность: при обработке специально созданного запроса модулем
+       ngx_http_spdy_module на 32-битных платформах могла повреждаться
+       память рабочего процесса, что потенциально могло приводить к
+       выполнению произвольного кода (CVE-2014-0088); ошибка появилась в
+       1.5.10.
+       Спасибо Lucas Molas из Programa STIC, Fundación Dr. Manuel Sadosky,
+       Buenos Aires, Argentina.
+
+    *) Добавление: переменная $ssl_session_reused.
+
+    *) Исправление: директива client_max_body_size могла не работать при
+       чтении тела запроса с использованием chunked transfer encoding;
+       ошибка появилась в 1.3.9.
+       Спасибо Lucas Molas.
+
+    *) Исправление: при проксировании WebSocket-соединений в рабочем
+       процессе мог произойти segmentation fault.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовался модуль ngx_http_spdy_module на 32-битных
+       платформах; ошибка появилась в 1.5.10.
+
+    *) Исправление: значение переменной $upstream_status могло быть
+       неверным, если использовались директивы proxy_cache_use_stale или
+       proxy_cache_revalidate.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если ошибки с кодом 400 с помощью директивы error_page
+       перенаправлялись в именованный location.
+
+    *) Исправление: nginx/Windows не собирался с Visual Studio 2013.
+
+
+Изменения в nginx 1.5.10                                          04.02.2014
+
+    *) Добавление: модуль ngx_http_spdy_module теперь использует протокол
+       SPDY 3.1.
+       Спасибо Automattic и MaxCDN за спонсирование разработки.
+
+    *) Добавление: модуль ngx_http_mp4_module теперь пропускает дорожки,
+       имеющие меньшую длину, чем запрошенная перемотка.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если переменная $ssl_session_id использовалась при логгировании;
+       ошибка появилась в 1.5.9.
+
+    *) Исправление: переменные $date_local и $date_gmt использовали неверный
+       формат вне модуля ngx_http_ssi_filter_module.
+
+    *) Исправление: клиентские соединения могли сразу закрываться, если
+       использовался отложенный accept; ошибка появилась в 1.3.15.
+
+    *) Исправление: сообщения "getsockopt(TCP_FASTOPEN) ... failed"
+       записывались в лог в процессе обновления исполняемого файла на Linux;
+       ошибка появилась в 1.5.8.
+       Спасибо Piotr Sikora.
+
+
+Изменения в nginx 1.5.9                                           22.01.2014
+
+    *) Изменение: теперь в заголовке X-Accel-Redirect nginx ожидает
+       закодированный URI.
+
+    *) Добавление: директива ssl_buffer_size.
+
+    *) Добавление: директиву limit_rate теперь можно использовать для
+       ограничения скорости передачи ответов клиенту в SPDY-соединениях.
+
+    *) Добавление: директива spdy_chunk_size.
+
+    *) Добавление: директива ssl_session_tickets.
+       Спасибо Dirkjan Bussink.
+
+    *) Исправление: переменная $ssl_session_id содержала всю сессию в
+       сериализованном виде вместо её идентификатора.
+       Спасибо Ivan Ristić.
+
+    *) Исправление: nginx неправильно обрабатывал закодированный символ "?"
+       в команде SSI include.
+
+    *) Исправление: модуль ngx_http_dav_module не раскодировал целевой URI
+       при обработке методов COPY и MOVE.
+
+    *) Исправление: resolver не понимал доменные имена с точкой в конце.
+       Спасибо Yichun Zhang.
+
+    *) Исправление: при проксировании в логах могли появляться сообщения
+       "zero size buf in output"; ошибка появилась в 1.3.9.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовался модуль ngx_http_spdy_module.
+
+    *) Исправление: при использовании методов обработки соединений select,
+       poll и /dev/poll проксируемые WebSocket-соединения могли зависать
+       сразу после открытия.
+
+    *) Исправление: директива xclient почтового прокси-сервера некорректно
+       передавала IPv6-адреса.
+
+
+Изменения в nginx 1.5.8                                           17.12.2013
+
+    *) Добавление: теперь resolver поддерживает IPv6.
+
+    *) Добавление: директива listen поддерживает параметр fastopen.
+       Спасибо Mathew Rodley.
+
+    *) Добавление: поддержка SSL в модуле ngx_http_uwsgi_module.
+       Спасибо Roberto De Ioris.
+
+    *) Добавление: скрипты подсветки синтаксиса для vim добавлены в contrib.
+       Спасибо Evan Miller.
+
+    *) Исправление: при чтении тела запроса с использованием chunked
+       transfer encoding по SSL-соединению мог произойти таймаут.
+
+    *) Исправление: директива master_process работала неправильно в
+       nginx/Windows.
+
+    *) Исправление: параметр setfib директивы listen мог не работать.
+
+    *) Исправление: в модуле ngx_http_spdy_module.
+
+
+Изменения в nginx 1.5.7                                           19.11.2013
+
+    *) Безопасность: символ, следующий за незакодированным пробелом в строке
+       запроса, обрабатывался неправильно (CVE-2013-4547); ошибка появилась
+       в 0.8.41.
+       Спасибо Ivan Fratric из Google Security Team.
+
+    *) Изменение: уровень логгирования ошибок auth_basic об отсутствии
+       пароля понижен с уровня error до info.
+
+    *) Добавление: директивы proxy_cache_revalidate,
+       fastcgi_cache_revalidate, scgi_cache_revalidate и
+       uwsgi_cache_revalidate.
+
+    *) Добавление: директива ssl_session_ticket_key.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: директива "add_header Cache-Control ''" добавляла строку
+       заголовка ответа "Cache-Control" с пустым значением.
+
+    *) Исправление: директива "satisfy any" могла вернуть ошибку 403 вместо
+       401 при использовании директив auth_request и auth_basic.
+       Спасибо Jan Marc Hoffmann.
+
+    *) Исправление: параметры accept_filter и deferred директивы listen
+       игнорировались для listen-сокетов, создаваемых в процессе обновления
+       исполняемого файла.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: часть данных, полученных от бэкенда при
+       небуферизированном проксировании, могла не отправляться клиенту
+       сразу, если использовались директивы gzip или gunzip.
+       Спасибо Yichun Zhang.
+
+    *) Исправление: в обработке ошибок в модуле
+       ngx_http_gunzip_filter_module.
+
+    *) Исправление: ответы могли зависать, если использовался модуль
+       ngx_http_spdy_module и директива auth_request.
+
+    *) Исправление: утечки памяти в nginx/Windows.
+
+
+Изменения в nginx 1.5.6                                           01.10.2013
+
+    *) Добавление: директива fastcgi_buffering.
+
+    *) Добавление: директивы proxy_ssl_protocols и proxy_ssl_ciphers.
+       Спасибо Piotr Sikora.
+
+    *) Добавление: оптимизация SSL handshake при использовании длинных
+       цепочек сертификатов.
+
+    *) Добавление: почтовый прокси-сервер поддерживает SMTP pipelining.
+
+    *) Исправление: в модуле ngx_http_auth_basic_module при использовании
+       метода шифрования паролей "$apr1$".
+       Спасибо Markus Linnala.
+
+    *) Исправление: на MacOSX, Cygwin и nginx/Windows для обработки запроса
+       мог использоваться неверный location, если для задания location'ов
+       использовались символы разных регистров.
+
+    *) Исправление: автоматическое перенаправление с добавлением
+       завершающего слэша для проксированных location'ов могло не работать.
+
+    *) Исправление: в почтовом прокси-сервере.
+
+    *) Исправление: в модуле ngx_http_spdy_module.
+
+
+Изменения в nginx 1.5.5                                           17.09.2013
+
+    *) Изменение: теперь nginx по умолчанию использует HTTP/1.0, если точно
+       определить протокол не удалось.
+
+    *) Добавление: директива disable_symlinks теперь использует O_PATH на
+       Linux.
+
+    *) Добавление: для определения того, что клиент закрыл соединение, при
+       использовании метода epoll теперь используются события EPOLLRDHUP.
+
+    *) Исправление: в директиве valid_referers при использовании параметра
+       server_names.
+
+    *) Исправление: переменная $request_time не работала в nginx/Windows.
+
+    *) Исправление: в директиве image_filter.
+       Спасибо Lanshun Zhou.
+
+    *) Исправление: совместимость с OpenSSL 1.0.1f.
+       Спасибо Piotr Sikora.
+
+
+Изменения в nginx 1.5.4                                           27.08.2013
+
+    *) Изменение: MIME-тип для расширения js изменён на
+       "application/javascript"; значение по умолчанию директивы
+       charset_types изменено соответственно.
+
+    *) Изменение: теперь директива image_filter с параметром size возвращает
+       ответ с MIME-типом "application/json".
+
+    *) Добавление: модуль ngx_http_auth_request_module.
+
+    *) Исправление: на старте или во время переконфигурации мог произойти
+       segmentation fault, если использовалась директива try_files с пустым
+       параметром.
+
+    *) Исправление: утечки памяти при использовании в директивах root и
+       auth_basic_user_file относительных путей, заданных с помощью
+       переменных.
+
+    *) Исправление: директива valid_referers неправильно выполняла
+       регулярные выражения, если заголовок Referer начинался с "https://".
+       Спасибо Liangbin Li.
+
+    *) Исправление: ответы могли зависать, если использовались подзапросы и
+       при обработке подзапроса происходила ошибка во время SSL handshake с
+       бэкендом.
+       Спасибо Aviram Cohen.
+
+    *) Исправление: в модуле ngx_http_autoindex_module.
+
+    *) Исправление: в модуле ngx_http_spdy_module.
+
+
+Изменения в nginx 1.5.3                                           30.07.2013
+
+    *) Изменение во внутреннем API: теперь при небуферизированной работе с
+       бэкендами u->length по умолчанию устанавливается в -1.
+
+    *) Изменение: теперь при получении неполного ответа от бэкенда nginx
+       отправляет полученную часть ответа, после чего закрывает соединение с
+       клиентом.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовался модуль ngx_http_spdy_module и директива
+       client_body_in_file_only.
+
+    *) Исправление: параметр so_keepalive директивы listen мог работать
+       некорректно на DragonFlyBSD.
+       Спасибо Sepherosa Ziehau.
+
+    *) Исправление: в модуле ngx_http_xslt_filter_module.
+
+    *) Исправление: в модуле ngx_http_sub_filter_module.
+
+
+Изменения в nginx 1.5.2                                           02.07.2013
+
+    *) Добавление: теперь можно использовать несколько директив error_log.
+
+    *) Исправление: метод $r->header_in() встроенного перла не возвращал
+       значения строк "Cookie" и "X-Forwarded-For" из заголовка запроса;
+       ошибка появилась в 1.3.14.
+
+    *) Исправление: в модуле ngx_http_spdy_module.
+       Спасибо Jim Radford.
+
+    *) Исправление: nginx не собирался на Linux при использовании x32 ABI.
+       Спасибо Сергею Иванцову.
+
+
+Изменения в nginx 1.5.1                                           04.06.2013
+
+    *) Добавление: директивы ssi_last_modified, sub_filter_last_modified и
+       xslt_last_modified.
+       Спасибо Алексею Колпакову.
+
+    *) Добавление: параметр http_403 в директивах proxy_next_upstream,
+       fastcgi_next_upstream, scgi_next_upstream и uwsgi_next_upstream.
+
+    *) Добавление: директивы allow и deny теперь поддерживают unix domain
+       сокеты.
+
+    *) Исправление: nginx не собирался с модулем ngx_mail_ssl_module, но без
+       модуля ngx_http_ssl_module; ошибка появилась в 1.3.14.
+
+    *) Исправление: в директиве proxy_set_body.
+       Спасибо Lanshun Zhou.
+
+    *) Исправление: в директиве lingering_time.
+       Спасибо Lanshun Zhou.
+
+    *) Исправление: параметр fail_timeout директивы server в блоке upstream
+       мог не работать, если использовался параметр max_fails; ошибка
+       появилась в 1.3.0.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовалась директива ssl_stapling.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: в почтовом прокси-сервере.
+       Спасибо Filipe Da Silva.
+
+    *) Исправление: nginx/Windows мог перестать принимать соединения, если
+       использовалось несколько рабочих процессов.
+
+
+Изменения в nginx 1.5.0                                           07.05.2013
+
+    *) Безопасность: при обработке специально созданного запроса мог
+       перезаписываться стек рабочего процесса, что могло приводить к
+       выполнению произвольного кода (CVE-2013-2028); ошибка появилась в
+       1.3.9.
+       Спасибо Greg MacManus, iSIGHT Partners Labs.
+
+
+Изменения в nginx 1.4.0                                           24.04.2013
+
+    *) Исправление: nginx не собирался с модулем ngx_http_perl_module, если
+       использовался параметр --with-openssl; ошибка появилась в 1.3.16.
+
+    *) Исправление: в работе с телом запроса из модуля ngx_http_perl_module;
+       ошибка появилась в 1.3.9.
+
+
+Изменения в nginx 1.3.16                                          16.04.2013
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовались подзапросы; ошибка появилась в 1.3.9.
+
+    *) Исправление: директива tcp_nodelay вызывала ошибку при проксировании
+       WebSocket-соединений в unix domain сокет.
+
+    *) Исправление: переменная $upstream_response_length возвращала значение
+       "0", если не использовалась буферизация.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: в методах обработки соединений eventport и /dev/poll.
+
+
+Изменения в nginx 1.3.15                                          26.03.2013
+
+    *) Изменение: открытие и закрытие соединения без отправки в нём
+       каких-либо данных больше не записывается в access_log с кодом ошибки
+       400.
+
+    *) Добавление: модуль ngx_http_spdy_module.
+       Спасибо Automattic за спонсирование разработки.
+
+    *) Добавление: директивы limit_req_status и limit_conn_status.
+       Спасибо Nick Marden.
+
+    *) Добавление: директива image_filter_interlace.
+       Спасибо Ивану Боброву.
+
+    *) Добавление: переменная $connections_waiting в модуле
+       ngx_http_stub_status_module.
+
+    *) Добавление: теперь почтовый прокси-сервер поддерживает IPv6-бэкенды.
+
+    *) Исправление: при повторной отправке запроса на бэкенд тело запроса
+       могло передаваться неправильно; ошибка появилась в 1.3.9.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: в директиве client_body_in_file_only; ошибка появилась в
+       1.3.9.
+
+    *) Исправление: ответы могли зависать, если использовались подзапросы и
+       при обработке подзапроса происходила DNS-ошибка.
+       Спасибо Lanshun Zhou.
+
+    *) Исправление: в процедуре учёта использования бэкендов.
+
+
+Изменения в nginx 1.3.14                                          05.03.2013
+
+    *) Добавление: переменные $connections_active, $connections_reading и
+       $connections_writing в модуле ngx_http_stub_status_module.
+
+    *) Добавление: поддержка WebSocket-соединений в модулях
+       ngx_http_uwsgi_module и ngx_http_scgi_module.
+
+    *) Исправление: в обработке виртуальных серверов при использовании SNI.
+
+    *) Исправление: при использовании директивы "ssl_session_cache shared"
+       новые сессии могли не сохраняться, если заканчивалось место в
+       разделяемой памяти.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: несколько заголовков X-Forwarded-For обрабатывались
+       неправильно.
+       Спасибо Neal Poole за спонсирование разработки.
+
+    *) Исправление: в модуле ngx_http_mp4_module.
+       Спасибо Gernot Vormayr.
+
+
+Изменения в nginx 1.3.13                                          19.02.2013
+
+    *) Изменение: теперь для сборки по умолчанию используется компилятор с
+       именем "cc".
+
+    *) Добавление: поддержка проксирования WebSocket-соединений.
+       Спасибо Apcera и CloudBees за спонсирование разработки.
+
+    *) Добавление: директива auth_basic_user_file поддерживает шифрование
+       паролей методом "{SHA}".
+       Спасибо Louis Opter.
+
+
+Изменения в nginx 1.3.12                                          05.02.2013
+
+    *) Добавление: директивы proxy_bind, fastcgi_bind, memcached_bind,
+       scgi_bind и uwsgi_bind поддерживают переменные.
+
+    *) Добавление: переменные $pipe, $request_length, $time_iso8601 и
+       $time_local теперь можно использовать не только в директиве
+       log_format.
+       Спасибо Kiril Kalchev.
+
+    *) Добавление: поддержка IPv6 в модуле ngx_http_geoip_module.
+       Спасибо Gregor Kališnik.
+
+    *) Исправление: директива proxy_method работала неверно, если была
+       указана на уровне http.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовался resolver и метод poll.
+
+    *) Исправление: nginx мог нагружать процессор во время SSL handshake с
+       бэкендом при использовании методов обработки соединений select, poll
+       и /dev/poll.
+
+    *) Исправление: ошибка "[crit] SSL_write() failed (SSL:)".
+
+    *) Исправление: в директиве client_body_in_file_only; ошибка появилась в
+       1.3.9.
+
+    *) Исправление: в директиве fastcgi_keep_conn.
+
+
+Изменения в nginx 1.3.11                                          10.01.2013
+
+    *) Исправление: при записи в лог мог происходить segmentation fault;
+       ошибка появилась в 1.3.10.
+
+    *) Исправление: директива proxy_pass не работала с IP-адресами без
+       явного указания порта; ошибка появилась в 1.3.10.
+
+    *) Исправление: на старте или во время переконфигурации происходил
+       segmentation fault, если директива keepalive была указана несколько
+       раз в одном блоке upstream.
+
+    *) Исправление: параметр default директивы geo не определял значение по
+       умолчанию для IPv6-адресов.
+
+
+Изменения в nginx 1.3.10                                          25.12.2012
+
+    *) Изменение: для указанных в конфигурационном файле доменных имён
+       теперь используются не только IPv4, но и IPv6 адреса.
+
+    *) Изменение: теперь при использовании директивы include с маской на
+       Unix-системах включаемые файлы сортируются в алфавитном порядке.
+
+    *) Изменение: директива add_header добавляет строки в ответы с кодом
+       201.
+
+    *) Добавление: директива geo теперь поддерживает IPv6 адреса в формате
+       CIDR.
+
+    *) Добавление: параметры flush и gzip в директиве access_log.
+
+    *) Добавление: директива auth_basic поддерживает переменные.
+
+    *) Исправление: nginx в некоторых случаях не собирался с модулем
+       ngx_http_perl_module.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовался модуль ngx_http_xslt_module.
+
+    *) Исправление: nginx мог не собираться на MacOSX.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: при использовании директивы limit_rate с большими
+       значениями скорости на 32-битных системах ответ мог возвращаться не
+       целиком.
+       Спасибо Алексею Антропову.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовалась директива if.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: ответ "100 Continue" выдавался вместе с ответом "413
+       Request Entity Too Large".
+
+    *) Исправление: директивы image_filter, image_filter_jpeg_quality и
+       image_filter_sharpen могли наследоваться некорректно.
+       Спасибо Ивану Боброву.
+
+    *) Исправление: при использовании директивы auth_basic под Linux могли
+       возникать ошибки "crypt_r() failed".
+
+    *) Исправление: в обработке backup-серверов.
+       Спасибо Thomas Chen.
+
+    *) Исправление: при проксировании HEAD-запросов мог возвращаться
+       некорректный ответ, если использовалась директива gzip.
+
+
+Изменения в nginx 1.3.9                                           27.11.2012
+
+    *) Добавление: поддержка chunked transfer encoding при получении тела
+       запроса.
+
+    *) Добавление: переменные $request_time и $msec теперь можно
+       использовать не только в директиве log_format.
+
+    *) Исправление: cache manager и cache loader могли не запускаться, если
+       использовалось более 512 listen-сокетов.
+
+    *) Исправление: в модуле ngx_http_dav_module.
+
+
+Изменения в nginx 1.3.8                                           30.10.2012
+
+    *) Добавление: параметр optional_no_ca директивы ssl_verify_client.
+       Спасибо Михаилу Казанцеву и Eric O'Connor.
+
+    *) Добавление: переменные $bytes_sent, $connection и
+       $connection_requests теперь можно использовать не только в директиве
+       log_format.
+       Спасибо Benjamin Grössing.
+
+    *) Добавление: параметр auto директивы worker_processes.
+
+    *) Исправление: сообщения "cache file ... has md5 collision".
+
+    *) Исправление: в модуле ngx_http_gunzip_filter_module.
+
+    *) Исправление: в директиве ssl_stapling.
+
+
+Изменения в nginx 1.3.7                                           02.10.2012
+
+    *) Добавление: поддержка OCSP stapling.
+       Спасибо Comodo, DigiCert и GlobalSign за спонсирование разработки.
+
+    *) Добавление: директива ssl_trusted_certificate.
+
+    *) Добавление: теперь resolver случайным образом меняет порядок
+       возвращаемых закэшированных адресов.
+       Спасибо Антону Жулину.
+
+    *) Исправление: совместимость с OpenSSL 0.9.7.
+
+
+Изменения в nginx 1.3.6                                           12.09.2012
+
+    *) Добавление: модуль ngx_http_gunzip_filter_module.
+
+    *) Добавление: директива memcached_gzip_flag.
+
+    *) Добавление: параметр always директивы gzip_static.
+
+    *) Исправление: в директиве "limit_req"; ошибка появилась в 1.1.14.
+       Спасибо Charles Chen.
+
+    *) Исправление: nginx не собирался gcc 4.7 с оптимизацией -O2 если
+       использовался параметр --with-ipv6.
+
+
+Изменения в nginx 1.3.5                                           21.08.2012
+
+    *) Изменение: модуль ngx_http_mp4_module больше не отфильтровывает
+       дорожки в форматах, отличных от H.264 и AAC.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если в директиве map в качестве значений использовались переменные.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault при
+       использовании директивы geo с параметром ranges, но без параметра
+       default; ошибка появилась в 0.8.43.
+       Спасибо Zhen Chen и Weibin Yao.
+
+    *) Исправление: в обработке параметра командной строки -p.
+
+    *) Исправление: в почтовом прокси-сервере.
+
+    *) Исправление: незначительных потенциальных ошибок.
+       Спасибо Coverity.
+
+    *) Исправление: nginx/Windows не собирался с Visual Studio 2005 Express.
+       Спасибо HAYASHI Kentaro.
+
+
+Изменения в nginx 1.3.4                                           31.07.2012
+
+    *) Изменение: теперь на слушающих IPv6-сокетах параметр ipv6only включён
+       по умолчанию.
+
+    *) Добавление: поддержка компилятора Clang.
+
+    *) Исправление: могли создаваться лишние слушающие сокеты.
+       Спасибо Роману Одайскому.
+
+    *) Исправление: nginx/Windows мог нагружать процессор, если при запуске
+       рабочего процесса происходила ошибка.
+       Спасибо Ricardo Villalobos Guevara.
+
+    *) Исправление: директивы proxy_pass_header, fastcgi_pass_header,
+       scgi_pass_header, uwsgi_pass_header, proxy_hide_header,
+       fastcgi_hide_header, scgi_hide_header и uwsgi_hide_header могли
+       наследоваться некорректно.
+
+
+Изменения в nginx 1.3.3                                           10.07.2012
+
+    *) Добавление: поддержка entity tags и директива etag.
+
+    *) Исправление: при использовании директивы map с параметром hostnames
+       не игнорировалась конечная точка в исходном значении.
+
+    *) Исправление: для обработки запроса мог использоваться неверный
+       location, если переход в именованный location происходил после
+       изменения URI с помощью директивы rewrite.
+
+
+Изменения в nginx 1.3.2                                           26.06.2012
+
+    *) Изменение: параметр single директивы keepalive теперь игнорируется.
+
+    *) Изменение: сжатие SSL теперь отключено в том числе при использовании
+       OpenSSL старее 1.0.0.
+
+    *) Добавление: директиву "ip_hash" теперь можно использовать для
+       балансировки IPv6 клиентов.
+
+    *) Добавление: переменную $status теперь можно использовать не только в
+       директиве log_format.
+
+    *) Исправление: при завершении рабочего процесса мог произойти
+       segmentation fault, если использовалась директива resolver.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовался модуль ngx_http_mp4_module.
+
+    *) Исправление: в модуле ngx_http_mp4_module.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовались конфликтующие имена серверов с масками.
+
+    *) Исправление: на платформе ARM nginx мог аварийно завершаться по
+       сигналу SIGBUS.
+
+    *) Исправление: во время переконфигурации на HP-UX в лог записывался
+       alert "sendmsg() failed (9: Bad file number)".
+
+
+Изменения в nginx 1.3.1                                           05.06.2012
+
+    *) Безопасность: теперь nginx/Windows игнорирует точку в конце
+       компонента URI и не разрешает URI, содержащие последовательность
+       ":$".
+       Спасибо Владимиру Кочеткову, Positive Research Center.
+
+    *) Добавление: директивы proxy_pass, fastcgi_pass, scgi_pass, uwsgi_pass
+       и директива server в блоке upstream теперь поддерживают IPv6-адреса.
+
+    *) Добавление: в директиве resolver теперь можно указывать порт и
+       задавать IPv6-адреса DNS-серверов.
+
+    *) Добавление: директива least_conn в блоке upstream.
+
+    *) Добавление: при использовании директивы ip_hash теперь можно задавать
+       веса серверов.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовалась директива image_filter; ошибка появилась в 1.3.0.
+
+    *) Исправление: nginx не собирался с модулем ngx_cpp_test_module; ошибка
+       появилась в 1.1.12.
+
+    *) Исправление: доступ к переменным из SSI и встроенного перла мог не
+       работать после переконфигурации.
+       Спасибо Yichun Zhang.
+
+    *) Исправление: в модуле ngx_http_xslt_filter_module.
+       Спасибо Kuramoto Eiji.
+
+    *) Исправление: утечки памяти при использовании переменной $geoip_org.
+       Спасибо Денису Латыпову.
+
+    *) Исправление: в директивах proxy_cookie_domain и proxy_cookie_path.
+
+
+Изменения в nginx 1.3.0                                           15.05.2012
+
+    *) Добавление: директива debug_connection теперь поддерживает
+       IPv6-адреса и параметр "unix:".
+
+    *) Добавление: директива set_real_ip_from и параметр proxy директивы geo
+       теперь поддерживают IPv6-адреса.
+
+    *) Добавление: директивы real_ip_recursive, geoip_proxy и
+       geoip_proxy_recursive.
+
+    *) Добавление: параметр proxy_recursive директивы geo.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовалась директива resolver.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовались директивы fastcgi_pass, scgi_pass или uwsgi_pass
+       и бэкенд возвращал некорректный ответ.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовалась директива rewrite и в новых аргументах запроса в
+       строке замены использовались переменные.
+
+    *) Исправление: nginx мог нагружать процессор, если было достигнуто
+       ограничение на количество открытых файлов.
+
+    *) Исправление: при использовании директивы proxy_next_upstream с
+       параметром http_404 nginx мог бесконечно перебирать бэкенды, если в
+       блоке upstream был хотя бы один сервер с флагом backup.
+
+    *) Исправление: при использовании директивы ip_hash установка параметра
+       down директивы server могла приводить к ненужному перераспределению
+       клиентов между бэкендами.
+
+    *) Исправление: утечки сокетов.
+       Спасибо Yichun Zhang.
+
+    *) Исправление: в модуле ngx_http_fastcgi_module.
+
+
+Изменения в nginx 1.2.0                                           23.04.2012
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовалась директива try_files; ошибка появилась в 1.1.19.
+
+    *) Исправление: ответ мог быть передан не полностью, если использовалось
+       больше IOV_MAX буферов.
+
+    *) Исправление: в работе параметра crop директивы image_filter.
+       Спасибо Maxim Bublis.
+
+
+Изменения в nginx 1.1.19                                          12.04.2012
+
+    *) Безопасность: при обработке специально созданного mp4 файла модулем
+       ngx_http_mp4_module могли перезаписываться области памяти рабочего
+       процесса, что могло приводить к выполнению произвольного кода
+       (CVE-2012-2089).
+       Спасибо Matthew Daley.
+
+    *) Исправление: nginx/Windows мог завершаться аварийно.
+       Спасибо Vincent Lee.
+
+    *) Исправление: nginx нагружал процессор, если все серверы в upstream'е
+       были помечены флагом backup.
+
+    *) Исправление: директивы allow и deny могли наследоваться некорректно,
+       если в них использовались IPv6 адреса.
+
+    *) Исправление: директивы modern_browser и ancient_browser могли
+       наследоваться некорректно.
+
+    *) Исправление: таймауты могли работать некорректно на Solaris/SPARC.
+
+    *) Исправление: в модуле ngx_http_mp4_module.
+
+
+Изменения в nginx 1.1.18                                          28.03.2012
+
+    *) Изменение: теперь keepalive соединения не запрещены для Safari по
+       умолчанию.
+
+    *) Добавление: переменная $connection_requests.
+
+    *) Добавление: переменные $tcpinfo_rtt, $tcpinfo_rttvar,
+       $tcpinfo_snd_cwnd и $tcpinfo_rcv_space.
+
+    *) Добавление: директива worker_cpu_affinity теперь работает на FreeBSD.
+
+    *) Добавление: директивы xslt_param и xslt_string_param.
+       Спасибо Samuel Behan.
+
+    *) Исправление: в configure.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: в модуле ngx_http_xslt_filter_module.
+
+    *) Исправление: nginx не собирался на Debian GNU/Hurd.
+
+
+Изменения в nginx 1.1.17                                          15.03.2012
+
+    *) Безопасность: содержимое ранее освобождённой памяти могло быть
+       отправлено клиенту, если бэкенд возвращал специально созданный ответ.
+       Спасибо Matthew Daley.
+
+    *) Исправление: при использовании встроенного перла из SSI.
+       Спасибо Matthew Daley.
+
+    *) Исправление: в модуле ngx_http_uwsgi_module.
+
+
+Изменения в nginx 1.1.16                                          29.02.2012
+
+    *) Изменение: ограничение на количество одновременных подзапросов
+       поднято до 200.
+
+    *) Добавление: параметр from в директиве disable_symlinks.
+
+    *) Добавление: директивы return и error_page теперь могут использоваться
+       для возврата перенаправлений с кодом 307.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовалась директива resolver и на глобальном уровне не была
+       задана директива error_log.
+       Спасибо Роману Арутюняну.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовались директивы "proxy_http_version 1.1" или
+       "fastcgi_keep_conn on".
+
+    *) Исправление: утечек памяти.
+       Спасибо Lanshun Zhou.
+
+    *) Исправление: в директиве disable_symlinks.
+
+    *) Исправление: при использовании ZFS размер кэша на диске мог считаться
+       некорректно; ошибка появилась в 1.0.1.
+
+    *) Исправление: nginx не собирался компилятором icc 12.1.
+
+    *) Исправление: nginx не собирался gcc на Solaris; ошибка появилась в
+       1.1.15.
+
+
+Изменения в nginx 1.1.15                                          15.02.2012
+
+    *) Добавление: директива disable_symlinks.
+
+    *) Добавление: директивы proxy_cookie_domain и proxy_cookie_path.
+
+    *) Исправление: nginx мог некорректно сообщать об ошибке "upstream
+       prematurely closed connection" вместо "upstream sent too big header".
+       Спасибо Feibo Li.
+
+    *) Исправление: nginx не собирался с модулем ngx_http_perl_module, если
+       использовался параметр --with-openssl.
+
+    *) Исправление: количество внутренних перенаправлений в именованные
+       location'ы не ограничивалось.
+
+    *) Исправление: вызов $r->flush() несколько раз подряд мог приводить к
+       ошибкам в модуле ngx_http_gzip_filter_module.
+
+    *) Исправление: при использовании директивы proxy_store с
+       SSI-подзапросами временные файлы могли не удаляться.
+
+    *) Исправление: в некоторых случаях некэшируемые переменные (такие, как
+       $args) возвращали старое пустое закэшированное значение.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если одновременно создавалось слишком много SSI-подзапросов; ошибка
+       появилась в 0.7.25.
+
+
+Изменения в nginx 1.1.14                                          30.01.2012
+
+    *) Добавление: теперь можно указать несколько ограничений limit_req
+       одновременно.
+
+    *) Исправление: в обработке ошибок при соединении с бэкендом.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: в обработке ошибок при использовании AIO на FreeBSD.
+
+    *) Исправление: в инициализации библиотеки OpenSSL.
+
+    *) Исправление: директивы proxy_redirect могли наследоваться
+       некорректно.
+
+    *) Исправление: утечки памяти при переконфигурации, если использовалась
+       директива pcre_jit.
+
+
+Изменения в nginx 1.1.13                                          16.01.2012
+
+    *) Добавление: параметры TLSv1.1 и TLSv1.2 в директиве ssl_protocols.
+
+    *) Исправление: параметры директивы limit_req наследовались некорректно;
+       ошибка появилась в 1.1.12.
+
+    *) Исправление: директива proxy_redirect некорректно обрабатывала
+       заголовок Refresh при использовании регулярных выражений.
+
+    *) Исправление: директива proxy_cache_use_stale с параметром error не
+       возвращала ответ из кэша, если все бэкенды были признаны
+       неработающими.
+
+    *) Исправление: директива worker_cpu_affinity могла не работать.
+
+    *) Исправление: nginx не собирался на Solaris; ошибка появилась в
+       1.1.12.
+
+    *) Исправление: в модуле ngx_http_mp4_module.
+
+
+Изменения в nginx 1.1.12                                          26.12.2011
+
+    *) Изменение: после перенаправления запроса с помощью директивы
+       error_page директива proxy_pass без URI теперь использует изменённый
+       URI.
+       Спасибо Lanshun Zhou.
+
+    *) Добавление: директивы proxy/fastcgi/scgi/uwsgi_cache_lock,
+       proxy/fastcgi/scgi/uwsgi_cache_lock_timeout.
+
+    *) Добавление: директива pcre_jit.
+
+    *) Добавление: SSI команда if поддерживает выделения в регулярных
+       выражениях.
+
+    *) Исправление: SSI команда if не работала внутри команды block.
+
+    *) Исправление: директивы limit_conn_log_level и limit_req_log_level
+       могли не работать.
+
+    *) Исправление: директива limit_rate не позволяла передавать на полной
+       скорости, даже если был указан очень большой лимит.
+
+    *) Исправление: директива sendfile_max_chunk не работала, если
+       использовалась директива limit_rate.
+
+    *) Исправление: если в директиве proxy_pass использовались переменные и
+       не был указан URI, всегда использовался URI исходного запроса.
+
+    *) Исправление: после перенаправления запроса с помощью директивы
+       try_files директива proxy_pass без URI могла использовать URI
+       исходного запроса.
+       Спасибо Lanshun Zhou.
+
+    *) Исправление: в модуле ngx_http_scgi_module.
+
+    *) Исправление: в модуле ngx_http_mp4_module.
+
+    *) Исправление: nginx не собирался на Solaris; ошибка появилась в 1.1.9.
+
+
+Изменения в nginx 1.1.11                                          12.12.2011
+
+    *) Добавление: параметр so_keepalive в директиве listen.
+       Спасибо Всеволоду Стахову.
+
+    *) Добавление: параметр if_not_empty в директивах
+       fastcgi/scgi/uwsgi_param.
+
+    *) Добавление: переменная $https.
+
+    *) Добавление: директива proxy_redirect поддерживает переменные в первом
+       параметре.
+
+    *) Добавление: директива proxy_redirect поддерживает регулярные
+       выражения.
+
+    *) Исправление: переменная $sent_http_cache_control могла содержать
+       неверное значение при использовании директивы expires.
+       Спасибо Yichun Zhang.
+
+    *) Исправление: директива read_ahead могла не работать при использовании
+       совместно с try_files и open_file_cache.
+
+    *) Исправление: если в параметре inactive директивы proxy_cache_path
+       было указано малое время, в рабочем процессе мог произойти
+       segmentation fault.
+
+    *) Исправление: ответы из кэша могли зависать.
+
+
+Изменения в nginx 1.1.10                                          30.11.2011
+
+    *) Исправление: при использовании AIO на Linux в рабочем процессе
+       происходил segmentation fault; ошибка появилась в 1.1.9.
+
+
+Изменения в nginx 1.1.9                                           28.11.2011
+
+    *) Изменение: теперь двойные кавычки экранируется при выводе
+       SSI-командой echo.
+       Спасибо Зауру Абасмирзоеву.
+
+    *) Добавление: параметр valid в директиве resolver. По умолчанию теперь
+       используется TTL, возвращённый DNS-сервером.
+       Спасибо Кириллу Коринскому.
+
+    *) Исправление: nginx мог перестать отвечать, если рабочий процесс
+       завершался аварийно.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовалось SNI; ошибка появилась в 1.1.2.
+
+    *) Исправление: в директиве keepalive_disable; ошибка появилась в 1.1.8.
+       Спасибо Александру Усову.
+
+    *) Исправление: сигнал SIGWINCH переставал работать после первого
+       обновления исполняемого файла; ошибка появилась в 1.1.1.
+
+    *) Исправление: теперь ответы бэкендов, длина которых не соответствует
+       заголовку Content-Length, не кэширутся.
+
+    *) Исправление: в директиве scgi_param при использовании составных
+       параметров.
+
+    *) Исправление: в методе epoll.
+       Спасибо Yichun Zhang.
+
+    *) Исправление: в модуле ngx_http_flv_module.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: в модуле ngx_http_mp4_module.
+
+    *) Исправление: теперь nginx понимает IPv6-адреса в строке запроса и в
+       заголовке Host.
+
+    *) Исправление: директивы add_header и expires не работали для ответов с
+       кодом 206, если запрос проксировался.
+
+    *) Исправление: nginx не собирался на FreeBSD 10.
+
+    *) Исправление: nginx не собирался на AIX.
+
+
+Изменения в nginx 1.1.8                                           14.11.2011
+
+    *) Изменение: модуль ngx_http_limit_zone_module переименован в
+       ngx_http_limit_conn_module.
+
+    *) Изменение: директива limit_zone заменена директивой limit_conn_zone с
+       новым синтаксисом.
+
+    *) Добавление: поддержка ограничения по нескольким limit_conn на одном
+       уровне.
+
+    *) Добавление: директива image_filter_sharpen.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если resolver получил большой DNS-ответ.
+       Спасибо Ben Hawkes.
+
+    *) Исправление: в вычислении ключа для кэширования, если использовалась
+       внутренняя реализация MD5; ошибка появилась в 1.0.4.
+
+    *) Исправление: строки "If-Modified-Since", "If-Range" и им подобные в
+       заголовке запроса клиента могли передаваться бэкенду при кэшировании;
+       или не передаваться при выключенном кэшировании, если кэширование
+       было включено в другой части конфигурации.
+
+    *) Исправление: модуль ngx_http_mp4_module выдавал неверную строку
+       "Content-Length" в заголовке ответа, использовался аргумент start.
+       Спасибо Piotr Sikora.
+
+
+Изменения в nginx 1.1.7                                           31.10.2011
+
+    *) Добавление: поддержка нескольких DNS серверов в директиве "resolver".
+       Спасибо Кириллу Коринскому.
+
+    *) Исправление: на старте или во время переконфигурации происходил
+       segmentation fault, если директива ssl использовалась на уровне http
+       и не был указан ssl_certificate.
+
+    *) Исправление: уменьшено потребление памяти при проксировании больших
+       файлов, если они буферизировались на диск.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовалась директива "proxy_http_version 1.1".
+
+    *) Исправление: в директиве "expires @time".
+
+
+Изменения в nginx 1.1.6                                           17.10.2011
+
+    *) Изменение во внутреннем API: теперь при внутреннем редиректе в
+       именованный location контексты модулей очищаются.
+       По запросу Yichun Zhang.
+
+    *) Изменение: теперь если сервер, описанный в блоке upstream, был
+       признан неработающим, то после истечения fail_timeout на него будет
+       отправлен только один запрос; сервер будет считаться работающим, если
+       успешно ответит на этот запрос.
+
+    *) Изменение: теперь символы 0x7F-0xFF в access_log записываются в виде
+       \xXX.
+
+    *) Добавление: директивы "proxy/fastcgi/scgi/uwsgi_ignore_headers"
+       теперь поддерживают значения X-Accel-Limit-Rate, X-Accel-Buffering и
+       X-Accel-Charset.
+
+    *) Добавление: уменьшение потребления памяти при использовании SSL.
+
+    *) Исправление: некоторые UTF-8 символы обрабатывались неправильно.
+       Спасибо Алексею Куцу.
+
+    *) Исправление: директивы модуля ngx_http_rewrite_module, заданные на
+       уровне server, применялись повторно, если для запроса не находилось
+       ни одного location'а.
+
+    *) Исправление: при использовании "aio sendfile" могла происходить
+       утечка сокетов.
+
+    *) Исправление: при использовании файлового AIO соединения с быстрыми
+       клиентами могли быть закрыты по истечению send_timeout.
+
+    *) Исправление: в модуле ngx_http_autoindex_module.
+
+    *) Исправление: модуль ngx_http_mp4_module не поддерживал перемотку на
+       32-битных платформах.
+
+
+Изменения в nginx 1.1.5                                           05.10.2011
+
+    *) Добавление: директивы uwsgi_buffering и scgi_buffering.
+       Спасибо Peter Smit.
+
+    *) Исправление: при использовании proxy_cache_bypass могли быть
+       закэшированы некэшируемые ответы.
+       Спасибо John Ferlito.
+
+    *) Исправление: в модуле ngx_http_proxy_module при работе с бэкендами по
+       HTTP/1.1.
+
+    *) Исправление: закэшированные ответы с пустым телом возвращались
+       некорректно; ошибка появилась в 0.8.31.
+
+    *) Исправление: ответы с кодом 201 модуля ngx_http_dav_module были
+       некорректны; ошибка появилась в 0.8.32.
+
+    *) Исправление: в директиве return.
+
+    *) Исправление: при использовании директивы "ssl_session_cache builtin"
+       происходил segmentation fault; ошибка появилась в 1.1.1.
+
+
+Изменения в nginx 1.1.4                                           20.09.2011
+
+    *) Добавление: модуль ngx_http_upstream_keepalive.
+
+    *) Добавление: директива proxy_http_version.
+
+    *) Добавление: директива fastcgi_keep_conn.
+
+    *) Добавление: директива worker_aio_requests.
+
+    *) Исправление: если nginx был собран с файловым AIO, он не мог
+       запускаться на Linux без поддержки AIO.
+
+    *) Исправление: в обработке ошибок при работе с Linux AIO.
+       Спасибо Hagai Avrahami.
+
+    *) Исправление: уменьшено потребление памяти для долгоживущих запросов.
+
+    *) Исправление: модуль ngx_http_mp4_module не поддерживал 64-битный
+       MP4-атом co64.
+
+
+Изменения в nginx 1.1.3                                           14.09.2011
+
+    *) Добавление: модуль ngx_http_mp4_module.
+
+    *) Исправление: в Linux AIO, используемым совместно с open_file_cache.
+
+    *) Исправление: open_file_cache не обновлял информацию о файле, если
+       файл был изменён не атомарно.
+
+    *) Исправление: nginx не собирался на MacOSX 10.7.
+
+
+Изменения в nginx 1.1.2                                           05.09.2011
+
+    *) Изменение: теперь, если суммарный размер всех диапазонов больше
+       размера исходного ответа, то nginx возвращает только исходный ответ,
+       не обрабатывая диапазоны.
+
+    *) Добавление: директива max_ranges.
+
+    *) Исправление: директивы ssl_verify_client, ssl_verify_depth и
+       ssl_prefer_server_cipher могли работать некорректно, если
+       использовался SNI.
+
+    *) Исправление: в директивах proxy/fastcgi/scgi/
+       uwsgi_ignore_client_abort.
+
+
+Изменения в nginx 1.1.1                                           22.08.2011
+
+    *) Изменение: теперь загрузчик кэша за каждую итерацию либо обрабатывает
+       число файлов, указанное в параметре load_files, либо работает не
+       дольше времени, указанного в параметре loader_threshold.
+
+    *) Изменение: SIGWINCH сигнал теперь работает только в режиме демона.
+
+    *) Добавление: теперь разделяемые зоны и кэши используют семафоры POSIX
+       на Solaris.
+       Спасибо Денису Иванову.
+
+    *) Добавление: теперь на NetBSD поддерживаются accept фильтры.
+
+    *) Исправление: nginx не собирался на Linux 3.0.
+
+    *) Исправление: в некоторых случаях nginx не использовал сжатие; ошибка
+       появилась в 1.1.0.
+
+    *) Исправление: обработка тела запроса могла быть неверной, если клиент
+       использовал pipelining.
+
+    *) Исправление: в директиве request_body_in_single_buf.
+
+    *) Исправление: в директивах proxy_set_body и proxy_pass_request_body
+       при использовании SSL-соединения с бэкендом.
+
+    *) Исправление: nginx нагружал процессор, если все серверы в upstream'е
+       были помечены флагом down.
+
+    *) Исправление: при переконфигурации мог произойти segmentation fault,
+       если в предыдущей конфигурации был определён, но не использовался
+       ssl_session_cache.
+
+    *) Исправление: при использовании большого количества backup-серверов в
+       рабочем процессе мог произойти segmentation fault.
+
+    *) Исправление: при использовании директив fastcgi/scgi/uwsgi_param со
+       значениями, начинающимися со строки "HTTP_", в рабочем процессе мог
+       произойти segmentation fault; ошибка появилась в 0.8.40.
+
+
+Изменения в nginx 1.1.0                                           01.08.2011
+
+    *) Добавление: уменьшение времени работы загрузчика кэша.
+
+    *) Добавление: параметры loader_files, loader_sleep и loader_threshold
+       директив proxy/fastcgi/scgi/uwsgi_cache_path.
+
+    *) Добавление: уменьшение времени загрузки конфигураций с большим
+       количеством HTTPS серверов.
+
+    *) Добавление: теперь nginx поддерживает шифры с обменом ECDHE-ключами.
+       Спасибо Adrian Kotelba.
+
+    *) Добавление: директива lingering_close.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: закрытия соединения для pipelined-запросов.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: nginx не запрещал сжатие при получении значения
+       "gzip;q=0" в строке "Accept-Encoding" в заголовке запроса клиента.
+
+    *) Исправление: таймаута при небуферизированном проксировании.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: утечки памяти при использовании переменных в директиве
+       proxy_pass при работе с бэкендом по HTTPS.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: в проверке параметра директивы proxy_pass, заданного
+       переменными.
+       Спасибо Lanshun Zhou.
+
+    *) Исправление: SSL не работал на QNX.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: SSL модули не собирались gcc 4.6 без параметра
+       --with-debug.
+
+
+Изменения в nginx 1.0.5                                           19.07.2011
+
+    *) Изменение: теперь по умолчанию используются следующие шифры SSL:
+       "HIGH:!aNULL:!MD5".
+       Спасибо Rob Stradling.
+
+    *) Добавление: директивы referer_hash_max_size и
+       referer_hash_bucket_size.
+       Спасибо Witold Filipczyk.
+
+    *) Добавление: переменная $uid_reset.
+
+    *) Исправление: при использовании кэширования в рабочем процессе мог
+       произойти segmentation fault.
+       Спасибо Lanshun Zhou.
+
+    *) Исправление: при использовании кэширования рабочие процессы могли
+       зациклиться во время переконфигурации; ошибка появилась в 0.8.48.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: сообщения "stalled cache updating".
+       Спасибо Максиму Дунину.
+
+
+Изменения в nginx 1.0.4                                           01.06.2011
+
+    *) Изменение: теперь в регулярных выражениях в директиве map можно
+       задать чувствительность к регистру с помощью префиксов "~" и "~*".
+
+    *) Добавление: теперь разделяемые зоны и кэши используют семафоры POSIX
+       на Linux.
+       Спасибо Денису Латыпову.
+
+    *) Исправление: сообщения "stalled cache updating".
+
+    *) Исправление: nginx не собирался с параметром
+       --without-http_auth_basic_module; ошибка появилась в 1.0.3.
+
+
+Изменения в nginx 1.0.3                                           25.05.2011
+
+    *) Добавление: директива auth_basic_user_file поддерживает шифрование
+       пароля методами "$apr1", "{PLAIN}" и "{SSHA}".
+       Спасибо Максиму Дунину.
+
+    *) Добавление: директива geoip_org и переменная $geoip_org.
+       Спасибо Александру Ускову, Arnaud Granal и Денису Латыпову.
+
+    *) Добавление: модули ngx_http_geo_module и ngx_http_geoip_module
+       поддерживают адреса IPv4, отображённые на IPv6 адреса.
+
+    *) Исправление: при проверке адреса IPv4, отображённого на адрес IPv6, в
+       рабочем процессе происходил segmentation fault, если директивы access
+       или deny были определены только для адресов IPv6; ошибка появилась в
+       0.8.22.
+
+    *) Исправление: закэшированный ответ мог быть испорчен, если значения
+       директив proxy/fastcgi/scgi/uwsgi_cache_bypass и proxy/fastcgi/scgi/
+       uwsgi_no_cache были разными; ошибка появилась в 0.8.46.
+
+
+Изменения в nginx 1.0.2                                           10.05.2011
+
+    *) Добавление: теперь разделяемые зоны и кэши используют семафоры POSIX.
+
+    *) Исправление: в работе параметра rotate директивы image_filter.
+       Спасибо Adam Bocim.
+
+    *) Исправление: nginx не собирался на Solaris; ошибка появилась в 1.0.1.
+
+
+Изменения в nginx 1.0.1                                           03.05.2011
+
+    *) Изменение: теперь директива split_clients использует алгоритм
+       MurmurHash2 из-за лучшего распределения.
+       Спасибо Олегу Мамонтову.
+
+    *) Изменение: теперь длинные строки, начинающиеся с нуля, не считаются
+       ложными значениями.
+       Спасибо Максиму Дунину.
+
+    *) Изменение: теперь по умолчанию nginx использует значение 511 для
+       listen backlog на Linux.
+
+    *) Добавление: переменные $upstream_... можно использовать в SSI и
+       перловом модулях.
+
+    *) Исправление: теперь nginx лучше ограничивает размер кэша на диске.
+       Спасибо Олегу Мамонтову.
+
+    *) Исправление: при парсинге неправильного IPv4 адреса мог произойти
+       segmentation fault; ошибка появилась в 0.8.22.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: nginx не собирался gcc 4.6 без параметра --with-debug.
+
+    *) Исправление: nginx не собирался на Solaris 9 и более ранних; ошибка
+       появилась в 0.9.3.
+       Спасибо Dagobert Michelsen.
+
+    *) Исправление: переменная $request_time имела неверные значения, если
+       использовались подзапросы; ошибка появилась в 0.8.47.
+       Спасибо Игорю А. Валькову.
+
+
+Изменения в nginx 1.0.0                                           12.04.2011
+
+    *) Исправление: cache manager мог нагружать процессор после
+       переконфигурации.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: директива "image_filter crop" неправильно работала в
+       сочетании с "image_filter rotate 180".
+
+    *) Исправление: директива "satisfy any" запрещала выдачу
+       пользовательской страницы для 401 кода.
+
+
+Изменения в nginx 0.9.7                                           04.04.2011
+
+    *) Добавление: теперь соединения в состоянии keepalive могут быть
+       закрыты преждевременно, если у воркера нет свободных соединений.
+       Спасибо Максиму Дунину.
+
+    *) Добавление: параметр rotate директивы image_filter.
+       Спасибо Adam Bocim.
+
+    *) Исправление: ситуации, когда бэкенд в директивах fastcgi_pass,
+       scgi_pass или uwsgi_pass задан выражением и ссылается на описанный
+       upstream.
+
+
+Изменения в nginx 0.9.6                                           21.03.2011
+
+    *) Добавление: директива map поддерживает регулярные выражения в
+       качестве значения первого параметра.
+
+    *) Добавление: переменная $time_iso8601 для access_log.
+       Спасибо Michael Lustfield.
+
+
+Изменения в nginx 0.9.5                                           21.02.2011
+
+    *) Изменение: теперь по умолчанию nginx использует значение -1 для
+       listen backlog на Linux.
+       Спасибо Андрею Нигматулину.
+
+    *) Добавление: параметр utf8 в директивах geoip_country и geoip_city.
+       Спасибо Денису Латыпову.
+
+    *) Исправление: исправление в умолчательной директиве proxy_redirect,
+       если в директиве proxy_pass не был описан URI.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: директива error_page не работала с нестандартными кодами
+       ошибок; ошибка появилась в 0.8.53.
+       Спасибо Максиму Дунину.
+
+
+Изменения в nginx 0.9.4                                           21.01.2011
+
+    *) Добавление: директива server_name поддерживает переменную $hostname.
+
+    *) Добавление: 494 код для ошибки "Request Header Too Large".
+
+
+Изменения в nginx 0.9.3                                           13.12.2010
+
+    *) Исправление: если для пары IPv6-адрес:порт описан только один сервер,
+       то выделения в регулярных выражениях в директиве server_name не
+       работали.
+
+    *) Исправление: nginx не собирался под Solaris; ошибка появилась в
+       0.9.0.
+
+
+Изменения в nginx 0.9.2                                           06.12.2010
+
+    *) Добавление: поддержка строки "If-Unmodified-Since" в заголовке
+       запроса клиента.
+
+    *) Изменение: использование accept(), если accept4() не реализован;
+       ошибка появилась в 0.9.0.
+
+    *) Исправление: nginx не собирался под Cygwin; ошибка появилась в 0.9.0.
+
+    *) Исправление: уязвимости в OpenSSL CVE-2010-4180.
+       Спасибо Максиму Дунину.
+
+
+Изменения в nginx 0.9.1                                           30.11.2010
+
+    *) Исправление: директивы вида "return CODE message" не работали; ошибка
+       появилась в 0.9.0.
+
+
+Изменения в nginx 0.9.0                                           29.11.2010
+
+    *) Добавление: директива keepalive_disable.
+
+    *) Добавление: директива map поддерживает переменные в качестве значения
+       определяемой переменной.
+
+    *) Добавление: директива map поддерживает пустые строки в качестве
+       значения первого параметра.
+
+    *) Добавление: директива map поддерживает выражения в первом параметре.
+
+    *) Добавление: страница руководства nginx(8).
+       Спасибо Сергею Осокину.
+
+    *) Добавление: поддержка accept4() в Linux.
+       Спасибо Simon Liu.
+
+    *) Изменение: устранение предупреждения линкера о "sys_errlist" и
+       "sys_nerr" под Linux; предупреждение появилось в 0.8.35.
+
+    *) Исправление: при использовании директивы auth_basic в рабочем
+       процессе мог произойти segmentation fault.
+       Спасибо Михаилу Лалетину.
+
+    *) Исправление: совместимость с модулем ngx_http_eval_module; ошибка
+       появилась в 0.8.42.
+
+
+Изменения в nginx 0.8.53                                          18.10.2010
+
+    *) Добавление: теперь директива error_page позволяет менять код статуса
+       у редиректа.
+
+    *) Добавление: директива gzip_disable поддерживает специальную маску
+       degradation.
+
+    *) Исправление: при использовании файлового AIO могла происходить утечка
+       сокетов.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: если в первом сервере не была описана директива listen и
+       нигде явно не описан сервер по умолчанию, то сервером по умолчанию
+       становился следующий сервер с директивой listen; ошибка появилась в
+       0.8.21.
+
+
+Изменения в nginx 0.8.52                                          28.09.2010
+
+    *) Исправление: nginx использовал режим SSL для listen сокета, если для
+       него был установлен любой listen-параметр; ошибка появилась в 0.8.51.
+
+
+Изменения в nginx 0.8.51                                          27.09.2010
+
+    *) Изменение: директива secure_link_expires упразднена.
+
+    *) Изменение: уровень логгирования ошибок resolver'а понижен с уровня
+       alert на error.
+
+    *) Добавление: теперь параметр "ssl" listen-сокета можно устанавливать
+       несколько раз.
+
+
+Изменения в nginx 0.8.50                                          02.09.2010
+
+    *) Добавление: директивы secure_link, secure_link_md5 и
+       secure_link_expires модуля ngx_http_secure_link_module.
+
+    *) Добавление: ключ -q.
+       Спасибо Геннадию Махомеду.
+
+    *) Исправление: при использовании кэширования рабочие процессы и могли
+       зациклиться во время переконфигурации; ошибка появилась в 0.8.48.
+
+    *) Исправление: в директиве gzip_disable.
+       Спасибо Derrick Petzold.
+
+    *) Исправление: nginx/Windows не мог посылать сигналы stop, quit,
+       reopen, reload процессу, запущенному в другой сессии.
+
+
+Изменения в nginx 0.8.49                                          09.08.2010
+
+    *) Добавление: директива image_filter_jpeg_quality поддерживает
+       переменные.
+
+    *) Исправление: при использовании переменной $geoip_region_name в
+       рабочем процессе мог произойти segmentation fault; ошибка появилась в
+       0.8.48.
+
+    *) Исправление: ошибки, перехваченные error_page, кэшировались только до
+       следующего запроса; ошибка появилась в 0.8.48.
+
+
+Изменения в nginx 0.8.48                                          03.08.2010
+
+    *) Изменение: теперь по умолчанию директива server_name имеет значение
+       пустое имя "".
+       Спасибо Геннадию Махомеду.
+
+    *) Изменение: теперь по умолчанию директива server_name_in_redirect
+       имеет значение off.
+
+    *) Добавление: переменные $geoip_dma_code, $geoip_area_code и
+       $geoip_region_name.
+       Спасибо Christine McGonagle.
+
+    *) Исправление: директивы proxy_pass, fastcgi_pass, uwsgi_pass и
+       scgi_pass не наследовались в блоки limit_except.
+
+    *) Исправление: директивы proxy_cache_min_uses, fastcgi_cache_min_uses
+       uwsgi_cache_min_uses и scgi_cache_min_uses не работали; ошибка
+       появилась в 0.8.46.
+
+    *) Исправление: директива fastcgi_split_path_info неверно использовала
+       выделения, если в выделения попадала только часть URI.
+       Спасибо Юрию Тарадаю и Frank Enderle.
+
+    *) Исправление: директива rewrite не экранировала символ ";" при
+       копировании из URI в аргументы.
+       Спасибо Daisuke Murase.
+
+    *) Исправление: модуль ngx_http_image_filter_module закрывал соединение,
+       если изображение было больше размера image_filter_buffer.
+
+
+Изменения в nginx 0.8.47                                          28.07.2010
+
+    *) Исправление: переменная $request_time имела неверные значения для
+       подзапросов.
+
+    *) Исправление: ошибки, перехваченные error_page, не кэшировались.
+
+    *) Исправление: если использовался параметр max_size, то cache manager
+       мог зациклиться; ошибка появилась в 0.8.46.
+
+
+Изменения в nginx 0.8.46                                          19.07.2010
+
+    *) Изменение: директивы proxy_no_cache, fastcgi_no_cache, uwsgi_no_cache
+       и scgi_no_cache теперь влияют только на сохранение закэшированного
+       ответа.
+
+    *) Добавление: директивы proxy_cache_bypass, fastcgi_cache_bypass,
+       uwsgi_cache_bypass и scgi_cache_bypass.
+
+    *) Исправление: nginx не освобождал память в keys_zone кэшей в случае
+       ошибки работы с бэкендом: память освобождалась только по истечении
+       времени неактивности или при недостатке памяти.
+
+
+Изменения в nginx 0.8.45                                          13.07.2010
+
+    *) Добавление: улучшения в модуле ngx_http_xslt_filter.
+       Спасибо Laurence Rowe.
+
+    *) Исправление: ответ SSI модуля мог передаваться не полностью после
+       команды include с параметром wait="yes"; ошибка появилась в 0.7.25.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: директива listen не поддерживала параметр setfib=0.
+
+
+Изменения в nginx 0.8.44                                          05.07.2010
+
+    *) Изменение: теперь nginx по умолчанию не кэширует ответы бэкендов, в
+       заголовке которых есть строка "Set-Cookie".
+
+    *) Добавление: директива listen поддерживает параметр setfib.
+       Спасибо Андрею Филонову.
+
+    *) Исправление: директива sub_filter могла изменять регистр букв при
+       частичном совпадении.
+
+    *) Исправление: совместимость с HP/UX.
+
+    *) Исправление: совместимость с компилятором AIX xlC_r.
+
+    *) Исправление: nginx считал большие пакеты SSLv2 как обычные текстовые
+       запросы.
+       Спасибо Miroslaw Jaworski.
+
+
+Изменения в nginx 0.8.43                                          30.06.2010
+
+    *) Добавление: ускорение загрузки больших баз geo-диапазонов.
+
+    *) Исправление: перенаправление ошибки в "location /zero {return 204;}"
+       без изменения кода ответа оставляло тело ошибки; ошибка появилась в
+       0.8.42.
+
+    *) Исправление: nginx мог закрывать IPv6 listen сокет во время
+       переконфигурации.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: переменную $uid_set можно использовать на любой стадии
+       обработки запроса.
+
+
+Изменения в nginx 0.8.42                                          21.06.2010
+
+    *) Изменение: теперь nginx проверяет location'ы, заданные регулярными
+       выражениями, если запрос полностью совпал с location'ом, заданным
+       строкой префикса. Предыдущее поведение появилось в 0.7.1.
+
+    *) Добавление: модуль ngx_http_scgi_module.
+       Спасибо Manlio Perillo.
+
+    *) Добавление: в директиве return можно добавлять текст ответа.
+
+
+Изменения в nginx 0.8.41                                          15.06.2010
+
+    *) Безопасность: рабочий процесс nginx/Windows мог завершаться аварийно
+       при запросе файла с неверной кодировкой UTF-8.
+
+    *) Изменение: теперь nginx разрешает использовать пробелы в строке
+       запроса.
+
+    *) Исправление: директива proxy_redirect неправильно изменяла строку
+       "Refresh" в заголовке ответа бэкенда.
+       Спасибо Андрею Андрееву и Максиму Согину.
+
+    *) Исправление: nginx не поддерживал путь без имени хоста в строке
+       "Destination" в заголовке запроса.
+
+
+Изменения в nginx 0.8.40                                          07.06.2010
+
+    *) Безопасность: теперь nginx/Windows игнорирует имя потока файла по
+       умолчанию.
+       Спасибо Jose Antonio Vazquez Gonzalez.
+
+    *) Добавление: модуль ngx_http_uwsgi_module.
+       Спасибо Roberto De Ioris.
+
+    *) Добавление: директива fastcgi_param со значением, начинающимся со
+       строки "HTTP_", изменяет строку заголовка в запросе клиента.
+
+    *) Исправление: строки "If-Modified-Since", "If-Range" и им подобные в
+       заголовке запроса клиента передавались FastCGI-серверу при
+       кэшировании.
+
+    *) Исправление: listen unix domain сокет нельзя было изменить во время
+       переконфигурации.
+       Спасибо Максиму Дунину.
+
+
+Изменения в nginx 0.8.39                                          31.05.2010
+
+    *) Исправление: наследуемая директива alias неправильно работала во
+       вложенном location'е.
+
+    *) Исправление: в комбинации директив alias с переменными и try_files;
+
+    *) Исправление: listen unix domain и IPv6 сокеты не наследовались во
+       время обновления без перерыва.
+       Спасибо Максиму Дунину.
+
+
+Изменения в nginx 0.8.38                                          24.05.2010
+
+    *) Добавление: директивы proxy_no_cache и fastcgi_no_cache.
+
+    *) Добавление: теперь при использовании переменной $scheme в директиве
+       rewrite автоматически делается редирект.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: теперь задержки в директиве limit_req соответствует
+       описанному алгоритму.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: переменную $uid_got нельзя было использовать в SSI и
+       перловом модулях.
+
+
+Изменения в nginx 0.8.37                                          17.05.2010
+
+    *) Добавление: модуль ngx_http_split_clients_module.
+
+    *) Добавление: директива map поддерживает ключи больше 255 символов.
+
+    *) Исправление: nginx игнорировал значения "private" и "no-store" в
+       строке "Cache-Control" в заголовке ответа бэкенда.
+
+    *) Исправление: параметр stub в SSI-директиве include не использовался,
+       если пустой ответ имел код 200.
+
+    *) Исправление: если проксированный или FastCGI запрос внутренне
+       перенаправлялся в другой проксированный или FastCGI location, то в
+       рабочем процессе мог произойти segmentation fault; ошибка появилась в
+       0.8.33.
+       Спасибо Yichun Zhang.
+
+    *) Исправление: соединения IMAP к серверу Zimbra могло зависнуть до
+       таймаута.
+       Спасибо Alan Batie.
+
+
+Изменения в nginx 0.8.36                                          22.04.2010
+
+    *) Исправление: модуль ngx_http_dav_module неправильно обрабатывал
+       методы DELETE, COPY и MOVE для симлинков.
+
+    *) Исправление: модуль SSI в подзапросах использовал закэшированные в
+       основном запросе значения переменных $query_string, $arg_... и им
+       подобных.
+
+    *) Исправление: значение переменной повторно экранировалось после
+       каждого вывода SSI-команды echo; ошибка появилась в 0.6.14.
+
+    *) Исправление: рабочий процесс зависал при запросе файла FIFO.
+       Спасибо Vicente Aguilar и Максиму Дунину.
+
+    *) Исправление: совместимость с OpenSSL-1.0.0 на 64-битном Linux.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: nginx не собирался с параметром --without-http-cache;
+       ошибка появилась в 0.8.35.
+
+
+Изменения в nginx 0.8.35                                          01.04.2010
+
+    *) Изменение: теперь charset-фильтр работает до SSI-фильтра.
+
+    *) Добавление: директива chunked_transfer_encoding.
+
+    *) Исправление: символ "&" при копировании в аргументы в правилах
+       rewrite не экранировался.
+
+    *) Исправление: nginx мог завершаться аварийно во время обработки
+       сигнала или при использовании директивы timer_resolution на
+       платформах, не поддерживающих методы kqueue или eventport.
+       Спасибо George Xie и Максиму Дунину.
+
+    *) Исправление: если временные файлы и постоянное место хранения
+       располагались на разных файловых системах, то у постоянных файлов
+       время изменения было неверным.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: модуль ngx_http_memcached_module мог выдавать ошибку
+       "memcached sent invalid trailer".
+       Спасибо Максиму Дунину.
+
+    *) Исправление: nginx не мог собрать библиотеку zlib-1.2.4 из исходных
+       текстов.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: в рабочем процессе происходил segmentation fault, если
+       перед ответом FastCGI-сервера было много вывода в stderr; ошибка
+       появилась в 0.8.34.
+       Спасибо Максиму Дунину.
+
+
+Изменения в nginx 0.8.34                                          03.03.2010
+
+    *) Исправление: nginx не поддерживал все шифры, используемые в
+       клиентских сертификатах.
+       Спасибо Иннокентию Еникееву.
+
+    *) Исправление: nginx неправильно кэшировал FastCGI-ответы, если перед
+       ответом было много вывода в stderr.
+
+    *) Исправление: nginx не поддерживал HTTPS-рефереры.
+
+    *) Исправление: nginx/Windows мог не находить файлы, если путь в
+       конфигурации был задан в другом регистре; ошибка появилась в 0.8.33.
+
+    *) Исправление: переменная $date_local выдавала неверное время, если
+       использовался формат "%s".
+       Спасибо Максиму Дунину.
+
+    *) Исправление: если ssl_session_cache не был установлен или установлен
+       в none, то при проверке клиентского сертификаты могла происходить
+       ошибка "session id context uninitialized"; ошибка появилась в 0.7.1.
+
+    *) Исправление: geo-диапазон возвращал значение по умолчанию, если
+       диапазон включал в себя одну и более сетей размером /16 и не
+       начинался на границе сети размером /16.
+
+    *) Исправление: блок, используемый в параметре stub в SSI-директиве
+       include, выводился с MIME-типом "text/plain".
+
+    *) Исправление: $r->sleep() не работал; ошибка появилась в 0.8.11.
+
+
+Изменения в nginx 0.8.33                                          01.02.2010
+
+    *) Безопасность: теперь nginx/Windows игнорирует пробелы в конце URI.
+       Спасибо Dan Crowley, Core Security Technologies.
+
+    *) Безопасность: теперь nginx/Windows игнорирует короткие имена файлов.
+       Спасибо Dan Crowley, Core Security Technologies.
+
+    *) Изменение: теперь keepalive соединения после запросов POST не
+       запрещаются для MSIE 7.0+.
+       Спасибо Adam Lounds.
+
+    *) Изменение: теперь keepalive соединения запрещены для Safari.
+       Спасибо Joshua Sierles.
+
+    *) Исправление: если проксированный или FastCGI запрос внутренне
+       перенаправлялся в другой проксированный или FastCGI location, то
+       переменная $upstream_response_time могла иметь ненормально большое
+       значение; ошибка появилась в 0.8.7.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault при
+       отбрасывания тела запроса; ошибка появилась в 0.8.11.
+
+
+Изменения в nginx 0.8.32                                          11.01.2010
+
+    *) Исправление: ошибки при использовании кодировки UTF-8 в
+       ngx_http_autoindex_module.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: именованные выделения в регулярных выражениях работали
+       только для двух переменных.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: теперь в строке заголовка запроса "Host" используется
+       имя "localhost", если в директиве auth_http указан unix domain сокет.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: nginx не поддерживал передачу chunk'ами для 201-ых
+       ответов.
+       Спасибо Julian Reich.
+
+    *) Исправление: если директива "expires modified" выставляла дату в
+       прошлом, то в строке заголовка ответа "Cache-Control" выдавалось
+       отрицательное число.
+       Спасибо Алексею Капранову.
+
+
+Изменения в nginx 0.8.31                                          23.12.2009
+
+    *) Добавление: теперь директива error_page может перенаправлять ответы
+       со статусом 301 и 302.
+
+    *) Добавление: переменные $geoip_city_continent_code, $geoip_latitude и
+       $geoip_longitude.
+       Спасибо Arvind Sundararajan.
+
+    *) Добавление: модуль ngx_http_image_filter_module теперь всегда удаляет
+       EXIF и другие данные, если они занимают больше 5% в JPEG-файле.
+
+    *) Исправление: nginx закрывал соединение при запросе закэшированного
+       ответа с пустым телом.
+       Спасибо Piotr Sikora.
+
+    *) Исправление: nginx мог не собираться gcc 4.x при использовании
+       оптимизации -O2 и выше.
+       Спасибо Максиму Дунину и Денису Латыпову.
+
+    *) Исправление: регулярные выражения в location всегда тестировались с
+       учётом регистра; ошибка появилась в 0.8.25.
+
+    *) Исправление: nginx кэшировал 304 ответ, если в заголовке
+       проксируемого запроса была строка "If-None-Match".
+       Спасибо Tim Dettrick и David Kostal.
+
+    *) Исправление: nginx/Windows пытался дважды удалить временный файл при
+       перезаписи уже существующего файла.
+
+
+Изменения в nginx 0.8.30                                          15.12.2009
+
+    *) Изменение: теперь по умолчанию размер буфера директивы
+       large_client_header_buffers равен 8K.
+       Спасибо Andrew Cholakian.
+
+    *) Добавление: файл conf/fastcgi.conf для простых конфигураций FastCGI.
+
+    *) Исправление: nginx/Windows пытался дважды переименовать временный
+       файл при перезаписи уже существующего файла.
+
+    *) Исправление: ошибки double free or corruption, возникающей, если имя
+       хоста не было найдено; ошибка появилась в 0.8.22.
+       Спасибо Константину Свисту.
+
+    *) Исправление: в использовании libatomic на некоторых платформах.
+       Спасибо W-Mark Kubacki.
+
+
+Изменения в nginx 0.8.29                                          30.11.2009
+
+    *) Изменение: теперь для проксируемых ответов HTTP/0.9 в лог пишется код
+       ответа "009".
+
+    *) Добавление: директивы addition_types, charset_types, gzip_types,
+       ssi_types, sub_filter_types и xslt_types поддерживают параметр "*".
+
+    *) Добавление: использование встроенных атомарных операций GCC 4.1+.
+       Спасибо W-Mark Kubacki.
+
+    *) Добавление: параметр --with-libatomic[=DIR] в configure.
+       Спасибо W-Mark Kubacki.
+
+    *) Исправление: listen unix domain сокет имели ограниченные права
+       доступа.
+
+    *) Исправление: закэшированные ответы ответов HTTP/0.9 неправильно
+       обрабатывались.
+
+    *) Исправление: именованные выделения в регулярных выражениях, заданные
+       как "?P<...>", не работали в директиве server_name.
+       Спасибо Максиму Дунину.
+
+
+Изменения в nginx 0.8.28                                          23.11.2009
+
+    *) Исправление: nginx не собирался с параметром --without-pcre; ошибка
+       появилась в 0.8.25.
+
+
+Изменения в nginx 0.8.27                                          17.11.2009
+
+    *) Исправление: регулярные выражения не работали в nginx/Windows; ошибка
+       появилась в 0.8.25.
+
+
+Изменения в nginx 0.8.26                                          16.11.2009
+
+    *) Исправление: ошибки при использовании выделений в директиве rewrite;
+       ошибка появилась в 0.8.25.
+
+    *) Исправление: nginx не собирался без параметра --with-debug; ошибка
+       появилась в 0.8.25.
+
+
+Изменения в nginx 0.8.25                                          16.11.2009
+
+    *) Изменение: теперь в лог ошибок не пишется сообщение, если переменная
+       не найдена с помощью метода $r->variable().
+
+    *) Добавление: модуль ngx_http_degradation_module.
+
+    *) Добавление: именованные выделения в регулярных выражениях.
+
+    *) Добавление: теперь при использовании переменных в директиве
+       proxy_pass не требуется задавать URI.
+
+    *) Добавление: теперь директива msie_padding работает и для Chrome.
+
+    *) Исправление: в рабочем процессе происходил segmentation fault при
+       недостатке памяти; ошибка появилась в 0.8.18.
+
+    *) Исправление: nginx передавал сжатые ответы клиентам, не
+       поддерживающим сжатие, при настройках gzip_static on и gzip_vary off;
+       ошибка появилась в 0.8.16.
+
+
+Изменения в nginx 0.8.24                                          11.11.2009
+
+    *) Исправление: nginx всегда добавлял строку "Content-Encoding: gzip" в
+       заголовок 304-ых ответов модуля ngx_http_gzip_static_module.
+
+    *) Исправление: nginx не собирался без параметра --with-debug; ошибка
+       появилась в 0.8.23.
+
+    *) Исправление: параметр "unix:" в директиве set_real_ip_from
+       неправильно наследовался с предыдущего уровня.
+
+    *) Исправление: в resolver'е при определении пустого имени.
+
+
+Изменения в nginx 0.8.23                                          11.11.2009
+
+    *) Безопасность: теперь SSL/TLS renegotiation запрещён.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: listen unix domain сокет не наследовался во время
+       обновления без перерыва.
+
+    *) Исправление: параметр "unix:" в директиве set_real_ip_from не работал
+       без ещё одной директивы с любым IP-адресом.
+
+    *) Исправление: segmentation fault и зацикливания в resolver'е.
+
+    *) Исправление: в resolver'е.
+       Спасибо Артёму Бохану.
+
+
+Изменения в nginx 0.8.22                                          03.11.2009
+
+    *) Добавление: директивы proxy_bind, fastcgi_bind и memcached_bind.
+
+    *) Добавление: директивы access и deny поддерживают IPv6.
+
+    *) Добавление: директива set_real_ip_from поддерживает IPv6 адреса в
+       заголовках запроса.
+
+    *) Добавление: параметр "unix:" в директиве set_real_ip_from.
+
+    *) Исправление: nginx не удалял unix domain сокет после тестирования
+       конфигурации.
+
+    *) Исправление: nginx удалял unix domain сокет во время обновления без
+       перерыва.
+
+    *) Исправление: оператор "!-x" не работал.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault при
+       использовании limit_rate в HTTPS сервере.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: при записи в лог переменной $limit_rate в рабочем
+       процессе происходил segmentation fault.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если внутри блока server не было директивы listen; ошибка появилась в
+       0.8.21.
+
+
+Изменения в nginx 0.8.21                                          26.10.2009
+
+    *) Добавление: теперь ключ -V показывает статус поддержки TLS SNI.
+
+    *) Добавление: директива listen модуля HTTP поддерживает unix domain
+       сокеты.
+       Спасибо Hongli Lai.
+
+    *) Добавление: параметр "default_server" в директиве listen.
+
+    *) Добавление: теперь параметр "default" не обязателен для установки
+       параметров listen-сокета.
+
+    *) Исправление: nginx не поддерживал даты в 2038 году на 32-битных
+       платформах;
+
+    *) Исправление: утечки сокетов; ошибка появилась в 0.8.11.
+
+
+Изменения в nginx 0.8.20                                          14.10.2009
+
+    *) Изменение: теперь по умолчанию используются следующие шифры SSL:
+       "HIGH:!ADH:!MD5".
+
+    *) Исправление: модуль ngx_http_autoindex_module не показывал последний
+       слэш для линков на каталоги; ошибка появилась в 0.7.15.
+
+    *) Исправление: nginx не закрывал лог, заданный параметром конфигурации
+       --error-log-path; ошибка появилась в 0.7.53.
+
+    *) Исправление: nginx не считал запятую разделителем в строке
+       "Cache-Control" в заголовке ответа бэкенда.
+
+    *) Исправление: nginx/Windows мог не создать временный файл, файл в кэше
+       или файл с помощью директив proxy/fastcgi_store, если рабочий процесс
+       не имел достаточно прав для работы с каталогами верхнего уровня.
+
+    *) Исправление: строки "Set-Cookie" и "P3P" в заголовке ответа
+       FastCGI-сервера не скрывались при кэшировании, если не использовались
+       директивы fastcgi_hide_header с любыми параметрами.
+
+    *) Исправление: nginx неверно считал размер кэша на диске.
+
+
+Изменения в nginx 0.8.19                                          06.10.2009
+
+    *) Изменение: теперь протокол SSLv2 по умолчанию запрещён.
+
+    *) Изменение: теперь по умолчанию используются следующие шифры SSL:
+       "ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM".
+
+    *) Исправление: директива limit_req не работала; ошибка появилась в
+       0.8.18.
+
+
+Изменения в nginx 0.8.18                                          06.10.2009
+
+    *) Добавление: директива read_ahead.
+
+    *) Добавление: теперь можно использовать несколько директив
+       perl_modules.
+
+    *) Добавление: директивы limit_req_log_level и limit_conn_log_level.
+
+    *) Исправление: теперь директива limit_req соответствует алгоритму leaky
+       bucket.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: nginx не работал на Linux/sparc.
+       Спасибо Marcus Ramberg.
+
+    *) Исправление: nginx слал символ '\0' в строке "Location" в заголовке в
+       ответе на запрос MKCOL.
+       Спасибо Xie Zhenye.
+
+    *) Исправление: вместо кода ответа 499 в лог записывался код 0; ошибка
+       появилась в 0.8.11.
+
+    *) Исправление: утечки сокетов; ошибка появилась в 0.8.11.
+
+
+Изменения в nginx 0.8.17                                          28.09.2009
+
+    *) Безопасность: теперь символы "/../" запрещены в строке "Destination"
+       в заголовке запроса.
+
+    *) Изменение: теперь значение переменной $host всегда в нижнем регистре.
+
+    *) Добавление: переменная $ssl_session_id.
+
+    *) Исправление: утечки сокетов; ошибка появилась в 0.8.11.
+
+
+Изменения в nginx 0.8.16                                          22.09.2009
+
+    *) Добавление: директива image_filter_transparency.
+
+    *) Исправление: директива "addition_types" была неверно названа
+       "addtion_types".
+
+    *) Исправление: порчи кэша resolver'а.
+       Спасибо Matthew Dempsky.
+
+    *) Исправление: утечки памяти в resolver'е.
+       Спасибо Matthew Dempsky.
+
+    *) Исправление: неверная строка запроса в переменной $request
+       записывалась в access_log только при использовании error_log на
+       уровне info или debug.
+
+    *) Исправление: в поддержке альфа-канала PNG в модуле
+       ngx_http_image_filter_module.
+
+    *) Исправление: nginx всегда добавлял строку "Vary: Accept-Encoding" в
+       заголовок ответа, если обе директивы gzip_static и gzip_vary были
+       включены.
+
+    *) Исправление: в поддержке кодировки UTF-8 директивой try_files в
+       nginx/Windows.
+
+    *) Исправление: ошибки при использовании post_action; ошибка появилась в
+       0.8.11.
+       Спасибо Игорю Артемьеву.
+
+
+Изменения в nginx 0.8.15                                          14.09.2009
+
+    *) Безопасность: при обработке специально созданного запроса в рабочем
+       процессе мог произойти segmentation fault.
+       Спасибо Chris Ries.
+
+    *) Исправление: если были описаны имена .domain.tld, .sub.domain.tld и
+       .domain-some.tld, то имя .sub.domain.tld попадало под маску
+       .domain.tld.
+
+    *) Исправление: в поддержке прозрачности в модуле
+       ngx_http_image_filter_module.
+
+    *) Исправление: в файловом AIO.
+
+    *) Исправление: ошибки при использовании X-Accel-Redirect; ошибка
+       появилась в 0.8.11.
+
+    *) Исправление: ошибки при использовании встроенного перла; ошибка
+       появилась в 0.8.11.
+
+
+Изменения в nginx 0.8.14                                          07.09.2009
+
+    *) Исправление: устаревший закэшированный запрос мог залипнуть в
+       состоянии "UPDATING".
+
+    *) Исправление: при использовании error_log на уровне info или debug в
+       рабочем процессе мог произойти segmentation fault.
+       Спасибо Сергею Боченкову.
+
+    *) Исправление: ошибки при использовании встроенного перла; ошибка
+       появилась в 0.8.11.
+
+    *) Исправление: директива error_page не перенаправляла ошибку 413;
+       ошибка появилась в 0.6.10.
+
+
+Изменения в nginx 0.8.13                                          31.08.2009
+
+    *) Исправление: в директиве "aio sendfile"; ошибка появилась в 0.8.12.
+
+    *) Исправление: nginx не собирался без параметра --with-file-aio на
+       FreeBSD; ошибка появилась в 0.8.12.
+
+
+Изменения в nginx 0.8.12                                          31.08.2009
+
+    *) Добавление: параметр sendfile в директиве aio во FreeBSD.
+
+    *) Исправление: ошибки при использовании try_files; ошибка появилась в
+       0.8.11.
+
+    *) Исправление: ошибки при использовании memcached; ошибка появилась в
+       0.8.11.
+
+
+Изменения в nginx 0.8.11                                          28.08.2009
+
+    *) Изменение: теперь директива "gzip_disable msie6" не запрещает сжатие
+       для MSIE 6.0 SV1.
+
+    *) Добавление: поддержка файлового AIO во FreeBSD и Linux.
+
+    *) Добавление: директива directio_alignment.
+
+
+Изменения в nginx 0.8.10                                          24.08.2009
+
+    *) Исправление: утечек памяти при использовании базы GeoIP City.
+
+    *) Исправление: ошибки при копировании временных файлов в постоянное
+       место хранения; ошибка появилась в 0.8.9.
+
+
+Изменения в nginx 0.8.9                                           17.08.2009
+
+    *) Добавление: теперь стартовый загрузчик кэша работает в отдельном
+       процесс; это должно улучшить обработку больших кэшей.
+
+    *) Добавление: теперь временные файлы и постоянное место хранения могут
+       располагаться на разных файловых системах.
+
+
+Изменения в nginx 0.8.8                                           10.08.2009
+
+    *) Исправление: в обработке заголовков ответа, разделённых в
+       FastCGI-записях.
+
+    *) Исправление: если запрос обрабатывался в двух проксированных или
+       FastCGI location'ах и в первом из них использовалось кэширование, то
+       в рабочем процессе происходил segmentation fault; ошибка появилась в
+       0.8.7.
+
+
+Изменения в nginx 0.8.7                                           27.07.2009
+
+    *) Изменение: минимальная поддерживаемая версия OpenSSL - 0.9.7.
+
+    *) Изменение: параметр ask директивы ssl_verify_client изменён на
+       параметр optional и теперь он проверяет клиентский сертификат, если
+       он был предложен.
+       Спасибо Brice Figureau.
+
+    *) Добавление: переменная $ssl_client_verify.
+       Спасибо Brice Figureau.
+
+    *) Добавление: директива ssl_crl.
+       Спасибо Brice Figureau.
+
+    *) Добавление: параметр proxy директивы geo.
+
+    *) Добавление: директива image_filter поддерживает переменные для
+       задания размеров.
+
+    *) Исправление: использование переменной $ssl_client_cert портило
+       память; ошибка появилась в 0.7.7.
+       Спасибо Сергею Журавлёву.
+
+    *) Исправление: директивы proxy_pass_header и fastcgi_pass_header" не
+       передавали клиенту строки "X-Accel-Redirect", "X-Accel-Limit-Rate",
+       "X-Accel-Buffering" и "X-Accel-Charset" из заголовка ответа бэкенда.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: в обработке строк "Last-Modified" и "Accept-Ranges" в
+       заголовке ответа бэкенда; ошибка появилась в 0.7.44.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: ошибки "[alert] zero size buf" при получении пустых
+       ответы в подзапросах; ошибка появилась в 0.8.5.
+
+
+Изменения в nginx 0.8.6                                           20.07.2009
+
+    *) Добавление: модуль ngx_http_geoip_module.
+
+    *) Исправление: XSLT-фильтр мог выдавать ошибку "not well formed XML
+       document" для правильного документа.
+       Спасибо Kuramoto Eiji.
+
+    *) Исправление: в MacOSX, Cygwin и nginx/Windows при проверке
+       location'ов, заданных регулярным выражением, теперь всегда делается
+       сравнение без учёта регистра символов.
+
+    *) Исправление: теперь nginx/Windows игнорирует точки в конце URI.
+       Спасибо Hugo Leisink.
+
+    *) Исправление: имя файла указанного в --conf-path игнорировалось при
+       установке; ошибка появилась в 0.6.6.
+       Спасибо Максиму Дунину.
+
+
+Изменения в nginx 0.8.5                                           13.07.2009
+
+    *) Исправление: теперь nginx разрешает подчёркивания в методе запроса.
+
+    *) Исправление: при использовании HTTP Basic-аутентификации на Windows
+       для неверных имени/пароля возвращалась 500-ая ошибка.
+
+    *) Исправление: ответы модуля ngx_http_perl_module не работали в
+       подзапросах.
+
+    *) Исправление: в модуле ngx_http_limit_req_module.
+       Спасибо Максиму Дунину.
+
+
+Изменения в nginx 0.8.4                                           22.06.2009
+
+    *) Исправление: nginx не собирался с параметром --without-http-cache;
+       ошибка появилась в 0.8.3.
+
+
+Изменения в nginx 0.8.3                                           19.06.2009
+
+    *) Добавление: переменная $upstream_cache_status.
+
+    *) Исправление: nginx не собирался на MacOSX 10.6.
+
+    *) Исправление: nginx не собирался с параметром --without-http-cache;
+       ошибка появилась в 0.8.2.
+
+    *) Исправление: если использовался перехват 401 ошибки от бэкенда и
+       бэкенд не возвращал строку "WWW-Authenticate" в заголовке ответа, то
+       в рабочем процессе происходил segmentation fault.
+       Спасибо Евгению Мычло.
+
+
+Изменения в nginx 0.8.2                                           15.06.2009
+
+    *) Исправление: во взаимодействии open_file_cache и proxy/fastcgi кэша
+       на старте.
+
+    *) Исправление: open_file_cache мог кэшировать открытые файлы очень
+       долго; ошибка появилась в 0.7.4.
+
+
+Изменения в nginx 0.8.1                                           08.06.2009
+
+    *) Добавление: параметр updating в директивах proxy_cache_use_stale и
+       fastcgi_cache_use_stale.
+
+    *) Исправление: строки "If-Modified-Since", "If-Range" и им подобные в
+       заголовке запроса клиента передавались бэкенду при кэшировании, если
+       не использовалась директива proxy_set_header с любыми параметрами.
+
+    *) Исправление: строки "Set-Cookie" и "P3P" в заголовке ответа бэкенда
+       не скрывались при кэшировании, если не использовались директивы
+       proxy_hide_header/fastcgi_hide_header с любыми параметрами.
+
+    *) Исправление: модуль ngx_http_image_filter_module не понимал формат
+       GIF87a.
+       Спасибо Денису Ильиных.
+
+    *) Исправление: nginx не собирался на Solaris 10 и более ранних; ошибка
+       появилась в 0.7.56.
+
+
+Изменения в nginx 0.8.0                                           02.06.2009
+
+    *) Добавление: директива keepalive_requests.
+
+    *) Добавление: директива limit_rate_after.
+       Спасибо Ivan Debnar.
+
+    *) Исправление: XSLT-фильтр не работал в подзапросах.
+
+    *) Исправление: обработке относительных путей в nginx/Windows.
+
+    *) Исправление: в proxy_store, fastcgi_store, proxy_cache и
+       fastcgi_cache в nginx/Windows.
+
+    *) Исправление: в обработке ошибок выделения памяти.
+       Спасибо Максиму Дунину и Кириллу Коринскому.
+
+
+Изменения в nginx 0.7.59                                          25.05.2009
+
+    *) Добавление: директивы proxy_cache_methods и fastcgi_cache_methods.
+
+    *) Исправление: утечки сокетов; ошибка появилась в 0.7.25.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: при использовании переменной $request_body в рабочем
+       процессе происходил segmentation fault, если в запросе не было тела;
+       ошибка появилась в 0.7.58.
+
+    *) Исправление: SSL-модули могли не собираться на Solaris и Linux;
+       ошибка появилась в 0.7.56.
+
+    *) Исправление: ответы модуля ngx_http_xslt_filter_module не
+       обрабатывались SSI-, charset- и gzip-фильтрами.
+
+    *) Исправление: директива charset не ставила кодировку для ответов
+       модуля ngx_http_gzip_static_module.
+
+
+Изменения в nginx 0.7.58                                          18.05.2009
+
+    *) Добавление: директива listen почтового прокси-сервера поддерживает
+       IPv6.
+
+    *) Добавление: директива image_filter_jpeg_quality.
+
+    *) Добавление: директива client_body_in_single_buffer.
+
+    *) Добавление: переменная $request_body.
+
+    *) Исправление: в модуле ngx_http_autoindex_module в ссылках на имена
+       файлов, содержащих символ ":".
+
+    *) Исправление: процедура "make upgrade" не работала; ошибка появилась в
+       0.7.53.
+       Спасибо Денису Латыпову.
+
+
+Изменения в nginx 0.7.57                                          12.05.2009
+
+    *) Исправление: при перенаправлении ошибок модуля
+       ngx_http_image_filter_module в именованный location в рабочем
+       процессе происходил floating-point fault; ошибка появилась в 0.7.56.
+
+
+Изменения в nginx 0.7.56                                          11.05.2009
+
+    *) Добавление: nginx/Windows поддерживает IPv6 в директиве listen модуля
+       HTTP.
+
+    *) Исправление: в модуле ngx_http_image_filter_module.
+
+
+Изменения в nginx 0.7.55                                          06.05.2009
+
+    *) Исправление: параметры http_XXX в директивах proxy_cache_use_stale и
+       fastcgi_cache_use_stale не работали.
+
+    *) Исправление: fastcgi кэш не кэшировал ответы, состоящие только из
+       заголовка.
+
+    *) Исправление: ошибки "select() failed (9: Bad file descriptor)" в
+       nginx/Unix и "select() failed (10038: ...)" в nginx/Windows.
+
+    *) Исправление: при использовании директивы debug_connection в рабочем
+       процессе мог произойти segmentation fault; ошибка появилась в 0.7.54.
+
+    *) Исправление: в сборке модуля ngx_http_image_filter_module.
+
+    *) Исправление: файлы больше 2G не передавались с использованием
+       $r->sendfile.
+       Спасибо Максиму Дунину.
+
+
+Изменения в nginx 0.7.54                                          01.05.2009
+
+    *) Добавление: модуль ngx_http_image_filter_module.
+
+    *) Добавление: директивы proxy_ignore_headers и fastcgi_ignore_headers.
+
+    *) Исправление: при использовании переменных "open_file_cache_errors on"
+       в рабочем процессе мог произойти segmentation fault; ошибка появилась
+       в 0.7.53.
+
+    *) Исправление: директива "port_in_redirect off" не работала; ошибка
+       появилась в 0.7.39.
+
+    *) Исправление: улучшение обработки ошибок метода select.
+
+    *) Исправление: ошибки "select() failed (10022: ...)" в nginx/Windows.
+
+    *) Исправление: в текстовых сообщениях об ошибках в nginx/Windows;
+       ошибка появилась в 0.7.53.
+
+
+Изменения в nginx 0.7.53                                          27.04.2009
+
+    *) Изменение: теперь лог, указанный в --error-log-path, создаётся с
+       самого начала работы.
+
+    *) Добавление: теперь ошибки и предупреждения при старте записываются в
+       error_log и выводятся на stderr.
+
+    *) Добавление: при сборке с пустым параметром --prefix= nginx использует
+       как префикс каталог, в котором он был запущен.
+
+    *) Добавление: ключ -p.
+
+    *) Добавление: ключ -s на Unix-платформах.
+
+    *) Добавление: ключи -? и -h.
+       Спасибо Jerome Loyet.
+
+    *) Добавление: теперь ключи можно задавать в сжатой форме.
+
+    *) Исправление: nginx/Windows не работал, если файл конфигурации был
+       задан ключом -c.
+
+    *) Исправление: при использовании директив proxy_store, fastcgi_store,
+       proxy_cache или fastcgi_cache временные файлы могли не удаляться.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: в заголовке Auth-Method запроса серверу аутентификации
+       почтового прокси-сервера передавалось неверное значение; ошибка
+       появилась в 0.7.34.
+       Спасибо Simon Lecaille.
+
+    *) Исправление: при логгировании на Linux не писались текстовые описания
+       системных ошибок; ошибка появилась в 0.7.45.
+
+    *) Исправление: директива fastcgi_cache_min_uses не работала.
+       Спасибо Андрею Воробьёву.
+
+
+Изменения в nginx 0.7.52                                          20.04.2009
+
+    *) Добавление: первая бинарная версия под Windows.
+
+    *) Исправление: корректная обработка метода HEAD при кэшировании.
+
+    *) Исправление: корректная обработка строк "If-Modified-Since",
+       "If-Range" и им подобных в заголовке запроса клиента при кэшировании.
+
+    *) Исправление: теперь строки "Set-Cookie" и "P3P" скрываются в
+       заголовке ответа для закэшированных ответов.
+
+    *) Исправление: если nginx был собран с модулем ngx_http_perl_module и
+       perl поддерживал потоки, то при выходе основного процесса могла
+       выдаваться ошибка "panic: MUTEX_LOCK".
+
+    *) Исправление: nginx не собирался с параметром --without-http-cache;
+       ошибка появилась в 0.7.48.
+
+    *) Исправление: nginx не собирался на платформах, отличных от i386,
+       amd64, sparc и ppc; ошибка появилась в 0.7.42.
+
+
+Изменения в nginx 0.7.51                                          12.04.2009
+
+    *) Добавление: директива try_files поддерживает код ответа в последнем
+       параметре.
+
+    *) Добавление: теперь в директиве return можно использовать любой код
+       ответа.
+
+    *) Исправление: директива error_page делала внешний редирект без строки
+       запроса; ошибка появилась в 0.7.44.
+
+    *) Исправление: если сервера слушали на нескольких явно описанных
+       адресах, то виртуальные сервера могли не работать; ошибка появилась в
+       0.7.39.
+
+
+Изменения в nginx 0.7.50                                          06.04.2009
+
+    *) Исправление: переменные $arg_... не работали; ошибка появилась в
+       0.7.49.
+
+
+Изменения в nginx 0.7.49                                          06.04.2009
+
+    *) Исправление: при использовании переменных $arg_... в рабочем процессе
+       мог произойти segmentation fault; ошибка появилась в 0.7.48.
+
+
+Изменения в nginx 0.7.48                                          06.04.2009
+
+    *) Добавление: директива proxy_cache_key.
+
+    *) Исправление: теперь nginx учитывает при кэшировании строки
+       "X-Accel-Expires", "Expires" и "Cache-Control" в заголовке ответа
+       бэкенда.
+
+    *) Исправление: теперь nginx кэширует только ответы на запросы GET.
+
+    *) Исправление: директива fastcgi_cache_key не наследовалась.
+
+    *) Исправление: переменные $arg_... не работали с SSI-подзапросами.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: nginx не собирался с библиотекой uclibc.
+       Спасибо Timothy Redaelli.
+
+    *) Исправление: nginx не собирался на OpenBSD; ошибка появилась
+       в 0.7.46.
+
+
+Изменения в nginx 0.7.47                                          01.04.2009
+
+    *) Исправление: nginx не собирался на FreeBSD 6 и более ранних версиях;
+       ошибка появилась в 0.7.46.
+
+    *) Исправление: nginx не собирался на MacOSX; ошибка появилась в 0.7.46.
+
+    *) Исправление: если использовался параметр max_size, то cache manager
+       мог удалить весь кэш; ошибка появилась в 0.7.46.
+
+    *) Изменение: в рабочем процессе мог произойти segmentation fault, если
+       директивы proxy_cache/fastcgi_cache и proxy_cache_valid/
+       fastcgi_cache_valid не были заданы на одном уровне; ошибка появилась
+       в 0.7.46.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault при
+       перенаправлении запроса проксированному или FastCGI-серверу с помощью
+       error_page или try_files; ошибка появилась в 0.7.44.
+
+
+Изменения в nginx 0.7.46                                          30.03.2009
+
+    *) Исправление: архив предыдущего релиза был неверным.
+
+
+Изменения в nginx 0.7.45                                          30.03.2009
+
+    *) Изменение: теперь директивы proxy_cache и proxy_cache_valid можно
+       задавать на разных уровнях.
+
+    *) Изменение: параметр clean_time в директиве proxy_cache_path удалён.
+
+    *) Добавление: параметр max_size в директиве proxy_cache_path.
+
+    *) Добавление: предварительная поддержка кэширования в модуле
+       ngx_http_fastcgi_module.
+
+    *) Добавление: теперь при ошибках выделения в разделяемой памяти в логе
+       указываются названия директивы и зоны.
+
+    *) Исправление: директива "add_header last-modified ''" не удаляла в
+       заголовке ответа строку "Last-Modified"; ошибка появилась в 0.7.44.
+
+    *) Исправление: в директиве auth_basic_user_file не работал
+       относительный путь, заданный строкой без переменных; ошибка появилась
+       в 0.7.44.
+       Спасибо Jerome Loyet.
+
+    *) Исправление: в директиве alias, заданной переменными без ссылок на
+       выделения в регулярных выражениях; ошибка появилась в 0.7.42.
+
+
+Изменения в nginx 0.7.44                                          23.03.2009
+
+    *) Добавление: предварительная поддержка кэширования в модуле
+       ngx_http_proxy_module.
+
+    *) Добавление: параметр --with-pcre в configure.
+
+    *) Добавление: теперь директива try_files может быть использована на
+       уровне server.
+
+    *) Исправление: директива try_files неправильно обрабатывала строку
+       запроса в последнем параметре.
+
+    *) Исправление: директива try_files могла неверно тестировать каталоги.
+
+    *) Исправление: если для пары адрес:порт описан только один сервер, то
+       выделения в регулярных выражениях в директиве server_name не
+       работали.
+
+
+Изменения в nginx 0.7.43                                          18.03.2009
+
+    *) Исправление: запрос обрабатывался неверно, если директива root
+       использовала переменные; ошибка появилась в 0.7.42.
+
+    *) Исправление: если сервер слушал на адресах типа "*", то значение
+       переменной $server_addr было "0.0.0.0"; ошибка появилась в 0.7.36.
+
+
+Изменения в nginx 0.7.42                                          16.03.2009
+
+    *) Изменение: ошибка "Invalid argument", возвращаемая
+       setsockopt(TCP_NODELAY) на Solaris, теперь игнорируется.
+
+    *) Изменение: при отсутствии файла, указанного в директиве
+       auth_basic_user_file, теперь возвращается ошибка 403 вместо 500.
+
+    *) Добавление: директива auth_basic_user_file поддерживает переменные.
+       Спасибо Кириллу Коринскому.
+
+    *) Добавление: директива listen поддерживает параметр ipv6only.
+       Спасибо Zhang Hua.
+
+    *) Исправление: в директиве alias со ссылками на выделения в регулярных
+       выражениях; ошибка появилась в 0.7.40.
+
+    *) Исправление: совместимость с Tru64 UNIX.
+       Спасибо Dustin Marquess.
+
+    *) Исправление: nginx не собирался без библиотеки PCRE; ошибка появилась
+       в 0.7.41.
+
+
+Изменения в nginx 0.7.41                                          11.03.2009
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если в server_name или location были выделения в регулярных
+       выражениях; ошибка появилась в 0.7.40.
+       Спасибо Владимиру Сопоту.
+
+
+Изменения в nginx 0.7.40                                          09.03.2009
+
+    *) Добавление: директива location поддерживает выделения в регулярных
+       выражениях.
+
+    *) Добавление: директиву alias с ссылками на выделения в регулярных
+       выражениях можно использовать внутри location'а, заданного регулярным
+       выражением с выделениями.
+
+    *) Добавление: директива server_name поддерживает выделения в регулярных
+       выражениях.
+
+    *) Изменение: модуль ngx_http_autoindex_module не показывал последний
+       слэш для каталогов на файловой системе XFS; ошибка появилась в
+       0.7.15.
+       Спасибо Дмитрию Кузьменко.
+
+
+Изменения в nginx 0.7.39                                          02.03.2009
+
+    *) Исправление: при включённом сжатии большие ответы с использованием
+       SSI могли зависать; ошибка появилась в 0.7.28.
+       Спасибо Артёму Бохану.
+
+    *) Исправление: при использовании коротких статических вариантов в
+       директиве try_files в рабочем процессе мог произойти segmentation
+       fault.
+
+
+Изменения в nginx 0.7.38                                          23.02.2009
+
+    *) Добавление: логгирование ошибок аутентификации.
+
+    *) Исправление: имя/пароль, заданные в auth_basic_user_file,
+       игнорировались после нечётного числа пустых строк.
+       Спасибо Александру Загребину.
+
+    *) Исправление: при использовании длинного пути в unix domain сокете в
+       главном процессе происходил segmentation fault; ошибка появилась в
+       0.7.36.
+
+
+Изменения в nginx 0.7.37                                          21.02.2009
+
+    *) Исправление: директивы, использующие upstream'ы, не работали; ошибка
+       появилась в 0.7.36.
+
+
+Изменения в nginx 0.7.36                                          21.02.2009
+
+    *) Добавление: предварительная поддержка IPv6; директива listen модуля
+       HTTP поддерживает IPv6.
+
+    *) Исправление: переменная $ancient_browser не работала для браузеров,
+       заданных директивами modern_browser.
+
+
+Изменения в nginx 0.7.35                                          16.02.2009
+
+    *) Исправление: директива ssl_engine не использовала SSL-акселератор для
+       асимметричных шифров.
+       Спасибо Marcin Gozdalik.
+
+    *) Исправление: директива try_files выставляла MIME-type, исходя из
+       расширения первоначального запроса.
+
+    *) Исправление: в директивах server_name, valid_referers и map
+       неправильно обрабатывались имена вида "*domain.tld", если
+       использовались маски вида ".domain.tld" и ".subdomain.domain.tld";
+       ошибка появилась в 0.7.9.
+
+
+Изменения в nginx 0.7.34                                          10.02.2009
+
+    *) Добавление: параметр off в директиве if_modified_since.
+
+    *) Добавление: теперь после команды XCLIENT nginx посылает команду
+       HELO/EHLO.
+       Спасибо Максиму Дунину.
+
+    *) Добавление: поддержка Microsoft-специфичного режима
+       "AUTH LOGIN with User Name" в почтовом прокси-сервере.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: в директиве rewrite, возвращающей редирект, старые
+       аргументы присоединялись к новым через символ "?" вместо "&";
+       ошибка появилась в 0.1.18.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: nginx не собирался на AIX.
+
+
+Изменения в nginx 0.7.33                                          02.02.2009
+
+    *) Исправление: если на запрос с телом возвращался редирект, то ответ
+       мог быть двойным при использовании методов epoll или rtsig.
+       Спасибо Eden Li.
+
+    *) Исправление: для некоторых типов редиректов в переменной
+       $sent_http_location было пустое значение.
+
+    *) Исправление: при использовании директивы resolver в SMTP
+       прокси-сервере в рабочем процессе мог произойти segmentation fault.
+
+
+Изменения в nginx 0.7.32                                          26.01.2009
+
+    *) Добавление: теперь в директиве try_files можно явно указать проверку
+       каталога.
+
+    *) Исправление: fastcgi_store не всегда сохранял файлы.
+
+    *) Исправление: в гео-диапазонах.
+
+    *) Исправление: ошибки выделения больших блоков в разделяемой памяти,
+       если nginx был собран без отладки.
+       Спасибо Андрею Квасову.
+
+
+Изменения в nginx 0.7.31                                          19.01.2009
+
+    *) Изменение: теперь директива try_files проверяет только файлы,
+       игнорируя каталоги.
+
+    *) Добавление: директива fastcgi_split_path_info.
+
+    *) Исправления в поддержке строки "Expect" в заголовке запроса.
+
+    *) Исправления в гео-диапазонах.
+
+    *) Исправление: при отсутствии ответа ngx_http_memcached_module
+       возвращал в теле ответа строку "END" вместо 404-ой страницы по
+       умолчанию; ошибка появилась в 0.7.18.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: при проксировании SMTP nginx выдавал сообщение
+       "250 2.0.0 OK" вместо "235 2.0.0 OK"; ошибка появилась в 0.7.22.
+       Спасибо Максиму Дунину.
+
+
+Изменения в nginx 0.7.30                                          24.12.2008
+
+    *) Исправление: в рабочем процессе происходил segmentation fault, если в
+       директивах fastcgi_pass или proxy_pass использовались переменные и
+       имя хоста должно было резолвиться; ошибка появилась в 0.7.29.
+
+
+Изменения в nginx 0.7.29                                          24.12.2008
+
+    *) Исправление: директивы fastcgi_pass и proxy_pass не поддерживали
+       переменные при использовании unix domain сокетов.
+
+    *) Исправления в обработке подзапросов; ошибки появились в 0.7.25.
+
+    *) Исправление: ответ "100 Continue" выдавался для запросов версии
+       HTTP/1.0;
+       Спасибо Максиму Дунину.
+
+    *) Исправление: в выделении памяти в модуле ngx_http_gzip_filter_module
+       под Cygwin.
+
+
+Изменения в nginx 0.7.28                                          22.12.2008
+
+    *) Изменение: в выделении памяти в модуле ngx_http_gzip_filter_module.
+
+    *) Изменение: значения по умолчанию для директивы gzip_buffers изменены
+       с 4 4k/8k на 32 4k или 16 8k.
+
+
+Изменения в nginx 0.7.27                                          15.12.2008
+
+    *) Добавление: директива try_files.
+
+    *) Добавление: директива fastcgi_pass поддерживает переменные.
+
+    *) Добавление: теперь директива geo может брать адрес из переменной.
+       Спасибо Андрею Нигматулину.
+
+    *) Добавление: теперь модификатор location'а можно указывать без пробела
+       перед названием.
+
+    *) Добавление: переменная $upstream_response_length.
+
+    *) Исправление: теперь директива add_header не добавляет пустое
+       значение.
+
+    *) Исправление: при запросе файла нулевой длины nginx закрывал
+       соединение, ничего не передав; ошибка появилась в 0.7.25.
+
+    *) Исправление: метод MOVE не мог перемещать файл в несуществующий
+       каталог.
+
+    *) Исправление: если в сервере не был описан ни один именованный
+       location, но такой location использовался в директиве error_page, то
+       в рабочем процессе происходил segmentation fault.
+       Спасибо Сергею Боченкову.
+
+
+Изменения в nginx 0.7.26                                          08.12.2008
+
+    *) Исправление: в обработке подзапросов; ошибка появилась в 0.7.25.
+
+
+Изменения в nginx 0.7.25                                          08.12.2008
+
+    *) Изменение: в обработке подзапросов.
+
+    *) Изменение: теперь разрешаются POST'ы без строки "Content-Length" в
+       заголовке запроса.
+
+    *) Исправление: теперь директивы limit_req и limit_conn указывают
+       причину запрета запроса.
+
+    *) Исправление: в параметре delete директивы geo.
+
+
+Изменения в nginx 0.7.24                                          01.12.2008
+
+    *) Добавление: директива if_modified_since.
+
+    *) Исправление: nginx не обрабатывал ответ FastCGI-сервера, если перед
+       ответом сервер передавал много сообщений в stderr.
+
+    *) Исправление: переменные "$cookie_..." не работали в SSI and в
+       перловом модуле.
+
+
+Изменения в nginx 0.7.23                                          27.11.2008
+
+    *) Добавление: параметры delete и ranges в директиве geo.
+
+    *) Добавление: ускорение загрузки geo-базы с большим числом значений.
+
+    *) Добавление: уменьшение памяти, необходимой для загрузки geo-базы.
+
+
+Изменения в nginx 0.7.22                                          20.11.2008
+
+    *) Добавление: параметр none в директиве smtp_auth.
+       Спасибо Максиму Дунину.
+
+    *) Добавление: переменные "$cookie_...".
+
+    *) Исправление: директива directio не работала с файловой системой XFS.
+
+    *) Исправление: resolver не понимал большие DNS-ответы.
+       Спасибо Zyb.
+
+
+Изменения в nginx 0.7.21                                          11.11.2008
+
+    *) Изменения в модуле ngx_http_limit_req_module.
+
+    *) Добавление: поддержка EXSLT в модуле ngx_http_xslt_module.
+       Спасибо Денису Латыпову.
+
+    *) Изменение: совместимость с glibc 2.3.
+       Спасибо Eric Benson и Максиму Дунину.
+
+    *) Исправление: nginx не запускался на MacOSX 10.4 и более ранних;
+       ошибка появилась в 0.7.6.
+
+
+Изменения в nginx 0.7.20                                          10.11.2008
+
+    *) Изменения в модуле ngx_http_gzip_filter_module.
+
+    *) Добавление: модуль ngx_http_limit_req_module.
+
+    *) Исправление: на платформах sparc и ppc рабочие процессы могли
+       выходить по сигналу SIGBUS; ошибка появилась в 0.7.3.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: директивы вида "proxy_pass http://host/some:uri" не
+       работали; ошибка появилась в 0.7.12.
+
+    *) Исправление: при использовании HTTPS запросы могли завершаться с
+       ошибкой "bad write retry".
+
+    *) Исправление: модуль ngx_http_secure_link_module не работал внутри
+       location'ов с именами меньше 3 символов.
+
+    *) Исправление: переменная $server_addr могла не иметь значения.
+
+
+Изменения в nginx 0.7.19                                          13.10.2008
+
+    *) Исправление: обновление номера версии.
+
+
+Изменения в nginx 0.7.18                                          13.10.2008
+
+    *) Изменение: директива underscores_in_headers; теперь nginx по
+       умолчанию не разрешает подчёркивания в именах строк в заголовке
+       запроса клиента.
+
+    *) Добавление: модуль ngx_http_secure_link_module.
+
+    *) Добавление: директива real_ip_header поддерживает любой заголовок.
+
+    *) Добавление: директива log_subrequest.
+
+    *) Добавление: переменная $realpath_root.
+
+    *) Добавление: параметры http_502 и http_504 в директиве
+       proxy_next_upstream.
+
+    *) Исправление: параметр http_503 в директивах proxy_next_upstream или
+       fastcgi_next_upstream не работал.
+
+    *) Исправление: nginx мог выдавать строку "Transfer-Encoding: chunked"
+       для запросов HEAD.
+
+    *) Исправление: теперь accept-лимит зависит от числа worker_connections.
+
+
+Изменения в nginx 0.7.17                                          15.09.2008
+
+    *) Добавление: директива directio теперь работает на Linux.
+
+    *) Добавление: переменная $pid.
+
+    *) Исправление: оптимизация directio, появившаяся в 0.7.15, не работала
+       при использовании open_file_cache.
+
+    *) Исправление: access_log с переменными не работал на Linux; ошибка
+       появилась в 0.7.7.
+
+    *) Исправление: модуль ngx_http_charset_module не понимал название
+       кодировки в кавычках, полученное от бэкенда.
+
+
+Изменения в nginx 0.7.16                                          08.09.2008
+
+    *) Исправление: nginx не собирался на 64-битных платформах; ошибка
+       появилась в 0.7.15.
+
+
+Изменения в nginx 0.7.15                                          08.09.2008
+
+    *) Добавление: модуль ngx_http_random_index_module.
+
+    *) Добавление: директива directio оптимизирована для запросов файлов,
+       начинающихся с произвольной позиции.
+
+    *) Добавление: директива directio при необходимости запрещает
+       использование sendfile.
+
+    *) Добавление: теперь nginx разрешает подчёркивания в именах строк в
+       заголовке запроса клиента.
+
+
+Изменения в nginx 0.7.14                                          01.09.2008
+
+    *) Изменение: теперь директивы ssl_certificate и ssl_certificate_key не
+       имеют значений по умолчанию.
+
+    *) Добавление: директива listen поддерживает параметр ssl.
+
+    *) Добавление: теперь при переконфигурации nginx учитывает изменение
+       временной зоны на FreeBSD и Linux.
+
+    *) Исправление: параметры директивы listen, такие как backlog, rcvbuf и
+       прочие, не устанавливались, если сервером по умолчанию был не первый
+       сервер.
+
+    *) Исправление: при использовании в качестве аргументов части URI,
+       выделенного с помощью директивы rewrite, эти аргументы не
+       экранировались.
+
+    *) Исправление: улучшения тестирования правильности конфигурационного
+       файла.
+
+
+Изменения в nginx 0.7.13                                          26.08.2008
+
+    *) Исправление: nginx не собирался на Linux и Solaris; ошибка появилась
+       в 0.7.12.
+
+
+Изменения в nginx 0.7.12                                          26.08.2008
+
+    *) Добавление: директива server_name поддерживает пустое имя "".
+
+    *) Добавление: директива gzip_disable поддерживает специальную маску
+       msie6.
+
+    *) Исправление: при использовании параметра max_fails=0 в upstream'е с
+       несколькими серверами рабочий процесс выходил по сигналу SIGFPE.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: при перенаправлении запроса с помощью директивы
+       error_page терялось тело запроса.
+
+    *) Исправление: при перенаправлении запроса с методом HEAD с помощью
+       директивы error_page возвращался полный ответ.
+
+    *) Исправление: метод $r->header_in() не возвращал значения строк
+       "Host", "User-Agent", и "Connection" из заголовка запроса; ошибка
+       появилась в 0.7.0.
+
+
+Изменения в nginx 0.7.11                                          18.08.2008
+
+    *) Изменение: теперь ngx_http_charset_module по умолчанию не работает
+       MIME-типом text/css.
+
+    *) Добавление: теперь nginx возвращает код 405 для метода POST при
+       запросе статического файла, только если файл существует.
+
+    *) Добавление: директива proxy_ssl_session_reuse.
+
+    *) Исправление: после перенаправления запроса с помощью
+       "X-Accel-Redirect" директива proxy_pass без URI могла использовать
+       оригинальный запрос.
+
+    *) Исправление: если у каталога были права доступа только на поиск
+       файлов и первый индексный файл отсутствовал, то nginx возвращал
+       ошибку 500.
+
+    *) Исправление: ошибок во вложенных location'ах; ошибки появились в
+       0.7.1.
+
+
+Изменения в nginx 0.7.10                                          13.08.2008
+
+    *) Исправление: ошибок в директивах addition_types, charset_types,
+       gzip_types, ssi_types, sub_filter_types и xslt_types; ошибки
+       появились в 0.7.9.
+
+    *) Исправление: рекурсивной error_page для 500 ошибки.
+
+    *) Исправление: теперь модуль ngx_http_realip_module устанавливает адрес
+       не для всего keepalive соединения, а для каждого запроса по этому
+       соединению.
+
+
+Изменения в nginx 0.7.9                                           12.08.2008
+
+    *) Изменение: теперь ngx_http_charset_module по умолчанию работает со
+       следующими MIME-типами: text/html, text/css, text/xml, text/plain,
+       text/vnd.wap.wml, application/x-javascript и application/rss+xml.
+
+    *) Добавление: директивы charset_types и addition_types.
+
+    *) Добавление: теперь директивы gzip_types, ssi_types и sub_filter_types
+       используют хэш.
+
+    *) Добавление: модуль ngx_cpp_test_module.
+
+    *) Добавление: директива expires поддерживает суточное время.
+
+    *) Добавление: улучшения и исправления в модуле ngx_http_xslt_module.
+       Спасибо Денису Латыпову и Максиму Дунину.
+
+    *) Исправление: директива log_not_found не работала при поиске индексных
+       файлов.
+
+    *) Исправление: HTTPS-соединения могли зависнуть, если использовались
+       методы kqueue, epoll, rtsig или eventport; ошибка появилась в 0.7.7.
+
+    *) Исправление: если в директивах server_name, valid_referers и map
+       использовалась маска вида "*.domain.tld" и при этом полное имя вида
+       "domain.tld" не было описано, то это имя попадало под маску; ошибка
+       появилась в 0.3.18.
+
+
+Изменения в nginx 0.7.8                                           04.08.2008
+
+    *) Добавление: модуль ngx_http_xslt_module.
+
+    *) Добавление: переменные "$arg_...".
+
+    *) Добавление: поддержка directio в Solaris.
+       Спасибо Ivan Debnar.
+
+    *) Исправление: теперь, если FastCGI-сервер присылает строку "Location"
+       в заголовке ответа без строки статуса, то nginx использует код
+       статуса 302.
+       Спасибо Максиму Дунину.
+
+
+Изменения в nginx 0.7.7                                           30.07.2008
+
+    *) Изменение: теперь ошибка EAGAIN при вызове connect() не считается
+       временной.
+
+    *) Изменение: значением переменной $ssl_client_cert теперь является
+       сертификат, перед каждой строкой которого, кроме первой, вставляется
+       символ табуляции; неизменённый сертификат доступен через переменную
+       $ssl_client_raw_cert.
+
+    *) Добавление: параметр ask директивы ssl_verify_client.
+
+    *) Добавление: улучшения в обработке byte-range.
+       Спасибо Максиму Дунину.
+
+    *) Добавление: директива directio.
+       Спасибо Jiang Hong.
+
+    *) Добавление: поддержка sendfile() в MacOSX 10.5.
+
+    *) Исправление: в MacOSX и Cygwin при проверке location'ов теперь
+       делается сравнение без учёта регистра символов; однако, сравнение
+       ограничено только однобайтными locale'ями.
+
+    *) Исправление: соединения почтового прокси-сервера зависали в режиме
+       SSL, если использовались методы select, poll или /dev/poll.
+
+    *) Исправление: ошибки при использовании кодировки UTF-8 в
+       ngx_http_autoindex_module.
+
+
+Изменения в nginx 0.7.6                                           07.07.2008
+
+    *) Исправление: теперь при использовании переменных в директиве
+       access_log всегда проверяется существовании root'а для запроса.
+
+    *) Исправление: модуль ngx_http_flv_module не поддерживал несколько
+       значений в аргументах запроса.
+
+
+Изменения в nginx 0.7.5                                           01.07.2008
+
+    *) Исправления в поддержке переменных в директиве access_log; ошибки
+       появились в 0.7.4.
+
+    *) Исправление: nginx не собирался с параметром
+       --without-http_gzip_module; ошибка появилась в 0.7.3.
+       Спасибо Кириллу Коринскому.
+
+    *) Исправление: при совместном использовании sub_filter и SSI ответы
+       могли передаваться неверно.
+
+
+Изменения в nginx 0.7.4                                           30.06.2008
+
+    *) Добавление: директива access_log поддерживает переменные.
+
+    *) Добавление: директива open_log_file_cache.
+
+    *) Добавление: ключ -g.
+
+    *) Добавление: поддержка строки "Expect" в заголовке запроса.
+
+    *) Исправление: большие включения в SSI могли передавались не полностью.
+
+
+Изменения в nginx 0.7.3                                           23.06.2008
+
+    *) Изменение: MIME-тип для расширения rss изменён на
+       "application/rss+xml".
+
+    *) Изменение: теперь директива "gzip_vary on" выдаёт строку
+       "Vary: Accept-Encoding" в заголовке ответа и для несжатых ответов.
+
+    *) Добавление: теперь при использовании протокола "https://" в директиве
+       rewrite автоматически делается редирект.
+
+    *) Исправление: директива proxy_pass не работала с протоколом HTTPS;
+       ошибка появилась в 0.6.9.
+
+
+Изменения в nginx 0.7.2                                           16.06.2008
+
+    *) Добавление: теперь nginx поддерживает шифры с обменом EDH-ключами.
+
+    *) Добавление: директива ssl_dhparam.
+
+    *) Добавление: переменная $ssl_client_cert.
+       Спасибо Manlio Perillo.
+
+    *) Исправление: после изменения URI с помощью директивы rewrite nginx не
+       искал новый location; ошибка появилась в 0.7.1.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: nginx не собирался без библиотеки PCRE; ошибка появилась
+       в 0.7.1.
+
+    *) Исправление: при редиректе запроса к каталогу с добавлением слэша
+       nginx не добавлял аргументы из оригинального запроса.
+
+
+Изменения в nginx 0.7.1                                           26.05.2008
+
+    *) Изменение: теперь поиск location'а делается с помощью дерева.
+
+    *) Изменение: директива optimize_server_names упразднена в связи с
+       появлением директивы server_name_in_redirect.
+
+    *) Изменение: некоторые давно устаревшие директивы больше не
+       поддерживаются.
+
+    *) Изменение: параметр "none" в директиве ssl_session_cache; теперь этот
+       параметр используется по умолчанию.
+       Спасибо Rob Mueller.
+
+    *) Исправление: рабочие процессы могли не реагировать на сигналы
+       переконфигурации и ротации логов.
+
+    *) Исправление: nginx не собирался на последних Fedora 9 Linux.
+       Спасибо Roxis.
+
+
+Изменения в nginx 0.7.0                                           19.05.2008
+
+    *) Изменение: теперь символы 0x00-0x1F, '"' и '\' в access_log
+       записываются в виде \xXX.
+       Спасибо Максиму Дунину.
+
+    *) Изменение: теперь nginx разрешает несколько строк "Host" в заголовке
+       запроса.
+
+    *) Добавление: директива expires поддерживает флаг modified.
+
+    *) Добавление: переменные $uid_got и $uid_set можно использовать на
+       любой стадии обработки запроса.
+
+    *) Добавление: переменная $hostname.
+       Спасибо Андрею Нигматулину.
+
+    *) Добавление: поддержка DESTDIR.
+       Спасибо Todd A. Fisher и Andras Voroskoi.
+
+    *) Исправление: при использовании keepalive на Linux в рабочем процессе
+       мог произойти segmentation fault.
+
+
+Изменения в nginx 0.6.31                                          12.05.2008
+
+    *) Исправление: nginx не обрабатывал ответ FastCGI-сервера, если строка
+       заголовка ответа была в конце записи FastCGI; ошибка появилась в
+       0.6.2.
+       Спасибо Сергею Серову.
+
+    *) Исправление: при удалении файла и использовании директивы
+       open_file_cache_errors off в рабочем процессе мог произойти
+       segmentation fault.
+
+
+Изменения в nginx 0.6.30                                          29.04.2008
+
+    *) Изменение: теперь, если маске, заданной в директиве include, не
+       соответствует ни один файл, то nginx не выдаёт ошибку.
+
+    *) Добавление: теперь время в директивах можно задавать без пробела,
+       например, "1h50m".
+
+    *) Исправление: утечек памяти, если директива ssl_verify_client имела
+       значение on.
+       Спасибо Chavelle Vincent.
+
+    *) Исправление: директива sub_filter могла вставлять заменяемый текст в
+       вывод.
+
+    *) Исправление: директива error_page не воспринимала параметры в
+       перенаправляемом URI.
+
+    *) Исправление: теперь при сборке с Cygwin nginx всегда открывает файлы
+       в бинарном режиме.
+
+    *) Исправление: nginx не собирался под OpenBSD; ошибка появилась в
+       0.6.15.
+
+
+Изменения в nginx 0.6.29                                          18.03.2008
+
+    *) Добавление: модуль ngx_google_perftools_module.
+
+    *) Исправление: модуль ngx_http_perl_module не собирался на 64-битных
+       платформах; ошибка появилась в 0.6.27.
+
+
+Изменения в nginx 0.6.28                                          13.03.2008
+
+    *) Исправление: метод rtsig не собирался; ошибка появилась в 0.6.27.
+
+
+Изменения в nginx 0.6.27                                          12.03.2008
+
+    *) Изменение: теперь на Linux 2.6.18+ по умолчанию не собирается метод
+       rtsig.
+
+    *) Изменение: теперь при перенаправлении запроса в именованный location
+       с помощью директивы error_page метод запроса не изменяется.
+
+    *) Добавление: директивы resolver и resolver_timeout в SMTP
+       прокси-сервере.
+
+    *) Добавление: директива post_action поддерживает именованные
+       location'ы.
+
+    *) Исправление: при перенаправлении запроса из location'а c обработчиком
+       proxy, FastCGI или memcached в именованный location со статическим
+       обработчиком в рабочем процессе происходил segmentation fault.
+
+    *) Исправление: браузеры не повторяли SSL handshake, если при первом
+       handshake не оказалось правильного клиентского сертификата.
+       Спасибо Александру Инюхину.
+
+    *) Исправление: при перенаправлении ошибок 495-497 с помощью директивы
+       error_page без изменения кода ошибки nginx пытался выделить очень
+       много памяти.
+
+    *) Исправление: утечки памяти в долгоживущих небуфферизированных
+       соединениях.
+
+    *) Исправление: утечки памяти в resolver'е.
+
+    *) Исправление: при перенаправлении запроса из location'а c обработчиком
+       proxy в другой location с обработчиком proxy в рабочем процессе
+       происходил segmentation fault.
+
+    *) Исправление: ошибки в кэшировании переменных $proxy_host и
+       $proxy_port.
+       Спасибо Сергею Боченкову.
+
+    *) Исправление: директива proxy_pass с переменными использовала порт,
+       описанной в другой директиве proxy_pass без переменных, но с таким же
+       именем хоста.
+       Спасибо Сергею Боченкову.
+
+    *) Исправление: во время переконфигурации на некоторых 64-битном
+       платформах в лог записывался alert "sendmsg() failed (9: Bad file
+       descriptor)".
+
+    *) Исправление: при повторном использовании в SSI пустого block'а в
+       качестве заглушки в рабочем процессе происходил segmentation fault.
+
+    *) Исправление: ошибки при копировании части URI, содержащего
+       экранированные символы, в аргументы.
+
+
+Изменения в nginx 0.6.26                                          11.02.2008
+
+    *) Исправление: директивы proxy_store и fastcgi_store не проверяли длину
+       ответа.
+
+    *) Исправление: при использовании большого значения в директиве expires
+       в рабочем процессе происходил segmentation fault.
+       Спасибо Joaquin Cuenca Abela.
+
+    *) Исправление: nginx неверно определял длину строки кэша на Pentium 4.
+       Спасибо Геннадию Махомеду.
+
+    *) Исправление: в проксированных подзапросах и подзапросах к
+       FastCGI-серверу вместо метода GET использовался оригинальный метод
+       клиента.
+
+    *) Исправление: утечки сокетов в режиме HTTPS при использовании
+       отложенного accept'а.
+       Спасибо Ben Maurer.
+
+    *) Исправление: nginx выдавал ошибочное сообщение "SSL_shutdown() failed
+       (SSL: )"; ошибка появилась в 0.6.23.
+
+    *) Исправление: при использовании HTTPS запросы могли завершаться с
+       ошибкой "bad write retry"; ошибка появилась в 0.6.23.
+
+
+Изменения в nginx 0.6.25                                          08.01.2008
+
+    *) Изменение: вместо специального параметра "*" в директиве server_name
+       теперь используется директива server_name_in_redirect.
+
+    *) Изменение: в качестве основного имени в директиве server_name теперь
+       можно использовать имена с масками и регулярными выражениями.
+
+    *) Изменение: директива satisfy_any заменена директивой satisfy.
+
+    *) Изменение: после переконфигурации старые рабочие процесс могли сильно
+       нагружать процессор при запуске под Linux OpenVZ.
+
+    *) Добавление: директива min_delete_depth.
+
+    *) Исправление: методы COPY и MOVE не работали с одиночными файлами.
+
+    *) Исправление: модуль ngx_http_gzip_static_module не позволял работать
+       модулю ngx_http_dav_module; ошибка появилась в 0.6.23.
+
+    *) Исправление: утечки сокетов в режиме HTTPS при использовании
+       отложенного accept'а.
+       Спасибо Ben Maurer.
+
+    *) Исправление: nginx не собирался без библиотеки PCRE; ошибка появилась
+       в 0.6.23.
+
+
+Изменения в nginx 0.6.24                                          27.12.2007
+
+    *) Исправление: при использовании HTTPS в рабочем процессе мог произойти
+       segmentation fault; ошибка появилась в 0.6.23.
+
+
+Изменения в nginx 0.6.23                                          27.12.2007
+
+    *) Изменение: параметр "off" в директиве ssl_session_cache; теперь этот
+       параметр используется по умолчанию.
+
+    *) Изменение: директива open_file_cache_retest переименована в
+       open_file_cache_valid.
+
+    *) Добавление: директива open_file_cache_min_uses.
+
+    *) Добавление: модуль ngx_http_gzip_static_module.
+
+    *) Добавление: директива gzip_disable.
+
+    *) Добавление: директиву memcached_pass можно использовать внутри блока
+       if.
+
+    *) Исправление: если внутри одного location'а использовались директивы
+       "memcached_pass" и "if", то в рабочем процессе происходил
+       segmentation fault.
+
+    *) Исправление: если при использовании директивы satisfy_any on" были
+       заданы директивы не всех модулей доступа, то заданные директивы не
+       проверялись.
+
+    *) Исправление: параметры, заданные регулярным выражением в директиве
+       valid_referers, не наследовалась с предыдущего уровня.
+
+    *) Исправление: директива post_action не работала, если запрос
+       завершался с кодом 499.
+
+    *) Исправление: оптимизация использования 16K буфера для SSL-соединения.
+       Спасибо Ben Maurer.
+
+    *) Исправление: STARTTLS в режиме SMTP не работал.
+       Спасибо Олегу Мотиенко.
+
+    *) Исправление: при использовании HTTPS запросы могли завершаться с
+       ошибкой "bad write retry"; ошибка появилась в 0.5.13.
+
+
+Изменения в nginx 0.6.22                                          19.12.2007
+
+    *) Изменение: теперь все методы модуля ngx_http_perl_module возвращают
+       значения, скопированные в память, выделенную perl'ом.
+
+    *) Исправление: если nginx был собран с модулем ngx_http_perl_module,
+       использовался perl до версии 5.8.6 и perl поддерживал потоки, то во
+       время переконфигурации основной процесс аварийно выходил; ошибка
+       появилась в 0.5.9.
+       Спасибо Борису Жмурову.
+
+    *) Исправление: в методы модуля ngx_http_perl_module могли передаваться
+       неверные результаты выделения в регулярных выражениях.
+
+    *) Исправление: если метод $r->has_request_body() вызывался для запроса,
+       у которого небольшое тело запроса было уже полностью получено, то в
+       рабочем процессе происходил segmentation fault.
+
+    *) Исправление: large_client_header_buffers не освобождались перед
+       переходом в состояние keep-alive.
+       Спасибо Олександру Штепе.
+
+    *) Исправление: в переменной $upstream_addr не записывался последний
+       адрес; ошибка появилась в 0.6.18.
+
+    *) Исправление: директива fastcgi_catch_stderr не возвращала ошибку;
+       теперь она возвращает ошибку 502, которую можно направить на
+       следующий сервер с помощью "fastcgi_next_upstream invalid_header".
+
+    *) Исправление: при использовании директивы fastcgi_catch_stderr в
+       основном процессе происходил segmentation fault; ошибка появилась в
+       0.6.10.
+       Спасибо Manlio Perillo.
+
+
+Изменения в nginx 0.6.21                                          03.12.2007
+
+    *) Изменение: если в значениях переменных директивы proxy_pass
+       используются только IP-адреса, то указывать resolver не нужно.
+
+    *) Исправление: при использовании директивы proxy_pass c URI-частью в
+       рабочем процессе мог произойти segmentation fault; ошибка появилась в
+       0.6.19.
+
+    *) Исправление: если resolver использовался на платформах, не
+       поддерживающих метод kqueue, то nginx выдавал alert "name is out of
+       response".
+       Спасибо Андрею Нигматулину.
+
+    *) Исправление: При использовании переменной $server_protocol в
+       FastCGI-параметрах и запросе, длина которого была близка к значению
+       директивы client_header_buffer_size, nginx выдавал alert "fastcgi:
+       the request record is too big".
+
+    *) Исправление: при обычном запросе версии HTTP/0.9 к HTTPS серверу
+       nginx возвращал обычный ответ.
+
+
+Изменения в nginx 0.6.20                                          28.11.2007
+
+    *) Исправление: при использовании директивы proxy_pass c URI-частью в
+       рабочем процессе мог произойти segmentation fault; ошибка появилась в
+       0.6.19.
+
+
+Изменения в nginx 0.6.19                                          27.11.2007
+
+    *) Исправление: версия 0.6.18 не собиралась.
+
+
+Изменения в nginx 0.6.18                                          27.11.2007
+
+    *) Изменение: теперь модуль ngx_http_userid_module в поле куки с номером
+       процесса добавляет микросекунды на время старта.
+
+    *) Изменение: в error_log теперь записывается полная строка запроса
+       вместо только URI.
+
+    *) Добавление: директива proxy_pass поддерживает переменные.
+
+    *) Добавление: директивы resolver и resolver_timeout.
+
+    *) Добавление: теперь директива "add_header last-modified ''" удаляет в
+       заголовке ответа строку "Last-Modified".
+
+    *) Исправление: директива limit_rate не позволяла передавать на полной
+       скорости, даже если был указан очень большой лимит.
+
+
+Изменения в nginx 0.6.17                                          15.11.2007
+
+    *) Добавление: поддержка строки "If-Range" в заголовке запроса.
+       Спасибо Александру Инюхину.
+
+    *) Исправление: при использовании директивы msie_refresh повторно
+       экранировались уже экранированные символы; ошибка появилась в 0.6.4.
+
+    *) Исправление: директива autoindex не работала при использовании "alias
+       /".
+
+    *) Исправление: при использовании подзапросов в рабочем процессе мог
+       произойти segmentation fault.
+
+    *) Исправление: при использовании SSL и gzip большие ответы могли
+       передаваться не полностью.
+
+    *) Исправление: если ответ проксированного сервера был версии HTTP/0.9,
+       то переменная $status была равна 0.
+
+
+Изменения в nginx 0.6.16                                          29.10.2007
+
+    *) Изменение: теперь на Linux используется uname(2) вместо procfs.
+       Спасибо Илье Новикову.
+
+    *) Исправление: если в директиве error_page использовался символ "?", то
+       он экранировался при проксировании запроса; ошибка появилась в
+       0.6.11.
+
+    *) Исправление: совместимость с mget.
+
+
+Изменения в nginx 0.6.15                                          22.10.2007
+
+    *) Добавление: совместимость с Cygwin.
+       Спасибо Владимиру Кутакову.
+
+    *) Добавление: директива merge_slashes.
+
+    *) Добавление: директива gzip_vary.
+
+    *) Добавление: директива server_tokens.
+
+    *) Исправление: nginx не раскодировал URI в команде SSI include.
+
+    *) Исправление: при использовании переменной в директивах charset или
+       source_charset на старте или во время переконфигурации происходил
+       segmentation fault,
+
+    *) Исправление: nginx возвращал ошибку 400 на запросы вида
+       "GET http://www.domain.com HTTP/1.0".
+       Спасибо James Oakley.
+
+    *) Исправление: после перенаправления запроса с телом запроса с помощью
+       директивы error_page nginx пытался снова прочитать тело запроса;
+       ошибка появилась в 0.6.7.
+
+    *) Исправление: в рабочем процессе происходил segmentation fault, если у
+       сервера, обрабатывающему запрос, не был явно определён server_name;
+       ошибка появилась в 0.6.7.
+
+
+Изменения в nginx 0.6.14                                          15.10.2007
+
+    *) Изменение: теперь по умолчанию команда SSI echo использует
+       кодирование entity.
+
+    *) Добавление: параметр encoding в команде SSI echo.
+
+    *) Добавление: директиву access_log можно использовать внутри блока
+       limit_except.
+
+    *) Исправление: если все сервера апстрима оказывались недоступными, то
+       до восстановления работоспособности у всех серверов вес становился
+       равным одному; ошибка появилась в 0.6.6.
+
+    *) Исправление: при использовании переменных $date_local и $date_gmt вне
+       модуля ngx_http_ssi_filter_module в рабочем процессе происходил
+       segmentation fault.
+
+    *) Исправление: при использовании включённом отладочном логе в рабочем
+       процессе мог произойти segmentation fault.
+       Спасибо Андрею Нигматулину.
+
+    *) Исправление: ngx_http_memcached_module не устанавливал
+       $upstream_response_time.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: рабочий процесс мог зациклиться при использовании
+       memcached.
+
+    *) Исправление: nginx распознавал параметры "close" и "keep-alive" в
+       строке "Connection" в заголовке запроса только, если они были в
+       нижнем регистре; ошибка появилась в 0.6.11.
+
+    *) Исправление: sub_filter не работал с пустой строкой замены.
+
+    *) Исправление: в парсинге sub_filter.
+
+
+Изменения в nginx 0.6.13                                          24.09.2007
+
+    *) Исправление: nginx не закрывал файл каталога для запроса HEAD, если
+       использовался autoindex
+       Спасибо Arkadiusz Patyk.
+
+
+Изменения в nginx 0.6.12                                          21.09.2007
+
+    *) Изменение: почтовый прокси-сервер разделён на три модуля: pop3, imap
+       и smtp.
+
+    *) Добавление: параметры конфигурации --without-mail_pop3_module,
+       --without-mail_imap_module и --without-mail_smtp_module.
+
+    *) Добавление: директивы smtp_greeting_delay и smtp_client_buffer модуля
+       ngx_mail_smtp_module.
+
+    *) Исправление: wildcard в конце имени сервера не работали; ошибка
+       появилась в 0.6.9.
+
+    *) Исправление: при использовании разделяемой библиотеки PCRE,
+       расположенной в нестандартном месте, nginx не запускался на Solaris.
+
+    *) Исправление: директивы proxy_hide_header и fastcgi_hide_header не
+       скрывали строки заголовка ответа с именем больше 32 символов.
+       Спасибо Manlio Perillo.
+
+
+Изменения в nginx 0.6.11                                          11.09.2007
+
+    *) Исправление: счётчик активных соединений всегда рос при использовании
+       почтового прокси-сервера.
+
+    *) Исправление: если бэкенд возвращал только заголовок ответа при
+       небуферизированном проксировании, то nginx закрывал соединение с
+       бэкендом по таймауту.
+
+    *) Исправление: nginx не поддерживал несколько строк "Connection" в
+       заголовке запроса.
+
+    *) Исправление: если в сервере апстрима был задан max_fails, то после
+       первой же неудачной попытки вес сервера навсегда становился равным
+       одному; ошибка появилась в 0.6.6.
+
+
+Изменения в nginx 0.6.10                                          03.09.2007
+
+    *) Добавление: директивы open_file_cache, open_file_cache_retest и
+       open_file_cache_errors.
+
+    *) Исправление: утечки сокетов; ошибка появилась в 0.6.7.
+
+    *) Исправление: В строку заголовка ответа "Content-Type", указанную в
+       методе $r->send_http_header(), не добавлялась кодировка, указанная в
+       директиве charset.
+
+    *) Исправление: при использовании метода /dev/poll в рабочем процессе
+       мог произойти segmentation fault.
+
+
+Изменения в nginx 0.6.9                                           28.08.2007
+
+    *) Исправление: рабочий процесс мог зациклиться при использовании
+       протокола HTTPS; ошибка появилась в 0.6.7.
+
+    *) Исправление: если сервер слушал на двух адресах или портах, то nginx
+       не запускался при использовании wildcard в конце имени сервера.
+
+    *) Исправление: директива ip_hash могла неверно помечать сервера как
+       нерабочие.
+
+    *) Исправление: nginx не собирался на amd64; ошибка появилась в 0.6.8.
+
+
+Изменения в nginx 0.6.8                                           20.08.2007
+
+    *) Изменение: теперь nginx пытается установить директивы
+       worker_priority, worker_rlimit_nofile, worker_rlimit_core,
+       worker_rlimit_sigpending без привилегий root'а.
+
+    *) Изменение: теперь nginx экранирует символы пробела и "%" при передаче
+       запроса серверу аутентификации почтового прокси-сервера.
+
+    *) Изменение: теперь nginx экранирует символ "%" в переменной
+       $memcached_key.
+
+    *) Исправление: при указании относительного пути к конфигурационному
+       файлу в качестве параметра ключа -c nginx определял путь относительно
+       конфигурационного префикса; ошибка появилась в 0.6.6.
+
+    *) Исправление: nginx не работал на FreeBSD/sparc64.
+
+
+Изменения в nginx 0.6.7                                           15.08.2007
+
+    *) Изменение: теперь пути, указанные в директивах include,
+       auth_basic_user_file, perl_modules, ssl_certificate,
+       ssl_certificate_key и ssl_client_certificate, определяются
+       относительно каталога конфигурационного файла nginx.conf, а не
+       относительно префикса.
+
+    *) Изменение: параметр --sysconfdir=PATH в configure упразднён.
+
+    *) Изменение: для обновления на лету версий 0.1.x создан специальный
+       сценарий make upgrade1.
+
+    *) Добавление: директивы server_name и valid_referers поддерживают
+       регулярные выражения.
+
+    *) Добавление: директива server в блоке upstream поддерживает параметр
+       backup.
+
+    *) Добавление: модуль ngx_http_perl_module поддерживает метод
+       $r->discard_request_body.
+
+    *) Добавление: директива "add_header Last-Modified ..." меняет строку
+       "Last-Modified" в заголовке ответа.
+
+    *) Исправление: если на запрос с телом возвращался ответ с кодом HTTP
+       отличным от 200, и после этого запроса соединение переходило в
+       состояние keep-alive, то на следующий запрос nginx возвращал 400.
+
+    *) Исправление: если в директиве auth_http был задан неправильный адрес,
+       то в рабочем процессе происходил segmentation fault.
+
+    *) Исправление: теперь по умолчанию nginx использует значение 511 для
+       listen backlog на всех платформах, кроме FreeBSD.
+       Спасибо Jiang Hong.
+
+    *) Исправление: рабочий процесс мог зациклиться, если server в блоке
+       upstream был помечен как down; ошибка появилась в 0.6.6.
+
+    *) Исправление: sendfilev() в Solaris теперь не используется при
+       передаче тела запроса FastCGI-серверу через unix domain сокет.
+
+
+Изменения в nginx 0.6.6                                           30.07.2007
+
+    *) Добавление: параметр --sysconfdir=PATH в configure.
+
+    *) Добавление: именованные location'ы.
+
+    *) Добавление: переменную $args можно устанавливать с помощью set.
+
+    *) Добавление: переменная $is_args.
+
+    *) Исправление: равномерное распределение запросов к апстримам с
+       большими весами.
+
+    *) Исправление: если клиент в почтовом прокси-сервере закрывал
+       соединение, то nginx мог не закрывать соединение с бэкендом.
+
+    *) Исправление: при использовании одного хоста в качестве бэкендов для
+       протоколов HTTP и HTTPS без явного указания портов, nginx использовал
+       только один порт - 80 или 443.
+
+    *) Исправление: nginx не собирался на Solaris/amd64 Sun Studio 11 и
+       более ранними версиями; ошибка появилась в 0.6.4.
+
+
+Изменения в nginx 0.6.5                                           23.07.2007
+
+    *) Добавление: переменная $nginx_version.
+       Спасибо Николаю Гречуху.
+
+    *) Добавление: почтовый прокси-сервер поддерживает AUTHENTICATE в режиме
+       IMAP.
+       Спасибо Максиму Дунину.
+
+    *) Добавление: почтовый прокси-сервер поддерживает STARTTLS в режиме
+       SMTP.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: теперь nginx экранирует пробел в переменной
+       $memcached_key.
+
+    *) Исправление: nginx неправильно собирался Sun Studio на Solaris/amd64.
+       Спасибо Jiang Hong.
+
+    *) Исправление: незначительных потенциальных ошибок.
+       Спасибо Coverity's Scan.
+
+
+Изменения в nginx 0.6.4                                           17.07.2007
+
+    *) Безопасность: при использовании директивы msie_refresh был возможен
+       XSS.
+       Спасибо Максиму Богуку.
+
+    *) Изменение: директивы proxy_store и fastcgi_store изменены.
+
+    *) Добавление: директивы proxy_store_access и fastcgi_store_access.
+
+    *) Исправление: nginx не работал на Solaris/sparc64, если был собран Sun
+       Studio.
+       Спасибо Андрею Нигматулину.
+
+    *) Изменение: обход ошибки в Sun Studio 12.
+       Спасибо Jiang Hong.
+
+
+Изменения в nginx 0.6.3                                           12.07.2007
+
+    *) Добавление: директивы proxy_store и fastcgi_store.
+
+    *) Исправление: при использовании директивы auth_http_header в рабочем
+       процессе мог произойти segmentation fault.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: если использовался метод аутентификации CRAM-MD5, но он
+       не был разрешён, то в рабочем процессе происходил segmentation fault.
+
+    *) Исправление: при использовании протокола HTTPS в директиве proxy_pass
+       в рабочем процессе мог произойти segmentation fault.
+
+    *) Исправление: в рабочем процессе мог произойти segmentation fault,
+       если использовался метод eventport.
+
+    *) Исправление: директивы proxy_ignore_client_abort и
+       fastcgi_ignore_client_abort не работали; ошибка появилась в 0.5.13.
+
+
+Изменения в nginx 0.6.2                                           09.07.2007
+
+    *) Исправление: если заголовок ответа был разделён в FastCGI-записях, то
+       nginx передавал клиенту мусор в таких заголовках.
+
+
+Изменения в nginx 0.6.1                                           17.06.2007
+
+    *) Исправление: в парсинге SSI.
+
+    *) Исправление: при использовании удалённого подзапроса в SSI
+       последующий подзапрос локального файла мог отдаваться клиенту в
+       неверном порядке.
+
+    *) Исправление: большие включения в SSI, сохранённые во временные файлы,
+       передавались не полностью.
+
+    *) Исправление: значение perl'овой переменной $$ модуля
+       ngx_http_perl_module было равно номеру главного процесса.
+
+
+Изменения в nginx 0.6.0                                           14.06.2007
+
+    *) Добавление: директивы "server_name", "map", and "valid_referers"
+       поддерживают маски вида "www.example.*".
+
+
+Изменения в nginx 0.5.25                                          11.06.2007
+
+    *) Исправление: nginx не собирался с параметром
+       --without-http_rewrite_module; ошибка появилась в 0.5.24.
+
+
+Изменения в nginx 0.5.24                                          06.06.2007
+
+    *) Безопасность: директива ssl_verify_client не работала, если запрос
+       выполнялся по протоколу HTTP/0.9.
+
+    *) Исправление: при использовании сжатия часть ответа могла передаваться
+       несжатой; ошибка появилась в 0.5.23.
+
+
+Изменения в nginx 0.5.23                                          04.06.2007
+
+    *) Добавление: модуль ngx_http_ssl_module поддерживает расширение TLS
+       Server Name Indication.
+
+    *) Добавление: директива fastcgi_catch_stderr.
+       Спасибо Николаю Гречуху, проект OWOX.
+
+    *) Исправление: на Линуксе в основном процессе происходил segmentation
+       fault, если два виртуальных сервера должны bind()ится к
+       пересекающимся портам.
+
+    *) Исправление: если nginx был собран с модулем ngx_http_perl_module и
+       perl поддерживал потоки, то во время второй переконфигурации
+       выдавались ошибки "panic: MUTEX_LOCK" и "perl_parse() failed".
+
+    *) Исправление: в использовании протокола HTTPS в директиве proxy_pass.
+
+
+Изменения в nginx 0.5.22                                          29.05.2007
+
+    *) Исправление: большое тело запроса могло не передаваться бэкенду;
+       ошибка появилась в 0.5.21.
+
+
+Изменения в nginx 0.5.21                                          28.05.2007
+
+    *) Исправление: если внутри сервера описано больше примерно десяти
+       location'ов, то location'ы, заданные с помощью регулярного выражения,
+       могли выполняться не в том, порядке, в каком они описаны.
+
+    *) Исправление: на 64-битной платформе рабочий процесс мог зациклиться,
+       если 33-тий по счёту или последующий бэкенд упал.
+       Спасибо Антону Поварову.
+
+    *) Исправление: при использовании библиотеки PCRE на Solaris/sparc64 мог
+       произойти bus error.
+       Спасибо Андрею Нигматулину.
+
+    *) Исправление: в использовании протокола HTTPS в директиве proxy_pass.
+
+
+Изменения в nginx 0.5.20                                          07.05.2007
+
+    *) Добавление: директива sendfile_max_chunk.
+
+    *) Добавление: переменные "$http_...", "$sent_http_..." и
+       "$upstream_http_..." можно менять директивой set.
+
+    *) Исправление: при использовании SSI-команды 'if expr="$var = /"' в
+       рабочем процессе мог произойти segmentation fault.
+
+    *) Исправление: завершающая строка multipart range ответа передавалась
+       неверно.
+       Спасибо Evan Miller.
+
+    *) Исправление: nginx не работал на Solaris/sparc64, если был собран Sun
+       Studio.
+       Спасибо Андрею Нигматулину.
+
+    *) Исправление: модуль ngx_http_perl_module не собирался make в Solaris.
+       Спасибо Андрею Нигматулину.
+
+
+Изменения в nginx 0.5.19                                          24.04.2007
+
+    *) Изменение: значение переменной $request_time теперь записывается с
+       точностью до миллисекунд.
+
+    *) Изменение: метод $r->rflush в модуле ngx_http_perl_module
+       переименован в $r->flush.
+
+    *) Добавление: переменная $upstream_addr.
+
+    *) Добавление: директивы proxy_headers_hash_max_size и
+       proxy_headers_hash_bucket_size.
+       Спасибо Володымыру Костырко.
+
+    *) Исправление: при использовании sendfile и limit_rate на 64-битных
+       платформах нельзя было передавать файлы больше 2G.
+
+    *) Исправление: при использовании sendfile на 64-битном Linux нельзя
+       было передавать файлы больше 2G.
+
+
+Изменения в nginx 0.5.18                                          19.04.2007
+
+    *) Добавление: модуль ngx_http_sub_filter_module.
+
+    *) Добавление: переменные "$upstream_http_...".
+
+    *) Добавление: теперь переменные $upstream_status и
+       $upstream_response_time содержат данные о всех обращениях к
+       апстримам, сделанным до X-Accel-Redirect.
+
+    *) Исправление: если nginx был собран с модулем ngx_http_perl_module и
+       perl не поддерживал multiplicity, то после первой переконфигурации и
+       после получения любого сигнала в основном процессе происходил
+       segmentation fault; ошибка появилась в 0.5.9.
+
+    *) Исправление: если perl не поддерживал multiplicity, то после
+       переконфигурации перловый код не работал; ошибка появилась в 0.3.38.
+
+
+Изменения в nginx 0.5.17                                          02.04.2007
+
+    *) Изменение: теперь nginx для метода TRACE всегда возвращает код 405.
+
+    *) Добавление: теперь nginx поддерживает директиву include внутри блока
+       types.
+
+    *) Исправление: использование переменной $document_root в директиве root
+       и alias запрещено: оно вызывало рекурсивное переполнение стека.
+
+    *) Исправление: в использовании протокола HTTPS в директиве proxy_pass.
+
+    *) Исправление: в некоторых случаях некэшируемые переменные (такие, как
+       $uri) возвращали старое закэшированное значение.
+
+
+Изменения в nginx 0.5.16                                          26.03.2007
+
+    *) Исправление: в качестве ключа для хэша в директиве ip_hash не
+       использовалась сеть класса С.
+       Спасибо Павлу Ярковому.
+
+    *) Исправление: если в строке "Content-Type" в заголовке ответа бэкенда
+       был указан charset и строка завершалась символом ";", то в рабочем
+       процессе мог произойти segmentation fault; ошибка появилась в 0.3.50.
+
+    *) Исправление: ошибки "[alert] zero size buf" при работе с
+       FastCGI-сервером, если тело запроса, записанное во временный файл,
+       было кратно 32K.
+
+    *) Исправление: nginx не собирался на Solaris без параметра
+       --with-debug; ошибка появилась в 0.5.15.
+
+
+Изменения в nginx 0.5.15                                          19.03.2007
+
+    *) Добавление: почтовый прокси-сервер поддерживает аутентифицированное
+       SMTP-проксирование и директивы smtp_auth, smtp_capabilities и
+       xclient.
+       Спасибо Антону Южанинову и Максиму Дунину.
+
+    *) Добавление: теперь keep-alive соединения закрываются сразу же по
+       получении сигнала переконфигурации.
+
+    *) Изменение: директивы imap и auth переименованы соответственно в mail
+       и pop3_auth.
+
+    *) Исправление: если использовался метод аутентификации CRAM-MD5 и не
+       был разрешён метод APOP, то в рабочем процессе происходил
+       segmentation fault.
+
+    *) Исправление: при использовании директивы starttls only в протоколе
+       POP3 nginx разрешал аутентификацию без перехода в режим SSL.
+
+    *) Исправление: рабочие процессы не выходили после переконфигурации и не
+       переоткрывали логи, если использовался метод eventport.
+
+    *) Исправление: при использовании директивы ip_hash рабочий процесс мог
+       зациклиться.
+
+    *) Исправление: теперь nginx не пишет в лог некоторые alert'ы, если
+       используются методы eventport или /dev/poll.
+
+
+Изменения в nginx 0.5.14                                          23.02.2007
+
+    *) Исправление: nginx игнорировал лишние закрывающие скобки "}" в конце
+       конфигурационного файла.
+
+
+Изменения в nginx 0.5.13                                          19.02.2007
+
+    *) Добавление: методы COPY и MOVE.
+
+    *) Исправление: модуль ngx_http_realip_module устанавливал мусор для
+       запросов, переданных по keep-alive соединению.
+
+    *) Исправление: nginx не работал на 64-битном big-endian Linux.
+       Спасибо Андрею Нигматулину.
+
+    *) Исправление: при получении слишком длинной команды IMAP/POP3-прокси
+       теперь сразу закрывает соединение, а не по таймауту.
+
+    *) Исправление: если при использовании метода epoll клиент закрывал
+       преждевременно соединение со своей стороны, то nginx закрывал это
+       соединение только по истечении таймаута на передачу.
+
+    *) Исправление: nginx не собирался на платформах, отличных от i386,
+       amd64, sparc и ppc; ошибка появилась в 0.5.8.
+
+
+Изменения в nginx 0.5.12                                          12.02.2007
+
+    *) Исправление: nginx не собирался на платформах, отличных от i386,
+       amd64, sparc и ppc; ошибка появилась в 0.5.8.
+
+    *) Исправление: при использовании временных файлов в время работы с
+       FastCGI-сервером в рабочем процессе мог произойти segmentation fault;
+       ошибка появилась в 0.5.8.
+
+    *) Исправление: если переменная $fastcgi_script_name записывалась в лог,
+       то в рабочем процессе мог произойти segmentation fault.
+
+    *) Исправление: ngx_http_perl_module не собирался на Solaris.
+
+
+Изменения в nginx 0.5.11                                          05.02.2007
+
+    *) Добавление: теперь configure определяет библиотеку PCRE в MacPorts.
+       Спасибо Chris McGrath.
+
+    *) Исправление: ответ был неверным, если запрашивалось несколько
+       диапазонов; ошибка появилась в 0.5.6.
+
+    *) Исправление: директива create_full_put_path не могла создавать
+       промежуточные каталоги, если не была установлена директива
+       dav_access.
+       Спасибо Evan Miller.
+
+    *) Исправление: вместо кодов ошибок "400" и "408" в access_log мог
+       записываться код "0".
+
+    *) Исправление: при сборке с оптимизацией -O2 в рабочем процессе мог
+       произойти segmentation fault.
+
+
+Изменения в nginx 0.5.10                                          26.01.2007
+
+    *) Исправление: во время обновления исполняемого файла новый процесс не
+       наследовал слушающие сокеты; ошибка появилась в 0.5.9.
+
+    *) Исправление: при сборке с оптимизацией -O2 в рабочем процессе мог
+       произойти segmentation fault; ошибка появилась в 0.5.1.
+
+
+Изменения в nginx 0.5.9                                           25.01.2007
+
+    *) Изменение: модуль ngx_http_memcached_module теперь в качестве ключа
+       использует значение переменной $memcached_key.
+
+    *) Добавление: переменная $memcached_key.
+
+    *) Добавление: параметр clean в директиве client_body_in_file_only.
+
+    *) Добавление: директива env.
+
+    *) Добавление: директива sendfile работает внутри блока if.
+
+    *) Добавление: теперь при ошибке записи в access_log nginx записывает
+       сообщение в error_log, но не чаще одного раза в минуту.
+
+    *) Исправление: директива "access_log off" не всегда запрещала запись в
+       лог.
+
+
+Изменения в nginx 0.5.8                                           19.01.2007
+
+    *) Исправление: если использовалась директива
+       "client_body_in_file_only on" и тело запроса было небольшое, то мог
+       произойти segmentation fault.
+
+    *) Исправление: происходил segmentation fault, если использовались
+       директивы "client_body_in_file_only on" и
+       "proxy_pass_request_body off" или "fastcgi_pass_request_body off", и
+       делался переход к следующему бэкенду.
+
+    *) Исправление: если при использовании директивы "proxy_buffering off"
+       соединение с клиентом было неактивно, то оно закрывалось по таймауту,
+       заданному директивой send_timeout; ошибка появилась в 0.4.7.
+
+    *) Исправление: если при использовании метода epoll клиент закрывал
+       преждевременно соединение со своей стороны, то nginx закрывал это
+       соединение только по истечении таймаута на передачу.
+
+    *) Исправление: ошибки "[alert] zero size buf" при работе с
+       FastCGI-сервером.
+
+    *) Исправление ошибок в директиве limit_zone.
+
+
+Изменения в nginx 0.5.7                                           15.01.2007
+
+    *) Добавление: оптимизация использования памяти в ssl_session_cache.
+
+    *) Исправление ошибок в директивах ssl_session_cache и limit_zone.
+
+    *) Исправление: на старте или во время переконфигурации происходил
+       segmentation fault, если директивы ssl_session_cache или limit_zone
+       использовались на 64-битных платформах.
+
+    *) Исправление: при использовании директив add_before_body или
+       add_after_body происходил segmentation fault, если в заголовке ответа
+       нет строки "Content-Type".
+
+    *) Исправление: библиотека OpenSSL всегда собиралась с поддержкой
+       потоков.
+       Спасибо Дену Иванову.
+
+    *) Исправление: совместимость библиотеки PCRE-6.5+ и компилятора icc.
+
+
+Изменения в nginx 0.5.6                                           09.01.2007
+
+    *) Изменение: теперь модуль ngx_http_index_module игнорирует все методы,
+       кроме GET, HEAD и POST.
+
+    *) Добавление: модуль ngx_http_limit_zone_module.
+
+    *) Добавление: переменная $binary_remote_addr.
+
+    *) Добавление: директивы ssl_session_cache модулей ngx_http_ssl_module и
+       ngx_imap_ssl_module.
+
+    *) Добавление: метод DELETE поддерживает рекурсивное удаление.
+
+    *) Исправление: при использовании $r->sendfile() byte-ranges
+       передавались неверно.
+
+
+Изменения в nginx 0.5.5                                           24.12.2006
+
+    *) Изменение: ключ -v больше не выводит информацию о компиляторе.
+
+    *) Добавление: ключ -V.
+
+    *) Добавление: директива worker_rlimit_core поддерживает указание
+       размера в K, M и G.
+
+    *) Исправление: модуль nginx.pm теперь может устанавливаться
+       непривилегированным пользователем.
+
+    *) Исправление: при использовании методов $r->request_body или
+       $r->request_body_file мог произойти segmentation fault.
+
+    *) Исправление: ошибок, специфичных для платформы ppc.
+
+
+Изменения в nginx 0.5.4                                           15.12.2006
+
+    *) Добавление: директиву perl можно использовать внутри блока
+       limit_except.
+
+    *) Исправление: модуль ngx_http_dav_module требовал строку "Date" в
+       заголовке запроса для метода DELETE.
+
+    *) Исправление: при использовании одного параметра в директиве
+       dav_access nginx мог сообщить об ошибке в конфигурации.
+
+    *) Исправление: при использовании переменной $host мог произойти
+       segmentation fault; ошибка появилась в 0.4.14.
+
+
+Изменения в nginx 0.5.3                                           13.12.2006
+
+    *) Добавление: модуль ngx_http_perl_module поддерживает методы
+       $r->status, $r->log_error и $r->sleep.
+
+    *) Добавление: метод $r->variable поддерживает переменные, неописанные в
+       конфигурации nginx'а.
+
+    *) Исправление: метод $r->has_request_body не работал.
+
+
+Изменения в nginx 0.5.2                                           11.12.2006
+
+    *) Исправление: если в директивах proxy_pass использовалось имя,
+       указанное в upstream, то nginx пытался найти IP-адрес этого имени;
+       ошибка появилась в 0.5.1.
+
+
+Изменения в nginx 0.5.1                                           11.12.2006
+
+    *) Исправление: директива post_action могла не работать после неудачного
+       завершения запроса.
+
+    *) Изменение: обход ошибки в Eudora для Mac; ошибка появилась в 0.4.11.
+       Спасибо Bron Gondwana.
+
+    *) Исправление: при указании в директиве fastcgi_pass имени описанного
+       upstream'а выдавалось сообщение "no port in upstream"; ошибка
+       появилась в 0.5.0.
+
+    *) Исправление: если в директивах proxy_pass и fastcgi_pass
+       использовались одинаковых имена серверов, но с разными портами, то
+       эти директивы использовали первый описанный порт; ошибка появилась в
+       0.5.0.
+
+    *) Исправление: если в директивах proxy_pass и fastcgi_pass
+       использовались unix domain сокеты, то эти директивы использовали
+       первый описанный сокет; ошибка появилась в 0.5.0.
+
+    *) Исправление: ngx_http_auth_basic_module игнорировал пользователя,
+       если он был указан в последней строке файла паролей и после пароля не
+       было перевода строки, возврата каретки или символа ":".
+
+    *) Исправление: переменная $upstream_response_time могла быть равна
+       "0.000", хотя время обработки было больше 1 миллисекунды.
+
+
+Изменения в nginx 0.5.0                                           04.12.2006
+
+    *) Изменение: параметры в виде "%name" в директиве log_format больше не
+       поддерживаются.
+
+    *) Изменение: директивы proxy_upstream_max_fails,
+       proxy_upstream_fail_timeout, fastcgi_upstream_max_fails, и
+       fastcgi_upstream_fail_timeout, memcached_upstream_max_fails и
+       memcached_upstream_fail_timeout больше не поддерживаются.
+
+    *) Добавление: директива server в блоке upstream поддерживает параметры
+       max_fails, fail_timeout и down.
+
+    *) Добавление: директива ip_hash в блоке upstream.
+
+    *) Добавление: статус WAIT в строке "Auth-Status" в заголовке ответа
+       сервера аутентификации IMAP/POP3 прокси.
+
+    *) Исправление: nginx не собирался на 64-битных платформах; ошибка
+       появилась в 0.4.14.
+
+
+Изменения в nginx 0.4.14                                          27.11.2006
+
+    *) Добавление: директива proxy_pass_error_message в IMAP/POP3 прокси.
+
+    *) Добавление: теперь configure определяет библиотеку PCRE на FreeBSD,
+       Linux и NetBSD.
+
+    *) Исправление: ngx_http_perl_module не работал с перлом, собранным с
+       поддержкой потоков; ошибка появилась в 0.3.38.
+
+    *) Исправление: ngx_http_perl_module не работал корректно, если перл
+       вызывался рекурсивно.
+
+    *) Исправление: nginx игнорировал имя сервера в строке запроса.
+
+    *) Исправление: если FastCGI сервер передавал много в stderr, то рабочий
+       процесс мог зациклиться.
+
+    *) Исправление: при изменении системного времени переменная
+       $upstream_response_time могла быть отрицательной.
+
+    *) Исправление: при использовании POP3 серверу аутентификации IMAP/POP3
+       прокси не передавался параметр Auth-Login-Attempt.
+
+    *) Исправление: при ошибке соединения с сервером аутентификации
+       IMAP/POP3 прокси мог произойти segmentation fault.
+
+
+Изменения в nginx 0.4.13                                          15.11.2006
+
+    *) Добавление: директиву proxy_pass можно использовать внутри блока
+       limit_except.
+
+    *) Добавление: директива limit_except поддерживает все WebDAV методы.
+
+    *) Исправление: при использовании директивы add_before_body без
+       директивы add_after_body ответ передавался не полностью.
+
+    *) Исправление: большое тело запроса не принималось, если использовались
+       метод epoll и deferred accept().
+
+    *) Исправление: для ответов модуля ngx_http_autoindex_module не
+       выставлялась кодировка; ошибка появилась в 0.3.50.
+
+    *) Исправление: ошибки "[alert] zero size buf" при работе с
+       FastCGI-сервером;
+
+    *) Исправление: параметр конфигурации --group= игнорировался.
+       Спасибо Thomas Moschny.
+
+    *) Исправление: 50-й подзапрос в SSI ответе не работал; ошибка появилась
+       в 0.3.50.
+
+
+Изменения в nginx 0.4.12                                          31.10.2006
+
+    *) Добавление: модуль ngx_http_perl_module поддерживает метод
+       $r->variable.
+
+    *) Исправление: при включении в ответ большого статического файла с
+       помощью SSI ответ мог передаваться не полностью.
+
+    *) Исправление: nginx не убирал "#fragment" в URI.
+
+
+Изменения в nginx 0.4.11                                          25.10.2006
+
+    *) Добавление: POP3 прокси поддерживает AUTH LOGIN PLAIN и CRAM-MD5.
+
+    *) Добавление: модуль ngx_http_perl_module поддерживает метод
+       $r->allow_ranges.
+
+    *) Исправление: при включённой поддержке команды APOP в POP3 прокси
+       могли не работать команды USER/PASS; ошибка появилась в 0.4.10.
+
+
+Изменения в nginx 0.4.10                                          23.10.2006
+
+    *) Добавление: POP3 прокси поддерживает APOP.
+
+    *) Исправление: при использовании методов select, poll и /dev/poll во
+       время ожидания ответа от сервера аутентификации IMAP/POP3 прокси
+       нагружал процессор.
+
+    *) Исправление: при использовании переменной $server_addr в директиве
+       map мог произойти segmentation fault.
+
+    *) Исправление: модуль ngx_http_flv_module не поддерживал byte ranges
+       для полных ответов; ошибка появилась в 0.4.7.
+
+    *) Исправление: nginx не собирался на Debian amd64; ошибка появилась в
+       0.4.9.
+
+
+Изменения в nginx 0.4.9                                           13.10.2006
+
+    *) Добавление: параметр set в команде SSI include.
+
+    *) Добавление: модуль ngx_http_perl_module теперь проверяет версию
+       модуля nginx.pm.
+
+
+Изменения в nginx 0.4.8                                           11.10.2006
+
+    *) Исправление: если до команды SSI include с параметром wait
+       выполнялась ещё одна команда SSI include, то параметр wait мог не
+       работать.
+
+    *) Исправление: модуль ngx_http_flv_module добавлял FLV-заголовок для
+       полных ответов.
+       Спасибо Алексею Ковырину.
+
+
+Изменения в nginx 0.4.7                                           10.10.2006
+
+    *) Добавление: модуль ngx_http_flv_module.
+
+    *) Добавление: переменная $request_body_file.
+
+    *) Добавление: директивы charset и source_charset поддерживают
+       переменные.
+
+    *) Исправление: если до команды SSI include с параметром wait
+       выполнялась ещё одна команда SSI include, то параметр wait мог не
+       работать.
+
+    *) Исправление: при использовании директивы "proxy_buffering off" или
+       при работе с memcached соединения могли не закрываться по таймауту.
+
+    *) Исправление: nginx не запускался на 64-битных платформах, отличных от
+       amd64, sparc64 и ppc64.
+
+
+Изменения в nginx 0.4.6                                           06.10.2006
+
+    *) Исправление: nginx не запускался на 64-битных платформах, отличных от
+       amd64, sparc64 и ppc64.
+
+    *) Исправление: при запросе версии HTTP/1.1 nginx передавал ответ
+       chunk'ами, если длина ответа в методе
+       $r->headers_out("Content-Length", ...) была задана текстовой строкой.
+
+    *) Исправление: после перенаправления ошибки с помощью директивы
+       error_page любая директива модуля ngx_http_rewrite_module возвращала
+       эту ошибку; ошибка появилась в 0.4.4.
+
+
+Изменения в nginx 0.4.5                                           02.10.2006
+
+    *) Исправление: nginx не собирался на Linux и Solaris; ошибка появилась
+       в 0.4.4.
+
+
+Изменения в nginx 0.4.4                                           02.10.2006
+
+    *) Добавление: переменная $scheme.
+
+    *) Добавление: директива expires поддерживает параметр max.
+
+    *) Добавление: директива include поддерживает маску "*".
+       Спасибо Jonathan Dance.
+
+    *) Исправление: директива return всегда изменяла код ответа,
+       перенаправленного директивой error_page.
+
+    *) Исправление: происходил segmentation fault, если в методе PUT
+       передавалось тело нулевой длины.
+
+    *) Исправление: при использовании переменных в директиве proxy_redirect
+       редирект изменялся неверно.
+
+
+Изменения в nginx 0.4.3                                           26.09.2006
+
+    *) Изменение: ошибку 499 теперь нельзя перенаправить с помощью директивы
+       error_page.
+
+    *) Добавление: поддержка Solaris 10 event ports.
+
+    *) Добавление: модуль ngx_http_browser_module.
+
+    *) Исправление: при перенаправлении ошибки 400 проксированному серверу
+       помощью директивы error_page мог произойти segmentation fault.
+
+    *) Исправление: происходил segmentation fault, если в директиве
+       proxy_pass использовался unix domain сокет; ошибка появилась в
+       0.3.47.
+
+    *) Исправление: SSI не работал с ответами memcached и
+       небуферизированными проксированными ответами.
+
+    *) Изменение: обход ошибки PAUSE hardware capability в Sun Studio.
+
+
+Изменения в nginx 0.4.2                                           14.09.2006
+
+    *) Исправление: убрана поддержка флага O_NOATIME на Linux; ошибка
+       появилась в 0.4.1.
+
+
+Изменения в nginx 0.4.1                                           14.09.2006
+
+    *) Исправление: совместимость с DragonFlyBSD.
+       Спасибо Павлу Назарову.
+
+    *) Изменение: обход ошибки в sendfile() в 64-битном Linux при передаче
+       файлов больше 2G.
+
+    *) Добавление: теперь на Linux nginx для статических запросов использует
+       флаг O_NOATIME.
+       Спасибо Yusuf Goolamabbas.
+
+
+Изменения в nginx 0.4.0                                           30.08.2006
+
+    *) Изменение во внутреннем API: инициализация модулей HTTP перенесена из
+       фазы init module в фазу HTTP postconfiguration.
+
+    *) Изменение: теперь тело запроса в модуле ngx_http_perl_module не
+       считывается заранее: нужно явно инициировать чтение с помощью метода
+       $r->has_request_body.
+
+    *) Добавление: модуль ngx_http_perl_module поддерживает код возврата
+       DECLINED.
+
+    *) Добавление: модуль ngx_http_dav_module поддерживает входящую строку
+       заголовка "Date" для метода PUT.
+
+    *) Добавление: директива ssi работает внутри блока if.
+
+    *) Исправление: происходил segmentation fault, если в директиве index
+       использовалась переменные и при этом первое имя индексного файла было
+       без переменных; ошибка появилась в 0.1.29.
+
+
+Изменения в nginx 0.3.61                                          28.08.2006
+
+    *) Изменение: директива tcp_nodelay теперь по умолчанию включена.
+
+    *) Добавление: директива msie_refresh.
+
+    *) Добавление: директива recursive_error_pages.
+
+    *) Исправление: директива rewrite возвращала неправильный редирект, если
+       редирект включал в себя выделенные закодированные символы из
+       оригинального URI.
+
+
+Изменения в nginx 0.3.60                                          18.08.2006
+
+    *) Исправление: во время перенаправления ошибки рабочий процесс мог
+       зациклиться; ошибка появилась в 0.3.59.
+
+
+Изменения в nginx 0.3.59                                          16.08.2006
+
+    *) Добавление: теперь можно делать несколько перенаправлений через
+       директиву error_page.
+
+    *) Исправление: директива dav_access не поддерживала три параметра.
+
+    *) Исправление: директива error_page не изменяла строку "Content-Type"
+       после перенаправления с помощью "X-Accel-Redirect"; ошибка появилась
+       в 0.3.58.
+
+
+Изменения в nginx 0.3.58                                          14.08.2006
+
+    *) Добавление: директива error_page поддерживает переменные.
+
+    *) Изменение: теперь на Linux используется интерфейс procfs вместо
+       sysctl.
+
+    *) Изменение: теперь при использовании "X-Accel-Redirect" строка
+       "Content-Type" наследуется из первоначального ответа.
+
+    *) Исправление: директива error_page не перенаправляла ошибку 413.
+
+    *) Исправление: завершающий "?" не удалял старые аргументы, если в
+       переписанном URI не было новых аргументов.
+
+    *) Исправление: nginx не запускался на 64-битной FreeBSD 7.0-CURRENT.
+
+
+Изменения в nginx 0.3.57                                          09.08.2006
+
+    *) Добавление: переменная $ssl_client_serial.
+
+    *) Исправление: в операторе "!-e" в директиве if.
+       Спасибо Андриану Буданцову.
+
+    *) Исправление: при проверке клиентского сертификата nginx не передавал
+       клиенту информацию о требуемых сертификатах.
+
+    *) Исправление: переменная $document_root не поддерживала переменные в
+       директиве root.
+
+
+Изменения в nginx 0.3.56                                          04.08.2006
+
+    *) Добавление: директива dav_access.
+
+    *) Добавление: директива if поддерживает операторы "-d", "!-d", "-e",
+       "!-e", "-x" и "!-x".
+
+    *) Исправление: при записи в access_log некоторых передаваемых клиенту
+       строк заголовков происходил segmentation fault, если запрос возвращал
+       редирект.
+
+
+Изменения в nginx 0.3.55                                          28.07.2006
+
+    *) Добавление: параметр stub в команде SSI include.
+
+    *) Добавление: команда SSI block.
+
+    *) Добавление: скрипт unicode2nginx добавлен в contrib.
+
+    *) Исправление: если root был задан только переменной, то корень
+       задавался относительно префикса сервера.
+
+    *) Исправление: если в запросе был "//" или "/.", и после этого
+       закодированные символы в виде "%XX", то проксируемый запрос
+       передавался незакодированным.
+
+    *) Исправление: метод $r->header_in("Cookie") модуля
+       ngx_http_perl_module теперь возвращает все строки "Cookie" в
+       заголовке запроса.
+
+    *) Исправление: происходил segmentation fault, если использовался
+       "client_body_in_file_only on" и делался переход к следующему бэкенду.
+
+    *) Исправление: при некоторых условиях во время переконфигурации коды
+       символов внутри директивы charset_map могли считаться неверными;
+       ошибка появилась в 0.3.50.
+
+
+Изменения в nginx 0.3.54                                          11.07.2006
+
+    *) Добавление: nginx теперь записывает в лог информацию о подзапросах.
+
+    *) Добавление: директивы proxy_next_upstream, fastcgi_next_upstream и
+       memcached_next_upstream поддерживают параметр off.
+
+    *) Добавление: директива debug_connection поддерживает запись адресов в
+       формате CIDR.
+
+    *) Исправление: при перекодировании ответа проксированного сервера или
+       сервера FastCGI в UTF-8 или наоборот ответ мог передаваться не
+       полностью.
+
+    *) Исправление: переменная $upstream_response_time содержала время
+       только первого обращения к бэкенду.
+
+    *) Исправление: nginx не собирался на платформе amd64; ошибка появилась
+       в 0.3.53.
+
+
+Изменения в nginx 0.3.53                                          07.07.2006
+
+    *) Изменение: директива add_header добавляет строки в ответы с кодом
+       204, 301 и 302.
+
+    *) Добавление: директива server в блоке upstream поддерживает параметр
+       weight.
+
+    *) Добавление: директива server_name поддерживает маску "*".
+
+    *) Добавление: nginx поддерживает тело запроса больше 2G.
+
+    *) Исправление: если при использовании "satisfy_any on" клиент успешно
+       проходил аутентификацию, в лог всё равно записалоcь сообщение "access
+       forbidden by rule".
+
+    *) Исправление: метод PUT мог ошибочно не создать файл и вернуть код
+       409.
+
+    *) Исправление: если во время аутентификации IMAP/POP3 бэкенд возвращал
+       ошибку, nginx продолжал проксирование.
+
+
+Изменения в nginx 0.3.52                                          03.07.2006
+
+    *) Изменение: восстановлено поведение модуля ngx_http_index_module для
+       запросов "POST /": как в версии до 0.3.40, модуль теперь не выдаёт
+       ошибку 405.
+
+    *) Исправление: при использовании ограничения скорости рабочий процесс
+       мог зациклиться; ошибка появилась в 0.3.37.
+
+    *) Исправление: модуль ngx_http_charset_module записывал в лог ошибку
+       "unknown charset", даже если перекодировка не требовалась; ошибка
+       появилась в 0.3.50.
+
+    *) Исправление: если в результате запроса PUT возвращался код 409, то
+       временный файл не удалялся.
+
+
+Изменения в nginx 0.3.51                                          30.06.2006
+
+    *) Исправление: при некоторых условиях в SSI мог пропадать символы "<";
+       ошибка появилась в 0.3.50.
+
+
+Изменения в nginx 0.3.50                                          28.06.2006
+
+    *) Изменение: директивы proxy_redirect_errors и fastcgi_redirect_errors
+       переименованы соответственно в proxy_intercept_errors и
+       fastcgi_intercept_errors.
+
+    *) Добавление: модуль ngx_http_charset_module поддерживает
+       перекодирование из однобайтных кодировок в UTF-8 и обратно.
+
+    *) Добавление: в режиме прокси и FastCGI поддерживается строка заголовка
+       "X-Accel-Charset" в ответе бэкенда.
+
+    *) Исправление: символ "\" в парах "\"" и "\'" в SSI командах убирался,
+       только если также использовался символ "$".
+
+    *) Исправление: при некоторых условиях в SSI после вставки могла быть
+       добавлена строка "<!--".
+
+    *) Исправление: если в заголовке ответа была строка "Content-Length: 0",
+       то при использовании небуферизированного проксировании не закрывалось
+       соединение с клиентом.
+
+
+Изменения в nginx 0.3.49                                          31.05.2006
+
+    *) Исправление: в директиве set.
+
+    *) Исправление: при включении в ssi двух и более подзапросов,
+       обрабатываемых через FastCGI, вместо вывода второго и остальных
+       подзапросов в ответ включался вывод первого подзапроса.
+
+
+Изменения в nginx 0.3.48                                          29.05.2006
+
+    *) Изменение: теперь модуль ngx_http_charset_module работает для
+       подзапросов, в ответах которых нет строки заголовка "Content-Type".
+
+    *) Исправление: если в директиве proxy_pass не было URI, то директива
+       "proxy_redirect default" добавляла в переписанный редирект в начало
+       лишний слэш.
+
+    *) Исправление: внутренний редирект всегда превращал любой HTTP-метод в
+       GET, теперь это делается только для редиректов, выполняемых с помощью
+       X-Accel-Redirect, и у которых метод не равен HEAD; ошибка появилась в
+       0.3.42.
+
+    *) Исправление: модуль ngx_http_perl_module не собирался, если перл был
+       с поддержкой потоков; ошибка появилась в 0.3.46.
+
+
+Изменения в nginx 0.3.47                                          23.05.2006
+
+    *) Добавление: директива upstream.
+
+    *) Изменение: символ "\" в парах "\"" и "\'" в SSI командах теперь
+       всегда убирается.
+
+
+Изменения в nginx 0.3.46                                          11.05.2006
+
+    *) Добавление: директивы proxy_hide_header, proxy_pass_header,
+       fastcgi_hide_header и fastcgi_pass_header.
+
+    *) Изменение: директивы proxy_pass_x_powered_by, fastcgi_x_powered_by и
+       proxy_pass_server упразднены.
+
+    *) Добавление: в режиме прокси поддерживается строка заголовка
+       "X-Accel-Buffering" в ответе бэкенда.
+
+    *) Исправление: ошибок и утечек памяти при переконфигурации в модуле
+       ngx_http_perl_module.
+
+
+Изменения в nginx 0.3.45                                          06.05.2006
+
+    *) Добавление: директивы ssl_verify_client, ssl_verify_depth и
+       ssl_client_certificate.
+
+    *) Изменение: теперь переменная $request_method возвращает метод только
+       основного запроса.
+
+    *) Изменение: в таблице перекодировки koi-win изменены коды символа
+       &deg;.
+
+    *) Добавление: в таблицу перекодировки koi-win добавлены символы евро и
+       номера.
+
+    *) Исправление: если nginx распределял запросы на несколько машин, то
+       при падении одной из них запросы, предназначенные для этой машины,
+       перенаправлялись только на одну машину вместо того, чтобы равномерно
+       распределяться между остальными.
+
+
+Изменения в nginx 0.3.44                                          04.05.2006
+
+    *) Добавление: параметр wait в команде SSI include.
+
+    *) Добавление: в таблицу перекодировки koi-win добавлены украинские и
+       белорусские символы.
+
+    *) Исправление: в SSI.
+
+
+Изменения в nginx 0.3.43                                          26.04.2006
+
+    *) Исправление: в SSI.
+
+
+Изменения в nginx 0.3.42                                          26.04.2006
+
+    *) Добавление: параметр bind в директиве listen в IMAP/POP3 прокси.
+
+    *) Исправление: ошибки при использовании в директиве rewrite одного и
+       того же выделения более одного раза.
+
+    *) Исправление: в лог не записывались переменные
+       $sent_http_content_type, $sent_http_content_length,
+       $sent_http_last_modified, $sent_http_connection,
+       $sent_http_keep_alive и $sent_http_transfer_encoding.
+
+    *) Исправление: переменная $sent_http_cache_control возвращала
+       содержимое только одной строки "Cache-Control" в заголовке ответа.
+
+
+Изменения в nginx 0.3.41                                          21.04.2006
+
+    *) Добавление: ключ -v.
+
+    *) Исправление: при включении в SSI удалённых подзапросов мог произойти
+       segmentation fault.
+
+    *) Исправление: в обработке FastCGI.
+
+    *) Исправление: если путь к перловым модулям не был указан с помощью
+       --with-perl_modules_path=PATH или директивы perl_modules, то на
+       старте происходил segmentation fault.
+
+
+Изменения в nginx 0.3.40                                          19.04.2006
+
+    *) Добавление: модуль ngx_http_dav_module поддерживает метод MKCOL.
+
+    *) Добавление: директива create_full_put_path.
+
+    *) Добавление: переменная $limit_rate.
+
+
+Изменения в nginx 0.3.39                                          17.04.2006
+
+    *) Добавление: директива uninitialized_variable_warn; уровень
+       логгирования сообщения о неинициализированной переменной понижен с
+       уровня alert на warn.
+
+    *) Добавление: директива override_charset.
+
+    *) Изменение: при использовании неизвестной переменной в SSI-командах
+       echo и if expr='$name' теперь не записывается в лог сообщение о
+       неизвестной переменной.
+
+    *) Исправление: счётчик активных соединений рос при превышении лимита
+       соединений, заданного директивой worker_connections; ошибка появилась
+       в 0.2.0.
+
+    *) Исправление: при некоторых условия ограничение скорости соединения
+       могло не работать; ошибка появилась в 0.3.38.
+
+
+Изменения в nginx 0.3.38                                          14.04.2006
+
+    *) Добавление: модуль ngx_http_dav_module.
+
+    *) Изменение: оптимизация модуля ngx_http_perl_module.
+       Спасибо Сергею Скворцову.
+
+    *) Добавление: модуль ngx_http_perl_module поддерживает метод
+       $r->request_body_file.
+
+    *) Добавление: директива client_body_in_file_only.
+
+    *) Изменение: теперь при переполнении диска nginx пытается писать
+       access_log'и только раз в секунду.
+       Спасибо Антону Южанинову и Максиму Дунину.
+
+    *) Исправление: теперь директива limit_rate точнее ограничивает скорость
+       при значениях больше 100 Kbyte/s.
+       Спасибо ForJest.
+
+    *) Исправление: IMAP/POP3 прокси теперь передаёт серверу авторизации
+       символы "\r" и "\n" в логине и пароле в закодированном виде.
+       Спасибо Максиму Дунину.
+
+
+Изменения в nginx 0.3.37                                          07.04.2006
+
+    *) Добавление: директива limit_except.
+
+    *) Добавление: директива if поддерживает операторы "!~", "!~*", "-f" и
+       "!-f".
+
+    *) Добавление: модуль ngx_http_perl_module поддерживает метод
+       $r->request_body.
+
+    *) Исправление: в модуле ngx_http_addition_filter_module.
+
+
+Изменения в nginx 0.3.36                                          05.04.2006
+
+    *) Добавление: модуль ngx_http_addition_filter_module.
+
+    *) Добавление: директивы proxy_pass и fastcgi_pass можно использовать
+       внутри блока if.
+
+    *) Добавление: директивы proxy_ignore_client_abort и
+       fastcgi_ignore_client_abort.
+
+    *) Добавление: переменная $request_completion.
+
+    *) Добавление: модуль ngx_http_perl_module поддерживает методы
+       $r->request_method и $r->remote_addr.
+
+    *) Добавление: модуль ngx_http_ssi_module поддерживает команду elif.
+
+    *) Исправление: строка "\/" в начале выражения команды if модуля
+       ngx_http_ssi_module воспринималась неверно.
+
+    *) Исправление: в использовании регулярных выражениях в команде if
+       модуля ngx_http_ssi_module.
+
+    *) Исправление: при задании относительного пути в директивах
+       client_body_temp_path, proxy_temp_path, fastcgi_temp_path и
+       perl_modules использовался каталог относительно текущего каталога, а
+       не относительно префикса сервера.
+
+
+Изменения в nginx 0.3.35                                          22.03.2006
+
+    *) Исправление: accept-фильтр и TCP_DEFER_ACCEPT устанавливались только
+       для первой директивы listen; ошибка появилась в 0.3.31.
+
+    *) Исправление: в директиве proxy_pass без URI при использовании в
+       подзапросе.
+
+
+Изменения в nginx 0.3.34                                          21.03.2006
+
+    *) Добавление: директива add_header поддерживает переменные.
+
+
+Изменения в nginx 0.3.33                                          15.03.2006
+
+    *) Добавление: параметр http_503 в директивах proxy_next_upstream или
+       fastcgi_next_upstream.
+
+    *) Исправление: ngx_http_perl_module не работал со встроенным в
+       конфигурационный файл кодом, если он не начинался сразу же с "sub".
+
+    *) Исправление: в директиве post_action.
+
+
+Изменения в nginx 0.3.32                                          11.03.2006
+
+    *) Исправление: удаление отладочного логгирования на старте и при
+       переконфигурации; ошибка появилась в 0.3.31.
+
+
+Изменения в nginx 0.3.31                                          10.03.2006
+
+    *) Изменение: теперь nginx передаёт неверные ответы проксированного
+       бэкенда.
+
+    *) Добавление: директивы listen поддерживают адрес в виде "*:порт".
+
+    *) Добавление: поддержка EVFILER_TIMER в MacOSX 10.4.
+
+    *) Изменение: обход ошибки обработки миллисекундных таймаутов kqueue в
+       64-битном ядре MacOSX.
+       Спасибо Андрею Нигматулину.
+
+    *) Исправление: если внутри одного сервера описаны несколько директив
+       listen, слушающих на разных адресах, то имена серверов вида
+       "*.domain.tld" работали только для первого адреса; ошибка появилась в
+       0.3.18.
+
+    *) Исправление: при использовании протокола HTTPS в директиве proxy_pass
+       не передавались запросы с телом, записанным во временный файл.
+
+    *) Исправление: совместимость с perl 5.8.8.
+
+
+Изменения в nginx 0.3.30                                          22.02.2006
+
+    *) Изменение: уровень записи в лог ошибки ECONNABORTED изменён на error
+       с уровня crit.
+
+    *) Исправление: модуль ngx_http_perl_module не собирался без модуля
+       ngx_http_ssi_filter_module.
+
+    *) Исправление: nginx не собирался на i386 платформе, если использовался
+       PIC; ошибка появилась в 0.3.27.
+
+
+Изменения в nginx 0.3.29                                          20.02.2006
+
+    *) Добавление: теперь nginx использует меньше памяти, если PHP в режиме
+       FastCGI передаёт большое количество предупреждений перед ответом.
+
+    *) Исправление: в ответах 204 для запросов версии HTTP/1.1 выдавалась
+       строка заголовка "Transfer-Encoding: chunked".
+
+    *) Исправление: nginx возвращал 502 код ответа, если FastCGI сервер
+       передавал полные строки заголовка ответа в отдельных FastCGI записях.
+
+    *) Исправление: если в директиве post_action был указан проксируемый
+       URI, то он выполнялся только после успешного завершения запроса.
+
+
+Изменения в nginx 0.3.28                                          16.02.2006
+
+    *) Добавление: директива restrict_host_names упразднена.
+
+    *) Добавление: параметр конфигурации --with-cpu-opt=ppc64.
+
+    *) Исправление: при некоторых условиях проксированное соединение с
+       клиентом завершалось преждевременно.
+       Спасибо Владимиру Шутову.
+
+    *) Исправление: строка заголовка "X-Accel-Limit-Rate" не учитывалась для
+       запросов, перенаправленных с помощью строки "X-Accel-Redirect".
+
+    *) Исправление: директива post_action работала только после успешного
+       завершения запроса.
+
+    *) Исправление: тело проксированного ответа, создаваемого директивой
+       post_action, передавалось клиенту.
+
+
+Изменения в nginx 0.3.27                                          08.02.2006
+
+    *) Изменение: директивы variables_hash_max_size и
+       variables_hash_bucket_size.
+
+    *) Добавление: переменная $body_bytes_sent доступна не только в
+       директиве log_format.
+
+    *) Добавление: переменные $ssl_protocol и $ssl_cipher.
+
+    *) Добавление: определение размера строки кэша распространённых
+       процессоров при старте.
+
+    *) Добавление: директива accept_mutex теперь поддерживается посредством
+       fcntl(2) на платформах, отличных от i386, amd64, sparc64 и ppc.
+
+    *) Добавление: директива lock_file и параметр автоконфигурации
+       --with-lock-path=PATH.
+
+    *) Исправление: при использовании протокола HTTPS в директиве proxy_pass
+       не передавались запросы с телом.
+
+
+Изменения в nginx 0.3.26                                          03.02.2006
+
+    *) Изменение: директива optimize_host_names переименована в
+       optimize_server_names.
+
+    *) Исправление: при проксировании подзапроса в SSI бэкенду передавался
+       URI основного запроса, если в директиве proxy_pass отсутствовал URI.
+
+
+Изменения в nginx 0.3.25                                          01.02.2006
+
+    *) Исправление: при неверной конфигурации на старте или во время
+       переконфигурации происходил segmentation fault; ошибка появилась в
+       0.3.24.
+
+
+Изменения в nginx 0.3.24                                          01.02.2006
+
+    *) Изменение: обход ошибки в kqueue во FreeBSD.
+
+    *) Исправление: ответ, создаваемый директивой post_action, теперь не
+       передаётся клиенту.
+
+    *) Исправление: при использовании большого количества лог-файлов
+       происходила утечка памяти.
+
+    *) Исправление: внутри одного location работала только первая директива
+       proxy_redirect.
+
+    *) Исправление: на 64-битных платформах при старте мог произойти
+       segmentation fault, если использовалось большое количество имён в
+       директивах server_name; ошибка появилась в 0.3.18.
+
+
+Изменения в nginx 0.3.23                                          24.01.2006
+
+    *) Добавление: директива optimize_host_names.
+
+    *) Исправление: ошибки при использовании переменных в директивах path и
+       alias.
+
+    *) Исправление: модуль ngx_http_perl_module неправильно собирался на
+       Linux и Solaris.
+
+
+Изменения в nginx 0.3.22                                          17.01.2006
+
+    *) Добавление: модуль ngx_http_perl_module поддерживает методы $r->args
+       и $r->unescape.
+
+    *) Добавление: метод $r->query_string в модуле ngx_http_perl_module
+       упразднён.
+
+    *) Исправление: если в директиве valid_referers указаны только none или
+       blocked, то происходил segmentation fault; ошибка появилась в 0.3.18.
+
+
+Изменения в nginx 0.3.21                                          16.01.2006
+
+    *) Добавление: модуль ngx_http_perl_module.
+
+    *) Изменение: директива valid_referers разрешает использовать рефереры
+       совсем без URI.
+
+
+Изменения в nginx 0.3.20                                          11.01.2006
+
+    *) Исправление: ошибки в обработке SSI.
+
+    *) Исправление: модуль ngx_http_memcached_module не поддерживал ключи в
+       виде /uri?args.
+
+
+Изменения в nginx 0.3.19                                          28.12.2005
+
+    *) Добавление: директивы path и alias поддерживают переменные.
+
+    *) Изменение: теперь директива valid_referers опять учитывает URI.
+
+    *) Исправление: ошибки в обработке SSI.
+
+
+Изменения в nginx 0.3.18                                          26.12.2005
+
+    *) Добавление: директива server_names поддерживает имена вида
+       ".domain.tld".
+
+    *) Добавление: директива server_names использует хэш для имён вида
+       "*.domain.tld" и более эффективный хэш для обычных имён.
+
+    *) Изменение: директивы server_names_hash_max_size и
+       server_names_hash_bucket_size.
+
+    *) Изменение: директивы server_names_hash и server_names_hash_threshold
+       упразднены.
+
+    *) Добавление: директива valid_referers использует хэш для имён сайтов.
+
+    *) Изменение: теперь директива valid_referers проверяет только имена
+       сайтов без учёта URI.
+
+    *) Исправление: некоторые имена вида ".domain.tld" неверно
+       обрабатывались модулем ngx_http_map_module.
+
+    *) Исправление: если конфигурационного файла не было, то происходил
+       segmentation fault; ошибка появилась в 0.3.12.
+
+    *) Исправление: на 64-битных платформах при старте мог произойти
+       segmentation fault; ошибка появилась в 0.3.16.
+
+
+Изменения в nginx 0.3.17                                          18.12.2005
+
+    *) Изменение: на Linux configure теперь проверяет наличие epoll и
+       sendfile64() в ядре.
+
+    *) Добавление: директива map поддерживает доменные имена в формате
+       ".domain.tld".
+
+    *) Исправление: во время SSL handshake не иcпользовались таймауты;
+       ошибка появилась в 0.2.4.
+
+    *) Исправление: в использовании протокола HTTPS в директиве proxy_pass.
+
+    *) Исправление: при использовании протокола HTTPS в директиве proxy_pass
+       по умолчанию использовался порт 80.
+
+
+Изменения в nginx 0.3.16                                          16.12.2005
+
+    *) Добавление: модуль ngx_http_map_module.
+
+    *) Добавление: директивы types_hash_max_size и types_hash_bucket_size.
+
+    *) Добавление: директива ssi_value_length.
+
+    *) Добавление: директива worker_rlimit_core.
+
+    *) Изменение: при сборке компиляторами icc 8.1 и 9.0 с оптимизацией для
+       Pentium 4 номер соединения в логах всегда был равен 1.
+
+    *) Исправление: команда config timefmt в SSI задавала неверный формат
+       времени.
+
+    *) Исправление: nginx не закрывал соединения с IMAP/POP3 бэкендом при
+       использовании SSL соединений; ошибка появилась в 0.3.13.
+       Спасибо Rob Mueller.
+
+    *) Исправление: segmentation fault мог произойти во время SSL shutdown;
+       ошибка появилась в 0.3.13.
+
+
+Изменения в nginx 0.3.15                                          07.12.2005
+
+    *) Добавление: новой код 444 в директиве return для закрытия соединения.
+
+    *) Добавление: директива so_keepalive в IMAP/POP3 прокси.
+
+    *) Исправление: nginx теперь вызывает abort() при обнаружении незакрытых
+       соединений только при плавном выходе и включённой директиве
+       debug_points.
+
+
+Изменения в nginx 0.3.14                                          05.12.2005
+
+    *) Исправление: в ответе 304 передавалось тело ответа; ошибка появилась
+       в 0.3.13.
+
+
+Изменения в nginx 0.3.13                                          05.12.2005
+
+    *) Добавление: IMAP/POP3 прокси поддерживает STARTTLS и STLS.
+
+    *) Исправление: IMAP/POP3 прокси не работала с методами select, poll и
+       /dev/poll.
+
+    *) Исправление: ошибки в обработке SSI.
+
+    *) Исправление: sendfilev() в Solaris теперь не используется при
+       передаче тела запроса FastCGI-серверу через unix domain сокет.
+
+    *) Исправление: директива auth_basic не запрещала аутентификацию; ошибка
+       появилась в 0.3.11.
+
+
+Изменения в nginx 0.3.12                                          26.11.2005
+
+    *) Безопасность: если nginx был собран с модулем ngx_http_realip_module,
+       то при использовании директивы "satisfy_any on" директивы доступа и
+       аутентификации не работали. Модуль ngx_http_realip_module не
+       собирался и не собирается по умолчанию.
+
+    *) Изменение: имя переменной "$time_gmt" изменено на "$time_local".
+
+    *) Изменение: директивы proxy_header_buffer_size и
+       fastcgi_header_buffer_size переименованы соответственно в
+       proxy_buffer_size и fastcgi_buffer_size.
+
+    *) Добавление: модуль ngx_http_memcached_module.
+
+    *) Добавление: директива proxy_buffering.
+
+    *) Исправление: изменение в работе с accept mutex при использовании
+       метода rtsig; ошибка появилась в 0.3.0.
+
+    *) Исправление: если клиент передал строку "Transfer-Encoding: chunked"
+       в заголовке запроса, то nginx теперь выдаёт ошибку 411.
+
+    *) Исправление: при наследовании директивы auth_basic с уровня http в
+       строке "WWW-Authenticate" заголовка ответа выводился realm без текста
+       "Basic realm".
+
+    *) Исправление: если в директиве access_log был явно указан формат
+       combined, то в лог записывались пустые строки; ошибка появилась в
+       0.3.8.
+
+    *) Исправление: nginx не работал на платформе sparc под любыми OS, кроме
+       Solaris.
+
+    *) Исправление: в директиве if теперь не нужно разделять пробелом строку
+       в кавычках и закрывающую скобку.
+
+
+Изменения в nginx 0.3.11                                          15.11.2005
+
+    *) Исправление: nginx не передавал при проксировании тело запроса и
+       строки заголовка клиента; ошибка появилась в 0.3.10.
+
+
+Изменения в nginx 0.3.10                                          15.11.2005
+
+    *) Изменение: директива valid_referers и переменная $invalid_referer
+       перенесены из модуля ngx_http_rewrite_module в новый модуль
+       ngx_http_referer_module.
+
+    *) Изменение: имя переменной "$apache_bytes_sent" изменено на
+       "$body_bytes_sent".
+
+    *) Добавление: переменные "$sent_http_...".
+
+    *) Добавление: директива if поддерживает операции "=" и "!=".
+
+    *) Добавление: директива proxy_pass поддерживает протокол HTTPS.
+
+    *) Добавление: директива proxy_set_body.
+
+    *) Добавление: директива post_action.
+
+    *) Добавление: модуль ngx_http_empty_gif_module.
+
+    *) Добавление: директива worker_cpu_affinity для Linux.
+
+    *) Исправление: директива rewrite не раскодировала символы в редиректах
+       в URI, теперь символы раскодируются, кроме символов %00-%25 и
+       %7F-%FF.
+
+    *) Исправление: nginx не собирался компилятором icc 9.0.
+
+    *) Исправление: если для статического файла нулевого размера был
+       разрешён SSI, то ответ передавался неверно при кодировании chunk'ами.
+
+
+Изменения в nginx 0.3.9                                           10.11.2005
+
+    *) Исправление: nginx считал небезопасными URI, в которых между двумя
+       слэшами находилось два любых символа; ошибка появилась в 0.3.8.
+
+
+Изменения в nginx 0.3.8                                           09.11.2005
+
+    *) Безопасность: nginx теперь проверят URI, полученные от бэкенда в
+       строке "X-Accel-Redirect" в заголовке ответа, или в SSI файле на
+       наличие путей "/../" и нулей.
+
+    *) Изменение: nginx теперь не воспринимает пустое имя как правильное в
+       строке "Authorization" в заголовке запроса.
+
+    *) Добавление: директива ssl_session_timeout модулей ngx_http_ssl_module
+       и ngx_imap_ssl_module.
+
+    *) Добавление: директива auth_http_header модуля
+       ngx_imap_auth_http_module.
+
+    *) Добавление: директива add_header.
+
+    *) Добавление: модуль ngx_http_realip_module.
+
+    *) Добавление: новые переменные для использования в директиве
+       log_format: $bytes_sent, $apache_bytes_sent, $status, $time_gmt,
+       $uri, $request_time, $request_length, $upstream_status,
+       $upstream_response_time, $gzip_ratio, $uid_got, $uid_set,
+       $connection, $pipe и $msec. Параметры в виде "%name" скоро будут
+       упразднены.
+
+    *) Изменение: в директиве "if" ложными значениями переменных теперь
+       являются пустая строка "" и строки, начинающиеся на "0".
+
+    *) Исправление: при работает с проксированными или FastCGI-серверами
+       nginx мог оставлять открытыми соединения и временные файлы с
+       запросами клиентов.
+
+    *) Исправление: рабочие процессы не сбрасывали буферизированные логи при
+       плавном выходе.
+
+    *) Исправление: если URI запроса изменялось с помощью rewrite, а затем
+       запрос проксировался в location, заданном регулярным выражением, то
+       бэкенду передавался неверный запрос; ошибка появилась в 0.2.6.
+
+    *) Исправление: директива expires не удаляла уже установленную строку
+       заголовка "Expires".
+
+    *) Исправление: при использовании метода rtsig и нескольких рабочих
+       процессах nginx мог перестать принимать запросы.
+
+    *) Исправление: в SSI командах неверно обрабатывались строки "\"" и
+       "\'".
+
+    *) Исправление: если ответ заканчивался сразу же после SSI команды, то
+       при использовании сжатия ответ передавался не до конца или не
+       передавался вообще.
+
+
+Изменения в nginx 0.3.7                                           27.10.2005
+
+    *) Добавление: директива access_log поддерживает параметр buffer=.
+
+    *) Исправление: nginx не собирался на платформах, отличных от i386,
+       amd64, sparc и ppc; ошибка появилась в 0.3.2.
+
+
+Изменения в nginx 0.3.6                                           24.10.2005
+
+    *) Изменение: IMAP/POP3 прокси теперь не передаёт серверу авторизации
+       пустой логин.
+
+    *) Добавление: директива log_format поддерживает переменные в виде
+       $name.
+
+    *) Исправление: если хотя бы в одном сервере не было описано ни одной
+       директивы listen, то nginx не слушал на 80 порту; ошибка появилась в
+       0.3.3.
+
+    *) Исправление: если в директиве proxy_pass отсутствовал URI, то всегда
+       использовался порт 80.
+
+
+Изменения в nginx 0.3.5                                           21.10.2005
+
+    *) Исправление: если логин IMAP/POP3 менялся сервером авторизации, то
+       мог произойти segmentation fault; ошибка появилась в 0.2.2.
+
+    *) Исправление: accept mutex не работал, все соединения обрабатывались
+       одним рабочим процессом; ошибка появилась в 0.3.3.
+
+    *) Исправление: при использовании метода rtsig и директивы
+       timer_resolution не работали таймауты.
+
+
+Изменения в nginx 0.3.4                                           19.10.2005
+
+    *) Исправление: nginx не собирался на Linux 2.4+ и MacOS X; ошибка
+       появилась в 0.3.3.
+
+
+Изменения в nginx 0.3.3                                           19.10.2005
+
+    *) Изменение: параметры "bl" и "af" директивы listen переименованы в
+       "backlog" и "accept_filter".
+
+    *) Добавление: параметры "rcvbuf" и "sndbuf" в директиве listen.
+
+    *) Изменение: параметр лога $msec теперь не требует дополнительного
+       системного вызова gettimeofday().
+
+    *) Добавление: ключ -t теперь проверяет директивы listen.
+
+    *) Исправление: если в директиве listen был указан неверный адрес, то
+       nginx после сигнала -HUP оставлял открытый сокет в состоянии CLOSED.
+
+    *) Исправление: для индексных файлов, содержащих в имени переменную, мог
+       неверно выставляться тип mime по умолчанию; ошибка появилась в 0.3.0.
+
+    *) Добавление: директива timer_resolution.
+
+    *) Добавление: параметр лога $upstream_response_time в миллисекундах.
+
+    *) Исправление: временный файл с телом запроса клиента теперь удаляется
+       сразу после того, как клиенту передан заголовок ответа.
+
+    *) Исправление: совместимость с OpenSSL 0.9.6.
+
+    *) Исправление: пути к файлам с SSL сертификатом и ключом не могли быть
+       относительными.
+
+    *) Исправление: директива ssl_prefer_server_ciphers не работала для
+       модуля ngx_imap_ssl_module.
+
+    *) Исправление: директива ssl_protocols позволяла задать только один
+       протокол.
+
+
+Изменения в nginx 0.3.2                                           12.10.2005
+
+    *) Добавление: поддержка Sun Studio 10 C compiler.
+
+    *) Добавление: директивы proxy_upstream_max_fails,
+       proxy_upstream_fail_timeout, fastcgi_upstream_max_fails и
+       fastcgi_upstream_fail_timeout.
+
+
+Изменения в nginx 0.3.1                                           10.10.2005
+
+    *) Исправление: во время переполнения очереди сигналов при использовании
+       метода rtsig происходил segmentation fault; ошибка появилась в 0.2.0.
+
+    *) Изменение: корректная обработка пар "\\", "\"", "\'" и "\$" в SSI.
+
+
+Изменения в nginx 0.3.0                                           07.10.2005
+
+    *) Изменение: убрано десятидневное ограничение времени работы рабочего
+       процесса. Ограничение было введено из-за переполнения миллисекундных
+       таймеров.
+
+
+Изменения в nginx 0.2.6                                           05.10.2005
+
+    *) Изменение: с 60 до 10 секунд уменьшено время повторного обращения к
+       бэкенду при использовании распределения нагрузки.
+
+    *) Изменение: директива proxy_pass_unparsed_uri упразднена, оригинальный
+       запрос теперь передаётся, если в директиве proxy_pass отсутствует
+       URI.
+
+    *) Добавление: директива error_page поддерживает редиректы и позволяет
+       более гибко менять код ошибки.
+
+    *) Изменение: в проксированных подзапросах теперь игнорируется
+       переданный charset.
+
+    *) Исправление: если после изменения URI в блоке if для запроса не
+       находилась новая конфигурация, то правила модуля
+       ngx_http_rewrite_module выполнялись снова.
+
+    *) Исправление: если директива set устанавливала переменную модуля
+       ngx_http_geo_module в какой-либо части конфигурации, то эта
+       переменная не была доступна в других частях конфигурации и выдавалась
+       ошибка "using uninitialized variable"; ошибка появилась в 0.2.2.
+
+
+Изменения в nginx 0.2.5                                           04.10.2005
+
+    *) Изменение: дублирующее значение переменной модуля ngx_http_geo_module
+       теперь выдаёт предупреждение и изменяет старое значение.
+
+    *) Добавление: модуль ngx_http_ssi_module поддерживает команду set.
+
+    *) Добавление: модуль ngx_http_ssi_module поддерживает параметр file в
+       команде include.
+
+    *) Добавление: модуль ngx_http_ssi_module поддерживает подстановку
+       значений переменных в выражениях команды if.
+
+
+Изменения в nginx 0.2.4                                           03.10.2005
+
+    *) Добавление: модуль ngx_http_ssi_module поддерживает выражения
+       "$var=text", "$var!=text", "$var=/text/" и "$var!=/text/" в команде
+       if.
+
+    *) Исправление: ошибки при проксировании location без слэша в конце;
+       ошибка появилась в 0.1.44.
+
+    *) Исправление: при использовании метода rtsig мог произойти
+       segmentation fault; ошибка появилась в 0.2.0.
+
+
+Изменения в nginx 0.2.3                                           30.09.2005
+
+    *) Исправление: nginx не собирался без параметра --with-debug; ошибка
+       появилась в 0.2.2.
+
+
+Изменения в nginx 0.2.2                                           30.09.2005
+
+    *) Добавление: команда config errmsg в модуле ngx_http_ssi_module.
+
+    *) Изменение: переменные модуля ngx_http_geo_module можно переопределять
+       директивой set.
+
+    *) Добавление: директивы ssl_protocols и ssl_prefer_server_ciphers
+       модулей ngx_http_ssl_module и ngx_imap_ssl_module.
+
+    *) Исправление: ошибка в модуле ngx_http_autoindex_module при показе
+       длинных имён файлов;
+
+    *) Исправление: модуль ngx_http_autoindex_module теперь не показывает
+       файлы, начинающиеся на точку.
+
+    *) Исправление: если SSL handshake завершался с ошибкой, то это могло
+       привести также к закрытию другого соединения.
+       Спасибо Rob Mueller.
+
+    *) Исправление: экспортные версии MSIE 5.x не могли соединиться по
+       HTTPS.
+
+
+Изменения в nginx 0.2.1                                           23.09.2005
+
+    *) Исправление: если все бэкенды, используемые для балансировки
+       нагрузки, оказывались в нерабочем состоянии после одной ошибки, то
+       nginx мог зациклится; ошибка появилась в 0.2.0.
+
+
+Изменения в nginx 0.2.0                                           23.09.2005
+
+    *) Изменились имена pid-файлов, используемые во время обновления
+       исполняемого файла. Ручное переименование теперь не нужно. Старый
+       основной процесс добавляет к своему pid-файл суффикс ".oldbin" и
+       запускает новый исполняемый файл. Новый основной процесс создаёт
+       обычный pid-файл без суффикса ".newbin". Если новый основной процесс
+       выходит, то старый процесс переименовывает свой pid-файл c суффиксом
+       ".oldbin" в pid-файл без суффикса. При обновлении с версии 0.1.х до
+       0.2.0 нужно учитывать, что оба процесса - старый 0.1.x и новый
+       0.2.0 - используют pid-файл без суффиксов.
+
+    *) Изменение: директива worker_connections, новое название директивы
+       connections; директива теперь задаёт максимальное число соединений, а
+       не максимально возможный номер дескриптора для сокета.
+
+    *) Добавление: SSL поддерживает кэширование сессий в пределах одного
+       рабочего процесса.
+
+    *) Добавление: директива satisfy_any.
+
+    *) Изменение: модули ngx_http_access_module и ngx_http_auth_basic_module
+       не работают для подзапросов.
+
+    *) Добавление: директивы worker_rlimit_nofile и
+       worker_rlimit_sigpending.
+
+    *) Исправление: если все бэкенды, используемые для балансировки
+       нагрузки, оказывались в нерабочем состоянии после одной ошибки, то
+       nginx не обращался к ним в течение 60 секунд.
+
+    *) Исправление: в парсинге аргументов IMAP/POP3 команд.
+       Спасибо Rob Mueller.
+
+    *) Исправление: ошибки при использовании SSL в IMAP/POP3 прокси.
+
+    *) Исправление: ошибки при использовании SSI и сжатия.
+
+    *) Исправление: в ответах 304 не добавлялись строки заголовка ответа
+       "Expires" и "Cache-Control".
+       Спасибо Александру Кукушкину.
+
+
+Изменения в nginx 0.1.45                                          08.09.2005
+
+    *) Изменение: директива ssl_engine упразднена в модуле
+       ngx_http_ssl_module и перенесена на глобальный уровень.
+
+    *) Исправление: ответы с подзапросами, включённые с помощью SSI, не
+       передавались через SSL соединение.
+
+    *) Разные исправления в IMAP/POP3 прокси.
+
+
+Изменения в nginx 0.1.44                                          06.09.2005
+
+    *) Добавление: IMAP/POP3 прокси поддерживает SSL.
+
+    *) Добавление: директива proxy_timeout модуля ngx_imap_proxy_module.
+
+    *) Добавление: директива userid_mark.
+
+    *) Добавление: значение переменной $remote_user определяется независимо
+       от того, используется ли авторизация или нет.
+
+
+Изменения в nginx 0.1.43                                          30.08.2005
+
+    *) Добавление: listen(2) backlog в директиве listen можно менять по
+       сигналу -HUP.
+
+    *) Добавление: скрипт geo2nginx.pl добавлен в contrib.
+
+    *) Изменение: параметры FastCGI с пустым значениями теперь передаются
+       серверу.
+
+    *) Исправление: если в ответе проксированного сервера или FastCGI
+       сервера была строка "Cache-Control", то при использовании директивы
+       expires происходил segmentation fault или рабочий процесс мог
+       зациклится; в режиме прокси ошибка появилась в 0.1.29.
+
+
+Изменения в nginx 0.1.42                                          23.08.2005
+
+    *) Исправление: если URI запроса получался нулевой длины после обработки
+       модулем ngx_http_rewrite_module, то в модуле ngx_http_proxy_module
+       происходил segmentation fault или bus error.
+
+    *) Исправление: директива limit_rate не работала внутри блока if; ошибка
+       появилась в 0.1.38.
+
+
+Изменения в nginx 0.1.41                                          25.07.2005
+
+    *) Исправление: если переменная использовалась в файле конфигурации, то
+       она не могла использоваться в SSI.
+
+
+Изменения в nginx 0.1.40                                          22.07.2005
+
+    *) Исправление: если клиент слал очень длинную строку заголовка, то в
+       логе не помещалась информация, связанная с этим запросом.
+
+    *) Исправление: при использовании "X-Accel-Redirect" не передавалась
+       строка "Set-Cookie"; ошибка появилась в 0.1.39.
+
+    *) Исправление: при использовании "X-Accel-Redirect" не передавалась
+       строка "Content-Disposition".
+
+    *) Исправление: по сигналу SIGQUIT основной процесс не закрывал сокеты,
+       на которых он слушал.
+
+    *) Исправление: после обновления исполняемого файла на лету на Linux и
+       Solaris название процесса в команде ps становилось короче.
+
+
+Изменения в nginx 0.1.39                                          14.07.2005
+
+    *) Изменения в модуле ngx_http_charset_module: директива default_charset
+       упразднена; директива charset задаёт кодировку ответа; директива
+       source_charset задаёт только исходную кодировку.
+
+    *) Исправление: при перенаправлении ошибки 401, полученной от бэкенда,
+       не передавалась строка заголовка "WWW-Authenticate".
+
+    *) Исправление: модули ngx_http_proxy_module и ngx_http_fastcgi_module
+       могли закрыть соединение до того, как что-нибудь было передано
+       клиенту; ошибка появилась в 0.1.38.
+
+    *) Изменение: обработка ошибки инициализации в crypt_r() в Linux glibc.
+
+    *) Исправление: модуль ngx_http_ssi_module не поддерживал относительные
+       URI в команде include virtual.
+
+    *) Исправление: если в строке заголовка ответа бэкенда была строка
+       "Location", которую nginx не должен был изменять, то в ответе
+       передавалось тело 500 ошибки; ошибка появилась в 0.1.29.
+
+    *) Исправление: некоторые директивы модулей ngx_http_proxy_module и
+       ngx_http_fastcgi_module не наследовались с уровня server на уровень
+       location; ошибка появилась в 0.1.29.
+
+    *) Исправление: модуль ngx_http_ssl_module не поддерживал цепочки
+       сертификатов.
+
+    *) Исправление: ошибка в модуле ngx_http_autoindex_module при показе
+       длинных имён файлов; ошибка появилась в 0.1.38.
+
+    *) Исправления в IMAP/POP3 прокси при взаимодействии с бэкендом на
+       стадии login.
+
+
+Изменения в nginx 0.1.38                                          08.07.2005
+
+    *) Добавление: директива limit_rate поддерживается в режиме прокси и
+       FastCGI.
+
+    *) Добавление: в режиме прокси и FastCGI поддерживается строка заголовка
+       "X-Accel-Limit-Rate" в ответе бэкенда.
+
+    *) Добавление: директива break.
+
+    *) Добавление: директива log_not_found.
+
+    *) Исправление: при перенаправлении запроса с помощью строки заголовка
+       "X-Accel-Redirect" не изменялся код ответа.
+
+    *) Исправление: переменные, установленные директивой set не могли
+       использоваться в SSI.
+
+    *) Исправление: при включении в SSI более одного удалённого подзапроса
+       мог произойти segmentation fault.
+
+    *) Исправление: если статусная строка в ответе бэкенда передавалась в
+       двух пакетах, то nginx считал ответ неверным; ошибка появилась в
+       0.1.29.
+
+    *) Добавление: директива ssi_types.
+
+    *) Добавление: директива autoindex_exact_size.
+
+    *) Исправление: модуль ngx_http_autoindex_module не поддерживал длинные
+       имена файлов в UTF-8.
+
+    *) Добавление: IMAP/POP3 прокси.
+
+
+Изменения в nginx 0.1.37                                          23.06.2005
+
+    *) Изменение: в конце файла nginx.pid теперь добавляется "\n".
+
+    *) Исправление: при включении большого количества вставок или нескольких
+       больших вставок с помощью SSI ответ мог передаваться не полностью.
+
+    *) Исправление: если все бэкенды возвращали ответ 404, то при
+       использовании параметра http_404 в директивах proxy_next_upstream или
+       fastcgi_next_upstream, nginx начинал запрашивать все бэкенды снова.
+
+
+Изменения в nginx 0.1.36                                          15.06.2005
+
+    *) Изменение: если в заголовке запроса есть дублирующиеся строки "Host",
+       "Connection", "Content-Length" и "Authorization", то nginx теперь
+       выдаёт ошибку 400.
+
+    *) Изменение: директива post_accept_timeout упразднена.
+
+    *) Добавление: параметры default, af=, bl=, deferred и bind в директиве
+       listen.
+
+    *) Добавление: поддержка accept фильтров во FreeBSD.
+
+    *) Добавление: поддержка TCP_DEFER_ACCEPT в Linux.
+
+    *) Исправление: модуль ngx_http_autoindex_module не поддерживал имена
+       файлов в UTF-8.
+
+    *) Исправление: после добавления новый лог-файл ротация этого лога по
+       сигналу -USR1 выполнялась, только если переконфигурировать nginx два
+       раза по сигналу -HUP.
+
+
+Изменения в nginx 0.1.35                                          07.06.2005
+
+    *) Добавление: директива working_directory.
+
+    *) Добавление: директива port_in_redirect.
+
+    *) Исправление: если заголовок ответа бэкенда не помещался в один пакет,
+       то происходил segmentation fault; ошибка появилась в 0.1.29.
+
+    *) Исправление: если было сконфигурировано более 10 серверов или в
+       сервере не описана директива "listen", то при запуске мог произойти
+       segmentation fault.
+
+    *) Исправление: если ответ не помещался во временный файл, то мог
+       произойти segmentation fault.
+
+    *) Исправление: nginx возвращал ошибку 400 на запросы вида
+       "GET http://www.domain.com/uri HTTP/1.0"; ошибка появилась в 0.1.28.
+
+
+Изменения в nginx 0.1.34                                          26.05.2005
+
+    *) Исправление: при включении больших ответов с помощью SSI рабочий
+       процесс мог зациклиться.
+
+    *) Исправление: переменные, устанавливаемые директивой "set", не были
+       доступны в SSI.
+
+    *) Добавление: директива autoindex_localtime.
+
+    *) Исправление: пустое значение в директиве proxy_set_header запрещает
+       передачу заголовка.
+
+
+Изменения в nginx 0.1.33                                          23.05.2005
+
+    *) Исправление: nginx не собирался с параметром --without-pcre; ошибка
+       появилась в 0.1.29.
+
+    *) Исправление: 3, 5, 7 и 8 директив proxy_set_header на одном уровне
+       вызывали bus fault при запуске.
+
+    *) Исправление: в редиректах внутри HTTPS сервера был указан протокол
+       HTTP.
+
+    *) Исправление: если директива rewrite использовала выделения внутри
+       директивы if, то возвращалась ошибка 500.
+
+
+Изменения в nginx 0.1.32                                          19.05.2005
+
+    *) Исправление: в редиректах, выдаваемых с помощью директивы rewrite, не
+       передавались аргументы; ошибка появилась в 0.1.29.
+
+    *) Добавление: директива if поддерживает выделения в регулярных
+       выражениях.
+
+    *) Добавление: директива set поддерживает переменные и выделения из
+       регулярных выражений.
+
+    *) Добавление: в режиме прокси и FastCGI поддерживается строка заголовка
+       "X-Accel-Redirect" в ответе бэкенда.
+
+
+Изменения в nginx 0.1.31                                          16.05.2005
+
+    *) Исправление: при использовании SSL ответ мог передаваться не до
+       конца.
+
+    *) Исправление: ошибки при обработке SSI в ответе, полученного от
+       FastCGI-сервера.
+
+    *) Исправление: ошибки при использовании SSI и сжатия.
+
+    *) Исправление: редирект с кодом 301 передавался без тела ответа; ошибка
+       появилась в 0.1.30.
+
+
+Изменения в nginx 0.1.30                                          14.05.2005
+
+    *) Исправление: при использовании SSI рабочий процесс мог зациклиться.
+
+    *) Исправление: при использовании SSL ответ мог передаваться не до
+       конца.
+
+    *) Исправление: если длина части ответа, полученного за один раз от
+       проксируемого или FastCGI сервера была равна 500 байт, то nginx
+       возвращал код ответа 500; в режиме прокси ошибка появилась только в
+       0.1.29.
+
+    *) Исправление: nginx не считал неверными директивы с 8-ю или 9-ю
+       параметрами.
+
+    *) Добавление: директива return может возвращать код ответа 204.
+
+    *) Добавление: директива ignore_invalid_headers.
+
+
+Изменения в nginx 0.1.29                                          12.05.2005
+
+    *) Добавление: модуль ngx_http_ssi_module поддерживает команду include
+       virtual.
+
+    *) Добавление: модуль ngx_http_ssi_module поддерживает условную команду
+       вида 'if expr="$NAME"' и команды else и endif. Допускается только
+       один уровень вложенности.
+
+    *) Добавление: модуль ngx_http_ssi_module поддерживает две переменные
+       DATE_LOCAL и DATE_GMT и команду config timefmt.
+
+    *) Добавление: директива ssi_ignore_recycled_buffers.
+
+    *) Исправление: если переменная QUERY_STRING не была определена, то в
+       команде echo не ставилось значение по умолчанию.
+
+    *) Изменение: модуль ngx_http_proxy_module полностью переписан.
+
+    *) Добавление: директивы proxy_redirect, proxy_pass_request_headers,
+       proxy_pass_request_body и proxy_method.
+
+    *) Добавление: директива proxy_set_header. Директива proxy_x_var
+       упразднена и должна быть заменена директивой proxy_set_header.
+
+    *) Изменение: директива proxy_preserve_host упразднена и должна быть
+       заменена директивами "proxy_set_header Host $host" и "proxy_redirect
+       off" или директивой "proxy_set_header Host $host:$proxy_port" и
+       соответствующими ей директивами proxy_redirect.
+
+    *) Изменение: директива proxy_set_x_real_ip упразднена и должна быть
+       заменена директивой "proxy_set_header X-Real-IP $remote_addr".
+
+    *) Изменение: директива proxy_add_x_forwarded_for упразднена и должна
+       быть заменена директивой
+       "proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for".
+
+    *) Изменение: директива proxy_set_x_url упразднена и должна быть
+       заменена директивой
+       "proxy_set_header X-URL http://$host:$server_port$request_uri".
+
+    *) Добавление: директива fastcgi_param.
+
+    *) Изменение: директивы fastcgi_root, fastcgi_set_var и fastcgi_params
+       упразднены и должны быть замены директивами fastcgi_param.
+
+    *) Добавление: директива index может использовать переменные.
+
+    *) Добавление: директива index может быть указана на уровне http и
+       server.
+
+    *) Изменение: только последний параметр в директиве index может быть
+       абсолютным.
+
+    *) Добавление: в директиве rewrite могут использоваться переменные.
+
+    *) Добавление: директива internal.
+
+    *) Добавление: переменные CONTENT_LENGTH, CONTENT_TYPE, REMOTE_PORT,
+       SERVER_ADDR, SERVER_PORT, SERVER_PROTOCOL, DOCUMENT_ROOT,
+       SERVER_NAME, REQUEST_METHOD, REQUEST_URI и REMOTE_USER.
+
+    *) Изменение: nginx теперь передаёт неверные строки в заголовках запроса
+       клиента и ответа бэкенда.
+
+    *) Исправление: если бэкенд долго не передавал ответ и send_timeout был
+       меньше, чем proxy_read_timeout, то клиенту возвращался ответ 408.
+
+    *) Исправление: если бэкенд передавал неверную строку в заголовке
+       ответа, то происходил segmentation fault; ошибка появилась в 0.1.26.
+
+    *) Исправление: при использовании отказоустойчивой конфигурации в
+       FastCGI мог происходить segmentation fault.
+
+    *) Исправление: директива expires не удаляла уже установленные строки
+       заголовка "Expires" и "Cache-Control".
+
+    *) Исправление: nginx не учитывал завершающую точку в строке заголовка
+       запроса "Host".
+
+    *) Исправление: модуль ngx_http_auth_module не работал на Linux.
+
+    *) Исправление: директива rewrite неверно работала, если в запросе
+       присутствовали аргументы.
+
+    *) Исправление: nginx не собирался на MacOS X.
+
+
+Изменения в nginx 0.1.28                                          08.04.2005
+
+    *) Исправление: при проксировании больших файлов nginx сильно нагружал
+       процессор.
+
+    *) Исправление: nginx не собирался gcc 4.0 на Linux.
+
+
+Изменения в nginx 0.1.27                                          28.03.2005
+
+    *) Добавление: параметр blocked в директиве valid_referers.
+
+    *) Изменение: ошибки обработки заголовка запроса теперь записываются на
+       уровне info, в лог также записывается имя сервера и строки заголовка
+       запроса "Host" и "Referer".
+
+    *) Изменение: при записи ошибок в лог записывается также строка
+       заголовка запроса "Host".
+
+    *) Добавление: директива proxy_pass_unparsed_uri. Специальная обработка
+       символов "://" в URI, введённая в версии 0.1.11, теперь упразднена.
+
+    *) Исправление: nginx не собирался на FreeBSD и Linux, если был указан
+       параметр конфигурации --without-ngx_http_auth_basic_module.
+
+
+Изменения в nginx 0.1.26                                          22.03.2005
+
+    *) Изменение: неверные строки заголовка, переданные клиентом, теперь
+       игнорируется и записываются в error_log на уровне info.
+
+    *) Изменение: при записи ошибок в лог записывается также имя сервера,
+       при обращении к которому произошла ошибка.
+
+    *) Добавление: модуль ngx_http_auth_basic_module и директивы auth_basic
+       и auth_basic_user_file.
+
+
+Изменения в nginx 0.1.25                                          19.03.2005
+
+    *) Исправление: nginx не работал на Linux parisc.
+
+    *) Добавление: nginx теперь не запускается под FreeBSD, если значение
+       sysctl kern.ipc.somaxconn слишком большое.
+
+    *) Исправление: если модуль ngx_http_index_module делал внутреннее
+       перенаправление запроса в модули ngx_http_proxy_module или
+       ngx_http_fastcgi_module, то файл индекса не закрывался после
+       обслуживания запроса.
+
+    *) Добавление: директива proxy_pass может использоваться в location,
+       заданных регулярным выражением.
+
+    *) Добавление: модуль ngx_http_rewrite_filter_module поддерживает
+       условия вида "if ($HTTP_USER_AGENT ~ MSIE)".
+
+    *) Исправление: nginx очень медленно запускался при большом количестве
+       адресов и использовании текстовых значений в директиве geo.
+
+    *) Изменение: имя переменной в директиве geo нужно указывать, как $name.
+       Прежний вариант без "$" пока работает, но вскоре будет убран.
+
+    *) Добавление: параметр лога "%{VARIABLE}v".
+
+    *) Добавление: директива "set $name value".
+
+    *) Исправление: совместимость с gcc 4.0.
+
+    *) Добавление: параметр автоконфигурации --with-openssl-opt=OPTIONS.
+
+
+Изменения в nginx 0.1.24                                          04.03.2005
+
+    *) Добавление: модуль ngx_http_ssi_filter_module поддерживает переменные
+       QUERY_STRING и DOCUMENT_URI.
+
+    *) Исправление: модуль ngx_http_autoindex_module мог выдавать ответ 404
+       на существующий каталог, если этот каталог был указан как alias.
+
+    *) Исправление: модуль ngx_http_ssi_filter_module неправильно работал
+       при больших ответах.
+
+    *) Исправление: отсутствие строки заголовка "Referer" всегда считалось
+       правильным referrer'ом.
+
+
+Изменения в nginx 0.1.23                                          01.03.2005
+
+    *) Добавление: модуль ngx_http_ssi_filter_module и директивы ssi,
+       ssi_silent_errors и ssi_min_file_chunk. Поддерживаются команды 'echo
+       var="HTTP_..." default=""' и 'echo var="REMOTE_ADDR"'.
+
+    *) Добавление: параметр лога %request_time.
+
+    *) Добавление: если запрос пришёл без строки заголовка "Host", то
+       директива proxy_preserve_host устанавливает в качестве этого
+       заголовка первое имя сервера из директивы server_name.
+
+    *) Исправление: nginx не собирался на платформах, отличных от i386,
+       amd64, sparc и ppc; ошибка появилась в 0.1.22.
+
+    *) Исправление: модуль ngx_http_autoindex_module теперь показывает
+       информацию не о символическом линке, а о файле или каталоге, на
+       который он указывает.
+
+    *) Исправление: если клиенту ничего не передавалось, то параметр
+       %apache_length записывал в лог отрицательную длину заголовка ответа.
+
+
+Изменения в nginx 0.1.22                                          22.02.2005
+
+    *) Исправление: модуль ngx_http_stub_status_module показывал неверную
+       статистику для обработанных соединений, если использовалось
+       проксирование или FastCGI-сервер.
+
+    *) Исправление: на Linux и Solaris установочные пути были неверно
+       заключены в кавычки; ошибка появилась в 0.1.21.
+
+
+Изменения в nginx 0.1.21                                          22.02.2005
+
+    *) Исправление: модуль ngx_http_stub_status_module показывал неверную
+       статистику при использовании метода rtsig или при использовании
+       нескольких рабочих процессов на SMP машине.
+
+    *) Исправление: nginx не собирался компилятором icc под Линуксом или
+       если библиотека zlib-1.2.x собиралась из исходных текстов.
+
+    *) Исправление: nginx не собирался под NetBSD 2.0.
+
+
+Изменения в nginx 0.1.20                                          17.02.2005
+
+    *) Добавление: новые параметры script_filename и remote_port в директиве
+       fastcgi_params.
+
+    *) Исправление: неправильно обрабатывался поток stderr от
+       FastCGI-сервера.
+
+
+Изменения в nginx 0.1.19                                          16.02.2005
+
+    *) Исправление: если в запросе есть нуль, то для локальных запросов
+       теперь возвращается ошибка 404.
+
+    *) Исправление: nginx не собирался под NetBSD 2.0.
+
+    *) Исправление: во время чтения тела запроса клиента в SSL соединении
+       мог произойти таймаут.
+
+
+Изменения в nginx 0.1.18                                          09.02.2005
+
+    *) Изменение: для совместимости с Solaris 10 в директивах devpoll_events
+       и devpoll_changes значения по умолчанию уменьшены с 512 до 32.
+
+    *) Исправление: директивы proxy_set_x_var и fastcgi_set_var не
+       наследовались.
+
+    *) Исправление: в директиве rewrite, возвращающей редирект, аргументы
+       присоединялись к URI через символ "&" вместо "?".
+
+    *) Исправление: строки для модуля ngx_http_geo_module без символа ";" во
+       включённом файле игнорировались.
+
+    *) Добавление: модуль ngx_http_stub_status_module.
+
+    *) Исправление: неизвестный формат лог-файла в директиве access_log
+       вызывал segmentation fault.
+
+    *) Добавление: новый параметр document_root в директиве fastcgi_params.
+
+    *) Добавление: директива fastcgi_redirect_errors.
+
+    *) Добавление: новый модификатор break в директиве rewrite позволяет
+       прекратить цикл rewrite/location и устанавливает текущую конфигурацию
+       для запроса.
+
+
+Изменения в nginx 0.1.17                                          03.02.2005
+
+    *) Изменение: модуль ngx_http_rewrite_module полностью переписан. Теперь
+       можно делать редиректы, возвращать коды ошибок и проверять переменные
+       и рефереры. Эти директивы можно использовать внутри location.
+       Директива redirect упразднена.
+
+    *) Добавление: модуль ngx_http_geo_module.
+
+    *) Добавление: директивы proxy_set_x_var и fastcgi_set_var.
+
+    *) Исправление: конфигурация location с модификатором "=" могла
+       использоваться в другом location.
+
+    *) Исправление: правильный тип ответа выставлялся только для запросов, у
+       которых в расширении были только маленькие буквы.
+
+    *) Исправление: если для location установлен proxy_pass или
+       fastcgi_pass, и доступ к нему запрещался, а ошибка перенаправлялась
+       на статическую страницу, то происходил segmentation fault.
+
+    *) Исправление: если в проксированном ответе в заголовке "Location"
+       передавался относительный URL, то к нему добавлялось имя хоста и
+       слэш; ошибка появилась в 0.1.14.
+
+    *) Исправление: на Linux в лог не записывался текст системной ошибки.
+
+
+Изменения в nginx 0.1.16                                          25.01.2005
+
+    *) Исправление: если ответ передавался chunk'ами, то при запросе HEAD
+       выдавался завершающий chunk.
+
+    *) Исправление: заголовок "Connection: keep-alive" выдавался, даже если
+       директива keepalive_timeout запрещала использование keep-alive.
+
+    *) Исправление: ошибки в модуле ngx_http_fastcgi_module вызывали
+       segmentation fault.
+
+    *) Исправление: при использовании SSL сжатый ответ мог передаваться не
+       до конца.
+
+    *) Исправление: опции TCP_NODELAY, TCP_NOPUSH и TCP_CORK, специфичные
+       для TCP сокетов, не используются для unix domain сокетов.
+
+    *) Добавление: директива rewrite поддерживает перезаписывание
+       аргументов.
+
+    *) Исправление: на запрос POST с заголовком "Content-Length: 0"
+       возвращался ответ 400; ошибка появилась в 0.1.14.
+
+
+Изменения в nginx 0.1.15                                          19.01.2005
+
+    *) Исправление: ошибка соединения с FastCGI-сервером вызывала
+       segmentation fault.
+
+    *) Исправление: корректная обработка регулярного выражения, в котором
+       число выделенных частей не совпадает с числом подстановок.
+
+    *) Добавление: location, который передаётся FastCGI-серверу, может быть
+       задан с помощью регулярного выражения.
+
+    *) Исправление: параметр FastCGI REQUEST_URI теперь передаётся вместе с
+       аргументами и в том виде, в котором был получен от клиента.
+
+    *) Исправление: для использования регулярных выражений в location нужно
+       было собирать nginx вместе с ngx_http_rewrite_module.
+
+    *) Исправление: если бэкенд слушал на 80-ом порту, то при использовании
+       директивы "proxy_preserve_host on" в заголовке "Host" указывался
+       также порт 80; ошибка появилась в 0.1.14.
+
+    *) Исправление: если задать одинаковые пути в параметрах
+       автоконфигурации --http-client-body-temp-path=PATH и
+       --http-proxy-temp-path=PATH или --http-client-body-temp-path=PATH и
+       --http-fastcgi-temp-path=PATH, то происходил segmentation fault.
+
+
+Изменения в nginx 0.1.14                                          18.01.2005
+
+    *) Добавление: параметры автоконфигурации
+       --http-client-body-temp-path=PATH, --http-proxy-temp-path=PATH и
+       --http-fastcgi-temp-path=PATH
+
+    *) Изменение: имя каталога с временными файлами, содержащие тело запроса
+       клиента, задаётся директивой client_body_temp_path, по умолчанию
+       <prefix>/client_body_temp.
+
+    *) Добавление: модуль ngx_http_fastcgi_module и директивы fastcgi_pass,
+       fastcgi_root, fastcgi_index, fastcgi_params, fastcgi_connect_timeout,
+       fastcgi_send_timeout, fastcgi_read_timeout, fastcgi_send_lowat,
+       fastcgi_header_buffer_size, fastcgi_buffers,
+       fastcgi_busy_buffers_size, fastcgi_temp_path,
+       fastcgi_max_temp_file_size, fastcgi_temp_file_write_size,
+       fastcgi_next_upstream и fastcgi_x_powered_by.
+
+    *) Исправление: ошибка "[alert] zero size buf"; ошибка появилась в
+       0.1.3.
+
+    *) Изменение: в директиве proxy_pass нужно обязательно указывать URI
+       после имени хоста.
+
+    *) Изменение: если в URI встречался символ %3F, то он считался началом
+       строки аргументов.
+
+    *) Добавление: поддержка unix domain сокетов в модуле
+       ngx_http_proxy_module.
+
+    *) Добавление: директивы ssl_engine и ssl_ciphers.
+       Спасибо Сергею Скворцову за SSL-акселератор.
+
+
+Изменения в nginx 0.1.13                                          21.12.2004
+
+    *) Добавление: директивы server_names_hash и
+       server_names_hash_threshold.
+
+    *) Исправление: имена *.domain.tld в директиве server_name не работали.
+
+    *) Исправление: параметр лога %request_length записывал неверную длину.
+
+
+Изменения в nginx 0.1.12                                          06.12.2004
+
+    *) Добавление: параметр лога %request_length.
+
+    *) Исправление: при использовании /dev/poll, select и poll на
+       платформах, где возможны ложные срабатывания указанных методов, могли
+       быть длительные задержки при обработке запроса по keep-alive
+       соединению. Наблюдалось по крайней мере на Solaris с использованием
+       /dev/poll.
+
+    *) Исправление: директива send_lowat игнорируется на Linux, так как
+       Linux не поддерживает опцию SO_SNDLOWAT.
+
+
+Изменения в nginx 0.1.11                                          02.12.2004
+
+    *) Добавление: директива worker_priority.
+
+    *) Изменение: под FreeBSD директивы tcp_nopush и tcp_nodelay вместе
+       влияют на передачу ответа.
+
+    *) Исправление: nginx не вызывал initgroups().
+       Спасибо Андрею Ситникову и Андрею Нигматулину.
+
+    *) Изменение: ngx_http_auto_index_module теперь выдаёт размер файлов в
+       байтах.
+
+    *) Исправление: ngx_http_auto_index_module возвращал ошибку 500, если в
+       каталоге есть битый symlink.
+
+    *) Исправление: файлы больше 4G не передавались с использованием
+       sendfile.
+
+    *) Исправление: если бэкенд резолвился в несколько адресов и при
+       ожидании от него ответа происходила ошибка, то процесс зацикливался.
+
+    *) Исправление: при использовании метода /dev/poll рабочий процесс мог
+       завершиться с сообщением "unknown cycle".
+
+    *) Исправление: ошибки "close() channel failed".
+
+    *) Исправление: автоматическое определение групп nobody и nogroup.
+
+    *) Исправление: директива send_lowat не работала на Linux.
+
+    *) Исправление: если в конфигурации не было раздела events, то
+       происходил segmentation fault.
+
+    *) Исправление: nginx не собирался под OpenBSD.
+
+    *) Исправление: двойные слэшы в "://" в URI превращались в ":/".
+
+
+Изменения в nginx 0.1.10                                          26.11.2004
+
+    *) Исправление: если в запросе без аргументов есть "//", "/./", "/../"
+       или "%XX", то терялся последний символ в строке запроса; ошибка
+       появилась в 0.1.9.
+
+    *) Исправление: исправление в версии 0.1.9 для файлов больше 2G на Linux
+       не работало.
+
+
+Изменения в nginx 0.1.9                                           25.11.2004
+
+    *) Исправление: если в запросе есть "//", "/./", "/../" или "%XX", то
+       проксируемый запрос передавался без аргументов.
+
+    *) Исправление: при сжатии больших ответов иногда они передавались не
+       полностью.
+
+    *) Исправление: не передавались файлы больше 2G на Linux,
+       неподдерживающем sendfile64().
+
+    *) Исправление: на Linux при конфигурации сборки нужно было обязательно
+       использовать параметр --with-poll_module; ошибка появилась в 0.1.8.
+
+
+Изменения в nginx 0.1.8                                           20.11.2004
+
+    *) Исправление: ошибка в модуле ngx_http_autoindex_module при показе
+       длинных имён файлов.
+
+    *) Добавление: модификатор "^~" в директиве location.
+
+    *) Добавление: директива proxy_max_temp_file_size.
+
+
+Изменения в nginx 0.1.7                                           12.11.2004
+
+    *) Исправление: при использовании sendfile, если передаваемый файл
+       менялся, то мог произойти segmentation fault на FreeBSD; ошибка
+       появилась в 0.1.5.
+
+
+Изменения в nginx 0.1.6                                           11.11.2004
+
+    *) Исправление: при некоторых комбинациях директив location c
+       регулярными выражениями использовалась конфигурация не из того
+       location.
+
+
+Изменения в nginx 0.1.5                                           11.11.2004
+
+    *) Исправление: на Solaris и Linux могло быть очень много сообщений
+       "recvmsg() returned not enough data".
+
+    *) Исправление: в режиме прокси без использования sendfile на Solaris
+       возникала ошибка "writev() failed (22: Invalid argument)". На других
+       платформах, не поддерживающих sendfile, процесс зацикливался.
+
+    *) Исправление: при использовании sendfile в режиме прокси на Solaris
+       возникал segmentation fault.
+
+    *) Исправление: segmentation fault на Solaris.
+
+    *) Исправление: обновление исполняемого файла на лету не работало на
+       Linux.
+
+    *) Исправление: в списке файлов, выдаваемом модулем
+       ngx_http_autoindex_module, не перекодировались пробелы, кавычки и
+       знаки процента.
+
+    *) Изменение: уменьшение операций копирования.
+
+    *) Добавление: директива userid_p3p.
+
+
+Изменения в nginx 0.1.4                                           26.10.2004
+
+    *) Исправление: ошибка в модуле ngx_http_autoindex_module.
+
+
+Изменения в nginx 0.1.3                                           25.10.2004
+
+    *) Добавление: модуль ngx_http_autoindex_module и директива autoindex.
+
+    *) Добавление: директива proxy_set_x_url.
+
+    *) Исправление: модуль проксировании мог привести к зацикливанию, если
+       не использовался sendfile.
+
+
+Изменения в nginx 0.1.2                                           21.10.2004
+
+    *) Добавление: параметры --user=USER, --group=GROUP и
+       --with-ld-opt=OPTIONS в configure.
+
+    *) Добавление: директива server_name поддерживает *.domain.tld.
+
+    *) Исправление: улучшена переносимость на неизвестные платформы.
+
+    *) Исправление: нельзя переконфигурировать nginx, если конфигурационный
+       файл указан в командной строке; ошибка появилась в 0.1.1.
+
+    *) Исправление: модуль проксировании мог привести к зацикливанию, если
+       не использовался sendfile.
+
+    *) Исправление: при использовании sendfile текст ответа не
+       перекодировался согласно директивам модуля charset; ошибка появилась
+       в 0.1.1.
+
+    *) Исправление: очень редкая ошибка при обработке kqueue.
+
+    *) Исправление: модуль сжатия сжимал уже сжатые ответы, полученные при
+       проксировании.
+
+
+Изменения в nginx 0.1.1                                           11.10.2004
+
+    *) Добавление: директива gzip_types.
+
+    *) Добавление: директива tcp_nodelay.
+
+    *) Добавление: директива send_lowat работает не только на платформах,
+       поддерживающих kqueue NOTE_LOWAT, но и на всех, поддерживающих
+       SO_SNDLOWAT.
+
+    *) Добавление: эмуляция setproctitle() для Linux и Solaris.
+
+    *) Исправление: ошибка при переписывании заголовка "Location" при
+       проксировании.
+
+    *) Исправление: ошибка в модуле ngx_http_chunked_module, приводившая к
+       зацикливанию.
+
+    *) Исправление: ошибки в модуле /dev/poll.
+
+    *) Исправление: при проксировании и использовании временных файлов
+       ответы портились.
+
+    *) Исправление: бэкенду передавались запросы с неперекодированными
+       символами.
+
+    *) Исправление: на Linux 2.4 при конфигурации сборки нужно было
+       обязательно использовать параметр --with-poll_module.
+
+
+Изменения в nginx 0.1.0                                           04.10.2004
+
+    *) Первая публично доступная версия.
+
diff --git a/nginx/LICENSE b/nginx/LICENSE
new file mode 100644 (file)
index 0000000..9401174
--- /dev/null
@@ -0,0 +1,26 @@
+/* 
+ * Copyright (C) 2002-2018 Igor Sysoev
+ * Copyright (C) 2011-2018 Nginx, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
diff --git a/nginx/README b/nginx/README
new file mode 100644 (file)
index 0000000..2f68e14
--- /dev/null
@@ -0,0 +1,3 @@
+
+Documentation is available at http://nginx.org
+
diff --git a/nginx/auto/cc/acc b/nginx/auto/cc/acc
new file mode 100644 (file)
index 0000000..64fa671
--- /dev/null
@@ -0,0 +1,14 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+# aCC: HP ANSI C++ B3910B A.03.55.02
+
+# C89 mode
+
+CFLAGS="$CFLAGS -Ae"
+CC_TEST_FLAGS="-Ae"
+
+PCRE_OPT="$PCRE_OPT -Ae"
+ZLIB_OPT="$ZLIB_OPT -Ae"
diff --git a/nginx/auto/cc/bcc b/nginx/auto/cc/bcc
new file mode 100644 (file)
index 0000000..e990a9f
--- /dev/null
@@ -0,0 +1,71 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+# Borland C++ 5.5
+
+# optimizations
+
+# maximize speed
+CFLAGS="$CFLAGS -O2"
+
+case $CPU in
+    pentium)
+        # optimize for Pentium and Athlon
+        CPU_OPT="-5"
+    ;;
+
+    pentiumpro)
+        # optimize for Pentium Pro, Pentium II and Pentium III
+        CPU_OPT="-6"
+    ;;
+esac
+
+# __stdcall
+#CPU_OPT="$CPU_OPT -ps"
+# __fastcall
+#CPU_OPT="$CPU_OPT -pr"
+
+CFLAGS="$CFLAGS $CPU_OPT"
+
+# multithreaded
+CFLAGS="$CFLAGS -tWM"
+
+# stop on warning
+CFLAGS="$CFLAGS -w!"
+
+# disable logo
+CFLAGS="$CFLAGS -q"
+
+
+# precompiled headers
+CORE_DEPS="$CORE_DEPS $NGX_OBJS/ngx_config.csm"
+NGX_PCH="$NGX_OBJS/ngx_config.csm"
+NGX_BUILD_PCH="-H=$NGX_OBJS/ngx_config.csm"
+NGX_USE_PCH="-Hu -H=$NGX_OBJS/ngx_config.csm"
+
+
+# Win32 GUI mode application
+#LINK="\$(CC) -laa"
+
+
+# the resource file
+NGX_RES="$NGX_OBJS/nginx.res"
+NGX_RCC="brcc32 -fo$NGX_OBJS/nginx.res \$(CORE_INCS) $NGX_WIN32_RC"
+# the pragma allows to link the resource file using bcc32 and
+# to avoid the direct ilink32 calling and the c0w32.obj's WinMain/main problem
+NGX_PRAGMA="#pragma resource \"$NGX_OBJS/nginx.res\""
+
+
+ngx_include_opt="-I"
+ngx_objout="-o"
+ngx_binout="-e"
+ngx_objext="obj"
+
+ngx_long_start='@&&|
+       '
+ngx_long_end='|'
+
+ngx_regex_dirsep='\\'
+ngx_dirsep="\\"
diff --git a/nginx/auto/cc/ccc b/nginx/auto/cc/ccc
new file mode 100644 (file)
index 0000000..c964045
--- /dev/null
@@ -0,0 +1,46 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+# Compaq C V6.5-207
+
+ngx_include_opt="-I"
+
+# warnings
+
+CFLAGS="$CFLAGS -msg_enable level6 -msg_fatal level6"
+
+CFLAGS="$CFLAGS -msg_disable unknownmacro"
+CFLAGS="$CFLAGS -msg_disable unusedincl"
+CFLAGS="$CFLAGS -msg_disable unnecincl"
+CFLAGS="$CFLAGS -msg_disable nestincl"
+CFLAGS="$CFLAGS -msg_disable strctpadding"
+CFLAGS="$CFLAGS -msg_disable ansialiascast"
+CFLAGS="$CFLAGS -msg_disable inlinestoclsmod"
+CFLAGS="$CFLAGS -msg_disable cxxkeyword"
+CFLAGS="$CFLAGS -msg_disable longlongsufx"
+CFLAGS="$CFLAGS -msg_disable valuepres"
+
+# STUB
+CFLAGS="$CFLAGS -msg_disable truncintcast"
+CFLAGS="$CFLAGS -msg_disable trunclongcast"
+
+CFLAGS="$CFLAGS -msg_disable truncintasn"
+CFLAGS="$CFLAGS -msg_disable trunclongint"
+CFLAGS="$CFLAGS -msg_disable intconcastsgn"
+CFLAGS="$CFLAGS -msg_disable intconstsign"
+CFLAGS="$CFLAGS -msg_disable switchlong"
+CFLAGS="$CFLAGS -msg_disable subscrbounds2"
+
+CFLAGS="$CFLAGS -msg_disable hexoctunsign"
+
+CFLAGS="$CFLAGS -msg_disable ignorecallval"
+CFLAGS="$CFLAGS -msg_disable nonstandcast"
+CFLAGS="$CFLAGS -msg_disable embedcomment"
+CFLAGS="$CFLAGS -msg_disable unreachcode"
+CFLAGS="$CFLAGS -msg_disable questcompare2"
+CFLAGS="$CFLAGS -msg_disable unusedtop"
+CFLAGS="$CFLAGS -msg_disable unrefdecl"
+
+CFLAGS="$CFLAGS -msg_disable bitnotint"
diff --git a/nginx/auto/cc/clang b/nginx/auto/cc/clang
new file mode 100644 (file)
index 0000000..9d900c2
--- /dev/null
@@ -0,0 +1,98 @@
+
+# Copyright (C) Nginx, Inc.
+
+
+# clang
+
+
+NGX_CLANG_VER=`$CC -v 2>&1 | grep 'version' 2>&1 \
+                           | sed -e 's/^.* version \(.*\)/\1/'`
+
+echo " + clang version: $NGX_CLANG_VER"
+
+have=NGX_COMPILER value="\"clang $NGX_CLANG_VER\"" . auto/define
+
+
+CC_TEST_FLAGS="-pipe"
+
+
+# optimizations
+
+#NGX_CLANG_OPT="-O2"
+#NGX_CLANG_OPT="-Oz"
+NGX_CLANG_OPT="-O"
+
+case $CPU in
+    pentium)
+        # optimize for Pentium
+        CPU_OPT="-march=pentium"
+        NGX_CPU_CACHE_LINE=32
+    ;;
+
+    pentiumpro | pentium3)
+        # optimize for Pentium Pro, Pentium II and Pentium III
+        CPU_OPT="-march=pentiumpro"
+        NGX_CPU_CACHE_LINE=32
+    ;;
+
+    pentium4)
+        # optimize for Pentium 4
+        CPU_OPT="-march=pentium4"
+        NGX_CPU_CACHE_LINE=128
+    ;;
+
+    athlon)
+        # optimize for Athlon
+        CPU_OPT="-march=athlon"
+        NGX_CPU_CACHE_LINE=64
+    ;;
+
+    opteron)
+        # optimize for Opteron
+        CPU_OPT="-march=opteron"
+        NGX_CPU_CACHE_LINE=64
+    ;;
+
+esac
+
+CC_AUX_FLAGS="$CC_AUX_FLAGS $CPU_OPT"
+
+
+CFLAGS="$CFLAGS -pipe $CPU_OPT"
+
+if [ ".$PCRE_OPT" = "." ]; then
+    PCRE_OPT="-O2 -pipe $CPU_OPT"
+else
+    PCRE_OPT="$PCRE_OPT -pipe"
+fi
+
+if [ ".$ZLIB_OPT" = "." ]; then
+    ZLIB_OPT="-O2 -pipe $CPU_OPT"
+else
+    ZLIB_OPT="$ZLIB_OPT -pipe"
+fi
+
+
+# warnings
+
+CFLAGS="$CFLAGS $NGX_CLANG_OPT -Wall -Wextra -Wpointer-arith"
+CFLAGS="$CFLAGS -Wconditional-uninitialized"
+#CFLAGS="$CFLAGS -Wmissing-prototypes"
+
+# we have a lot of unused function arguments
+CFLAGS="$CFLAGS -Wno-unused-parameter"
+
+# deprecated system OpenSSL library on OS X
+if [ "$NGX_SYSTEM" = "Darwin" ]; then
+    CFLAGS="$CFLAGS -Wno-deprecated-declarations"
+fi
+
+# stop on warning
+CFLAGS="$CFLAGS -Werror"
+
+# debug
+CFLAGS="$CFLAGS -g"
+
+if [ ".$CPP" = "." ]; then
+    CPP="$CC -E"
+fi
diff --git a/nginx/auto/cc/conf b/nginx/auto/cc/conf
new file mode 100644 (file)
index 0000000..afbca62
--- /dev/null
@@ -0,0 +1,254 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+LINK="\$(CC)"
+
+MAIN_LINK=
+MODULE_LINK="-shared"
+
+ngx_include_opt="-I "
+ngx_compile_opt="-c"
+ngx_pic_opt="-fPIC"
+ngx_objout="-o "
+ngx_binout="-o "
+ngx_objext="o"
+ngx_binext=
+ngx_modext=".so"
+
+ngx_long_start=
+ngx_long_end=
+
+ngx_regex_dirsep="\/"
+ngx_dirsep='/'
+
+ngx_regex_cont=' \\\
+       '
+ngx_cont=' \
+       '
+ngx_tab=' \
+               '
+ngx_spacer=
+
+ngx_long_regex_cont=$ngx_regex_cont
+ngx_long_cont=$ngx_cont
+
+. auto/cc/name
+
+if test -n "$CFLAGS"; then
+
+    CC_TEST_FLAGS="$CFLAGS $NGX_CC_OPT"
+
+    case $NGX_CC_NAME in
+
+        ccc)
+            # Compaq C V6.5-207
+
+            ngx_include_opt="-I"
+        ;;
+
+        sunc)
+
+            MAIN_LINK=
+            MODULE_LINK="-G"
+
+            case "$NGX_MACHINE" in
+
+                i86pc)
+                    NGX_AUX=" src/os/unix/ngx_sunpro_x86.il"
+                ;;
+
+                sun4u | sun4v)
+                    NGX_AUX=" src/os/unix/ngx_sunpro_sparc64.il"
+                ;;
+
+            esac
+
+            case $CPU in
+
+                amd64)
+                    NGX_AUX=" src/os/unix/ngx_sunpro_amd64.il"
+                ;;
+
+            esac
+        ;;
+
+    esac
+
+else
+
+    case $NGX_CC_NAME in
+        gcc)
+            # gcc 2.7.2.3, 2.8.1, 2.95.4, egcs-1.1.2
+            #     3.0.4, 3.1.1, 3.2.3, 3.3.2, 3.3.3, 3.3.4, 3.4.0, 3.4.2
+            #     4.0.0, 4.0.1, 4.1.0
+
+            . auto/cc/gcc
+        ;;
+
+        clang)
+            # Clang C compiler
+
+            . auto/cc/clang
+        ;;
+
+        icc)
+            # Intel C++ compiler 7.1, 8.0, 8.1
+
+            . auto/cc/icc
+        ;;
+
+        sunc)
+            # Sun C 5.7 Patch 117837-04 2005/05/11
+
+            . auto/cc/sunc
+        ;;
+
+        ccc)
+            # Compaq C V6.5-207
+
+            . auto/cc/ccc
+        ;;
+
+        acc)
+            # aCC: HP ANSI C++ B3910B A.03.55.02
+
+            . auto/cc/acc
+        ;;
+
+        msvc*)
+            # MSVC++ 6.0 SP2, MSVC++ Toolkit 2003
+
+            . auto/cc/msvc
+        ;;
+
+        owc)
+            # Open Watcom C 1.0, 1.2
+
+            . auto/cc/owc
+        ;;
+
+        bcc)
+            # Borland C++ 5.5
+
+            . auto/cc/bcc
+        ;;
+
+    esac
+
+    CC_TEST_FLAGS="$CC_TEST_FLAGS $NGX_CC_OPT"
+
+fi
+
+CFLAGS="$CFLAGS $NGX_CC_OPT"
+NGX_TEST_LD_OPT="$NGX_LD_OPT"
+
+if [ "$NGX_PLATFORM" != win32 ]; then
+
+    if test -n "$NGX_LD_OPT"; then
+        ngx_feature=--with-ld-opt=\"$NGX_LD_OPT\"
+        ngx_feature_name=
+        ngx_feature_run=no
+        ngx_feature_incs=
+        ngx_feature_path=
+        ngx_feature_libs=
+        ngx_feature_test=
+        . auto/feature
+
+        if [ $ngx_found = no ]; then
+            echo $0: error: the invalid value in --with-ld-opt=\"$NGX_LD_OPT\"
+            echo
+            exit 1
+        fi
+    fi
+
+
+    ngx_feature="-Wl,-E switch"
+    ngx_feature_name=
+    ngx_feature_run=no
+    ngx_feature_incs=
+    ngx_feature_path=
+    ngx_feature_libs=-Wl,-E
+    ngx_feature_test=
+    . auto/feature
+
+    if [ $ngx_found = yes ]; then
+        MAIN_LINK="-Wl,-E"
+    fi
+
+
+    if [ "$NGX_CC_NAME" = "sunc" ]; then
+        echo "checking for gcc builtin atomic operations ... disabled"
+    else
+        ngx_feature="gcc builtin atomic operations"
+        ngx_feature_name=NGX_HAVE_GCC_ATOMIC
+        ngx_feature_run=yes
+        ngx_feature_incs=
+        ngx_feature_path=
+        ngx_feature_libs=
+        ngx_feature_test="long  n = 0;
+                          if (!__sync_bool_compare_and_swap(&n, 0, 1))
+                              return 1;
+                          if (__sync_fetch_and_add(&n, 1) != 1)
+                              return 1;
+                          if (n != 2)
+                              return 1;
+                          __sync_synchronize();"
+        . auto/feature
+    fi
+
+
+    if [ "$NGX_CC_NAME" = "ccc" ]; then
+        echo "checking for C99 variadic macros ... disabled"
+    else
+        ngx_feature="C99 variadic macros"
+        ngx_feature_name="NGX_HAVE_C99_VARIADIC_MACROS"
+        ngx_feature_run=yes
+        ngx_feature_incs="#include <stdio.h>
+#define var(dummy, ...)  sprintf(__VA_ARGS__)"
+        ngx_feature_path=
+        ngx_feature_libs=
+        ngx_feature_test="char  buf[30]; buf[0] = '0';
+                          var(0, buf, \"%d\", 1);
+                          if (buf[0] != '1') return 1"
+        . auto/feature
+    fi
+
+
+    ngx_feature="gcc variadic macros"
+    ngx_feature_name="NGX_HAVE_GCC_VARIADIC_MACROS"
+    ngx_feature_run=yes
+    ngx_feature_incs="#include <stdio.h>
+#define var(dummy, args...)  sprintf(args)"
+    ngx_feature_path=
+    ngx_feature_libs=
+    ngx_feature_test="char  buf[30]; buf[0] = '0';
+                      var(0, buf, \"%d\", 1);
+                      if (buf[0] != '1') return 1"
+    . auto/feature
+
+
+    ngx_feature="gcc builtin 64 bit byteswap"
+    ngx_feature_name="NGX_HAVE_GCC_BSWAP64"
+    ngx_feature_run=no
+    ngx_feature_incs=
+    ngx_feature_path=
+    ngx_feature_libs=
+    ngx_feature_test="if (__builtin_bswap64(0)) return 1"
+    . auto/feature
+
+
+#    ngx_feature="inline"
+#    ngx_feature_name=
+#    ngx_feature_run=no
+#    ngx_feature_incs="int inline f(void) { return 1 }"
+#    ngx_feature_path=
+#    ngx_feature_libs=
+#    ngx_feature_test=
+#    . auto/feature
+#
+#    if [ $ngx_found = yes ]; then
+#    fi
+
+fi
diff --git a/nginx/auto/cc/gcc b/nginx/auto/cc/gcc
new file mode 100644 (file)
index 0000000..a5c5c18
--- /dev/null
@@ -0,0 +1,179 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+# gcc 2.7.2.3, 2.8.1, 2.95.4, egcs-1.1.2
+#     3.0.4, 3.1.1, 3.2.3, 3.3.2, 3.3.3, 3.3.4, 3.4.0, 3.4.2
+#     4.0.0, 4.0.1, 4.1.0
+
+
+NGX_GCC_VER=`$CC -v 2>&1 | grep 'gcc version' 2>&1 \
+                         | sed -e 's/^.* version \(.*\)/\1/'`
+
+echo " + gcc version: $NGX_GCC_VER"
+
+have=NGX_COMPILER value="\"gcc $NGX_GCC_VER\"" . auto/define
+
+
+# Solaris 7's /usr/ccs/bin/as does not support "-pipe"
+
+CC_TEST_FLAGS="-pipe"
+
+ngx_feature="gcc -pipe switch"
+ngx_feature_name=
+ngx_feature_run=no
+ngx_feature_incs=
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test=
+. auto/feature
+
+CC_TEST_FLAGS=
+
+if [ $ngx_found = yes ]; then
+    PIPE="-pipe"
+fi
+
+
+case "$NGX_MACHINE" in
+
+    sun4u | sun4v | sparc | sparc64 )
+        # "-mcpu=v9" enables the "casa" assembler instruction
+        CFLAGS="$CFLAGS -mcpu=v9"
+    ;;
+
+esac
+
+
+# optimizations
+
+#NGX_GCC_OPT="-O2"
+#NGX_GCC_OPT="-Os"
+NGX_GCC_OPT="-O"
+
+#CFLAGS="$CFLAGS -fomit-frame-pointer"
+
+case $CPU in
+    pentium)
+        # optimize for Pentium and Athlon
+        CPU_OPT="-march=pentium"
+        NGX_CPU_CACHE_LINE=32
+    ;;
+
+    pentiumpro | pentium3)
+        # optimize for Pentium Pro, Pentium II and Pentium III
+        CPU_OPT="-march=pentiumpro"
+        NGX_CPU_CACHE_LINE=32
+    ;;
+
+    pentium4)
+        # optimize for Pentium 4, gcc 3.x
+        CPU_OPT="-march=pentium4"
+        NGX_CPU_CACHE_LINE=128
+    ;;
+
+    athlon)
+        # optimize for Athlon, gcc 3.x
+        CPU_OPT="-march=athlon"
+        NGX_CPU_CACHE_LINE=64
+    ;;
+
+    opteron)
+        # optimize for Opteron, gcc 3.x
+        CPU_OPT="-march=opteron"
+        NGX_CPU_CACHE_LINE=64
+    ;;
+
+    sparc32)
+        # build 32-bit UltraSparc binary
+        CPU_OPT="-m32"
+        CORE_LINK="$CORE_LINK -m32"
+        NGX_CPU_CACHE_LINE=64
+    ;;
+
+    sparc64)
+        # build 64-bit UltraSparc binary
+        CPU_OPT="-m64"
+        CORE_LINK="$CORE_LINK -m64"
+        NGX_CPU_CACHE_LINE=64
+    ;;
+
+    ppc64)
+        # build 64-bit PowerPC binary
+        CPU_OPT="-m64"
+        CPU_OPT="$CPU_OPT -falign-functions=32 -falign-labels=32"
+        CPU_OPT="$CPU_OPT -falign-loops=32 -falign-jumps=32"
+        CORE_LINK="$CORE_LINK -m64"
+        NGX_CPU_CACHE_LINE=128
+    ;;
+
+esac
+
+CC_AUX_FLAGS="$CC_AUX_FLAGS $CPU_OPT"
+
+case "$NGX_GCC_VER" in
+    2.7*)
+        # batch build
+        CPU_OPT=
+    ;;
+esac
+
+
+CFLAGS="$CFLAGS $PIPE $CPU_OPT"
+
+if [ ".$PCRE_OPT" = "." ]; then
+    PCRE_OPT="-O2 -fomit-frame-pointer $PIPE $CPU_OPT"
+else
+    PCRE_OPT="$PCRE_OPT $PIPE"
+fi
+
+if [ ".$ZLIB_OPT" = "." ]; then
+    ZLIB_OPT="-O2 -fomit-frame-pointer $PIPE $CPU_OPT"
+else
+    ZLIB_OPT="$ZLIB_OPT $PIPE"
+fi
+
+
+# warnings
+
+# -W requires at least -O
+CFLAGS="$CFLAGS ${NGX_GCC_OPT:--O} -W"
+
+CFLAGS="$CFLAGS -Wall -Wpointer-arith"
+#CFLAGS="$CFLAGS -Wconversion"
+#CFLAGS="$CFLAGS -Winline"
+#CFLAGS="$CFLAGS -Wmissing-prototypes"
+
+case "$NGX_GCC_VER" in
+    2.*)
+        # we have a lot of the unused function arguments
+        CFLAGS="$CFLAGS -Wno-unused"
+    ;;
+
+    *)
+        # we have a lot of the unused function arguments
+        CFLAGS="$CFLAGS -Wno-unused-parameter"
+        # 4.2.1 shows the warning in wrong places
+        #CFLAGS="$CFLAGS -Wunreachable-code"
+
+        # deprecated system OpenSSL library on OS X
+        if [ "$NGX_SYSTEM" = "Darwin" ]; then
+            CFLAGS="$CFLAGS -Wno-deprecated-declarations"
+        fi
+    ;;
+esac
+
+
+# stop on warning
+CFLAGS="$CFLAGS -Werror"
+
+# debug
+CFLAGS="$CFLAGS -g"
+
+# DragonFly's gcc3 generates DWARF
+#CFLAGS="$CFLAGS -g -gstabs"
+
+if [ ".$CPP" = "." ]; then
+    CPP="$CC -E"
+fi
diff --git a/nginx/auto/cc/icc b/nginx/auto/cc/icc
new file mode 100644 (file)
index 0000000..c47f6e4
--- /dev/null
@@ -0,0 +1,117 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+# Intel C++ compiler 7.1, 8.0, 8.1, 9.0, 11.1
+
+NGX_ICC_VER=`$CC -V 2>&1 | grep 'Version' 2>&1 \
+                         | sed -e 's/^.* Version \([^ ]*\) *Build.*$/\1/'`
+
+echo " + icc version: $NGX_ICC_VER"
+
+have=NGX_COMPILER value="\"Intel C Compiler $NGX_ICC_VER\"" . auto/define
+
+
+# optimizations
+
+CFLAGS="$CFLAGS -O"
+
+CORE_LINK="$CORE_LINK -opt_report_file=$NGX_OBJS/opt_report_file"
+
+
+case $CPU in
+    pentium)
+        # optimize for Pentium and Athlon
+        CPU_OPT="-march=pentium"
+    ;;
+
+    pentiumpro)
+        # optimize for Pentium Pro, Pentium II and Pentium III
+        CPU_OPT="-mcpu=pentiumpro -march=pentiumpro"
+    ;;
+
+    pentium4)
+        # optimize for Pentium 4, default
+        CPU_OPT="-march=pentium4"
+    ;;
+esac
+
+CFLAGS="$CFLAGS $CPU_OPT"
+
+if [ ".$PCRE_OPT" = "." ]; then
+    PCRE_OPT="-O $CPU_OPT"
+fi
+
+if [ ".$ZLIB_OPT" = "." ]; then
+    ZLIB_OPT="-O $CPU_OPT"
+fi
+
+
+# warnings
+
+CFLAGS="$CFLAGS -w2"
+
+# disable some warnings
+
+# invalid type conversion: "int" to "char *"
+CFLAGS="$CFLAGS -wd171"
+# argument is incompatible with corresponding format string conversion
+CFLAGS="$CFLAGS -wd181"
+# zero used for undefined preprocessing identifier
+CFLAGS="$CFLAGS -wd193"
+# the format string ends before this argument
+CFLAGS="$CFLAGS -wd268"
+# invalid format string conversion
+CFLAGS="$CFLAGS -wd269"
+# conversion from "long long" to "size_t" may lose significant bits
+CFLAGS="$CFLAGS -wd810"
+# parameter was never referenced
+CFLAGS="$CFLAGS -wd869"
+# attribute "unused" is only allowed in a function definition, warning on pTHX_
+CFLAGS="$CFLAGS -wd1301"
+
+# STUB
+# enumerated type mixed with another type
+CFLAGS="$CFLAGS -wd188"
+# controlling expression is constant
+CFLAGS="$CFLAGS -wd279"
+# operands are evaluated in unspecified order
+CFLAGS="$CFLAGS -wd981"
+# external definition with no prior declaration
+CFLAGS="$CFLAGS -wd1418"
+# external declaration in primary source file
+CFLAGS="$CFLAGS -wd1419"
+
+case "$NGX_ICC_VER" in
+    9.*)
+        # "cc" clobber ignored, warnings for Linux's htonl()/htons()
+        CFLAGS="$CFLAGS -wd1469"
+        # explicit conversion of a 64-bit integral type to a smaller
+        # integral type
+        CFLAGS="$CFLAGS -wd1683"
+        # conversion from pointer to same-sized integral type,
+        # warning on offsetof()
+        CFLAGS="$CFLAGS -wd1684"
+        # floating-point equality and inequality comparisons are unreliable,
+        # warning on SvTRUE()
+        CFLAGS="$CFLAGS -wd1572"
+    ;;
+
+    8.*)
+        # "cc" clobber ignored, warnings for Linux's htonl()/htons()
+        CFLAGS="$CFLAGS -wd1469"
+        # floating-point equality and inequality comparisons are unreliable,
+        # warning on SvTRUE()
+        CFLAGS="$CFLAGS -wd1572"
+    ;;
+
+    *)
+    ;;
+esac
+
+# stop on warning
+CFLAGS="$CFLAGS -Werror"
+
+# debug
+CFLAGS="$CFLAGS -g"
diff --git a/nginx/auto/cc/msvc b/nginx/auto/cc/msvc
new file mode 100644 (file)
index 0000000..8257252
--- /dev/null
@@ -0,0 +1,156 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+# MSVC 6.0 SP2                            cl 12.00
+# MSVC Toolkit 2003 (7.1)                 cl 13.10
+# MSVC 2005 Express Edition SP1 (8.0)     cl 14.00
+# MSVC 2008 Express Edition (9.0)         cl 15.00
+# MSVC 2010 (10.0)                        cl 16.00
+# MSVC 2015 (14.0)                        cl 19.00
+
+
+NGX_MSVC_VER=`$NGX_WINE $CC 2>&1 | grep 'Compiler Version' 2>&1 \
+                                 | sed -e 's/^.* Version \(.*\)/\1/'`
+
+echo " + cl version: $NGX_MSVC_VER"
+
+have=NGX_COMPILER value="\"cl $NGX_MSVC_VER\"" . auto/define
+
+
+ngx_msvc_ver=`echo $NGX_MSVC_VER | sed -e 's/^\([0-9]*\).*/\1/'`
+
+
+# optimizations
+
+# maximize speed, equivalent to -Og -Oi -Ot -Oy -Ob2 -Gs -GF -Gy
+CFLAGS="$CFLAGS -O2"
+
+# enable global optimization
+#CFLAGS="$CFLAGS -Og"
+# enable intrinsic functions
+#CFLAGS="$CFLAGS -Oi"
+
+# disable inline expansion
+#CFLAGS="$CFLAGS -Ob0"
+# explicit inline expansion
+#CFLAGS="$CFLAGS -Ob1"
+# explicit and implicit inline expansion
+#CFLAGS="$CFLAGS -Ob2"
+
+# enable frame pointer omission
+#CFLAGS="$CFLAGS -Oy"
+# disable stack checking calls
+#CFLAGS="$CFLAGS -Gs"
+
+# pools strings as read/write
+#CFLAGS="$CFLAGS -Gf"
+# pools strings as read-only
+#CFLAGS="$CFLAGS -GF"
+
+
+case $CPU in
+    pentium)
+        # optimize for Pentium and Athlon
+        CPU_OPT="-G5"
+    ;;
+
+    pentiumpro)
+        # optimize for Pentium Pro, Pentium II and Pentium III
+        CPU_OPT="-G6"
+    ;;
+
+    pentium4)
+        # optimize for Pentium 4, MSVC 7
+        CPU_OPT="-G7"
+    ;;
+esac
+
+# __cdecl, default, must be used with OpenSSL, md5 asm, and sha1 asm
+#CPU_OPT="$CPU_OPT -Gd"
+# __stdcall
+#CPU_OPT="$CPU_OPT -Gz"
+# __fastcall
+#CPU_OPT="$CPU_OPT -Gr"
+
+
+CFLAGS="$CFLAGS $CPU_OPT"
+
+
+# warnings
+
+CFLAGS="$CFLAGS -W4"
+
+# stop on warning
+CFLAGS="$CFLAGS -WX"
+
+# disable logo
+CFLAGS="$CFLAGS -nologo"
+
+# the link flags
+CORE_LINK="$CORE_LINK -link -verbose:lib"
+
+# link with libcmt.lib, multithreaded
+LIBC="-MT"
+# link with msvcrt.dll
+# however, MSVC Toolkit 2003 has no MSVCRT.LIB
+#LIBC="-MD"
+
+CFLAGS="$CFLAGS $LIBC"
+
+CORE_LIBS="$CORE_LIBS kernel32.lib user32.lib"
+
+# Win32 GUI mode application
+#CORE_LINK="$CORE_LINK -subsystem:windows -entry:mainCRTStartup"
+
+# debug
+# msvc under Wine issues
+# C1902: Program database manager mismatch; please check your installation
+if [ -z "$NGX_WINE" ]; then
+   CFLAGS="$CFLAGS -Zi"
+   CORE_LINK="$CORE_LINK -debug"
+fi
+
+
+# MSVC 2005 supports C99 variadic macros
+if [ "$ngx_msvc_ver" -ge 14 ]; then
+    have=NGX_HAVE_C99_VARIADIC_MACROS . auto/have
+fi
+
+
+# precompiled headers
+CORE_DEPS="$CORE_DEPS $NGX_OBJS/ngx_config.pch"
+CORE_LINK="$CORE_LINK $NGX_OBJS/ngx_pch.obj"
+NGX_PCH="$NGX_OBJS/ngx_config.pch"
+NGX_BUILD_PCH="-Ycngx_config.h -Fp$NGX_OBJS/ngx_config.pch"
+NGX_USE_PCH="-Yungx_config.h -Fp$NGX_OBJS/ngx_config.pch"
+
+
+# the resource file
+NGX_RES="$NGX_OBJS/nginx.res"
+NGX_RCC="rc -fo$NGX_RES \$(CORE_INCS) $NGX_WIN32_RC"
+CORE_LINK="$NGX_RES $CORE_LINK"
+
+
+# dynamic modules
+#MAIN_LINK="-link -def:$NGX_OBJS/nginx.def"
+#MODULE_LINK="-LD $NGX_OBJS/nginx.lib"
+
+
+ngx_pic_opt=
+ngx_objout="-Fo"
+ngx_binout="-Fe"
+ngx_objext="obj"
+
+ngx_long_start='@<<
+       '
+ngx_long_end='<<'
+ngx_long_regex_cont=' \
+       '
+ngx_long_cont='
+       '
+
+# MSVC understand / in path
+#ngx_regex_dirsep='\\'
+#ngx_dirsep="\\"
diff --git a/nginx/auto/cc/name b/nginx/auto/cc/name
new file mode 100644 (file)
index 0000000..ded93f5
--- /dev/null
@@ -0,0 +1,70 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+if [ "$NGX_PLATFORM" != win32 ]; then
+
+    ngx_feature="C compiler"
+    ngx_feature_name=
+    ngx_feature_run=yes
+    ngx_feature_incs=
+    ngx_feature_path=
+    ngx_feature_libs=
+    ngx_feature_test=
+    . auto/feature
+
+    if [ $ngx_found = no ]; then
+        echo
+        echo $0: error: C compiler $CC is not found
+        echo
+        exit 1
+    fi
+
+fi
+
+
+if [ "$CC" = cl ]; then
+    NGX_CC_NAME=msvc
+    echo " + using Microsoft Visual C++ compiler"
+
+elif [ "$CC" = wcl386 ]; then
+    NGX_CC_NAME=owc
+    echo " + using Open Watcom C compiler"
+
+elif [ "$CC" = bcc32 ]; then
+    NGX_CC_NAME=bcc
+    echo " + using Borland C++ compiler"
+
+elif `$CC -V 2>&1 | grep '^Intel(R) C' >/dev/null 2>&1`; then
+    NGX_CC_NAME=icc
+    echo " + using Intel C++ compiler"
+
+elif `$CC -v 2>&1 | grep 'gcc version' >/dev/null 2>&1`; then
+    NGX_CC_NAME=gcc
+    echo " + using GNU C compiler"
+
+elif `$CC -v 2>&1 | grep 'clang version' >/dev/null 2>&1`; then
+    NGX_CC_NAME=clang
+    echo " + using Clang C compiler"
+
+elif `$CC -v 2>&1 | grep 'LLVM version' >/dev/null 2>&1`; then
+    NGX_CC_NAME=clang
+    echo " + using Clang C compiler"
+
+elif `$CC -V 2>&1 | grep 'Sun C' >/dev/null 2>&1`; then
+    NGX_CC_NAME=sunc
+    echo " + using Sun C compiler"
+
+elif `$CC -V 2>&1 | grep '^Compaq C' >/dev/null 2>&1`; then
+    NGX_CC_NAME=ccc
+    echo " + using Compaq C compiler"
+
+elif `$CC -V 2>&1 | grep '^aCC: ' >/dev/null 2>&1`; then
+    NGX_CC_NAME=acc
+    echo " + using HP aC++ compiler"
+
+else
+    NGX_CC_NAME=unknown
+
+fi
diff --git a/nginx/auto/cc/owc b/nginx/auto/cc/owc
new file mode 100644 (file)
index 0000000..f7fd88c
--- /dev/null
@@ -0,0 +1,103 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+# Open Watcom C 1.0, 1.2, 1.3
+
+# optimizations
+
+# maximize speed
+CFLAGS="$CFLAGS -ot"
+# reorder instructions for best pipeline usage
+CFLAGS="$CFLAGS -op"
+# inline intrinsic functions
+CFLAGS="$CFLAGS -oi"
+# inline expansion
+CFLAGS="$CFLAGS -oe"
+# disable stack checking calls
+CFLAGS="$CFLAGS -s"
+
+case $CPU in
+    pentium)
+        # optimize for Pentium and Athlon
+        # register-based arguments passing conventions
+        CPU_OPT="-5r"
+        # stack-based arguments passing conventions
+        #CPU_OPT="-5s"
+    ;;
+
+    pentiumpro)
+        # optimize for Pentium Pro, Pentium II and Pentium III
+        # register-based arguments passing conventions
+        CPU_OPT="-6r"
+        # stack-based arguments passing conventions
+        #CPU_OPT="-6s"
+    ;;
+esac
+
+CFLAGS="$CFLAGS $CPU_OPT"
+
+
+# warnings
+
+# maximum level
+CFLAGS="$CFLAGS -wx"
+#CFLAGS="$CFLAGS -w3"
+
+# stop on warning
+CFLAGS="$CFLAGS -we"
+
+# built target is NT
+CFLAGS="$CFLAGS -bt=nt"
+
+# multithreaded
+CFLAGS="$CFLAGS -bm"
+
+# debug
+CFLAGS="$CFLAGS -d2"
+
+# quiet
+CFLAGS="$CFLAGS -zq"
+
+# Open Watcom C 1.2
+have=NGX_HAVE_C99_VARIADIC_MACROS . auto/have
+
+
+# the precompiled headers
+#CORE_DEPS="$CORE_DEPS $NGX_OBJS/ngx_config.pch"
+#NGX_PCH="$NGX_OBJS/ngx_config.pch"
+#NGX_BUILD_PCH="-fhq=$NGX_OBJS/ngx_config.pch"
+#NGX_USE_PCH="-fh=$NGX_OBJS/ngx_config.pch"
+
+
+# the link flags, built target is NT GUI mode application
+#CORE_LINK="$CORE_LINK -l=nt_win"
+
+
+# the resource file
+NGX_RCC="wrc \$(CORE_INCS) -fo=$NGX_OBJS/nginx.res "
+NGX_RCC="$NGX_RCC $NGX_WIN32_RC $NGX_OBJS/nginx.exe"
+
+
+ngx_include_opt="-i="
+ngx_objout="-fo"
+ngx_binout="-fe="
+ngx_objext="obj"
+
+ngx_regex_dirsep='\\'
+ngx_dirsep="\\"
+
+ngx_long_start=' '
+ngx_long_end=' '
+ngx_long_regex_cont=' \&\
+       '
+ngx_long_cont=' &
+       '
+
+ngx_regex_cont=' \&\
+       '
+ngx_cont=' &
+       '
+ngx_tab=' &
+               '
diff --git a/nginx/auto/cc/sunc b/nginx/auto/cc/sunc
new file mode 100644 (file)
index 0000000..552c2d3
--- /dev/null
@@ -0,0 +1,163 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+# Sun C 5.7 Patch 117837-04 2005/05/11    Sun Studio 10
+# Sun C 5.8 2005/10/13                    Sun Studio 11
+# Sun C 5.9 SunOS_i386 2007/05/03         Sun Studio 12
+# Sun C 5.9 SunOS_sparc 2007/05/03
+# Sun C 5.10 SunOS_i386 2009/06/03        Sun Studio 12.1
+# Sun C 5.11 SunOS_i386 2010/08/13        Oracle Solaris Studio 12.2
+# Sun C 5.12 SunOS_i386 2011/11/16        Oracle Solaris Studio 12.3
+# Sun C 5.13 SunOS_i386 2014/10/20        Oracle Solaris Studio 12.4
+# Sun C 5.14 SunOS_i386 2016/05/31        Oracle Developer Studio 12.5
+
+NGX_SUNC_VER=`$CC -V 2>&1 | grep 'Sun C' 2>&1 \
+                          | sed -e 's/^.* Sun C \(.*\)/\1/'`
+
+echo " + Sun C version: $NGX_SUNC_VER"
+
+have=NGX_COMPILER value="\"Sun C $NGX_SUNC_VER\"" . auto/define
+
+
+cat << END > $NGX_AUTOTEST.c
+
+int main(void) {
+    printf("%d", __SUNPRO_C);
+    return 0;
+}
+
+END
+
+eval "$CC -o $NGX_AUTOTEST $NGX_AUTOTEST.c >> $NGX_ERR 2>&1"
+
+if [ -x $NGX_AUTOTEST ]; then
+    ngx_sunc_ver=`$NGX_AUTOTEST`
+fi
+
+rm -rf $NGX_AUTOTEST*
+
+# 1424 == 0x590, Sun Studio 12
+
+if [ "$ngx_sunc_ver" -ge 1424 ]; then
+    ngx_sparc32="-m32"
+    ngx_sparc64="-m64"
+    ngx_amd64="-m64"
+
+else
+    ngx_sparc32="-xarch=v8plus"
+    ngx_sparc64="-xarch=v9"
+    ngx_amd64="-xarch=amd64"
+fi
+
+case "$NGX_MACHINE" in
+
+    i86pc)
+        NGX_AUX=" src/os/unix/ngx_sunpro_x86.il"
+    ;;
+
+    sun4u | sun4v)
+        NGX_AUX=" src/os/unix/ngx_sunpro_sparc64.il"
+    ;;
+
+esac
+
+MAIN_LINK=
+MODULE_LINK="-G"
+
+
+# optimizations
+
+# 20736 == 0x5100, Sun Studio 12.1
+
+if [ "$ngx_sunc_ver" -ge 20736 ]; then
+    ngx_fast="-fast"
+
+else
+    # older versions had problems with bit-fields
+    ngx_fast="-fast -xalias_level=any"
+fi
+
+IPO=-xipo
+CFLAGS="$CFLAGS $ngx_fast $IPO"
+CORE_LINK="$CORE_LINK $ngx_fast $IPO"
+
+
+case $CPU in
+    pentium)
+        # optimize for Pentium and Athlon
+        CPU_OPT="-xchip=pentium"
+    ;;
+
+    pentiumpro)
+        # optimize for Pentium Pro, Pentium II
+        CPU_OPT="-xchip=pentium_pro"
+    ;;
+
+    pentium3)
+        # optimize for Pentium III
+        CPU_OPT="-xchip=pentium3"
+        #CPU_OPT="$CPU_OPT -xarch=sse"
+        CPU_OPT="$CPU_OPT -xcache=16/32/4:256/32/4"
+    ;;
+
+    pentium4)
+        # optimize for Pentium 4
+        CPU_OPT="-xchip=pentium4"
+        #CPU_OPT="$CPU_OPT -xarch=sse2"
+        CPU_OPT="$CPU_OPT -xcache=8/64/4:256/128/8"
+    ;;
+
+    opteron)
+        # optimize for Opteron
+        CPU_OPT="-xchip=opteron"
+        #CPU_OPT="$CPU_OPT -xarch=sse2"
+        CPU_OPT="$CPU_OPT -xcache=64/64/2:1024/64/16"
+    ;;
+
+    sparc32)
+        # build 32-bit UltraSparc binary
+        CPU_OPT="$ngx_sparc32"
+        CORE_LINK="$CORE_LINK $ngx_sparc32"
+        CC_AUX_FLAGS="$CC_AUX_FLAGS $ngx_sparc32"
+        NGX_CPU_CACHE_LINE=64
+    ;;
+
+    sparc64)
+        # build 64-bit UltraSparc binary
+        CPU_OPT="$ngx_sparc64"
+        CORE_LINK="$CORE_LINK $ngx_sparc64"
+        CC_AUX_FLAGS="$CC_AUX_FLAGS $ngx_sparc64"
+        NGX_CPU_CACHE_LINE=64
+    ;;
+
+    amd64)
+        # build 64-bit amd64 binary
+        CPU_OPT="$ngx_amd64"
+        CORE_LINK="$CORE_LINK $ngx_amd64"
+        CC_AUX_FLAGS="$CC_AUX_FLAGS $ngx_amd64"
+        NGX_AUX=" src/os/unix/ngx_sunpro_amd64.il"
+        NGX_CPU_CACHE_LINE=64
+    ;;
+
+esac
+
+
+CFLAGS="$CFLAGS $CPU_OPT"
+
+
+if [ ".$PCRE_OPT" = "." ]; then
+    PCRE_OPT="$ngx_fast $IPO $CPU_OPT"
+fi
+
+if [ ".$ZLIB_OPT" = "." ]; then
+    ZLIB_OPT="$ngx_fast $IPO $CPU_OPT"
+fi
+
+
+# stop on warning
+CFLAGS="$CFLAGS -errwarn=%all"
+
+# debug
+CFLAGS="$CFLAGS -g"
diff --git a/nginx/auto/define b/nginx/auto/define
new file mode 100644 (file)
index 0000000..b5a7622
--- /dev/null
@@ -0,0 +1,12 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+cat << END >> $NGX_AUTO_CONFIG_H
+
+#ifndef $have
+#define $have  $value
+#endif
+
+END
diff --git a/nginx/auto/endianness b/nginx/auto/endianness
new file mode 100644 (file)
index 0000000..1b552b6
--- /dev/null
@@ -0,0 +1,50 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+echo $ngx_n "checking for system byte ordering ...$ngx_c"
+
+cat << END >> $NGX_AUTOCONF_ERR
+
+----------------------------------------
+checking for system byte ordering
+
+END
+
+
+cat << END > $NGX_AUTOTEST.c
+
+int main(void) {
+    int i = 0x11223344;
+    char *p;
+
+    p = (char *) &i;
+    if (*p == 0x44) return 0;
+    return 1;
+}
+
+END
+
+ngx_test="$CC $CC_TEST_FLAGS $CC_AUX_FLAGS \
+          -o $NGX_AUTOTEST $NGX_AUTOTEST.c $NGX_LD_OPT $ngx_feature_libs"
+
+eval "$ngx_test >> $NGX_AUTOCONF_ERR 2>&1"
+
+if [ -x $NGX_AUTOTEST ]; then
+    if $NGX_AUTOTEST >/dev/null 2>&1; then
+        echo " little endian"
+        have=NGX_HAVE_LITTLE_ENDIAN . auto/have
+    else
+        echo " big endian"
+    fi
+
+    rm -rf $NGX_AUTOTEST*
+
+else
+    rm -rf $NGX_AUTOTEST*
+
+    echo
+    echo "$0: error: cannot detect system byte ordering"
+    exit 1
+fi
diff --git a/nginx/auto/feature b/nginx/auto/feature
new file mode 100644 (file)
index 0000000..3561f59
--- /dev/null
@@ -0,0 +1,123 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+echo $ngx_n "checking for $ngx_feature ...$ngx_c"
+
+cat << END >> $NGX_AUTOCONF_ERR
+
+----------------------------------------
+checking for $ngx_feature
+
+END
+
+ngx_found=no
+
+if test -n "$ngx_feature_name"; then
+    ngx_have_feature=`echo $ngx_feature_name \
+                   | tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ`
+fi
+
+if test -n "$ngx_feature_path"; then
+    for ngx_temp in $ngx_feature_path; do
+        ngx_feature_inc_path="$ngx_feature_inc_path -I $ngx_temp"
+    done
+fi
+
+cat << END > $NGX_AUTOTEST.c
+
+#include <sys/types.h>
+$NGX_INCLUDE_UNISTD_H
+$ngx_feature_incs
+
+int main(void) {
+    $ngx_feature_test;
+    return 0;
+}
+
+END
+
+
+ngx_test="$CC $CC_TEST_FLAGS $CC_AUX_FLAGS $ngx_feature_inc_path \
+          -o $NGX_AUTOTEST $NGX_AUTOTEST.c $NGX_TEST_LD_OPT $ngx_feature_libs"
+
+ngx_feature_inc_path=
+
+eval "/bin/sh -c \"$ngx_test\" >> $NGX_AUTOCONF_ERR 2>&1"
+
+
+if [ -x $NGX_AUTOTEST ]; then
+
+    case "$ngx_feature_run" in
+
+        yes)
+            # /bin/sh is used to intercept "Killed" or "Abort trap" messages
+            if /bin/sh -c $NGX_AUTOTEST >> $NGX_AUTOCONF_ERR 2>&1; then
+                echo " found"
+                ngx_found=yes
+
+                if test -n "$ngx_feature_name"; then
+                    have=$ngx_have_feature . auto/have
+                fi
+
+            else
+                echo " found but is not working"
+            fi
+        ;;
+
+        value)
+            # /bin/sh is used to intercept "Killed" or "Abort trap" messages
+            if /bin/sh -c $NGX_AUTOTEST >> $NGX_AUTOCONF_ERR 2>&1; then
+                echo " found"
+                ngx_found=yes
+
+                cat << END >> $NGX_AUTO_CONFIG_H
+
+#ifndef $ngx_feature_name
+#define $ngx_feature_name  `$NGX_AUTOTEST`
+#endif
+
+END
+            else
+                echo " found but is not working"
+            fi
+        ;;
+
+        bug)
+            # /bin/sh is used to intercept "Killed" or "Abort trap" messages
+            if /bin/sh -c $NGX_AUTOTEST >> $NGX_AUTOCONF_ERR 2>&1; then
+                echo " not found"
+
+            else
+                echo " found"
+                ngx_found=yes
+
+                if test -n "$ngx_feature_name"; then
+                    have=$ngx_have_feature . auto/have
+                fi
+            fi
+        ;;
+
+        *)
+            echo " found"
+            ngx_found=yes
+
+            if test -n "$ngx_feature_name"; then
+                have=$ngx_have_feature . auto/have
+            fi
+        ;;
+
+    esac
+
+else
+    echo " not found"
+
+    echo "----------"    >> $NGX_AUTOCONF_ERR
+    cat $NGX_AUTOTEST.c  >> $NGX_AUTOCONF_ERR
+    echo "----------"    >> $NGX_AUTOCONF_ERR
+    echo $ngx_test       >> $NGX_AUTOCONF_ERR
+    echo "----------"    >> $NGX_AUTOCONF_ERR
+fi
+
+rm -rf $NGX_AUTOTEST*
diff --git a/nginx/auto/have b/nginx/auto/have
new file mode 100644 (file)
index 0000000..f8e3751
--- /dev/null
@@ -0,0 +1,12 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+cat << END >> $NGX_AUTO_CONFIG_H
+
+#ifndef $have
+#define $have  1
+#endif
+
+END
diff --git a/nginx/auto/have_headers b/nginx/auto/have_headers
new file mode 100644 (file)
index 0000000..a3a7543
--- /dev/null
@@ -0,0 +1,12 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+cat << END >> $NGX_AUTO_HEADERS_H
+
+#ifndef $have
+#define $have  1
+#endif
+
+END
diff --git a/nginx/auto/headers b/nginx/auto/headers
new file mode 100644 (file)
index 0000000..5a2e6b9
--- /dev/null
@@ -0,0 +1,13 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+ngx_include="unistd.h";      . auto/include
+ngx_include="inttypes.h";    . auto/include
+ngx_include="limits.h";      . auto/include
+ngx_include="sys/filio.h";   . auto/include
+ngx_include="sys/param.h";   . auto/include
+ngx_include="sys/mount.h";   . auto/include
+ngx_include="sys/statvfs.h"; . auto/include
+ngx_include="crypt.h";       . auto/include
diff --git a/nginx/auto/include b/nginx/auto/include
new file mode 100644 (file)
index 0000000..c1bd364
--- /dev/null
@@ -0,0 +1,58 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+echo $ngx_n "checking for $ngx_include ...$ngx_c"
+
+cat << END >> $NGX_AUTOCONF_ERR
+
+----------------------------------------
+checking for $ngx_include
+
+END
+
+
+ngx_found=no
+
+cat << END > $NGX_AUTOTEST.c
+
+$NGX_INCLUDE_SYS_PARAM_H
+#include <$ngx_include>
+
+int main(void) {
+    return 0;
+}
+
+END
+
+
+ngx_test="$CC -o $NGX_AUTOTEST $NGX_AUTOTEST.c"
+
+eval "$ngx_test >> $NGX_AUTOCONF_ERR 2>&1"
+
+if [ -x $NGX_AUTOTEST ]; then
+
+    ngx_found=yes
+
+    echo " found"
+
+    ngx_name=`echo $ngx_include \
+              | tr abcdefghijklmnopqrstuvwxyz/. ABCDEFGHIJKLMNOPQRSTUVWXYZ__`
+
+
+    have=NGX_HAVE_$ngx_name . auto/have_headers
+
+    eval "NGX_INCLUDE_$ngx_name='#include <$ngx_include>'"
+
+else
+    echo " not found"
+
+    echo "----------"    >> $NGX_AUTOCONF_ERR
+    cat $NGX_AUTOTEST.c  >> $NGX_AUTOCONF_ERR
+    echo "----------"    >> $NGX_AUTOCONF_ERR
+    echo $ngx_test       >> $NGX_AUTOCONF_ERR
+    echo "----------"    >> $NGX_AUTOCONF_ERR
+fi
+
+rm -rf $NGX_AUTOTEST*
diff --git a/nginx/auto/init b/nginx/auto/init
new file mode 100644 (file)
index 0000000..910f529
--- /dev/null
@@ -0,0 +1,51 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+NGX_MAKEFILE=$NGX_OBJS/Makefile
+NGX_MODULES_C=$NGX_OBJS/ngx_modules.c
+
+NGX_AUTO_HEADERS_H=$NGX_OBJS/ngx_auto_headers.h
+NGX_AUTO_CONFIG_H=$NGX_OBJS/ngx_auto_config.h
+
+NGX_AUTOTEST=$NGX_OBJS/autotest
+NGX_AUTOCONF_ERR=$NGX_OBJS/autoconf.err
+
+# STUBs
+NGX_ERR=$NGX_OBJS/autoconf.err
+MAKEFILE=$NGX_OBJS/Makefile
+
+
+NGX_PCH=
+NGX_USE_PCH=
+
+
+# check the echo's "-n" option and "\c" capability
+
+if echo "test\c" | grep c >/dev/null; then
+
+    if echo -n test | grep n >/dev/null; then
+        ngx_n=
+        ngx_c=
+
+    else
+        ngx_n=-n
+        ngx_c=
+    fi
+
+else
+    ngx_n=
+    ngx_c='\c'
+fi
+
+
+# create Makefile
+
+cat << END > Makefile
+
+default:       build
+
+clean:
+       rm -rf Makefile $NGX_OBJS
+END
diff --git a/nginx/auto/install b/nginx/auto/install
new file mode 100644 (file)
index 0000000..d884487
--- /dev/null
@@ -0,0 +1,218 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+if [ $USE_PERL != NO ]; then
+
+    cat << END                                                >> $NGX_MAKEFILE
+
+install_perl_modules:
+       cd $NGX_OBJS/src/http/modules/perl && \$(MAKE) install
+END
+
+    NGX_INSTALL_PERL_MODULES=install_perl_modules
+
+fi
+
+
+case ".$NGX_SBIN_PATH" in
+    ./*)
+    ;;
+
+    *)
+        NGX_SBIN_PATH=$NGX_PREFIX/$NGX_SBIN_PATH
+    ;;
+esac
+
+
+case ".$NGX_MODULES_PATH" in
+    ./*)
+    ;;
+
+    *)
+        NGX_MODULES_PATH=$NGX_PREFIX/$NGX_MODULES_PATH
+    ;;
+esac
+
+NGX_MODULES_PATH=`dirname $NGX_MODULES_PATH/.`
+
+
+case ".$NGX_CONF_PATH" in
+    ./*)
+    ;;
+
+    *)
+        NGX_CONF_PATH=$NGX_PREFIX/$NGX_CONF_PATH
+    ;;
+esac
+
+
+NGX_CONF_PREFIX=`dirname $NGX_CONF_PATH`
+
+
+case ".$NGX_PID_PATH" in
+    ./*)
+    ;;
+
+    *)
+        NGX_PID_PATH=$NGX_PREFIX/$NGX_PID_PATH
+    ;;
+esac
+
+
+case ".$NGX_ERROR_LOG_PATH" in
+    ./* | .)
+    ;;
+
+    *)
+        NGX_ERROR_LOG_PATH=$NGX_PREFIX/$NGX_ERROR_LOG_PATH
+    ;;
+esac
+
+
+case ".$NGX_HTTP_LOG_PATH" in
+    ./*)
+    ;;
+
+    *)
+        NGX_HTTP_LOG_PATH=$NGX_PREFIX/$NGX_HTTP_LOG_PATH
+    ;;
+esac
+
+
+if test -f man/nginx.8 ; then
+    NGX_MAN=man/nginx.8
+else
+    NGX_MAN=docs/man/nginx.8
+fi
+
+if test -d html ; then
+    NGX_HTML=html
+else
+    NGX_HTML=docs/html
+fi
+
+cat << END                                                    >> $NGX_MAKEFILE
+
+manpage:       $NGX_OBJS/nginx.8
+
+$NGX_OBJS/nginx.8:     $NGX_MAN $NGX_AUTO_CONFIG_H
+       sed -e "s|%%PREFIX%%|$NGX_PREFIX|" \\
+               -e "s|%%PID_PATH%%|$NGX_PID_PATH|" \\
+               -e "s|%%CONF_PATH%%|$NGX_CONF_PATH|" \\
+               -e "s|%%ERROR_LOG_PATH%%|${NGX_ERROR_LOG_PATH:-stderr}|" \\
+               < $NGX_MAN > \$@
+
+install:       build $NGX_INSTALL_PERL_MODULES
+       test -d '\$(DESTDIR)$NGX_PREFIX' || mkdir -p '\$(DESTDIR)$NGX_PREFIX'
+
+       test -d '\$(DESTDIR)`dirname "$NGX_SBIN_PATH"`' \\
+               || mkdir -p '\$(DESTDIR)`dirname "$NGX_SBIN_PATH"`'
+       test ! -f '\$(DESTDIR)$NGX_SBIN_PATH' \\
+               || mv '\$(DESTDIR)$NGX_SBIN_PATH' \\
+                       '\$(DESTDIR)$NGX_SBIN_PATH.old'
+       cp $NGX_OBJS/nginx '\$(DESTDIR)$NGX_SBIN_PATH'
+
+       test -d '\$(DESTDIR)$NGX_CONF_PREFIX' \\
+               || mkdir -p '\$(DESTDIR)$NGX_CONF_PREFIX'
+
+       cp conf/koi-win '\$(DESTDIR)$NGX_CONF_PREFIX'
+       cp conf/koi-utf '\$(DESTDIR)$NGX_CONF_PREFIX'
+       cp conf/win-utf '\$(DESTDIR)$NGX_CONF_PREFIX'
+
+       test -f '\$(DESTDIR)$NGX_CONF_PREFIX/mime.types' \\
+               || cp conf/mime.types '\$(DESTDIR)$NGX_CONF_PREFIX'
+       cp conf/mime.types '\$(DESTDIR)$NGX_CONF_PREFIX/mime.types.default'
+
+       test -f '\$(DESTDIR)$NGX_CONF_PREFIX/fastcgi_params' \\
+               || cp conf/fastcgi_params '\$(DESTDIR)$NGX_CONF_PREFIX'
+       cp conf/fastcgi_params \\
+               '\$(DESTDIR)$NGX_CONF_PREFIX/fastcgi_params.default'
+
+       test -f '\$(DESTDIR)$NGX_CONF_PREFIX/fastcgi.conf' \\
+               || cp conf/fastcgi.conf '\$(DESTDIR)$NGX_CONF_PREFIX'
+       cp conf/fastcgi.conf '\$(DESTDIR)$NGX_CONF_PREFIX/fastcgi.conf.default'
+
+       test -f '\$(DESTDIR)$NGX_CONF_PREFIX/uwsgi_params' \\
+               || cp conf/uwsgi_params '\$(DESTDIR)$NGX_CONF_PREFIX'
+       cp conf/uwsgi_params \\
+               '\$(DESTDIR)$NGX_CONF_PREFIX/uwsgi_params.default'
+
+       test -f '\$(DESTDIR)$NGX_CONF_PREFIX/scgi_params' \\
+               || cp conf/scgi_params '\$(DESTDIR)$NGX_CONF_PREFIX'
+       cp conf/scgi_params \\
+               '\$(DESTDIR)$NGX_CONF_PREFIX/scgi_params.default'
+
+       test -f '\$(DESTDIR)$NGX_CONF_PATH' \\
+               || cp conf/nginx.conf '\$(DESTDIR)$NGX_CONF_PATH'
+       cp conf/nginx.conf '\$(DESTDIR)$NGX_CONF_PREFIX/nginx.conf.default'
+
+       test -d '\$(DESTDIR)`dirname "$NGX_PID_PATH"`' \\
+               || mkdir -p '\$(DESTDIR)`dirname "$NGX_PID_PATH"`'
+
+       test -d '\$(DESTDIR)`dirname "$NGX_HTTP_LOG_PATH"`' \\
+               || mkdir -p '\$(DESTDIR)`dirname "$NGX_HTTP_LOG_PATH"`'
+
+       test -d '\$(DESTDIR)$NGX_PREFIX/html' \\
+               || cp -R $NGX_HTML '\$(DESTDIR)$NGX_PREFIX'
+END
+
+
+if test -n "$NGX_ERROR_LOG_PATH"; then
+    cat << END                                                >> $NGX_MAKEFILE
+
+       test -d '\$(DESTDIR)`dirname "$NGX_ERROR_LOG_PATH"`' \\
+               || mkdir -p '\$(DESTDIR)`dirname "$NGX_ERROR_LOG_PATH"`'
+END
+
+fi
+
+
+if test -n "$DYNAMIC_MODULES"; then
+    cat << END                                                >> $NGX_MAKEFILE
+
+       test -d '\$(DESTDIR)$NGX_MODULES_PATH' \\
+               || mkdir -p '\$(DESTDIR)$NGX_MODULES_PATH'
+END
+
+fi
+
+
+for ngx_module in $DYNAMIC_MODULES
+do
+    ngx_module=$ngx_module$ngx_modext
+
+    cat << END                                                >> $NGX_MAKEFILE
+
+       test ! -f '\$(DESTDIR)$NGX_MODULES_PATH/$ngx_module' \\
+               || mv '\$(DESTDIR)$NGX_MODULES_PATH/$ngx_module' \\
+                       '\$(DESTDIR)$NGX_MODULES_PATH/$ngx_module.old'
+       cp $NGX_OBJS/$ngx_module '\$(DESTDIR)$NGX_MODULES_PATH/$ngx_module'
+END
+
+done
+
+
+# create Makefile
+
+cat << END >> Makefile
+
+build:
+       \$(MAKE) -f $NGX_MAKEFILE
+
+install:
+       \$(MAKE) -f $NGX_MAKEFILE install
+
+modules:
+       \$(MAKE) -f $NGX_MAKEFILE modules
+
+upgrade:
+       $NGX_SBIN_PATH -t
+
+       kill -USR2 \`cat $NGX_PID_PATH\`
+       sleep 1
+       test -f $NGX_PID_PATH.oldbin
+
+       kill -QUIT \`cat $NGX_PID_PATH.oldbin\`
+END
diff --git a/nginx/auto/lib/conf b/nginx/auto/lib/conf
new file mode 100644 (file)
index 0000000..2c7af10
--- /dev/null
@@ -0,0 +1,54 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+if [ $USE_PCRE = YES -o $PCRE != NONE ]; then
+    . auto/lib/pcre/conf
+
+else
+    if [ $USE_PCRE = DISABLED -a $HTTP = YES -a $HTTP_REWRITE = YES ]; then
+
+cat << END
+
+$0: error: the HTTP rewrite module requires the PCRE library.
+You can either disable the module by using --without-http_rewrite_module
+option or you have to enable the PCRE support.
+
+END
+        exit 1
+    fi
+fi
+
+
+if [ $USE_OPENSSL = YES ]; then
+    . auto/lib/openssl/conf
+fi
+
+if [ $USE_ZLIB = YES ]; then
+    . auto/lib/zlib/conf
+fi
+
+if [ $USE_LIBXSLT != NO ]; then
+    . auto/lib/libxslt/conf
+fi
+
+if [ $USE_LIBGD != NO ]; then
+    . auto/lib/libgd/conf
+fi
+
+if [ $USE_PERL != NO ]; then
+    . auto/lib/perl/conf
+fi
+
+if [ $USE_GEOIP != NO ]; then
+    . auto/lib/geoip/conf
+fi
+
+if [ $NGX_GOOGLE_PERFTOOLS = YES ]; then
+    . auto/lib/google-perftools/conf
+fi
+
+if [ $NGX_LIBATOMIC != NO ]; then
+    . auto/lib/libatomic/conf
+fi
diff --git a/nginx/auto/lib/geoip/conf b/nginx/auto/lib/geoip/conf
new file mode 100644 (file)
index 0000000..8302aae
--- /dev/null
@@ -0,0 +1,97 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+    ngx_feature="GeoIP library"
+    ngx_feature_name=
+    ngx_feature_run=no
+    ngx_feature_incs="#include <GeoIP.h>"
+    ngx_feature_path=
+    ngx_feature_libs="-lGeoIP"
+    ngx_feature_test="GeoIP_open(NULL, 0)"
+    . auto/feature
+
+
+if [ $ngx_found = no ]; then
+
+    # FreeBSD port
+
+    ngx_feature="GeoIP library in /usr/local/"
+    ngx_feature_path="/usr/local/include"
+
+    if [ $NGX_RPATH = YES ]; then
+        ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lGeoIP"
+    else
+        ngx_feature_libs="-L/usr/local/lib -lGeoIP"
+    fi
+
+    . auto/feature
+fi
+
+
+if [ $ngx_found = no ]; then
+
+    # NetBSD port
+
+    ngx_feature="GeoIP library in /usr/pkg/"
+    ngx_feature_path="/usr/pkg/include"
+
+    if [ $NGX_RPATH = YES ]; then
+        ngx_feature_libs="-R/usr/pkg/lib -L/usr/pkg/lib -lGeoIP"
+    else
+        ngx_feature_libs="-L/usr/pkg/lib -lGeoIP"
+    fi
+
+    . auto/feature
+fi
+
+
+if [ $ngx_found = no ]; then
+
+    # MacPorts
+
+    ngx_feature="GeoIP library in /opt/local/"
+    ngx_feature_path="/opt/local/include"
+
+    if [ $NGX_RPATH = YES ]; then
+        ngx_feature_libs="-R/opt/local/lib -L/opt/local/lib -lGeoIP"
+    else
+        ngx_feature_libs="-L/opt/local/lib -lGeoIP"
+    fi
+
+    . auto/feature
+fi
+
+
+if [ $ngx_found = yes ]; then
+
+    CORE_INCS="$CORE_INCS $ngx_feature_path"
+
+    if [ $USE_GEOIP = YES ]; then
+        CORE_LIBS="$CORE_LIBS $ngx_feature_libs"
+    fi
+
+    NGX_LIB_GEOIP=$ngx_feature_libs
+
+    ngx_feature="GeoIP IPv6 support"
+    ngx_feature_name="NGX_HAVE_GEOIP_V6"
+    ngx_feature_run=no
+    ngx_feature_incs="#include <stdio.h>
+                      #include <GeoIP.h>"
+    #ngx_feature_path=
+    #ngx_feature_libs=
+    ngx_feature_test="printf(\"%d\", GEOIP_CITY_EDITION_REV0_V6);"
+    . auto/feature
+
+else
+
+cat << END
+
+$0: error: the GeoIP module requires the GeoIP library.
+You can either do not enable the module or install the library.
+
+END
+
+    exit 1
+fi
diff --git a/nginx/auto/lib/google-perftools/conf b/nginx/auto/lib/google-perftools/conf
new file mode 100644 (file)
index 0000000..5d5ddae
--- /dev/null
@@ -0,0 +1,61 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+    ngx_feature="Google perftools"
+    ngx_feature_name=
+    ngx_feature_run=no
+    ngx_feature_incs=
+    ngx_feature_path=
+    ngx_feature_libs="-lprofiler"
+    ngx_feature_test="ProfilerStop()"
+    . auto/feature
+
+
+if [ $ngx_found = no ]; then
+
+    # FreeBSD port
+
+    ngx_feature="Google perftools in /usr/local/"
+
+    if [ $NGX_RPATH = YES ]; then
+        ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lprofiler"
+    else
+        ngx_feature_libs="-L/usr/local/lib -lprofiler"
+    fi
+
+    . auto/feature
+fi
+
+
+if [ $ngx_found = no ]; then
+
+    # MacPorts
+
+    ngx_feature="Google perftools in /opt/local/"
+
+    if [ $NGX_RPATH = YES ]; then
+        ngx_feature_libs="-R/opt/local/lib -L/opt/local/lib -lprofiler"
+    else
+        ngx_feature_libs="-L/opt/local/lib -lprofiler"
+    fi
+
+    . auto/feature
+fi
+
+
+if [ $ngx_found = yes ]; then
+    CORE_LIBS="$CORE_LIBS $ngx_feature_libs"
+
+else
+
+cat << END
+
+$0: error: the Google perftools module requires the Google perftools
+library. You can either do not enable the module or install the library.
+
+END
+
+    exit 1
+fi
diff --git a/nginx/auto/lib/libatomic/conf b/nginx/auto/lib/libatomic/conf
new file mode 100644 (file)
index 0000000..d1e484a
--- /dev/null
@@ -0,0 +1,43 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+if [ $NGX_LIBATOMIC != YES ]; then
+
+    have=NGX_HAVE_LIBATOMIC . auto/have
+    CORE_INCS="$CORE_INCS $NGX_LIBATOMIC/src"
+    LINK_DEPS="$LINK_DEPS $NGX_LIBATOMIC/src/libatomic_ops.a"
+    CORE_LIBS="$CORE_LIBS $NGX_LIBATOMIC/src/libatomic_ops.a"
+
+else
+
+    ngx_feature="atomic_ops library"
+    ngx_feature_name=NGX_HAVE_LIBATOMIC
+    ngx_feature_run=yes
+    ngx_feature_incs="#define AO_REQUIRE_CAS
+                      #include <atomic_ops.h>"
+    ngx_feature_path=
+    ngx_feature_libs="-latomic_ops"
+    ngx_feature_test="long  n = 0;
+                      if (!AO_compare_and_swap(&n, 0, 1))
+                          return 1;
+                      if (AO_fetch_and_add(&n, 1) != 1)
+                          return 1;
+                      if (n != 2)
+                          return 1;
+                      AO_nop();"
+    . auto/feature
+
+    if [ $ngx_found = yes ]; then
+        CORE_LIBS="$CORE_LIBS $ngx_feature_libs"
+    else
+
+cat << END
+
+$0: error: libatomic_ops library was not found.
+
+END
+        exit 1
+    fi
+fi
diff --git a/nginx/auto/lib/libatomic/make b/nginx/auto/lib/libatomic/make
new file mode 100644 (file)
index 0000000..c90318e
--- /dev/null
@@ -0,0 +1,16 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+    cat << END                                            >> $NGX_MAKEFILE
+
+$NGX_LIBATOMIC/src/libatomic_ops.a:    $NGX_LIBATOMIC/Makefile
+       cd $NGX_LIBATOMIC && \$(MAKE)
+
+$NGX_LIBATOMIC/Makefile:       $NGX_MAKEFILE
+       cd $NGX_LIBATOMIC \\
+       && if [ -f Makefile ]; then \$(MAKE) distclean; fi \\
+       && ./configure
+
+END
diff --git a/nginx/auto/lib/libgd/conf b/nginx/auto/lib/libgd/conf
new file mode 100644 (file)
index 0000000..87761f1
--- /dev/null
@@ -0,0 +1,93 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+    ngx_feature="GD library"
+    ngx_feature_name=
+    ngx_feature_run=no
+    ngx_feature_incs="#include <gd.h>"
+    ngx_feature_path=
+    ngx_feature_libs="-lgd"
+    ngx_feature_test="gdImagePtr img = gdImageCreateFromGifPtr(1, NULL);"
+    . auto/feature
+
+
+if [ $ngx_found = no ]; then
+
+    # FreeBSD port
+
+    ngx_feature="GD library in /usr/local/"
+    ngx_feature_path="/usr/local/include"
+
+    if [ $NGX_RPATH = YES ]; then
+        ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lgd"
+    else
+        ngx_feature_libs="-L/usr/local/lib -lgd"
+    fi
+
+    . auto/feature
+fi
+
+
+if [ $ngx_found = no ]; then
+
+    # NetBSD port
+
+    ngx_feature="GD library in /usr/pkg/"
+    ngx_feature_path="/usr/pkg/include"
+
+    if [ $NGX_RPATH = YES ]; then
+        ngx_feature_libs="-R/usr/pkg/lib -L/usr/pkg/lib -lgd"
+    else
+        ngx_feature_libs="-L/usr/pkg/lib -lgd"
+    fi
+
+    . auto/feature
+fi
+
+
+if [ $ngx_found = no ]; then
+
+    # MacPorts
+
+    ngx_feature="GD library in /opt/local/"
+    ngx_feature_path="/opt/local/include"
+
+    if [ $NGX_RPATH = YES ]; then
+        ngx_feature_libs="-R/opt/local/lib -L/opt/local/lib -lgd"
+    else
+        ngx_feature_libs="-L/opt/local/lib -lgd"
+    fi
+
+    . auto/feature
+fi
+
+
+if [ $ngx_found = yes ]; then
+
+    CORE_INCS="$CORE_INCS $ngx_feature_path"
+
+    if [ $USE_LIBGD = YES ]; then
+        CORE_LIBS="$CORE_LIBS $ngx_feature_libs"
+    fi
+
+    NGX_LIB_LIBGD=$ngx_feature_libs
+
+    ngx_feature="GD WebP support"
+    ngx_feature_name="NGX_HAVE_GD_WEBP"
+    ngx_feature_test="gdImagePtr img = gdImageCreateFromWebpPtr(1, NULL);"
+    . auto/feature
+
+else
+
+cat << END
+
+$0: error: the HTTP image filter module requires the GD library.
+You can either do not enable the module or install the libraries.
+
+END
+
+    exit 1
+
+fi
diff --git a/nginx/auto/lib/libxslt/conf b/nginx/auto/lib/libxslt/conf
new file mode 100644 (file)
index 0000000..3a0f37b
--- /dev/null
@@ -0,0 +1,165 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+    ngx_feature="libxslt"
+    ngx_feature_name=
+    ngx_feature_run=no
+    ngx_feature_incs="#include <libxml/parser.h>
+                      #include <libxml/tree.h>
+                      #include <libxslt/xslt.h>
+                      #include <libxslt/xsltInternals.h>
+                      #include <libxslt/transform.h>
+                      #include <libxslt/xsltutils.h>"
+    ngx_feature_path="/usr/include/libxml2"
+    ngx_feature_libs="-lxml2 -lxslt"
+    ngx_feature_test="xmlParserCtxtPtr    ctxt = NULL;
+                      xsltStylesheetPtr   sheet = NULL;
+                      xmlDocPtr           doc;
+                      doc = xmlParseChunk(ctxt, NULL, 0, 0);
+                      xsltApplyStylesheet(sheet, doc, NULL);"
+    . auto/feature
+
+
+if [ $ngx_found = no ]; then
+
+    # FreeBSD port
+
+    ngx_feature="libxslt in /usr/local/"
+    ngx_feature_path="/usr/local/include/libxml2 /usr/local/include"
+
+    if [ $NGX_RPATH = YES ]; then
+        ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lxml2 -lxslt"
+    else
+        ngx_feature_libs="-L/usr/local/lib -lxml2 -lxslt"
+    fi
+
+    . auto/feature
+fi
+
+
+if [ $ngx_found = no ]; then
+
+    # NetBSD port
+
+    ngx_feature="libxslt in /usr/pkg/"
+    ngx_feature_path="/usr/pkg/include/libxml2 /usr/pkg/include"
+
+    if [ $NGX_RPATH = YES ]; then
+        ngx_feature_libs="-R/usr/pkg/lib -L/usr/pkg/lib -lxml2 -lxslt"
+    else
+        ngx_feature_libs="-L/usr/pkg/lib -lxml2 -lxslt"
+    fi
+
+    . auto/feature
+fi
+
+
+if [ $ngx_found = no ]; then
+
+    # MacPorts
+
+    ngx_feature="libxslt in /opt/local/"
+    ngx_feature_path="/opt/local/include/libxml2 /opt/local/include"
+
+    if [ $NGX_RPATH = YES ]; then
+        ngx_feature_libs="-R/opt/local/lib -L/opt/local/lib -lxml2 -lxslt"
+    else
+        ngx_feature_libs="-L/opt/local/lib -lxml2 -lxslt"
+    fi
+
+    . auto/feature
+fi
+
+
+if [ $ngx_found = yes ]; then
+
+    CORE_INCS="$CORE_INCS $ngx_feature_path"
+
+    if [ $USE_LIBXSLT = YES ]; then
+        CORE_LIBS="$CORE_LIBS $ngx_feature_libs"
+    fi
+
+    NGX_LIB_LIBXSLT=$ngx_feature_libs
+
+else
+
+cat << END
+
+$0: error: the HTTP XSLT module requires the libxml2/libxslt
+libraries. You can either do not enable the module or install the libraries.
+
+END
+
+    exit 1
+fi
+
+
+    ngx_feature="libexslt"
+    ngx_feature_name=NGX_HAVE_EXSLT
+    ngx_feature_run=no
+    ngx_feature_incs="#include <libexslt/exslt.h>"
+    ngx_feature_path="/usr/include/libxml2"
+    ngx_feature_libs="-lexslt"
+    ngx_feature_test="exsltRegisterAll();"
+    . auto/feature
+
+if [ $ngx_found = no ]; then
+
+    # FreeBSD port
+
+    ngx_feature="libexslt in /usr/local/"
+    ngx_feature_path="/usr/local/include/libxml2 /usr/local/include"
+
+    if [ $NGX_RPATH = YES ]; then
+        ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lexslt"
+    else
+        ngx_feature_libs="-L/usr/local/lib -lexslt"
+    fi
+
+    . auto/feature
+fi
+
+
+if [ $ngx_found = no ]; then
+
+    # NetBSD port
+
+    ngx_feature="libexslt in /usr/pkg/"
+    ngx_feature_path="/usr/pkg/include/libxml2 /usr/local/include"
+
+    if [ $NGX_RPATH = YES ]; then
+        ngx_feature_libs="-R/usr/pkg/lib -L/usr/pkg/lib -lexslt"
+    else
+        ngx_feature_libs="-L/usr/pkg/lib -lexslt"
+    fi
+
+    . auto/feature
+fi
+
+
+if [ $ngx_found = no ]; then
+
+    # MacPorts
+
+    ngx_feature="libexslt in /opt/local/"
+    ngx_feature_path="/opt/local/include/libxml2 /opt/local/include"
+
+    if [ $NGX_RPATH = YES ]; then
+        ngx_feature_libs="-R/opt/local/lib -L/opt/local/lib -lexslt"
+    else
+        ngx_feature_libs="-L/opt/local/lib -lexslt"
+    fi
+
+    . auto/feature
+fi
+
+
+if [ $ngx_found = yes ]; then
+    if [ $USE_LIBXSLT = YES ]; then
+        CORE_LIBS="$CORE_LIBS -lexslt"
+    fi
+
+    NGX_LIB_LIBXSLT="$NGX_LIB_LIBXSLT -lexslt"
+fi
diff --git a/nginx/auto/lib/make b/nginx/auto/lib/make
new file mode 100644 (file)
index 0000000..b64e329
--- /dev/null
@@ -0,0 +1,24 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+if [ $PCRE != NONE -a $PCRE != NO -a $PCRE != YES ]; then
+    . auto/lib/pcre/make
+fi
+
+if [ $OPENSSL != NONE -a $OPENSSL != NO -a $OPENSSL != YES ]; then
+    . auto/lib/openssl/make
+fi
+
+if [ $ZLIB != NONE -a $ZLIB != NO -a $ZLIB != YES ]; then
+    . auto/lib/zlib/make
+fi
+
+if [ $NGX_LIBATOMIC != NO -a $NGX_LIBATOMIC != YES ]; then
+    . auto/lib/libatomic/make
+fi
+
+if [ $USE_PERL != NO ]; then
+    . auto/lib/perl/make
+fi
diff --git a/nginx/auto/lib/openssl/conf b/nginx/auto/lib/openssl/conf
new file mode 100644 (file)
index 0000000..4fb52df
--- /dev/null
@@ -0,0 +1,142 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+if [ $OPENSSL != NONE ]; then
+
+    case "$CC" in
+
+        cl | bcc32)
+            have=NGX_OPENSSL . auto/have
+            have=NGX_SSL . auto/have
+
+            CFLAGS="$CFLAGS -DNO_SYS_TYPES_H"
+
+            CORE_INCS="$CORE_INCS $OPENSSL/openssl/include"
+            CORE_DEPS="$CORE_DEPS $OPENSSL/openssl/include/openssl/ssl.h"
+
+            if [ -f $OPENSSL/ms/do_ms.bat ]; then
+                # before OpenSSL 1.1.0
+                CORE_LIBS="$CORE_LIBS $OPENSSL/openssl/lib/ssleay32.lib"
+                CORE_LIBS="$CORE_LIBS $OPENSSL/openssl/lib/libeay32.lib"
+            else
+                # OpenSSL 1.1.0+
+                CORE_LIBS="$CORE_LIBS $OPENSSL/openssl/lib/libssl.lib"
+                CORE_LIBS="$CORE_LIBS $OPENSSL/openssl/lib/libcrypto.lib"
+            fi
+
+            # libeay32.lib requires gdi32.lib
+            CORE_LIBS="$CORE_LIBS gdi32.lib"
+            # OpenSSL 1.0.0 requires crypt32.lib
+            CORE_LIBS="$CORE_LIBS crypt32.lib"
+        ;;
+
+        *)
+            have=NGX_OPENSSL . auto/have
+            have=NGX_SSL . auto/have
+
+            CORE_INCS="$CORE_INCS $OPENSSL/.openssl/include"
+            CORE_DEPS="$CORE_DEPS $OPENSSL/.openssl/include/openssl/ssl.h"
+            CORE_LIBS="$CORE_LIBS $OPENSSL/.openssl/lib/libssl.a"
+            CORE_LIBS="$CORE_LIBS $OPENSSL/.openssl/lib/libcrypto.a"
+            CORE_LIBS="$CORE_LIBS $NGX_LIBDL"
+            CORE_LIBS="$CORE_LIBS $NGX_LIBPTHREAD"
+
+            if [ "$NGX_PLATFORM" = win32 ]; then
+                CORE_LIBS="$CORE_LIBS -lgdi32 -lcrypt32 -lws2_32"
+            fi
+        ;;
+    esac
+
+else
+
+    if [ "$NGX_PLATFORM" != win32 ]; then
+
+        OPENSSL=NO
+
+        ngx_feature="OpenSSL library"
+        ngx_feature_name="NGX_OPENSSL"
+        ngx_feature_run=no
+        ngx_feature_incs="#include <openssl/ssl.h>"
+        ngx_feature_path=
+        ngx_feature_libs="-lssl -lcrypto $NGX_LIBDL $NGX_LIBPTHREAD"
+        ngx_feature_test="SSL_CTX_set_options(NULL, 0)"
+        . auto/feature
+
+        if [ $ngx_found = no ]; then
+
+            # FreeBSD port
+
+            ngx_feature="OpenSSL library in /usr/local/"
+            ngx_feature_path="/usr/local/include"
+
+            if [ $NGX_RPATH = YES ]; then
+                ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lssl -lcrypto"
+            else
+                ngx_feature_libs="-L/usr/local/lib -lssl -lcrypto"
+            fi
+
+            ngx_feature_libs="$ngx_feature_libs $NGX_LIBDL $NGX_LIBPTHREAD"
+
+            . auto/feature
+        fi
+
+        if [ $ngx_found = no ]; then
+
+            # NetBSD port
+
+            ngx_feature="OpenSSL library in /usr/pkg/"
+            ngx_feature_path="/usr/pkg/include"
+
+            if [ $NGX_RPATH = YES ]; then
+                ngx_feature_libs="-R/usr/pkg/lib -L/usr/pkg/lib -lssl -lcrypto"
+            else
+                ngx_feature_libs="-L/usr/pkg/lib -lssl -lcrypto"
+            fi
+
+            ngx_feature_libs="$ngx_feature_libs $NGX_LIBDL $NGX_LIBPTHREAD"
+
+            . auto/feature
+        fi
+
+        if [ $ngx_found = no ]; then
+
+            # MacPorts
+
+            ngx_feature="OpenSSL library in /opt/local/"
+            ngx_feature_path="/opt/local/include"
+
+            if [ $NGX_RPATH = YES ]; then
+                ngx_feature_libs="-R/opt/local/lib -L/opt/local/lib -lssl -lcrypto"
+            else
+                ngx_feature_libs="-L/opt/local/lib -lssl -lcrypto"
+            fi
+
+            ngx_feature_libs="$ngx_feature_libs $NGX_LIBDL $NGX_LIBPTHREAD"
+
+            . auto/feature
+        fi
+
+        if [ $ngx_found = yes ]; then
+            have=NGX_SSL . auto/have
+            CORE_INCS="$CORE_INCS $ngx_feature_path"
+            CORE_LIBS="$CORE_LIBS $ngx_feature_libs"
+            OPENSSL=YES
+        fi
+    fi
+
+    if [ $OPENSSL != YES ]; then
+
+cat << END
+
+$0: error: SSL modules require the OpenSSL library.
+You can either do not enable the modules, or install the OpenSSL library
+into the system, or build the OpenSSL library statically from the source
+with nginx by using --with-openssl=<path> option.
+
+END
+        exit 1
+    fi
+
+fi
diff --git a/nginx/auto/lib/openssl/make b/nginx/auto/lib/openssl/make
new file mode 100644 (file)
index 0000000..126a238
--- /dev/null
@@ -0,0 +1,62 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+case "$CC" in
+
+    cl)
+
+        cat << END                                            >> $NGX_MAKEFILE
+
+$OPENSSL/openssl/include/openssl/ssl.h:        $NGX_MAKEFILE
+       \$(MAKE) -f auto/lib/openssl/makefile.msvc                      \
+               OPENSSL="$OPENSSL" OPENSSL_OPT="$OPENSSL_OPT"
+
+END
+
+    ;;
+
+    bcc32)
+
+        ngx_opt=`echo "-DOPENSSL=\"$OPENSSL\" -DOPENSSL_OPT=\"$OPENSSL_OPT\"" \
+            | sed -e "s/\//$ngx_regex_dirsep/g"`
+
+        cat << END                                            >> $NGX_MAKEFILE
+
+`echo "$OPENSSL\\openssl\\lib\\libeay32.lib:                           \
+       $OPENSSL\\openssl\\include\\openssl\\ssl.h"                     \
+       | sed -e "s/\//$ngx_regex_dirsep/g"`
+
+`echo "$OPENSSL\\openssl\\lib\\ssleay32.lib:                           \
+       $OPENSSL\\openssl\\include\\openssl\\ssl.h"                     \
+       | sed -e "s/\//$ngx_regex_dirsep/g"`
+
+`echo "$OPENSSL\\openssl\\include\\openssl\\ssl.h:     $NGX_MAKEFILE"  \
+       | sed -e "s/\//$ngx_regex_dirsep/g"`
+       \$(MAKE) -f auto/lib/openssl/makefile.bcc $ngx_opt
+
+END
+
+    ;;
+
+    *)
+        case $OPENSSL in
+            /*) ngx_prefix="$OPENSSL/.openssl" ;;
+            *)  ngx_prefix="$PWD/$OPENSSL/.openssl" ;;
+        esac
+
+        cat << END                                            >> $NGX_MAKEFILE
+
+$OPENSSL/.openssl/include/openssl/ssl.h:       $NGX_MAKEFILE
+       cd $OPENSSL \\
+       && if [ -f Makefile ]; then \$(MAKE) clean; fi \\
+       && ./config --prefix=$ngx_prefix no-shared no-threads $OPENSSL_OPT \\
+       && \$(MAKE) \\
+       && \$(MAKE) install_sw LIBDIR=lib
+
+END
+
+    ;;
+
+esac
diff --git a/nginx/auto/lib/openssl/makefile.bcc b/nginx/auto/lib/openssl/makefile.bcc
new file mode 100644 (file)
index 0000000..6a94ff7
--- /dev/null
@@ -0,0 +1,18 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+all:
+       cd $(OPENSSL)
+
+       perl Configure BC-32 no-shared --prefix=openssl $(OPENSSL_OPT)
+
+       ms\do_nasm
+
+       $(MAKE) -f ms\bcb.mak
+       $(MAKE) -f ms\bcb.mak install
+
+       # Borland's make does not expand "[ch]" in
+       #    copy "inc32\openssl\*.[ch]" "openssl\include\openssl"
+       copy inc32\openssl\*.h openssl\include\openssl
diff --git a/nginx/auto/lib/openssl/makefile.msvc b/nginx/auto/lib/openssl/makefile.msvc
new file mode 100644 (file)
index 0000000..5b90dcb
--- /dev/null
@@ -0,0 +1,21 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+all:
+       cd $(OPENSSL)
+
+       perl Configure VC-WIN32 no-shared                               \
+               --prefix="%cd%/openssl"                                 \
+               --openssldir="%cd%/openssl/ssl"                         \
+               $(OPENSSL_OPT)
+
+       if exist ms\do_ms.bat (                                         \
+               ms\do_ms                                                \
+               && $(MAKE) -f ms\nt.mak                                 \
+               && $(MAKE) -f ms\nt.mak install                         \
+       ) else (                                                        \
+               $(MAKE)                                                 \
+               && $(MAKE) install_sw                                   \
+       )
diff --git a/nginx/auto/lib/pcre/conf b/nginx/auto/lib/pcre/conf
new file mode 100644 (file)
index 0000000..5e3960f
--- /dev/null
@@ -0,0 +1,203 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+if [ $PCRE != NONE ]; then
+    CORE_INCS="$CORE_INCS $PCRE"
+
+    case "$NGX_CC_NAME" in
+
+        msvc | owc | bcc)
+            have=NGX_PCRE . auto/have
+            have=PCRE_STATIC . auto/have
+            CORE_DEPS="$CORE_DEPS $PCRE/pcre.h"
+            LINK_DEPS="$LINK_DEPS $PCRE/pcre.lib"
+            CORE_LIBS="$CORE_LIBS $PCRE/pcre.lib"
+        ;;
+
+        icc)
+            have=NGX_PCRE . auto/have
+            CORE_DEPS="$CORE_DEPS $PCRE/pcre.h"
+
+            LINK_DEPS="$LINK_DEPS $PCRE/.libs/libpcre.a"
+
+            echo $ngx_n "checking for PCRE library ...$ngx_c"
+
+            if [ -f $PCRE/pcre.h ]; then
+                ngx_pcre_ver=`grep PCRE_MAJOR $PCRE/pcre.h \
+                              | sed -e 's/^.*PCRE_MAJOR.* \(.*\)$/\1/'`
+
+            else if [ -f $PCRE/configure.in ]; then
+                ngx_pcre_ver=`grep PCRE_MAJOR= $PCRE/configure.in \
+                              | sed -e 's/^.*=\(.*\)$/\1/'`
+
+            else
+                ngx_pcre_ver=`grep pcre_major, $PCRE/configure.ac \
+                              | sed -e 's/^.*pcre_major,.*\[\(.*\)\].*$/\1/'`
+            fi
+            fi
+
+            echo " $ngx_pcre_ver major version found"
+
+            # to allow -ipo optimization we link with the *.o but not library
+
+            case "$ngx_pcre_ver" in
+                4|5)
+                    CORE_LIBS="$CORE_LIBS $PCRE/pcre.o"
+                ;;
+
+                6)
+                    CORE_LIBS="$CORE_LIBS $PCRE/pcre_chartables.o"
+                    CORE_LIBS="$CORE_LIBS $PCRE/pcre_compile.o"
+                    CORE_LIBS="$CORE_LIBS $PCRE/pcre_exec.o"
+                    CORE_LIBS="$CORE_LIBS $PCRE/pcre_fullinfo.o"
+                    CORE_LIBS="$CORE_LIBS $PCRE/pcre_globals.o"
+                    CORE_LIBS="$CORE_LIBS $PCRE/pcre_tables.o"
+                    CORE_LIBS="$CORE_LIBS $PCRE/pcre_try_flipped.o"
+                ;;
+
+                *)
+                    CORE_LIBS="$CORE_LIBS $PCRE/pcre_chartables.o"
+                    CORE_LIBS="$CORE_LIBS $PCRE/pcre_compile.o"
+                    CORE_LIBS="$CORE_LIBS $PCRE/pcre_exec.o"
+                    CORE_LIBS="$CORE_LIBS $PCRE/pcre_fullinfo.o"
+                    CORE_LIBS="$CORE_LIBS $PCRE/pcre_globals.o"
+                    CORE_LIBS="$CORE_LIBS $PCRE/pcre_tables.o"
+                    CORE_LIBS="$CORE_LIBS $PCRE/pcre_try_flipped.o"
+                    CORE_LIBS="$CORE_LIBS $PCRE/pcre_newline.o"
+                ;;
+
+            esac
+        ;;
+
+        *)
+            have=NGX_PCRE . auto/have
+
+            if [ "$NGX_PLATFORM" = win32 ]; then
+                have=PCRE_STATIC . auto/have
+            fi
+
+            CORE_DEPS="$CORE_DEPS $PCRE/pcre.h"
+            LINK_DEPS="$LINK_DEPS $PCRE/.libs/libpcre.a"
+            CORE_LIBS="$CORE_LIBS $PCRE/.libs/libpcre.a"
+        ;;
+
+    esac
+
+
+    if [ $PCRE_JIT = YES ]; then
+        have=NGX_HAVE_PCRE_JIT . auto/have
+        PCRE_CONF_OPT="$PCRE_CONF_OPT --enable-jit"
+    fi
+
+else
+
+    if [ "$NGX_PLATFORM" != win32 ]; then
+
+        PCRE=NO
+
+        ngx_feature="PCRE library"
+        ngx_feature_name="NGX_PCRE"
+        ngx_feature_run=no
+        ngx_feature_incs="#include <pcre.h>"
+        ngx_feature_path=
+        ngx_feature_libs="-lpcre"
+        ngx_feature_test="pcre *re;
+                          re = pcre_compile(NULL, 0, NULL, 0, NULL);
+                          if (re == NULL) return 1"
+        . auto/feature
+
+        if [ $ngx_found = no ]; then
+
+            # FreeBSD port
+
+            ngx_feature="PCRE library in /usr/local/"
+            ngx_feature_path="/usr/local/include"
+
+            if [ $NGX_RPATH = YES ]; then
+                ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lpcre"
+            else
+                ngx_feature_libs="-L/usr/local/lib -lpcre"
+            fi
+
+            . auto/feature
+        fi
+
+        if [ $ngx_found = no ]; then
+
+            # RedHat RPM, Solaris package
+
+            ngx_feature="PCRE library in /usr/include/pcre/"
+            ngx_feature_path="/usr/include/pcre"
+            ngx_feature_libs="-lpcre"
+
+            . auto/feature
+        fi
+
+        if [ $ngx_found = no ]; then
+
+            # NetBSD port
+
+            ngx_feature="PCRE library in /usr/pkg/"
+            ngx_feature_path="/usr/pkg/include"
+
+            if [ $NGX_RPATH = YES ]; then
+                ngx_feature_libs="-R/usr/pkg/lib -L/usr/pkg/lib -lpcre"
+            else
+                ngx_feature_libs="-L/usr/pkg/lib -lpcre"
+            fi
+
+            . auto/feature
+        fi
+
+        if [ $ngx_found = no ]; then
+
+            # MacPorts
+
+            ngx_feature="PCRE library in /opt/local/"
+            ngx_feature_path="/opt/local/include"
+
+            if [ $NGX_RPATH = YES ]; then
+                ngx_feature_libs="-R/opt/local/lib -L/opt/local/lib -lpcre"
+            else
+                ngx_feature_libs="-L/opt/local/lib -lpcre"
+            fi
+
+            . auto/feature
+        fi
+
+        if [ $ngx_found = yes ]; then
+            CORE_INCS="$CORE_INCS $ngx_feature_path"
+            CORE_LIBS="$CORE_LIBS $ngx_feature_libs"
+            PCRE=YES
+        fi
+
+        if [ $PCRE = YES ]; then
+            ngx_feature="PCRE JIT support"
+            ngx_feature_name="NGX_HAVE_PCRE_JIT"
+            ngx_feature_test="int jit = 0;
+                              pcre_free_study(NULL);
+                              pcre_config(PCRE_CONFIG_JIT, &jit);
+                              if (jit != 1) return 1;"
+            . auto/feature
+
+            if [ $ngx_found = yes ]; then
+                PCRE_JIT=YES
+            fi
+        fi
+    fi
+
+    if [ $PCRE != YES ]; then
+cat << END
+
+$0: error: the HTTP rewrite module requires the PCRE library.
+You can either disable the module by using --without-http_rewrite_module
+option, or install the PCRE library into the system, or build the PCRE library
+statically from the source with nginx by using --with-pcre=<path> option.
+
+END
+        exit 1
+    fi
+
+fi
diff --git a/nginx/auto/lib/pcre/make b/nginx/auto/lib/pcre/make
new file mode 100644 (file)
index 0000000..97c9f3b
--- /dev/null
@@ -0,0 +1,64 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+case "$NGX_CC_NAME" in
+
+    msvc)
+        ngx_makefile=makefile.msvc
+        ngx_opt="CPU_OPT=\"$CPU_OPT\" LIBC=$LIBC"
+        ngx_pcre="PCRE=\"$PCRE\""
+    ;;
+
+    owc)
+        ngx_makefile=makefile.owc
+        ngx_opt="CPU_OPT=\"$CPU_OPT\""
+        ngx_pcre=`echo PCRE=\"$PCRE\" | sed -e "s/\//$ngx_regex_dirsep/g"`
+    ;;
+
+    bcc)
+        ngx_makefile=makefile.bcc
+        ngx_opt="-DCPU_OPT=\"$CPU_OPT\""
+        ngx_pcre=`echo \-DPCRE=\"$PCRE\" | sed -e "s/\//$ngx_regex_dirsep/g"`
+    ;;
+
+    *)
+        ngx_makefile=
+    ;;
+
+esac
+
+
+if [ -n "$ngx_makefile" ]; then
+
+    cat << END                                                >> $NGX_MAKEFILE
+
+`echo "$PCRE/pcre.lib: $PCRE/pcre.h $NGX_MAKEFILE"                     \
+       | sed -e "s/\//$ngx_regex_dirsep/g"`
+       \$(MAKE) -f auto/lib/pcre/$ngx_makefile $ngx_pcre $ngx_opt
+
+`echo "$PCRE/pcre.h:" | sed -e "s/\//$ngx_regex_dirsep/g"`
+       \$(MAKE) -f auto/lib/pcre/$ngx_makefile $ngx_pcre pcre.h
+
+END
+
+else
+
+    cat << END                                                >> $NGX_MAKEFILE
+
+$PCRE/pcre.h:  $PCRE/Makefile
+
+$PCRE/Makefile:        $NGX_MAKEFILE
+       cd $PCRE \\
+       && if [ -f Makefile ]; then \$(MAKE) distclean; fi \\
+       && CC="\$(CC)" CFLAGS="$PCRE_OPT" \\
+       ./configure --disable-shared $PCRE_CONF_OPT
+
+$PCRE/.libs/libpcre.a: $PCRE/Makefile
+       cd $PCRE \\
+       && \$(MAKE) libpcre.la
+
+END
+
+fi
diff --git a/nginx/auto/lib/pcre/makefile.bcc b/nginx/auto/lib/pcre/makefile.bcc
new file mode 100644 (file)
index 0000000..7a0f2be
--- /dev/null
@@ -0,0 +1,27 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+CFLAGS =       -q -O2 -tWM -w-8004 $(CPU_OPT)
+PCREFLAGS =    -DHAVE_CONFIG_H -DPCRE_STATIC -DPOSIX_MALLOC_THRESHOLD=10 \
+               -DSUPPORT_PCRE8 -DHAVE_MEMMOVE
+
+
+pcre.lib:
+       cd $(PCRE)
+
+       bcc32 -c $(CFLAGS) -I. $(PCREFLAGS) pcre_*.c
+
+       copy /y nul pcre.lst
+       for %n in (*.obj) do @echo +%n ^^& >> pcre.lst
+       echo + >> pcre.lst
+
+       tlib pcre.lib @pcre.lst
+
+pcre.h:
+       cd $(PCRE)
+
+       copy /y pcre.h.generic pcre.h
+       copy /y config.h.generic config.h
+       copy /y pcre_chartables.c.dist pcre_chartables.c
diff --git a/nginx/auto/lib/pcre/makefile.msvc b/nginx/auto/lib/pcre/makefile.msvc
new file mode 100644 (file)
index 0000000..07fd9a2
--- /dev/null
@@ -0,0 +1,23 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+CFLAGS =       -O2 -Ob1 -Oi -Gs $(LIBC) $(CPU_OPT)
+PCREFLAGS =    -DHAVE_CONFIG_H -DPCRE_STATIC -DPOSIX_MALLOC_THRESHOLD=10 \
+               -DSUPPORT_PCRE8 -DHAVE_MEMMOVE
+
+
+pcre.lib:
+       cd $(PCRE)
+
+       cl -nologo -c $(CFLAGS) -I . $(PCREFLAGS) pcre_*.c
+
+       link -lib -out:pcre.lib -verbose:lib pcre_*.obj
+
+pcre.h:
+       cd $(PCRE)
+
+       copy /y pcre.h.generic pcre.h
+       copy /y config.h.generic config.h
+       copy /y pcre_chartables.c.dist pcre_chartables.c
diff --git a/nginx/auto/lib/pcre/makefile.owc b/nginx/auto/lib/pcre/makefile.owc
new file mode 100644 (file)
index 0000000..122fd5b
--- /dev/null
@@ -0,0 +1,25 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+CFLAGS =       -c -zq -bt=nt -ot -op -oi -oe -s -bm $(CPU_OPT)
+PCREFLAGS =    -DHAVE_CONFIG_H -DPCRE_STATIC -DPOSIX_MALLOC_THRESHOLD=10 &
+               -DSUPPORT_PCRE8 -DHAVE_MEMMOVE
+
+
+pcre.lib:
+       cd $(PCRE)
+
+       wcl386 $(CFLAGS) -i=. $(PCREFLAGS) pcre_*.c
+
+       dir /b *.obj > pcre.lst
+
+       wlib -n pcre.lib @pcre.lst
+
+pcre.h:
+       cd $(PCRE)
+
+       copy /y pcre.h.generic pcre.h
+       copy /y config.h.generic config.h
+       copy /y pcre_chartables.c.dist pcre_chartables.c
diff --git a/nginx/auto/lib/perl/conf b/nginx/auto/lib/perl/conf
new file mode 100644 (file)
index 0000000..e16a1bc
--- /dev/null
@@ -0,0 +1,83 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+echo "checking for perl"
+
+
+NGX_PERL_VER=`$NGX_PERL -v 2>&1 | grep '^This is perl' 2>&1 \
+                                | sed -e 's/^This is perl, \(.*\)/\1/'`
+
+if test -n "$NGX_PERL_VER"; then
+    echo " + perl version: $NGX_PERL_VER"
+
+    if [ "`$NGX_PERL -e 'use 5.008006; print "OK"'`" != "OK" ]; then
+        echo
+        echo "$0: error: perl 5.8.6 or higher is required"
+        echo
+
+        exit 1;
+    fi
+
+    if [ "`$NGX_PERL -MExtUtils::Embed -e 'print "OK"'`" != "OK" ]; then
+        echo
+        echo "$0: error: perl module ExtUtils::Embed is required"
+        echo
+
+        exit 1;
+    fi
+
+    NGX_PM_CFLAGS=`$NGX_PERL -MExtUtils::Embed -e ccopts`
+    NGX_PM_LDFLAGS=`$NGX_PERL -MConfig -e 'print $Config{lddlflags}'`
+
+    NGX_PERL_CFLAGS="$CFLAGS `$NGX_PERL -MExtUtils::Embed -e ccopts`"
+
+    # gcc 4.1/4.2 warn about unused values in pTHX_
+    NGX_PERL_CFLAGS=`echo $NGX_PERL_CFLAGS \
+                     | sed -e 's/-Wunused-value/-Wno-unused-value/'`
+    # icc8 warns 'declaration hides parameter "my_perl"' in ENTER and LEAVE
+    NGX_PERL_CFLAGS=`echo $NGX_PERL_CFLAGS \
+                     | sed -e 's/-wd171/-wd171 -wd1599/'`
+
+    ngx_perl_ldopts=`$NGX_PERL -MExtUtils::Embed -e ldopts`
+
+    ngx_perl_dlext=`$NGX_PERL -MConfig -e 'print $Config{dlext}'`
+    ngx_perl_libdir="src/http/modules/perl/blib/arch/auto"
+    ngx_perl_module="$ngx_perl_libdir/nginx/nginx.$ngx_perl_dlext"
+
+    if $NGX_PERL -V:usemultiplicity | grep define > /dev/null; then
+        have=NGX_HAVE_PERL_MULTIPLICITY . auto/have
+        echo " + perl interpreter multiplicity found"
+    fi
+
+    if $NGX_PERL -V:useithreads | grep undef > /dev/null; then
+        # FreeBSD port wants to link with -pthread non-threaded perl
+        ngx_perl_ldopts=`echo $ngx_perl_ldopts | sed 's/ -pthread//'`
+    fi
+
+    if [ "$NGX_SYSTEM" = "Darwin" ]; then
+        # OS X system perl wants to link universal binaries
+        ngx_perl_ldopts=`echo $ngx_perl_ldopts \
+                         | sed -e 's/-arch i386//' -e 's/-arch x86_64//'`
+    fi
+
+    if [ $USE_PERL = YES ]; then
+        CORE_LINK="$CORE_LINK $ngx_perl_ldopts"
+    fi
+
+    NGX_LIB_PERL="$ngx_perl_ldopts"
+
+    if test -n "$NGX_PERL_MODULES"; then
+        have=NGX_PERL_MODULES value="(u_char *) \"$NGX_PERL_MODULES\""
+        . auto/define
+        NGX_PERL_MODULES_MAN=$NGX_PERL_MODULES/man3
+    fi
+
+else
+    echo
+    echo "$0: error: perl 5.8.6 or higher is required"
+    echo
+
+    exit 1;
+fi
diff --git a/nginx/auto/lib/perl/make b/nginx/auto/lib/perl/make
new file mode 100644 (file)
index 0000000..74e0f3a
--- /dev/null
@@ -0,0 +1,46 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+cat << END                                                    >> $NGX_MAKEFILE
+
+$NGX_OBJS/src/http/modules/perl/ngx_http_perl_module.o: \\
+               $NGX_OBJS/$ngx_perl_module
+
+$NGX_OBJS/$ngx_perl_module: \\
+               \$(CORE_DEPS) \$(HTTP_DEPS) \\
+               src/http/modules/perl/ngx_http_perl_module.h \\
+               $NGX_OBJS/src/http/modules/perl/Makefile
+       cd $NGX_OBJS/src/http/modules/perl && \$(MAKE)
+
+       rm -rf $NGX_OBJS/install_perl
+
+
+$NGX_OBJS/src/http/modules/perl/Makefile: \\
+               $NGX_AUTO_CONFIG_H \\
+               src/core/nginx.h \\
+               src/http/modules/perl/Makefile.PL \\
+               src/http/modules/perl/nginx.pm \\
+               src/http/modules/perl/nginx.xs \\
+               src/http/modules/perl/typemap
+       grep 'define NGINX_VERSION' src/core/nginx.h \\
+               | sed -e 's/^.*"\(.*\)".*/\1/' > \\
+               $NGX_OBJS/src/http/modules/perl/version
+       sed "s/%%VERSION%%/\`cat $NGX_OBJS/src/http/modules/perl/version\`/" \\
+               src/http/modules/perl/nginx.pm > \\
+               $NGX_OBJS/src/http/modules/perl/nginx.pm
+       cp -p src/http/modules/perl/nginx.xs $NGX_OBJS/src/http/modules/perl/
+       cp -p src/http/modules/perl/typemap $NGX_OBJS/src/http/modules/perl/
+       cp -p src/http/modules/perl/Makefile.PL $NGX_OBJS/src/http/modules/perl/
+
+       cd $NGX_OBJS/src/http/modules/perl \\
+               && NGX_PM_CFLAGS="\$(NGX_PM_CFLAGS) -g $NGX_CC_OPT" \\
+                       NGX_PM_LDFLAGS="$NGX_LD_OPT \$(NGX_PM_LDFLAGS)" \\
+                       NGX_INCS="$CORE_INCS $NGX_OBJS $HTTP_INCS" \\
+                       NGX_DEPS="\$(CORE_DEPS) \$(HTTP_DEPS)" \\
+               $NGX_PERL Makefile.PL \\
+                       LIB=$NGX_PERL_MODULES \\
+                       INSTALLSITEMAN3DIR=$NGX_PERL_MODULES_MAN
+
+END
diff --git a/nginx/auto/lib/zlib/conf b/nginx/auto/lib/zlib/conf
new file mode 100644 (file)
index 0000000..239592e
--- /dev/null
@@ -0,0 +1,79 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+if [ $ZLIB != NONE ]; then
+    CORE_INCS="$CORE_INCS $ZLIB"
+
+    case "$NGX_CC_NAME" in
+
+        msvc | owc | bcc)
+            have=NGX_ZLIB . auto/have
+            LINK_DEPS="$LINK_DEPS $ZLIB/zlib.lib"
+            CORE_LIBS="$CORE_LIBS $ZLIB/zlib.lib"
+        ;;
+
+        icc)
+            have=NGX_ZLIB . auto/have
+            LINK_DEPS="$LINK_DEPS $ZLIB/libz.a"
+
+            # to allow -ipo optimization we link with the *.o but not library
+            CORE_LIBS="$CORE_LIBS $ZLIB/adler32.o"
+            CORE_LIBS="$CORE_LIBS $ZLIB/crc32.o"
+            CORE_LIBS="$CORE_LIBS $ZLIB/deflate.o"
+            CORE_LIBS="$CORE_LIBS $ZLIB/trees.o"
+            CORE_LIBS="$CORE_LIBS $ZLIB/zutil.o"
+            CORE_LIBS="$CORE_LIBS $ZLIB/compress.o"
+
+            if [ $ZLIB_ASM != NO ]; then
+                CORE_LIBS="$CORE_LIBS $ZLIB/match.o"
+            fi
+        ;;
+
+        *)
+            have=NGX_ZLIB . auto/have
+            LINK_DEPS="$LINK_DEPS $ZLIB/libz.a"
+            CORE_LIBS="$CORE_LIBS $ZLIB/libz.a"
+            #CORE_LIBS="$CORE_LIBS -L $ZLIB -lz"
+        ;;
+
+    esac
+
+else
+
+    if [ "$NGX_PLATFORM" != win32 ]; then
+        ZLIB=NO
+
+        # FreeBSD, Solaris, Linux
+
+        ngx_feature="zlib library"
+        ngx_feature_name="NGX_ZLIB"
+        ngx_feature_run=no
+        ngx_feature_incs="#include <zlib.h>"
+        ngx_feature_path=
+        ngx_feature_libs="-lz"
+        ngx_feature_test="z_stream z; deflate(&z, Z_NO_FLUSH)"
+        . auto/feature
+
+
+        if [ $ngx_found = yes ]; then
+            CORE_LIBS="$CORE_LIBS $ngx_feature_libs"
+            ZLIB=YES
+            ngx_found=no
+        fi
+    fi
+
+    if [ $ZLIB != YES ]; then
+cat << END
+
+$0: error: the HTTP gzip module requires the zlib library.
+You can either disable the module by using --without-http_gzip_module
+option, or install the zlib library into the system, or build the zlib library
+statically from the source with nginx by using --with-zlib=<path> option.
+
+END
+        exit 1
+    fi
+
+fi
diff --git a/nginx/auto/lib/zlib/make b/nginx/auto/lib/zlib/make
new file mode 100644 (file)
index 0000000..0082ad5
--- /dev/null
@@ -0,0 +1,135 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+case "$NGX_CC_NAME" in
+
+    msvc)
+        ngx_makefile=makefile.msvc
+        ngx_opt="CPU_OPT=\"$CPU_OPT\" LIBC=$LIBC"
+        ngx_zlib="ZLIB=\"$ZLIB\""
+
+    ;;
+
+    owc)
+        ngx_makefile=makefile.owc
+        ngx_opt="CPU_OPT=\"$CPU_OPT\""
+        ngx_zlib=`echo ZLIB=\"$ZLIB\" | sed -e "s/\//$ngx_regex_dirsep/g"`
+    ;;
+
+    bcc)
+        ngx_makefile=makefile.bcc
+        ngx_opt="-DCPU_OPT=\"$CPU_OPT\""
+        ngx_zlib=`echo \-DZLIB=\"$ZLIB\" | sed -e "s/\//$ngx_regex_dirsep/g"`
+    ;;
+
+    *)
+        ngx_makefile=
+    ;;
+
+esac
+
+
+done=NO
+
+
+case "$NGX_PLATFORM" in
+
+    win32)
+
+        if [ -n "$ngx_makefile" ]; then
+            cat << END                                        >> $NGX_MAKEFILE
+
+`echo "$ZLIB/zlib.lib: $NGX_MAKEFILE" | sed -e "s/\//$ngx_regex_dirsep/g"`
+       \$(MAKE) -f auto/lib/zlib/$ngx_makefile $ngx_opt $ngx_zlib
+
+END
+
+        else
+
+            cat << END                                        >> $NGX_MAKEFILE
+
+$ZLIB/libz.a:  $NGX_MAKEFILE
+       cd $ZLIB \\
+       && \$(MAKE) distclean \\
+       && \$(MAKE) -f win32/Makefile.gcc \\
+               CFLAGS="$ZLIB_OPT" CC="\$(CC)" \\
+               libz.a
+
+END
+
+        fi
+
+        done=YES
+    ;;
+
+    # FreeBSD: i386
+    # Linux: i686
+
+    *:i386 | *:i686)
+        case $ZLIB_ASM in
+            pentium)
+
+                cat << END                                    >> $NGX_MAKEFILE
+
+$ZLIB/libz.a:  $NGX_MAKEFILE
+       cd $ZLIB \\
+       && \$(MAKE) distclean \\
+       && cp contrib/asm586/match.S . \\
+       && CFLAGS="$ZLIB_OPT -DASMV" CC="\$(CC)" \\
+               ./configure \\
+       && \$(MAKE) OBJA=match.o libz.a
+
+END
+
+                done=YES
+            ;;
+
+            pentiumpro)
+
+                cat << END                                    >> $NGX_MAKEFILE
+
+$ZLIB/libz.a:  $NGX_MAKEFILE
+       cd $ZLIB \\
+       && \$(MAKE) distclean \\
+       && cp contrib/asm686/match.S . \\
+       && CFLAGS="$ZLIB_OPT -DASMV" CC="\$(CC)" \\
+               ./configure \\
+       && \$(MAKE) OBJA=match.o libz.a
+
+END
+
+                done=YES
+            ;;
+
+            NO)
+            ;;
+
+            *)
+                echo "$0: error: invalid --with-zlib-asm=$ZLIB_ASM option."
+                echo "The valid values are \"pentium\" and \"pentiumpro\" only".
+                echo
+
+                exit 1;
+            ;;
+        esac
+    ;;
+
+esac
+
+
+if [ $done = NO ]; then
+
+    cat << END                                                >> $NGX_MAKEFILE
+
+$ZLIB/libz.a:  $NGX_MAKEFILE
+       cd $ZLIB \\
+       && \$(MAKE) distclean \\
+       && CFLAGS="$ZLIB_OPT" CC="\$(CC)" \\
+               ./configure \\
+       && \$(MAKE) libz.a
+
+END
+
+fi
diff --git a/nginx/auto/lib/zlib/makefile.bcc b/nginx/auto/lib/zlib/makefile.bcc
new file mode 100644 (file)
index 0000000..97a30ea
--- /dev/null
@@ -0,0 +1,17 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+CFLAGS = -q -O2 -tWM -w-8004 -w-8012 $(CPU_OPT)
+
+zlib.lib:
+       cd $(ZLIB)
+
+       bcc32 -c $(CFLAGS) adler32.c crc32.c deflate.c \
+               trees.c zutil.c compress.c \
+               inflate.c inffast.c inftrees.c
+
+       tlib zlib.lib +adler32.obj +crc32.obj +deflate.obj \
+               +trees.obj +zutil.obj +compress.obj \
+               +inflate.obj +inffast.obj +inftrees.obj
diff --git a/nginx/auto/lib/zlib/makefile.msvc b/nginx/auto/lib/zlib/makefile.msvc
new file mode 100644 (file)
index 0000000..6fbd691
--- /dev/null
@@ -0,0 +1,17 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+CFLAGS = -nologo -O2 -Ob1 -Oi -Gs $(LIBC) $(CPU_OPT)
+
+zlib.lib:
+       cd $(ZLIB)
+
+       cl -c $(CFLAGS) adler32.c crc32.c deflate.c \
+               trees.c zutil.c compress.c \
+               inflate.c inffast.c inftrees.c
+
+       link -lib -out:zlib.lib adler32.obj crc32.obj deflate.obj \
+               trees.obj zutil.obj compress.obj \
+               inflate.obj inffast.obj inftrees.obj
diff --git a/nginx/auto/lib/zlib/makefile.owc b/nginx/auto/lib/zlib/makefile.owc
new file mode 100644 (file)
index 0000000..9e123be
--- /dev/null
@@ -0,0 +1,14 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+CFLAGS = -zq -bt=nt -ot -op -oi -oe -s -bm $(CPU_OPT)
+
+zlib.lib:
+       cd $(ZLIB)
+
+       wcl386 -c $(CFLAGS) adler32.c crc32.c deflate.c trees.c zutil.c &
+               compress.c inflate.c inffast.c inftrees.c
+       wlib -n zlib.lib adler32.obj crc32.obj deflate.obj trees.obj &
+               zutil.obj compress.obj inflate.obj inffast.obj inftrees.obj
diff --git a/nginx/auto/make b/nginx/auto/make
new file mode 100644 (file)
index 0000000..0e21251
--- /dev/null
@@ -0,0 +1,700 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+echo "creating $NGX_MAKEFILE"
+
+mkdir -p $NGX_OBJS/src/core $NGX_OBJS/src/event $NGX_OBJS/src/event/modules \
+         $NGX_OBJS/src/os/unix $NGX_OBJS/src/os/win32 \
+         $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/modules \
+         $NGX_OBJS/src/http/modules/perl \
+         $NGX_OBJS/src/mail \
+         $NGX_OBJS/src/stream \
+         $NGX_OBJS/src/misc
+
+
+ngx_objs_dir=$NGX_OBJS$ngx_regex_dirsep
+ngx_use_pch=`echo $NGX_USE_PCH | sed -e "s/\//$ngx_regex_dirsep/g"`
+
+
+cat << END                                                     > $NGX_MAKEFILE
+
+CC =   $CC
+CFLAGS = $CFLAGS
+CPP =  $CPP
+LINK = $LINK
+
+END
+
+
+if test -n "$NGX_PERL_CFLAGS"; then
+    echo NGX_PERL_CFLAGS = $NGX_PERL_CFLAGS                   >> $NGX_MAKEFILE
+    echo NGX_PM_CFLAGS = $NGX_PM_CFLAGS                       >> $NGX_MAKEFILE
+    echo NGX_PM_LDFLAGS = $NGX_PM_LDFLAGS                     >> $NGX_MAKEFILE
+fi
+
+
+# ALL_INCS, required by the addons and by OpenWatcom C precompiled headers
+
+ngx_incs=`echo $CORE_INCS $NGX_OBJS $HTTP_INCS $MAIL_INCS $STREAM_INCS\
+    | sed -e "s/  *\([^ ][^ ]*\)/$ngx_regex_cont$ngx_include_opt\1/g" \
+          -e "s/\//$ngx_regex_dirsep/g"`
+
+cat << END                                                    >> $NGX_MAKEFILE
+
+ALL_INCS = $ngx_include_opt$ngx_incs
+
+END
+
+
+ngx_all_srcs="$CORE_SRCS"
+
+
+# the core dependencies and include paths
+
+ngx_deps=`echo $CORE_DEPS $NGX_AUTO_CONFIG_H $NGX_PCH \
+    | sed -e "s/  *\([^ ][^ ]*\)/$ngx_regex_cont\1/g" \
+          -e "s/\//$ngx_regex_dirsep/g"`
+
+ngx_incs=`echo $CORE_INCS $NGX_OBJS \
+    | sed -e "s/  *\([^ ][^ ]*\)/$ngx_regex_cont$ngx_include_opt\1/g" \
+          -e "s/\//$ngx_regex_dirsep/g"`
+
+
+if [ $NGX_USE_VCL = YES ]; then
+
+cat << END                                                    >> $NGX_MAKEFILE
+
+VPPCOM_LIB_PATH = $VPP_LIB_PATH
+
+
+NGXVCL_INCS = -I $VPP_SRC_PATH
+
+
+NGXVCL_LIBS = -L \$(VPPCOM_LIB_PATH) -l ngxvcl -l vppcom -l vlibmemoryclient -l svm -l vppinfra
+
+END
+
+ngx_incs=$ngx_incs" \\
+       \$(NGXVCL_INCS)"
+
+fi
+
+
+cat << END                                                    >> $NGX_MAKEFILE
+
+CORE_DEPS = $ngx_deps
+
+
+CORE_INCS = $ngx_include_opt$ngx_incs
+
+END
+
+
+# the http dependencies and include paths
+
+if [ $HTTP = YES ]; then
+
+    ngx_all_srcs="$ngx_all_srcs $HTTP_SRCS"
+
+    ngx_deps=`echo $HTTP_DEPS \
+        | sed -e "s/  *\([^ ][^ ]*\)/$ngx_regex_cont\1/g" \
+              -e "s/\//$ngx_regex_dirsep/g"`
+
+    ngx_incs=`echo $HTTP_INCS \
+        | sed -e "s/  *\([^ ][^ ]*\)/$ngx_regex_cont$ngx_include_opt\1/g" \
+              -e "s/\//$ngx_regex_dirsep/g"`
+
+    cat << END                                                >> $NGX_MAKEFILE
+
+HTTP_DEPS = $ngx_deps
+
+
+HTTP_INCS = $ngx_include_opt$ngx_incs
+
+END
+
+fi
+
+
+# the mail dependencies and include paths
+
+if [ $MAIL != NO ]; then
+
+    if [ $MAIL = YES ]; then
+        ngx_all_srcs="$ngx_all_srcs $MAIL_SRCS"
+    fi
+
+    ngx_deps=`echo $MAIL_DEPS \
+        | sed -e "s/  *\([^ ][^ ]*\)/$ngx_regex_cont\1/g" \
+              -e "s/\//$ngx_regex_dirsep/g"`
+
+    ngx_incs=`echo $MAIL_INCS \
+        | sed -e "s/  *\([^ ][^ ]*\)/$ngx_regex_cont$ngx_include_opt\1/g" \
+              -e "s/\//$ngx_regex_dirsep/g"`
+
+    cat << END                                                >> $NGX_MAKEFILE
+
+MAIL_DEPS = $ngx_deps
+
+
+MAIL_INCS = $ngx_include_opt$ngx_incs
+
+END
+
+fi
+
+
+# the stream dependencies and include paths
+
+if [ $STREAM != NO ]; then
+
+    if [ $STREAM = YES ]; then
+        ngx_all_srcs="$ngx_all_srcs $STREAM_SRCS"
+    fi
+
+    ngx_deps=`echo $STREAM_DEPS \
+        | sed -e "s/  *\([^ ][^ ]*\)/$ngx_regex_cont\1/g" \
+              -e "s/\//$ngx_regex_dirsep/g"`
+
+    ngx_incs=`echo $STREAM_INCS \
+        | sed -e "s/  *\([^ ][^ ]*\)/$ngx_regex_cont$ngx_include_opt\1/g" \
+              -e "s/\//$ngx_regex_dirsep/g"`
+
+    cat << END                                                >> $NGX_MAKEFILE
+
+STREAM_DEPS = $ngx_deps
+
+
+STREAM_INCS = $ngx_include_opt$ngx_incs
+
+END
+
+fi
+
+
+ngx_all_srcs="$ngx_all_srcs $MISC_SRCS"
+
+
+if test -n "$NGX_ADDON_SRCS$DYNAMIC_MODULES"; then
+
+cat << END                                                >> $NGX_MAKEFILE
+
+ADDON_DEPS = \$(CORE_DEPS) $NGX_ADDON_DEPS
+
+END
+
+fi
+
+
+# nginx
+
+ngx_all_srcs=`echo $ngx_all_srcs | sed -e "s/\//$ngx_regex_dirsep/g"`
+
+for ngx_src in $NGX_ADDON_SRCS
+do
+    ngx_obj="addon/`basename \`dirname $ngx_src\``"
+
+    test -d $NGX_OBJS/$ngx_obj || mkdir -p $NGX_OBJS/$ngx_obj
+
+    ngx_obj=`echo $ngx_obj/\`basename $ngx_src\` \
+        | sed -e "s/\//$ngx_regex_dirsep/g"`
+
+    ngx_all_srcs="$ngx_all_srcs $ngx_obj"
+done
+
+ngx_all_objs=`echo $ngx_all_srcs \
+    | sed -e "s#\([^ ]*\.\)cpp#$NGX_OBJS\/\1$ngx_objext#g" \
+          -e "s#\([^ ]*\.\)cc#$NGX_OBJS\/\1$ngx_objext#g" \
+          -e "s#\([^ ]*\.\)c#$NGX_OBJS\/\1$ngx_objext#g" \
+          -e "s#\([^ ]*\.\)S#$NGX_OBJS\/\1$ngx_objext#g"`
+
+ngx_modules_c=`echo $NGX_MODULES_C | sed -e "s/\//$ngx_regex_dirsep/g"`
+
+ngx_modules_obj=`echo $ngx_modules_c | sed -e "s/\(.*\.\)c/\1$ngx_objext/"`
+
+
+if test -n "$NGX_RES"; then
+   ngx_res=$NGX_RES
+else
+   ngx_res="$NGX_RC $NGX_ICONS"
+   ngx_rcc=`echo $NGX_RCC | sed -e "s/\//$ngx_regex_dirsep/g"`
+fi
+
+ngx_deps=`echo $ngx_all_objs $ngx_modules_obj $ngx_res $LINK_DEPS \
+    | sed -e "s/  *\([^ ][^ ]*\)/$ngx_regex_cont\1/g" \
+          -e "s/\//$ngx_regex_dirsep/g"`
+
+ngx_objs=`echo $ngx_all_objs $ngx_modules_obj \
+    | sed -e "s/  *\([^ ][^ ]*\)/$ngx_long_regex_cont\1/g" \
+          -e "s/\//$ngx_regex_dirsep/g"`
+
+ngx_libs=
+if test -n "$NGX_LD_OPT$CORE_LIBS"; then
+    ngx_libs=`echo $NGX_LD_OPT $CORE_LIBS \
+        | sed -e "s/\//$ngx_regex_dirsep/g" -e "s/^/$ngx_long_regex_cont/"`
+fi
+
+ngx_link=${CORE_LINK:+`echo $CORE_LINK \
+    | sed -e "s/\//$ngx_regex_dirsep/g" -e "s/^/$ngx_long_regex_cont/"`}
+
+ngx_main_link=${MAIN_LINK:+`echo $MAIN_LINK \
+    | sed -e "s/\//$ngx_regex_dirsep/g" -e "s/^/$ngx_long_regex_cont/"`}
+
+
+ngxvcl_libs=
+if [ $NGX_USE_VCL = YES ]; then
+ngxvcl_libs="\\
+       \$(NGXVCL_LIBS)"
+fi
+
+
+cat << END                                                    >> $NGX_MAKEFILE
+
+build: binary modules manpage
+
+binary:        $NGX_OBJS${ngx_dirsep}nginx$ngx_binext
+
+$NGX_OBJS${ngx_dirsep}nginx$ngx_binext:        $ngx_deps $ngx_spacer
+       \$(LINK) $ngx_long_start$ngx_binout$NGX_OBJS${ngx_dirsep}nginx$ngx_long_cont$ngx_objs$ngx_libs$ngx_link$ngx_main_link $ngxvcl_libs
+       $ngx_rcc
+$ngx_long_end
+
+modules:
+END
+
+
+# ngx_modules.c
+
+if test -n "$NGX_PCH"; then
+    ngx_cc="\$(CC) $ngx_compile_opt \$(CFLAGS) $ngx_use_pch \$(ALL_INCS)"
+else
+    ngx_cc="\$(CC) $ngx_compile_opt \$(CFLAGS) \$(CORE_INCS)"
+fi
+
+cat << END                                                    >> $NGX_MAKEFILE
+
+$ngx_modules_obj:      \$(CORE_DEPS)$ngx_cont$ngx_modules_c
+       $ngx_cc$ngx_tab$ngx_objout$ngx_modules_obj$ngx_tab$ngx_modules_c$NGX_AUX
+
+END
+
+
+# the core sources
+
+for ngx_src in $CORE_SRCS
+do
+    ngx_src=`echo $ngx_src | sed -e "s/\//$ngx_regex_dirsep/g"`
+    ngx_obj=`echo $ngx_src \
+        | sed -e "s#^\(.*\.\)cpp\\$#$ngx_objs_dir\1$ngx_objext#g" \
+              -e "s#^\(.*\.\)cc\\$#$ngx_objs_dir\1$ngx_objext#g" \
+              -e "s#^\(.*\.\)c\\$#$ngx_objs_dir\1$ngx_objext#g" \
+              -e "s#^\(.*\.\)S\\$#$ngx_objs_dir\1$ngx_objext#g"`
+
+    cat << END                                                >> $NGX_MAKEFILE
+
+$ngx_obj:      \$(CORE_DEPS)$ngx_cont$ngx_src
+       $ngx_cc$ngx_tab$ngx_objout$ngx_obj$ngx_tab$ngx_src$NGX_AUX
+
+END
+
+done
+
+
+# the http sources
+
+if [ $HTTP = YES ]; then
+
+    if test -n "$NGX_PCH"; then
+        ngx_cc="\$(CC) $ngx_compile_opt \$(CFLAGS) $ngx_use_pch \$(ALL_INCS)"
+    else
+        ngx_cc="\$(CC) $ngx_compile_opt \$(CFLAGS) \$(CORE_INCS) \$(HTTP_INCS)"
+        ngx_perl_cc="\$(CC) $ngx_compile_opt \$(NGX_PERL_CFLAGS)"
+        ngx_perl_cc="$ngx_perl_cc \$(CORE_INCS) \$(HTTP_INCS)"
+    fi
+
+    for ngx_source in $HTTP_SRCS
+    do
+        ngx_src=`echo $ngx_source | sed -e "s/\//$ngx_regex_dirsep/g"`
+        ngx_obj=`echo $ngx_src \
+            | sed -e "s#^\(.*\.\)cpp\\$#$ngx_objs_dir\1$ngx_objext#g" \
+                  -e "s#^\(.*\.\)cc\\$#$ngx_objs_dir\1$ngx_objext#g" \
+                  -e "s#^\(.*\.\)c\\$#$ngx_objs_dir\1$ngx_objext#g" \
+                  -e "s#^\(.*\.\)S\\$#$ngx_objs_dir\1$ngx_objext#g"`
+
+        if [ $ngx_source = src/http/modules/perl/ngx_http_perl_module.c ]; then
+
+            cat << END                                        >> $NGX_MAKEFILE
+
+$ngx_obj:      \$(CORE_DEPS) \$(HTTP_DEPS)$ngx_cont$ngx_src
+       $ngx_perl_cc$ngx_tab$ngx_objout$ngx_obj$ngx_tab$ngx_src$NGX_AUX
+
+END
+        else
+
+            cat << END                                        >> $NGX_MAKEFILE
+
+$ngx_obj:      \$(CORE_DEPS) \$(HTTP_DEPS)$ngx_cont$ngx_src
+       $ngx_cc$ngx_tab$ngx_objout$ngx_obj$ngx_tab$ngx_src$NGX_AUX
+
+END
+
+        fi
+     done
+
+fi
+
+
+# the mail sources
+
+if [ $MAIL = YES ]; then
+
+    if test -n "$NGX_PCH"; then
+        ngx_cc="\$(CC) $ngx_compile_opt \$(CFLAGS) $ngx_use_pch \$(ALL_INCS)"
+    else
+        ngx_cc="\$(CC) $ngx_compile_opt \$(CFLAGS) \$(CORE_INCS) \$(MAIL_INCS)"
+    fi
+
+    for ngx_src in $MAIL_SRCS
+    do
+        ngx_src=`echo $ngx_src | sed -e "s/\//$ngx_regex_dirsep/g"`
+        ngx_obj=`echo $ngx_src \
+            | sed -e "s#^\(.*\.\)cpp\\$#$ngx_objs_dir\1$ngx_objext#g" \
+                  -e "s#^\(.*\.\)cc\\$#$ngx_objs_dir\1$ngx_objext#g" \
+                  -e "s#^\(.*\.\)c\\$#$ngx_objs_dir\1$ngx_objext#g" \
+                  -e "s#^\(.*\.\)S\\$#$ngx_objs_dir\1$ngx_objext#g"`
+
+        cat << END                                            >> $NGX_MAKEFILE
+
+$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
+
+fi
+
+
+# the stream sources
+
+if [ $STREAM = YES ]; then
+
+    if test -n "$NGX_PCH"; then
+        ngx_cc="\$(CC) $ngx_compile_opt \$(CFLAGS) $ngx_use_pch \$(ALL_INCS)"
+    else
+        ngx_cc="\$(CC) $ngx_compile_opt \$(CFLAGS) \$(CORE_INCS) \$(STREAM_INCS)"
+    fi
+
+    for ngx_src in $STREAM_SRCS
+    do
+        ngx_src=`echo $ngx_src | sed -e "s/\//$ngx_regex_dirsep/g"`
+        ngx_obj=`echo $ngx_src \
+            | sed -e "s#^\(.*\.\)cpp\\$#$ngx_objs_dir\1$ngx_objext#g" \
+                  -e "s#^\(.*\.\)cc\\$#$ngx_objs_dir\1$ngx_objext#g" \
+                  -e "s#^\(.*\.\)c\\$#$ngx_objs_dir\1$ngx_objext#g" \
+                  -e "s#^\(.*\.\)S\\$#$ngx_objs_dir\1$ngx_objext#g"`
+
+        cat << END                                            >> $NGX_MAKEFILE
+
+$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
+
+fi
+
+
+# the misc sources
+
+if test -n "$MISC_SRCS"; then
+
+    ngx_cc="\$(CC) $ngx_compile_opt \$(CFLAGS) $ngx_use_pch \$(ALL_INCS)"
+
+    for ngx_src in $MISC_SRCS
+    do
+        ngx_src=`echo $ngx_src | sed -e "s/\//$ngx_regex_dirsep/g"`
+        ngx_obj=`echo $ngx_src \
+            | sed -e "s#^\(.*\.\)cpp\\$#$ngx_objs_dir\1$ngx_objext#g" \
+                  -e "s#^\(.*\.\)cc\\$#$ngx_objs_dir\1$ngx_objext#g" \
+                  -e "s#^\(.*\.\)c\\$#$ngx_objs_dir\1$ngx_objext#g" \
+                  -e "s#^\(.*\.\)S\\$#$ngx_objs_dir\1$ngx_objext#g"`
+
+        cat << END                                            >> $NGX_MAKEFILE
+
+$ngx_obj:      \$(CORE_DEPS) $ngx_cont$ngx_src
+       $ngx_cc$ngx_tab$ngx_objout$ngx_obj$ngx_tab$ngx_src$NGX_AUX
+
+END
+     done
+
+fi
+
+
+# the addons sources
+
+if test -n "$NGX_ADDON_SRCS"; then
+
+    ngx_cc="\$(CC) $ngx_compile_opt \$(CFLAGS) $ngx_use_pch \$(ALL_INCS)"
+
+    for ngx_src in $NGX_ADDON_SRCS
+    do
+        ngx_obj="addon/`basename \`dirname $ngx_src\``"
+
+        ngx_obj=`echo $ngx_obj/\`basename $ngx_src\` \
+            | sed -e "s/\//$ngx_regex_dirsep/g"`
+
+        ngx_obj=`echo $ngx_obj \
+            | sed -e "s#^\(.*\.\)cpp\\$#$ngx_objs_dir\1$ngx_objext#g" \
+                  -e "s#^\(.*\.\)cc\\$#$ngx_objs_dir\1$ngx_objext#g" \
+                  -e "s#^\(.*\.\)c\\$#$ngx_objs_dir\1$ngx_objext#g" \
+                  -e "s#^\(.*\.\)S\\$#$ngx_objs_dir\1$ngx_objext#g"`
+
+        ngx_src=`echo $ngx_src | sed -e "s/\//$ngx_regex_dirsep/g"`
+
+        cat << END                                            >> $NGX_MAKEFILE
+
+$ngx_obj:      \$(ADDON_DEPS)$ngx_cont$ngx_src
+       $ngx_cc$ngx_tab$ngx_objout$ngx_obj$ngx_tab$ngx_src$NGX_AUX
+
+END
+     done
+
+fi
+
+
+# the addons config.make
+
+if test -n "$NGX_ADDONS$DYNAMIC_ADDONS"; then
+
+    for ngx_addon_dir in $NGX_ADDONS $DYNAMIC_ADDONS
+    do
+        if test -f $ngx_addon_dir/config.make; then
+            . $ngx_addon_dir/config.make
+        fi
+    done
+fi
+
+
+# Win32 resource file
+
+if test -n "$NGX_RES"; then
+
+    ngx_res=`echo "$NGX_RES:   $NGX_RC $NGX_ICONS" \
+                 | sed -e "s/\//$ngx_regex_dirsep/g"`
+    ngx_rcc=`echo $NGX_RCC | sed -e "s/\//$ngx_regex_dirsep/g"`
+
+    cat << END                                                >> $NGX_MAKEFILE
+
+$ngx_res
+       $ngx_rcc
+
+END
+
+fi
+
+
+# the precompiled headers
+
+if test -n "$NGX_PCH"; then
+    echo "#include <ngx_config.h>" > $NGX_OBJS/ngx_pch.c
+
+    ngx_pch="src/core/ngx_config.h $OS_CONFIG $NGX_OBJS/ngx_auto_config.h"
+    ngx_pch=`echo "$NGX_PCH:   $ngx_pch" | sed -e "s/\//$ngx_regex_dirsep/g"`
+
+    ngx_src="\$(CC) \$(CFLAGS) $NGX_BUILD_PCH $ngx_compile_opt \$(ALL_INCS)"
+    ngx_src="$ngx_src $ngx_objout$NGX_OBJS/ngx_pch.obj $NGX_OBJS/ngx_pch.c"
+    ngx_src=`echo $ngx_src | sed -e "s/\//$ngx_regex_dirsep/g"`
+
+    cat << END                                                >> $NGX_MAKEFILE
+
+$ngx_pch
+       $ngx_src
+
+END
+
+fi
+
+
+# dynamic modules
+
+if test -n "$NGX_PCH"; then
+    ngx_cc="\$(CC) $ngx_compile_opt $ngx_pic_opt \$(CFLAGS) $ngx_use_pch \$(ALL_INCS)"
+else
+    ngx_cc="\$(CC) $ngx_compile_opt $ngx_pic_opt \$(CFLAGS) \$(ALL_INCS)"
+    ngx_perl_cc="\$(CC) $ngx_compile_opt $ngx_pic_opt \$(NGX_PERL_CFLAGS)"
+    ngx_perl_cc="$ngx_perl_cc \$(ALL_INCS)"
+fi
+
+for ngx_module in $DYNAMIC_MODULES
+do
+    eval ngx_module_srcs="\$${ngx_module}_SRCS"
+    eval eval ngx_module_libs="\\\"\$${ngx_module}_LIBS\\\""
+
+    eval ngx_module_modules="\$${ngx_module}_MODULES"
+    eval ngx_module_order="\$${ngx_module}_ORDER"
+
+    ngx_modules_c=$NGX_OBJS/${ngx_module}_modules.c
+
+    cat << END                                    > $ngx_modules_c
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+END
+
+    for mod in $ngx_module_modules
+    do
+        echo "extern ngx_module_t  $mod;"         >> $ngx_modules_c
+    done
+
+    echo                                          >> $ngx_modules_c
+    echo 'ngx_module_t *ngx_modules[] = {'        >> $ngx_modules_c
+
+    for mod in $ngx_module_modules
+    do
+        echo "    &$mod,"                         >> $ngx_modules_c
+    done
+
+    cat << END                                    >> $ngx_modules_c
+    NULL
+};
+
+END
+
+    echo 'char *ngx_module_names[] = {'           >> $ngx_modules_c
+
+    for mod in $ngx_module_modules
+    do
+        echo "    \"$mod\","                      >> $ngx_modules_c
+    done
+
+    cat << END                                    >> $ngx_modules_c
+    NULL
+};
+
+END
+
+    echo 'char *ngx_module_order[] = {'           >> $ngx_modules_c
+
+    for mod in $ngx_module_order
+    do
+        echo "    \"$mod\","                      >> $ngx_modules_c
+    done
+
+    cat << END                                    >> $ngx_modules_c
+    NULL
+};
+
+END
+
+    ngx_modules_c=`echo $ngx_modules_c | sed -e "s/\//$ngx_regex_dirsep/g"`
+
+    ngx_modules_obj=`echo $ngx_modules_c \
+        | sed -e "s/\(.*\.\)c/\1$ngx_objext/"`
+
+    ngx_module_objs=
+    for ngx_src in $ngx_module_srcs
+    do
+        case "$ngx_src" in
+            src/*)
+                ngx_obj=$ngx_src
+                ;;
+            *)
+                ngx_obj="addon/`basename \`dirname $ngx_src\``"
+                mkdir -p $NGX_OBJS/$ngx_obj
+                ngx_obj="$ngx_obj/`basename $ngx_src`"
+                ;;
+        esac
+
+        ngx_module_objs="$ngx_module_objs $ngx_obj"
+    done
+
+    ngx_module_objs=`echo $ngx_module_objs \
+        | sed -e "s#\([^ ]*\.\)cpp#$NGX_OBJS\/\1$ngx_objext#g" \
+              -e "s#\([^ ]*\.\)cc#$NGX_OBJS\/\1$ngx_objext#g" \
+              -e "s#\([^ ]*\.\)c#$NGX_OBJS\/\1$ngx_objext#g" \
+              -e "s#\([^ ]*\.\)S#$NGX_OBJS\/\1$ngx_objext#g"`
+
+    ngx_deps=`echo $ngx_module_objs $ngx_modules_obj $LINK_DEPS \
+        | sed -e "s/  *\([^ ][^ ]*\)/$ngx_regex_cont\1/g" \
+              -e "s/\//$ngx_regex_dirsep/g"`
+
+    ngx_objs=`echo $ngx_module_objs $ngx_modules_obj \
+        | sed -e "s/  *\([^ ][^ ]*\)/$ngx_long_regex_cont\1/g" \
+              -e "s/\//$ngx_regex_dirsep/g"`
+
+    ngx_obj=$NGX_OBJS$ngx_dirsep$ngx_module$ngx_modext
+
+    if [ "$NGX_PLATFORM" = win32 ]; then
+        ngx_module_libs="$CORE_LIBS $ngx_module_libs"
+    fi
+
+    ngx_libs=
+    if test -n "$NGX_LD_OPT$ngx_module_libs"; then
+        ngx_libs=`echo $NGX_LD_OPT $ngx_module_libs \
+            | sed -e "s/\//$ngx_regex_dirsep/g" -e "s/^/$ngx_long_regex_cont/"`
+    fi
+
+    ngx_link=${CORE_LINK:+`echo $CORE_LINK \
+        | sed -e "s/\//$ngx_regex_dirsep/g" -e "s/^/$ngx_long_regex_cont/"`}
+
+    ngx_module_link=${MODULE_LINK:+`echo $MODULE_LINK \
+        | sed -e "s/\//$ngx_regex_dirsep/g" -e "s/^/$ngx_long_regex_cont/"`}
+
+
+    cat << END                                            >> $NGX_MAKEFILE
+
+modules:       $ngx_obj
+
+$ngx_obj:      $ngx_deps$ngx_spacer
+       \$(LINK) $ngx_long_start$ngx_binout$ngx_obj$ngx_long_cont$ngx_objs$ngx_libs$ngx_link$ngx_module_link
+$ngx_long_end
+
+$ngx_modules_obj:      \$(CORE_DEPS)$ngx_cont$ngx_modules_c
+       $ngx_cc$ngx_tab$ngx_objout$ngx_modules_obj$ngx_tab$ngx_modules_c$NGX_AUX
+
+END
+
+    for ngx_source in $ngx_module_srcs
+    do
+        case "$ngx_source" in
+            src/*)
+                ngx_obj=`echo $ngx_source | sed -e "s/\//$ngx_regex_dirsep/g"`
+                ;;
+            *)
+                ngx_obj="addon/`basename \`dirname $ngx_source\``"
+                ngx_obj=`echo $ngx_obj/\`basename $ngx_source\` \
+                    | sed -e "s/\//$ngx_regex_dirsep/g"`
+                ;;
+        esac
+
+        ngx_obj=`echo $ngx_obj \
+            | sed -e "s#^\(.*\.\)cpp\\$#$ngx_objs_dir\1$ngx_objext#g" \
+                  -e "s#^\(.*\.\)cc\\$#$ngx_objs_dir\1$ngx_objext#g" \
+                  -e "s#^\(.*\.\)c\\$#$ngx_objs_dir\1$ngx_objext#g" \
+                  -e "s#^\(.*\.\)S\\$#$ngx_objs_dir\1$ngx_objext#g"`
+
+        ngx_src=`echo $ngx_source | sed -e "s/\//$ngx_regex_dirsep/g"`
+
+        if [ $ngx_source = src/http/modules/perl/ngx_http_perl_module.c ]; then
+
+            cat << END                                        >> $NGX_MAKEFILE
+
+$ngx_obj:      \$(ADDON_DEPS)$ngx_cont$ngx_src
+       $ngx_perl_cc$ngx_tab$ngx_objout$ngx_obj$ngx_tab$ngx_src$NGX_AUX
+
+END
+        else
+
+            cat << END                                        >> $NGX_MAKEFILE
+
+$ngx_obj:      \$(ADDON_DEPS)$ngx_cont$ngx_src
+       $ngx_cc$ngx_tab$ngx_objout$ngx_obj$ngx_tab$ngx_src$NGX_AUX
+
+END
+
+        fi
+    done
+done
diff --git a/nginx/auto/module b/nginx/auto/module
new file mode 100644 (file)
index 0000000..a2b578d
--- /dev/null
@@ -0,0 +1,138 @@
+
+# Copyright (C) Ruslan Ermilov
+# Copyright (C) Nginx, Inc.
+
+
+case $ngx_module_type in
+    HTTP_*) ngx_var=HTTP ;;
+    *)      ngx_var=$ngx_module_type ;;
+esac
+
+
+if [ "$ngx_module_link" = DYNAMIC ]; then
+
+    for ngx_module in $ngx_module_name; do
+        # extract the first name
+        break
+    done
+
+    DYNAMIC_MODULES="$DYNAMIC_MODULES $ngx_module"
+    eval ${ngx_module}_SRCS=\"$ngx_module_srcs\"
+
+    eval ${ngx_module}_MODULES=\"$ngx_module_name\"
+
+    if [ -z "$ngx_module_order" -a \
+         \( "$ngx_module_type" = "HTTP_FILTER" \
+         -o "$ngx_module_type" = "HTTP_AUX_FILTER" \) ]
+    then
+        eval ${ngx_module}_ORDER=\"$ngx_module_name \
+                                   ngx_http_copy_filter_module\"
+    else
+        eval ${ngx_module}_ORDER=\"$ngx_module_order\"
+    fi
+
+    if test -n "$ngx_module_incs"; then
+        CORE_INCS="$CORE_INCS $ngx_module_incs"
+    fi
+
+    if test -n "$ngx_module_deps"; then
+        NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_module_deps"
+    fi
+
+    libs=
+    for lib in $ngx_module_libs
+    do
+        case $lib in
+
+            LIBXSLT | LIBGD | GEOIP | PERL)
+                libs="$libs \$NGX_LIB_$lib"
+
+                if eval [ "\$USE_${lib}" = NO ] ; then
+                    eval USE_${lib}=DYNAMIC
+                fi
+            ;;
+
+            PCRE | OPENSSL | ZLIB)
+                eval USE_${lib}=YES
+            ;;
+
+            MD5 | SHA1)
+                # obsolete
+            ;;
+
+            *)
+                libs="$libs $lib"
+            ;;
+
+        esac
+    done
+    eval ${ngx_module}_LIBS=\'$libs\'
+
+elif [ "$ngx_module_link" = YES ]; then
+
+    eval ${ngx_module_type}_MODULES=\"\$${ngx_module_type}_MODULES \
+                                      $ngx_module_name\"
+
+    eval ${ngx_var}_SRCS=\"\$${ngx_var}_SRCS $ngx_module_srcs\"
+
+    if test -n "$ngx_module_incs"; then
+        eval ${ngx_var}_INCS=\"\$${ngx_var}_INCS $ngx_module_incs\"
+    fi
+
+    if test -n "$ngx_module_deps"; then
+        eval ${ngx_var}_DEPS=\"\$${ngx_var}_DEPS $ngx_module_deps\"
+    fi
+
+    for lib in $ngx_module_libs
+    do
+        case $lib in
+
+            PCRE | OPENSSL | ZLIB | LIBXSLT | LIBGD | PERL | GEOIP)
+                eval USE_${lib}=YES
+            ;;
+
+            MD5 | SHA1)
+                # obsolete
+            ;;
+
+            *)
+                CORE_LIBS="$CORE_LIBS $lib"
+            ;;
+
+        esac
+    done
+
+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"
+
+    if test -n "$ngx_module_incs"; then
+        eval ${ngx_var}_INCS=\"\$${ngx_var}_INCS $ngx_module_incs\"
+    fi
+
+    if test -n "$ngx_module_deps"; then
+        NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_module_deps"
+    fi
+
+    for lib in $ngx_module_libs
+    do
+        case $lib in
+
+            PCRE | OPENSSL | ZLIB | LIBXSLT | LIBGD | PERL | GEOIP)
+                eval USE_${lib}=YES
+            ;;
+
+            MD5 | SHA1)
+                # obsolete
+            ;;
+
+            *)
+                CORE_LIBS="$CORE_LIBS $lib"
+            ;;
+
+        esac
+    done
+fi
diff --git a/nginx/auto/modules b/nginx/auto/modules
new file mode 100644 (file)
index 0000000..73a9bae
--- /dev/null
@@ -0,0 +1,1400 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+if [ $EVENT_SELECT = NO -a $EVENT_FOUND = NO ]; then
+    EVENT_SELECT=YES
+fi
+
+if [ $EVENT_SELECT = YES ]; then
+    have=NGX_HAVE_SELECT . auto/have
+    CORE_SRCS="$CORE_SRCS $SELECT_SRCS"
+    EVENT_MODULES="$EVENT_MODULES $SELECT_MODULE"
+fi
+
+
+if [ $EVENT_POLL = NO -a $EVENT_FOUND = NO ]; then
+    EVENT_POLL=YES
+fi
+
+if [ $EVENT_POLL = YES ]; then
+    have=NGX_HAVE_POLL . auto/have
+    CORE_SRCS="$CORE_SRCS $POLL_SRCS"
+    EVENT_MODULES="$EVENT_MODULES $POLL_MODULE"
+fi
+
+
+if [ $NGX_TEST_BUILD_DEVPOLL = YES ]; then
+    have=NGX_HAVE_DEVPOLL . auto/have
+    have=NGX_TEST_BUILD_DEVPOLL . auto/have
+    EVENT_MODULES="$EVENT_MODULES $DEVPOLL_MODULE"
+    CORE_SRCS="$CORE_SRCS $DEVPOLL_SRCS"
+fi
+
+
+if [ $NGX_TEST_BUILD_EVENTPORT = YES ]; then
+    have=NGX_HAVE_EVENTPORT . auto/have
+    have=NGX_TEST_BUILD_EVENTPORT . auto/have
+    EVENT_MODULES="$EVENT_MODULES $EVENTPORT_MODULE"
+    CORE_SRCS="$CORE_SRCS $EVENTPORT_SRCS"
+fi
+
+if [ $NGX_TEST_BUILD_EPOLL = YES ]; then
+    have=NGX_HAVE_EPOLL . auto/have
+    have=NGX_HAVE_EPOLLRDHUP . auto/have
+    have=NGX_HAVE_EPOLLEXCLUSIVE . auto/have
+    have=NGX_HAVE_EVENTFD . auto/have
+    have=NGX_TEST_BUILD_EPOLL . auto/have
+    EVENT_MODULES="$EVENT_MODULES $EPOLL_MODULE"
+    CORE_SRCS="$CORE_SRCS $EPOLL_SRCS"
+fi
+
+if [ $NGX_TEST_BUILD_SOLARIS_SENDFILEV = YES ]; then
+    have=NGX_TEST_BUILD_SOLARIS_SENDFILEV . auto/have
+    CORE_SRCS="$CORE_SRCS $SOLARIS_SENDFILEV_SRCS"
+fi
+
+
+if [ $HTTP = YES ]; then
+    HTTP_MODULES=
+    HTTP_DEPS=
+    HTTP_INCS=
+
+    ngx_module_type=HTTP
+
+    if :; then
+        ngx_module_name="ngx_http_module \
+                         ngx_http_core_module \
+                         ngx_http_log_module \
+                         ngx_http_upstream_module"
+        ngx_module_incs="src/http src/http/modules"
+        ngx_module_deps="src/http/ngx_http.h \
+                         src/http/ngx_http_request.h \
+                         src/http/ngx_http_config.h \
+                         src/http/ngx_http_core_module.h \
+                         src/http/ngx_http_cache.h \
+                         src/http/ngx_http_variables.h \
+                         src/http/ngx_http_script.h \
+                         src/http/ngx_http_upstream.h \
+                         src/http/ngx_http_upstream_round_robin.h"
+        ngx_module_srcs="src/http/ngx_http.c \
+                         src/http/ngx_http_core_module.c \
+                         src/http/ngx_http_special_response.c \
+                         src/http/ngx_http_request.c \
+                         src/http/ngx_http_parse.c \
+                         src/http/modules/ngx_http_log_module.c \
+                         src/http/ngx_http_request_body.c \
+                         src/http/ngx_http_variables.c \
+                         src/http/ngx_http_script.c \
+                         src/http/ngx_http_upstream.c \
+                         src/http/ngx_http_upstream_round_robin.c"
+        ngx_module_libs=
+        ngx_module_link=YES
+
+        . auto/module
+    fi
+
+
+    if [ $HTTP_CACHE = YES ]; then
+        have=NGX_HTTP_CACHE . auto/have
+        HTTP_SRCS="$HTTP_SRCS $HTTP_FILE_CACHE_SRCS"
+    fi
+
+
+    if [ $HTTP_SSI = YES ]; then
+        HTTP_POSTPONE=YES
+    fi
+
+
+    if [ $HTTP_SLICE = YES ]; then
+        HTTP_POSTPONE=YES
+    fi
+
+
+    if [ $HTTP_ADDITION = YES ]; then
+        HTTP_POSTPONE=YES
+    fi
+
+
+    # the module order is important
+    #     ngx_http_static_module
+    #     ngx_http_gzip_static_module
+    #     ngx_http_dav_module
+    #     ngx_http_autoindex_module
+    #     ngx_http_index_module
+    #     ngx_http_random_index_module
+    #
+    #     ngx_http_access_module
+    #     ngx_http_realip_module
+    #
+    #
+    # the filter order is important
+    #     ngx_http_write_filter
+    #     ngx_http_header_filter
+    #     ngx_http_chunked_filter
+    #     ngx_http_v2_filter
+    #     ngx_http_range_header_filter
+    #     ngx_http_gzip_filter
+    #     ngx_http_postpone_filter
+    #     ngx_http_ssi_filter
+    #     ngx_http_charset_filter
+    #         ngx_http_xslt_filter
+    #         ngx_http_image_filter
+    #         ngx_http_sub_filter
+    #         ngx_http_addition_filter
+    #         ngx_http_gunzip_filter
+    #         ngx_http_userid_filter
+    #         ngx_http_headers_filter
+    #     ngx_http_copy_filter
+    #     ngx_http_range_body_filter
+    #     ngx_http_not_modified_filter
+    #     ngx_http_slice_filter
+
+    ngx_module_type=HTTP_FILTER
+    HTTP_FILTER_MODULES=
+
+    ngx_module_order="ngx_http_static_module \
+                      ngx_http_gzip_static_module \
+                      ngx_http_dav_module \
+                      ngx_http_autoindex_module \
+                      ngx_http_index_module \
+                      ngx_http_random_index_module \
+                      ngx_http_access_module \
+                      ngx_http_realip_module \
+                      ngx_http_write_filter_module \
+                      ngx_http_header_filter_module \
+                      ngx_http_chunked_filter_module \
+                      ngx_http_v2_filter_module \
+                      ngx_http_range_header_filter_module \
+                      ngx_http_gzip_filter_module \
+                      ngx_http_postpone_filter_module \
+                      ngx_http_ssi_filter_module \
+                      ngx_http_charset_filter_module \
+                      ngx_http_xslt_filter_module \
+                      ngx_http_image_filter_module \
+                      ngx_http_sub_filter_module \
+                      ngx_http_addition_filter_module \
+                      ngx_http_gunzip_filter_module \
+                      ngx_http_userid_filter_module \
+                      ngx_http_headers_filter_module \
+                      ngx_http_copy_filter_module \
+                      ngx_http_range_body_filter_module \
+                      ngx_http_not_modified_filter_module \
+                      ngx_http_slice_filter_module"
+
+    if :; then
+        ngx_module_name=ngx_http_write_filter_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/ngx_http_write_filter_module.c
+        ngx_module_libs=
+        ngx_module_link=YES
+
+        . auto/module
+    fi
+
+    if :; then
+        ngx_module_name=ngx_http_header_filter_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/ngx_http_header_filter_module.c
+        ngx_module_libs=
+        ngx_module_link=YES
+
+        . auto/module
+    fi
+
+    if :; then
+        ngx_module_name=ngx_http_chunked_filter_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_chunked_filter_module.c
+        ngx_module_libs=
+        ngx_module_link=YES
+
+        . auto/module
+    fi
+
+    if [ $HTTP_V2 = YES ]; then
+        ngx_module_name=ngx_http_v2_filter_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/v2/ngx_http_v2_filter_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_V2
+
+        . auto/module
+    fi
+
+    if :; then
+        ngx_module_name=ngx_http_range_header_filter_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_range_filter_module.c
+        ngx_module_libs=
+        ngx_module_link=YES
+
+        . auto/module
+    fi
+
+    if [ $HTTP_GZIP = YES ]; then
+        have=NGX_HTTP_GZIP . auto/have
+        USE_ZLIB=YES
+
+        ngx_module_name=ngx_http_gzip_filter_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_gzip_filter_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_GZIP
+
+        . auto/module
+    fi
+
+    if [ $HTTP_POSTPONE = YES ]; then
+        ngx_module_name=ngx_http_postpone_filter_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/ngx_http_postpone_filter_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_POSTPONE
+
+        . auto/module
+    fi
+
+    if [ $HTTP_SSI = YES ]; then
+        have=NGX_HTTP_SSI . auto/have
+
+        ngx_module_name=ngx_http_ssi_filter_module
+        ngx_module_incs=
+        ngx_module_deps=src/http/modules/ngx_http_ssi_filter_module.h
+        ngx_module_srcs=src/http/modules/ngx_http_ssi_filter_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_SSI
+
+        . auto/module
+    fi
+
+    if [ $HTTP_CHARSET = YES ]; then
+        ngx_module_name=ngx_http_charset_filter_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_charset_filter_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_CHARSET
+
+        . auto/module
+    fi
+
+    if [ $HTTP_XSLT != NO ]; then
+        ngx_module_name=ngx_http_xslt_filter_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_xslt_filter_module.c
+        ngx_module_libs=LIBXSLT
+        ngx_module_link=$HTTP_XSLT
+
+        . auto/module
+    fi
+
+    if [ $HTTP_IMAGE_FILTER != NO ]; then
+        ngx_module_name=ngx_http_image_filter_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_image_filter_module.c
+        ngx_module_libs=LIBGD
+        ngx_module_link=$HTTP_IMAGE_FILTER
+
+        . auto/module
+    fi
+
+    if [ $HTTP_SUB = YES ]; then
+        ngx_module_name=ngx_http_sub_filter_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_sub_filter_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_SUB
+
+        . auto/module
+    fi
+
+    if [ $HTTP_ADDITION = YES ]; then
+        ngx_module_name=ngx_http_addition_filter_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_addition_filter_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_ADDITION
+
+        . auto/module
+    fi
+
+    if [ $HTTP_GUNZIP = YES ]; then
+        have=NGX_HTTP_GZIP . auto/have
+        USE_ZLIB=YES
+
+        ngx_module_name=ngx_http_gunzip_filter_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_gunzip_filter_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_GUNZIP
+
+        . auto/module
+    fi
+
+    if [ $HTTP_USERID = YES ]; then
+        ngx_module_name=ngx_http_userid_filter_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_userid_filter_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_USERID
+
+        . auto/module
+    fi
+
+    if :; then
+        ngx_module_name=ngx_http_headers_filter_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_headers_filter_module.c
+        ngx_module_libs=
+        ngx_module_link=YES
+
+        . auto/module
+    fi
+
+
+    ngx_module_type=HTTP_INIT_FILTER
+    HTTP_INIT_FILTER_MODULES=
+
+    if :; then
+        ngx_module_name=ngx_http_copy_filter_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/ngx_http_copy_filter_module.c
+        ngx_module_libs=
+        ngx_module_link=YES
+
+        . auto/module
+    fi
+
+    if :; then
+        ngx_module_name=ngx_http_range_body_filter_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=
+        ngx_module_libs=
+        ngx_module_link=YES
+
+        . auto/module
+    fi
+
+    if :; then
+        ngx_module_name=ngx_http_not_modified_filter_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_not_modified_filter_module.c
+        ngx_module_libs=
+        ngx_module_link=YES
+
+        . auto/module
+    fi
+
+    if [ $HTTP_SLICE = YES ]; then
+        ngx_module_name=ngx_http_slice_filter_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_slice_filter_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_SLICE
+
+        . auto/module
+    fi
+
+
+    ngx_module_type=HTTP
+
+    if [ $HTTP_V2 = YES ]; then
+        have=NGX_HTTP_V2 . auto/have
+        have=NGX_HTTP_HEADERS . auto/have
+
+        ngx_module_name=ngx_http_v2_module
+        ngx_module_incs=src/http/v2
+        ngx_module_deps="src/http/v2/ngx_http_v2.h \
+                         src/http/v2/ngx_http_v2_module.h"
+        ngx_module_srcs="src/http/v2/ngx_http_v2.c \
+                         src/http/v2/ngx_http_v2_table.c \
+                         src/http/v2/ngx_http_v2_encode.c \
+                         src/http/v2/ngx_http_v2_huff_decode.c \
+                         src/http/v2/ngx_http_v2_huff_encode.c \
+                         src/http/v2/ngx_http_v2_module.c"
+        ngx_module_libs=
+        ngx_module_link=$HTTP_V2
+
+        . auto/module
+    fi
+
+    if :; then
+        ngx_module_name=ngx_http_static_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_static_module.c
+        ngx_module_libs=
+        ngx_module_link=YES
+
+        . auto/module
+    fi
+
+    if [ $HTTP_GZIP_STATIC = YES ]; then
+        have=NGX_HTTP_GZIP . auto/have
+
+        ngx_module_name=ngx_http_gzip_static_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_gzip_static_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_GZIP_STATIC
+
+        . auto/module
+    fi
+
+    if [ $HTTP_DAV = YES ]; then
+        have=NGX_HTTP_DAV . auto/have
+
+        ngx_module_name=ngx_http_dav_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_dav_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_DAV
+
+        . auto/module
+    fi
+
+    if [ $HTTP_AUTOINDEX = YES ]; then
+        ngx_module_name=ngx_http_autoindex_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_autoindex_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_AUTOINDEX
+
+        . auto/module
+    fi
+
+    if :; then
+        ngx_module_name=ngx_http_index_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_index_module.c
+        ngx_module_libs=
+        ngx_module_link=YES
+
+        . auto/module
+    fi
+
+    if [ $HTTP_RANDOM_INDEX = YES ]; then
+        ngx_module_name=ngx_http_random_index_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_random_index_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_RANDOM_INDEX
+
+        . auto/module
+    fi
+
+    if [ $HTTP_MIRROR = YES ]; then
+        ngx_module_name=ngx_http_mirror_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_mirror_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_MIRROR
+
+        . auto/module
+    fi
+
+    if :; then
+        ngx_module_name=ngx_http_try_files_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_try_files_module.c
+        ngx_module_libs=
+        ngx_module_link=YES
+
+        . auto/module
+    fi
+
+    if [ $HTTP_AUTH_REQUEST = YES ]; then
+        ngx_module_name=ngx_http_auth_request_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_auth_request_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_AUTH_REQUEST
+
+        . auto/module
+    fi
+
+    if [ $HTTP_AUTH_BASIC = YES ]; then
+        have=NGX_CRYPT . auto/have
+
+        ngx_module_name=ngx_http_auth_basic_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_auth_basic_module.c
+        ngx_module_libs=$CRYPT_LIB
+        ngx_module_link=$HTTP_AUTH_BASIC
+
+        . auto/module
+    fi
+
+    if [ $HTTP_ACCESS = YES ]; then
+        ngx_module_name=ngx_http_access_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_access_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_ACCESS
+
+        . auto/module
+    fi
+
+    if [ $HTTP_LIMIT_CONN = YES ]; then
+        ngx_module_name=ngx_http_limit_conn_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_limit_conn_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_LIMIT_CONN
+
+        . auto/module
+    fi
+
+    if [ $HTTP_LIMIT_REQ = YES ]; then
+        ngx_module_name=ngx_http_limit_req_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_limit_req_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_LIMIT_REQ
+
+        . auto/module
+    fi
+
+    if [ $HTTP_REALIP = YES ]; then
+        have=NGX_HTTP_REALIP . auto/have
+        have=NGX_HTTP_X_FORWARDED_FOR . auto/have
+
+        ngx_module_name=ngx_http_realip_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_realip_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_REALIP
+
+        . auto/module
+    fi
+
+    if [ $HTTP_STATUS = YES ]; then
+        ngx_module_name=ngx_http_status_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_status_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_STATUS
+
+        . auto/module
+    fi
+
+    if [ $HTTP_GEO = YES ]; then
+        have=NGX_HTTP_X_FORWARDED_FOR . auto/have
+
+        ngx_module_name=ngx_http_geo_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_geo_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_GEO
+
+        . auto/module
+    fi
+
+    if [ $HTTP_GEOIP != NO ]; then
+        have=NGX_HTTP_X_FORWARDED_FOR . auto/have
+
+        ngx_module_name=ngx_http_geoip_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_geoip_module.c
+        ngx_module_libs=GEOIP
+        ngx_module_link=$HTTP_GEOIP
+
+        . auto/module
+    fi
+
+    if [ $HTTP_MAP = YES ]; then
+        ngx_module_name=ngx_http_map_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_map_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_MAP
+
+        . auto/module
+    fi
+
+    if [ $HTTP_SPLIT_CLIENTS = YES ]; then
+        ngx_module_name=ngx_http_split_clients_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_split_clients_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_SPLIT_CLIENTS
+
+        . auto/module
+    fi
+
+    if [ $HTTP_REFERER = YES ]; then
+        ngx_module_name=ngx_http_referer_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_referer_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_REFERER
+
+        . auto/module
+    fi
+
+    if [ $HTTP_REWRITE = YES -a $USE_PCRE != DISABLED ]; then
+        USE_PCRE=YES
+
+        ngx_module_name=ngx_http_rewrite_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_rewrite_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_REWRITE
+
+        . auto/module
+    fi
+
+    if [ $HTTP_SSL = YES ]; then
+        USE_OPENSSL=YES
+        have=NGX_HTTP_SSL . auto/have
+
+        ngx_module_name=ngx_http_ssl_module
+        ngx_module_incs=
+        ngx_module_deps=src/http/modules/ngx_http_ssl_module.h
+        ngx_module_srcs=src/http/modules/ngx_http_ssl_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_SSL
+
+        . auto/module
+    fi
+
+    if [ $HTTP_PROXY = YES ]; then
+        have=NGX_HTTP_X_FORWARDED_FOR . auto/have
+
+        ngx_module_name=ngx_http_proxy_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_proxy_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_PROXY
+
+        . auto/module
+    fi
+
+    if [ $HTTP_FASTCGI = YES ]; then
+        ngx_module_name=ngx_http_fastcgi_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_fastcgi_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_FASTCGI
+
+        . auto/module
+    fi
+
+    if [ $HTTP_UWSGI = YES ]; then
+        ngx_module_name=ngx_http_uwsgi_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_uwsgi_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_UWSGI
+
+        . auto/module
+    fi
+
+    if [ $HTTP_SCGI = YES ]; then
+        ngx_module_name=ngx_http_scgi_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_scgi_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_SCGI
+
+        . auto/module
+    fi
+
+    if [ $HTTP_GRPC = YES -a $HTTP_V2 = YES ]; then
+        ngx_module_name=ngx_http_grpc_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_grpc_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_GRPC
+
+        . auto/module
+    fi
+
+    if [ $HTTP_PERL != NO ]; then
+        ngx_module_name=ngx_http_perl_module
+        ngx_module_incs=src/http/modules/perl
+        ngx_module_deps=src/http/modules/perl/ngx_http_perl_module.h
+        ngx_module_srcs=src/http/modules/perl/ngx_http_perl_module.c
+        ngx_module_libs=PERL
+        ngx_module_link=$HTTP_PERL
+
+        . auto/module
+    fi
+
+    if [ $HTTP_MEMCACHED = YES ]; then
+        ngx_module_name=ngx_http_memcached_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_memcached_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_MEMCACHED
+
+        . auto/module
+    fi
+
+    if [ $HTTP_EMPTY_GIF = YES ]; then
+        ngx_module_name=ngx_http_empty_gif_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_empty_gif_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_EMPTY_GIF
+
+        . auto/module
+    fi
+
+    if [ $HTTP_BROWSER = YES ]; then
+        ngx_module_name=ngx_http_browser_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_browser_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_BROWSER
+
+        . auto/module
+    fi
+
+    if [ $HTTP_SECURE_LINK = YES ]; then
+        ngx_module_name=ngx_http_secure_link_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_secure_link_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_SECURE_LINK
+
+        . auto/module
+    fi
+
+    if [ $HTTP_DEGRADATION = YES ]; then
+        have=NGX_HTTP_DEGRADATION . auto/have
+
+        ngx_module_name=ngx_http_degradation_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_degradation_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_DEGRADATION
+
+        . auto/module
+    fi
+
+    if [ $HTTP_FLV = YES ]; then
+        ngx_module_name=ngx_http_flv_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_flv_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_FLV
+
+        . auto/module
+    fi
+
+    if [ $HTTP_MP4 = YES ]; then
+        ngx_module_name=ngx_http_mp4_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_mp4_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_MP4
+
+        . auto/module
+    fi
+
+    if [ $HTTP_UPSTREAM_HASH = YES ]; then
+        ngx_module_name=ngx_http_upstream_hash_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_upstream_hash_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_UPSTREAM_HASH
+
+        . auto/module
+    fi
+
+    if [ $HTTP_UPSTREAM_IP_HASH = YES ]; then
+        ngx_module_name=ngx_http_upstream_ip_hash_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_upstream_ip_hash_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_UPSTREAM_IP_HASH
+
+        . auto/module
+    fi
+
+    if [ $HTTP_UPSTREAM_LEAST_CONN = YES ]; then
+        ngx_module_name=ngx_http_upstream_least_conn_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_upstream_least_conn_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_UPSTREAM_LEAST_CONN
+
+        . auto/module
+    fi
+
+    if [ $HTTP_UPSTREAM_KEEPALIVE = YES ]; then
+        ngx_module_name=ngx_http_upstream_keepalive_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_upstream_keepalive_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_UPSTREAM_KEEPALIVE
+
+        . auto/module
+    fi
+
+    if [ $HTTP_UPSTREAM_ZONE = YES ]; then
+        have=NGX_HTTP_UPSTREAM_ZONE . auto/have
+
+        ngx_module_name=ngx_http_upstream_zone_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_upstream_zone_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_UPSTREAM_ZONE
+
+        . auto/module
+    fi
+
+    if [ $HTTP_STUB_STATUS = YES ]; then
+        have=NGX_STAT_STUB . auto/have
+
+        ngx_module_name=ngx_http_stub_status_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_stub_status_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_STUB_STATUS
+
+        . auto/module
+    fi
+fi
+
+
+if [ $MAIL != NO ]; then
+    MAIL_MODULES=
+    MAIL_DEPS=
+    MAIL_INCS=
+
+    ngx_module_type=MAIL
+    ngx_module_libs=
+    ngx_module_link=YES
+
+    ngx_module_order=
+
+    ngx_module_name="ngx_mail_module ngx_mail_core_module"
+    ngx_module_incs="src/mail"
+    ngx_module_deps="src/mail/ngx_mail.h"
+    ngx_module_srcs="src/mail/ngx_mail.c \
+                     src/mail/ngx_mail_core_module.c \
+                     src/mail/ngx_mail_handler.c \
+                     src/mail/ngx_mail_parse.c"
+
+    . auto/module
+
+    ngx_module_incs=
+
+    if [ $MAIL_SSL = YES ]; then
+        USE_OPENSSL=YES
+        have=NGX_MAIL_SSL . auto/have
+
+        ngx_module_name=ngx_mail_ssl_module
+        ngx_module_deps=src/mail/ngx_mail_ssl_module.h
+        ngx_module_srcs=src/mail/ngx_mail_ssl_module.c
+
+        . auto/module
+    fi
+
+    if [ $MAIL_POP3 = YES ]; then
+        ngx_module_name=ngx_mail_pop3_module
+        ngx_module_deps=src/mail/ngx_mail_pop3_module.h
+        ngx_module_srcs="src/mail/ngx_mail_pop3_module.c \
+                         src/mail/ngx_mail_pop3_handler.c"
+
+        . auto/module
+    fi
+
+    if [ $MAIL_IMAP = YES ]; then
+        ngx_module_name=ngx_mail_imap_module
+        ngx_module_deps=src/mail/ngx_mail_imap_module.h
+        ngx_module_srcs="src/mail/ngx_mail_imap_module.c \
+                         src/mail/ngx_mail_imap_handler.c"
+
+        . auto/module
+    fi
+
+    if [ $MAIL_SMTP = YES ]; then
+        ngx_module_name=ngx_mail_smtp_module
+        ngx_module_deps=src/mail/ngx_mail_smtp_module.h
+        ngx_module_srcs="src/mail/ngx_mail_smtp_module.c \
+                         src/mail/ngx_mail_smtp_handler.c"
+
+        . auto/module
+    fi
+
+    ngx_module_name=ngx_mail_auth_http_module
+    ngx_module_deps=
+    ngx_module_srcs=src/mail/ngx_mail_auth_http_module.c
+
+    . auto/module
+
+    ngx_module_name=ngx_mail_proxy_module
+    ngx_module_deps=
+    ngx_module_srcs=src/mail/ngx_mail_proxy_module.c
+
+    . auto/module
+fi
+
+
+if [ $STREAM != NO ]; then
+    STREAM_MODULES=
+    STREAM_DEPS=
+    STREAM_INCS=
+
+    ngx_module_type=STREAM
+    ngx_module_libs=
+    ngx_module_link=YES
+
+    ngx_module_order=
+
+    ngx_module_name="ngx_stream_module \
+                     ngx_stream_core_module \
+                     ngx_stream_log_module \
+                     ngx_stream_proxy_module \
+                     ngx_stream_upstream_module \
+                     ngx_stream_write_filter_module"
+    ngx_module_incs="src/stream"
+    ngx_module_deps="src/stream/ngx_stream.h \
+                     src/stream/ngx_stream_variables.h \
+                     src/stream/ngx_stream_script.h \
+                     src/stream/ngx_stream_upstream.h \
+                     src/stream/ngx_stream_upstream_round_robin.h"
+    ngx_module_srcs="src/stream/ngx_stream.c \
+                     src/stream/ngx_stream_variables.c \
+                     src/stream/ngx_stream_script.c \
+                     src/stream/ngx_stream_handler.c \
+                     src/stream/ngx_stream_core_module.c \
+                     src/stream/ngx_stream_log_module.c \
+                     src/stream/ngx_stream_proxy_module.c \
+                     src/stream/ngx_stream_upstream.c \
+                     src/stream/ngx_stream_upstream_round_robin.c \
+                     src/stream/ngx_stream_write_filter_module.c"
+
+    . auto/module
+
+    ngx_module_incs=
+
+    if [ $STREAM_SSL = YES ]; then
+        USE_OPENSSL=YES
+        have=NGX_STREAM_SSL . auto/have
+
+        ngx_module_name=ngx_stream_ssl_module
+        ngx_module_deps=src/stream/ngx_stream_ssl_module.h
+        ngx_module_srcs=src/stream/ngx_stream_ssl_module.c
+        ngx_module_libs=
+        ngx_module_link=$STREAM_SSL
+
+        . auto/module
+    fi
+
+    if [ $STREAM_REALIP = YES ]; then
+        ngx_module_name=ngx_stream_realip_module
+        ngx_module_deps=
+        ngx_module_srcs=src/stream/ngx_stream_realip_module.c
+        ngx_module_libs=
+        ngx_module_link=$STREAM_REALIP
+
+        . auto/module
+    fi
+
+    if [ $STREAM_LIMIT_CONN = YES ]; then
+        ngx_module_name=ngx_stream_limit_conn_module
+        ngx_module_deps=
+        ngx_module_srcs=src/stream/ngx_stream_limit_conn_module.c
+        ngx_module_libs=
+        ngx_module_link=$STREAM_LIMIT_CONN
+
+        . auto/module
+    fi
+
+    if [ $STREAM_ACCESS = YES ]; then
+        ngx_module_name=ngx_stream_access_module
+        ngx_module_deps=
+        ngx_module_srcs=src/stream/ngx_stream_access_module.c
+        ngx_module_libs=
+        ngx_module_link=$STREAM_ACCESS
+
+        . auto/module
+    fi
+
+    if [ $STREAM_GEO = YES ]; then
+        ngx_module_name=ngx_stream_geo_module
+        ngx_module_deps=
+        ngx_module_srcs=src/stream/ngx_stream_geo_module.c
+        ngx_module_libs=
+        ngx_module_link=$STREAM_GEO
+
+        . auto/module
+    fi
+
+    if [ $STREAM_GEOIP != NO ]; then
+        ngx_module_name=ngx_stream_geoip_module
+        ngx_module_deps=
+        ngx_module_srcs=src/stream/ngx_stream_geoip_module.c
+        ngx_module_libs=GEOIP
+        ngx_module_link=$STREAM_GEOIP
+
+        . auto/module
+    fi
+
+    if [ $STREAM_MAP = YES ]; then
+        ngx_module_name=ngx_stream_map_module
+        ngx_module_deps=
+        ngx_module_srcs=src/stream/ngx_stream_map_module.c
+        ngx_module_libs=
+        ngx_module_link=$STREAM_MAP
+
+        . auto/module
+    fi
+
+    if [ $STREAM_SPLIT_CLIENTS = YES ]; then
+        ngx_module_name=ngx_stream_split_clients_module
+        ngx_module_deps=
+        ngx_module_srcs=src/stream/ngx_stream_split_clients_module.c
+        ngx_module_libs=
+        ngx_module_link=$STREAM_SPLIT_CLIENTS
+
+        . auto/module
+    fi
+
+    if [ $STREAM_RETURN = YES ]; then
+        ngx_module_name=ngx_stream_return_module
+        ngx_module_deps=
+        ngx_module_srcs=src/stream/ngx_stream_return_module.c
+        ngx_module_libs=
+        ngx_module_link=$STREAM_RETURN
+
+        . auto/module
+    fi
+
+    if [ $STREAM_UPSTREAM_HASH = YES ]; then
+        ngx_module_name=ngx_stream_upstream_hash_module
+        ngx_module_deps=
+        ngx_module_srcs=src/stream/ngx_stream_upstream_hash_module.c
+        ngx_module_libs=
+        ngx_module_link=$STREAM_UPSTREAM_HASH
+
+        . auto/module
+    fi
+
+    if [ $STREAM_UPSTREAM_LEAST_CONN = YES ]; then
+        ngx_module_name=ngx_stream_upstream_least_conn_module
+        ngx_module_deps=
+        ngx_module_srcs=src/stream/ngx_stream_upstream_least_conn_module.c
+        ngx_module_libs=
+        ngx_module_link=$STREAM_UPSTREAM_LEAST_CONN
+
+        . auto/module
+    fi
+
+    if [ $STREAM_UPSTREAM_ZONE = YES ]; then
+        have=NGX_STREAM_UPSTREAM_ZONE . auto/have
+
+        ngx_module_name=ngx_stream_upstream_zone_module
+        ngx_module_deps=
+        ngx_module_srcs=src/stream/ngx_stream_upstream_zone_module.c
+        ngx_module_libs=
+        ngx_module_link=$STREAM_UPSTREAM_ZONE
+
+        . auto/module
+    fi
+
+    if [ $STREAM_SSL_PREREAD = YES ]; then
+        ngx_module_name=ngx_stream_ssl_preread_module
+        ngx_module_deps=
+        ngx_module_srcs=src/stream/ngx_stream_ssl_preread_module.c
+        ngx_module_libs=
+        ngx_module_link=$STREAM_SSL_PREREAD
+
+        . auto/module
+    fi
+fi
+
+
+#if [ -r $NGX_OBJS/auto ]; then
+#    . $NGX_OBJS/auto
+#fi
+
+
+if test -n "$NGX_ADDONS"; then
+
+    echo configuring additional modules
+
+    for ngx_addon_dir in $NGX_ADDONS
+    do
+        echo "adding module in $ngx_addon_dir"
+
+        ngx_module_type=
+        ngx_module_name=
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=
+        ngx_module_libs=
+        ngx_module_order=
+        ngx_module_link=ADDON
+
+        if test -f $ngx_addon_dir/config; then
+            . $ngx_addon_dir/config
+
+            echo " + $ngx_addon_name was configured"
+
+        else
+            echo "$0: error: no $ngx_addon_dir/config was found"
+            exit 1
+        fi
+    done
+fi
+
+
+if test -n "$DYNAMIC_ADDONS"; then
+
+    echo configuring additional dynamic modules
+
+    for ngx_addon_dir in $DYNAMIC_ADDONS
+    do
+        echo "adding module in $ngx_addon_dir"
+
+        ngx_module_type=
+        ngx_module_name=
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=
+        ngx_module_libs=
+        ngx_module_order=
+        ngx_module_link=DYNAMIC
+
+        if test -f $ngx_addon_dir/config; then
+            . $ngx_addon_dir/config
+
+            echo " + $ngx_addon_name was configured"
+
+        else
+            echo "$0: error: no $ngx_addon_dir/config was found"
+            exit 1
+        fi
+    done
+fi
+
+
+if [ $USE_OPENSSL = YES ]; then
+    ngx_module_type=CORE
+    ngx_module_name=ngx_openssl_module
+    ngx_module_incs=
+    ngx_module_deps=src/event/ngx_event_openssl.h
+    ngx_module_srcs="src/event/ngx_event_openssl.c
+                     src/event/ngx_event_openssl_stapling.c"
+    ngx_module_libs=
+    ngx_module_link=YES
+    ngx_module_order=
+
+    . auto/module
+fi
+
+
+if [ $USE_PCRE = YES ]; then
+    ngx_module_type=CORE
+    ngx_module_name=ngx_regex_module
+    ngx_module_incs=
+    ngx_module_deps=src/core/ngx_regex.h
+    ngx_module_srcs=src/core/ngx_regex.c
+    ngx_module_libs=
+    ngx_module_link=YES
+    ngx_module_order=
+
+    . auto/module
+fi
+
+
+modules="$CORE_MODULES $EVENT_MODULES"
+
+
+# thread pool module should be initialized after events
+if [ $USE_THREADS = YES ]; then
+    modules="$modules $THREAD_POOL_MODULE"
+fi
+
+
+if [ $HTTP = YES ]; then
+    modules="$modules $HTTP_MODULES $HTTP_FILTER_MODULES \
+             $HTTP_AUX_FILTER_MODULES $HTTP_INIT_FILTER_MODULES"
+
+    NGX_ADDON_DEPS="$NGX_ADDON_DEPS \$(HTTP_DEPS)"
+fi
+
+
+if [ $MAIL != NO ]; then
+
+    if [ $MAIL = YES ]; then
+        modules="$modules $MAIL_MODULES"
+
+    elif [ $MAIL = DYNAMIC ]; then
+        ngx_module_name=$MAIL_MODULES
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=$MAIL_SRCS
+        ngx_module_libs=
+        ngx_module_link=DYNAMIC
+
+        . auto/module
+    fi
+
+    NGX_ADDON_DEPS="$NGX_ADDON_DEPS \$(MAIL_DEPS)"
+fi
+
+
+if [ $STREAM != NO ]; then
+
+    if [ $STREAM = YES ]; then
+        modules="$modules $STREAM_MODULES"
+
+    elif [ $STREAM = DYNAMIC ]; then
+        ngx_module_name=$STREAM_MODULES
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=$STREAM_SRCS
+        ngx_module_libs=
+        ngx_module_link=DYNAMIC
+
+        . auto/module
+    fi
+
+    NGX_ADDON_DEPS="$NGX_ADDON_DEPS \$(STREAM_DEPS)"
+fi
+
+
+ngx_module_type=MISC
+MISC_MODULES=
+
+if [ $NGX_GOOGLE_PERFTOOLS = YES ]; then
+    ngx_module_name=ngx_google_perftools_module
+    ngx_module_incs=
+    ngx_module_deps=
+    ngx_module_srcs=src/misc/ngx_google_perftools_module.c
+    ngx_module_libs=
+    ngx_module_link=$NGX_GOOGLE_PERFTOOLS
+
+    . auto/module
+fi
+
+if [ $NGX_CPP_TEST = YES ]; then
+    ngx_module_name=
+    ngx_module_incs=
+    ngx_module_deps=
+    ngx_module_srcs=src/misc/ngx_cpp_test_module.cpp
+    ngx_module_libs=-lstdc++
+    ngx_module_link=$NGX_CPP_TEST
+
+    . auto/module
+fi
+
+modules="$modules $MISC_MODULES"
+
+
+if [ $NGX_COMPAT = YES ]; then
+    have=NGX_COMPAT . auto/have
+    have=NGX_HTTP_GZIP . auto/have
+    have=NGX_HTTP_DAV . auto/have
+    have=NGX_HTTP_REALIP . auto/have
+    have=NGX_HTTP_X_FORWARDED_FOR . auto/have
+    have=NGX_HTTP_HEADERS . auto/have
+    have=NGX_HTTP_UPSTREAM_ZONE . auto/have
+    have=NGX_STREAM_UPSTREAM_ZONE . auto/have
+fi
+
+
+cat << END                                    > $NGX_MODULES_C
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+$NGX_PRAGMA
+
+END
+
+for mod in $modules
+do
+    echo "extern ngx_module_t  $mod;"         >> $NGX_MODULES_C
+done
+
+echo                                          >> $NGX_MODULES_C
+echo 'ngx_module_t *ngx_modules[] = {'        >> $NGX_MODULES_C
+
+for mod in $modules
+do
+    echo "    &$mod,"                         >> $NGX_MODULES_C
+done
+
+cat << END                                    >> $NGX_MODULES_C
+    NULL
+};
+
+END
+
+echo 'char *ngx_module_names[] = {'           >> $NGX_MODULES_C
+
+for mod in $modules
+do
+    echo "    \"$mod\","                      >> $NGX_MODULES_C
+done
+
+cat << END                                    >> $NGX_MODULES_C
+    NULL
+};
+
+END
diff --git a/nginx/auto/nohave b/nginx/auto/nohave
new file mode 100644 (file)
index 0000000..dfb1718
--- /dev/null
@@ -0,0 +1,12 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+cat << END >> $NGX_AUTO_CONFIG_H
+
+#ifndef $have
+#define $have  0
+#endif
+
+END
diff --git a/nginx/auto/options b/nginx/auto/options
new file mode 100644 (file)
index 0000000..a229efa
--- /dev/null
@@ -0,0 +1,626 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+help=no
+
+NGX_PREFIX=
+NGX_SBIN_PATH=
+NGX_MODULES_PATH=
+NGX_CONF_PREFIX=
+NGX_CONF_PATH=
+NGX_ERROR_LOG_PATH=
+NGX_PID_PATH=
+NGX_LOCK_PATH=
+NGX_USER=
+NGX_GROUP=
+NGX_BUILD=
+
+CC=${CC:-cc}
+CPP=
+NGX_OBJS=objs
+
+NGX_DEBUG=NO
+NGX_CC_OPT=
+NGX_LD_OPT=
+CPU=NO
+
+NGX_RPATH=NO
+
+NGX_TEST_BUILD_DEVPOLL=NO
+NGX_TEST_BUILD_EVENTPORT=NO
+NGX_TEST_BUILD_EPOLL=NO
+NGX_TEST_BUILD_SOLARIS_SENDFILEV=NO
+
+NGX_PLATFORM=
+NGX_WINE=
+
+EVENT_FOUND=NO
+
+EVENT_SELECT=NO
+EVENT_POLL=NO
+
+USE_THREADS=NO
+
+NGX_FILE_AIO=NO
+
+HTTP=YES
+
+NGX_HTTP_LOG_PATH=
+NGX_HTTP_CLIENT_TEMP_PATH=
+NGX_HTTP_PROXY_TEMP_PATH=
+NGX_HTTP_FASTCGI_TEMP_PATH=
+NGX_HTTP_UWSGI_TEMP_PATH=
+NGX_HTTP_SCGI_TEMP_PATH=
+
+HTTP_CACHE=YES
+HTTP_CHARSET=YES
+HTTP_GZIP=YES
+HTTP_SSL=NO
+HTTP_V2=NO
+HTTP_SSI=YES
+HTTP_POSTPONE=NO
+HTTP_REALIP=NO
+HTTP_XSLT=NO
+HTTP_IMAGE_FILTER=NO
+HTTP_SUB=NO
+HTTP_ADDITION=NO
+HTTP_DAV=NO
+HTTP_ACCESS=YES
+HTTP_AUTH_BASIC=YES
+HTTP_AUTH_REQUEST=NO
+HTTP_MIRROR=YES
+HTTP_USERID=YES
+HTTP_SLICE=NO
+HTTP_AUTOINDEX=YES
+HTTP_RANDOM_INDEX=NO
+HTTP_STATUS=NO
+HTTP_GEO=YES
+HTTP_GEOIP=NO
+HTTP_MAP=YES
+HTTP_SPLIT_CLIENTS=YES
+HTTP_REFERER=YES
+HTTP_REWRITE=YES
+HTTP_PROXY=YES
+HTTP_FASTCGI=YES
+HTTP_UWSGI=YES
+HTTP_SCGI=YES
+HTTP_GRPC=YES
+HTTP_PERL=NO
+HTTP_MEMCACHED=YES
+HTTP_LIMIT_CONN=YES
+HTTP_LIMIT_REQ=YES
+HTTP_EMPTY_GIF=YES
+HTTP_BROWSER=YES
+HTTP_SECURE_LINK=NO
+HTTP_DEGRADATION=NO
+HTTP_FLV=NO
+HTTP_MP4=NO
+HTTP_GUNZIP=NO
+HTTP_GZIP_STATIC=NO
+HTTP_UPSTREAM_HASH=YES
+HTTP_UPSTREAM_IP_HASH=YES
+HTTP_UPSTREAM_LEAST_CONN=YES
+HTTP_UPSTREAM_KEEPALIVE=YES
+HTTP_UPSTREAM_ZONE=YES
+
+# STUB
+HTTP_STUB_STATUS=NO
+
+MAIL=NO
+MAIL_SSL=NO
+MAIL_POP3=YES
+MAIL_IMAP=YES
+MAIL_SMTP=YES
+
+STREAM=NO
+STREAM_SSL=NO
+STREAM_REALIP=NO
+STREAM_LIMIT_CONN=YES
+STREAM_ACCESS=YES
+STREAM_GEO=YES
+STREAM_GEOIP=NO
+STREAM_MAP=YES
+STREAM_SPLIT_CLIENTS=YES
+STREAM_RETURN=YES
+STREAM_UPSTREAM_HASH=YES
+STREAM_UPSTREAM_LEAST_CONN=YES
+STREAM_UPSTREAM_ZONE=YES
+STREAM_SSL_PREREAD=NO
+
+DYNAMIC_MODULES=
+
+NGX_ADDONS=
+NGX_ADDON_DEPS=
+DYNAMIC_ADDONS=
+
+NGX_COMPAT=NO
+
+USE_PCRE=NO
+PCRE=NONE
+PCRE_OPT=
+PCRE_CONF_OPT=
+PCRE_JIT=NO
+
+USE_OPENSSL=NO
+OPENSSL=NONE
+
+NGX_USE_VCL=NO
+VPP_LIB_PATH=
+VPP_SRC_PATH=
+
+USE_ZLIB=NO
+ZLIB=NONE
+ZLIB_OPT=
+ZLIB_ASM=NO
+
+USE_PERL=NO
+NGX_PERL=perl
+
+USE_LIBXSLT=NO
+USE_LIBGD=NO
+USE_GEOIP=NO
+
+NGX_GOOGLE_PERFTOOLS=NO
+NGX_CPP_TEST=NO
+
+NGX_LIBATOMIC=NO
+
+NGX_CPU_CACHE_LINE=
+
+NGX_POST_CONF_MSG=
+
+opt=
+
+for option
+do
+    opt="$opt `echo $option | sed -e \"s/\(--[^=]*=\)\(.* .*\)/\1'\2'/\"`"
+
+    case "$option" in
+        -*=*) value=`echo "$option" | sed -e 's/[-_a-zA-Z0-9]*=//'` ;;
+           *) value="" ;;
+    esac
+
+    case "$option" in
+        --help)                          help=yes                   ;;
+
+        --prefix=)                       NGX_PREFIX="!"             ;;
+        --prefix=*)                      NGX_PREFIX="$value"        ;;
+        --sbin-path=*)                   NGX_SBIN_PATH="$value"     ;;
+        --modules-path=*)                NGX_MODULES_PATH="$value"  ;;
+        --conf-path=*)                   NGX_CONF_PATH="$value"     ;;
+        --error-log-path=*)              NGX_ERROR_LOG_PATH="$value";;
+        --pid-path=*)                    NGX_PID_PATH="$value"      ;;
+        --lock-path=*)                   NGX_LOCK_PATH="$value"     ;;
+        --user=*)                        NGX_USER="$value"          ;;
+        --group=*)                       NGX_GROUP="$value"         ;;
+
+        --crossbuild=*)                  NGX_PLATFORM="$value"      ;;
+
+        --build=*)                       NGX_BUILD="$value"         ;;
+        --builddir=*)                    NGX_OBJS="$value"          ;;
+
+        --with-select_module)            EVENT_SELECT=YES           ;;
+        --without-select_module)         EVENT_SELECT=NONE          ;;
+        --with-poll_module)              EVENT_POLL=YES             ;;
+        --without-poll_module)           EVENT_POLL=NONE            ;;
+
+        --with-threads)                  USE_THREADS=YES            ;;
+
+        --with-file-aio)                 NGX_FILE_AIO=YES           ;;
+
+        --with-ipv6)
+            NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG
+$0: warning: the \"--with-ipv6\" option is deprecated"
+        ;;
+
+        --without-http)                  HTTP=NO                    ;;
+        --without-http-cache)            HTTP_CACHE=NO              ;;
+
+        --http-log-path=*)               NGX_HTTP_LOG_PATH="$value" ;;
+        --http-client-body-temp-path=*)  NGX_HTTP_CLIENT_TEMP_PATH="$value" ;;
+        --http-proxy-temp-path=*)        NGX_HTTP_PROXY_TEMP_PATH="$value" ;;
+        --http-fastcgi-temp-path=*)      NGX_HTTP_FASTCGI_TEMP_PATH="$value" ;;
+        --http-uwsgi-temp-path=*)        NGX_HTTP_UWSGI_TEMP_PATH="$value" ;;
+        --http-scgi-temp-path=*)         NGX_HTTP_SCGI_TEMP_PATH="$value" ;;
+
+        --with-http_ssl_module)          HTTP_SSL=YES               ;;
+        --with-http_v2_module)           HTTP_V2=YES                ;;
+        --with-http_realip_module)       HTTP_REALIP=YES            ;;
+        --with-http_addition_module)     HTTP_ADDITION=YES          ;;
+        --with-http_xslt_module)         HTTP_XSLT=YES              ;;
+        --with-http_xslt_module=dynamic) HTTP_XSLT=DYNAMIC          ;;
+        --with-http_image_filter_module) HTTP_IMAGE_FILTER=YES      ;;
+        --with-http_image_filter_module=dynamic)
+                                         HTTP_IMAGE_FILTER=DYNAMIC  ;;
+        --with-http_geoip_module)        HTTP_GEOIP=YES             ;;
+        --with-http_geoip_module=dynamic)
+                                         HTTP_GEOIP=DYNAMIC         ;;
+        --with-http_sub_module)          HTTP_SUB=YES               ;;
+        --with-http_dav_module)          HTTP_DAV=YES               ;;
+        --with-http_flv_module)          HTTP_FLV=YES               ;;
+        --with-http_mp4_module)          HTTP_MP4=YES               ;;
+        --with-http_gunzip_module)       HTTP_GUNZIP=YES            ;;
+        --with-http_gzip_static_module)  HTTP_GZIP_STATIC=YES       ;;
+        --with-http_auth_request_module) HTTP_AUTH_REQUEST=YES      ;;
+        --with-http_random_index_module) HTTP_RANDOM_INDEX=YES      ;;
+        --with-http_secure_link_module)  HTTP_SECURE_LINK=YES       ;;
+        --with-http_degradation_module)  HTTP_DEGRADATION=YES       ;;
+        --with-http_slice_module)        HTTP_SLICE=YES             ;;
+
+        --without-http_charset_module)   HTTP_CHARSET=NO            ;;
+        --without-http_gzip_module)      HTTP_GZIP=NO               ;;
+        --without-http_ssi_module)       HTTP_SSI=NO                ;;
+        --without-http_userid_module)    HTTP_USERID=NO             ;;
+        --without-http_access_module)    HTTP_ACCESS=NO             ;;
+        --without-http_auth_basic_module) HTTP_AUTH_BASIC=NO        ;;
+        --without-http_mirror_module)    HTTP_MIRROR=NO             ;;
+        --without-http_autoindex_module) HTTP_AUTOINDEX=NO          ;;
+        --without-http_status_module)    HTTP_STATUS=NO             ;;
+        --without-http_geo_module)       HTTP_GEO=NO                ;;
+        --without-http_map_module)       HTTP_MAP=NO                ;;
+        --without-http_split_clients_module) HTTP_SPLIT_CLIENTS=NO  ;;
+        --without-http_referer_module)   HTTP_REFERER=NO            ;;
+        --without-http_rewrite_module)   HTTP_REWRITE=NO            ;;
+        --without-http_proxy_module)     HTTP_PROXY=NO              ;;
+        --without-http_fastcgi_module)   HTTP_FASTCGI=NO            ;;
+        --without-http_uwsgi_module)     HTTP_UWSGI=NO              ;;
+        --without-http_scgi_module)      HTTP_SCGI=NO               ;;
+        --without-http_grpc_module)      HTTP_GRPC=NO               ;;
+        --without-http_memcached_module) HTTP_MEMCACHED=NO          ;;
+        --without-http_limit_conn_module) HTTP_LIMIT_CONN=NO        ;;
+        --without-http_limit_req_module) HTTP_LIMIT_REQ=NO         ;;
+        --without-http_empty_gif_module) HTTP_EMPTY_GIF=NO          ;;
+        --without-http_browser_module)   HTTP_BROWSER=NO            ;;
+        --without-http_upstream_hash_module) HTTP_UPSTREAM_HASH=NO  ;;
+        --without-http_upstream_ip_hash_module) HTTP_UPSTREAM_IP_HASH=NO ;;
+        --without-http_upstream_least_conn_module)
+                                         HTTP_UPSTREAM_LEAST_CONN=NO ;;
+        --without-http_upstream_keepalive_module) HTTP_UPSTREAM_KEEPALIVE=NO ;;
+        --without-http_upstream_zone_module) HTTP_UPSTREAM_ZONE=NO  ;;
+
+        --with-http_perl_module)         HTTP_PERL=YES              ;;
+        --with-http_perl_module=dynamic) HTTP_PERL=DYNAMIC          ;;
+        --with-perl_modules_path=*)      NGX_PERL_MODULES="$value"  ;;
+        --with-perl=*)                   NGX_PERL="$value"          ;;
+
+        # STUB
+        --with-http_stub_status_module)  HTTP_STUB_STATUS=YES       ;;
+
+        --with-mail)                     MAIL=YES                   ;;
+        --with-mail=dynamic)             MAIL=DYNAMIC               ;;
+        --with-mail_ssl_module)          MAIL_SSL=YES               ;;
+        # STUB
+        --with-imap)
+            MAIL=YES
+            NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG
+$0: warning: the \"--with-imap\" option is deprecated, \
+use the \"--with-mail\" option instead"
+        ;;
+        --with-imap_ssl_module)
+            MAIL_SSL=YES
+            NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG
+$0: warning: the \"--with-imap_ssl_module\" option is deprecated, \
+use the \"--with-mail_ssl_module\" option instead"
+        ;;
+        --without-mail_pop3_module)      MAIL_POP3=NO               ;;
+        --without-mail_imap_module)      MAIL_IMAP=NO               ;;
+        --without-mail_smtp_module)      MAIL_SMTP=NO               ;;
+
+        --with-stream)                   STREAM=YES                 ;;
+        --with-stream=dynamic)           STREAM=DYNAMIC             ;;
+        --with-stream_ssl_module)        STREAM_SSL=YES             ;;
+        --with-stream_realip_module)     STREAM_REALIP=YES          ;;
+        --with-stream_geoip_module)      STREAM_GEOIP=YES           ;;
+        --with-stream_geoip_module=dynamic)
+                                         STREAM_GEOIP=DYNAMIC       ;;
+        --with-stream_ssl_preread_module)
+                                         STREAM_SSL_PREREAD=YES     ;;
+        --without-stream_limit_conn_module)
+                                         STREAM_LIMIT_CONN=NO       ;;
+        --without-stream_access_module)  STREAM_ACCESS=NO           ;;
+        --without-stream_geo_module)     STREAM_GEO=NO              ;;
+        --without-stream_map_module)     STREAM_MAP=NO              ;;
+        --without-stream_split_clients_module)
+                                         STREAM_SPLIT_CLIENTS=NO    ;;
+        --without-stream_return_module)  STREAM_RETURN=NO           ;;
+        --without-stream_upstream_hash_module)
+                                         STREAM_UPSTREAM_HASH=NO    ;;
+        --without-stream_upstream_least_conn_module)
+                                         STREAM_UPSTREAM_LEAST_CONN=NO ;;
+        --without-stream_upstream_zone_module)
+                                         STREAM_UPSTREAM_ZONE=NO    ;;
+
+        --with-google_perftools_module)  NGX_GOOGLE_PERFTOOLS=YES   ;;
+        --with-cpp_test_module)          NGX_CPP_TEST=YES           ;;
+
+        --add-module=*)                  NGX_ADDONS="$NGX_ADDONS $value" ;;
+        --add-dynamic-module=*)          DYNAMIC_ADDONS="$DYNAMIC_ADDONS $value" ;;
+
+        --with-compat)                   NGX_COMPAT=YES             ;;
+
+        --with-cc=*)                     CC="$value"                ;;
+        --with-cpp=*)                    CPP="$value"               ;;
+        --with-cc-opt=*)                 NGX_CC_OPT="$value"        ;;
+        --with-ld-opt=*)                 NGX_LD_OPT="$value"        ;;
+        --with-cpu-opt=*)                CPU="$value"               ;;
+        --with-debug)                    NGX_DEBUG=YES              ;;
+
+        --without-pcre)                  USE_PCRE=DISABLED          ;;
+        --with-pcre)                     USE_PCRE=YES               ;;
+        --with-pcre=*)                   PCRE="$value"              ;;
+        --with-pcre-opt=*)               PCRE_OPT="$value"          ;;
+        --with-pcre-jit)                 PCRE_JIT=YES               ;;
+
+        --with-openssl=*)                OPENSSL="$value"           ;;
+        --with-openssl-opt=*)            OPENSSL_OPT="$value"       ;;
+
+        --with-vcl)                      NGX_USE_VCL=YES            ;;
+        --vpp-lib-path=*)                VPP_LIB_PATH="$value"      ;;
+        --vpp-src-path=*)                VPP_SRC_PATH="$value"      ;;
+
+        --with-md5=*)
+            NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG
+$0: warning: the \"--with-md5\" option is deprecated"
+        ;;
+        --with-md5-opt=*)
+            NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG
+$0: warning: the \"--with-md5-opt\" option is deprecated"
+        ;;
+        --with-md5-asm)
+            NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG
+$0: warning: the \"--with-md5-asm\" option is deprecated"
+        ;;
+
+        --with-sha1=*)
+            NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG
+$0: warning: the \"--with-sha1\" option is deprecated"
+        ;;
+        --with-sha1-opt=*)
+            NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG
+$0: warning: the \"--with-sha1-opt\" option is deprecated"
+        ;;
+        --with-sha1-asm)
+            NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG
+$0: warning: the \"--with-sha1-asm\" option is deprecated"
+        ;;
+
+        --with-zlib=*)                   ZLIB="$value"              ;;
+        --with-zlib-opt=*)               ZLIB_OPT="$value"          ;;
+        --with-zlib-asm=*)               ZLIB_ASM="$value"          ;;
+
+        --with-libatomic)                NGX_LIBATOMIC=YES          ;;
+        --with-libatomic=*)              NGX_LIBATOMIC="$value"     ;;
+
+        --test-build-devpoll)            NGX_TEST_BUILD_DEVPOLL=YES ;;
+        --test-build-eventport)          NGX_TEST_BUILD_EVENTPORT=YES ;;
+        --test-build-epoll)              NGX_TEST_BUILD_EPOLL=YES   ;;
+        --test-build-solaris-sendfilev)  NGX_TEST_BUILD_SOLARIS_SENDFILEV=YES ;;
+
+        *)
+            echo "$0: error: invalid option \"$option\""
+            exit 1
+        ;;
+    esac
+done
+
+
+NGX_CONFIGURE="$opt"
+
+
+if [ $help = yes ]; then
+
+cat << END
+
+  --help                             print this message
+
+  --prefix=PATH                      set installation prefix
+  --sbin-path=PATH                   set nginx binary pathname
+  --modules-path=PATH                set modules path
+  --conf-path=PATH                   set nginx.conf pathname
+  --error-log-path=PATH              set error log pathname
+  --pid-path=PATH                    set nginx.pid pathname
+  --lock-path=PATH                   set nginx.lock pathname
+
+  --user=USER                        set non-privileged user for
+                                     worker processes
+  --group=GROUP                      set non-privileged group for
+                                     worker processes
+
+  --build=NAME                       set build name
+  --builddir=DIR                     set build directory
+
+  --with-select_module               enable select module
+  --without-select_module            disable select module
+  --with-poll_module                 enable poll module
+  --without-poll_module              disable poll module
+
+  --with-threads                     enable thread pool support
+
+  --with-file-aio                    enable file AIO support
+
+  --with-http_ssl_module             enable ngx_http_ssl_module
+  --with-http_v2_module              enable ngx_http_v2_module
+  --with-http_realip_module          enable ngx_http_realip_module
+  --with-http_addition_module        enable ngx_http_addition_module
+  --with-http_xslt_module            enable ngx_http_xslt_module
+  --with-http_xslt_module=dynamic    enable dynamic ngx_http_xslt_module
+  --with-http_image_filter_module    enable ngx_http_image_filter_module
+  --with-http_image_filter_module=dynamic
+                                     enable dynamic ngx_http_image_filter_module
+  --with-http_geoip_module           enable ngx_http_geoip_module
+  --with-http_geoip_module=dynamic   enable dynamic ngx_http_geoip_module
+  --with-http_sub_module             enable ngx_http_sub_module
+  --with-http_dav_module             enable ngx_http_dav_module
+  --with-http_flv_module             enable ngx_http_flv_module
+  --with-http_mp4_module             enable ngx_http_mp4_module
+  --with-http_gunzip_module          enable ngx_http_gunzip_module
+  --with-http_gzip_static_module     enable ngx_http_gzip_static_module
+  --with-http_auth_request_module    enable ngx_http_auth_request_module
+  --with-http_random_index_module    enable ngx_http_random_index_module
+  --with-http_secure_link_module     enable ngx_http_secure_link_module
+  --with-http_degradation_module     enable ngx_http_degradation_module
+  --with-http_slice_module           enable ngx_http_slice_module
+  --with-http_stub_status_module     enable ngx_http_stub_status_module
+
+  --without-http_charset_module      disable ngx_http_charset_module
+  --without-http_gzip_module         disable ngx_http_gzip_module
+  --without-http_ssi_module          disable ngx_http_ssi_module
+  --without-http_userid_module       disable ngx_http_userid_module
+  --without-http_access_module       disable ngx_http_access_module
+  --without-http_auth_basic_module   disable ngx_http_auth_basic_module
+  --without-http_mirror_module       disable ngx_http_mirror_module
+  --without-http_autoindex_module    disable ngx_http_autoindex_module
+  --without-http_geo_module          disable ngx_http_geo_module
+  --without-http_map_module          disable ngx_http_map_module
+  --without-http_split_clients_module disable ngx_http_split_clients_module
+  --without-http_referer_module      disable ngx_http_referer_module
+  --without-http_rewrite_module      disable ngx_http_rewrite_module
+  --without-http_proxy_module        disable ngx_http_proxy_module
+  --without-http_fastcgi_module      disable ngx_http_fastcgi_module
+  --without-http_uwsgi_module        disable ngx_http_uwsgi_module
+  --without-http_scgi_module         disable ngx_http_scgi_module
+  --without-http_grpc_module         disable ngx_http_grpc_module
+  --without-http_memcached_module    disable ngx_http_memcached_module
+  --without-http_limit_conn_module   disable ngx_http_limit_conn_module
+  --without-http_limit_req_module    disable ngx_http_limit_req_module
+  --without-http_empty_gif_module    disable ngx_http_empty_gif_module
+  --without-http_browser_module      disable ngx_http_browser_module
+  --without-http_upstream_hash_module
+                                     disable ngx_http_upstream_hash_module
+  --without-http_upstream_ip_hash_module
+                                     disable ngx_http_upstream_ip_hash_module
+  --without-http_upstream_least_conn_module
+                                     disable ngx_http_upstream_least_conn_module
+  --without-http_upstream_keepalive_module
+                                     disable ngx_http_upstream_keepalive_module
+  --without-http_upstream_zone_module
+                                     disable ngx_http_upstream_zone_module
+
+  --with-http_perl_module            enable ngx_http_perl_module
+  --with-http_perl_module=dynamic    enable dynamic ngx_http_perl_module
+  --with-perl_modules_path=PATH      set Perl modules path
+  --with-perl=PATH                   set perl binary pathname
+
+  --http-log-path=PATH               set http access log pathname
+  --http-client-body-temp-path=PATH  set path to store
+                                     http client request body temporary files
+  --http-proxy-temp-path=PATH        set path to store
+                                     http proxy temporary files
+  --http-fastcgi-temp-path=PATH      set path to store
+                                     http fastcgi temporary files
+  --http-uwsgi-temp-path=PATH        set path to store
+                                     http uwsgi temporary files
+  --http-scgi-temp-path=PATH         set path to store
+                                     http scgi temporary files
+
+  --without-http                     disable HTTP server
+  --without-http-cache               disable HTTP cache
+
+  --with-mail                        enable POP3/IMAP4/SMTP proxy module
+  --with-mail=dynamic                enable dynamic POP3/IMAP4/SMTP proxy module
+  --with-mail_ssl_module             enable ngx_mail_ssl_module
+  --without-mail_pop3_module         disable ngx_mail_pop3_module
+  --without-mail_imap_module         disable ngx_mail_imap_module
+  --without-mail_smtp_module         disable ngx_mail_smtp_module
+
+  --with-stream                      enable TCP/UDP proxy module
+  --with-stream=dynamic              enable dynamic TCP/UDP proxy module
+  --with-stream_ssl_module           enable ngx_stream_ssl_module
+  --with-stream_realip_module        enable ngx_stream_realip_module
+  --with-stream_geoip_module         enable ngx_stream_geoip_module
+  --with-stream_geoip_module=dynamic enable dynamic ngx_stream_geoip_module
+  --with-stream_ssl_preread_module   enable ngx_stream_ssl_preread_module
+  --without-stream_limit_conn_module disable ngx_stream_limit_conn_module
+  --without-stream_access_module     disable ngx_stream_access_module
+  --without-stream_geo_module        disable ngx_stream_geo_module
+  --without-stream_map_module        disable ngx_stream_map_module
+  --without-stream_split_clients_module
+                                     disable ngx_stream_split_clients_module
+  --without-stream_return_module     disable ngx_stream_return_module
+  --without-stream_upstream_hash_module
+                                     disable ngx_stream_upstream_hash_module
+  --without-stream_upstream_least_conn_module
+                                     disable ngx_stream_upstream_least_conn_module
+  --without-stream_upstream_zone_module
+                                     disable ngx_stream_upstream_zone_module
+
+  --with-google_perftools_module     enable ngx_google_perftools_module
+  --with-cpp_test_module             enable ngx_cpp_test_module
+
+  --add-module=PATH                  enable external module
+  --add-dynamic-module=PATH          enable dynamic external module
+
+  --with-compat                      dynamic modules compatibility
+
+  --with-cc=PATH                     set C compiler pathname
+  --with-cpp=PATH                    set C preprocessor pathname
+  --with-cc-opt=OPTIONS              set additional C compiler options
+  --with-ld-opt=OPTIONS              set additional linker options
+  --with-cpu-opt=CPU                 build for the specified CPU, valid values:
+                                     pentium, pentiumpro, pentium3, pentium4,
+                                     athlon, opteron, sparc32, sparc64, ppc64
+
+  --without-pcre                     disable PCRE library usage
+  --with-pcre                        force PCRE library usage
+  --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
+
+  --with-zlib=DIR                    set path to zlib library sources
+  --with-zlib-opt=OPTIONS            set additional build options for zlib
+  --with-zlib-asm=CPU                use zlib assembler sources optimized
+                                     for the specified CPU, valid values:
+                                     pentium, pentiumpro
+
+  --with-libatomic                   force libatomic_ops library usage
+  --with-libatomic=DIR               set path to libatomic_ops library sources
+
+  --with-openssl=DIR                 set path to OpenSSL library sources
+  --with-openssl-opt=OPTIONS         set additional build options for OpenSSL
+
+  --with-debug                       enable debug logging
+
+END
+
+    exit 1
+fi
+
+
+if [ ".$NGX_PLATFORM" = ".win32" ]; then
+    NGX_WINE=$WINE
+fi
+
+
+NGX_SBIN_PATH=${NGX_SBIN_PATH:-sbin/nginx}
+NGX_MODULES_PATH=${NGX_MODULES_PATH:-modules}
+NGX_CONF_PATH=${NGX_CONF_PATH:-conf/nginx.conf}
+NGX_CONF_PREFIX=`dirname $NGX_CONF_PATH`
+NGX_PID_PATH=${NGX_PID_PATH:-logs/nginx.pid}
+NGX_LOCK_PATH=${NGX_LOCK_PATH:-logs/nginx.lock}
+
+if [ ".$NGX_ERROR_LOG_PATH" = ".stderr" ]; then
+    NGX_ERROR_LOG_PATH=
+else
+    NGX_ERROR_LOG_PATH=${NGX_ERROR_LOG_PATH:-logs/error.log}
+fi
+
+NGX_HTTP_LOG_PATH=${NGX_HTTP_LOG_PATH:-logs/access.log}
+NGX_HTTP_CLIENT_TEMP_PATH=${NGX_HTTP_CLIENT_TEMP_PATH:-client_body_temp}
+NGX_HTTP_PROXY_TEMP_PATH=${NGX_HTTP_PROXY_TEMP_PATH:-proxy_temp}
+NGX_HTTP_FASTCGI_TEMP_PATH=${NGX_HTTP_FASTCGI_TEMP_PATH:-fastcgi_temp}
+NGX_HTTP_UWSGI_TEMP_PATH=${NGX_HTTP_UWSGI_TEMP_PATH:-uwsgi_temp}
+NGX_HTTP_SCGI_TEMP_PATH=${NGX_HTTP_SCGI_TEMP_PATH:-scgi_temp}
+
+case ".$NGX_PERL_MODULES" in
+    ./*)
+    ;;
+
+    .)
+    ;;
+
+    *)
+        NGX_PERL_MODULES=$NGX_PREFIX/$NGX_PERL_MODULES
+    ;;
+esac
diff --git a/nginx/auto/os/conf b/nginx/auto/os/conf
new file mode 100644 (file)
index 0000000..7c6cb69
--- /dev/null
@@ -0,0 +1,129 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+echo "checking for $NGX_SYSTEM specific features"
+
+case "$NGX_PLATFORM" in
+
+    FreeBSD:*)
+        . auto/os/freebsd
+    ;;
+
+    Linux:*)
+        . auto/os/linux
+    ;;
+
+    SunOS:*)
+        . auto/os/solaris
+    ;;
+
+    Darwin:*)
+        . auto/os/darwin
+    ;;
+
+    win32)
+        . auto/os/win32
+    ;;
+
+    DragonFly:*)
+        have=NGX_FREEBSD . auto/have_headers
+        CORE_INCS="$UNIX_INCS"
+        CORE_DEPS="$UNIX_DEPS $FREEBSD_DEPS"
+        CORE_SRCS="$UNIX_SRCS $FREEBSD_SRCS"
+
+        echo " + sendfile() found"
+        have=NGX_HAVE_SENDFILE . auto/have
+        CORE_SRCS="$CORE_SRCS $FREEBSD_SENDFILE_SRCS"
+
+        ngx_spacer='
+'
+    ;;
+
+    NetBSD:*)
+        CORE_INCS="$UNIX_INCS"
+        CORE_DEPS="$UNIX_DEPS $POSIX_DEPS"
+        CORE_SRCS="$UNIX_SRCS"
+
+        NGX_RPATH=YES
+    ;;
+
+    HP-UX:*)
+        # HP/UX
+        have=NGX_HPUX . auto/have_headers
+        CORE_INCS="$UNIX_INCS"
+        CORE_DEPS="$UNIX_DEPS $POSIX_DEPS"
+        CORE_SRCS="$UNIX_SRCS"
+        CC_AUX_FLAGS="$CC_AUX_FLAGS -D_XOPEN_SOURCE -D_XOPEN_SOURCE_EXTENDED=1"
+        CC_AUX_FLAGS="$CC_AUX_FLAGS -D_HPUX_ALT_XOPEN_SOCKET_API"
+    ;;
+
+    OSF1:*)
+        # Tru64 UNIX
+        have=NGX_TRU64 . auto/have_headers
+        have=NGX_HAVE_STRERROR_R . auto/nohave
+        CORE_INCS="$UNIX_INCS"
+        CORE_DEPS="$UNIX_DEPS $POSIX_DEPS"
+        CORE_SRCS="$UNIX_SRCS"
+    ;;
+
+    GNU:*)
+        # GNU Hurd
+        have=NGX_GNU_HURD . auto/have_headers
+        CORE_INCS="$UNIX_INCS"
+        CORE_DEPS="$UNIX_DEPS $POSIX_DEPS"
+        CORE_SRCS="$UNIX_SRCS"
+        CC_AUX_FLAGS="$CC_AUX_FLAGS -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64"
+    ;;
+
+    *)
+        CORE_INCS="$UNIX_INCS"
+        CORE_DEPS="$UNIX_DEPS $POSIX_DEPS"
+        CORE_SRCS="$UNIX_SRCS"
+    ;;
+
+esac
+
+
+case "$NGX_MACHINE" in
+
+    i386 | i686 | i86pc)
+        have=NGX_HAVE_NONALIGNED . auto/have
+        NGX_MACH_CACHE_LINE=32
+    ;;
+
+    amd64 | x86_64)
+        have=NGX_HAVE_NONALIGNED . auto/have
+        NGX_MACH_CACHE_LINE=64
+    ;;
+
+    sun4u | sun4v | sparc | sparc64)
+        have=NGX_ALIGNMENT value=16 . auto/define
+        # TODO
+        NGX_MACH_CACHE_LINE=64
+    ;;
+
+    ia64 )
+        have=NGX_ALIGNMENT value=16 . auto/define
+        # TODO
+        NGX_MACH_CACHE_LINE=64
+    ;;
+
+    aarch64 )
+        have=NGX_ALIGNMENT value=16 . auto/define
+        NGX_MACH_CACHE_LINE=64
+    ;;
+
+    *)
+        have=NGX_ALIGNMENT value=16 . auto/define
+        NGX_MACH_CACHE_LINE=32
+    ;;
+
+esac
+
+if test -z "$NGX_CPU_CACHE_LINE"; then
+    NGX_CPU_CACHE_LINE=$NGX_MACH_CACHE_LINE
+fi
+
+have=NGX_CPU_CACHE_LINE value=$NGX_CPU_CACHE_LINE . auto/define
diff --git a/nginx/auto/os/darwin b/nginx/auto/os/darwin
new file mode 100644 (file)
index 0000000..429468f
--- /dev/null
@@ -0,0 +1,120 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+have=NGX_DARWIN . auto/have_headers
+
+CORE_INCS="$UNIX_INCS"
+CORE_DEPS="$UNIX_DEPS $DARWIN_DEPS"
+CORE_SRCS="$UNIX_SRCS $DARWIN_SRCS"
+
+
+
+ngx_spacer='
+'
+
+MAIN_LINK=
+MODULE_LINK="-shared -Wl,-undefined,dynamic_lookup"
+
+CC_AUX_FLAGS="$CC_AUX_FLAGS -D__APPLE_USE_RFC_3542"
+
+
+# kqueue
+
+echo " + kqueue found"
+have=NGX_HAVE_KQUEUE . auto/have
+have=NGX_HAVE_CLEAR_EVENT . auto/have
+EVENT_MODULES="$EVENT_MODULES $KQUEUE_MODULE"
+CORE_SRCS="$CORE_SRCS $KQUEUE_SRCS"
+EVENT_FOUND=YES
+NGX_KQUEUE_CHECKED=YES
+
+ngx_feature="kqueue's EVFILT_TIMER"
+ngx_feature_name="NGX_HAVE_TIMER_EVENT"
+ngx_feature_run=yes
+ngx_feature_incs="#include <sys/event.h>
+                  #include <sys/time.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="int      kq;
+                  struct kevent    kev;
+                  struct timespec  ts;
+
+                  if ((kq = kqueue()) == -1) return 1;
+
+                  kev.ident = 0;
+                  kev.filter = EVFILT_TIMER;
+                  kev.flags = EV_ADD|EV_ENABLE;
+                  kev.fflags = 0;
+                  kev.data = 1000;
+                  kev.udata = 0;
+
+                  ts.tv_sec = 0;
+                  ts.tv_nsec = 0;
+
+                  if (kevent(kq, &kev, 1, &kev, 1, &ts) == -1) return 1;
+
+                  if (kev.flags & EV_ERROR) return 1;"
+
+. auto/feature
+
+
+ngx_feature="Darwin 64-bit kqueue millisecond timeout bug"
+ngx_feature_name=NGX_DARWIN_KEVENT_BUG
+ngx_feature_run=bug
+ngx_feature_incs="#include <sys/event.h>
+                  #include <sys/time.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="int  kq;
+                  struct kevent    kev;
+                  struct timespec  ts;
+                  struct timeval   tv, tv0;
+
+                  kq = kqueue();
+
+                  ts.tv_sec = 0;
+                  ts.tv_nsec = 999000000;
+
+                  gettimeofday(&tv, 0);
+                  kevent(kq, NULL, 0, &kev, 1, &ts);
+                  gettimeofday(&tv0, 0);
+                  timersub(&tv0, &tv, &tv);
+
+                  if (tv.tv_sec * 1000000 + tv.tv_usec < 900000) return 1;"
+
+. auto/feature
+
+
+# sendfile()
+
+ngx_feature="sendfile()"
+ngx_feature_name="NGX_HAVE_SENDFILE"
+ngx_feature_run=yes
+ngx_feature_incs="#include <sys/types.h>
+                  #include <sys/socket.h>
+                  #include <sys/uio.h>
+                  #include <sys/errno.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="int s = 0, fd = 1;
+                  off_t n; off_t off = 0;
+                  n = sendfile(s, fd, off, &n, NULL, 0);
+                  if (n == -1 && errno == ENOSYS) return 1"
+. auto/feature
+
+if [ $ngx_found = yes ]; then
+    CORE_SRCS="$CORE_SRCS $DARWIN_SENDFILE_SRCS"
+fi
+
+
+ngx_feature="atomic(3)"
+ngx_feature_name=NGX_DARWIN_ATOMIC
+ngx_feature_run=no
+ngx_feature_incs="#include <libkern/OSAtomic.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="int32_t  lock = 0;
+                  if (!OSAtomicCompareAndSwap32Barrier(0, 1, &lock)) return 1"
+. auto/feature
diff --git a/nginx/auto/os/freebsd b/nginx/auto/os/freebsd
new file mode 100644 (file)
index 0000000..937ca20
--- /dev/null
@@ -0,0 +1,107 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+have=NGX_FREEBSD . auto/have_headers
+
+CORE_INCS="$UNIX_INCS"
+CORE_DEPS="$UNIX_DEPS $FREEBSD_DEPS"
+CORE_SRCS="$UNIX_SRCS $FREEBSD_SRCS"
+
+ngx_spacer='
+'
+
+
+# __FreeBSD_version and sysctl kern.osreldate are the best ways
+# to determine whether some capability exists and is safe to use.
+# __FreeBSD_version is used for the testing of the build environment.
+# sysctl kern.osreldate is used for the testing of the kernel capabilities.
+
+version=`grep "#define __FreeBSD_version" /usr/include/osreldate.h \
+         | sed -e 's/^.* \(.*\)$/\1/'`
+
+osreldate=`/sbin/sysctl -n kern.osreldate`
+
+
+# setproctitle() in libutil
+
+if [ \( $version -ge 500000 -a $version -lt 500012 \) \
+     -o $version -lt 410002 ]
+then
+    echo " + setproctitle() in libutil"
+
+    CORE_LIBS="$CORE_LIBS -lutil"
+    NGX_SETPROCTITLE_LIB="-lutil"
+fi
+
+# sendfile
+
+if [ $osreldate -gt 300007 ]; then
+    echo " + sendfile() found"
+
+    have=NGX_HAVE_SENDFILE . auto/have
+    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"
+
+        have=NGX_HAVE_AIO_SENDFILE . auto/have
+    fi
+fi
+
+# POSIX semaphores
+# http://www.freebsd.org/cgi/query-pr.cgi?pr=kern/127545
+
+if [ $osreldate -ge 701106 ]; then
+    echo " + POSIX semaphores should work"
+else
+    have=NGX_HAVE_POSIX_SEM . auto/nohave
+fi
+
+
+# kqueue
+
+if [ \( $osreldate -lt 500000 -a $osreldate -ge 410000 \) \
+     -o $osreldate -ge 500011 ]
+then
+    echo " + kqueue found"
+
+    have=NGX_HAVE_KQUEUE . auto/have
+    have=NGX_HAVE_CLEAR_EVENT . auto/have
+    EVENT_MODULES="$EVENT_MODULES $KQUEUE_MODULE"
+    CORE_SRCS="$CORE_SRCS $KQUEUE_SRCS"
+    EVENT_FOUND=YES
+fi
+
+
+NGX_KQUEUE_CHECKED=YES
+
+
+# kqueue's NOTE_LOWAT
+
+if [ \( $version -lt 500000 -a $version -ge 430000 \) \
+     -o $version -ge 500018 ]
+then
+    echo " + kqueue's NOTE_LOWAT found"
+    have=NGX_HAVE_LOWAT_EVENT . auto/have
+fi
+
+# kqueue's EVFILT_TIMER
+
+if [ \( $version -lt 500000 -a $version -ge 440001 \) \
+     -o $version -ge 500023 ]
+then
+    echo " + kqueue's EVFILT_TIMER found"
+    have=NGX_HAVE_TIMER_EVENT . auto/have
+fi
+
+
+# cpuset_setaffinity()
+
+if [ $version -ge 701000 ]; then
+    echo " + cpuset_setaffinity() found"
+    have=NGX_HAVE_CPUSET_SETAFFINITY . auto/have
+fi
diff --git a/nginx/auto/os/linux b/nginx/auto/os/linux
new file mode 100644 (file)
index 0000000..2c8a9bb
--- /dev/null
@@ -0,0 +1,208 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+have=NGX_LINUX . auto/have_headers
+
+CORE_INCS="$UNIX_INCS"
+CORE_DEPS="$UNIX_DEPS $LINUX_DEPS"
+CORE_SRCS="$UNIX_SRCS $LINUX_SRCS"
+
+ngx_spacer='
+'
+
+cc_aux_flags="$CC_AUX_FLAGS"
+CC_AUX_FLAGS="$cc_aux_flags -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64"
+
+
+# Linux kernel version
+
+version=$((`uname -r \
+    | sed -n -e 's/^\([0-9][0-9]*\)\.\([0-9][0-9]*\)\.\([0-9][0-9]*\).*/ \
+                                                 \1*256*256+\2*256+\3/p' \
+             -e 's/^\([0-9][0-9]*\)\.\([0-9][0-9]*\).*/\1*256*256+\2*256/p'`))
+
+version=${version:-0}
+
+
+# posix_fadvise64() had been implemented in 2.5.60
+
+if [ $version -lt 132412 ]; then
+    have=NGX_HAVE_POSIX_FADVISE . auto/nohave
+fi
+
+# epoll, EPOLLET version
+
+ngx_feature="epoll"
+ngx_feature_name="NGX_HAVE_EPOLL"
+ngx_feature_run=yes
+ngx_feature_incs="#include <sys/epoll.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="int efd = 0;
+                  struct epoll_event ee;
+                  ee.events = EPOLLIN|EPOLLOUT|EPOLLET;
+                  ee.data.ptr = NULL;
+                  (void) ee;
+                  efd = epoll_create(100);
+                  if (efd == -1) return 1;"
+. auto/feature
+
+if [ $ngx_found = yes ]; then
+    have=NGX_HAVE_CLEAR_EVENT . auto/have
+    CORE_SRCS="$CORE_SRCS $EPOLL_SRCS"
+    EVENT_MODULES="$EVENT_MODULES $EPOLL_MODULE"
+    EVENT_FOUND=YES
+
+
+    # EPOLLRDHUP appeared in Linux 2.6.17, glibc 2.8
+
+    ngx_feature="EPOLLRDHUP"
+    ngx_feature_name="NGX_HAVE_EPOLLRDHUP"
+    ngx_feature_run=no
+    ngx_feature_incs="#include <sys/epoll.h>"
+    ngx_feature_path=
+    ngx_feature_libs=
+    ngx_feature_test="int efd = 0, fd = 0;
+                      struct epoll_event ee;
+                      ee.events = EPOLLIN|EPOLLRDHUP|EPOLLET;
+                      ee.data.ptr = NULL;
+                      epoll_ctl(efd, EPOLL_CTL_ADD, fd, &ee)"
+    . auto/feature
+
+
+    # EPOLLEXCLUSIVE appeared in Linux 4.5, glibc 2.24
+
+    ngx_feature="EPOLLEXCLUSIVE"
+    ngx_feature_name="NGX_HAVE_EPOLLEXCLUSIVE"
+    ngx_feature_run=no
+    ngx_feature_incs="#include <sys/epoll.h>"
+    ngx_feature_path=
+    ngx_feature_libs=
+    ngx_feature_test="int efd = 0, fd = 0;
+                      struct epoll_event ee;
+                      ee.events = EPOLLIN|EPOLLEXCLUSIVE;
+                      ee.data.ptr = NULL;
+                      epoll_ctl(efd, EPOLL_CTL_ADD, fd, &ee)"
+    . auto/feature
+fi
+
+
+# O_PATH and AT_EMPTY_PATH were introduced in 2.6.39, glibc 2.14
+
+ngx_feature="O_PATH"
+ngx_feature_name="NGX_HAVE_O_PATH"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/types.h>
+                  #include <sys/stat.h>
+                  #include <fcntl.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="int fd; struct stat sb;
+                  fd = openat(AT_FDCWD, \".\", O_PATH|O_DIRECTORY|O_NOFOLLOW);
+                  if (fstatat(fd, \"\", &sb, AT_EMPTY_PATH) != 0) return 1"
+. auto/feature
+
+
+# sendfile()
+
+CC_AUX_FLAGS="$cc_aux_flags -D_GNU_SOURCE"
+ngx_feature="sendfile()"
+ngx_feature_name="NGX_HAVE_SENDFILE"
+ngx_feature_run=yes
+ngx_feature_incs="#include <sys/sendfile.h>
+                  #include <errno.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="int s = 0, fd = 1;
+                  ssize_t n; off_t off = 0;
+                  n = sendfile(s, fd, &off, 1);
+                  if (n == -1 && errno == ENOSYS) return 1"
+. auto/feature
+
+if [ $ngx_found = yes ]; then
+    CORE_SRCS="$CORE_SRCS $LINUX_SENDFILE_SRCS"
+fi
+
+
+# sendfile64()
+
+CC_AUX_FLAGS="$cc_aux_flags -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64"
+ngx_feature="sendfile64()"
+ngx_feature_name="NGX_HAVE_SENDFILE64"
+ngx_feature_run=yes
+ngx_feature_incs="#include <sys/sendfile.h>
+                  #include <errno.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="int s = 0, fd = 1;
+                  ssize_t n; off_t off = 0;
+                  n = sendfile(s, fd, &off, 1);
+                  if (n == -1 && errno == ENOSYS) return 1"
+. auto/feature
+
+
+ngx_include="sys/prctl.h"; . auto/include
+
+# prctl(PR_SET_DUMPABLE)
+
+ngx_feature="prctl(PR_SET_DUMPABLE)"
+ngx_feature_name="NGX_HAVE_PR_SET_DUMPABLE"
+ngx_feature_run=yes
+ngx_feature_incs="#include <sys/prctl.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) == -1) return 1"
+. auto/feature
+
+
+# prctl(PR_SET_KEEPCAPS)
+
+ngx_feature="prctl(PR_SET_KEEPCAPS)"
+ngx_feature_name="NGX_HAVE_PR_SET_KEEPCAPS"
+ngx_feature_run=yes
+ngx_feature_incs="#include <sys/prctl.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) return 1"
+. auto/feature
+
+
+# capabilities
+
+ngx_feature="capabilities"
+ngx_feature_name="NGX_HAVE_CAPABILITIES"
+ngx_feature_run=no
+ngx_feature_incs="#include <linux/capability.h>
+                  #include <sys/syscall.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="struct __user_cap_data_struct    data;
+                  struct __user_cap_header_struct  header;
+
+                  header.version = _LINUX_CAPABILITY_VERSION_1;
+                  data.effective = CAP_TO_MASK(CAP_NET_RAW);
+                  data.permitted = 0;
+
+                  (void) SYS_capset"
+. auto/feature
+
+
+# crypt_r()
+
+ngx_feature="crypt_r()"
+ngx_feature_name="NGX_HAVE_GNU_CRYPT_R"
+ngx_feature_run=no
+ngx_feature_incs="#include <crypt.h>"
+ngx_feature_path=
+ngx_feature_libs=-lcrypt
+ngx_feature_test="struct crypt_data  cd;
+                  crypt_r(\"key\", \"salt\", &cd);"
+. auto/feature
+
+
+ngx_include="sys/vfs.h";     . auto/include
+
+
+CC_AUX_FLAGS="$cc_aux_flags -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64"
diff --git a/nginx/auto/os/solaris b/nginx/auto/os/solaris
new file mode 100644 (file)
index 0000000..1dcfe84
--- /dev/null
@@ -0,0 +1,61 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+have=NGX_SOLARIS . auto/have_headers
+
+CORE_INCS="$UNIX_INCS"
+CORE_DEPS="$UNIX_DEPS $SOLARIS_DEPS"
+CORE_SRCS="$UNIX_SRCS $SOLARIS_SRCS "
+CORE_LIBS="$CORE_LIBS -lsocket -lnsl"
+
+NGX_RPATH=YES
+
+# Solaris's make does not support a blank line between target and rules
+ngx_spacer=
+
+CC_AUX_FLAGS="$CC_AUX_FLAGS -D_FILE_OFFSET_BITS=64 -lsocket -lnsl"
+
+
+if [ $ZLIB_ASM != NO ]; then
+    echo "$0: error: the --with-zlib-asm=CPU option is not supported"
+    echo "on that platform"
+    echo
+
+    exit 1
+fi
+
+
+ngx_feature="sendfilev()"
+ngx_feature_name="NGX_HAVE_SENDFILE"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/sendfile.h>"
+ngx_feature_path=
+ngx_feature_libs="-lsendfile"
+ngx_feature_test="int fd = 1; sendfilevec_t vec[1];
+                  size_t sent; ssize_t n;
+                  n = sendfilev(fd, vec, 1, &sent);
+                  if (n == -1) return 1"
+. auto/feature
+
+
+if [ $ngx_found = yes ]; then
+    CORE_SRCS="$CORE_SRCS $SOLARIS_SENDFILEV_SRCS"
+    CORE_LIBS="$CORE_LIBS -lsendfile"
+fi
+
+
+ngx_feature="event ports"
+ngx_feature_name="NGX_HAVE_EVENTPORT"
+ngx_feature_run=no
+ngx_feature_incs="#include <port.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="(void) port_create()"
+. auto/feature
+
+if [ $ngx_found = yes ]; then
+    CORE_SRCS="$CORE_SRCS $EVENTPORT_SRCS"
+    EVENT_MODULES="$EVENT_MODULES $EVENTPORT_MODULE"
+fi
diff --git a/nginx/auto/os/win32 b/nginx/auto/os/win32
new file mode 100644 (file)
index 0000000..7a82774
--- /dev/null
@@ -0,0 +1,43 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+have=NGX_WIN32 . auto/have_headers
+
+CORE_INCS="$WIN32_INCS"
+CORE_DEPS="$WIN32_DEPS"
+CORE_SRCS="$WIN32_SRCS $IOCP_SRCS"
+OS_CONFIG="$WIN32_CONFIG"
+NGX_ICONS="$NGX_WIN32_ICONS"
+SELECT_SRCS=$WIN32_SELECT_SRCS
+
+ngx_pic_opt=
+ngx_binext=".exe"
+
+case "$NGX_CC_NAME" in
+
+    gcc)
+        CORE_LIBS="$CORE_LIBS -ladvapi32 -lws2_32"
+        MAIN_LINK="$MAIN_LINK -Wl,--export-all-symbols"
+        MAIN_LINK="$MAIN_LINK -Wl,--out-implib=$NGX_OBJS/libnginx.a"
+        MODULE_LINK="-shared -L $NGX_OBJS -lnginx"
+    ;;
+
+    *)
+        CORE_LIBS="$CORE_LIBS advapi32.lib ws2_32.lib"
+    ;;
+
+esac
+
+EVENT_MODULES="$EVENT_MODULES $IOCP_MODULE"
+EVENT_FOUND=YES
+
+if [ $EVENT_SELECT = NO ]; then
+    CORE_SRCS="$CORE_SRCS $SELECT_SRCS"
+    EVENT_MODULES="$EVENT_MODULES $SELECT_MODULE"
+fi
+
+have=NGX_HAVE_INET6 . auto/have
+
+have=NGX_HAVE_IOCP . auto/have
diff --git a/nginx/auto/sources b/nginx/auto/sources
new file mode 100644 (file)
index 0000000..1398147
--- /dev/null
@@ -0,0 +1,255 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+CORE_MODULES="ngx_core_module ngx_errlog_module ngx_conf_module"
+
+CORE_INCS="src/core"
+
+CORE_DEPS="src/core/nginx.h \
+           src/core/ngx_config.h \
+           src/core/ngx_core.h \
+           src/core/ngx_log.h \
+           src/core/ngx_palloc.h \
+           src/core/ngx_array.h \
+           src/core/ngx_list.h \
+           src/core/ngx_hash.h \
+           src/core/ngx_buf.h \
+           src/core/ngx_queue.h \
+           src/core/ngx_string.h \
+           src/core/ngx_parse.h \
+           src/core/ngx_parse_time.h \
+           src/core/ngx_inet.h \
+           src/core/ngx_file.h \
+           src/core/ngx_crc.h \
+           src/core/ngx_crc32.h \
+           src/core/ngx_murmurhash.h \
+           src/core/ngx_md5.h \
+           src/core/ngx_sha1.h \
+           src/core/ngx_rbtree.h \
+           src/core/ngx_radix_tree.h \
+           src/core/ngx_rwlock.h \
+           src/core/ngx_slab.h \
+           src/core/ngx_times.h \
+           src/core/ngx_shmtx.h \
+           src/core/ngx_connection.h \
+           src/core/ngx_cycle.h \
+           src/core/ngx_conf_file.h \
+           src/core/ngx_module.h \
+           src/core/ngx_resolver.h \
+           src/core/ngx_open_file_cache.h \
+           src/core/ngx_crypt.h \
+           src/core/ngx_proxy_protocol.h \
+           src/core/ngx_syslog.h"
+
+
+CORE_SRCS="src/core/nginx.c \
+           src/core/ngx_log.c \
+           src/core/ngx_palloc.c \
+           src/core/ngx_array.c \
+           src/core/ngx_list.c \
+           src/core/ngx_hash.c \
+           src/core/ngx_buf.c \
+           src/core/ngx_queue.c \
+           src/core/ngx_output_chain.c \
+           src/core/ngx_string.c \
+           src/core/ngx_parse.c \
+           src/core/ngx_parse_time.c \
+           src/core/ngx_inet.c \
+           src/core/ngx_file.c \
+           src/core/ngx_crc32.c \
+           src/core/ngx_murmurhash.c \
+           src/core/ngx_md5.c \
+           src/core/ngx_sha1.c \
+           src/core/ngx_rbtree.c \
+           src/core/ngx_radix_tree.c \
+           src/core/ngx_slab.c \
+           src/core/ngx_times.c \
+           src/core/ngx_shmtx.c \
+           src/core/ngx_connection.c \
+           src/core/ngx_cycle.c \
+           src/core/ngx_spinlock.c \
+           src/core/ngx_rwlock.c \
+           src/core/ngx_cpuinfo.c \
+           src/core/ngx_conf_file.c \
+           src/core/ngx_module.c \
+           src/core/ngx_resolver.c \
+           src/core/ngx_open_file_cache.c \
+           src/core/ngx_crypt.c \
+           src/core/ngx_proxy_protocol.c \
+           src/core/ngx_syslog.c"
+
+
+EVENT_MODULES="ngx_events_module ngx_event_core_module"
+
+EVENT_INCS="src/event src/event/modules"
+
+EVENT_DEPS="src/event/ngx_event.h \
+            src/event/ngx_event_timer.h \
+            src/event/ngx_event_posted.h \
+            src/event/ngx_event_connect.h \
+            src/event/ngx_event_pipe.h"
+
+EVENT_SRCS="src/event/ngx_event.c \
+            src/event/ngx_event_timer.c \
+            src/event/ngx_event_posted.c \
+            src/event/ngx_event_accept.c \
+            src/event/ngx_event_connect.c \
+            src/event/ngx_event_pipe.c"
+
+
+SELECT_MODULE=ngx_select_module
+SELECT_SRCS=src/event/modules/ngx_select_module.c
+WIN32_SELECT_SRCS=src/event/modules/ngx_win32_select_module.c
+
+POLL_MODULE=ngx_poll_module
+POLL_SRCS=src/event/modules/ngx_poll_module.c
+
+KQUEUE_MODULE=ngx_kqueue_module
+KQUEUE_SRCS=src/event/modules/ngx_kqueue_module.c
+
+DEVPOLL_MODULE=ngx_devpoll_module
+DEVPOLL_SRCS=src/event/modules/ngx_devpoll_module.c
+
+EVENTPORT_MODULE=ngx_eventport_module
+EVENTPORT_SRCS=src/event/modules/ngx_eventport_module.c
+
+EPOLL_MODULE=ngx_epoll_module
+EPOLL_SRCS=src/event/modules/ngx_epoll_module.c
+
+IOCP_MODULE=ngx_iocp_module
+IOCP_SRCS=src/event/modules/ngx_iocp_module.c
+
+FILE_AIO_SRCS="src/os/unix/ngx_file_aio_read.c"
+LINUX_AIO_SRCS="src/os/unix/ngx_linux_aio_read.c"
+
+UNIX_INCS="$CORE_INCS $EVENT_INCS src/os/unix"
+
+UNIX_DEPS="$CORE_DEPS $EVENT_DEPS \
+            src/os/unix/ngx_time.h \
+            src/os/unix/ngx_errno.h \
+            src/os/unix/ngx_alloc.h \
+            src/os/unix/ngx_files.h \
+            src/os/unix/ngx_channel.h \
+            src/os/unix/ngx_shmem.h \
+            src/os/unix/ngx_process.h \
+            src/os/unix/ngx_setaffinity.h \
+            src/os/unix/ngx_setproctitle.h \
+            src/os/unix/ngx_atomic.h \
+            src/os/unix/ngx_gcc_atomic_x86.h \
+            src/os/unix/ngx_thread.h \
+            src/os/unix/ngx_socket.h \
+            src/os/unix/ngx_os.h \
+            src/os/unix/ngx_user.h \
+            src/os/unix/ngx_dlopen.h \
+            src/os/unix/ngx_process_cycle.h"
+
+# add to UNIX_DEPS
+#            src/os/unix/ngx_gcc_atomic_amd64.h \
+#            src/os/unix/ngx_gcc_atomic_sparc64.h \
+#            src/os/unix/ngx_gcc_atomic_ppc.h \
+#            src/os/unix/ngx_sunpro_atomic_sparc64.h \
+#            src/os/unix/ngx_sunpro_x86.il \
+#            src/os/unix/ngx_sunpro_amd64.il \
+#            src/os/unix/ngx_sunpro_sparc64.il \
+
+
+UNIX_SRCS="$CORE_SRCS $EVENT_SRCS \
+            src/os/unix/ngx_time.c \
+            src/os/unix/ngx_errno.c \
+            src/os/unix/ngx_alloc.c \
+            src/os/unix/ngx_files.c \
+            src/os/unix/ngx_socket.c \
+            src/os/unix/ngx_recv.c \
+            src/os/unix/ngx_readv_chain.c \
+            src/os/unix/ngx_udp_recv.c \
+            src/os/unix/ngx_send.c \
+            src/os/unix/ngx_writev_chain.c \
+            src/os/unix/ngx_udp_send.c \
+            src/os/unix/ngx_udp_sendmsg_chain.c \
+            src/os/unix/ngx_channel.c \
+            src/os/unix/ngx_shmem.c \
+            src/os/unix/ngx_process.c \
+            src/os/unix/ngx_daemon.c \
+            src/os/unix/ngx_setaffinity.c \
+            src/os/unix/ngx_setproctitle.c \
+            src/os/unix/ngx_posix_init.c \
+            src/os/unix/ngx_user.c \
+            src/os/unix/ngx_dlopen.c \
+            src/os/unix/ngx_process_cycle.c"
+
+POSIX_DEPS=src/os/unix/ngx_posix_config.h
+
+THREAD_POOL_MODULE=ngx_thread_pool_module
+THREAD_POOL_DEPS=src/core/ngx_thread_pool.h
+THREAD_POOL_SRCS="src/core/ngx_thread_pool.c
+                  src/os/unix/ngx_thread_cond.c
+                  src/os/unix/ngx_thread_mutex.c
+                  src/os/unix/ngx_thread_id.c"
+
+FREEBSD_DEPS="src/os/unix/ngx_freebsd_config.h src/os/unix/ngx_freebsd.h"
+FREEBSD_SRCS=src/os/unix/ngx_freebsd_init.c
+FREEBSD_SENDFILE_SRCS=src/os/unix/ngx_freebsd_sendfile_chain.c
+
+LINUX_DEPS="src/os/unix/ngx_linux_config.h src/os/unix/ngx_linux.h"
+LINUX_SRCS=src/os/unix/ngx_linux_init.c
+LINUX_SENDFILE_SRCS=src/os/unix/ngx_linux_sendfile_chain.c
+
+
+SOLARIS_DEPS="src/os/unix/ngx_solaris_config.h src/os/unix/ngx_solaris.h"
+SOLARIS_SRCS=src/os/unix/ngx_solaris_init.c
+SOLARIS_SENDFILEV_SRCS=src/os/unix/ngx_solaris_sendfilev_chain.c
+
+
+DARWIN_DEPS="src/os/unix/ngx_darwin_config.h src/os/unix/ngx_darwin.h"
+DARWIN_SRCS=src/os/unix/ngx_darwin_init.c
+DARWIN_SENDFILE_SRCS=src/os/unix/ngx_darwin_sendfile_chain.c
+
+
+WIN32_INCS="$CORE_INCS $EVENT_INCS src/os/win32"
+
+WIN32_DEPS="$CORE_DEPS $EVENT_DEPS \
+            src/os/win32/ngx_win32_config.h \
+            src/os/win32/ngx_time.h \
+            src/os/win32/ngx_errno.h \
+            src/os/win32/ngx_alloc.h \
+            src/os/win32/ngx_files.h \
+            src/os/win32/ngx_shmem.h \
+            src/os/win32/ngx_process.h \
+            src/os/win32/ngx_atomic.h \
+            src/os/win32/ngx_thread.h \
+            src/os/win32/ngx_socket.h \
+            src/os/win32/ngx_os.h \
+            src/os/win32/ngx_user.h \
+            src/os/win32/ngx_dlopen.h \
+            src/os/win32/ngx_process_cycle.h"
+
+WIN32_CONFIG=src/os/win32/ngx_win32_config.h
+
+WIN32_SRCS="$CORE_SRCS $EVENT_SRCS \
+            src/os/win32/ngx_errno.c \
+            src/os/win32/ngx_alloc.c \
+            src/os/win32/ngx_files.c \
+            src/os/win32/ngx_shmem.c \
+            src/os/win32/ngx_time.c \
+            src/os/win32/ngx_process.c \
+            src/os/win32/ngx_thread.c \
+            src/os/win32/ngx_socket.c \
+            src/os/win32/ngx_wsarecv.c \
+            src/os/win32/ngx_wsarecv_chain.c \
+            src/os/win32/ngx_udp_wsarecv.c \
+            src/os/win32/ngx_wsasend.c \
+            src/os/win32/ngx_wsasend_chain.c \
+            src/os/win32/ngx_win32_init.c \
+            src/os/win32/ngx_user.c \
+            src/os/win32/ngx_dlopen.c \
+            src/os/win32/ngx_event_log.c \
+            src/os/win32/ngx_process_cycle.c \
+            src/event/ngx_event_acceptex.c"
+
+NGX_WIN32_ICONS="src/os/win32/nginx.ico"
+NGX_WIN32_RC="src/os/win32/nginx.rc"
+
+
+HTTP_FILE_CACHE_SRCS=src/http/ngx_http_file_cache.c
diff --git a/nginx/auto/stubs b/nginx/auto/stubs
new file mode 100644 (file)
index 0000000..d8bc1f0
--- /dev/null
@@ -0,0 +1,8 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+have=NGX_SUPPRESS_WARN . auto/have
+
+have=NGX_SMP . auto/have
diff --git a/nginx/auto/summary b/nginx/auto/summary
new file mode 100644 (file)
index 0000000..9aa776e
--- /dev/null
@@ -0,0 +1,82 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+echo
+echo "Configuration summary"
+
+
+if [ $USE_THREADS = YES ]; then
+    echo "  + using threads"
+fi
+
+if [ $USE_PCRE = DISABLED ]; then
+    echo "  + PCRE library is disabled"
+
+else
+    case $PCRE in
+        YES)   echo "  + using system PCRE library" ;;
+        NONE)  echo "  + PCRE library is not used" ;;
+        *)     echo "  + using PCRE library: $PCRE" ;;
+    esac
+fi
+
+case $OPENSSL in
+    YES)   echo "  + using system OpenSSL library" ;;
+    NONE)  echo "  + OpenSSL library is not used" ;;
+    *)     echo "  + using OpenSSL library: $OPENSSL" ;;
+esac
+
+case $ZLIB in
+    YES)   echo "  + using system zlib library" ;;
+    NONE)  echo "  + zlib library is not used" ;;
+    *)     echo "  + using zlib library: $ZLIB" ;;
+esac
+
+case $NGX_LIBATOMIC in
+    YES)   echo "  + using system libatomic_ops library" ;;
+    NO)    ;; # not used
+    *)     echo "  + using libatomic_ops library: $NGX_LIBATOMIC" ;;
+esac
+
+echo
+
+
+cat << END
+  nginx path prefix: "$NGX_PREFIX"
+  nginx binary file: "$NGX_SBIN_PATH"
+  nginx modules path: "$NGX_MODULES_PATH"
+  nginx configuration prefix: "$NGX_CONF_PREFIX"
+  nginx configuration file: "$NGX_CONF_PATH"
+  nginx pid file: "$NGX_PID_PATH"
+END
+
+if test -n "$NGX_ERROR_LOG_PATH"; then
+    echo "  nginx error log file: \"$NGX_ERROR_LOG_PATH\""
+else
+    echo "  nginx logs errors to stderr"
+fi
+
+cat << END
+  nginx http access log file: "$NGX_HTTP_LOG_PATH"
+  nginx http client request body temporary files: "$NGX_HTTP_CLIENT_TEMP_PATH"
+END
+
+if [ $HTTP_PROXY = YES ]; then
+    echo "  nginx http proxy temporary files: \"$NGX_HTTP_PROXY_TEMP_PATH\""
+fi
+
+if [ $HTTP_FASTCGI = YES ]; then
+    echo "  nginx http fastcgi temporary files: \"$NGX_HTTP_FASTCGI_TEMP_PATH\""
+fi
+
+if [ $HTTP_UWSGI = YES ]; then
+    echo "  nginx http uwsgi temporary files: \"$NGX_HTTP_UWSGI_TEMP_PATH\""
+fi
+
+if [ $HTTP_SCGI = YES ]; then
+    echo "  nginx http scgi temporary files: \"$NGX_HTTP_SCGI_TEMP_PATH\""
+fi
+
+echo "$NGX_POST_CONF_MSG"
diff --git a/nginx/auto/threads b/nginx/auto/threads
new file mode 100644 (file)
index 0000000..943127f
--- /dev/null
@@ -0,0 +1,21 @@
+
+# Copyright (C) Nginx, Inc.
+
+
+if [ $USE_THREADS = YES ]; then
+
+    if [ "$NGX_PLATFORM" = win32 ]; then
+        cat << END
+
+$0: --with-threads is not supported on Windows
+
+END
+        exit 1
+    fi
+
+    have=NGX_THREADS . auto/have
+    CORE_DEPS="$CORE_DEPS $THREAD_POOL_DEPS"
+    CORE_SRCS="$CORE_SRCS $THREAD_POOL_SRCS"
+    CORE_LIBS="$CORE_LIBS -lpthread"
+    NGX_LIBPTHREAD="-lpthread"
+fi
diff --git a/nginx/auto/types/sizeof b/nginx/auto/types/sizeof
new file mode 100644 (file)
index 0000000..480d8cf
--- /dev/null
@@ -0,0 +1,76 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+echo $ngx_n "checking for $ngx_type size ...$ngx_c"
+
+cat << END >> $NGX_AUTOCONF_ERR
+
+----------------------------------------
+checking for $ngx_type size
+
+END
+
+ngx_size=
+
+cat << END > $NGX_AUTOTEST.c
+
+#include <sys/types.h>
+#include <sys/time.h>
+$NGX_INCLUDE_UNISTD_H
+#include <signal.h>
+#include <stdio.h>
+#include <sys/resource.h>
+$NGX_INCLUDE_INTTYPES_H
+$NGX_INCLUDE_AUTO_CONFIG_H
+
+int main(void) {
+    printf("%d", (int) sizeof($ngx_type));
+    return 0;
+}
+
+END
+
+
+ngx_test="$CC $CC_TEST_FLAGS $CC_AUX_FLAGS \
+          -o $NGX_AUTOTEST $NGX_AUTOTEST.c $NGX_LD_OPT $ngx_feature_libs"
+
+eval "$ngx_test >> $NGX_AUTOCONF_ERR 2>&1"
+
+
+if [ -x $NGX_AUTOTEST ]; then
+    ngx_size=`$NGX_AUTOTEST`
+    echo " $ngx_size bytes"
+fi
+
+
+case $ngx_size in
+    4)
+        ngx_max_value=2147483647
+        ngx_max_len='(sizeof("-2147483648") - 1)'
+    ;;
+
+    8)
+        ngx_max_value=9223372036854775807LL
+        ngx_max_len='(sizeof("-9223372036854775808") - 1)'
+    ;;
+
+    *)
+        echo
+        echo "$0: error: can not detect $ngx_type size"
+
+        echo "----------"    >> $NGX_AUTOCONF_ERR
+        cat $NGX_AUTOTEST.c  >> $NGX_AUTOCONF_ERR
+        echo "----------"    >> $NGX_AUTOCONF_ERR
+        echo $ngx_test       >> $NGX_AUTOCONF_ERR
+        echo "----------"    >> $NGX_AUTOCONF_ERR
+
+        rm -rf $NGX_AUTOTEST*
+
+        exit 1
+esac
+
+
+rm -rf $NGX_AUTOTEST*
+
diff --git a/nginx/auto/types/typedef b/nginx/auto/types/typedef
new file mode 100644 (file)
index 0000000..d54c289
--- /dev/null
@@ -0,0 +1,82 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+echo $ngx_n "checking for $ngx_type ...$ngx_c"
+
+cat << END >> $NGX_AUTOCONF_ERR
+
+----------------------------------------
+checking for $ngx_type
+
+END
+
+ngx_found=no
+
+for ngx_try in $ngx_type $ngx_types
+do
+
+    cat << END > $NGX_AUTOTEST.c
+
+#include <sys/types.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <netinet/in.h>
+$NGX_INCLUDE_INTTYPES_H
+
+int main(void) {
+    $ngx_try i = 0;
+    return (int) i;
+}
+
+END
+
+    ngx_test="$CC $CC_TEST_FLAGS $CC_AUX_FLAGS \
+              -o $NGX_AUTOTEST $NGX_AUTOTEST.c $NGX_LD_OPT $ngx_feature_libs"
+
+    eval "$ngx_test >> $NGX_AUTOCONF_ERR 2>&1"
+
+    if [ -x $NGX_AUTOTEST ]; then
+        if [ $ngx_try = $ngx_type ]; then
+            echo " found"
+            ngx_found=yes
+        else
+            echo ", $ngx_try used"
+            ngx_found=$ngx_try
+        fi
+    fi
+
+    if [ $ngx_found = no ]; then
+        if [ $ngx_try = $ngx_type ]; then
+            echo $ngx_n " $ngx_try not found$ngx_c"
+        else
+            echo $ngx_n ", $ngx_try not found$ngx_c"
+        fi
+
+        echo "----------"    >> $NGX_AUTOCONF_ERR
+        cat $NGX_AUTOTEST.c  >> $NGX_AUTOCONF_ERR
+        echo "----------"    >> $NGX_AUTOCONF_ERR
+        echo $ngx_test       >> $NGX_AUTOCONF_ERR
+        echo "----------"    >> $NGX_AUTOCONF_ERR
+    fi
+
+    rm -rf $NGX_AUTOTEST*
+
+    if [ $ngx_found != no ]; then
+        break
+    fi
+done
+
+if [ $ngx_found = no ]; then
+    echo
+    echo "$0: error: can not define $ngx_type"
+
+    exit 1
+fi
+
+if [ $ngx_found != yes ]; then
+    echo "typedef $ngx_found  $ngx_type;"   >> $NGX_AUTO_CONFIG_H
+fi
diff --git a/nginx/auto/types/uintptr_t b/nginx/auto/types/uintptr_t
new file mode 100644 (file)
index 0000000..a33d6d0
--- /dev/null
@@ -0,0 +1,50 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+echo $ngx_n "checking for uintptr_t ...$ngx_c"
+
+cat << END >> $NGX_AUTOCONF_ERR
+
+----------------------------------------
+checking for uintptr_t
+
+END
+
+found=no
+
+cat << END > $NGX_AUTOTEST.c
+
+#include <sys/types.h>
+$NGX_INCLUDE_INTTYPES_H
+
+int main(void) {
+    uintptr_t i = 0;
+    return (int) i;
+}
+
+END
+
+ngx_test="$CC $CC_TEST_FLAGS $CC_AUX_FLAGS \
+          -o $NGX_AUTOTEST $NGX_AUTOTEST.c $NGX_LD_OPT"
+
+eval "$ngx_test >> $NGX_AUTOCONF_ERR 2>&1"
+
+if [ -x $NGX_AUTOTEST ]; then
+    echo " uintptr_t found"
+    found=yes
+else
+    echo $ngx_n " uintptr_t not found" $ngx_c
+fi
+
+rm -rf $NGX_AUTOTEST*
+
+
+if [ $found = no ]; then
+    found="uint`expr 8 \* $ngx_ptr_size`_t"
+    echo ", $found used"
+
+    echo "typedef $found  uintptr_t;"                   >> $NGX_AUTO_CONFIG_H
+    echo "typedef $found  intptr_t;" | sed -e 's/u//g'  >> $NGX_AUTO_CONFIG_H
+fi
diff --git a/nginx/auto/types/value b/nginx/auto/types/value
new file mode 100644 (file)
index 0000000..ac88a39
--- /dev/null
@@ -0,0 +1,12 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+cat << END >> $NGX_AUTO_CONFIG_H
+
+#ifndef $ngx_param
+#define $ngx_param  $ngx_value
+#endif
+
+END
diff --git a/nginx/auto/unix b/nginx/auto/unix
new file mode 100644 (file)
index 0000000..43d3b25
--- /dev/null
@@ -0,0 +1,1027 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+NGX_USER=${NGX_USER:-nobody}
+
+if [ -z "$NGX_GROUP" ]; then
+    if [ $NGX_USER = nobody ]; then
+        if grep nobody /etc/group 2>&1 >/dev/null; then
+            echo "checking for nobody group ... found"
+            NGX_GROUP=nobody
+        else
+            echo "checking for nobody group ... not found"
+
+            if grep nogroup /etc/group 2>&1 >/dev/null; then
+                echo "checking for nogroup group ... found"
+                NGX_GROUP=nogroup
+            else
+                echo "checking for nogroup group ... not found"
+                NGX_GROUP=nobody
+            fi
+        fi
+    else
+        NGX_GROUP=$NGX_USER
+    fi
+fi
+
+
+ngx_feature="poll()"
+ngx_feature_name=
+ngx_feature_run=no
+ngx_feature_incs="#include <poll.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="int  n; struct pollfd  pl;
+                  pl.fd = 0;
+                  pl.events = 0;
+                  pl.revents = 0;
+                  n = poll(&pl, 1, 0);
+                  if (n == -1) return 1"
+. auto/feature
+
+if [ $ngx_found = no ]; then
+    EVENT_POLL=NONE
+fi
+
+
+ngx_feature="/dev/poll"
+ngx_feature_name="NGX_HAVE_DEVPOLL"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/devpoll.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="int  n, dp; struct dvpoll  dvp;
+                  dp = 0;
+                  dvp.dp_fds = NULL;
+                  dvp.dp_nfds = 0;
+                  dvp.dp_timeout = 0;
+                  n = ioctl(dp, DP_POLL, &dvp);
+                  if (n == -1) return 1"
+. auto/feature
+
+if [ $ngx_found = yes ]; then
+    CORE_SRCS="$CORE_SRCS $DEVPOLL_SRCS"
+    EVENT_MODULES="$EVENT_MODULES $DEVPOLL_MODULE"
+    EVENT_FOUND=YES
+fi
+
+
+if test -z "$NGX_KQUEUE_CHECKED"; then
+    ngx_feature="kqueue"
+    ngx_feature_name="NGX_HAVE_KQUEUE"
+    ngx_feature_run=no
+    ngx_feature_incs="#include <sys/event.h>"
+    ngx_feature_path=
+    ngx_feature_libs=
+    ngx_feature_test="(void) kqueue()"
+    . auto/feature
+
+    if [ $ngx_found = yes ]; then
+
+        have=NGX_HAVE_CLEAR_EVENT . auto/have
+        EVENT_MODULES="$EVENT_MODULES $KQUEUE_MODULE"
+        CORE_SRCS="$CORE_SRCS $KQUEUE_SRCS"
+        EVENT_FOUND=YES
+
+        ngx_feature="kqueue's NOTE_LOWAT"
+        ngx_feature_name="NGX_HAVE_LOWAT_EVENT"
+        ngx_feature_run=no
+        ngx_feature_incs="#include <sys/event.h>"
+        ngx_feature_path=
+        ngx_feature_libs=
+        ngx_feature_test="struct kevent  kev;
+                          kev.fflags = NOTE_LOWAT;
+                          (void) kev"
+        . auto/feature
+
+
+        ngx_feature="kqueue's EVFILT_TIMER"
+        ngx_feature_name="NGX_HAVE_TIMER_EVENT"
+        ngx_feature_run=yes
+        ngx_feature_incs="#include <sys/event.h>
+                          #include <sys/time.h>"
+        ngx_feature_path=
+        ngx_feature_libs=
+        ngx_feature_test="int      kq;
+                  struct kevent    kev;
+                  struct timespec  ts;
+
+                  if ((kq = kqueue()) == -1) return 1;
+
+                  kev.ident = 0;
+                  kev.filter = EVFILT_TIMER;
+                  kev.flags = EV_ADD|EV_ENABLE;
+                  kev.fflags = 0;
+                  kev.data = 1000;
+                  kev.udata = 0;
+
+                  ts.tv_sec = 0;
+                  ts.tv_nsec = 0;
+
+                  if (kevent(kq, &kev, 1, &kev, 1, &ts) == -1) return 1;
+
+                  if (kev.flags & EV_ERROR) return 1;"
+
+        . auto/feature
+    fi
+fi
+
+
+if [ "$NGX_SYSTEM" = "NetBSD" ]; then
+
+    # NetBSD 2.0 incompatibly defines kevent.udata as "intptr_t"
+
+    cat << END >> $NGX_AUTO_CONFIG_H
+
+#define NGX_KQUEUE_UDATA_T
+
+END
+
+else
+    cat << END >> $NGX_AUTO_CONFIG_H
+
+#define NGX_KQUEUE_UDATA_T  (void *)
+
+END
+
+fi
+
+
+ngx_feature="crypt()"
+ngx_feature_name=
+ngx_feature_run=no
+ngx_feature_incs=
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="crypt(\"test\", \"salt\");"
+. auto/feature
+
+
+if [ $ngx_found = no ]; then
+
+    ngx_feature="crypt() in libcrypt"
+    ngx_feature_name=
+    ngx_feature_run=no
+    ngx_feature_incs=
+    ngx_feature_path=
+    ngx_feature_libs=-lcrypt
+    . auto/feature
+
+    if [ $ngx_found = yes ]; then
+        CRYPT_LIB="-lcrypt"
+    fi
+fi
+
+
+ngx_feature="F_READAHEAD"
+ngx_feature_name="NGX_HAVE_F_READAHEAD"
+ngx_feature_run=no
+ngx_feature_incs="#include <fcntl.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="fcntl(0, F_READAHEAD, 1);"
+. auto/feature
+
+
+ngx_feature="posix_fadvise()"
+ngx_feature_name="NGX_HAVE_POSIX_FADVISE"
+ngx_feature_run=no
+ngx_feature_incs="#include <fcntl.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="posix_fadvise(0, 0, 0, POSIX_FADV_SEQUENTIAL);"
+. auto/feature
+
+
+ngx_feature="O_DIRECT"
+ngx_feature_name="NGX_HAVE_O_DIRECT"
+ngx_feature_run=no
+ngx_feature_incs="#include <fcntl.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="fcntl(0, F_SETFL, O_DIRECT);"
+. auto/feature
+
+
+if [ $ngx_found = yes -a "$NGX_SYSTEM" = "Linux" ]; then
+    have=NGX_HAVE_ALIGNED_DIRECTIO . auto/have
+fi
+
+ngx_feature="F_NOCACHE"
+ngx_feature_name="NGX_HAVE_F_NOCACHE"
+ngx_feature_run=no
+ngx_feature_incs="#include <fcntl.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="fcntl(0, F_NOCACHE, 1);"
+. auto/feature
+
+
+ngx_feature="directio()"
+ngx_feature_name="NGX_HAVE_DIRECTIO"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/types.h>
+                  #include <sys/fcntl.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="directio(0, DIRECTIO_ON);"
+. auto/feature
+
+
+ngx_feature="statfs()"
+ngx_feature_name="NGX_HAVE_STATFS"
+ngx_feature_run=no
+ngx_feature_incs="$NGX_INCLUDE_SYS_PARAM_H
+                  $NGX_INCLUDE_SYS_MOUNT_H
+                  $NGX_INCLUDE_SYS_VFS_H"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="struct statfs  fs;
+                  statfs(\".\", &fs);"
+. auto/feature
+
+
+ngx_feature="statvfs()"
+ngx_feature_name="NGX_HAVE_STATVFS"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/types.h>
+                  #include <sys/statvfs.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="struct statvfs  fs;
+                  statvfs(\".\", &fs);"
+. auto/feature
+
+
+ngx_feature="dlopen()"
+ngx_feature_name="NGX_HAVE_DLOPEN"
+ngx_feature_run=no
+ngx_feature_incs="#include <dlfcn.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="dlopen(NULL, RTLD_NOW | RTLD_GLOBAL); dlsym(NULL, \"\")"
+. auto/feature
+
+
+if [ $ngx_found = no ]; then
+
+    ngx_feature="dlopen() in libdl"
+    ngx_feature_libs="-ldl"
+    . auto/feature
+
+    if [ $ngx_found = yes ]; then
+        CORE_LIBS="$CORE_LIBS -ldl"
+        NGX_LIBDL="-ldl"
+    fi
+fi
+
+
+ngx_feature="sched_yield()"
+ngx_feature_name="NGX_HAVE_SCHED_YIELD"
+ngx_feature_run=no
+ngx_feature_incs="#include <sched.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="sched_yield()"
+. auto/feature
+
+
+if [ $ngx_found = no ]; then
+
+    ngx_feature="sched_yield() in librt"
+    ngx_feature_libs="-lrt"
+    . auto/feature
+
+    if [ $ngx_found = yes ]; then
+        CORE_LIBS="$CORE_LIBS -lrt"
+    fi
+fi
+
+
+ngx_feature="sched_setaffinity()"
+ngx_feature_name="NGX_HAVE_SCHED_SETAFFINITY"
+ngx_feature_run=no
+ngx_feature_incs="#include <sched.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="cpu_set_t mask;
+                  CPU_ZERO(&mask);
+                  sched_setaffinity(0, sizeof(cpu_set_t), &mask)"
+. auto/feature
+
+
+ngx_feature="SO_SETFIB"
+ngx_feature_name="NGX_HAVE_SETFIB"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/socket.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="setsockopt(0, SOL_SOCKET, SO_SETFIB, NULL, 0)"
+. auto/feature
+
+
+ngx_feature="SO_REUSEPORT"
+ngx_feature_name="NGX_HAVE_REUSEPORT"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/socket.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="setsockopt(0, SOL_SOCKET, SO_REUSEPORT, NULL, 0)"
+. auto/feature
+
+
+ngx_feature="SO_ACCEPTFILTER"
+ngx_feature_name="NGX_HAVE_DEFERRED_ACCEPT"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/socket.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="setsockopt(0, SOL_SOCKET, SO_ACCEPTFILTER, NULL, 0)"
+. auto/feature
+
+
+# OpenBSD bind to any address for transparent proxying
+
+ngx_feature="SO_BINDANY"
+ngx_feature_name="NGX_HAVE_TRANSPARENT_PROXY"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/socket.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="setsockopt(0, SOL_SOCKET, SO_BINDANY, NULL, 0)"
+. auto/feature
+
+
+# Linux transparent proxying
+
+ngx_feature="IP_TRANSPARENT"
+ngx_feature_name="NGX_HAVE_TRANSPARENT_PROXY"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/socket.h>
+                  #include <netinet/in.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="setsockopt(0, IPPROTO_IP, IP_TRANSPARENT, NULL, 0)"
+. auto/feature
+
+
+# FreeBSD bind to any address for transparent proxying
+
+ngx_feature="IP_BINDANY"
+ngx_feature_name="NGX_HAVE_TRANSPARENT_PROXY"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/socket.h>
+                  #include <netinet/in.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="setsockopt(0, IPPROTO_IP, IP_BINDANY, NULL, 0)"
+. auto/feature
+
+
+# Linux IP_BIND_ADDRESS_NO_PORT
+
+ngx_feature="IP_BIND_ADDRESS_NO_PORT"
+ngx_feature_name="NGX_HAVE_IP_BIND_ADDRESS_NO_PORT"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/socket.h>
+                  #include <netinet/in.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="setsockopt(0, IPPROTO_IP, IP_BIND_ADDRESS_NO_PORT, NULL, 0)"
+. auto/feature
+
+
+# BSD way to get IPv4 datagram destination address
+
+ngx_feature="IP_RECVDSTADDR"
+ngx_feature_name="NGX_HAVE_IP_RECVDSTADDR"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/socket.h>
+                  #include <netinet/in.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="setsockopt(0, IPPROTO_IP, IP_RECVDSTADDR, NULL, 0)"
+. auto/feature
+
+
+# BSD way to set IPv4 datagram source address
+
+ngx_feature="IP_SENDSRCADDR"
+ngx_feature_name="NGX_HAVE_IP_SENDSRCADDR"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/socket.h>
+                  #include <netinet/in.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="setsockopt(0, IPPROTO_IP, IP_SENDSRCADDR, NULL, 0)"
+. auto/feature
+
+
+# Linux way to get IPv4 datagram destination address
+
+ngx_feature="IP_PKTINFO"
+ngx_feature_name="NGX_HAVE_IP_PKTINFO"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/socket.h>
+                  #include <netinet/in.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="struct in_pktinfo  pkt;
+                  pkt.ipi_spec_dst.s_addr = INADDR_ANY;
+                  (void) pkt;
+                  setsockopt(0, IPPROTO_IP, IP_PKTINFO, NULL, 0)"
+. auto/feature
+
+
+# RFC 3542 way to get IPv6 datagram destination address
+
+ngx_feature="IPV6_RECVPKTINFO"
+ngx_feature_name="NGX_HAVE_IPV6_RECVPKTINFO"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/socket.h>
+                  #include <netinet/in.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="setsockopt(0, IPPROTO_IPV6, IPV6_RECVPKTINFO, NULL, 0)"
+. auto/feature
+
+
+ngx_feature="TCP_DEFER_ACCEPT"
+ngx_feature_name="NGX_HAVE_DEFERRED_ACCEPT"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/socket.h>
+                  #include <netinet/in.h>
+                  #include <netinet/tcp.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="setsockopt(0, IPPROTO_TCP, TCP_DEFER_ACCEPT, NULL, 0)"
+. auto/feature
+
+
+ngx_feature="TCP_KEEPIDLE"
+ngx_feature_name="NGX_HAVE_KEEPALIVE_TUNABLE"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/socket.h>
+                  #include <netinet/in.h>
+                  #include <netinet/tcp.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="setsockopt(0, IPPROTO_TCP, TCP_KEEPIDLE, NULL, 0);
+                  setsockopt(0, IPPROTO_TCP, TCP_KEEPINTVL, NULL, 0);
+                  setsockopt(0, IPPROTO_TCP, TCP_KEEPCNT, NULL, 0)"
+. auto/feature
+
+
+ngx_feature="TCP_FASTOPEN"
+ngx_feature_name="NGX_HAVE_TCP_FASTOPEN"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/socket.h>
+                  #include <netinet/in.h>
+                  #include <netinet/tcp.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="setsockopt(0, IPPROTO_TCP, TCP_FASTOPEN, NULL, 0)"
+. auto/feature
+
+
+ngx_feature="TCP_INFO"
+ngx_feature_name="NGX_HAVE_TCP_INFO"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/socket.h>
+                  #include <netinet/in.h>
+                  #include <netinet/tcp.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="socklen_t optlen = sizeof(struct tcp_info);
+                  struct tcp_info ti;
+                  ti.tcpi_rtt = 0;
+                  ti.tcpi_rttvar = 0;
+                  ti.tcpi_snd_cwnd = 0;
+                  ti.tcpi_rcv_space = 0;
+                  getsockopt(0, IPPROTO_TCP, TCP_INFO, &ti, &optlen)"
+. auto/feature
+
+
+ngx_feature="accept4()"
+ngx_feature_name="NGX_HAVE_ACCEPT4"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/socket.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="accept4(0, NULL, NULL, SOCK_NONBLOCK)"
+. auto/feature
+
+if [ $NGX_FILE_AIO = YES ]; then
+
+    ngx_feature="kqueue AIO support"
+    ngx_feature_name="NGX_HAVE_FILE_AIO"
+    ngx_feature_run=no
+    ngx_feature_incs="#include <aio.h>"
+    ngx_feature_path=
+    ngx_feature_libs=
+    ngx_feature_test="struct aiocb  iocb;
+                      iocb.aio_sigevent.sigev_notify = SIGEV_KEVENT;
+                      (void) aio_read(&iocb)"
+    . auto/feature
+
+    if [ $ngx_found = yes ]; then
+        CORE_SRCS="$CORE_SRCS $FILE_AIO_SRCS"
+    fi
+
+    if [ $ngx_found = no ]; then
+
+        ngx_feature="Linux AIO support"
+        ngx_feature_name="NGX_HAVE_FILE_AIO"
+        ngx_feature_run=no
+        ngx_feature_incs="#include <linux/aio_abi.h>
+                          #include <sys/eventfd.h>"
+        ngx_feature_path=
+        ngx_feature_libs=
+        ngx_feature_test="struct iocb  iocb;
+                          iocb.aio_lio_opcode = IOCB_CMD_PREAD;
+                          iocb.aio_flags = IOCB_FLAG_RESFD;
+                          iocb.aio_resfd = -1;
+                          (void) iocb;
+                          (void) eventfd(0, 0)"
+        . auto/feature
+
+        if [ $ngx_found = yes ]; then
+            have=NGX_HAVE_EVENTFD . auto/have
+            have=NGX_HAVE_SYS_EVENTFD_H . auto/have
+            CORE_SRCS="$CORE_SRCS $LINUX_AIO_SRCS"
+        fi
+    fi
+
+    if [ $ngx_found = no ]; then
+
+        ngx_feature="Linux AIO support (SYS_eventfd)"
+        ngx_feature_incs="#include <linux/aio_abi.h>
+                          #include <sys/syscall.h>"
+        ngx_feature_test="struct iocb  iocb;
+                          iocb.aio_lio_opcode = IOCB_CMD_PREAD;
+                          iocb.aio_flags = IOCB_FLAG_RESFD;
+                          iocb.aio_resfd = -1;
+                          (void) iocb;
+                          (void) SYS_eventfd"
+        . auto/feature
+
+        if [ $ngx_found = yes ]; then
+            have=NGX_HAVE_EVENTFD . auto/have
+            CORE_SRCS="$CORE_SRCS $LINUX_AIO_SRCS"
+        fi
+    fi
+
+    if [ $ngx_found = no ]; then
+        cat << END
+
+$0: no supported file AIO was found
+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 <sys/eventfd.h>"
+    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 <sys/syscall.h>"
+        ngx_feature_test="(void) SYS_eventfd"
+        . auto/feature
+    fi
+fi
+
+
+have=NGX_HAVE_UNIX_DOMAIN . auto/have
+
+ngx_feature_libs=
+
+
+# C types
+
+ngx_type="int"; . auto/types/sizeof
+
+ngx_type="long"; . auto/types/sizeof
+
+ngx_type="long long"; . auto/types/sizeof
+
+ngx_type="void *"; . auto/types/sizeof; ngx_ptr_size=$ngx_size
+ngx_param=NGX_PTR_SIZE; ngx_value=$ngx_size; . auto/types/value
+
+
+# POSIX types
+
+NGX_INCLUDE_AUTO_CONFIG_H="#include \"ngx_auto_config.h\""
+
+ngx_type="uint32_t"; ngx_types="u_int32_t"; . auto/types/typedef
+ngx_type="uint64_t"; ngx_types="u_int64_t"; . auto/types/typedef
+
+ngx_type="sig_atomic_t"; ngx_types="int"; . auto/types/typedef
+. auto/types/sizeof
+ngx_param=NGX_SIG_ATOMIC_T_SIZE; ngx_value=$ngx_size; . auto/types/value
+
+ngx_type="socklen_t"; ngx_types="int"; . auto/types/typedef
+
+ngx_type="in_addr_t"; ngx_types="uint32_t u_int32_t"; . auto/types/typedef
+
+ngx_type="in_port_t"; ngx_types="u_short"; . auto/types/typedef
+
+ngx_type="rlim_t"; ngx_types="int"; . auto/types/typedef
+
+. auto/types/uintptr_t
+
+. auto/endianness
+
+ngx_type="size_t"; . auto/types/sizeof
+ngx_param=NGX_MAX_SIZE_T_VALUE; ngx_value=$ngx_max_value; . auto/types/value
+ngx_param=NGX_SIZE_T_LEN; ngx_value=$ngx_max_len; . auto/types/value
+
+ngx_type="off_t"; . auto/types/sizeof
+ngx_param=NGX_MAX_OFF_T_VALUE; ngx_value=$ngx_max_value; . auto/types/value
+ngx_param=NGX_OFF_T_LEN; ngx_value=$ngx_max_len; . auto/types/value
+
+ngx_type="time_t"; . auto/types/sizeof
+ngx_param=NGX_TIME_T_SIZE; ngx_value=$ngx_size; . auto/types/value
+ngx_param=NGX_TIME_T_LEN; ngx_value=$ngx_max_len; . auto/types/value
+ngx_param=NGX_MAX_TIME_T_VALUE; ngx_value=$ngx_max_value; . auto/types/value
+
+
+# syscalls, libc calls and some features
+
+
+ngx_feature="AF_INET6"
+ngx_feature_name="NGX_HAVE_INET6"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/socket.h>
+                  #include <netinet/in.h>
+                  #include <arpa/inet.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="struct sockaddr_in6  sin6;
+                  sin6.sin6_family = AF_INET6;
+                  (void) sin6"
+. auto/feature
+
+
+ngx_feature="setproctitle()"
+ngx_feature_name="NGX_HAVE_SETPROCTITLE"
+ngx_feature_run=no
+ngx_feature_incs="#include <stdlib.h>"
+ngx_feature_path=
+ngx_feature_libs=$NGX_SETPROCTITLE_LIB
+ngx_feature_test="setproctitle(\"test\");"
+. auto/feature
+
+
+ngx_feature="pread()"
+ngx_feature_name="NGX_HAVE_PREAD"
+ngx_feature_run=no
+ngx_feature_incs=
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="char buf[1]; ssize_t n; n = pread(0, buf, 1, 0);
+                  if (n == -1) return 1"
+. auto/feature
+
+
+ngx_feature="pwrite()"
+ngx_feature_name="NGX_HAVE_PWRITE"
+ngx_feature_run=no
+ngx_feature_incs=
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="char buf[1]; ssize_t n; n = pwrite(1, buf, 1, 0);
+                  if (n == -1) return 1"
+. auto/feature
+
+
+# pwritev() was introduced in FreeBSD 6 and Linux 2.6.30, glibc 2.10
+
+ngx_feature="pwritev()"
+ngx_feature_name="NGX_HAVE_PWRITEV"
+ngx_feature_run=no
+ngx_feature_incs='#include <sys/uio.h>'
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="char buf[1]; struct iovec vec[1]; ssize_t n;
+                  vec[0].iov_base = buf;
+                  vec[0].iov_len = 1;
+                  n = pwritev(1, vec, 1, 0);
+                  if (n == -1) return 1"
+. auto/feature
+
+
+ngx_feature="sys_nerr"
+ngx_feature_name="NGX_SYS_NERR"
+ngx_feature_run=value
+ngx_feature_incs='#include <errno.h>
+                  #include <stdio.h>'
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test='printf("%d", sys_nerr);'
+. auto/feature
+
+
+if [ $ngx_found = no ]; then
+
+    # Cygiwn defines _sys_nerr
+    ngx_feature="_sys_nerr"
+    ngx_feature_name="NGX_SYS_NERR"
+    ngx_feature_run=value
+    ngx_feature_incs='#include <errno.h>
+                      #include <stdio.h>'
+    ngx_feature_path=
+    ngx_feature_libs=
+    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
+    ngx_feature_run=value
+    ngx_feature_incs='#include <errno.h>
+                      #include <string.h>
+                      #include <stdio.h>'
+    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);'
+    . auto/feature
+fi
+
+
+ngx_feature="localtime_r()"
+ngx_feature_name="NGX_HAVE_LOCALTIME_R"
+ngx_feature_run=no
+ngx_feature_incs="#include <time.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="struct tm t; time_t c=0; localtime_r(&c, &t)"
+. auto/feature
+
+
+ngx_feature="clock_gettime(CLOCK_MONOTONIC)"
+ngx_feature_name="NGX_HAVE_CLOCK_MONOTONIC"
+ngx_feature_run=no
+ngx_feature_incs="#include <time.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts)"
+. auto/feature
+
+
+if [ $ngx_found = no ]; then
+
+    # Linux before glibc 2.17, notably CentOS 6
+
+    ngx_feature="clock_gettime(CLOCK_MONOTONIC) in librt"
+    ngx_feature_libs="-lrt"
+    . auto/feature
+
+    if [ $ngx_found = yes ]; then
+        CORE_LIBS="$CORE_LIBS -lrt"
+    fi
+fi
+
+
+ngx_feature="posix_memalign()"
+ngx_feature_name="NGX_HAVE_POSIX_MEMALIGN"
+ngx_feature_run=no
+ngx_feature_incs="#include <stdlib.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="void *p; int n; n = posix_memalign(&p, 4096, 4096);
+                  if (n != 0) return 1"
+. auto/feature
+
+
+ngx_feature="memalign()"
+ngx_feature_name="NGX_HAVE_MEMALIGN"
+ngx_feature_run=no
+ngx_feature_incs="#include <stdlib.h>
+                  #include <malloc.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="void *p; p = memalign(4096, 4096);
+                  if (p == NULL) return 1"
+. auto/feature
+
+
+ngx_feature="mmap(MAP_ANON|MAP_SHARED)"
+ngx_feature_name="NGX_HAVE_MAP_ANON"
+ngx_feature_run=yes
+ngx_feature_incs="#include <sys/mman.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="void *p;
+                  p = mmap(NULL, 4096, PROT_READ|PROT_WRITE,
+                           MAP_ANON|MAP_SHARED, -1, 0);
+                  if (p == MAP_FAILED) return 1;"
+. auto/feature
+
+
+ngx_feature='mmap("/dev/zero", MAP_SHARED)'
+ngx_feature_name="NGX_HAVE_MAP_DEVZERO"
+ngx_feature_run=yes
+ngx_feature_incs="#include <sys/mman.h>
+                  #include <sys/stat.h>
+                  #include <fcntl.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test='void *p; int  fd;
+                  fd = open("/dev/zero", O_RDWR);
+                  p = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
+                  if (p == MAP_FAILED) return 1;'
+. auto/feature
+
+
+ngx_feature="System V shared memory"
+ngx_feature_name="NGX_HAVE_SYSVSHM"
+ngx_feature_run=yes
+ngx_feature_incs="#include <sys/ipc.h>
+                  #include <sys/shm.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="int  id;
+                  id = shmget(IPC_PRIVATE, 4096, (SHM_R|SHM_W|IPC_CREAT));
+                  if (id == -1) return 1;
+                  shmctl(id, IPC_RMID, NULL);"
+. auto/feature
+
+
+ngx_feature="POSIX semaphores"
+ngx_feature_name="NGX_HAVE_POSIX_SEM"
+ngx_feature_run=yes
+ngx_feature_incs="#include <semaphore.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="sem_t  sem;
+                  if (sem_init(&sem, 1, 0) == -1) return 1;
+                  sem_destroy(&sem);"
+. auto/feature
+
+
+if [ $ngx_found = no ]; then
+
+    # Linux has POSIX semaphores in libpthread
+    ngx_feature="POSIX semaphores in libpthread"
+    ngx_feature_libs=-lpthread
+    . auto/feature
+
+    if [ $ngx_found = yes ]; then
+        CORE_LIBS="$CORE_LIBS -lpthread"
+        NGX_LIBPTHREAD="-lpthread"
+    fi
+fi
+
+
+if [ $ngx_found = no ]; then
+
+    # Solaris has POSIX semaphores in librt
+    ngx_feature="POSIX semaphores in librt"
+    ngx_feature_libs=-lrt
+    . auto/feature
+
+    if [ $ngx_found = yes ]; then
+        CORE_LIBS="$CORE_LIBS -lrt"
+    fi
+fi
+
+
+ngx_feature="struct msghdr.msg_control"
+ngx_feature_name="NGX_HAVE_MSGHDR_MSG_CONTROL"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/socket.h>
+                  #include <stdio.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="struct msghdr  msg;
+                  printf(\"%d\", (int) sizeof(msg.msg_control))"
+. auto/feature
+
+
+ngx_feature="ioctl(FIONBIO)"
+ngx_feature_name="NGX_HAVE_FIONBIO"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/ioctl.h>
+                  #include <stdio.h>
+                  $NGX_INCLUDE_SYS_FILIO_H"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="int i = FIONBIO; printf(\"%d\", i)"
+. auto/feature
+
+
+ngx_feature="struct tm.tm_gmtoff"
+ngx_feature_name="NGX_HAVE_GMTOFF"
+ngx_feature_run=no
+ngx_feature_incs="#include <time.h>
+                  #include <stdio.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="struct tm  tm; tm.tm_gmtoff = 0;
+                  printf(\"%d\", (int) tm.tm_gmtoff)"
+. auto/feature
+
+
+ngx_feature="struct dirent.d_namlen"
+ngx_feature_name="NGX_HAVE_D_NAMLEN"
+ngx_feature_run=no
+ngx_feature_incs="#include <dirent.h>
+                  #include <stdio.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="struct dirent  dir; dir.d_namlen = 0;
+                  printf(\"%d\", (int) dir.d_namlen)"
+. auto/feature
+
+
+ngx_feature="struct dirent.d_type"
+ngx_feature_name="NGX_HAVE_D_TYPE"
+ngx_feature_run=no
+ngx_feature_incs="#include <dirent.h>
+                  #include <stdio.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="struct dirent  dir; dir.d_type = DT_REG;
+                  printf(\"%d\", (int) dir.d_type)"
+. auto/feature
+
+
+ngx_feature="sysconf(_SC_NPROCESSORS_ONLN)"
+ngx_feature_name="NGX_HAVE_SC_NPROCESSORS_ONLN"
+ngx_feature_run=no
+ngx_feature_incs=
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="sysconf(_SC_NPROCESSORS_ONLN)"
+. auto/feature
+
+
+ngx_feature="sysconf(_SC_LEVEL1_DCACHE_LINESIZE)"
+ngx_feature_name="NGX_HAVE_LEVEL1_DCACHE_LINESIZE"
+ngx_feature_run=no
+ngx_feature_incs=
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="sysconf(_SC_LEVEL1_DCACHE_LINESIZE)"
+. auto/feature
+
+
+ngx_feature="openat(), fstatat()"
+ngx_feature_name="NGX_HAVE_OPENAT"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/types.h>
+                  #include <sys/stat.h>
+                  #include <fcntl.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="struct stat sb;
+                  openat(AT_FDCWD, \".\", O_RDONLY|O_NOFOLLOW);
+                  fstatat(AT_FDCWD, \".\", &sb, AT_SYMLINK_NOFOLLOW);"
+. auto/feature
+
+
+ngx_feature="getaddrinfo()"
+ngx_feature_name="NGX_HAVE_GETADDRINFO"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/types.h>
+                  #include <sys/socket.h>
+                  #include <netdb.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test='struct addrinfo *res;
+                  if (getaddrinfo("localhost", NULL, NULL, &res) != 0) return 1;
+                  freeaddrinfo(res)'
+. auto/feature
diff --git a/nginx/conf/fastcgi.conf b/nginx/conf/fastcgi.conf
new file mode 100644 (file)
index 0000000..091738c
--- /dev/null
@@ -0,0 +1,26 @@
+
+fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
+fastcgi_param  QUERY_STRING       $query_string;
+fastcgi_param  REQUEST_METHOD     $request_method;
+fastcgi_param  CONTENT_TYPE       $content_type;
+fastcgi_param  CONTENT_LENGTH     $content_length;
+
+fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
+fastcgi_param  REQUEST_URI        $request_uri;
+fastcgi_param  DOCUMENT_URI       $document_uri;
+fastcgi_param  DOCUMENT_ROOT      $document_root;
+fastcgi_param  SERVER_PROTOCOL    $server_protocol;
+fastcgi_param  REQUEST_SCHEME     $scheme;
+fastcgi_param  HTTPS              $https if_not_empty;
+
+fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
+fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;
+
+fastcgi_param  REMOTE_ADDR        $remote_addr;
+fastcgi_param  REMOTE_PORT        $remote_port;
+fastcgi_param  SERVER_ADDR        $server_addr;
+fastcgi_param  SERVER_PORT        $server_port;
+fastcgi_param  SERVER_NAME        $server_name;
+
+# PHP only, required if PHP was built with --enable-force-cgi-redirect
+fastcgi_param  REDIRECT_STATUS    200;
diff --git a/nginx/conf/fastcgi_params b/nginx/conf/fastcgi_params
new file mode 100644 (file)
index 0000000..28decb9
--- /dev/null
@@ -0,0 +1,25 @@
+
+fastcgi_param  QUERY_STRING       $query_string;
+fastcgi_param  REQUEST_METHOD     $request_method;
+fastcgi_param  CONTENT_TYPE       $content_type;
+fastcgi_param  CONTENT_LENGTH     $content_length;
+
+fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
+fastcgi_param  REQUEST_URI        $request_uri;
+fastcgi_param  DOCUMENT_URI       $document_uri;
+fastcgi_param  DOCUMENT_ROOT      $document_root;
+fastcgi_param  SERVER_PROTOCOL    $server_protocol;
+fastcgi_param  REQUEST_SCHEME     $scheme;
+fastcgi_param  HTTPS              $https if_not_empty;
+
+fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
+fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;
+
+fastcgi_param  REMOTE_ADDR        $remote_addr;
+fastcgi_param  REMOTE_PORT        $remote_port;
+fastcgi_param  SERVER_ADDR        $server_addr;
+fastcgi_param  SERVER_PORT        $server_port;
+fastcgi_param  SERVER_NAME        $server_name;
+
+# PHP only, required if PHP was built with --enable-force-cgi-redirect
+fastcgi_param  REDIRECT_STATUS    200;
diff --git a/nginx/conf/koi-utf b/nginx/conf/koi-utf
new file mode 100644 (file)
index 0000000..e7974ff
--- /dev/null
@@ -0,0 +1,109 @@
+
+# This map is not a full koi8-r <> utf8 map: it does not contain
+# box-drawing and some other characters.  Besides this map contains
+# several koi8-u and Byelorussian letters which are not in koi8-r.
+# If you need a full and standard map, use contrib/unicode2nginx/koi-utf
+# map instead.
+
+charset_map  koi8-r  utf-8 {
+
+    80  E282AC ; # euro
+
+    95  E280A2 ; # bullet
+
+    9A  C2A0 ;   # &nbsp;
+
+    9E  C2B7 ;   # &middot;
+
+    A3  D191 ;   # small yo
+    A4  D194 ;   # small Ukrainian ye
+
+    A6  D196 ;   # small Ukrainian i
+    A7  D197 ;   # small Ukrainian yi
+
+    AD  D291 ;   # small Ukrainian soft g
+    AE  D19E ;   # small Byelorussian short u
+
+    B0  C2B0 ;   # &deg;
+
+    B3  D081 ;   # capital YO
+    B4  D084 ;   # capital Ukrainian YE
+
+    B6  D086 ;   # capital Ukrainian I
+    B7  D087 ;   # capital Ukrainian YI
+
+    B9  E28496 ; # numero sign
+
+    BD  D290 ;   # capital Ukrainian soft G
+    BE  D18E ;   # capital Byelorussian short U
+
+    BF  C2A9 ;   # (C)
+
+    C0  D18E ;   # small yu
+    C1  D0B0 ;   # small a
+    C2  D0B1 ;   # small b
+    C3  D186 ;   # small ts
+    C4  D0B4 ;   # small d
+    C5  D0B5 ;   # small ye
+    C6  D184 ;   # small f
+    C7  D0B3 ;   # small g
+    C8  D185 ;   # small kh
+    C9  D0B8 ;   # small i
+    CA  D0B9 ;   # small j
+    CB  D0BA ;   # small k
+    CC  D0BB ;   # small l
+    CD  D0BC ;   # small m
+    CE  D0BD ;   # small n
+    CF  D0BE ;   # small o
+
+    D0  D0BF ;   # small p
+    D1  D18F ;   # small ya
+    D2  D180 ;   # small r
+    D3  D181 ;   # small s
+    D4  D182 ;   # small t
+    D5  D183 ;   # small u
+    D6  D0B6 ;   # small zh
+    D7  D0B2 ;   # small v
+    D8  D18C ;   # small soft sign
+    D9  D18B ;   # small y
+    DA  D0B7 ;   # small z
+    DB  D188 ;   # small sh
+    DC  D18D ;   # small e
+    DD  D189 ;   # small shch
+    DE  D187 ;   # small ch
+    DF  D18A ;   # small hard sign
+
+    E0  D0AE ;   # capital YU
+    E1  D090 ;   # capital A
+    E2  D091 ;   # capital B
+    E3  D0A6 ;   # capital TS
+    E4  D094 ;   # capital D
+    E5  D095 ;   # capital YE
+    E6  D0A4 ;   # capital F
+    E7  D093 ;   # capital G
+    E8  D0A5 ;   # capital KH
+    E9  D098 ;   # capital I
+    EA  D099 ;   # capital J
+    EB  D09A ;   # capital K
+    EC  D09B ;   # capital L
+    ED  D09C ;   # capital M
+    EE  D09D ;   # capital N
+    EF  D09E ;   # capital O
+
+    F0  D09F ;   # capital P
+    F1  D0AF ;   # capital YA
+    F2  D0A0 ;   # capital R
+    F3  D0A1 ;   # capital S
+    F4  D0A2 ;   # capital T
+    F5  D0A3 ;   # capital U
+    F6  D096 ;   # capital ZH
+    F7  D092 ;   # capital V
+    F8  D0AC ;   # capital soft sign
+    F9  D0AB ;   # capital Y
+    FA  D097 ;   # capital Z
+    FB  D0A8 ;   # capital SH
+    FC  D0AD ;   # capital E
+    FD  D0A9 ;   # capital SHCH
+    FE  D0A7 ;   # capital CH
+    FF  D0AA ;   # capital hard sign
+}
diff --git a/nginx/conf/koi-win b/nginx/conf/koi-win
new file mode 100644 (file)
index 0000000..72afabe
--- /dev/null
@@ -0,0 +1,103 @@
+
+charset_map  koi8-r  windows-1251 {
+
+    80  88 ; # euro
+
+    95  95 ; # bullet
+
+    9A  A0 ; # &nbsp;
+
+    9E  B7 ; # &middot;
+
+    A3  B8 ; # small yo
+    A4  BA ; # small Ukrainian ye
+
+    A6  B3 ; # small Ukrainian i
+    A7  BF ; # small Ukrainian yi
+
+    AD  B4 ; # small Ukrainian soft g
+    AE  A2 ; # small Byelorussian short u
+
+    B0  B0 ; # &deg;
+
+    B3  A8 ; # capital YO
+    B4  AA ; # capital Ukrainian YE
+
+    B6  B2 ; # capital Ukrainian I
+    B7  AF ; # capital Ukrainian YI
+
+    B9  B9 ; # numero sign
+
+    BD  A5 ; # capital Ukrainian soft G
+    BE  A1 ; # capital Byelorussian short U
+
+    BF  A9 ; # (C)
+
+    C0  FE ; # small yu
+    C1  E0 ; # small a
+    C2  E1 ; # small b
+    C3  F6 ; # small ts
+    C4  E4 ; # small d
+    C5  E5 ; # small ye
+    C6  F4 ; # small f
+    C7  E3 ; # small g
+    C8  F5 ; # small kh
+    C9  E8 ; # small i
+    CA  E9 ; # small j
+    CB  EA ; # small k
+    CC  EB ; # small l
+    CD  EC ; # small m
+    CE  ED ; # small n
+    CF  EE ; # small o
+
+    D0  EF ; # small p
+    D1  FF ; # small ya
+    D2  F0 ; # small r
+    D3  F1 ; # small s
+    D4  F2 ; # small t
+    D5  F3 ; # small u
+    D6  E6 ; # small zh
+    D7  E2 ; # small v
+    D8  FC ; # small soft sign
+    D9  FB ; # small y
+    DA  E7 ; # small z
+    DB  F8 ; # small sh
+    DC  FD ; # small e
+    DD  F9 ; # small shch
+    DE  F7 ; # small ch
+    DF  FA ; # small hard sign
+
+    E0  DE ; # capital YU
+    E1  C0 ; # capital A
+    E2  C1 ; # capital B
+    E3  D6 ; # capital TS
+    E4  C4 ; # capital D
+    E5  C5 ; # capital YE
+    E6  D4 ; # capital F
+    E7  C3 ; # capital G
+    E8  D5 ; # capital KH
+    E9  C8 ; # capital I
+    EA  C9 ; # capital J
+    EB  CA ; # capital K
+    EC  CB ; # capital L
+    ED  CC ; # capital M
+    EE  CD ; # capital N
+    EF  CE ; # capital O
+
+    F0  CF ; # capital P
+    F1  DF ; # capital YA
+    F2  D0 ; # capital R
+    F3  D1 ; # capital S
+    F4  D2 ; # capital T
+    F5  D3 ; # capital U
+    F6  C6 ; # capital ZH
+    F7  C2 ; # capital V
+    F8  DC ; # capital soft sign
+    F9  DB ; # capital Y
+    FA  C7 ; # capital Z
+    FB  D8 ; # capital SH
+    FC  DD ; # capital E
+    FD  D9 ; # capital SHCH
+    FE  D7 ; # capital CH
+    FF  DA ; # capital hard sign
+}
diff --git a/nginx/conf/mime.types b/nginx/conf/mime.types
new file mode 100644 (file)
index 0000000..8a2348a
--- /dev/null
@@ -0,0 +1,95 @@
+
+types {
+    text/html                                        html htm shtml;
+    text/css                                         css;
+    text/xml                                         xml;
+    image/gif                                        gif;
+    image/jpeg                                       jpeg jpg;
+    application/javascript                           js;
+    application/atom+xml                             atom;
+    application/rss+xml                              rss;
+
+    text/mathml                                      mml;
+    text/plain                                       txt;
+    text/vnd.sun.j2me.app-descriptor                 jad;
+    text/vnd.wap.wml                                 wml;
+    text/x-component                                 htc;
+
+    image/png                                        png;
+    image/svg+xml                                    svg svgz;
+    image/tiff                                       tif tiff;
+    image/vnd.wap.wbmp                               wbmp;
+    image/webp                                       webp;
+    image/x-icon                                     ico;
+    image/x-jng                                      jng;
+    image/x-ms-bmp                                   bmp;
+
+    application/font-woff                            woff;
+    application/java-archive                         jar war ear;
+    application/json                                 json;
+    application/mac-binhex40                         hqx;
+    application/msword                               doc;
+    application/pdf                                  pdf;
+    application/postscript                           ps eps ai;
+    application/rtf                                  rtf;
+    application/vnd.apple.mpegurl                    m3u8;
+    application/vnd.google-earth.kml+xml             kml;
+    application/vnd.google-earth.kmz                 kmz;
+    application/vnd.ms-excel                         xls;
+    application/vnd.ms-fontobject                    eot;
+    application/vnd.ms-powerpoint                    ppt;
+    application/vnd.oasis.opendocument.graphics      odg;
+    application/vnd.oasis.opendocument.presentation  odp;
+    application/vnd.oasis.opendocument.spreadsheet   ods;
+    application/vnd.oasis.opendocument.text          odt;
+    application/vnd.openxmlformats-officedocument.presentationml.presentation
+                                                     pptx;
+    application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
+                                                     xlsx;
+    application/vnd.openxmlformats-officedocument.wordprocessingml.document
+                                                     docx;
+    application/vnd.wap.wmlc                         wmlc;
+    application/x-7z-compressed                      7z;
+    application/x-cocoa                              cco;
+    application/x-java-archive-diff                  jardiff;
+    application/x-java-jnlp-file                     jnlp;
+    application/x-makeself                           run;
+    application/x-perl                               pl pm;
+    application/x-pilot                              prc pdb;
+    application/x-rar-compressed                     rar;
+    application/x-redhat-package-manager             rpm;
+    application/x-sea                                sea;
+    application/x-shockwave-flash                    swf;
+    application/x-stuffit                            sit;
+    application/x-tcl                                tcl tk;
+    application/x-x509-ca-cert                       der pem crt;
+    application/x-xpinstall                          xpi;
+    application/xhtml+xml                            xhtml;
+    application/xspf+xml                             xspf;
+    application/zip                                  zip;
+
+    application/octet-stream                         bin exe dll;
+    application/octet-stream                         deb;
+    application/octet-stream                         dmg;
+    application/octet-stream                         iso img;
+    application/octet-stream                         msi msp msm;
+
+    audio/midi                                       mid midi kar;
+    audio/mpeg                                       mp3;
+    audio/ogg                                        ogg;
+    audio/x-m4a                                      m4a;
+    audio/x-realaudio                                ra;
+
+    video/3gpp                                       3gpp 3gp;
+    video/mp2t                                       ts;
+    video/mp4                                        mp4;
+    video/mpeg                                       mpeg mpg;
+    video/quicktime                                  mov;
+    video/webm                                       webm;
+    video/x-flv                                      flv;
+    video/x-m4v                                      m4v;
+    video/x-mng                                      mng;
+    video/x-ms-asf                                   asx asf;
+    video/x-ms-wmv                                   wmv;
+    video/x-msvideo                                  avi;
+}
diff --git a/nginx/conf/nginx.conf b/nginx/conf/nginx.conf
new file mode 100644 (file)
index 0000000..29bc085
--- /dev/null
@@ -0,0 +1,117 @@
+
+#user  nobody;
+worker_processes  1;
+
+#error_log  logs/error.log;
+#error_log  logs/error.log  notice;
+#error_log  logs/error.log  info;
+
+#pid        logs/nginx.pid;
+
+
+events {
+    worker_connections  1024;
+}
+
+
+http {
+    include       mime.types;
+    default_type  application/octet-stream;
+
+    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
+    #                  '$status $body_bytes_sent "$http_referer" '
+    #                  '"$http_user_agent" "$http_x_forwarded_for"';
+
+    #access_log  logs/access.log  main;
+
+    sendfile        on;
+    #tcp_nopush     on;
+
+    #keepalive_timeout  0;
+    keepalive_timeout  65;
+
+    #gzip  on;
+
+    server {
+        listen       80;
+        server_name  localhost;
+
+        #charset koi8-r;
+
+        #access_log  logs/host.access.log  main;
+
+        location / {
+            root   html;
+            index  index.html index.htm;
+        }
+
+        #error_page  404              /404.html;
+
+        # redirect server error pages to the static page /50x.html
+        #
+        error_page   500 502 503 504  /50x.html;
+        location = /50x.html {
+            root   html;
+        }
+
+        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
+        #
+        #location ~ \.php$ {
+        #    proxy_pass   http://127.0.0.1;
+        #}
+
+        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
+        #
+        #location ~ \.php$ {
+        #    root           html;
+        #    fastcgi_pass   127.0.0.1:9000;
+        #    fastcgi_index  index.php;
+        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
+        #    include        fastcgi_params;
+        #}
+
+        # deny access to .htaccess files, if Apache's document root
+        # concurs with nginx's one
+        #
+        #location ~ /\.ht {
+        #    deny  all;
+        #}
+    }
+
+
+    # another virtual host using mix of IP-, name-, and port-based configuration
+    #
+    #server {
+    #    listen       8000;
+    #    listen       somename:8080;
+    #    server_name  somename  alias  another.alias;
+
+    #    location / {
+    #        root   html;
+    #        index  index.html index.htm;
+    #    }
+    #}
+
+
+    # HTTPS server
+    #
+    #server {
+    #    listen       443 ssl;
+    #    server_name  localhost;
+
+    #    ssl_certificate      cert.pem;
+    #    ssl_certificate_key  cert.key;
+
+    #    ssl_session_cache    shared:SSL:1m;
+    #    ssl_session_timeout  5m;
+
+    #    ssl_ciphers  HIGH:!aNULL:!MD5;
+    #    ssl_prefer_server_ciphers  on;
+
+    #    location / {
+    #        root   html;
+    #        index  index.html index.htm;
+    #    }
+    #}
+
+}
diff --git a/nginx/conf/scgi_params b/nginx/conf/scgi_params
new file mode 100644 (file)
index 0000000..6d4ce4f
--- /dev/null
@@ -0,0 +1,17 @@
+
+scgi_param  REQUEST_METHOD     $request_method;
+scgi_param  REQUEST_URI        $request_uri;
+scgi_param  QUERY_STRING       $query_string;
+scgi_param  CONTENT_TYPE       $content_type;
+
+scgi_param  DOCUMENT_URI       $document_uri;
+scgi_param  DOCUMENT_ROOT      $document_root;
+scgi_param  SCGI               1;
+scgi_param  SERVER_PROTOCOL    $server_protocol;
+scgi_param  REQUEST_SCHEME     $scheme;
+scgi_param  HTTPS              $https if_not_empty;
+
+scgi_param  REMOTE_ADDR        $remote_addr;
+scgi_param  REMOTE_PORT        $remote_port;
+scgi_param  SERVER_PORT        $server_port;
+scgi_param  SERVER_NAME        $server_name;
diff --git a/nginx/conf/uwsgi_params b/nginx/conf/uwsgi_params
new file mode 100644 (file)
index 0000000..09c732c
--- /dev/null
@@ -0,0 +1,17 @@
+
+uwsgi_param  QUERY_STRING       $query_string;
+uwsgi_param  REQUEST_METHOD     $request_method;
+uwsgi_param  CONTENT_TYPE       $content_type;
+uwsgi_param  CONTENT_LENGTH     $content_length;
+
+uwsgi_param  REQUEST_URI        $request_uri;
+uwsgi_param  PATH_INFO          $document_uri;
+uwsgi_param  DOCUMENT_ROOT      $document_root;
+uwsgi_param  SERVER_PROTOCOL    $server_protocol;
+uwsgi_param  REQUEST_SCHEME     $scheme;
+uwsgi_param  HTTPS              $https if_not_empty;
+
+uwsgi_param  REMOTE_ADDR        $remote_addr;
+uwsgi_param  REMOTE_PORT        $remote_port;
+uwsgi_param  SERVER_PORT        $server_port;
+uwsgi_param  SERVER_NAME        $server_name;
diff --git a/nginx/conf/win-utf b/nginx/conf/win-utf
new file mode 100644 (file)
index 0000000..ed8bc00
--- /dev/null
@@ -0,0 +1,126 @@
+
+# This map is not a full windows-1251 <> utf8 map: it does not
+# contain Serbian and Macedonian letters.  If you need a full map,
+# use contrib/unicode2nginx/win-utf map instead.
+
+charset_map  windows-1251  utf-8 {
+
+    82  E2809A ; # single low-9 quotation mark
+
+    84  E2809E ; # double low-9 quotation mark
+    85  E280A6 ; # ellipsis
+    86  E280A0 ; # dagger
+    87  E280A1 ; # double dagger
+    88  E282AC ; # euro
+    89  E280B0 ; # per mille
+
+    91  E28098 ; # left single quotation mark
+    92  E28099 ; # right single quotation mark
+    93  E2809C ; # left double quotation mark
+    94  E2809D ; # right double quotation mark
+    95  E280A2 ; # bullet
+    96  E28093 ; # en dash
+    97  E28094 ; # em dash
+
+    99  E284A2 ; # trade mark sign
+
+    A0  C2A0 ;   # &nbsp;
+    A1  D18E ;   # capital Byelorussian short U
+    A2  D19E ;   # small Byelorussian short u
+
+    A4  C2A4 ;   # currency sign
+    A5  D290 ;   # capital Ukrainian soft G
+    A6  C2A6 ;   # borken bar
+    A7  C2A7 ;   # section sign
+    A8  D081 ;   # capital YO
+    A9  C2A9 ;   # (C)
+    AA  D084 ;   # capital Ukrainian YE
+    AB  C2AB ;   # left-pointing double angle quotation mark
+    AC  C2AC ;   # not sign
+    AD  C2AD ;   # soft hypen
+    AE  C2AE ;   # (R)
+    AF  D087 ;   # capital Ukrainian YI
+
+    B0  C2B0 ;   # &deg;
+    B1  C2B1 ;   # plus-minus sign
+    B2  D086 ;   # capital Ukrainian I
+    B3  D196 ;   # small Ukrainian i
+    B4  D291 ;   # small Ukrainian soft g
+    B5  C2B5 ;   # micro sign
+    B6  C2B6 ;   # pilcrow sign
+    B7  C2B7 ;   # &middot;
+    B8  D191 ;   # small yo
+    B9  E28496 ; # numero sign
+    BA  D194 ;   # small Ukrainian ye
+    BB  C2BB ;   # right-pointing double angle quotation mark
+
+    BF  D197 ;   # small Ukrainian yi
+
+    C0  D090 ;   # capital A
+    C1  D091 ;   # capital B
+    C2  D092 ;   # capital V
+    C3  D093 ;   # capital G
+    C4  D094 ;   # capital D
+    C5  D095 ;   # capital YE
+    C6  D096 ;   # capital ZH
+    C7  D097 ;   # capital Z
+    C8  D098 ;   # capital I
+    C9  D099 ;   # capital J
+    CA  D09A ;   # capital K
+    CB  D09B ;   # capital L
+    CC  D09C ;   # capital M
+    CD  D09D ;   # capital N
+    CE  D09E ;   # capital O
+    CF  D09F ;   # capital P
+
+    D0  D0A0 ;   # capital R
+    D1  D0A1 ;   # capital S
+    D2  D0A2 ;   # capital T
+    D3  D0A3 ;   # capital U
+    D4  D0A4 ;   # capital F
+    D5  D0A5 ;   # capital KH
+    D6  D0A6 ;   # capital TS
+    D7  D0A7 ;   # capital CH
+    D8  D0A8 ;   # capital SH
+    D9  D0A9 ;   # capital SHCH
+    DA  D0AA ;   # capital hard sign
+    DB  D0AB ;   # capital Y
+    DC  D0AC ;   # capital soft sign
+    DD  D0AD ;   # capital E
+    DE  D0AE ;   # capital YU
+    DF  D0AF ;   # capital YA
+
+    E0  D0B0 ;   # small a
+    E1  D0B1 ;   # small b
+    E2  D0B2 ;   # small v
+    E3  D0B3 ;   # small g
+    E4  D0B4 ;   # small d
+    E5  D0B5 ;   # small ye
+    E6  D0B6 ;   # small zh
+    E7  D0B7 ;   # small z
+    E8  D0B8 ;   # small i
+    E9  D0B9 ;   # small j
+    EA  D0BA ;   # small k
+    EB  D0BB ;   # small l
+    EC  D0BC ;   # small m
+    ED  D0BD ;   # small n
+    EE  D0BE ;   # small o
+    EF  D0BF ;   # small p
+
+    F0  D180 ;   # small r
+    F1  D181 ;   # small s
+    F2  D182 ;   # small t
+    F3  D183 ;   # small u
+    F4  D184 ;   # small f
+    F5  D185 ;   # small kh
+    F6  D186 ;   # small ts
+    F7  D187 ;   # small ch
+    F8  D188 ;   # small sh
+    F9  D189 ;   # small shch
+    FA  D18A ;   # small hard sign
+    FB  D18B ;   # small y
+    FC  D18C ;   # small soft sign
+    FD  D18D ;   # small e
+    FE  D18E ;   # small yu
+    FF  D18F ;   # small ya
+}
diff --git a/nginx/configure b/nginx/configure
new file mode 100755 (executable)
index 0000000..fe8d36f
--- /dev/null
@@ -0,0 +1,121 @@
+#!/bin/sh
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+LC_ALL=C
+export LC_ALL
+
+. auto/options
+. auto/init
+. auto/sources
+
+test -d $NGX_OBJS || mkdir -p $NGX_OBJS
+
+echo > $NGX_AUTO_HEADERS_H
+echo > $NGX_AUTOCONF_ERR
+
+echo "#define NGX_CONFIGURE \"$NGX_CONFIGURE\"" > $NGX_AUTO_CONFIG_H
+
+
+if [ $NGX_DEBUG = YES ]; then
+    have=NGX_DEBUG . auto/have
+fi
+
+
+if [ $NGX_USE_VCL = YES ]; then
+    have=NGX_USE_VCL . auto/have
+fi
+
+
+if test -z "$NGX_PLATFORM"; then
+    echo "checking for OS"
+
+    NGX_SYSTEM=`uname -s 2>/dev/null`
+    NGX_RELEASE=`uname -r 2>/dev/null`
+    NGX_MACHINE=`uname -m 2>/dev/null`
+
+    echo " + $NGX_SYSTEM $NGX_RELEASE $NGX_MACHINE"
+
+    NGX_PLATFORM="$NGX_SYSTEM:$NGX_RELEASE:$NGX_MACHINE";
+
+    case "$NGX_SYSTEM" in
+        MINGW32_* | MINGW64_* | MSYS_*)
+            NGX_PLATFORM=win32
+        ;;
+    esac
+
+else
+    echo "building for $NGX_PLATFORM"
+    NGX_SYSTEM=$NGX_PLATFORM
+fi
+
+. auto/cc/conf
+
+if [ "$NGX_PLATFORM" != win32 ]; then
+    . auto/headers
+fi
+
+. auto/os/conf
+
+if [ "$NGX_PLATFORM" != win32 ]; then
+    . auto/unix
+fi
+
+. auto/threads
+. auto/modules
+. auto/lib/conf
+
+case ".$NGX_PREFIX" in
+    .)
+        NGX_PREFIX=${NGX_PREFIX:-/usr/local/nginx}
+        have=NGX_PREFIX value="\"$NGX_PREFIX/\"" . auto/define
+    ;;
+
+    .!)
+        NGX_PREFIX=
+    ;;
+
+    *)
+        have=NGX_PREFIX value="\"$NGX_PREFIX/\"" . auto/define
+    ;;
+esac
+
+if [ ".$NGX_CONF_PREFIX" != "." ]; then
+    have=NGX_CONF_PREFIX value="\"$NGX_CONF_PREFIX/\"" . auto/define
+fi
+
+have=NGX_SBIN_PATH value="\"$NGX_SBIN_PATH\"" . auto/define
+have=NGX_CONF_PATH value="\"$NGX_CONF_PATH\"" . auto/define
+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
+
+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
+have=NGX_HTTP_PROXY_TEMP_PATH value="\"$NGX_HTTP_PROXY_TEMP_PATH\""
+. auto/define
+have=NGX_HTTP_FASTCGI_TEMP_PATH value="\"$NGX_HTTP_FASTCGI_TEMP_PATH\""
+. auto/define
+have=NGX_HTTP_UWSGI_TEMP_PATH value="\"$NGX_HTTP_UWSGI_TEMP_PATH\""
+. auto/define
+have=NGX_HTTP_SCGI_TEMP_PATH value="\"$NGX_HTTP_SCGI_TEMP_PATH\""
+. auto/define
+
+. auto/make
+. auto/lib/make
+. auto/install
+
+# STUB
+. auto/stubs
+
+have=NGX_USER value="\"$NGX_USER\"" . auto/define
+have=NGX_GROUP value="\"$NGX_GROUP\"" . auto/define
+
+if [ ".$NGX_BUILD" != "." ]; then
+    have=NGX_BUILD value="\"$NGX_BUILD\"" . auto/define
+fi
+
+. auto/summary
diff --git a/nginx/contrib/README b/nginx/contrib/README
new file mode 100644 (file)
index 0000000..fec4b20
--- /dev/null
@@ -0,0 +1,21 @@
+
+geo2nginx.pl           by Andrei Nigmatulin
+
+       The perl script to convert CSV geoip database ( free download
+       at http://www.maxmind.com/app/geoip_country ) to format, suitable
+       for use by the ngx_http_geo_module.
+
+
+unicode2nginx          by Maxim Dounin
+
+       The perl script to convert unicode mappings ( available
+       at http://www.unicode.org/Public/MAPPINGS/ ) to the nginx
+       configuration file format.
+       Two generated full maps for windows-1251 and koi8-r.
+
+
+vim                    by Evan Miller
+
+       Syntax highlighting of nginx configuration for vim, to be
+       placed into ~/.vim/.
+
diff --git a/nginx/contrib/geo2nginx.pl b/nginx/contrib/geo2nginx.pl
new file mode 100644 (file)
index 0000000..29243ec
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/bin/perl -w\r
+\r
+# (c) Andrei Nigmatulin, 2005\r
+#\r
+# this script provided "as is", without any warranties. use it at your own risk.\r
+#\r
+# special thanx to Andrew Sitnikov for perl port\r
+#\r
+# this script converts CSV geoip database (free download at http://www.maxmind.com/app/geoip_country)\r
+# to format, suitable for use with nginx_http_geo module (http://sysoev.ru/nginx)\r
+#\r
+# for example, line with ip range\r
+#\r
+#   "62.16.68.0","62.16.127.255","1041253376","1041268735","RU","Russian Federation"\r
+#\r
+# will be converted to four subnetworks:\r
+#\r
+#   62.16.68.0/22 RU;\r
+#   62.16.72.0/21 RU;\r
+#   62.16.80.0/20 RU;\r
+#   62.16.96.0/19 RU;\r
+\r
+\r
+use warnings;\r
+use strict;\r
+\r
+while( <STDIN> ){\r
+       if (/"[^"]+","[^"]+","([^"]+)","([^"]+)","([^"]+)"/){\r
+               print_subnets($1, $2, $3);\r
+       }\r
+}\r
+\r
+sub  print_subnets {\r
+       my ($a1, $a2, $c) = @_;\r
+       my $l;\r
+    while ($a1 <= $a2) {\r
+               for ($l = 0; ($a1 & (1 << $l)) == 0 && ($a1 + ((1 << ($l + 1)) - 1)) <= $a2; $l++){};\r
+               print long2ip($a1) . "/" . (32 - $l) . " " . $c . ";\n";\r
+       $a1 += (1 << $l);\r
+       }\r
+}\r
+\r
+sub long2ip {\r
+       my $ip = shift;\r
+\r
+       my $str = 0;\r
+\r
+       $str = ($ip & 255);\r
+\r
+       $ip >>= 8;\r
+       $str = ($ip & 255).".$str";\r
+\r
+       $ip >>= 8;\r
+       $str = ($ip & 255).".$str";\r
+\r
+       $ip >>= 8;\r
+       $str = ($ip & 255).".$str";\r
+}\r
diff --git a/nginx/contrib/unicode2nginx/koi-utf b/nginx/contrib/unicode2nginx/koi-utf
new file mode 100644 (file)
index 0000000..48853af
--- /dev/null
@@ -0,0 +1,131 @@
+charset_map  koi8-r  utf-8 {
+
+    80  E29480 ; #     BOX DRAWINGS LIGHT HORIZONTAL
+    81  E29482 ; #     BOX DRAWINGS LIGHT VERTICAL
+    82  E2948C ; #     BOX DRAWINGS LIGHT DOWN AND RIGHT
+    83  E29490 ; #     BOX DRAWINGS LIGHT DOWN AND LEFT
+    84  E29494 ; #     BOX DRAWINGS LIGHT UP AND RIGHT
+    85  E29498 ; #     BOX DRAWINGS LIGHT UP AND LEFT
+    86  E2949C ; #     BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+    87  E294A4 ; #     BOX DRAWINGS LIGHT VERTICAL AND LEFT
+    88  E294AC ; #     BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+    89  E294B4 ; #     BOX DRAWINGS LIGHT UP AND HORIZONTAL
+    8A  E294BC ; #     BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+    8B  E29680 ; #     UPPER HALF BLOCK
+    8C  E29684 ; #     LOWER HALF BLOCK
+    8D  E29688 ; #     FULL BLOCK
+    8E  E2968C ; #     LEFT HALF BLOCK
+    8F  E29690 ; #     RIGHT HALF BLOCK
+    90  E29691 ; #     LIGHT SHADE
+    91  E29692 ; #     MEDIUM SHADE
+    92  E29693 ; #     DARK SHADE
+    93  E28CA0 ; #     TOP HALF INTEGRAL
+    94  E296A0 ; #     BLACK SQUARE
+    95  E28899 ; #     BULLET OPERATOR
+    96  E2889A ; #     SQUARE ROOT
+    97  E28988 ; #     ALMOST EQUAL TO
+    98  E289A4 ; #     LESS-THAN OR EQUAL TO
+    99  E289A5 ; #     GREATER-THAN OR EQUAL TO
+    9A  C2A0 ; #       NO-BREAK SPACE
+    9B  E28CA1 ; #     BOTTOM HALF INTEGRAL
+    9C  C2B0 ; #       DEGREE SIGN
+    9D  C2B2 ; #       SUPERSCRIPT TWO
+    9E  C2B7 ; #       MIDDLE DOT
+    9F  C3B7 ; #       DIVISION SIGN
+    A0  E29590 ; #     BOX DRAWINGS DOUBLE HORIZONTAL
+    A1  E29591 ; #     BOX DRAWINGS DOUBLE VERTICAL
+    A2  E29592 ; #     BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE
+    A3  D191 ; #       CYRILLIC SMALL LETTER IO
+    A4  E29593 ; #     BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE
+    A5  E29594 ; #     BOX DRAWINGS DOUBLE DOWN AND RIGHT
+    A6  E29595 ; #     BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE
+    A7  E29596 ; #     BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE
+    A8  E29597 ; #     BOX DRAWINGS DOUBLE DOWN AND LEFT
+    A9  E29598 ; #     BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE
+    AA  E29599 ; #     BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE
+    AB  E2959A ; #     BOX DRAWINGS DOUBLE UP AND RIGHT
+    AC  E2959B ; #     BOX DRAWINGS UP SINGLE AND LEFT DOUBLE
+    AD  E2959C ; #     BOX DRAWINGS UP DOUBLE AND LEFT SINGLE
+    AE  E2959D ; #     BOX DRAWINGS DOUBLE UP AND LEFT
+    AF  E2959E ; #     BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE
+    B0  E2959F ; #     BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE
+    B1  E295A0 ; #     BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+    B2  E295A1 ; #     BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE
+    B3  D081 ; #       CYRILLIC CAPITAL LETTER IO
+    B4  E295A2 ; #     BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE
+    B5  E295A3 ; #     BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+    B6  E295A4 ; #     BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE
+    B7  E295A5 ; #     BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE
+    B8  E295A6 ; #     BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+    B9  E295A7 ; #     BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE
+    BA  E295A8 ; #     BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE
+    BB  E295A9 ; #     BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+    BC  E295AA ; #     BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE
+    BD  E295AB ; #     BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE
+    BE  E295AC ; #     BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+    BF  C2A9 ; #       COPYRIGHT SIGN
+    C0  D18E ; #       CYRILLIC SMALL LETTER YU
+    C1  D0B0 ; #       CYRILLIC SMALL LETTER A
+    C2  D0B1 ; #       CYRILLIC SMALL LETTER BE
+    C3  D186 ; #       CYRILLIC SMALL LETTER TSE
+    C4  D0B4 ; #       CYRILLIC SMALL LETTER DE
+    C5  D0B5 ; #       CYRILLIC SMALL LETTER IE
+    C6  D184 ; #       CYRILLIC SMALL LETTER EF
+    C7  D0B3 ; #       CYRILLIC SMALL LETTER GHE
+    C8  D185 ; #       CYRILLIC SMALL LETTER HA
+    C9  D0B8 ; #       CYRILLIC SMALL LETTER I
+    CA  D0B9 ; #       CYRILLIC SMALL LETTER SHORT I
+    CB  D0BA ; #       CYRILLIC SMALL LETTER KA
+    CC  D0BB ; #       CYRILLIC SMALL LETTER EL
+    CD  D0BC ; #       CYRILLIC SMALL LETTER EM
+    CE  D0BD ; #       CYRILLIC SMALL LETTER EN
+    CF  D0BE ; #       CYRILLIC SMALL LETTER O
+    D0  D0BF ; #       CYRILLIC SMALL LETTER PE
+    D1  D18F ; #       CYRILLIC SMALL LETTER YA
+    D2  D180 ; #       CYRILLIC SMALL LETTER ER
+    D3  D181 ; #       CYRILLIC SMALL LETTER ES
+    D4  D182 ; #       CYRILLIC SMALL LETTER TE
+    D5  D183 ; #       CYRILLIC SMALL LETTER U
+    D6  D0B6 ; #       CYRILLIC SMALL LETTER ZHE
+    D7  D0B2 ; #       CYRILLIC SMALL LETTER VE
+    D8  D18C ; #       CYRILLIC SMALL LETTER SOFT SIGN
+    D9  D18B ; #       CYRILLIC SMALL LETTER YERU
+    DA  D0B7 ; #       CYRILLIC SMALL LETTER ZE
+    DB  D188 ; #       CYRILLIC SMALL LETTER SHA
+    DC  D18D ; #       CYRILLIC SMALL LETTER E
+    DD  D189 ; #       CYRILLIC SMALL LETTER SHCHA
+    DE  D187 ; #       CYRILLIC SMALL LETTER CHE
+    DF  D18A ; #       CYRILLIC SMALL LETTER HARD SIGN
+    E0  D0AE ; #       CYRILLIC CAPITAL LETTER YU
+    E1  D090 ; #       CYRILLIC CAPITAL LETTER A
+    E2  D091 ; #       CYRILLIC CAPITAL LETTER BE
+    E3  D0A6 ; #       CYRILLIC CAPITAL LETTER TSE
+    E4  D094 ; #       CYRILLIC CAPITAL LETTER DE
+    E5  D095 ; #       CYRILLIC CAPITAL LETTER IE
+    E6  D0A4 ; #       CYRILLIC CAPITAL LETTER EF
+    E7  D093 ; #       CYRILLIC CAPITAL LETTER GHE
+    E8  D0A5 ; #       CYRILLIC CAPITAL LETTER HA
+    E9  D098 ; #       CYRILLIC CAPITAL LETTER I
+    EA  D099 ; #       CYRILLIC CAPITAL LETTER SHORT I
+    EB  D09A ; #       CYRILLIC CAPITAL LETTER KA
+    EC  D09B ; #       CYRILLIC CAPITAL LETTER EL
+    ED  D09C ; #       CYRILLIC CAPITAL LETTER EM
+    EE  D09D ; #       CYRILLIC CAPITAL LETTER EN
+    EF  D09E ; #       CYRILLIC CAPITAL LETTER O
+    F0  D09F ; #       CYRILLIC CAPITAL LETTER PE
+    F1  D0AF ; #       CYRILLIC CAPITAL LETTER YA
+    F2  D0A0 ; #       CYRILLIC CAPITAL LETTER ER
+    F3  D0A1 ; #       CYRILLIC CAPITAL LETTER ES
+    F4  D0A2 ; #       CYRILLIC CAPITAL LETTER TE
+    F5  D0A3 ; #       CYRILLIC CAPITAL LETTER U
+    F6  D096 ; #       CYRILLIC CAPITAL LETTER ZHE
+    F7  D092 ; #       CYRILLIC CAPITAL LETTER VE
+    F8  D0AC ; #       CYRILLIC CAPITAL LETTER SOFT SIGN
+    F9  D0AB ; #       CYRILLIC CAPITAL LETTER YERU
+    FA  D097 ; #       CYRILLIC CAPITAL LETTER ZE
+    FB  D0A8 ; #       CYRILLIC CAPITAL LETTER SHA
+    FC  D0AD ; #       CYRILLIC CAPITAL LETTER E
+    FD  D0A9 ; #       CYRILLIC CAPITAL LETTER SHCHA
+    FE  D0A7 ; #       CYRILLIC CAPITAL LETTER CHE
+    FF  D0AA ; #       CYRILLIC CAPITAL LETTER HARD SIGN
+}
diff --git a/nginx/contrib/unicode2nginx/unicode-to-nginx.pl b/nginx/contrib/unicode2nginx/unicode-to-nginx.pl
new file mode 100755 (executable)
index 0000000..d113fed
--- /dev/null
@@ -0,0 +1,48 @@
+#!/usr/bin/perl -w
+
+# Convert unicode mappings to nginx configuration file format.
+
+# You may find useful mappings in various places, including
+# unicode.org official site:
+#
+# http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1251.TXT
+# http://www.unicode.org/Public/MAPPINGS/VENDORS/MISC/KOI8-R.TXT
+
+# Needs perl 5.6 or later.
+
+# Written by Maxim Dounin, [email protected]
+
+###############################################################################
+
+require 5.006;
+
+while (<>) {
+       # Skip comments and empty lines
+
+       next if /^#/;
+       next if /^\s*$/;
+       chomp;
+
+       # Convert mappings
+
+       if (/^\s*0x(..)\s*0x(....)\s*(#.*)/) {
+               # Mapping <from-code> <unicode-code> "#" <unicode-name>
+               my $cs_code = $1;
+               my $un_code = $2;
+               my $un_name = $3;
+
+               # Produce UTF-8 sequence from character code;
+
+               my $un_utf8 = join('',
+                       map { sprintf("%02X", $_) }
+                       unpack("U0C*", pack("U", hex($un_code)))
+               );
+
+               print "    $cs_code  $un_utf8 ; $un_name\n";
+
+       } else {
+               warn "Unrecognized line: '$_'";
+       }
+}
+
+###############################################################################
diff --git a/nginx/contrib/unicode2nginx/win-utf b/nginx/contrib/unicode2nginx/win-utf
new file mode 100644 (file)
index 0000000..af9f9aa
--- /dev/null
@@ -0,0 +1,130 @@
+charset_map  windows-1251  utf-8 {
+
+    80  D082 ; #CYRILLIC CAPITAL LETTER DJE
+    81  D083 ; #CYRILLIC CAPITAL LETTER GJE
+    82  E2809A ; #SINGLE LOW-9 QUOTATION MARK
+    83  D193 ; #CYRILLIC SMALL LETTER GJE
+    84  E2809E ; #DOUBLE LOW-9 QUOTATION MARK
+    85  E280A6 ; #HORIZONTAL ELLIPSIS
+    86  E280A0 ; #DAGGER
+    87  E280A1 ; #DOUBLE DAGGER
+    88  E282AC ; #EURO SIGN
+    89  E280B0 ; #PER MILLE SIGN
+    8A  D089 ; #CYRILLIC CAPITAL LETTER LJE
+    8B  E280B9 ; #SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+    8C  D08A ; #CYRILLIC CAPITAL LETTER NJE
+    8D  D08C ; #CYRILLIC CAPITAL LETTER KJE
+    8E  D08B ; #CYRILLIC CAPITAL LETTER TSHE
+    8F  D08F ; #CYRILLIC CAPITAL LETTER DZHE
+    90  D192 ; #CYRILLIC SMALL LETTER DJE
+    91  E28098 ; #LEFT SINGLE QUOTATION MARK
+    92  E28099 ; #RIGHT SINGLE QUOTATION MARK
+    93  E2809C ; #LEFT DOUBLE QUOTATION MARK
+    94  E2809D ; #RIGHT DOUBLE QUOTATION MARK
+    95  E280A2 ; #BULLET
+    96  E28093 ; #EN DASH
+    97  E28094 ; #EM DASH
+    99  E284A2 ; #TRADE MARK SIGN
+    9A  D199 ; #CYRILLIC SMALL LETTER LJE
+    9B  E280BA ; #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+    9C  D19A ; #CYRILLIC SMALL LETTER NJE
+    9D  D19C ; #CYRILLIC SMALL LETTER KJE
+    9E  D19B ; #CYRILLIC SMALL LETTER TSHE
+    9F  D19F ; #CYRILLIC SMALL LETTER DZHE
+    A0  C2A0 ; #NO-BREAK SPACE
+    A1  D08E ; #CYRILLIC CAPITAL LETTER SHORT U
+    A2  D19E ; #CYRILLIC SMALL LETTER SHORT U
+    A3  D088 ; #CYRILLIC CAPITAL LETTER JE
+    A4  C2A4 ; #CURRENCY SIGN
+    A5  D290 ; #CYRILLIC CAPITAL LETTER GHE WITH UPTURN
+    A6  C2A6 ; #BROKEN BAR
+    A7  C2A7 ; #SECTION SIGN
+    A8  D081 ; #CYRILLIC CAPITAL LETTER IO
+    A9  C2A9 ; #COPYRIGHT SIGN
+    AA  D084 ; #CYRILLIC CAPITAL LETTER UKRAINIAN IE
+    AB  C2AB ; #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+    AC  C2AC ; #NOT SIGN
+    AD  C2AD ; #SOFT HYPHEN
+    AE  C2AE ; #REGISTERED SIGN
+    AF  D087 ; #CYRILLIC CAPITAL LETTER YI
+    B0  C2B0 ; #DEGREE SIGN
+    B1  C2B1 ; #PLUS-MINUS SIGN
+    B2  D086 ; #CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
+    B3  D196 ; #CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+    B4  D291 ; #CYRILLIC SMALL LETTER GHE WITH UPTURN
+    B5  C2B5 ; #MICRO SIGN
+    B6  C2B6 ; #PILCROW SIGN
+    B7  C2B7 ; #MIDDLE DOT
+    B8  D191 ; #CYRILLIC SMALL LETTER IO
+    B9  E28496 ; #NUMERO SIGN
+    BA  D194 ; #CYRILLIC SMALL LETTER UKRAINIAN IE
+    BB  C2BB ; #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+    BC  D198 ; #CYRILLIC SMALL LETTER JE
+    BD  D085 ; #CYRILLIC CAPITAL LETTER DZE
+    BE  D195 ; #CYRILLIC SMALL LETTER DZE
+    BF  D197 ; #CYRILLIC SMALL LETTER YI
+    C0  D090 ; #CYRILLIC CAPITAL LETTER A
+    C1  D091 ; #CYRILLIC CAPITAL LETTER BE
+    C2  D092 ; #CYRILLIC CAPITAL LETTER VE
+    C3  D093 ; #CYRILLIC CAPITAL LETTER GHE
+    C4  D094 ; #CYRILLIC CAPITAL LETTER DE
+    C5  D095 ; #CYRILLIC CAPITAL LETTER IE
+    C6  D096 ; #CYRILLIC CAPITAL LETTER ZHE
+    C7  D097 ; #CYRILLIC CAPITAL LETTER ZE
+    C8  D098 ; #CYRILLIC CAPITAL LETTER I
+    C9  D099 ; #CYRILLIC CAPITAL LETTER SHORT I
+    CA  D09A ; #CYRILLIC CAPITAL LETTER KA
+    CB  D09B ; #CYRILLIC CAPITAL LETTER EL
+    CC  D09C ; #CYRILLIC CAPITAL LETTER EM
+    CD  D09D ; #CYRILLIC CAPITAL LETTER EN
+    CE  D09E ; #CYRILLIC CAPITAL LETTER O
+    CF  D09F ; #CYRILLIC CAPITAL LETTER PE
+    D0  D0A0 ; #CYRILLIC CAPITAL LETTER ER
+    D1  D0A1 ; #CYRILLIC CAPITAL LETTER ES
+    D2  D0A2 ; #CYRILLIC CAPITAL LETTER TE
+    D3  D0A3 ; #CYRILLIC CAPITAL LETTER U
+    D4  D0A4 ; #CYRILLIC CAPITAL LETTER EF
+    D5  D0A5 ; #CYRILLIC CAPITAL LETTER HA
+    D6  D0A6 ; #CYRILLIC CAPITAL LETTER TSE
+    D7  D0A7 ; #CYRILLIC CAPITAL LETTER CHE
+    D8  D0A8 ; #CYRILLIC CAPITAL LETTER SHA
+    D9  D0A9 ; #CYRILLIC CAPITAL LETTER SHCHA
+    DA  D0AA ; #CYRILLIC CAPITAL LETTER HARD SIGN
+    DB  D0AB ; #CYRILLIC CAPITAL LETTER YERU
+    DC  D0AC ; #CYRILLIC CAPITAL LETTER SOFT SIGN
+    DD  D0AD ; #CYRILLIC CAPITAL LETTER E
+    DE  D0AE ; #CYRILLIC CAPITAL LETTER YU
+    DF  D0AF ; #CYRILLIC CAPITAL LETTER YA
+    E0  D0B0 ; #CYRILLIC SMALL LETTER A
+    E1  D0B1 ; #CYRILLIC SMALL LETTER BE
+    E2  D0B2 ; #CYRILLIC SMALL LETTER VE
+    E3  D0B3 ; #CYRILLIC SMALL LETTER GHE
+    E4  D0B4 ; #CYRILLIC SMALL LETTER DE
+    E5  D0B5 ; #CYRILLIC SMALL LETTER IE
+    E6  D0B6 ; #CYRILLIC SMALL LETTER ZHE
+    E7  D0B7 ; #CYRILLIC SMALL LETTER ZE
+    E8  D0B8 ; #CYRILLIC SMALL LETTER I
+    E9  D0B9 ; #CYRILLIC SMALL LETTER SHORT I
+    EA  D0BA ; #CYRILLIC SMALL LETTER KA
+    EB  D0BB ; #CYRILLIC SMALL LETTER EL
+    EC  D0BC ; #CYRILLIC SMALL LETTER EM
+    ED  D0BD ; #CYRILLIC SMALL LETTER EN
+    EE  D0BE ; #CYRILLIC SMALL LETTER O
+    EF  D0BF ; #CYRILLIC SMALL LETTER PE
+    F0  D180 ; #CYRILLIC SMALL LETTER ER
+    F1  D181 ; #CYRILLIC SMALL LETTER ES
+    F2  D182 ; #CYRILLIC SMALL LETTER TE
+    F3  D183 ; #CYRILLIC SMALL LETTER U
+    F4  D184 ; #CYRILLIC SMALL LETTER EF
+    F5  D185 ; #CYRILLIC SMALL LETTER HA
+    F6  D186 ; #CYRILLIC SMALL LETTER TSE
+    F7  D187 ; #CYRILLIC SMALL LETTER CHE
+    F8  D188 ; #CYRILLIC SMALL LETTER SHA
+    F9  D189 ; #CYRILLIC SMALL LETTER SHCHA
+    FA  D18A ; #CYRILLIC SMALL LETTER HARD SIGN
+    FB  D18B ; #CYRILLIC SMALL LETTER YERU
+    FC  D18C ; #CYRILLIC SMALL LETTER SOFT SIGN
+    FD  D18D ; #CYRILLIC SMALL LETTER E
+    FE  D18E ; #CYRILLIC SMALL LETTER YU
+    FF  D18F ; #CYRILLIC SMALL LETTER YA
+}
diff --git a/nginx/contrib/vim/ftdetect/nginx.vim b/nginx/contrib/vim/ftdetect/nginx.vim
new file mode 100644 (file)
index 0000000..3ae470d
--- /dev/null
@@ -0,0 +1,4 @@
+au BufRead,BufNewFile *.nginx set ft=nginx
+au BufRead,BufNewFile */etc/nginx/* set ft=nginx
+au BufRead,BufNewFile */usr/local/nginx/conf/* set ft=nginx
+au BufRead,BufNewFile nginx.conf set ft=nginx
diff --git a/nginx/contrib/vim/ftplugin/nginx.vim b/nginx/contrib/vim/ftplugin/nginx.vim
new file mode 100644 (file)
index 0000000..463eea9
--- /dev/null
@@ -0,0 +1 @@
+setlocal commentstring=#\ %s
diff --git a/nginx/contrib/vim/indent/nginx.vim b/nginx/contrib/vim/indent/nginx.vim
new file mode 100644 (file)
index 0000000..8601366
--- /dev/null
@@ -0,0 +1,11 @@
+if exists("b:did_indent")
+    finish
+endif
+let b:did_indent = 1
+
+setlocal indentexpr=
+
+" cindent actually works for nginx' simple file structure
+setlocal cindent
+" Just make sure that the comments are not reset as defs would be.
+setlocal cinkeys-=0#
diff --git a/nginx/contrib/vim/syntax/nginx.vim b/nginx/contrib/vim/syntax/nginx.vim
new file mode 100644 (file)
index 0000000..075b19a
--- /dev/null
@@ -0,0 +1,2268 @@
+" Vim syntax file
+" Language: nginx.conf
+
+if exists("b:current_syntax")
+  finish
+end
+
+" general syntax
+
+if has("patch-7.4.1142")
+    " except control characters, ";", "{", and "}"
+    syn iskeyword 33-58,60-122,124,126-255
+endif
+
+syn match ngxName '\([^;{} \t\\]\|\\.\)\+'
+    \ contains=@ngxDirectives
+    \ nextgroup=@ngxParams skipwhite skipempty
+syn match ngxParam '\(\${\|[^;{ \t\\]\|\\.\)\+'
+    \ contained
+    \ contains=ngxVariable
+    \ nextgroup=@ngxParams skipwhite skipempty
+syn region ngxString start=+\z(["']\)+ end=+\z1+ skip=+\\\\\|\\\z1+
+    \ contains=ngxVariableString
+    \ nextgroup=@ngxParams skipwhite skipempty
+syn match ngxParamComment '#.*$'
+    \ nextgroup=@ngxParams skipwhite skipempty
+syn match ngxSemicolon ';' contained
+syn region ngxBlock start=+{+ end=+}+ contained
+    \ contains=@ngxTopLevel
+syn match ngxComment '#.*$'
+
+syn match ngxVariable '\$\(\w\+\|{\w\+}\)' contained
+syn match ngxVariableString '\$\(\w\+\|{\w\+}\)' contained
+
+syn cluster ngxTopLevel
+    \ contains=ngxName,ngxString,ngxComment
+syn cluster ngxDirectives
+    \ contains=ngxDirective,ngxDirectiveBlock,ngxDirectiveImportant
+    \ add=ngxDirectiveControl,ngxDirectiveError,ngxDirectiveDeprecated
+    \ add=ngxDirectiveThirdParty,ngxDirectiveThirdPartyDeprecated
+syn cluster ngxParams
+    \ contains=ngxParam,ngxString,ngxParamComment,ngxSemicolon,ngxBlock
+
+" boolean parameters
+
+syn keyword ngxBoolean contained on off
+    \ nextgroup=@ngxParams skipwhite skipempty
+syn cluster ngxParams add=ngxBoolean
+
+" listen directive
+
+syn cluster ngxTopLevel add=ngxDirectiveListen
+syn keyword ngxDirectiveListen listen
+    \ nextgroup=@ngxListenParams skipwhite skipempty
+syn match ngxListenParam '\(\${\|[^;{ \t\\]\|\\.\)\+'
+    \ contained
+    \ nextgroup=@ngxListenParams skipwhite skipempty
+syn region ngxListenString start=+\z(["']\)+ end=+\z1+ skip=+\\\\\|\\\z1+
+    \ contained
+    \ nextgroup=@ngxListenParams skipwhite skipempty
+syn match ngxListenComment '#.*$'
+    \ contained
+    \ nextgroup=@ngxListenParams skipwhite skipempty
+syn keyword ngxListenOptions contained
+    \ default_server ssl http2 proxy_protocol
+    \ setfib fastopen backlog rcvbuf sndbuf accept_filter deferred bind
+    \ ipv6only reuseport so_keepalive
+    \ nextgroup=@ngxListenParams skipwhite skipempty
+syn keyword ngxListenOptionsDeprecated contained
+    \ spdy
+    \ nextgroup=@ngxListenParams skipwhite skipempty
+syn cluster ngxListenParams
+    \ contains=ngxListenParam,ngxListenString,ngxListenComment
+    \ add=ngxListenOptions,ngxListenOptionsDeprecated
+
+syn keyword ngxDirectiveBlock contained http
+syn keyword ngxDirectiveBlock contained stream
+syn keyword ngxDirectiveBlock contained mail
+syn keyword ngxDirectiveBlock contained events
+syn keyword ngxDirectiveBlock contained server
+syn keyword ngxDirectiveBlock contained types
+syn keyword ngxDirectiveBlock contained location
+syn keyword ngxDirectiveBlock contained upstream
+syn keyword ngxDirectiveBlock contained charset_map
+syn keyword ngxDirectiveBlock contained limit_except
+syn keyword ngxDirectiveBlock contained if
+syn keyword ngxDirectiveBlock contained geo
+syn keyword ngxDirectiveBlock contained map
+syn keyword ngxDirectiveBlock contained split_clients
+syn keyword ngxDirectiveBlock contained match
+
+syn keyword ngxDirectiveImportant contained include
+syn keyword ngxDirectiveImportant contained root
+syn keyword ngxDirectiveImportant contained server_name
+syn keyword ngxDirectiveImportant contained internal
+syn keyword ngxDirectiveImportant contained proxy_pass
+syn keyword ngxDirectiveImportant contained memcached_pass
+syn keyword ngxDirectiveImportant contained fastcgi_pass
+syn keyword ngxDirectiveImportant contained scgi_pass
+syn keyword ngxDirectiveImportant contained uwsgi_pass
+syn keyword ngxDirectiveImportant contained try_files
+
+syn keyword ngxDirectiveControl contained break
+syn keyword ngxDirectiveControl contained return
+syn keyword ngxDirectiveControl contained rewrite
+syn keyword ngxDirectiveControl contained set
+
+syn keyword ngxDirectiveError contained error_page
+syn keyword ngxDirectiveError contained post_action
+
+syn keyword ngxDirectiveDeprecated contained proxy_downstream_buffer
+syn keyword ngxDirectiveDeprecated contained proxy_upstream_buffer
+syn keyword ngxDirectiveDeprecated contained spdy_chunk_size
+syn keyword ngxDirectiveDeprecated contained spdy_headers_comp
+syn keyword ngxDirectiveDeprecated contained spdy_keepalive_timeout
+syn keyword ngxDirectiveDeprecated contained spdy_max_concurrent_streams
+syn keyword ngxDirectiveDeprecated contained spdy_pool_size
+syn keyword ngxDirectiveDeprecated contained spdy_recv_buffer_size
+syn keyword ngxDirectiveDeprecated contained spdy_recv_timeout
+syn keyword ngxDirectiveDeprecated contained spdy_streams_index_size
+syn keyword ngxDirectiveDeprecated contained upstream_conf
+
+syn keyword ngxDirective contained absolute_redirect
+syn keyword ngxDirective contained accept_mutex
+syn keyword ngxDirective contained accept_mutex_delay
+syn keyword ngxDirective contained acceptex_read
+syn keyword ngxDirective contained access_log
+syn keyword ngxDirective contained add_after_body
+syn keyword ngxDirective contained add_before_body
+syn keyword ngxDirective contained add_header
+syn keyword ngxDirective contained add_trailer
+syn keyword ngxDirective contained addition_types
+syn keyword ngxDirective contained aio
+syn keyword ngxDirective contained aio_write
+syn keyword ngxDirective contained alias
+syn keyword ngxDirective contained allow
+syn keyword ngxDirective contained ancient_browser
+syn keyword ngxDirective contained ancient_browser_value
+syn keyword ngxDirective contained auth_basic
+syn keyword ngxDirective contained auth_basic_user_file
+syn keyword ngxDirective contained auth_http
+syn keyword ngxDirective contained auth_http_header
+syn keyword ngxDirective contained auth_http_pass_client_cert
+syn keyword ngxDirective contained auth_http_timeout
+syn keyword ngxDirective contained auth_jwt
+syn keyword ngxDirective contained auth_jwt_key_file
+syn keyword ngxDirective contained auth_request
+syn keyword ngxDirective contained auth_request_set
+syn keyword ngxDirective contained autoindex
+syn keyword ngxDirective contained autoindex_exact_size
+syn keyword ngxDirective contained autoindex_format
+syn keyword ngxDirective contained autoindex_localtime
+syn keyword ngxDirective contained charset
+syn keyword ngxDirective contained charset_types
+syn keyword ngxDirective contained chunked_transfer_encoding
+syn keyword ngxDirective contained client_body_buffer_size
+syn keyword ngxDirective contained client_body_in_file_only
+syn keyword ngxDirective contained client_body_in_single_buffer
+syn keyword ngxDirective contained client_body_temp_path
+syn keyword ngxDirective contained client_body_timeout
+syn keyword ngxDirective contained client_header_buffer_size
+syn keyword ngxDirective contained client_header_timeout
+syn keyword ngxDirective contained client_max_body_size
+syn keyword ngxDirective contained connection_pool_size
+syn keyword ngxDirective contained create_full_put_path
+syn keyword ngxDirective contained daemon
+syn keyword ngxDirective contained dav_access
+syn keyword ngxDirective contained dav_methods
+syn keyword ngxDirective contained debug_connection
+syn keyword ngxDirective contained debug_points
+syn keyword ngxDirective contained default_type
+syn keyword ngxDirective contained degradation
+syn keyword ngxDirective contained degrade
+syn keyword ngxDirective contained deny
+syn keyword ngxDirective contained devpoll_changes
+syn keyword ngxDirective contained devpoll_events
+syn keyword ngxDirective contained directio
+syn keyword ngxDirective contained directio_alignment
+syn keyword ngxDirective contained disable_symlinks
+syn keyword ngxDirective contained empty_gif
+syn keyword ngxDirective contained env
+syn keyword ngxDirective contained epoll_events
+syn keyword ngxDirective contained error_log
+syn keyword ngxDirective contained etag
+syn keyword ngxDirective contained eventport_events
+syn keyword ngxDirective contained expires
+syn keyword ngxDirective contained f4f
+syn keyword ngxDirective contained f4f_buffer_size
+syn keyword ngxDirective contained fastcgi_bind
+syn keyword ngxDirective contained fastcgi_buffer_size
+syn keyword ngxDirective contained fastcgi_buffering
+syn keyword ngxDirective contained fastcgi_buffers
+syn keyword ngxDirective contained fastcgi_busy_buffers_size
+syn keyword ngxDirective contained fastcgi_cache
+syn keyword ngxDirective contained fastcgi_cache_background_update
+syn keyword ngxDirective contained fastcgi_cache_bypass
+syn keyword ngxDirective contained fastcgi_cache_key
+syn keyword ngxDirective contained fastcgi_cache_lock
+syn keyword ngxDirective contained fastcgi_cache_lock_age
+syn keyword ngxDirective contained fastcgi_cache_lock_timeout
+syn keyword ngxDirective contained fastcgi_cache_max_range_offset
+syn keyword ngxDirective contained fastcgi_cache_methods
+syn keyword ngxDirective contained fastcgi_cache_min_uses
+syn keyword ngxDirective contained fastcgi_cache_path
+syn keyword ngxDirective contained fastcgi_cache_purge
+syn keyword ngxDirective contained fastcgi_cache_revalidate
+syn keyword ngxDirective contained fastcgi_cache_use_stale
+syn keyword ngxDirective contained fastcgi_cache_valid
+syn keyword ngxDirective contained fastcgi_catch_stderr
+syn keyword ngxDirective contained fastcgi_connect_timeout
+syn keyword ngxDirective contained fastcgi_force_ranges
+syn keyword ngxDirective contained fastcgi_hide_header
+syn keyword ngxDirective contained fastcgi_ignore_client_abort
+syn keyword ngxDirective contained fastcgi_ignore_headers
+syn keyword ngxDirective contained fastcgi_index
+syn keyword ngxDirective contained fastcgi_intercept_errors
+syn keyword ngxDirective contained fastcgi_keep_conn
+syn keyword ngxDirective contained fastcgi_limit_rate
+syn keyword ngxDirective contained fastcgi_max_temp_file_size
+syn keyword ngxDirective contained fastcgi_next_upstream
+syn keyword ngxDirective contained fastcgi_next_upstream_timeout
+syn keyword ngxDirective contained fastcgi_next_upstream_tries
+syn keyword ngxDirective contained fastcgi_no_cache
+syn keyword ngxDirective contained fastcgi_param
+syn keyword ngxDirective contained fastcgi_pass_header
+syn keyword ngxDirective contained fastcgi_pass_request_body
+syn keyword ngxDirective contained fastcgi_pass_request_headers
+syn keyword ngxDirective contained fastcgi_read_timeout
+syn keyword ngxDirective contained fastcgi_request_buffering
+syn keyword ngxDirective contained fastcgi_send_lowat
+syn keyword ngxDirective contained fastcgi_send_timeout
+syn keyword ngxDirective contained fastcgi_split_path_info
+syn keyword ngxDirective contained fastcgi_store
+syn keyword ngxDirective contained fastcgi_store_access
+syn keyword ngxDirective contained fastcgi_temp_file_write_size
+syn keyword ngxDirective contained fastcgi_temp_path
+syn keyword ngxDirective contained flv
+syn keyword ngxDirective contained geoip_city
+syn keyword ngxDirective contained geoip_country
+syn keyword ngxDirective contained geoip_org
+syn keyword ngxDirective contained geoip_proxy
+syn keyword ngxDirective contained geoip_proxy_recursive
+syn keyword ngxDirective contained google_perftools_profiles
+syn keyword ngxDirective contained grpc_bind
+syn keyword ngxDirective contained grpc_buffer_size
+syn keyword ngxDirective contained grpc_connect_timeout
+syn keyword ngxDirective contained grpc_hide_header
+syn keyword ngxDirective contained grpc_ignore_headers
+syn keyword ngxDirective contained grpc_intercept_errors
+syn keyword ngxDirective contained grpc_next_upstream
+syn keyword ngxDirective contained grpc_next_upstream_timeout
+syn keyword ngxDirective contained grpc_next_upstream_tries
+syn keyword ngxDirective contained grpc_pass
+syn keyword ngxDirective contained grpc_pass_header
+syn keyword ngxDirective contained grpc_read_timeout
+syn keyword ngxDirective contained grpc_send_timeout
+syn keyword ngxDirective contained grpc_set_header
+syn keyword ngxDirective contained grpc_ssl_certificate
+syn keyword ngxDirective contained grpc_ssl_certificate_key
+syn keyword ngxDirective contained grpc_ssl_ciphers
+syn keyword ngxDirective contained grpc_ssl_crl
+syn keyword ngxDirective contained grpc_ssl_name
+syn keyword ngxDirective contained grpc_ssl_password_file
+syn keyword ngxDirective contained grpc_ssl_protocols
+syn keyword ngxDirective contained grpc_ssl_server_name
+syn keyword ngxDirective contained grpc_ssl_session_reuse
+syn keyword ngxDirective contained grpc_ssl_trusted_certificate
+syn keyword ngxDirective contained grpc_ssl_verify
+syn keyword ngxDirective contained grpc_ssl_verify_depth
+syn keyword ngxDirective contained gunzip
+syn keyword ngxDirective contained gunzip_buffers
+syn keyword ngxDirective contained gzip
+syn keyword ngxDirective contained gzip_buffers
+syn keyword ngxDirective contained gzip_comp_level
+syn keyword ngxDirective contained gzip_disable
+syn keyword ngxDirective contained gzip_hash
+syn keyword ngxDirective contained gzip_http_version
+syn keyword ngxDirective contained gzip_min_length
+syn keyword ngxDirective contained gzip_no_buffer
+syn keyword ngxDirective contained gzip_proxied
+syn keyword ngxDirective contained gzip_static
+syn keyword ngxDirective contained gzip_types
+syn keyword ngxDirective contained gzip_vary
+syn keyword ngxDirective contained gzip_window
+syn keyword ngxDirective contained hash
+syn keyword ngxDirective contained health_check
+syn keyword ngxDirective contained health_check_timeout
+syn keyword ngxDirective contained hls
+syn keyword ngxDirective contained hls_buffers
+syn keyword ngxDirective contained hls_forward_args
+syn keyword ngxDirective contained hls_fragment
+syn keyword ngxDirective contained hls_mp4_buffer_size
+syn keyword ngxDirective contained hls_mp4_max_buffer_size
+syn keyword ngxDirective contained http2_body_preread_size
+syn keyword ngxDirective contained http2_chunk_size
+syn keyword ngxDirective contained http2_idle_timeout
+syn keyword ngxDirective contained http2_max_concurrent_pushes
+syn keyword ngxDirective contained http2_max_concurrent_streams
+syn keyword ngxDirective contained http2_max_field_size
+syn keyword ngxDirective contained http2_max_header_size
+syn keyword ngxDirective contained http2_max_requests
+syn keyword ngxDirective contained http2_pool_size
+syn keyword ngxDirective contained http2_push
+syn keyword ngxDirective contained http2_push_preload
+syn keyword ngxDirective contained http2_recv_buffer_size
+syn keyword ngxDirective contained http2_recv_timeout
+syn keyword ngxDirective contained http2_streams_index_size
+syn keyword ngxDirective contained if_modified_since
+syn keyword ngxDirective contained ignore_invalid_headers
+syn keyword ngxDirective contained image_filter
+syn keyword ngxDirective contained image_filter_buffer
+syn keyword ngxDirective contained image_filter_interlace
+syn keyword ngxDirective contained image_filter_jpeg_quality
+syn keyword ngxDirective contained image_filter_sharpen
+syn keyword ngxDirective contained image_filter_transparency
+syn keyword ngxDirective contained image_filter_webp_quality
+syn keyword ngxDirective contained imap_auth
+syn keyword ngxDirective contained imap_capabilities
+syn keyword ngxDirective contained imap_client_buffer
+syn keyword ngxDirective contained index
+syn keyword ngxDirective contained iocp_threads
+syn keyword ngxDirective contained ip_hash
+syn keyword ngxDirective contained js_access
+syn keyword ngxDirective contained js_content
+syn keyword ngxDirective contained js_filter
+syn keyword ngxDirective contained js_include
+syn keyword ngxDirective contained js_preread
+syn keyword ngxDirective contained js_set
+syn keyword ngxDirective contained keepalive
+syn keyword ngxDirective contained keepalive_disable
+syn keyword ngxDirective contained keepalive_requests
+syn keyword ngxDirective contained keepalive_timeout
+syn keyword ngxDirective contained kqueue_changes
+syn keyword ngxDirective contained kqueue_events
+syn keyword ngxDirective contained large_client_header_buffers
+syn keyword ngxDirective contained least_conn
+syn keyword ngxDirective contained least_time
+syn keyword ngxDirective contained limit_conn
+syn keyword ngxDirective contained limit_conn_log_level
+syn keyword ngxDirective contained limit_conn_status
+syn keyword ngxDirective contained limit_conn_zone
+syn keyword ngxDirective contained limit_rate
+syn keyword ngxDirective contained limit_rate_after
+syn keyword ngxDirective contained limit_req
+syn keyword ngxDirective contained limit_req_log_level
+syn keyword ngxDirective contained limit_req_status
+syn keyword ngxDirective contained limit_req_zone
+syn keyword ngxDirective contained lingering_close
+syn keyword ngxDirective contained lingering_time
+syn keyword ngxDirective contained lingering_timeout
+syn keyword ngxDirective contained load_module
+syn keyword ngxDirective contained lock_file
+syn keyword ngxDirective contained log_format
+syn keyword ngxDirective contained log_not_found
+syn keyword ngxDirective contained log_subrequest
+syn keyword ngxDirective contained map_hash_bucket_size
+syn keyword ngxDirective contained map_hash_max_size
+syn keyword ngxDirective contained master_process
+syn keyword ngxDirective contained max_ranges
+syn keyword ngxDirective contained memcached_bind
+syn keyword ngxDirective contained memcached_buffer_size
+syn keyword ngxDirective contained memcached_connect_timeout
+syn keyword ngxDirective contained memcached_force_ranges
+syn keyword ngxDirective contained memcached_gzip_flag
+syn keyword ngxDirective contained memcached_next_upstream
+syn keyword ngxDirective contained memcached_next_upstream_timeout
+syn keyword ngxDirective contained memcached_next_upstream_tries
+syn keyword ngxDirective contained memcached_read_timeout
+syn keyword ngxDirective contained memcached_send_timeout
+syn keyword ngxDirective contained merge_slashes
+syn keyword ngxDirective contained min_delete_depth
+syn keyword ngxDirective contained mirror
+syn keyword ngxDirective contained mirror_request_body
+syn keyword ngxDirective contained modern_browser
+syn keyword ngxDirective contained modern_browser_value
+syn keyword ngxDirective contained mp4
+syn keyword ngxDirective contained mp4_buffer_size
+syn keyword ngxDirective contained mp4_max_buffer_size
+syn keyword ngxDirective contained mp4_limit_rate
+syn keyword ngxDirective contained mp4_limit_rate_after
+syn keyword ngxDirective contained msie_padding
+syn keyword ngxDirective contained msie_refresh
+syn keyword ngxDirective contained multi_accept
+syn keyword ngxDirective contained ntlm
+syn keyword ngxDirective contained open_file_cache
+syn keyword ngxDirective contained open_file_cache_errors
+syn keyword ngxDirective contained open_file_cache_events
+syn keyword ngxDirective contained open_file_cache_min_uses
+syn keyword ngxDirective contained open_file_cache_valid
+syn keyword ngxDirective contained open_log_file_cache
+syn keyword ngxDirective contained output_buffers
+syn keyword ngxDirective contained override_charset
+syn keyword ngxDirective contained pcre_jit
+syn keyword ngxDirective contained perl
+syn keyword ngxDirective contained perl_modules
+syn keyword ngxDirective contained perl_require
+syn keyword ngxDirective contained perl_set
+syn keyword ngxDirective contained pid
+syn keyword ngxDirective contained pop3_auth
+syn keyword ngxDirective contained pop3_capabilities
+syn keyword ngxDirective contained port_in_redirect
+syn keyword ngxDirective contained post_acceptex
+syn keyword ngxDirective contained postpone_gzipping
+syn keyword ngxDirective contained postpone_output
+syn keyword ngxDirective contained preread_buffer_size
+syn keyword ngxDirective contained preread_timeout
+syn keyword ngxDirective contained protocol
+syn keyword ngxDirective contained proxy
+syn keyword ngxDirective contained proxy_bind
+syn keyword ngxDirective contained proxy_buffer
+syn keyword ngxDirective contained proxy_buffer_size
+syn keyword ngxDirective contained proxy_buffering
+syn keyword ngxDirective contained proxy_buffers
+syn keyword ngxDirective contained proxy_busy_buffers_size
+syn keyword ngxDirective contained proxy_cache
+syn keyword ngxDirective contained proxy_cache_background_update
+syn keyword ngxDirective contained proxy_cache_bypass
+syn keyword ngxDirective contained proxy_cache_convert_head
+syn keyword ngxDirective contained proxy_cache_key
+syn keyword ngxDirective contained proxy_cache_lock
+syn keyword ngxDirective contained proxy_cache_lock_age
+syn keyword ngxDirective contained proxy_cache_lock_timeout
+syn keyword ngxDirective contained proxy_cache_max_range_offset
+syn keyword ngxDirective contained proxy_cache_methods
+syn keyword ngxDirective contained proxy_cache_min_uses
+syn keyword ngxDirective contained proxy_cache_path
+syn keyword ngxDirective contained proxy_cache_purge
+syn keyword ngxDirective contained proxy_cache_revalidate
+syn keyword ngxDirective contained proxy_cache_use_stale
+syn keyword ngxDirective contained proxy_cache_valid
+syn keyword ngxDirective contained proxy_connect_timeout
+syn keyword ngxDirective contained proxy_cookie_domain
+syn keyword ngxDirective contained proxy_cookie_path
+syn keyword ngxDirective contained proxy_download_rate
+syn keyword ngxDirective contained proxy_force_ranges
+syn keyword ngxDirective contained proxy_headers_hash_bucket_size
+syn keyword ngxDirective contained proxy_headers_hash_max_size
+syn keyword ngxDirective contained proxy_hide_header
+syn keyword ngxDirective contained proxy_http_version
+syn keyword ngxDirective contained proxy_ignore_client_abort
+syn keyword ngxDirective contained proxy_ignore_headers
+syn keyword ngxDirective contained proxy_intercept_errors
+syn keyword ngxDirective contained proxy_limit_rate
+syn keyword ngxDirective contained proxy_max_temp_file_size
+syn keyword ngxDirective contained proxy_method
+syn keyword ngxDirective contained proxy_next_upstream
+syn keyword ngxDirective contained proxy_next_upstream_timeout
+syn keyword ngxDirective contained proxy_next_upstream_tries
+syn keyword ngxDirective contained proxy_no_cache
+syn keyword ngxDirective contained proxy_pass_error_message
+syn keyword ngxDirective contained proxy_pass_header
+syn keyword ngxDirective contained proxy_pass_request_body
+syn keyword ngxDirective contained proxy_pass_request_headers
+syn keyword ngxDirective contained proxy_protocol
+syn keyword ngxDirective contained proxy_protocol_timeout
+syn keyword ngxDirective contained proxy_read_timeout
+syn keyword ngxDirective contained proxy_redirect
+syn keyword ngxDirective contained proxy_request_buffering
+syn keyword ngxDirective contained proxy_responses
+syn keyword ngxDirective contained proxy_send_lowat
+syn keyword ngxDirective contained proxy_send_timeout
+syn keyword ngxDirective contained proxy_set_body
+syn keyword ngxDirective contained proxy_set_header
+syn keyword ngxDirective contained proxy_ssl
+syn keyword ngxDirective contained proxy_ssl_certificate
+syn keyword ngxDirective contained proxy_ssl_certificate_key
+syn keyword ngxDirective contained proxy_ssl_ciphers
+syn keyword ngxDirective contained proxy_ssl_crl
+syn keyword ngxDirective contained proxy_ssl_name
+syn keyword ngxDirective contained proxy_ssl_password_file
+syn keyword ngxDirective contained proxy_ssl_protocols
+syn keyword ngxDirective contained proxy_ssl_server_name
+syn keyword ngxDirective contained proxy_ssl_session_reuse
+syn keyword ngxDirective contained proxy_ssl_trusted_certificate
+syn keyword ngxDirective contained proxy_ssl_verify
+syn keyword ngxDirective contained proxy_ssl_verify_depth
+syn keyword ngxDirective contained proxy_store
+syn keyword ngxDirective contained proxy_store_access
+syn keyword ngxDirective contained proxy_temp_file_write_size
+syn keyword ngxDirective contained proxy_temp_path
+syn keyword ngxDirective contained proxy_timeout
+syn keyword ngxDirective contained proxy_upload_rate
+syn keyword ngxDirective contained queue
+syn keyword ngxDirective contained random_index
+syn keyword ngxDirective contained read_ahead
+syn keyword ngxDirective contained real_ip_header
+syn keyword ngxDirective contained real_ip_recursive
+syn keyword ngxDirective contained recursive_error_pages
+syn keyword ngxDirective contained referer_hash_bucket_size
+syn keyword ngxDirective contained referer_hash_max_size
+syn keyword ngxDirective contained request_pool_size
+syn keyword ngxDirective contained reset_timedout_connection
+syn keyword ngxDirective contained resolver
+syn keyword ngxDirective contained resolver_timeout
+syn keyword ngxDirective contained rewrite_log
+syn keyword ngxDirective contained satisfy
+syn keyword ngxDirective contained scgi_bind
+syn keyword ngxDirective contained scgi_buffer_size
+syn keyword ngxDirective contained scgi_buffering
+syn keyword ngxDirective contained scgi_buffers
+syn keyword ngxDirective contained scgi_busy_buffers_size
+syn keyword ngxDirective contained scgi_cache
+syn keyword ngxDirective contained scgi_cache_background_update
+syn keyword ngxDirective contained scgi_cache_bypass
+syn keyword ngxDirective contained scgi_cache_key
+syn keyword ngxDirective contained scgi_cache_lock
+syn keyword ngxDirective contained scgi_cache_lock_age
+syn keyword ngxDirective contained scgi_cache_lock_timeout
+syn keyword ngxDirective contained scgi_cache_max_range_offset
+syn keyword ngxDirective contained scgi_cache_methods
+syn keyword ngxDirective contained scgi_cache_min_uses
+syn keyword ngxDirective contained scgi_cache_path
+syn keyword ngxDirective contained scgi_cache_purge
+syn keyword ngxDirective contained scgi_cache_revalidate
+syn keyword ngxDirective contained scgi_cache_use_stale
+syn keyword ngxDirective contained scgi_cache_valid
+syn keyword ngxDirective contained scgi_connect_timeout
+syn keyword ngxDirective contained scgi_force_ranges
+syn keyword ngxDirective contained scgi_hide_header
+syn keyword ngxDirective contained scgi_ignore_client_abort
+syn keyword ngxDirective contained scgi_ignore_headers
+syn keyword ngxDirective contained scgi_intercept_errors
+syn keyword ngxDirective contained scgi_limit_rate
+syn keyword ngxDirective contained scgi_max_temp_file_size
+syn keyword ngxDirective contained scgi_next_upstream
+syn keyword ngxDirective contained scgi_next_upstream_timeout
+syn keyword ngxDirective contained scgi_next_upstream_tries
+syn keyword ngxDirective contained scgi_no_cache
+syn keyword ngxDirective contained scgi_param
+syn keyword ngxDirective contained scgi_pass_header
+syn keyword ngxDirective contained scgi_pass_request_body
+syn keyword ngxDirective contained scgi_pass_request_headers
+syn keyword ngxDirective contained scgi_read_timeout
+syn keyword ngxDirective contained scgi_request_buffering
+syn keyword ngxDirective contained scgi_send_timeout
+syn keyword ngxDirective contained scgi_store
+syn keyword ngxDirective contained scgi_store_access
+syn keyword ngxDirective contained scgi_temp_file_write_size
+syn keyword ngxDirective contained scgi_temp_path
+syn keyword ngxDirective contained secure_link
+syn keyword ngxDirective contained secure_link_md5
+syn keyword ngxDirective contained secure_link_secret
+syn keyword ngxDirective contained send_lowat
+syn keyword ngxDirective contained send_timeout
+syn keyword ngxDirective contained sendfile
+syn keyword ngxDirective contained sendfile_max_chunk
+syn keyword ngxDirective contained server_name_in_redirect
+syn keyword ngxDirective contained server_names_hash_bucket_size
+syn keyword ngxDirective contained server_names_hash_max_size
+syn keyword ngxDirective contained server_tokens
+syn keyword ngxDirective contained session_log
+syn keyword ngxDirective contained session_log_format
+syn keyword ngxDirective contained session_log_zone
+syn keyword ngxDirective contained set_real_ip_from
+syn keyword ngxDirective contained slice
+syn keyword ngxDirective contained smtp_auth
+syn keyword ngxDirective contained smtp_capabilities
+syn keyword ngxDirective contained smtp_client_buffer
+syn keyword ngxDirective contained smtp_greeting_delay
+syn keyword ngxDirective contained source_charset
+syn keyword ngxDirective contained ssi
+syn keyword ngxDirective contained ssi_ignore_recycled_buffers
+syn keyword ngxDirective contained ssi_last_modified
+syn keyword ngxDirective contained ssi_min_file_chunk
+syn keyword ngxDirective contained ssi_silent_errors
+syn keyword ngxDirective contained ssi_types
+syn keyword ngxDirective contained ssi_value_length
+syn keyword ngxDirective contained ssl
+syn keyword ngxDirective contained ssl_buffer_size
+syn keyword ngxDirective contained ssl_certificate
+syn keyword ngxDirective contained ssl_certificate_key
+syn keyword ngxDirective contained ssl_ciphers
+syn keyword ngxDirective contained ssl_client_certificate
+syn keyword ngxDirective contained ssl_crl
+syn keyword ngxDirective contained ssl_dhparam
+syn keyword ngxDirective contained ssl_ecdh_curve
+syn keyword ngxDirective contained ssl_engine
+syn keyword ngxDirective contained ssl_handshake_timeout
+syn keyword ngxDirective contained ssl_password_file
+syn keyword ngxDirective contained ssl_prefer_server_ciphers
+syn keyword ngxDirective contained ssl_preread
+syn keyword ngxDirective contained ssl_protocols
+syn keyword ngxDirective contained ssl_session_cache
+syn keyword ngxDirective contained ssl_session_ticket_key
+syn keyword ngxDirective contained ssl_session_tickets
+syn keyword ngxDirective contained ssl_session_timeout
+syn keyword ngxDirective contained ssl_stapling
+syn keyword ngxDirective contained ssl_stapling_file
+syn keyword ngxDirective contained ssl_stapling_responder
+syn keyword ngxDirective contained ssl_stapling_verify
+syn keyword ngxDirective contained ssl_trusted_certificate
+syn keyword ngxDirective contained ssl_verify_client
+syn keyword ngxDirective contained ssl_verify_depth
+syn keyword ngxDirective contained starttls
+syn keyword ngxDirective contained state
+syn keyword ngxDirective contained status
+syn keyword ngxDirective contained status_format
+syn keyword ngxDirective contained status_zone
+syn keyword ngxDirective contained sticky
+syn keyword ngxDirective contained sticky_cookie_insert
+syn keyword ngxDirective contained stub_status
+syn keyword ngxDirective contained sub_filter
+syn keyword ngxDirective contained sub_filter_last_modified
+syn keyword ngxDirective contained sub_filter_once
+syn keyword ngxDirective contained sub_filter_types
+syn keyword ngxDirective contained subrequest_output_buffer_size
+syn keyword ngxDirective contained tcp_nodelay
+syn keyword ngxDirective contained tcp_nopush
+syn keyword ngxDirective contained thread_pool
+syn keyword ngxDirective contained timeout
+syn keyword ngxDirective contained timer_resolution
+syn keyword ngxDirective contained types_hash_bucket_size
+syn keyword ngxDirective contained types_hash_max_size
+syn keyword ngxDirective contained underscores_in_headers
+syn keyword ngxDirective contained uninitialized_variable_warn
+syn keyword ngxDirective contained use
+syn keyword ngxDirective contained user
+syn keyword ngxDirective contained userid
+syn keyword ngxDirective contained userid_domain
+syn keyword ngxDirective contained userid_expires
+syn keyword ngxDirective contained userid_mark
+syn keyword ngxDirective contained userid_name
+syn keyword ngxDirective contained userid_p3p
+syn keyword ngxDirective contained userid_path
+syn keyword ngxDirective contained userid_service
+syn keyword ngxDirective contained uwsgi_bind
+syn keyword ngxDirective contained uwsgi_buffer_size
+syn keyword ngxDirective contained uwsgi_buffering
+syn keyword ngxDirective contained uwsgi_buffers
+syn keyword ngxDirective contained uwsgi_busy_buffers_size
+syn keyword ngxDirective contained uwsgi_cache
+syn keyword ngxDirective contained uwsgi_cache_background_update
+syn keyword ngxDirective contained uwsgi_cache_bypass
+syn keyword ngxDirective contained uwsgi_cache_key
+syn keyword ngxDirective contained uwsgi_cache_lock
+syn keyword ngxDirective contained uwsgi_cache_lock_age
+syn keyword ngxDirective contained uwsgi_cache_lock_timeout
+syn keyword ngxDirective contained uwsgi_cache_max_range_offset
+syn keyword ngxDirective contained uwsgi_cache_methods
+syn keyword ngxDirective contained uwsgi_cache_min_uses
+syn keyword ngxDirective contained uwsgi_cache_path
+syn keyword ngxDirective contained uwsgi_cache_purge
+syn keyword ngxDirective contained uwsgi_cache_revalidate
+syn keyword ngxDirective contained uwsgi_cache_use_stale
+syn keyword ngxDirective contained uwsgi_cache_valid
+syn keyword ngxDirective contained uwsgi_connect_timeout
+syn keyword ngxDirective contained uwsgi_force_ranges
+syn keyword ngxDirective contained uwsgi_hide_header
+syn keyword ngxDirective contained uwsgi_ignore_client_abort
+syn keyword ngxDirective contained uwsgi_ignore_headers
+syn keyword ngxDirective contained uwsgi_intercept_errors
+syn keyword ngxDirective contained uwsgi_limit_rate
+syn keyword ngxDirective contained uwsgi_max_temp_file_size
+syn keyword ngxDirective contained uwsgi_modifier1
+syn keyword ngxDirective contained uwsgi_modifier2
+syn keyword ngxDirective contained uwsgi_next_upstream
+syn keyword ngxDirective contained uwsgi_next_upstream_timeout
+syn keyword ngxDirective contained uwsgi_next_upstream_tries
+syn keyword ngxDirective contained uwsgi_no_cache
+syn keyword ngxDirective contained uwsgi_param
+syn keyword ngxDirective contained uwsgi_pass_header
+syn keyword ngxDirective contained uwsgi_pass_request_body
+syn keyword ngxDirective contained uwsgi_pass_request_headers
+syn keyword ngxDirective contained uwsgi_read_timeout
+syn keyword ngxDirective contained uwsgi_request_buffering
+syn keyword ngxDirective contained uwsgi_send_timeout
+syn keyword ngxDirective contained uwsgi_ssl_certificate
+syn keyword ngxDirective contained uwsgi_ssl_certificate_key
+syn keyword ngxDirective contained uwsgi_ssl_ciphers
+syn keyword ngxDirective contained uwsgi_ssl_crl
+syn keyword ngxDirective contained uwsgi_ssl_name
+syn keyword ngxDirective contained uwsgi_ssl_password_file
+syn keyword ngxDirective contained uwsgi_ssl_protocols
+syn keyword ngxDirective contained uwsgi_ssl_server_name
+syn keyword ngxDirective contained uwsgi_ssl_session_reuse
+syn keyword ngxDirective contained uwsgi_ssl_trusted_certificate
+syn keyword ngxDirective contained uwsgi_ssl_verify
+syn keyword ngxDirective contained uwsgi_ssl_verify_depth
+syn keyword ngxDirective contained uwsgi_store
+syn keyword ngxDirective contained uwsgi_store_access
+syn keyword ngxDirective contained uwsgi_string
+syn keyword ngxDirective contained uwsgi_temp_file_write_size
+syn keyword ngxDirective contained uwsgi_temp_path
+syn keyword ngxDirective contained valid_referers
+syn keyword ngxDirective contained variables_hash_bucket_size
+syn keyword ngxDirective contained variables_hash_max_size
+syn keyword ngxDirective contained worker_aio_requests
+syn keyword ngxDirective contained worker_connections
+syn keyword ngxDirective contained worker_cpu_affinity
+syn keyword ngxDirective contained worker_priority
+syn keyword ngxDirective contained worker_processes
+syn keyword ngxDirective contained worker_rlimit_core
+syn keyword ngxDirective contained worker_rlimit_nofile
+syn keyword ngxDirective contained worker_shutdown_timeout
+syn keyword ngxDirective contained working_directory
+syn keyword ngxDirective contained xclient
+syn keyword ngxDirective contained xml_entities
+syn keyword ngxDirective contained xslt_last_modified
+syn keyword ngxDirective contained xslt_param
+syn keyword ngxDirective contained xslt_string_param
+syn keyword ngxDirective contained xslt_stylesheet
+syn keyword ngxDirective contained xslt_types
+syn keyword ngxDirective contained zone
+
+" 3rd party modules list taken from
+" https://github.com/freebsd/freebsd-ports/blob/master/www/nginx-devel/Makefile
+" -----------------------------------------------------------------------------
+
+" Accept Language
+" https://github.com/giom/nginx_accept_language_module
+syn keyword ngxDirectiveThirdParty contained set_from_accept_language
+
+" Digest Authentication
+" https://github.com/atomx/nginx-http-auth-digest
+syn keyword ngxDirectiveThirdParty contained auth_digest
+syn keyword ngxDirectiveThirdParty contained auth_digest_drop_time
+syn keyword ngxDirectiveThirdParty contained auth_digest_evasion_time
+syn keyword ngxDirectiveThirdParty contained auth_digest_expires
+syn keyword ngxDirectiveThirdParty contained auth_digest_maxtries
+syn keyword ngxDirectiveThirdParty contained auth_digest_replays
+syn keyword ngxDirectiveThirdParty contained auth_digest_shm_size
+syn keyword ngxDirectiveThirdParty contained auth_digest_timeout
+syn keyword ngxDirectiveThirdParty contained auth_digest_user_file
+
+" SPNEGO Authentication
+" https://github.com/stnoonan/spnego-http-auth-nginx-module
+syn keyword ngxDirectiveThirdParty contained auth_gss
+syn keyword ngxDirectiveThirdParty contained auth_gss_allow_basic_fallback
+syn keyword ngxDirectiveThirdParty contained auth_gss_authorized_principal
+syn keyword ngxDirectiveThirdParty contained auth_gss_force_realm
+syn keyword ngxDirectiveThirdParty contained auth_gss_format_full
+syn keyword ngxDirectiveThirdParty contained auth_gss_keytab
+syn keyword ngxDirectiveThirdParty contained auth_gss_realm
+syn keyword ngxDirectiveThirdParty contained auth_gss_service_name
+
+" LDAP Authentication
+" https://github.com/kvspb/nginx-auth-ldap
+syn keyword ngxDirectiveThirdParty contained auth_ldap
+syn keyword ngxDirectiveThirdParty contained auth_ldap_cache_enabled
+syn keyword ngxDirectiveThirdParty contained auth_ldap_cache_expiration_time
+syn keyword ngxDirectiveThirdParty contained auth_ldap_cache_size
+syn keyword ngxDirectiveThirdParty contained auth_ldap_servers
+syn keyword ngxDirectiveThirdParty contained auth_ldap_servers_size
+syn keyword ngxDirectiveThirdParty contained ldap_server
+
+" PAM Authentication
+" https://github.com/sto/ngx_http_auth_pam_module
+syn keyword ngxDirectiveThirdParty contained auth_pam
+syn keyword ngxDirectiveThirdParty contained auth_pam_service_name
+syn keyword ngxDirectiveThirdParty contained auth_pam_set_pam_env
+
+" AJP protocol proxy
+" https://github.com/yaoweibin/nginx_ajp_module
+syn keyword ngxDirectiveThirdParty contained ajp_buffer_size
+syn keyword ngxDirectiveThirdParty contained ajp_buffers
+syn keyword ngxDirectiveThirdParty contained ajp_busy_buffers_size
+syn keyword ngxDirectiveThirdParty contained ajp_cache
+syn keyword ngxDirectiveThirdParty contained ajp_cache_key
+syn keyword ngxDirectiveThirdParty contained ajp_cache_lock
+syn keyword ngxDirectiveThirdParty contained ajp_cache_lock_timeout
+syn keyword ngxDirectiveThirdParty contained ajp_cache_methods
+syn keyword ngxDirectiveThirdParty contained ajp_cache_min_uses
+syn keyword ngxDirectiveThirdParty contained ajp_cache_path
+syn keyword ngxDirectiveThirdParty contained ajp_cache_use_stale
+syn keyword ngxDirectiveThirdParty contained ajp_cache_valid
+syn keyword ngxDirectiveThirdParty contained ajp_connect_timeout
+syn keyword ngxDirectiveThirdParty contained ajp_header_packet_buffer_size
+syn keyword ngxDirectiveThirdParty contained ajp_hide_header
+syn keyword ngxDirectiveThirdParty contained ajp_ignore_client_abort
+syn keyword ngxDirectiveThirdParty contained ajp_ignore_headers
+syn keyword ngxDirectiveThirdParty contained ajp_intercept_errors
+syn keyword ngxDirectiveThirdParty contained ajp_keep_conn
+syn keyword ngxDirectiveThirdParty contained ajp_max_data_packet_size
+syn keyword ngxDirectiveThirdParty contained ajp_max_temp_file_size
+syn keyword ngxDirectiveThirdParty contained ajp_next_upstream
+syn keyword ngxDirectiveThirdParty contained ajp_pass
+syn keyword ngxDirectiveThirdParty contained ajp_pass_header
+syn keyword ngxDirectiveThirdParty contained ajp_pass_request_body
+syn keyword ngxDirectiveThirdParty contained ajp_pass_request_headers
+syn keyword ngxDirectiveThirdParty contained ajp_read_timeout
+syn keyword ngxDirectiveThirdParty contained ajp_send_lowat
+syn keyword ngxDirectiveThirdParty contained ajp_send_timeout
+syn keyword ngxDirectiveThirdParty contained ajp_store
+syn keyword ngxDirectiveThirdParty contained ajp_store_access
+syn keyword ngxDirectiveThirdParty contained ajp_temp_file_write_size
+syn keyword ngxDirectiveThirdParty contained ajp_temp_path
+syn keyword ngxDirectiveThirdParty contained ajp_upstream_fail_timeout
+syn keyword ngxDirectiveThirdParty contained ajp_upstream_max_fails
+
+" AWS proxy
+" https://github.com/anomalizer/ngx_aws_auth
+syn keyword ngxDirectiveThirdParty contained aws_access_key
+syn keyword ngxDirectiveThirdParty contained aws_endpoint
+syn keyword ngxDirectiveThirdParty contained aws_key_scope
+syn keyword ngxDirectiveThirdParty contained aws_s3_bucket
+syn keyword ngxDirectiveThirdParty contained aws_sign
+syn keyword ngxDirectiveThirdParty contained aws_signing_key
+
+" embedding Clojure or Java or Groovy programs
+" https://github.com/nginx-clojure/nginx-clojure
+syn keyword ngxDirectiveThirdParty contained access_handler_code
+syn keyword ngxDirectiveThirdParty contained access_handler_name
+syn keyword ngxDirectiveThirdParty contained access_handler_property
+syn keyword ngxDirectiveThirdParty contained access_handler_type
+syn keyword ngxDirectiveThirdParty contained always_read_body
+syn keyword ngxDirectiveThirdParty contained auto_upgrade_ws
+syn keyword ngxDirectiveThirdParty contained body_filter_code
+syn keyword ngxDirectiveThirdParty contained body_filter_name
+syn keyword ngxDirectiveThirdParty contained body_filter_property
+syn keyword ngxDirectiveThirdParty contained body_filter_type
+syn keyword ngxDirectiveThirdParty contained content_handler_code
+syn keyword ngxDirectiveThirdParty contained content_handler_name
+syn keyword ngxDirectiveThirdParty contained content_handler_property
+syn keyword ngxDirectiveThirdParty contained content_handler_type
+syn keyword ngxDirectiveThirdParty contained handler_code
+syn keyword ngxDirectiveThirdParty contained handler_name
+syn keyword ngxDirectiveThirdParty contained handler_type
+syn keyword ngxDirectiveThirdParty contained handlers_lazy_init
+syn keyword ngxDirectiveThirdParty contained header_filter_code
+syn keyword ngxDirectiveThirdParty contained header_filter_name
+syn keyword ngxDirectiveThirdParty contained header_filter_property
+syn keyword ngxDirectiveThirdParty contained header_filter_type
+syn keyword ngxDirectiveThirdParty contained jvm_classpath
+syn keyword ngxDirectiveThirdParty contained jvm_classpath_check
+syn keyword ngxDirectiveThirdParty contained jvm_exit_handler_code
+syn keyword ngxDirectiveThirdParty contained jvm_exit_handler_name
+syn keyword ngxDirectiveThirdParty contained jvm_handler_type
+syn keyword ngxDirectiveThirdParty contained jvm_init_handler_code
+syn keyword ngxDirectiveThirdParty contained jvm_init_handler_name
+syn keyword ngxDirectiveThirdParty contained jvm_options
+syn keyword ngxDirectiveThirdParty contained jvm_path
+syn keyword ngxDirectiveThirdParty contained jvm_var
+syn keyword ngxDirectiveThirdParty contained jvm_workers
+syn keyword ngxDirectiveThirdParty contained max_balanced_tcp_connections
+syn keyword ngxDirectiveThirdParty contained rewrite_handler_code
+syn keyword ngxDirectiveThirdParty contained rewrite_handler_name
+syn keyword ngxDirectiveThirdParty contained rewrite_handler_property
+syn keyword ngxDirectiveThirdParty contained rewrite_handler_type
+syn keyword ngxDirectiveThirdParty contained shared_map
+syn keyword ngxDirectiveThirdParty contained write_page_size
+
+" Certificate Transparency
+" https://github.com/grahamedgecombe/nginx-ct
+syn keyword ngxDirectiveThirdParty contained ssl_ct
+syn keyword ngxDirectiveThirdParty contained ssl_ct_static_scts
+
+" ngx_echo
+" https://github.com/openresty/echo-nginx-module
+syn keyword ngxDirectiveThirdParty contained echo_abort_parent
+syn keyword ngxDirectiveThirdParty contained echo_after_body
+syn keyword ngxDirectiveThirdParty contained echo_before_body
+syn keyword ngxDirectiveThirdParty contained echo_blocking_sleep
+syn keyword ngxDirectiveThirdParty contained echo_end
+syn keyword ngxDirectiveThirdParty contained echo_exec
+syn keyword ngxDirectiveThirdParty contained echo_flush
+syn keyword ngxDirectiveThirdParty contained echo_foreach_split
+syn keyword ngxDirectiveThirdParty contained echo_location
+syn keyword ngxDirectiveThirdParty contained echo_location_async
+syn keyword ngxDirectiveThirdParty contained echo_read_request_body
+syn keyword ngxDirectiveThirdParty contained echo_request_body
+syn keyword ngxDirectiveThirdParty contained echo_reset_timer
+syn keyword ngxDirectiveThirdParty contained echo_status
+syn keyword ngxDirectiveThirdParty contained echo_subrequest
+syn keyword ngxDirectiveThirdParty contained echo_subrequest_async
+
+" FastDFS
+" https://github.com/happyfish100/fastdfs-nginx-module
+syn keyword ngxDirectiveThirdParty contained ngx_fastdfs_module
+
+" ngx_headers_more
+" https://github.com/openresty/headers-more-nginx-module
+syn keyword ngxDirectiveThirdParty contained more_clear_headers
+syn keyword ngxDirectiveThirdParty contained more_clear_input_headers
+syn keyword ngxDirectiveThirdParty contained more_set_headers
+syn keyword ngxDirectiveThirdParty contained more_set_input_headers
+
+" NGINX WebDAV missing commands support (PROPFIND & OPTIONS)
+" https://github.com/arut/nginx-dav-ext-module
+syn keyword ngxDirectiveThirdParty contained dav_ext_methods
+
+" ngx_eval
+" https://github.com/openresty/nginx-eval-module
+syn keyword ngxDirectiveThirdParty contained eval
+syn keyword ngxDirectiveThirdParty contained eval_buffer_size
+syn keyword ngxDirectiveThirdParty contained eval_escalate
+syn keyword ngxDirectiveThirdParty contained eval_override_content_type
+syn keyword ngxDirectiveThirdParty contained eval_subrequest_in_memory
+
+" Fancy Index
+" https://github.com/aperezdc/ngx-fancyindex
+syn keyword ngxDirectiveThirdParty contained fancyindex
+syn keyword ngxDirectiveThirdParty contained fancyindex_css_href
+syn keyword ngxDirectiveThirdParty contained fancyindex_default_sort
+syn keyword ngxDirectiveThirdParty contained fancyindex_directories_first
+syn keyword ngxDirectiveThirdParty contained fancyindex_exact_size
+syn keyword ngxDirectiveThirdParty contained fancyindex_footer
+syn keyword ngxDirectiveThirdParty contained fancyindex_header
+syn keyword ngxDirectiveThirdParty contained fancyindex_hide_symlinks
+syn keyword ngxDirectiveThirdParty contained fancyindex_ignore
+syn keyword ngxDirectiveThirdParty contained fancyindex_localtime
+syn keyword ngxDirectiveThirdParty contained fancyindex_name_length
+syn keyword ngxDirectiveThirdParty contained fancyindex_show_path
+syn keyword ngxDirectiveThirdParty contained fancyindex_time_format
+
+" Footer filter
+" https://github.com/alibaba/nginx-http-footer-filter
+syn keyword ngxDirectiveThirdParty contained footer
+syn keyword ngxDirectiveThirdParty contained footer_types
+
+" ngx_http_geoip2_module
+" https://github.com/leev/ngx_http_geoip2_module
+syn keyword ngxDirectiveThirdParty contained geoip2
+syn keyword ngxDirectiveThirdParty contained geoip2_proxy
+syn keyword ngxDirectiveThirdParty contained geoip2_proxy_recursive
+
+" A version of the Nginx HTTP stub status module that outputs in JSON format
+" https://github.com/nginx-modules/nginx-json-status-module
+syn keyword ngxDirectiveThirdParty contained json_status
+syn keyword ngxDirectiveThirdParty contained json_status_type
+
+" MogileFS client for nginx
+" https://github.com/vkholodkov/nginx-mogilefs-module
+syn keyword ngxDirectiveThirdParty contained mogilefs_class
+syn keyword ngxDirectiveThirdParty contained mogilefs_connect_timeout
+syn keyword ngxDirectiveThirdParty contained mogilefs_domain
+syn keyword ngxDirectiveThirdParty contained mogilefs_methods
+syn keyword ngxDirectiveThirdParty contained mogilefs_noverify
+syn keyword ngxDirectiveThirdParty contained mogilefs_pass
+syn keyword ngxDirectiveThirdParty contained mogilefs_read_timeout
+syn keyword ngxDirectiveThirdParty contained mogilefs_send_timeout
+syn keyword ngxDirectiveThirdParty contained mogilefs_tracker
+
+" Ancient nginx plugin; probably not useful to anyone
+" https://github.com/kr/nginx-notice
+syn keyword ngxDirectiveThirdParty contained notice
+syn keyword ngxDirectiveThirdParty contained notice_type
+
+" nchan
+" https://github.com/slact/nchan
+syn keyword ngxDirectiveThirdParty contained nchan_access_control_allow_origin
+syn keyword ngxDirectiveThirdParty contained nchan_authorize_request
+syn keyword ngxDirectiveThirdParty contained nchan_channel_event_string
+syn keyword ngxDirectiveThirdParty contained nchan_channel_events_channel_id
+syn keyword ngxDirectiveThirdParty contained nchan_channel_group
+syn keyword ngxDirectiveThirdParty contained nchan_channel_group_accounting
+syn keyword ngxDirectiveThirdParty contained nchan_channel_id
+syn keyword ngxDirectiveThirdParty contained nchan_channel_id_split_delimiter
+syn keyword ngxDirectiveThirdParty contained nchan_channel_timeout
+syn keyword ngxDirectiveThirdParty contained nchan_deflate_message_for_websocket
+syn keyword ngxDirectiveThirdParty contained nchan_eventsource_event
+syn keyword ngxDirectiveThirdParty contained nchan_group_location
+syn keyword ngxDirectiveThirdParty contained nchan_group_max_channels
+syn keyword ngxDirectiveThirdParty contained nchan_group_max_messages
+syn keyword ngxDirectiveThirdParty contained nchan_group_max_messages_disk
+syn keyword ngxDirectiveThirdParty contained nchan_group_max_messages_memory
+syn keyword ngxDirectiveThirdParty contained nchan_group_max_subscribers
+syn keyword ngxDirectiveThirdParty contained nchan_longpoll_multipart_response
+syn keyword ngxDirectiveThirdParty contained nchan_max_channel_id_length
+syn keyword ngxDirectiveThirdParty contained nchan_max_channel_subscribers
+syn keyword ngxDirectiveThirdParty contained nchan_max_reserved_memory
+syn keyword ngxDirectiveThirdParty contained nchan_message_buffer_length
+syn keyword ngxDirectiveThirdParty contained nchan_message_max_buffer_length
+syn keyword ngxDirectiveThirdParty contained nchan_message_temp_path
+syn keyword ngxDirectiveThirdParty contained nchan_message_timeout
+syn keyword ngxDirectiveThirdParty contained nchan_permessage_deflate_compression_level
+syn keyword ngxDirectiveThirdParty contained nchan_permessage_deflate_compression_memlevel
+syn keyword ngxDirectiveThirdParty contained nchan_permessage_deflate_compression_strategy
+syn keyword ngxDirectiveThirdParty contained nchan_permessage_deflate_compression_window
+syn keyword ngxDirectiveThirdParty contained nchan_pub_channel_id
+syn keyword ngxDirectiveThirdParty contained nchan_publisher
+syn keyword ngxDirectiveThirdParty contained nchan_publisher_channel_id
+syn keyword ngxDirectiveThirdParty contained nchan_publisher_location
+syn keyword ngxDirectiveThirdParty contained nchan_publisher_upstream_request
+syn keyword ngxDirectiveThirdParty contained nchan_pubsub
+syn keyword ngxDirectiveThirdParty contained nchan_pubsub_channel_id
+syn keyword ngxDirectiveThirdParty contained nchan_pubsub_location
+syn keyword ngxDirectiveThirdParty contained nchan_redis_fakesub_timer_interval
+syn keyword ngxDirectiveThirdParty contained nchan_redis_idle_channel_cache_timeout
+syn keyword ngxDirectiveThirdParty contained nchan_redis_namespace
+syn keyword ngxDirectiveThirdParty contained nchan_redis_pass
+syn keyword ngxDirectiveThirdParty contained nchan_redis_pass_inheritable
+syn keyword ngxDirectiveThirdParty contained nchan_redis_ping_interval
+syn keyword ngxDirectiveThirdParty contained nchan_redis_publish_msgpacked_max_size
+syn keyword ngxDirectiveThirdParty contained nchan_redis_server
+syn keyword ngxDirectiveThirdParty contained nchan_redis_storage_mode
+syn keyword ngxDirectiveThirdParty contained nchan_redis_url
+syn keyword ngxDirectiveThirdParty contained nchan_redis_wait_after_connecting
+syn keyword ngxDirectiveThirdParty contained nchan_shared_memory_size
+syn keyword ngxDirectiveThirdParty contained nchan_storage_engine
+syn keyword ngxDirectiveThirdParty contained nchan_store_messages
+syn keyword ngxDirectiveThirdParty contained nchan_stub_status
+syn keyword ngxDirectiveThirdParty contained nchan_sub_channel_id
+syn keyword ngxDirectiveThirdParty contained nchan_subscribe_existing_channels_only
+syn keyword ngxDirectiveThirdParty contained nchan_subscribe_request
+syn keyword ngxDirectiveThirdParty contained nchan_subscriber
+syn keyword ngxDirectiveThirdParty contained nchan_subscriber_channel_id
+syn keyword ngxDirectiveThirdParty contained nchan_subscriber_compound_etag_message_id
+syn keyword ngxDirectiveThirdParty contained nchan_subscriber_first_message
+syn keyword ngxDirectiveThirdParty contained nchan_subscriber_http_raw_stream_separator
+syn keyword ngxDirectiveThirdParty contained nchan_subscriber_last_message_id
+syn keyword ngxDirectiveThirdParty contained nchan_subscriber_location
+syn keyword ngxDirectiveThirdParty contained nchan_subscriber_message_id_custom_etag_header
+syn keyword ngxDirectiveThirdParty contained nchan_subscriber_timeout
+syn keyword ngxDirectiveThirdParty contained nchan_unsubscribe_request
+syn keyword ngxDirectiveThirdParty contained nchan_use_redis
+syn keyword ngxDirectiveThirdParty contained nchan_websocket_client_heartbeat
+syn keyword ngxDirectiveThirdParty contained nchan_websocket_ping_interval
+syn keyword ngxDirectiveThirdParty contained push_authorized_channels_only
+syn keyword ngxDirectiveThirdParty contained push_channel_group
+syn keyword ngxDirectiveThirdParty contained push_channel_timeout
+syn keyword ngxDirectiveThirdParty contained push_max_channel_id_length
+syn keyword ngxDirectiveThirdParty contained push_max_channel_subscribers
+syn keyword ngxDirectiveThirdParty contained push_max_message_buffer_length
+syn keyword ngxDirectiveThirdParty contained push_max_reserved_memory
+syn keyword ngxDirectiveThirdParty contained push_message_buffer_length
+syn keyword ngxDirectiveThirdParty contained push_message_timeout
+syn keyword ngxDirectiveThirdParty contained push_min_message_buffer_length
+syn keyword ngxDirectiveThirdParty contained push_publisher
+syn keyword ngxDirectiveThirdParty contained push_store_messages
+syn keyword ngxDirectiveThirdParty contained push_subscriber
+syn keyword ngxDirectiveThirdParty contained push_subscriber_concurrency
+syn keyword ngxDirectiveThirdParty contained push_subscriber_timeout
+
+" Push Stream
+" https://github.com/wandenberg/nginx-push-stream-module
+syn keyword ngxDirectiveThirdParty contained push_stream_allow_connections_to_events_channel
+syn keyword ngxDirectiveThirdParty contained push_stream_allowed_origins
+syn keyword ngxDirectiveThirdParty contained push_stream_authorized_channels_only
+syn keyword ngxDirectiveThirdParty contained push_stream_channel_deleted_message_text
+syn keyword ngxDirectiveThirdParty contained push_stream_channel_inactivity_time
+syn keyword ngxDirectiveThirdParty contained push_stream_channel_info_on_publish
+syn keyword ngxDirectiveThirdParty contained push_stream_channels_path
+syn keyword ngxDirectiveThirdParty contained push_stream_channels_statistics
+syn keyword ngxDirectiveThirdParty contained push_stream_events_channel_id
+syn keyword ngxDirectiveThirdParty contained push_stream_footer_template
+syn keyword ngxDirectiveThirdParty contained push_stream_header_template
+syn keyword ngxDirectiveThirdParty contained push_stream_header_template_file
+syn keyword ngxDirectiveThirdParty contained push_stream_last_event_id
+syn keyword ngxDirectiveThirdParty contained push_stream_last_received_message_tag
+syn keyword ngxDirectiveThirdParty contained push_stream_last_received_message_time
+syn keyword ngxDirectiveThirdParty contained push_stream_longpolling_connection_ttl
+syn keyword ngxDirectiveThirdParty contained push_stream_max_channel_id_length
+syn keyword ngxDirectiveThirdParty contained push_stream_max_messages_stored_per_channel
+syn keyword ngxDirectiveThirdParty contained push_stream_max_number_of_channels
+syn keyword ngxDirectiveThirdParty contained push_stream_max_number_of_wildcard_channels
+syn keyword ngxDirectiveThirdParty contained push_stream_max_subscribers_per_channel
+syn keyword ngxDirectiveThirdParty contained push_stream_message_template
+syn keyword ngxDirectiveThirdParty contained push_stream_message_ttl
+syn keyword ngxDirectiveThirdParty contained push_stream_padding_by_user_agent
+syn keyword ngxDirectiveThirdParty contained push_stream_ping_message_interval
+syn keyword ngxDirectiveThirdParty contained push_stream_ping_message_text
+syn keyword ngxDirectiveThirdParty contained push_stream_publisher
+syn keyword ngxDirectiveThirdParty contained push_stream_shared_memory_size
+syn keyword ngxDirectiveThirdParty contained push_stream_store_messages
+syn keyword ngxDirectiveThirdParty contained push_stream_subscriber
+syn keyword ngxDirectiveThirdParty contained push_stream_subscriber_connection_ttl
+syn keyword ngxDirectiveThirdParty contained push_stream_timeout_with_body
+syn keyword ngxDirectiveThirdParty contained push_stream_user_agent
+syn keyword ngxDirectiveThirdParty contained push_stream_websocket_allow_publish
+syn keyword ngxDirectiveThirdParty contained push_stream_wildcard_channel_max_qtd
+syn keyword ngxDirectiveThirdParty contained push_stream_wildcard_channel_prefix
+
+" redis module
+" https://www.nginx.com/resources/wiki/modules/redis/
+syn keyword ngxDirectiveThirdParty contained redis_bind
+syn keyword ngxDirectiveThirdParty contained redis_buffer_size
+syn keyword ngxDirectiveThirdParty contained redis_connect_timeout
+syn keyword ngxDirectiveThirdParty contained redis_gzip_flag
+syn keyword ngxDirectiveThirdParty contained redis_next_upstream
+syn keyword ngxDirectiveThirdParty contained redis_pass
+syn keyword ngxDirectiveThirdParty contained redis_read_timeout
+syn keyword ngxDirectiveThirdParty contained redis_send_timeout
+
+" ngx_http_response
+" http://catap.ru/downloads/nginx/
+syn keyword ngxDirectiveThirdParty contained response
+syn keyword ngxDirectiveThirdParty contained response_type
+
+" nginx_substitutions_filter
+" https://github.com/yaoweibin/ngx_http_substitutions_filter_module
+syn keyword ngxDirectiveThirdParty contained subs_buffers
+syn keyword ngxDirectiveThirdParty contained subs_filter
+syn keyword ngxDirectiveThirdParty contained subs_filter_bypass
+syn keyword ngxDirectiveThirdParty contained subs_filter_types
+syn keyword ngxDirectiveThirdParty contained subs_line_buffer_size
+
+" Tarantool nginx upstream module
+" https://github.com/tarantool/nginx_upstream_module
+syn keyword ngxDirectiveThirdParty contained tnt_allowed_indexes
+syn keyword ngxDirectiveThirdParty contained tnt_allowed_spaces
+syn keyword ngxDirectiveThirdParty contained tnt_buffer_size
+syn keyword ngxDirectiveThirdParty contained tnt_connect_timeout
+syn keyword ngxDirectiveThirdParty contained tnt_delete
+syn keyword ngxDirectiveThirdParty contained tnt_http_methods
+syn keyword ngxDirectiveThirdParty contained tnt_http_rest_methods
+syn keyword ngxDirectiveThirdParty contained tnt_in_multiplier
+syn keyword ngxDirectiveThirdParty contained tnt_insert
+syn keyword ngxDirectiveThirdParty contained tnt_method
+syn keyword ngxDirectiveThirdParty contained tnt_multireturn_skip_count
+syn keyword ngxDirectiveThirdParty contained tnt_next_upstream
+syn keyword ngxDirectiveThirdParty contained tnt_next_upstream_timeout
+syn keyword ngxDirectiveThirdParty contained tnt_next_upstream_tries
+syn keyword ngxDirectiveThirdParty contained tnt_out_multiplier
+syn keyword ngxDirectiveThirdParty contained tnt_pass
+syn keyword ngxDirectiveThirdParty contained tnt_pass_http_request
+syn keyword ngxDirectiveThirdParty contained tnt_pass_http_request_buffer_size
+syn keyword ngxDirectiveThirdParty contained tnt_pure_result
+syn keyword ngxDirectiveThirdParty contained tnt_read_timeout
+syn keyword ngxDirectiveThirdParty contained tnt_replace
+syn keyword ngxDirectiveThirdParty contained tnt_select
+syn keyword ngxDirectiveThirdParty contained tnt_select_limit_max
+syn keyword ngxDirectiveThirdParty contained tnt_send_timeout
+syn keyword ngxDirectiveThirdParty contained tnt_set_header
+syn keyword ngxDirectiveThirdParty contained tnt_update
+syn keyword ngxDirectiveThirdParty contained tnt_upsert
+
+" A module for nginx web server for handling file uploads using multipart/form-data encoding (RFC 1867)
+" https://github.com/Austinb/nginx-upload-module
+syn keyword ngxDirectiveThirdParty contained upload_aggregate_form_field
+syn keyword ngxDirectiveThirdParty contained upload_archive_elm
+syn keyword ngxDirectiveThirdParty contained upload_archive_elm_separator
+syn keyword ngxDirectiveThirdParty contained upload_archive_path
+syn keyword ngxDirectiveThirdParty contained upload_archive_path_separator
+syn keyword ngxDirectiveThirdParty contained upload_buffer_size
+syn keyword ngxDirectiveThirdParty contained upload_cleanup
+syn keyword ngxDirectiveThirdParty contained upload_content_type
+syn keyword ngxDirectiveThirdParty contained upload_discard
+syn keyword ngxDirectiveThirdParty contained upload_field_name
+syn keyword ngxDirectiveThirdParty contained upload_file_crc32
+syn keyword ngxDirectiveThirdParty contained upload_file_md5
+syn keyword ngxDirectiveThirdParty contained upload_file_md5_uc
+syn keyword ngxDirectiveThirdParty contained upload_file_name
+syn keyword ngxDirectiveThirdParty contained upload_file_sha1
+syn keyword ngxDirectiveThirdParty contained upload_file_sha1_uc
+syn keyword ngxDirectiveThirdParty contained upload_file_size
+syn keyword ngxDirectiveThirdParty contained upload_filter
+syn keyword ngxDirectiveThirdParty contained upload_max_file_size
+syn keyword ngxDirectiveThirdParty contained upload_max_output_body_len
+syn keyword ngxDirectiveThirdParty contained upload_max_part_header_len
+syn keyword ngxDirectiveThirdParty contained upload_pass
+syn keyword ngxDirectiveThirdParty contained upload_pass_args
+syn keyword ngxDirectiveThirdParty contained upload_pass_form_field
+syn keyword ngxDirectiveThirdParty contained upload_set_form_field
+syn keyword ngxDirectiveThirdParty contained upload_store
+syn keyword ngxDirectiveThirdParty contained upload_store_access
+syn keyword ngxDirectiveThirdParty contained upload_tmp_path
+syn keyword ngxDirectiveThirdParty contained upload_unzip
+syn keyword ngxDirectiveThirdParty contained upload_unzip_buffers
+syn keyword ngxDirectiveThirdParty contained upload_unzip_hash
+syn keyword ngxDirectiveThirdParty contained upload_unzip_max_file_name_len
+syn keyword ngxDirectiveThirdParty contained upload_unzip_window
+syn keyword ngxDirectiveThirdParty contained upload_void_content_type
+
+" nginx-upload-progress-module
+" https://github.com/masterzen/nginx-upload-progress-module
+syn keyword ngxDirectiveThirdParty contained report_uploads
+syn keyword ngxDirectiveThirdParty contained track_uploads
+syn keyword ngxDirectiveThirdParty contained upload_progress
+syn keyword ngxDirectiveThirdParty contained upload_progress_content_type
+syn keyword ngxDirectiveThirdParty contained upload_progress_header
+syn keyword ngxDirectiveThirdParty contained upload_progress_java_output
+syn keyword ngxDirectiveThirdParty contained upload_progress_json_output
+syn keyword ngxDirectiveThirdParty contained upload_progress_jsonp_output
+syn keyword ngxDirectiveThirdParty contained upload_progress_jsonp_parameter
+syn keyword ngxDirectiveThirdParty contained upload_progress_template
+
+" Health checks upstreams for nginx
+" https://github.com/yaoweibin/nginx_upstream_check_module
+syn keyword ngxDirectiveThirdParty contained check
+syn keyword ngxDirectiveThirdParty contained check_fastcgi_param
+syn keyword ngxDirectiveThirdParty contained check_http_expect_alive
+syn keyword ngxDirectiveThirdParty contained check_http_send
+syn keyword ngxDirectiveThirdParty contained check_keepalive_requests
+syn keyword ngxDirectiveThirdParty contained check_shm_size
+syn keyword ngxDirectiveThirdParty contained check_status
+
+" The fair load balancer module for nginx
+" https://github.com/cryptofuture/nginx-upstream-fair
+syn keyword ngxDirectiveThirdParty contained fair
+syn keyword ngxDirectiveThirdParty contained upstream_fair_shm_size
+
+" Nginx Video Thumb Extractor Module
+" https://github.com/wandenberg/nginx-video-thumbextractor-module
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_image_height
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_image_width
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_jpeg_baseline
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_jpeg_dpi
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_jpeg_optimize
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_jpeg_progressive_mode
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_jpeg_quality
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_jpeg_smooth
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_next_time
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_only_keyframe
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_processes_per_worker
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_threads
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_tile_color
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_tile_cols
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_tile_margin
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_tile_max_cols
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_tile_max_rows
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_tile_padding
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_tile_rows
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_tile_sample_interval
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_video_filename
+syn keyword ngxDirectiveThirdParty contained video_thumbextractor_video_second
+
+" drizzle-nginx-module - Upstream module for talking to MySQL and Drizzle directly
+" https://github.com/openresty/drizzle-nginx-module
+syn keyword ngxDirectiveThirdParty contained drizzle_buffer_size
+syn keyword ngxDirectiveThirdParty contained drizzle_connect_timeout
+syn keyword ngxDirectiveThirdParty contained drizzle_dbname
+syn keyword ngxDirectiveThirdParty contained drizzle_keepalive
+syn keyword ngxDirectiveThirdParty contained drizzle_module_header
+syn keyword ngxDirectiveThirdParty contained drizzle_pass
+syn keyword ngxDirectiveThirdParty contained drizzle_query
+syn keyword ngxDirectiveThirdParty contained drizzle_recv_cols_timeout
+syn keyword ngxDirectiveThirdParty contained drizzle_recv_rows_timeout
+syn keyword ngxDirectiveThirdParty contained drizzle_send_query_timeout
+syn keyword ngxDirectiveThirdParty contained drizzle_server
+syn keyword ngxDirectiveThirdParty contained drizzle_status
+
+" ngx_dynamic_upstream
+" https://github.com/cubicdaiya/ngx_dynamic_upstream
+syn keyword ngxDirectiveThirdParty contained dynamic_upstream
+
+" encrypt and decrypt nginx variable values
+" https://github.com/openresty/encrypted-session-nginx-module
+syn keyword ngxDirectiveThirdParty contained encrypted_session_expires
+syn keyword ngxDirectiveThirdParty contained encrypted_session_iv
+syn keyword ngxDirectiveThirdParty contained encrypted_session_key
+syn keyword ngxDirectiveThirdParty contained set_decrypt_session
+syn keyword ngxDirectiveThirdParty contained set_encrypt_session
+
+" serve content directly from MongoDB's GridFS
+" https://github.com/mdirolf/nginx-gridfs
+syn keyword ngxDirectiveThirdParty contained gridfs
+syn keyword ngxDirectiveThirdParty contained mongo
+
+" Adds support for arithmetic operations to NGINX config
+" https://github.com/arut/nginx-let-module
+syn keyword ngxDirectiveThirdParty contained let
+
+" ngx_http_lua_module - Embed the power of Lua into Nginx HTTP Servers
+" https://github.com/openresty/lua-nginx-module
+syn keyword ngxDirectiveThirdParty contained access_by_lua
+syn keyword ngxDirectiveThirdParty contained access_by_lua_block
+syn keyword ngxDirectiveThirdParty contained access_by_lua_file
+syn keyword ngxDirectiveThirdParty contained access_by_lua_no_postpone
+syn keyword ngxDirectiveThirdParty contained balancer_by_lua_block
+syn keyword ngxDirectiveThirdParty contained balancer_by_lua_file
+syn keyword ngxDirectiveThirdParty contained body_filter_by_lua
+syn keyword ngxDirectiveThirdParty contained body_filter_by_lua_block
+syn keyword ngxDirectiveThirdParty contained body_filter_by_lua_file
+syn keyword ngxDirectiveThirdParty contained content_by_lua
+syn keyword ngxDirectiveThirdParty contained content_by_lua_block
+syn keyword ngxDirectiveThirdParty contained content_by_lua_file
+syn keyword ngxDirectiveThirdParty contained header_filter_by_lua
+syn keyword ngxDirectiveThirdParty contained header_filter_by_lua_block
+syn keyword ngxDirectiveThirdParty contained header_filter_by_lua_file
+syn keyword ngxDirectiveThirdParty contained init_by_lua
+syn keyword ngxDirectiveThirdParty contained init_by_lua_block
+syn keyword ngxDirectiveThirdParty contained init_by_lua_file
+syn keyword ngxDirectiveThirdParty contained init_worker_by_lua
+syn keyword ngxDirectiveThirdParty contained init_worker_by_lua_block
+syn keyword ngxDirectiveThirdParty contained init_worker_by_lua_file
+syn keyword ngxDirectiveThirdParty contained log_by_lua
+syn keyword ngxDirectiveThirdParty contained log_by_lua_block
+syn keyword ngxDirectiveThirdParty contained log_by_lua_file
+syn keyword ngxDirectiveThirdParty contained lua_capture_error_log
+syn keyword ngxDirectiveThirdParty contained lua_check_client_abort
+syn keyword ngxDirectiveThirdParty contained lua_code_cache
+syn keyword ngxDirectiveThirdParty contained lua_fake_shm
+syn keyword ngxDirectiveThirdParty contained lua_http10_buffering
+syn keyword ngxDirectiveThirdParty contained lua_malloc_trim
+syn keyword ngxDirectiveThirdParty contained lua_max_pending_timers
+syn keyword ngxDirectiveThirdParty contained lua_max_running_timers
+syn keyword ngxDirectiveThirdParty contained lua_need_request_body
+syn keyword ngxDirectiveThirdParty contained lua_package_cpath
+syn keyword ngxDirectiveThirdParty contained lua_package_path
+syn keyword ngxDirectiveThirdParty contained lua_regex_cache_max_entries
+syn keyword ngxDirectiveThirdParty contained lua_regex_match_limit
+syn keyword ngxDirectiveThirdParty contained lua_shared_dict
+syn keyword ngxDirectiveThirdParty contained lua_socket_buffer_size
+syn keyword ngxDirectiveThirdParty contained lua_socket_connect_timeout
+syn keyword ngxDirectiveThirdParty contained lua_socket_keepalive_timeout
+syn keyword ngxDirectiveThirdParty contained lua_socket_log_errors
+syn keyword ngxDirectiveThirdParty contained lua_socket_pool_size
+syn keyword ngxDirectiveThirdParty contained lua_socket_read_timeout
+syn keyword ngxDirectiveThirdParty contained lua_socket_send_lowat
+syn keyword ngxDirectiveThirdParty contained lua_socket_send_timeout
+syn keyword ngxDirectiveThirdParty contained lua_ssl_ciphers
+syn keyword ngxDirectiveThirdParty contained lua_ssl_crl
+syn keyword ngxDirectiveThirdParty contained lua_ssl_protocols
+syn keyword ngxDirectiveThirdParty contained lua_ssl_trusted_certificate
+syn keyword ngxDirectiveThirdParty contained lua_ssl_verify_depth
+syn keyword ngxDirectiveThirdParty contained lua_transform_underscores_in_response_headers
+syn keyword ngxDirectiveThirdParty contained lua_use_default_type
+syn keyword ngxDirectiveThirdParty contained rewrite_by_lua
+syn keyword ngxDirectiveThirdParty contained rewrite_by_lua_block
+syn keyword ngxDirectiveThirdParty contained rewrite_by_lua_file
+syn keyword ngxDirectiveThirdParty contained rewrite_by_lua_no_postpone
+syn keyword ngxDirectiveThirdParty contained set_by_lua
+syn keyword ngxDirectiveThirdParty contained set_by_lua_block
+syn keyword ngxDirectiveThirdParty contained set_by_lua_file
+syn keyword ngxDirectiveThirdParty contained ssl_certificate_by_lua_block
+syn keyword ngxDirectiveThirdParty contained ssl_certificate_by_lua_file
+syn keyword ngxDirectiveThirdParty contained ssl_session_fetch_by_lua_block
+syn keyword ngxDirectiveThirdParty contained ssl_session_fetch_by_lua_file
+syn keyword ngxDirectiveThirdParty contained ssl_session_store_by_lua_block
+syn keyword ngxDirectiveThirdParty contained ssl_session_store_by_lua_file
+
+" ngx_memc - An extended version of the standard memcached module
+" https://github.com/openresty/memc-nginx-module
+syn keyword ngxDirectiveThirdParty contained memc_buffer_size
+syn keyword ngxDirectiveThirdParty contained memc_cmds_allowed
+syn keyword ngxDirectiveThirdParty contained memc_connect_timeout
+syn keyword ngxDirectiveThirdParty contained memc_flags_to_last_modified
+syn keyword ngxDirectiveThirdParty contained memc_ignore_client_abort
+syn keyword ngxDirectiveThirdParty contained memc_next_upstream
+syn keyword ngxDirectiveThirdParty contained memc_pass
+syn keyword ngxDirectiveThirdParty contained memc_read_timeout
+syn keyword ngxDirectiveThirdParty contained memc_send_timeout
+syn keyword ngxDirectiveThirdParty contained memc_upstream_fail_timeout
+syn keyword ngxDirectiveThirdParty contained memc_upstream_max_fails
+
+" ModSecurity web application firewall
+" https://github.com/SpiderLabs/ModSecurity/tree/master
+syn keyword ngxDirectiveThirdParty contained ModSecurityConfig
+syn keyword ngxDirectiveThirdParty contained ModSecurityEnabled
+syn keyword ngxDirectiveThirdParty contained pool_context_hash_size
+
+" NAXSI is an open-source, high performance, low rules maintenance WAF for NGINX
+" https://github.com/nbs-system/naxsi
+syn keyword ngxDirectiveThirdParty contained BasicRule
+syn keyword ngxDirectiveThirdParty contained CheckRule
+syn keyword ngxDirectiveThirdParty contained DeniedUrl
+syn keyword ngxDirectiveThirdParty contained LearningMode
+syn keyword ngxDirectiveThirdParty contained LibInjectionSql
+syn keyword ngxDirectiveThirdParty contained LibInjectionXss
+syn keyword ngxDirectiveThirdParty contained MainRule
+syn keyword ngxDirectiveThirdParty contained SecRulesDisabled
+syn keyword ngxDirectiveThirdParty contained SecRulesEnabled
+syn keyword ngxDirectiveThirdParty contained basic_rule
+syn keyword ngxDirectiveThirdParty contained check_rule
+syn keyword ngxDirectiveThirdParty contained denied_url
+syn keyword ngxDirectiveThirdParty contained learning_mode
+syn keyword ngxDirectiveThirdParty contained libinjection_sql
+syn keyword ngxDirectiveThirdParty contained libinjection_xss
+syn keyword ngxDirectiveThirdParty contained main_rule
+syn keyword ngxDirectiveThirdParty contained rules_disabled
+syn keyword ngxDirectiveThirdParty contained rules_enabled
+
+" Phusion Passenger
+" https://www.phusionpassenger.com/library/config/nginx/reference/
+syn keyword ngxDirectiveThirdParty contained passenger_abort_on_startup_error
+syn keyword ngxDirectiveThirdParty contained passenger_abort_websockets_on_process_shutdown
+syn keyword ngxDirectiveThirdParty contained passenger_app_env
+syn keyword ngxDirectiveThirdParty contained passenger_app_file_descriptor_ulimit
+syn keyword ngxDirectiveThirdParty contained passenger_app_group_name
+syn keyword ngxDirectiveThirdParty contained passenger_app_rights
+syn keyword ngxDirectiveThirdParty contained passenger_app_root
+syn keyword ngxDirectiveThirdParty contained passenger_app_type
+syn keyword ngxDirectiveThirdParty contained passenger_base_uri
+syn keyword ngxDirectiveThirdParty contained passenger_buffer_response
+syn keyword ngxDirectiveThirdParty contained passenger_buffer_size
+syn keyword ngxDirectiveThirdParty contained passenger_buffers
+syn keyword ngxDirectiveThirdParty contained passenger_busy_buffers_size
+syn keyword ngxDirectiveThirdParty contained passenger_concurrency_model
+syn keyword ngxDirectiveThirdParty contained passenger_core_file_descriptor_ulimit
+syn keyword ngxDirectiveThirdParty contained passenger_ctl
+syn keyword ngxDirectiveThirdParty contained passenger_data_buffer_dir
+syn keyword ngxDirectiveThirdParty contained passenger_debugger
+syn keyword ngxDirectiveThirdParty contained passenger_default_group
+syn keyword ngxDirectiveThirdParty contained passenger_default_user
+syn keyword ngxDirectiveThirdParty contained passenger_disable_security_update_check
+syn keyword ngxDirectiveThirdParty contained passenger_document_root
+syn keyword ngxDirectiveThirdParty contained passenger_enabled
+syn keyword ngxDirectiveThirdParty contained passenger_env_var
+syn keyword ngxDirectiveThirdParty contained passenger_file_descriptor_log_file
+syn keyword ngxDirectiveThirdParty contained passenger_fly_with
+syn keyword ngxDirectiveThirdParty contained passenger_force_max_concurrent_requests_per_process
+syn keyword ngxDirectiveThirdParty contained passenger_friendly_error_pages
+syn keyword ngxDirectiveThirdParty contained passenger_group
+syn keyword ngxDirectiveThirdParty contained passenger_headers_hash_bucket_size
+syn keyword ngxDirectiveThirdParty contained passenger_headers_hash_max_size
+syn keyword ngxDirectiveThirdParty contained passenger_ignore_client_abort
+syn keyword ngxDirectiveThirdParty contained passenger_ignore_headers
+syn keyword ngxDirectiveThirdParty contained passenger_instance_registry_dir
+syn keyword ngxDirectiveThirdParty contained passenger_intercept_errors
+syn keyword ngxDirectiveThirdParty contained passenger_load_shell_envvars
+syn keyword ngxDirectiveThirdParty contained passenger_log_file
+syn keyword ngxDirectiveThirdParty contained passenger_log_level
+syn keyword ngxDirectiveThirdParty contained passenger_max_instances
+syn keyword ngxDirectiveThirdParty contained passenger_max_instances_per_app
+syn keyword ngxDirectiveThirdParty contained passenger_max_pool_size
+syn keyword ngxDirectiveThirdParty contained passenger_max_preloader_idle_time
+syn keyword ngxDirectiveThirdParty contained passenger_max_request_queue_size
+syn keyword ngxDirectiveThirdParty contained passenger_max_request_queue_time
+syn keyword ngxDirectiveThirdParty contained passenger_max_request_time
+syn keyword ngxDirectiveThirdParty contained passenger_max_requests
+syn keyword ngxDirectiveThirdParty contained passenger_memory_limit
+syn keyword ngxDirectiveThirdParty contained passenger_meteor_app_settings
+syn keyword ngxDirectiveThirdParty contained passenger_min_instances
+syn keyword ngxDirectiveThirdParty contained passenger_nodejs
+syn keyword ngxDirectiveThirdParty contained passenger_pass_header
+syn keyword ngxDirectiveThirdParty contained passenger_pool_idle_time
+syn keyword ngxDirectiveThirdParty contained passenger_pre_start
+syn keyword ngxDirectiveThirdParty contained passenger_python
+syn keyword ngxDirectiveThirdParty contained passenger_read_timeout
+syn keyword ngxDirectiveThirdParty contained passenger_request_queue_overflow_status_code
+syn keyword ngxDirectiveThirdParty contained passenger_resist_deployment_errors
+syn keyword ngxDirectiveThirdParty contained passenger_response_buffer_high_watermark
+syn keyword ngxDirectiveThirdParty contained passenger_restart_dir
+syn keyword ngxDirectiveThirdParty contained passenger_rolling_restarts
+syn keyword ngxDirectiveThirdParty contained passenger_root
+syn keyword ngxDirectiveThirdParty contained passenger_ruby
+syn keyword ngxDirectiveThirdParty contained passenger_security_update_check_proxy
+syn keyword ngxDirectiveThirdParty contained passenger_set_header
+syn keyword ngxDirectiveThirdParty contained passenger_show_version_in_header
+syn keyword ngxDirectiveThirdParty contained passenger_socket_backlog
+syn keyword ngxDirectiveThirdParty contained passenger_spawn_method
+syn keyword ngxDirectiveThirdParty contained passenger_start_timeout
+syn keyword ngxDirectiveThirdParty contained passenger_startup_file
+syn keyword ngxDirectiveThirdParty contained passenger_stat_throttle_rate
+syn keyword ngxDirectiveThirdParty contained passenger_sticky_sessions
+syn keyword ngxDirectiveThirdParty contained passenger_sticky_sessions_cookie_name
+syn keyword ngxDirectiveThirdParty contained passenger_thread_count
+syn keyword ngxDirectiveThirdParty contained passenger_turbocaching
+syn keyword ngxDirectiveThirdParty contained passenger_user
+syn keyword ngxDirectiveThirdParty contained passenger_user_switching
+syn keyword ngxDirectiveThirdParty contained passenger_vary_turbocache_by_cookie
+syn keyword ngxDirectiveThirdPartyDeprecated contained passenger_analytics_log_group
+syn keyword ngxDirectiveThirdPartyDeprecated contained passenger_analytics_log_user
+syn keyword ngxDirectiveThirdPartyDeprecated contained passenger_debug_log_file
+syn keyword ngxDirectiveThirdPartyDeprecated contained passenger_use_global_queue
+syn keyword ngxDirectiveThirdPartyDeprecated contained rack_env
+syn keyword ngxDirectiveThirdPartyDeprecated contained rails_app_spawner_idle_time
+syn keyword ngxDirectiveThirdPartyDeprecated contained rails_env
+syn keyword ngxDirectiveThirdPartyDeprecated contained rails_framework_spawner_idle_time
+syn keyword ngxDirectiveThirdPartyDeprecated contained rails_spawn_method
+syn keyword ngxDirectiveThirdPartyDeprecated contained union_station_filter
+syn keyword ngxDirectiveThirdPartyDeprecated contained union_station_gateway_address
+syn keyword ngxDirectiveThirdPartyDeprecated contained union_station_gateway_cert
+syn keyword ngxDirectiveThirdPartyDeprecated contained union_station_gateway_port
+syn keyword ngxDirectiveThirdPartyDeprecated contained union_station_key
+syn keyword ngxDirectiveThirdPartyDeprecated contained union_station_proxy_address
+syn keyword ngxDirectiveThirdPartyDeprecated contained union_station_support
+
+" ngx_postgres is an upstream module that allows nginx to communicate directly with PostgreSQL database
+" https://github.com/FRiCKLE/ngx_postgres
+syn keyword ngxDirectiveThirdParty contained postgres_connect_timeout
+syn keyword ngxDirectiveThirdParty contained postgres_escape
+syn keyword ngxDirectiveThirdParty contained postgres_keepalive
+syn keyword ngxDirectiveThirdParty contained postgres_output
+syn keyword ngxDirectiveThirdParty contained postgres_pass
+syn keyword ngxDirectiveThirdParty contained postgres_query
+syn keyword ngxDirectiveThirdParty contained postgres_result_timeout
+syn keyword ngxDirectiveThirdParty contained postgres_rewrite
+syn keyword ngxDirectiveThirdParty contained postgres_server
+syn keyword ngxDirectiveThirdParty contained postgres_set
+
+" ngx_rds_csv - Nginx output filter module to convert Resty-DBD-Streams (RDS) to Comma-Separated Values (CSV)
+" https://github.com/openresty/rds-csv-nginx-module
+syn keyword ngxDirectiveThirdParty contained rds_csv
+syn keyword ngxDirectiveThirdParty contained rds_csv_buffer_size
+syn keyword ngxDirectiveThirdParty contained rds_csv_content_type
+syn keyword ngxDirectiveThirdParty contained rds_csv_field_name_header
+syn keyword ngxDirectiveThirdParty contained rds_csv_field_separator
+syn keyword ngxDirectiveThirdParty contained rds_csv_row_terminator
+
+" ngx_rds_json - an output filter that formats Resty DBD Streams generated by ngx_drizzle and others to JSON
+" https://github.com/openresty/rds-json-nginx-module
+syn keyword ngxDirectiveThirdParty contained rds_json
+syn keyword ngxDirectiveThirdParty contained rds_json_buffer_size
+syn keyword ngxDirectiveThirdParty contained rds_json_content_type
+syn keyword ngxDirectiveThirdParty contained rds_json_errcode_key
+syn keyword ngxDirectiveThirdParty contained rds_json_errstr_key
+syn keyword ngxDirectiveThirdParty contained rds_json_format
+syn keyword ngxDirectiveThirdParty contained rds_json_ret
+syn keyword ngxDirectiveThirdParty contained rds_json_root
+syn keyword ngxDirectiveThirdParty contained rds_json_success_property
+syn keyword ngxDirectiveThirdParty contained rds_json_user_property
+
+" ngx_redis2 - Nginx upstream module for the Redis 2.0 protocol
+" https://github.com/openresty/redis2-nginx-module
+syn keyword ngxDirectiveThirdParty contained redis2_bind
+syn keyword ngxDirectiveThirdParty contained redis2_buffer_size
+syn keyword ngxDirectiveThirdParty contained redis2_connect_timeout
+syn keyword ngxDirectiveThirdParty contained redis2_literal_raw_query
+syn keyword ngxDirectiveThirdParty contained redis2_next_upstream
+syn keyword ngxDirectiveThirdParty contained redis2_pass
+syn keyword ngxDirectiveThirdParty contained redis2_query
+syn keyword ngxDirectiveThirdParty contained redis2_raw_queries
+syn keyword ngxDirectiveThirdParty contained redis2_raw_query
+syn keyword ngxDirectiveThirdParty contained redis2_read_timeout
+syn keyword ngxDirectiveThirdParty contained redis2_send_timeout
+
+" NGINX-based Media Streaming Server
+" https://github.com/arut/nginx-rtmp-module
+syn keyword ngxDirectiveThirdParty contained ack_window
+syn keyword ngxDirectiveThirdParty contained application
+syn keyword ngxDirectiveThirdParty contained buffer
+syn keyword ngxDirectiveThirdParty contained buflen
+syn keyword ngxDirectiveThirdParty contained busy
+syn keyword ngxDirectiveThirdParty contained chunk_size
+syn keyword ngxDirectiveThirdParty contained dash
+syn keyword ngxDirectiveThirdParty contained dash_cleanup
+syn keyword ngxDirectiveThirdParty contained dash_fragment
+syn keyword ngxDirectiveThirdParty contained dash_nested
+syn keyword ngxDirectiveThirdParty contained dash_path
+syn keyword ngxDirectiveThirdParty contained dash_playlist_length
+syn keyword ngxDirectiveThirdParty contained drop_idle_publisher
+syn keyword ngxDirectiveThirdParty contained exec
+syn keyword ngxDirectiveThirdParty contained exec_block
+syn keyword ngxDirectiveThirdParty contained exec_kill_signal
+syn keyword ngxDirectiveThirdParty contained exec_options
+syn keyword ngxDirectiveThirdParty contained exec_play
+syn keyword ngxDirectiveThirdParty contained exec_play_done
+syn keyword ngxDirectiveThirdParty contained exec_publish
+syn keyword ngxDirectiveThirdParty contained exec_publish_done
+syn keyword ngxDirectiveThirdParty contained exec_pull
+syn keyword ngxDirectiveThirdParty contained exec_push
+syn keyword ngxDirectiveThirdParty contained exec_record_done
+syn keyword ngxDirectiveThirdParty contained exec_static
+syn keyword ngxDirectiveThirdParty contained hls_audio_buffer_size
+syn keyword ngxDirectiveThirdParty contained hls_base_url
+syn keyword ngxDirectiveThirdParty contained hls_cleanup
+syn keyword ngxDirectiveThirdParty contained hls_continuous
+syn keyword ngxDirectiveThirdParty contained hls_fragment_naming
+syn keyword ngxDirectiveThirdParty contained hls_fragment_naming_granularity
+syn keyword ngxDirectiveThirdParty contained hls_fragment_slicing
+syn keyword ngxDirectiveThirdParty contained hls_fragments_per_key
+syn keyword ngxDirectiveThirdParty contained hls_key_path
+syn keyword ngxDirectiveThirdParty contained hls_key_url
+syn keyword ngxDirectiveThirdParty contained hls_keys
+syn keyword ngxDirectiveThirdParty contained hls_max_audio_delay
+syn keyword ngxDirectiveThirdParty contained hls_max_fragment
+syn keyword ngxDirectiveThirdParty contained hls_muxdelay
+syn keyword ngxDirectiveThirdParty contained hls_nested
+syn keyword ngxDirectiveThirdParty contained hls_path
+syn keyword ngxDirectiveThirdParty contained hls_playlist_length
+syn keyword ngxDirectiveThirdParty contained hls_sync
+syn keyword ngxDirectiveThirdParty contained hls_type
+syn keyword ngxDirectiveThirdParty contained hls_variant
+syn keyword ngxDirectiveThirdParty contained idle_streams
+syn keyword ngxDirectiveThirdParty contained interleave
+syn keyword ngxDirectiveThirdParty contained live
+syn keyword ngxDirectiveThirdParty contained max_connections
+syn keyword ngxDirectiveThirdParty contained max_message
+syn keyword ngxDirectiveThirdParty contained max_streams
+syn keyword ngxDirectiveThirdParty contained meta
+syn keyword ngxDirectiveThirdParty contained netcall_buffer
+syn keyword ngxDirectiveThirdParty contained netcall_timeout
+syn keyword ngxDirectiveThirdParty contained notify_method
+syn keyword ngxDirectiveThirdParty contained notify_relay_redirect
+syn keyword ngxDirectiveThirdParty contained notify_update_strict
+syn keyword ngxDirectiveThirdParty contained notify_update_timeout
+syn keyword ngxDirectiveThirdParty contained on_connect
+syn keyword ngxDirectiveThirdParty contained on_disconnect
+syn keyword ngxDirectiveThirdParty contained on_done
+syn keyword ngxDirectiveThirdParty contained on_play
+syn keyword ngxDirectiveThirdParty contained on_play_done
+syn keyword ngxDirectiveThirdParty contained on_publish
+syn keyword ngxDirectiveThirdParty contained on_publish_done
+syn keyword ngxDirectiveThirdParty contained on_record_done
+syn keyword ngxDirectiveThirdParty contained on_update
+syn keyword ngxDirectiveThirdParty contained out_cork
+syn keyword ngxDirectiveThirdParty contained out_queue
+syn keyword ngxDirectiveThirdParty contained ping
+syn keyword ngxDirectiveThirdParty contained ping_timeout
+syn keyword ngxDirectiveThirdParty contained play
+syn keyword ngxDirectiveThirdParty contained play_local_path
+syn keyword ngxDirectiveThirdParty contained play_restart
+syn keyword ngxDirectiveThirdParty contained play_temp_path
+syn keyword ngxDirectiveThirdParty contained play_time_fix
+syn keyword ngxDirectiveThirdParty contained publish_notify
+syn keyword ngxDirectiveThirdParty contained publish_time_fix
+syn keyword ngxDirectiveThirdParty contained pull
+syn keyword ngxDirectiveThirdParty contained pull_reconnect
+syn keyword ngxDirectiveThirdParty contained push
+syn keyword ngxDirectiveThirdParty contained push_reconnect
+syn keyword ngxDirectiveThirdParty contained record
+syn keyword ngxDirectiveThirdParty contained record_append
+syn keyword ngxDirectiveThirdParty contained record_interval
+syn keyword ngxDirectiveThirdParty contained record_lock
+syn keyword ngxDirectiveThirdParty contained record_max_frames
+syn keyword ngxDirectiveThirdParty contained record_max_size
+syn keyword ngxDirectiveThirdParty contained record_notify
+syn keyword ngxDirectiveThirdParty contained record_path
+syn keyword ngxDirectiveThirdParty contained record_suffix
+syn keyword ngxDirectiveThirdParty contained record_unique
+syn keyword ngxDirectiveThirdParty contained recorder
+syn keyword ngxDirectiveThirdParty contained relay_buffer
+syn keyword ngxDirectiveThirdParty contained respawn
+syn keyword ngxDirectiveThirdParty contained respawn_timeout
+syn keyword ngxDirectiveThirdParty contained rtmp
+syn keyword ngxDirectiveThirdParty contained rtmp_auto_push
+syn keyword ngxDirectiveThirdParty contained rtmp_auto_push_reconnect
+syn keyword ngxDirectiveThirdParty contained rtmp_control
+syn keyword ngxDirectiveThirdParty contained rtmp_socket_dir
+syn keyword ngxDirectiveThirdParty contained rtmp_stat
+syn keyword ngxDirectiveThirdParty contained rtmp_stat_stylesheet
+syn keyword ngxDirectiveThirdParty contained session_relay
+syn keyword ngxDirectiveThirdParty contained so_keepalive
+syn keyword ngxDirectiveThirdParty contained stream_buckets
+syn keyword ngxDirectiveThirdParty contained sync
+syn keyword ngxDirectiveThirdParty contained wait_key
+syn keyword ngxDirectiveThirdParty contained wait_video
+
+" ngx_set_misc - Various set_xxx directives added to nginx's rewrite module (md5/sha1, sql/json quoting, and many more)
+" https://github.com/openresty/set-misc-nginx-module
+syn keyword ngxDirectiveThirdParty contained set_base32_alphabet
+syn keyword ngxDirectiveThirdParty contained set_base32_padding
+syn keyword ngxDirectiveThirdParty contained set_decode_base32
+syn keyword ngxDirectiveThirdParty contained set_decode_base64
+syn keyword ngxDirectiveThirdParty contained set_decode_hex
+syn keyword ngxDirectiveThirdParty contained set_encode_base32
+syn keyword ngxDirectiveThirdParty contained set_encode_base64
+syn keyword ngxDirectiveThirdParty contained set_encode_hex
+syn keyword ngxDirectiveThirdParty contained set_escape_uri
+syn keyword ngxDirectiveThirdParty contained set_formatted_gmt_time
+syn keyword ngxDirectiveThirdParty contained set_formatted_local_time
+syn keyword ngxDirectiveThirdParty contained set_hashed_upstream
+syn keyword ngxDirectiveThirdParty contained set_hmac_sha1
+syn keyword ngxDirectiveThirdParty contained set_if_empty
+syn keyword ngxDirectiveThirdParty contained set_local_today
+syn keyword ngxDirectiveThirdParty contained set_misc_base32_padding
+syn keyword ngxDirectiveThirdParty contained set_quote_json_str
+syn keyword ngxDirectiveThirdParty contained set_quote_pgsql_str
+syn keyword ngxDirectiveThirdParty contained set_quote_sql_str
+syn keyword ngxDirectiveThirdParty contained set_random
+syn keyword ngxDirectiveThirdParty contained set_rotate
+syn keyword ngxDirectiveThirdParty contained set_secure_random_alphanum
+syn keyword ngxDirectiveThirdParty contained set_secure_random_lcalpha
+syn keyword ngxDirectiveThirdParty contained set_unescape_uri
+
+" nginx-sflow-module
+" https://github.com/sflow/nginx-sflow-module
+syn keyword ngxDirectiveThirdParty contained sflow
+
+" Shibboleth auth request module for Nginx
+" https://github.com/nginx-shib/nginx-http-shibboleth
+syn keyword ngxDirectiveThirdParty contained shib_request
+syn keyword ngxDirectiveThirdParty contained shib_request_set
+syn keyword ngxDirectiveThirdParty contained shib_request_use_headers
+
+" nginx module which adds ability to cache static files
+" https://github.com/FRiCKLE/ngx_slowfs_cache
+syn keyword ngxDirectiveThirdParty contained slowfs_big_file_size
+syn keyword ngxDirectiveThirdParty contained slowfs_cache
+syn keyword ngxDirectiveThirdParty contained slowfs_cache_key
+syn keyword ngxDirectiveThirdParty contained slowfs_cache_min_uses
+syn keyword ngxDirectiveThirdParty contained slowfs_cache_path
+syn keyword ngxDirectiveThirdParty contained slowfs_cache_purge
+syn keyword ngxDirectiveThirdParty contained slowfs_cache_valid
+syn keyword ngxDirectiveThirdParty contained slowfs_temp_path
+
+" Dynamic Image Transformation Module For nginx
+" https://github.com/cubicdaiya/ngx_small_light
+syn keyword ngxDirectiveThirdParty contained small_light
+syn keyword ngxDirectiveThirdParty contained small_light_buffer
+syn keyword ngxDirectiveThirdParty contained small_light_getparam_mode
+syn keyword ngxDirectiveThirdParty contained small_light_imlib2_temp_dir
+syn keyword ngxDirectiveThirdParty contained small_light_material_dir
+syn keyword ngxDirectiveThirdParty contained small_light_pattern_define
+syn keyword ngxDirectiveThirdParty contained small_light_radius_max
+syn keyword ngxDirectiveThirdParty contained small_light_sigma_max
+
+" ngx_srcache - Transparent subrequest-based caching layout for arbitrary nginx locations
+" https://github.com/openresty/srcache-nginx-module
+syn keyword ngxDirectiveThirdParty contained srcache_buffer
+syn keyword ngxDirectiveThirdParty contained srcache_default_expire
+syn keyword ngxDirectiveThirdParty contained srcache_fetch
+syn keyword ngxDirectiveThirdParty contained srcache_fetch_skip
+syn keyword ngxDirectiveThirdParty contained srcache_header_buffer_size
+syn keyword ngxDirectiveThirdParty contained srcache_ignore_content_encoding
+syn keyword ngxDirectiveThirdParty contained srcache_max_expire
+syn keyword ngxDirectiveThirdParty contained srcache_methods
+syn keyword ngxDirectiveThirdParty contained srcache_request_cache_control
+syn keyword ngxDirectiveThirdParty contained srcache_response_cache_control
+syn keyword ngxDirectiveThirdParty contained srcache_store
+syn keyword ngxDirectiveThirdParty contained srcache_store_hide_header
+syn keyword ngxDirectiveThirdParty contained srcache_store_max_size
+syn keyword ngxDirectiveThirdParty contained srcache_store_no_cache
+syn keyword ngxDirectiveThirdParty contained srcache_store_no_store
+syn keyword ngxDirectiveThirdParty contained srcache_store_pass_header
+syn keyword ngxDirectiveThirdParty contained srcache_store_private
+syn keyword ngxDirectiveThirdParty contained srcache_store_ranges
+syn keyword ngxDirectiveThirdParty contained srcache_store_skip
+syn keyword ngxDirectiveThirdParty contained srcache_store_statuses
+
+" NGINX-based VOD Packager
+" https://github.com/kaltura/nginx-vod-module
+syn keyword ngxDirectiveThirdParty contained vod
+syn keyword ngxDirectiveThirdParty contained vod_align_segments_to_key_frames
+syn keyword ngxDirectiveThirdParty contained vod_apply_dynamic_mapping
+syn keyword ngxDirectiveThirdParty contained vod_base_url
+syn keyword ngxDirectiveThirdParty contained vod_bootstrap_segment_durations
+syn keyword ngxDirectiveThirdParty contained vod_cache_buffer_size
+syn keyword ngxDirectiveThirdParty contained vod_clip_from_param_name
+syn keyword ngxDirectiveThirdParty contained vod_clip_to_param_name
+syn keyword ngxDirectiveThirdParty contained vod_drm_clear_lead_segment_count
+syn keyword ngxDirectiveThirdParty contained vod_drm_enabled
+syn keyword ngxDirectiveThirdParty contained vod_drm_info_cache
+syn keyword ngxDirectiveThirdParty contained vod_drm_max_info_length
+syn keyword ngxDirectiveThirdParty contained vod_drm_request_uri
+syn keyword ngxDirectiveThirdParty contained vod_drm_single_key
+syn keyword ngxDirectiveThirdParty contained vod_drm_upstream_location
+syn keyword ngxDirectiveThirdParty contained vod_dynamic_clip_map_uri
+syn keyword ngxDirectiveThirdParty contained vod_dynamic_mapping_cache
+syn keyword ngxDirectiveThirdParty contained vod_encryption_iv_seed
+syn keyword ngxDirectiveThirdParty contained vod_expires
+syn keyword ngxDirectiveThirdParty contained vod_expires_live
+syn keyword ngxDirectiveThirdParty contained vod_expires_live_time_dependent
+syn keyword ngxDirectiveThirdParty contained vod_fallback_upstream_location
+syn keyword ngxDirectiveThirdParty contained vod_force_continuous_timestamps
+syn keyword ngxDirectiveThirdParty contained vod_force_playlist_type_vod
+syn keyword ngxDirectiveThirdParty contained vod_gop_look_ahead
+syn keyword ngxDirectiveThirdParty contained vod_gop_look_behind
+syn keyword ngxDirectiveThirdParty contained vod_ignore_edit_list
+syn keyword ngxDirectiveThirdParty contained vod_initial_read_size
+syn keyword ngxDirectiveThirdParty contained vod_lang_param_name
+syn keyword ngxDirectiveThirdParty contained vod_last_modified
+syn keyword ngxDirectiveThirdParty contained vod_last_modified_types
+syn keyword ngxDirectiveThirdParty contained vod_live_mapping_cache
+syn keyword ngxDirectiveThirdParty contained vod_live_response_cache
+syn keyword ngxDirectiveThirdParty contained vod_live_window_duration
+syn keyword ngxDirectiveThirdParty contained vod_manifest_duration_policy
+syn keyword ngxDirectiveThirdParty contained vod_manifest_segment_durations_mode
+syn keyword ngxDirectiveThirdParty contained vod_mapping_cache
+syn keyword ngxDirectiveThirdParty contained vod_max_frames_size
+syn keyword ngxDirectiveThirdParty contained vod_max_mapping_response_size
+syn keyword ngxDirectiveThirdParty contained vod_max_metadata_size
+syn keyword ngxDirectiveThirdParty contained vod_max_upstream_headers_size
+syn keyword ngxDirectiveThirdParty contained vod_media_set_map_uri
+syn keyword ngxDirectiveThirdParty contained vod_media_set_override_json
+syn keyword ngxDirectiveThirdParty contained vod_metadata_cache
+syn keyword ngxDirectiveThirdParty contained vod_min_single_nalu_per_frame_segment
+syn keyword ngxDirectiveThirdParty contained vod_mode
+syn keyword ngxDirectiveThirdParty contained vod_multi_uri_suffix
+syn keyword ngxDirectiveThirdParty contained vod_notification_uri
+syn keyword ngxDirectiveThirdParty contained vod_open_file_thread_pool
+syn keyword ngxDirectiveThirdParty contained vod_output_buffer_pool
+syn keyword ngxDirectiveThirdParty contained vod_parse_hdlr_name
+syn keyword ngxDirectiveThirdParty contained vod_path_response_postfix
+syn keyword ngxDirectiveThirdParty contained vod_path_response_prefix
+syn keyword ngxDirectiveThirdParty contained vod_performance_counters
+syn keyword ngxDirectiveThirdParty contained vod_proxy_header_name
+syn keyword ngxDirectiveThirdParty contained vod_proxy_header_value
+syn keyword ngxDirectiveThirdParty contained vod_redirect_segments_url
+syn keyword ngxDirectiveThirdParty contained vod_remote_upstream_location
+syn keyword ngxDirectiveThirdParty contained vod_response_cache
+syn keyword ngxDirectiveThirdParty contained vod_secret_key
+syn keyword ngxDirectiveThirdParty contained vod_segment_count_policy
+syn keyword ngxDirectiveThirdParty contained vod_segment_duration
+syn keyword ngxDirectiveThirdParty contained vod_segments_base_url
+syn keyword ngxDirectiveThirdParty contained vod_source_clip_map_uri
+syn keyword ngxDirectiveThirdParty contained vod_speed_param_name
+syn keyword ngxDirectiveThirdParty contained vod_status
+syn keyword ngxDirectiveThirdParty contained vod_time_shift_param_name
+syn keyword ngxDirectiveThirdParty contained vod_tracks_param_name
+syn keyword ngxDirectiveThirdParty contained vod_upstream_extra_args
+syn keyword ngxDirectiveThirdParty contained vod_upstream_location
+
+" Nginx virtual host traffic status module
+" https://github.com/vozlt/nginx-module-vts
+syn keyword ngxDirectiveThirdParty contained vhost_traffic_status
+syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_average_method
+syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_bypass_limit
+syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_bypass_stats
+syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_display
+syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_display_format
+syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_display_jsonp
+syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_display_sum_key
+syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_dump
+syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_filter
+syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_filter_by_host
+syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_filter_by_set_key
+syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_filter_check_duplicate
+syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_limit
+syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_limit_check_duplicate
+syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_limit_traffic
+syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_limit_traffic_by_set_key
+syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_set_by_filter
+syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_zone
+
+" xss-nginx-module - Native cross-site scripting support in nginx
+" https://github.com/openresty/xss-nginx-module
+syn keyword ngxDirectiveThirdParty contained xss_callback_arg
+syn keyword ngxDirectiveThirdParty contained xss_check_status
+syn keyword ngxDirectiveThirdParty contained xss_get
+syn keyword ngxDirectiveThirdParty contained xss_input_types
+syn keyword ngxDirectiveThirdParty contained xss_output_type
+syn keyword ngxDirectiveThirdParty contained xss_override_status
+
+" Add support for array-typed variables to nginx config files
+" https://github.com/openresty/array-var-nginx-module
+syn keyword ngxDirectiveThirdParty contained array_join
+syn keyword ngxDirectiveThirdParty contained array_map
+syn keyword ngxDirectiveThirdParty contained array_map_op
+syn keyword ngxDirectiveThirdParty contained array_split
+
+" NGINX module for Brotli compression
+" https://github.com/eustas/ngx_brotli
+syn keyword ngxDirectiveThirdParty contained brotli
+syn keyword ngxDirectiveThirdParty contained brotli_buffers
+syn keyword ngxDirectiveThirdParty contained brotli_comp_level
+syn keyword ngxDirectiveThirdParty contained brotli_min_length
+syn keyword ngxDirectiveThirdParty contained brotli_static
+syn keyword ngxDirectiveThirdParty contained brotli_types
+syn keyword ngxDirectiveThirdParty contained brotli_window
+
+" form-input-nginx-module
+" https://github.com/calio/form-input-nginx-module
+syn keyword ngxDirectiveThirdParty contained set_form_input
+syn keyword ngxDirectiveThirdParty contained set_form_input_multi
+
+" character conversion nginx module using libiconv
+" https://github.com/calio/iconv-nginx-module
+syn keyword ngxDirectiveThirdParty contained iconv_buffer_size
+syn keyword ngxDirectiveThirdParty contained iconv_filter
+syn keyword ngxDirectiveThirdParty contained set_iconv
+
+" 3rd party modules list taken from
+" https://www.nginx.com/resources/wiki/modules/
+" ---------------------------------------------
+
+" Nginx Module for Authenticating Akamai G2O requests
+" https://github.com/kaltura/nginx_mod_akamai_g2o
+syn keyword ngxDirectiveThirdParty contained g2o
+syn keyword ngxDirectiveThirdParty contained g2o_data_header
+syn keyword ngxDirectiveThirdParty contained g2o_hash_function
+syn keyword ngxDirectiveThirdParty contained g2o_key
+syn keyword ngxDirectiveThirdParty contained g2o_log_level
+syn keyword ngxDirectiveThirdParty contained g2o_nonce
+syn keyword ngxDirectiveThirdParty contained g2o_sign_header
+syn keyword ngxDirectiveThirdParty contained g2o_time_window
+syn keyword ngxDirectiveThirdParty contained g2o_version
+
+" nginx_lua_module
+" https://github.com/alacner/nginx_lua_module
+syn keyword ngxDirectiveThirdParty contained lua_file
+
+" Nginx Audio Track for HTTP Live Streaming
+" https://github.com/flavioribeiro/nginx-audio-track-for-hls-module
+syn keyword ngxDirectiveThirdParty contained ngx_hls_audio_track
+syn keyword ngxDirectiveThirdParty contained ngx_hls_audio_track_output_format
+syn keyword ngxDirectiveThirdParty contained ngx_hls_audio_track_output_header
+syn keyword ngxDirectiveThirdParty contained ngx_hls_audio_track_rootpath
+
+" A Nginx module to dump backtrace when a worker process exits abnormally
+" https://github.com/alibaba/nginx-backtrace
+syn keyword ngxDirectiveThirdParty contained backtrace_log
+syn keyword ngxDirectiveThirdParty contained backtrace_max_stack_size
+
+" circle_gif module
+" https://github.com/evanmiller/nginx_circle_gif
+syn keyword ngxDirectiveThirdParty contained circle_gif
+syn keyword ngxDirectiveThirdParty contained circle_gif_max_radius
+syn keyword ngxDirectiveThirdParty contained circle_gif_min_radius
+syn keyword ngxDirectiveThirdParty contained circle_gif_step_radius
+
+" Upstream Consistent Hash
+" https://github.com/replay/ngx_http_consistent_hash
+syn keyword ngxDirectiveThirdParty contained consistent_hash
+
+" Nginx module for etags on dynamic content
+" https://github.com/kali/nginx-dynamic-etags
+syn keyword ngxDirectiveThirdParty contained dynamic_etags
+
+" Enhanced Nginx Memcached Module
+" https://github.com/bpaquet/ngx_http_enhanced_memcached_module
+syn keyword ngxDirectiveThirdParty contained enhanced_memcached_allow_delete
+syn keyword ngxDirectiveThirdParty contained enhanced_memcached_allow_put
+syn keyword ngxDirectiveThirdParty contained enhanced_memcached_bind
+syn keyword ngxDirectiveThirdParty contained enhanced_memcached_buffer_size
+syn keyword ngxDirectiveThirdParty contained enhanced_memcached_connect_timeout
+syn keyword ngxDirectiveThirdParty contained enhanced_memcached_flush
+syn keyword ngxDirectiveThirdParty contained enhanced_memcached_flush_namespace
+syn keyword ngxDirectiveThirdParty contained enhanced_memcached_hash_keys_with_md5
+syn keyword ngxDirectiveThirdParty contained enhanced_memcached_pass
+syn keyword ngxDirectiveThirdParty contained enhanced_memcached_read_timeout
+syn keyword ngxDirectiveThirdParty contained enhanced_memcached_send_timeout
+syn keyword ngxDirectiveThirdParty contained enhanced_memcached_stats
+
+" nginx max connections queue
+" https://github.com/ezmobius/nginx-ey-balancer
+syn keyword ngxDirectiveThirdParty contained max_connections_max_queue_length
+syn keyword ngxDirectiveThirdParty contained max_connections_queue_timeout
+
+" Nginx module for POST authentication and authorization
+" https://github.com/veruu/ngx_form_auth
+syn keyword ngxDirectiveThirdParty contained form_auth
+syn keyword ngxDirectiveThirdParty contained form_auth_login
+syn keyword ngxDirectiveThirdParty contained form_auth_pam_service
+syn keyword ngxDirectiveThirdParty contained form_auth_password
+syn keyword ngxDirectiveThirdParty contained form_auth_remote_user
+
+" ngx_http_accounting_module
+" https://github.com/Lax/ngx_http_accounting_module
+syn keyword ngxDirectiveThirdParty contained http_accounting
+syn keyword ngxDirectiveThirdParty contained http_accounting_id
+syn keyword ngxDirectiveThirdParty contained http_accounting_interval
+syn keyword ngxDirectiveThirdParty contained http_accounting_log
+syn keyword ngxDirectiveThirdParty contained http_accounting_perturb
+
+" concatenating files in a given context: CSS and JS files usually
+" https://github.com/alibaba/nginx-http-concat
+syn keyword ngxDirectiveThirdParty contained concat
+syn keyword ngxDirectiveThirdParty contained concat_delimiter
+syn keyword ngxDirectiveThirdParty contained concat_ignore_file_error
+syn keyword ngxDirectiveThirdParty contained concat_max_files
+syn keyword ngxDirectiveThirdParty contained concat_types
+syn keyword ngxDirectiveThirdParty contained concat_unique
+
+" update upstreams' config by restful interface
+" https://github.com/yzprofile/ngx_http_dyups_module
+syn keyword ngxDirectiveThirdParty contained dyups_interface
+syn keyword ngxDirectiveThirdParty contained dyups_read_msg_log
+syn keyword ngxDirectiveThirdParty contained dyups_read_msg_timeout
+syn keyword ngxDirectiveThirdParty contained dyups_shm_zone_size
+syn keyword ngxDirectiveThirdParty contained dyups_trylock
+syn keyword ngxDirectiveThirdParty contained dyups_upstream_conf
+
+" add given content to the end of the response according to the condition specified
+" https://github.com/flygoast/ngx_http_footer_if_filter
+syn keyword ngxDirectiveThirdParty contained footer_if
+
+" NGINX HTTP Internal Redirect Module
+" https://github.com/flygoast/ngx_http_internal_redirect
+syn keyword ngxDirectiveThirdParty contained internal_redirect_if
+syn keyword ngxDirectiveThirdParty contained internal_redirect_if_no_postpone
+
+" nginx-ip-blocker
+" https://github.com/tmthrgd/nginx-ip-blocker
+syn keyword ngxDirectiveThirdParty contained ip_blocker
+
+" IP2Location Nginx
+" https://github.com/chrislim2888/ip2location-nginx
+syn keyword ngxDirectiveThirdParty contained ip2location_database
+
+" Limit upload rate
+" https://github.com/cfsego/limit_upload_rate
+syn keyword ngxDirectiveThirdParty contained limit_upload_rate
+syn keyword ngxDirectiveThirdParty contained limit_upload_rate_after
+syn keyword ngxDirectiveThirdParty contained limit_upload_rate_log_level
+
+" limit the number of connections to upstream
+" https://github.com/cfsego/nginx-limit-upstream
+syn keyword ngxDirectiveThirdParty contained limit_upstream_conn
+syn keyword ngxDirectiveThirdParty contained limit_upstream_log_level
+syn keyword ngxDirectiveThirdParty contained limit_upstream_zone
+
+" conditional accesslog for nginx
+" https://github.com/cfsego/ngx_log_if
+syn keyword ngxDirectiveThirdParty contained access_log_bypass_if
+
+" log messages over ZeroMQ
+" https://github.com/alticelabs/nginx-log-zmq
+syn keyword ngxDirectiveThirdParty contained log_zmq_endpoint
+syn keyword ngxDirectiveThirdParty contained log_zmq_format
+syn keyword ngxDirectiveThirdParty contained log_zmq_off
+syn keyword ngxDirectiveThirdParty contained log_zmq_server
+
+" simple module to uppercase/lowercase strings in the nginx config
+" https://github.com/replay/ngx_http_lower_upper_case
+syn keyword ngxDirectiveThirdParty contained lower
+syn keyword ngxDirectiveThirdParty contained upper
+
+" content filter for nginx, which returns the md5 hash of the content otherwise returned
+" https://github.com/kainswor/nginx_md5_filter
+syn keyword ngxDirectiveThirdParty contained md5_filter
+
+" Non-blocking upstream module for Nginx to connect to MongoDB
+" https://github.com/simpl/ngx_mongo
+syn keyword ngxDirectiveThirdParty contained mongo_auth
+syn keyword ngxDirectiveThirdParty contained mongo_bind
+syn keyword ngxDirectiveThirdParty contained mongo_buffer_size
+syn keyword ngxDirectiveThirdParty contained mongo_buffering
+syn keyword ngxDirectiveThirdParty contained mongo_buffers
+syn keyword ngxDirectiveThirdParty contained mongo_busy_buffers_size
+syn keyword ngxDirectiveThirdParty contained mongo_connect_timeout
+syn keyword ngxDirectiveThirdParty contained mongo_json
+syn keyword ngxDirectiveThirdParty contained mongo_next_upstream
+syn keyword ngxDirectiveThirdParty contained mongo_pass
+syn keyword ngxDirectiveThirdParty contained mongo_query
+syn keyword ngxDirectiveThirdParty contained mongo_read_timeout
+syn keyword ngxDirectiveThirdParty contained mongo_send_timeout
+
+" Nginx OCSP processing module designed for response caching
+" https://github.com/kyprizel/nginx_ocsp_proxy-module
+syn keyword ngxDirectiveThirdParty contained ocsp_cache_timeout
+syn keyword ngxDirectiveThirdParty contained ocsp_proxy
+
+" Nginx OpenSSL version check at startup
+" https://github.com/apcera/nginx-openssl-version
+syn keyword ngxDirectiveThirdParty contained openssl_builddate_minimum
+syn keyword ngxDirectiveThirdParty contained openssl_version_minimum
+
+" Automatic PageSpeed optimization module for Nginx
+" https://github.com/pagespeed/ngx_pagespeed
+syn keyword ngxDirectiveThirdParty contained pagespeed
+
+" PECL Memcache standard hashing compatible loadbalancer for Nginx
+" https://github.com/replay/ngx_http_php_memcache_standard_balancer
+syn keyword ngxDirectiveThirdParty contained hash_key
+
+" nginx module to parse php sessions
+" https://github.com/replay/ngx_http_php_session
+syn keyword ngxDirectiveThirdParty contained php_session_parse
+syn keyword ngxDirectiveThirdParty contained php_session_strip_formatting
+
+" Nginx HTTP rDNS module
+" https://github.com/flant/nginx-http-rdns
+syn keyword ngxDirectiveThirdParty contained rdns
+syn keyword ngxDirectiveThirdParty contained rdns_allow
+syn keyword ngxDirectiveThirdParty contained rdns_deny
+
+" Streaming regular expression replacement in response bodies
+" https://github.com/openresty/replace-filter-nginx-module
+syn keyword ngxDirectiveThirdParty contained replace_filter
+syn keyword ngxDirectiveThirdParty contained replace_filter_last_modified
+syn keyword ngxDirectiveThirdParty contained replace_filter_max_buffered_size
+syn keyword ngxDirectiveThirdParty contained replace_filter_skip
+syn keyword ngxDirectiveThirdParty contained replace_filter_types
+
+" Link RRDtool's graphing facilities directly into nginx
+" https://github.com/evanmiller/mod_rrd_graph
+syn keyword ngxDirectiveThirdParty contained rrd_graph
+syn keyword ngxDirectiveThirdParty contained rrd_graph_root
+
+" Module for nginx to proxy rtmp using http protocol
+" https://github.com/kwojtek/nginx-rtmpt-proxy-module
+syn keyword ngxDirectiveThirdParty contained rtmpt_proxy
+syn keyword ngxDirectiveThirdParty contained rtmpt_proxy_http_timeout
+syn keyword ngxDirectiveThirdParty contained rtmpt_proxy_rtmp_timeout
+syn keyword ngxDirectiveThirdParty contained rtmpt_proxy_stat
+syn keyword ngxDirectiveThirdParty contained rtmpt_proxy_stylesheet
+syn keyword ngxDirectiveThirdParty contained rtmpt_proxy_target
+
+" Syntactically Awesome NGINX Module
+" https://github.com/mneudert/sass-nginx-module
+syn keyword ngxDirectiveThirdParty contained sass_compile
+syn keyword ngxDirectiveThirdParty contained sass_error_log
+syn keyword ngxDirectiveThirdParty contained sass_include_path
+syn keyword ngxDirectiveThirdParty contained sass_indent
+syn keyword ngxDirectiveThirdParty contained sass_is_indented_syntax
+syn keyword ngxDirectiveThirdParty contained sass_linefeed
+syn keyword ngxDirectiveThirdParty contained sass_output_style
+syn keyword ngxDirectiveThirdParty contained sass_precision
+syn keyword ngxDirectiveThirdParty contained sass_source_comments
+syn keyword ngxDirectiveThirdParty contained sass_source_map_embed
+
+" Nginx Selective Cache Purge Module
+" https://github.com/wandenberg/nginx-selective-cache-purge-module
+syn keyword ngxDirectiveThirdParty contained selective_cache_purge_query
+syn keyword ngxDirectiveThirdParty contained selective_cache_purge_redis_database
+syn keyword ngxDirectiveThirdParty contained selective_cache_purge_redis_host
+syn keyword ngxDirectiveThirdParty contained selective_cache_purge_redis_password
+syn keyword ngxDirectiveThirdParty contained selective_cache_purge_redis_port
+syn keyword ngxDirectiveThirdParty contained selective_cache_purge_redis_unix_socket
+
+" cconv nginx module
+" https://github.com/liseen/set-cconv-nginx-module
+syn keyword ngxDirectiveThirdParty contained set_cconv_to_simp
+syn keyword ngxDirectiveThirdParty contained set_cconv_to_trad
+syn keyword ngxDirectiveThirdParty contained set_pinyin_to_normal
+
+" Nginx module that allows the setting of variables to the value of a variety of hashes
+" https://github.com/simpl/ngx_http_set_hash
+syn keyword ngxDirectiveThirdParty contained set_md5
+syn keyword ngxDirectiveThirdParty contained set_md5_upper
+syn keyword ngxDirectiveThirdParty contained set_murmur2
+syn keyword ngxDirectiveThirdParty contained set_murmur2_upper
+syn keyword ngxDirectiveThirdParty contained set_sha1
+syn keyword ngxDirectiveThirdParty contained set_sha1_upper
+
+" Nginx module to set the language of a request based on a number of options
+" https://github.com/simpl/ngx_http_set_lang
+syn keyword ngxDirectiveThirdParty contained lang_cookie
+syn keyword ngxDirectiveThirdParty contained lang_get_var
+syn keyword ngxDirectiveThirdParty contained lang_host
+syn keyword ngxDirectiveThirdParty contained lang_list
+syn keyword ngxDirectiveThirdParty contained lang_post_var
+syn keyword ngxDirectiveThirdParty contained lang_referer
+syn keyword ngxDirectiveThirdParty contained set_lang
+syn keyword ngxDirectiveThirdParty contained set_lang_method
+
+" Nginx Sorted Querystring Module
+" https://github.com/wandenberg/nginx-sorted-querystring-module
+syn keyword ngxDirectiveThirdParty contained sorted_querysting_filter_parameter
+
+" Nginx upstream module for Sphinx 2.x search daemon
+" https://github.com/reeteshranjan/sphinx2-nginx-module
+syn keyword ngxDirectiveThirdParty contained sphinx2_bind
+syn keyword ngxDirectiveThirdParty contained sphinx2_buffer_size
+syn keyword ngxDirectiveThirdParty contained sphinx2_connect_timeout
+syn keyword ngxDirectiveThirdParty contained sphinx2_next_upstream
+syn keyword ngxDirectiveThirdParty contained sphinx2_pass
+syn keyword ngxDirectiveThirdParty contained sphinx2_read_timeout
+syn keyword ngxDirectiveThirdParty contained sphinx2_send_timeout
+
+" Nginx module for retrieving user attributes and groups from SSSD
+" https://github.com/veruu/ngx_sssd_info
+syn keyword ngxDirectiveThirdParty contained sssd_info
+syn keyword ngxDirectiveThirdParty contained sssd_info_attribute
+syn keyword ngxDirectiveThirdParty contained sssd_info_attribute_separator
+syn keyword ngxDirectiveThirdParty contained sssd_info_attributes
+syn keyword ngxDirectiveThirdParty contained sssd_info_group
+syn keyword ngxDirectiveThirdParty contained sssd_info_group_separator
+syn keyword ngxDirectiveThirdParty contained sssd_info_groups
+syn keyword ngxDirectiveThirdParty contained sssd_info_output_to
+
+" An nginx module for sending statistics to statsd
+" https://github.com/zebrafishlabs/nginx-statsd
+syn keyword ngxDirectiveThirdParty contained statsd_count
+syn keyword ngxDirectiveThirdParty contained statsd_sample_rate
+syn keyword ngxDirectiveThirdParty contained statsd_server
+syn keyword ngxDirectiveThirdParty contained statsd_timing
+
+" ngx_stream_echo - TCP/stream echo module for NGINX (a port of the ngx_http_echo module)
+" https://github.com/openresty/stream-echo-nginx-module
+syn keyword ngxDirectiveThirdParty contained echo
+syn keyword ngxDirectiveThirdParty contained echo_client_error_log_level
+syn keyword ngxDirectiveThirdParty contained echo_discard_request
+syn keyword ngxDirectiveThirdParty contained echo_duplicate
+syn keyword ngxDirectiveThirdParty contained echo_flush_wait
+syn keyword ngxDirectiveThirdParty contained echo_lingering_close
+syn keyword ngxDirectiveThirdParty contained echo_lingering_time
+syn keyword ngxDirectiveThirdParty contained echo_lingering_timeout
+syn keyword ngxDirectiveThirdParty contained echo_read_buffer_size
+syn keyword ngxDirectiveThirdParty contained echo_read_bytes
+syn keyword ngxDirectiveThirdParty contained echo_read_line
+syn keyword ngxDirectiveThirdParty contained echo_read_timeout
+syn keyword ngxDirectiveThirdParty contained echo_request_data
+syn keyword ngxDirectiveThirdParty contained echo_send_timeout
+syn keyword ngxDirectiveThirdParty contained echo_sleep
+
+" Embed the power of Lua into NGINX TCP/UDP servers
+" https://github.com/openresty/stream-lua-nginx-module
+syn keyword ngxDirectiveThirdParty contained lua_add_variable
+syn keyword ngxDirectiveThirdParty contained preread_by_lua_block
+syn keyword ngxDirectiveThirdParty contained preread_by_lua_file
+syn keyword ngxDirectiveThirdParty contained preread_by_lua_no_postpone
+
+" nginx-upsync-module
+" https://github.com/weibocom/nginx-upsync-module
+syn keyword ngxDirectiveThirdParty contained upstream_show
+syn keyword ngxDirectiveThirdParty contained upsync
+syn keyword ngxDirectiveThirdParty contained upsync_dump_path
+syn keyword ngxDirectiveThirdParty contained upsync_lb
+
+" Whitespace stripper for nginx
+" https://github.com/evanmiller/mod_strip
+syn keyword ngxDirectiveThirdParty contained strip
+
+" Split one big HTTP/Range request to multiple subrange requesets
+" https://github.com/Qihoo360/ngx_http_subrange_module
+syn keyword ngxDirectiveThirdParty contained subrange
+
+" summarizer-nginx-module
+" https://github.com/reeteshranjan/summarizer-nginx-module
+syn keyword ngxDirectiveThirdParty contained summarizer_bind
+syn keyword ngxDirectiveThirdParty contained summarizer_buffer_size
+syn keyword ngxDirectiveThirdParty contained summarizer_connect_timeout
+syn keyword ngxDirectiveThirdParty contained summarizer_next_upstream
+syn keyword ngxDirectiveThirdParty contained summarizer_pass
+syn keyword ngxDirectiveThirdParty contained summarizer_read_timeout
+syn keyword ngxDirectiveThirdParty contained summarizer_send_timeout
+
+" nginx module providing API to communicate with supervisord and manage (start/stop) backends on-demand
+" https://github.com/FRiCKLE/ngx_supervisord
+syn keyword ngxDirectiveThirdParty contained supervisord
+syn keyword ngxDirectiveThirdParty contained supervisord_inherit_backend_status
+syn keyword ngxDirectiveThirdParty contained supervisord_name
+syn keyword ngxDirectiveThirdParty contained supervisord_start
+syn keyword ngxDirectiveThirdParty contained supervisord_stop
+
+" simple robot mitigation module using cookie based challenge/response technique. Not supported any more.
+" https://github.com/kyprizel/testcookie-nginx-module
+syn keyword ngxDirectiveThirdParty contained testcookie
+syn keyword ngxDirectiveThirdParty contained testcookie_arg
+syn keyword ngxDirectiveThirdParty contained testcookie_deny_keepalive
+syn keyword ngxDirectiveThirdParty contained testcookie_domain
+syn keyword ngxDirectiveThirdParty contained testcookie_expires
+syn keyword ngxDirectiveThirdParty contained testcookie_fallback
+syn keyword ngxDirectiveThirdParty contained testcookie_get_only
+syn keyword ngxDirectiveThirdParty contained testcookie_httponly_flag
+syn keyword ngxDirectiveThirdParty contained testcookie_https_location
+syn keyword ngxDirectiveThirdParty contained testcookie_internal
+syn keyword ngxDirectiveThirdParty contained testcookie_max_attempts
+syn keyword ngxDirectiveThirdParty contained testcookie_name
+syn keyword ngxDirectiveThirdParty contained testcookie_p3p
+syn keyword ngxDirectiveThirdParty contained testcookie_pass
+syn keyword ngxDirectiveThirdParty contained testcookie_path
+syn keyword ngxDirectiveThirdParty contained testcookie_port_in_redirect
+syn keyword ngxDirectiveThirdParty contained testcookie_redirect_via_refresh
+syn keyword ngxDirectiveThirdParty contained testcookie_refresh_encrypt_cookie
+syn keyword ngxDirectiveThirdParty contained testcookie_refresh_encrypt_cookie_iv
+syn keyword ngxDirectiveThirdParty contained testcookie_refresh_encrypt_cookie_key
+syn keyword ngxDirectiveThirdParty contained testcookie_refresh_status
+syn keyword ngxDirectiveThirdParty contained testcookie_refresh_template
+syn keyword ngxDirectiveThirdParty contained testcookie_secret
+syn keyword ngxDirectiveThirdParty contained testcookie_secure_flag
+syn keyword ngxDirectiveThirdParty contained testcookie_session
+syn keyword ngxDirectiveThirdParty contained testcookie_whitelist
+
+" ngx_http_types_filter_module
+" https://github.com/flygoast/ngx_http_types_filter
+syn keyword ngxDirectiveThirdParty contained types_filter
+syn keyword ngxDirectiveThirdParty contained types_filter_use_default
+
+" A module allowing the nginx to use files embedded in a zip file
+" https://github.com/youzee/nginx-unzip-module
+syn keyword ngxDirectiveThirdParty contained file_in_unzip
+syn keyword ngxDirectiveThirdParty contained file_in_unzip_archivefile
+syn keyword ngxDirectiveThirdParty contained file_in_unzip_extract
+
+" An asynchronous domain name resolve module for nginx upstream
+" https://github.com/wdaike/ngx_upstream_jdomain
+syn keyword ngxDirectiveThirdParty contained jdomain
+
+" Nginx url encoding converting module
+" https://github.com/vozlt/nginx-module-url
+syn keyword ngxDirectiveThirdParty contained url_encoding_convert
+syn keyword ngxDirectiveThirdParty contained url_encoding_convert_alloc_size
+syn keyword ngxDirectiveThirdParty contained url_encoding_convert_alloc_size_x
+syn keyword ngxDirectiveThirdParty contained url_encoding_convert_from
+syn keyword ngxDirectiveThirdParty contained url_encoding_convert_phase
+syn keyword ngxDirectiveThirdParty contained url_encoding_convert_to
+
+" A nginx module to match browsers and crawlers
+" https://github.com/alibaba/nginx-http-user-agent
+syn keyword ngxDirectiveThirdParty contained user_agent
+
+" nginx load-balancer module implementing ketama consistent hashing
+" https://github.com/flygoast/ngx_http_upstream_ketama_chash
+syn keyword ngxDirectiveThirdParty contained ketama_chash
+
+
+
+
+" highlight
+
+hi link ngxComment Comment
+hi link ngxParamComment Comment
+hi link ngxListenComment Comment
+hi link ngxVariable Identifier
+hi link ngxVariableString PreProc
+hi link ngxString String
+hi link ngxListenString String
+
+hi link ngxBoolean Boolean
+hi link ngxDirectiveBlock Statement
+hi link ngxDirectiveImportant Type
+hi link ngxDirectiveListen Type
+hi link ngxDirectiveControl Keyword
+hi link ngxDirectiveError Constant
+hi link ngxDirectiveDeprecated Error
+hi link ngxDirective Identifier
+hi link ngxDirectiveThirdParty Special
+hi link ngxDirectiveThirdPartyDeprecated Error
+
+hi link ngxListenOptions Keyword
+hi link ngxListenOptionsDeprecated Error
+
+let b:current_syntax = "nginx"
diff --git a/nginx/html/50x.html b/nginx/html/50x.html
new file mode 100644 (file)
index 0000000..f60f5e7
--- /dev/null
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Error</title>
+<style>
+    body {
+        width: 35em;
+        margin: 0 auto;
+        font-family: Tahoma, Verdana, Arial, sans-serif;
+    }
+</style>
+</head>
+<body>
+<h1>An error occurred.</h1>
+<p>Sorry, the page you are looking for is currently unavailable.<br/>
+Please try again later.</p>
+<p>If you are the system administrator of this resource then you should check
+the <a href="http://nginx.org/r/error_log">error log</a> for details.</p>
+<p><em>Faithfully yours, nginx.</em></p>
+</body>
+</html>
diff --git a/nginx/html/index.html b/nginx/html/index.html
new file mode 100644 (file)
index 0000000..2ca3b95
--- /dev/null
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Welcome to nginx!</title>
+<style>
+    body {
+        width: 35em;
+        margin: 0 auto;
+        font-family: Tahoma, Verdana, Arial, sans-serif;
+    }
+</style>
+</head>
+<body>
+<h1>Welcome to nginx!</h1>
+<p>If you see this page, the nginx web server is successfully installed and
+working. Further configuration is required.</p>
+
+<p>For online documentation and support please refer to
+<a href="http://nginx.org/">nginx.org</a>.<br/>
+Commercial support is available at
+<a href="http://nginx.com/">nginx.com</a>.</p>
+
+<p><em>Thank you for using nginx.</em></p>
+</body>
+</html>
diff --git a/nginx/man/nginx.8 b/nginx/man/nginx.8
new file mode 100644 (file)
index 0000000..1f4dc89
--- /dev/null
@@ -0,0 +1,206 @@
+.\"
+.\" Copyright (C) 2010 Sergey A. Osokin
+.\" Copyright (C) Nginx, Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\"
+.Dd June 16, 2015
+.Dt NGINX 8
+.Os
+.Sh NAME
+.Nm nginx
+.Nd "HTTP and reverse proxy server, mail proxy server"
+.Sh SYNOPSIS
+.Nm
+.Op Fl ?hqTtVv
+.Op Fl c Ar file
+.Op Fl g Ar directives
+.Op Fl p Ar prefix
+.Op Fl s Ar signal
+.Sh DESCRIPTION
+.Nm
+(pronounced
+.Dq engine x )
+is an HTTP and reverse proxy server, as well as a mail proxy server.
+It is known for its high performance, stability, rich feature set, simple
+configuration, and low resource consumption.
+.Pp
+The options are as follows:
+.Bl -tag -width ".Fl d Ar directives"
+.It Fl ?\& , h
+Print help.
+.It Fl c Ar file
+Use an alternative configuration
+.Ar file .
+.It Fl g Ar directives
+Set global configuration directives.
+See
+.Sx EXAMPLES
+for details.
+.It Fl p Ar prefix
+Set the prefix path.
+The default value is
+.Pa %%PREFIX%% .
+.It Fl q
+Suppress non-error messages during configuration testing.
+.It Fl s Ar signal
+Send a signal to the master process.
+The argument
+.Ar signal
+can be one of:
+.Cm stop , quit , reopen , reload .
+The following table shows the corresponding system signals:
+.Pp
+.Bl -tag -width ".Cm reopen" -compact
+.It Cm stop
+.Dv SIGTERM
+.It Cm quit
+.Dv SIGQUIT
+.It Cm reopen
+.Dv SIGUSR1
+.It Cm reload
+.Dv SIGHUP
+.El
+.It Fl t
+Do not run, just test the configuration file.
+.Nm
+checks the configuration file syntax and then tries to open files
+referenced in the configuration file.
+.It Fl T
+Same as
+.Fl t ,
+but additionally dump configuration files to standard output.
+.It Fl V
+Print the
+.Nm
+version, compiler version, and
+.Pa configure
+script parameters.
+.It Fl v
+Print the
+.Nm
+version.
+.El
+.Sh SIGNALS
+The master process of
+.Nm
+can handle the following signals:
+.Pp
+.Bl -tag -width ".Dv SIGINT , SIGTERM" -compact
+.It Dv SIGINT , SIGTERM
+Shut down quickly.
+.It Dv SIGHUP
+Reload configuration, start the new worker process with a new
+configuration, and gracefully shut down old worker processes.
+.It Dv SIGQUIT
+Shut down gracefully.
+.It Dv SIGUSR1
+Reopen log files.
+.It Dv SIGUSR2
+Upgrade the
+.Nm
+executable on the fly.
+.It Dv SIGWINCH
+Shut down worker processes gracefully.
+.El
+.Pp
+While there is no need to explicitly control worker processes normally,
+they support some signals too:
+.Pp
+.Bl -tag -width ".Dv SIGINT , SIGTERM" -compact
+.It Dv SIGTERM
+Shut down quickly.
+.It Dv SIGQUIT
+Shut down gracefully.
+.It Dv SIGUSR1
+Reopen log files.
+.El
+.Sh DEBUGGING LOG
+To enable a debugging log, reconfigure
+.Nm
+to build with debugging:
+.Pp
+.Dl "./configure --with-debug ..."
+.Pp
+and then set the
+.Cm debug
+level of the
+.Va error_log :
+.Pp
+.Dl "error_log /path/to/log debug;"
+.Pp
+It is also possible to enable the debugging for a particular IP address:
+.Bd -literal -offset indent
+events {
+       debug_connection 127.0.0.1;
+}
+.Ed
+.Sh ENVIRONMENT
+The
+.Ev NGINX
+environment variable is used internally by
+.Nm
+and should not be set directly by the user.
+.Sh FILES
+.Bl -tag -width indent
+.It Pa %%PID_PATH%%
+Contains the process ID of
+.Nm .
+The contents of this file are not sensitive, so it can be world-readable.
+.It Pa %%CONF_PATH%%
+The main configuration file.
+.It Pa %%ERROR_LOG_PATH%%
+Error log file.
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, or 1 if the command fails.
+.Sh EXAMPLES
+Test configuration file
+.Pa ~/mynginx.conf
+with global directives for PID and quantity of worker processes:
+.Bd -literal -offset indent
+nginx -t -c ~/mynginx.conf \e
+       -g "pid /var/run/mynginx.pid; worker_processes 2;"
+.Ed
+.Sh SEE ALSO
+.\"Xr nginx.conf 5
+.\"Pp
+Documentation at
+.Pa http://nginx.org/en/docs/ .
+.Pp
+For questions and technical support, please refer to
+.Pa http://nginx.org/en/support.html .
+.Sh HISTORY
+Development of
+.Nm
+started in 2002, with the first public release on October 4, 2004.
+.Sh AUTHORS
+.An -nosplit
+.An Igor Sysoev Aq [email protected] .
+.Pp
+This manual page was originally written by
+.An Sergey A. Osokin Aq [email protected]
+as a result of compiling many
+.Nm
+documents from all over the world.
diff --git a/nginx/src/core/nginx.c b/nginx/src/core/nginx.c
new file mode 100644 (file)
index 0000000..db1d61b
--- /dev/null
@@ -0,0 +1,1596 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <nginx.h>
+
+
+static void ngx_show_version_info(void);
+static ngx_int_t ngx_add_inherited_sockets(ngx_cycle_t *cycle);
+static void ngx_cleanup_environment(void *data);
+static ngx_int_t ngx_get_options(int argc, char *const *argv);
+static ngx_int_t ngx_process_options(ngx_cycle_t *cycle);
+static ngx_int_t ngx_save_argv(ngx_cycle_t *cycle, int argc, char *const *argv);
+static void *ngx_core_module_create_conf(ngx_cycle_t *cycle);
+static char *ngx_core_module_init_conf(ngx_cycle_t *cycle, void *conf);
+static char *ngx_set_user(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_set_env(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_set_priority(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_set_cpu_affinity(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_set_worker_processes(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_load_module(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+#if (NGX_HAVE_DLOPEN)
+static void ngx_unload_module(void *data);
+#endif
+
+
+static ngx_conf_enum_t  ngx_debug_points[] = {
+    { ngx_string("stop"), NGX_DEBUG_POINTS_STOP },
+    { ngx_string("abort"), NGX_DEBUG_POINTS_ABORT },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t  ngx_core_commands[] = {
+
+    { ngx_string("daemon"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      0,
+      offsetof(ngx_core_conf_t, daemon),
+      NULL },
+
+    { ngx_string("master_process"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      0,
+      offsetof(ngx_core_conf_t, master),
+      NULL },
+
+    { ngx_string("timer_resolution"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      0,
+      offsetof(ngx_core_conf_t, timer_resolution),
+      NULL },
+
+    { ngx_string("pid"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      0,
+      offsetof(ngx_core_conf_t, pid),
+      NULL },
+
+    { ngx_string("lock_file"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      0,
+      offsetof(ngx_core_conf_t, lock_file),
+      NULL },
+
+    { ngx_string("worker_processes"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+      ngx_set_worker_processes,
+      0,
+      0,
+      NULL },
+
+    { ngx_string("debug_points"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      0,
+      offsetof(ngx_core_conf_t, debug_points),
+      &ngx_debug_points },
+
+    { ngx_string("user"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE12,
+      ngx_set_user,
+      0,
+      0,
+      NULL },
+
+    { ngx_string("worker_priority"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+      ngx_set_priority,
+      0,
+      0,
+      NULL },
+
+    { ngx_string("worker_cpu_affinity"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_1MORE,
+      ngx_set_cpu_affinity,
+      0,
+      0,
+      NULL },
+
+    { ngx_string("worker_rlimit_nofile"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      0,
+      offsetof(ngx_core_conf_t, rlimit_nofile),
+      NULL },
+
+    { ngx_string("worker_rlimit_core"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_off_slot,
+      0,
+      offsetof(ngx_core_conf_t, rlimit_core),
+      NULL },
+
+    { ngx_string("worker_shutdown_timeout"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      0,
+      offsetof(ngx_core_conf_t, shutdown_timeout),
+      NULL },
+
+    { ngx_string("working_directory"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      0,
+      offsetof(ngx_core_conf_t, working_directory),
+      NULL },
+
+    { ngx_string("env"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+      ngx_set_env,
+      0,
+      0,
+      NULL },
+
+    { ngx_string("load_module"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+      ngx_load_module,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_core_module_t  ngx_core_module_ctx = {
+    ngx_string("core"),
+    ngx_core_module_create_conf,
+    ngx_core_module_init_conf
+};
+
+
+ngx_module_t  ngx_core_module = {
+    NGX_MODULE_V1,
+    &ngx_core_module_ctx,                  /* module context */
+    ngx_core_commands,                     /* module directives */
+    NGX_CORE_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_uint_t   ngx_show_help;
+static ngx_uint_t   ngx_show_version;
+static ngx_uint_t   ngx_show_configure;
+static u_char      *ngx_prefix;
+static u_char      *ngx_conf_file;
+static u_char      *ngx_conf_params;
+static char        *ngx_signal;
+
+
+static char **ngx_os_environ;
+
+
+int ngx_cdecl
+main(int argc, char *const *argv)
+{
+    ngx_buf_t        *b;
+    ngx_log_t        *log;
+    ngx_uint_t        i;
+    ngx_cycle_t      *cycle, init_cycle;
+    ngx_conf_dump_t  *cd;
+    ngx_core_conf_t  *ccf;
+
+    ngxvcl_app_create("Nginx with VCL");
+
+    ngx_debug_init();
+
+    if (ngx_strerror_init() != NGX_OK) {
+        return 1;
+    }
+
+    if (ngx_get_options(argc, argv) != NGX_OK) {
+        return 1;
+    }
+
+    if (ngx_show_version) {
+        ngx_show_version_info();
+
+        if (!ngx_test_config) {
+            return 0;
+        }
+    }
+
+    /* TODO */ ngx_max_sockets = -1;
+
+    ngx_time_init();
+
+#if (NGX_PCRE)
+    ngx_regex_init();
+#endif
+
+    ngx_pid = ngx_getpid();
+    ngx_parent = ngx_getppid();
+
+    log = ngx_log_init(ngx_prefix);
+    if (log == NULL) {
+        return 1;
+    }
+
+    /* STUB */
+#if (NGX_OPENSSL)
+    ngx_ssl_init(log);
+#endif
+
+    /*
+     * init_cycle->log is required for signal handlers and
+     * ngx_process_options()
+     */
+
+    ngx_memzero(&init_cycle, sizeof(ngx_cycle_t));
+    init_cycle.log = log;
+    ngx_cycle = &init_cycle;
+
+    init_cycle.pool = ngx_create_pool(1024, log);
+    if (init_cycle.pool == NULL) {
+        return 1;
+    }
+
+    if (ngx_save_argv(&init_cycle, argc, argv) != NGX_OK) {
+        return 1;
+    }
+
+    if (ngx_process_options(&init_cycle) != NGX_OK) {
+        return 1;
+    }
+
+    if (ngx_os_init(log) != NGX_OK) {
+        return 1;
+    }
+
+    /*
+     * ngx_crc32_table_init() requires ngx_cacheline_size set in ngx_os_init()
+     */
+
+    if (ngx_crc32_table_init() != NGX_OK) {
+        return 1;
+    }
+
+    /*
+     * ngx_slab_sizes_init() requires ngx_pagesize set in ngx_os_init()
+     */
+
+    ngx_slab_sizes_init();
+
+    if (ngx_add_inherited_sockets(&init_cycle) != NGX_OK) {
+        return 1;
+    }
+
+    if (ngx_preinit_modules() != NGX_OK) {
+        return 1;
+    }
+
+    cycle = ngx_init_cycle(&init_cycle);
+    if (cycle == NULL) {
+        if (ngx_test_config) {
+            ngx_log_stderr(0, "configuration file %s test failed",
+                           init_cycle.conf_file.data);
+        }
+
+        return 1;
+    }
+
+    if (ngx_test_config) {
+        if (!ngx_quiet_mode) {
+            ngx_log_stderr(0, "configuration file %s test is successful",
+                           cycle->conf_file.data);
+        }
+
+        if (ngx_dump_config) {
+            cd = cycle->config_dump.elts;
+
+            for (i = 0; i < cycle->config_dump.nelts; i++) {
+
+                ngx_write_stdout("# configuration file ");
+                (void) ngx_write_fd(ngx_stdout, cd[i].name.data,
+                                    cd[i].name.len);
+                ngx_write_stdout(":" NGX_LINEFEED);
+
+                b = cd[i].buffer;
+
+                (void) ngx_write_fd(ngx_stdout, b->pos, b->last - b->pos);
+                ngx_write_stdout(NGX_LINEFEED);
+            }
+        }
+
+        return 0;
+    }
+
+    if (ngx_signal) {
+        return ngx_signal_process(cycle, ngx_signal);
+    }
+
+    ngx_os_status(cycle->log);
+
+    ngx_cycle = cycle;
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+    if (ccf->master && ngx_process == NGX_PROCESS_SINGLE) {
+        ngx_process = NGX_PROCESS_MASTER;
+    }
+
+#if !(NGX_WIN32)
+
+    if (ngx_init_signals(cycle->log) != NGX_OK) {
+        return 1;
+    }
+
+    if (!ngx_inherited && ccf->daemon) {
+        if (ngx_daemon(cycle->log) != NGX_OK) {
+            return 1;
+        }
+
+        ngx_daemonized = 1;
+    }
+
+    if (ngx_inherited) {
+        ngx_daemonized = 1;
+    }
+
+#endif
+
+    if (ngx_create_pidfile(&ccf->pid, cycle->log) != NGX_OK) {
+        return 1;
+    }
+
+    if (ngx_log_redirect_stderr(cycle) != NGX_OK) {
+        return 1;
+    }
+
+    if (log->file->fd != ngx_stderr) {
+        if (ngx_close_file(log->file->fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          ngx_close_file_n " built-in log failed");
+        }
+    }
+
+    ngx_use_stderr = 0;
+
+    if (ngx_process == NGX_PROCESS_SINGLE) {
+        ngxvcl_wait_vep_only();
+        ngx_single_process_cycle(cycle);
+
+    } else {
+        ngxvcl_wait_kep_and_vep();
+        ngx_master_process_cycle(cycle);
+    }
+
+    ngxvcl_app_destroy();
+
+    return 0;
+}
+
+
+static void
+ngx_show_version_info(void)
+{
+    ngx_write_stderr("nginx version: " NGINX_VER_BUILD NGX_LINEFEED);
+
+    if (ngx_show_help) {
+        ngx_write_stderr(
+            "Usage: nginx [-?hvVtTq] [-s signal] [-c filename] "
+                         "[-p prefix] [-g directives]" NGX_LINEFEED
+                         NGX_LINEFEED
+            "Options:" NGX_LINEFEED
+            "  -?,-h         : this help" NGX_LINEFEED
+            "  -v            : show version and exit" NGX_LINEFEED
+            "  -V            : show version and configure options then exit"
+                               NGX_LINEFEED
+            "  -t            : test configuration and exit" NGX_LINEFEED
+            "  -T            : test configuration, dump it and exit"
+                               NGX_LINEFEED
+            "  -q            : suppress non-error messages "
+                               "during configuration testing" NGX_LINEFEED
+            "  -s signal     : send signal to a master process: "
+                               "stop, quit, reopen, reload" NGX_LINEFEED
+#ifdef NGX_PREFIX
+            "  -p prefix     : set prefix path (default: " NGX_PREFIX ")"
+                               NGX_LINEFEED
+#else
+            "  -p prefix     : set prefix path (default: NONE)" NGX_LINEFEED
+#endif
+            "  -c filename   : set configuration file (default: " NGX_CONF_PATH
+                               ")" NGX_LINEFEED
+            "  -g directives : set global directives out of configuration "
+                               "file" NGX_LINEFEED NGX_LINEFEED
+        );
+    }
+
+    if (ngx_show_configure) {
+
+#ifdef NGX_COMPILER
+        ngx_write_stderr("built by " NGX_COMPILER NGX_LINEFEED);
+#endif
+
+#if (NGX_SSL)
+        if (ngx_strcmp(ngx_ssl_version(), OPENSSL_VERSION_TEXT) == 0) {
+            ngx_write_stderr("built with " OPENSSL_VERSION_TEXT NGX_LINEFEED);
+        } else {
+            ngx_write_stderr("built with " OPENSSL_VERSION_TEXT
+                             " (running with ");
+            ngx_write_stderr((char *) (uintptr_t) ngx_ssl_version());
+            ngx_write_stderr(")" NGX_LINEFEED);
+        }
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+        ngx_write_stderr("TLS SNI support enabled" NGX_LINEFEED);
+#else
+        ngx_write_stderr("TLS SNI support disabled" NGX_LINEFEED);
+#endif
+#endif
+
+        ngx_write_stderr("configure arguments:" NGX_CONFIGURE NGX_LINEFEED);
+    }
+}
+
+
+static ngx_int_t
+ngx_add_inherited_sockets(ngx_cycle_t *cycle)
+{
+    u_char           *p, *v, *inherited;
+    ngx_int_t         s;
+    ngx_listening_t  *ls;
+
+    inherited = (u_char *) getenv(NGINX_VAR);
+
+    if (inherited == NULL) {
+        return NGX_OK;
+    }
+
+    ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
+                  "using inherited sockets from \"%s\"", inherited);
+
+    if (ngx_array_init(&cycle->listening, cycle->pool, 10,
+                       sizeof(ngx_listening_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    for (p = inherited, v = p; *p; p++) {
+        if (*p == ':' || *p == ';') {
+            s = ngx_atoi(v, p - v);
+            if (s == NGX_ERROR) {
+                ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+                              "invalid socket number \"%s\" in " NGINX_VAR
+                              " environment variable, ignoring the rest"
+                              " of the variable", v);
+                break;
+            }
+
+            v = p + 1;
+
+            ls = ngx_array_push(&cycle->listening);
+            if (ls == NULL) {
+                return NGX_ERROR;
+            }
+
+            ngx_memzero(ls, sizeof(ngx_listening_t));
+
+            ls->fd = (ngx_socket_t) s;
+        }
+    }
+
+    if (v != p) {
+        ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+                      "invalid socket number \"%s\" in " NGINX_VAR
+                      " environment variable, ignoring", v);
+    }
+
+    ngx_inherited = 1;
+
+    return ngx_set_inherited_sockets(cycle);
+}
+
+
+char **
+ngx_set_environment(ngx_cycle_t *cycle, ngx_uint_t *last)
+{
+    char                **p, **env;
+    ngx_str_t            *var;
+    ngx_uint_t            i, n;
+    ngx_core_conf_t      *ccf;
+    ngx_pool_cleanup_t   *cln;
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+    if (last == NULL && ccf->environment) {
+        return ccf->environment;
+    }
+
+    var = ccf->env.elts;
+
+    for (i = 0; i < ccf->env.nelts; i++) {
+        if (ngx_strcmp(var[i].data, "TZ") == 0
+            || ngx_strncmp(var[i].data, "TZ=", 3) == 0)
+        {
+            goto tz_found;
+        }
+    }
+
+    var = ngx_array_push(&ccf->env);
+    if (var == NULL) {
+        return NULL;
+    }
+
+    var->len = 2;
+    var->data = (u_char *) "TZ";
+
+    var = ccf->env.elts;
+
+tz_found:
+
+    n = 0;
+
+    for (i = 0; i < ccf->env.nelts; i++) {
+
+        if (var[i].data[var[i].len] == '=') {
+            n++;
+            continue;
+        }
+
+        for (p = ngx_os_environ; *p; p++) {
+
+            if (ngx_strncmp(*p, var[i].data, var[i].len) == 0
+                && (*p)[var[i].len] == '=')
+            {
+                n++;
+                break;
+            }
+        }
+    }
+
+    if (last) {
+        env = ngx_alloc((*last + n + 1) * sizeof(char *), cycle->log);
+        if (env == NULL) {
+            return NULL;
+        }
+
+        *last = n;
+
+    } else {
+        cln = ngx_pool_cleanup_add(cycle->pool, 0);
+        if (cln == NULL) {
+            return NULL;
+        }
+
+        env = ngx_alloc((n + 1) * sizeof(char *), cycle->log);
+        if (env == NULL) {
+            return NULL;
+        }
+
+        cln->handler = ngx_cleanup_environment;
+        cln->data = env;
+    }
+
+    n = 0;
+
+    for (i = 0; i < ccf->env.nelts; i++) {
+
+        if (var[i].data[var[i].len] == '=') {
+            env[n++] = (char *) var[i].data;
+            continue;
+        }
+
+        for (p = ngx_os_environ; *p; p++) {
+
+            if (ngx_strncmp(*p, var[i].data, var[i].len) == 0
+                && (*p)[var[i].len] == '=')
+            {
+                env[n++] = *p;
+                break;
+            }
+        }
+    }
+
+    env[n] = NULL;
+
+    if (last == NULL) {
+        ccf->environment = env;
+        environ = env;
+    }
+
+    return env;
+}
+
+
+static void
+ngx_cleanup_environment(void *data)
+{
+    char  **env = data;
+
+    if (environ == env) {
+
+        /*
+         * if the environment is still used, as it happens on exit,
+         * the only option is to leak it
+         */
+
+        return;
+    }
+
+    ngx_free(env);
+}
+
+
+ngx_pid_t
+ngx_exec_new_binary(ngx_cycle_t *cycle, char *const *argv)
+{
+    char             **env, *var;
+    u_char            *p;
+    ngx_uint_t         i, n;
+    ngx_pid_t          pid;
+    ngx_exec_ctx_t     ctx;
+    ngx_core_conf_t   *ccf;
+    ngx_listening_t   *ls;
+
+    ngx_memzero(&ctx, sizeof(ngx_exec_ctx_t));
+
+    ctx.path = argv[0];
+    ctx.name = "new binary process";
+    ctx.argv = argv;
+
+    n = 2;
+    env = ngx_set_environment(cycle, &n);
+    if (env == NULL) {
+        return NGX_INVALID_PID;
+    }
+
+    var = ngx_alloc(sizeof(NGINX_VAR)
+                    + cycle->listening.nelts * (NGX_INT32_LEN + 1) + 2,
+                    cycle->log);
+    if (var == NULL) {
+        ngx_free(env);
+        return NGX_INVALID_PID;
+    }
+
+    p = ngx_cpymem(var, NGINX_VAR "=", sizeof(NGINX_VAR));
+
+    ls = cycle->listening.elts;
+    for (i = 0; i < cycle->listening.nelts; i++) {
+        p = ngx_sprintf(p, "%ud;", ls[i].fd);
+    }
+
+    *p = '\0';
+
+    env[n++] = var;
+
+#if (NGX_SETPROCTITLE_USES_ENV)
+
+    /* allocate the spare 300 bytes for the new binary process title */
+
+    env[n++] = "SPARE=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+               "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+               "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+               "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+               "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
+
+#endif
+
+    env[n] = NULL;
+
+#if (NGX_DEBUG)
+    {
+    char  **e;
+    for (e = env; *e; e++) {
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, cycle->log, 0, "env: %s", *e);
+    }
+    }
+#endif
+
+    ctx.envp = (char *const *) env;
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+    if (ngx_rename_file(ccf->pid.data, ccf->oldpid.data) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      ngx_rename_file_n " %s to %s failed "
+                      "before executing new binary process \"%s\"",
+                      ccf->pid.data, ccf->oldpid.data, argv[0]);
+
+        ngx_free(env);
+        ngx_free(var);
+
+        return NGX_INVALID_PID;
+    }
+
+    pid = ngx_execute(cycle, &ctx);
+
+    if (pid == NGX_INVALID_PID) {
+        if (ngx_rename_file(ccf->oldpid.data, ccf->pid.data)
+            == NGX_FILE_ERROR)
+        {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          ngx_rename_file_n " %s back to %s failed after "
+                          "an attempt to execute new binary process \"%s\"",
+                          ccf->oldpid.data, ccf->pid.data, argv[0]);
+        }
+    }
+
+    ngx_free(env);
+    ngx_free(var);
+
+    return pid;
+}
+
+
+static ngx_int_t
+ngx_get_options(int argc, char *const *argv)
+{
+    u_char     *p;
+    ngx_int_t   i;
+
+    for (i = 1; i < argc; i++) {
+
+        p = (u_char *) argv[i];
+
+        if (*p++ != '-') {
+            ngx_log_stderr(0, "invalid option: \"%s\"", argv[i]);
+            return NGX_ERROR;
+        }
+
+        while (*p) {
+
+            switch (*p++) {
+
+            case '?':
+            case 'h':
+                ngx_show_version = 1;
+                ngx_show_help = 1;
+                break;
+
+            case 'v':
+                ngx_show_version = 1;
+                break;
+
+            case 'V':
+                ngx_show_version = 1;
+                ngx_show_configure = 1;
+                break;
+
+            case 't':
+                ngx_test_config = 1;
+                break;
+
+            case 'T':
+                ngx_test_config = 1;
+                ngx_dump_config = 1;
+                break;
+
+            case 'q':
+                ngx_quiet_mode = 1;
+                break;
+
+            case 'p':
+                if (*p) {
+                    ngx_prefix = p;
+                    goto next;
+                }
+
+                if (argv[++i]) {
+                    ngx_prefix = (u_char *) argv[i];
+                    goto next;
+                }
+
+                ngx_log_stderr(0, "option \"-p\" requires directory name");
+                return NGX_ERROR;
+
+            case 'c':
+                if (*p) {
+                    ngx_conf_file = p;
+                    goto next;
+                }
+
+                if (argv[++i]) {
+                    ngx_conf_file = (u_char *) argv[i];
+                    goto next;
+                }
+
+                ngx_log_stderr(0, "option \"-c\" requires file name");
+                return NGX_ERROR;
+
+            case 'g':
+                if (*p) {
+                    ngx_conf_params = p;
+                    goto next;
+                }
+
+                if (argv[++i]) {
+                    ngx_conf_params = (u_char *) argv[i];
+                    goto next;
+                }
+
+                ngx_log_stderr(0, "option \"-g\" requires parameter");
+                return NGX_ERROR;
+
+            case 's':
+                if (*p) {
+                    ngx_signal = (char *) p;
+
+                } else if (argv[++i]) {
+                    ngx_signal = argv[i];
+
+                } else {
+                    ngx_log_stderr(0, "option \"-s\" requires parameter");
+                    return NGX_ERROR;
+                }
+
+                if (ngx_strcmp(ngx_signal, "stop") == 0
+                    || ngx_strcmp(ngx_signal, "quit") == 0
+                    || ngx_strcmp(ngx_signal, "reopen") == 0
+                    || ngx_strcmp(ngx_signal, "reload") == 0)
+                {
+                    ngx_process = NGX_PROCESS_SIGNALLER;
+                    goto next;
+                }
+
+                ngx_log_stderr(0, "invalid option: \"-s %s\"", ngx_signal);
+                return NGX_ERROR;
+
+            default:
+                ngx_log_stderr(0, "invalid option: \"%c\"", *(p - 1));
+                return NGX_ERROR;
+            }
+        }
+
+    next:
+
+        continue;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_save_argv(ngx_cycle_t *cycle, int argc, char *const *argv)
+{
+#if (NGX_FREEBSD)
+
+    ngx_os_argv = (char **) argv;
+    ngx_argc = argc;
+    ngx_argv = (char **) argv;
+
+#else
+    size_t     len;
+    ngx_int_t  i;
+
+    ngx_os_argv = (char **) argv;
+    ngx_argc = argc;
+
+    ngx_argv = ngx_alloc((argc + 1) * sizeof(char *), cycle->log);
+    if (ngx_argv == NULL) {
+        return NGX_ERROR;
+    }
+
+    for (i = 0; i < argc; i++) {
+        len = ngx_strlen(argv[i]) + 1;
+
+        ngx_argv[i] = ngx_alloc(len, cycle->log);
+        if (ngx_argv[i] == NULL) {
+            return NGX_ERROR;
+        }
+
+        (void) ngx_cpystrn((u_char *) ngx_argv[i], (u_char *) argv[i], len);
+    }
+
+    ngx_argv[i] = NULL;
+
+#endif
+
+    ngx_os_environ = environ;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_process_options(ngx_cycle_t *cycle)
+{
+    u_char  *p;
+    size_t   len;
+
+    if (ngx_prefix) {
+        len = ngx_strlen(ngx_prefix);
+        p = ngx_prefix;
+
+        if (len && !ngx_path_separator(p[len - 1])) {
+            p = ngx_pnalloc(cycle->pool, len + 1);
+            if (p == NULL) {
+                return NGX_ERROR;
+            }
+
+            ngx_memcpy(p, ngx_prefix, len);
+            p[len++] = '/';
+        }
+
+        cycle->conf_prefix.len = len;
+        cycle->conf_prefix.data = p;
+        cycle->prefix.len = len;
+        cycle->prefix.data = p;
+
+    } else {
+
+#ifndef NGX_PREFIX
+
+        p = ngx_pnalloc(cycle->pool, NGX_MAX_PATH);
+        if (p == NULL) {
+            return NGX_ERROR;
+        }
+
+        if (ngx_getcwd(p, NGX_MAX_PATH) == 0) {
+            ngx_log_stderr(ngx_errno, "[emerg]: " ngx_getcwd_n " failed");
+            return NGX_ERROR;
+        }
+
+        len = ngx_strlen(p);
+
+        p[len++] = '/';
+
+        cycle->conf_prefix.len = len;
+        cycle->conf_prefix.data = p;
+        cycle->prefix.len = len;
+        cycle->prefix.data = p;
+
+#else
+
+#ifdef NGX_CONF_PREFIX
+        ngx_str_set(&cycle->conf_prefix, NGX_CONF_PREFIX);
+#else
+        ngx_str_set(&cycle->conf_prefix, NGX_PREFIX);
+#endif
+        ngx_str_set(&cycle->prefix, NGX_PREFIX);
+
+#endif
+    }
+
+    if (ngx_conf_file) {
+        cycle->conf_file.len = ngx_strlen(ngx_conf_file);
+        cycle->conf_file.data = ngx_conf_file;
+
+    } else {
+        ngx_str_set(&cycle->conf_file, NGX_CONF_PATH);
+    }
+
+    if (ngx_conf_full_name(cycle, &cycle->conf_file, 0) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    for (p = cycle->conf_file.data + cycle->conf_file.len - 1;
+         p > cycle->conf_file.data;
+         p--)
+    {
+        if (ngx_path_separator(*p)) {
+            cycle->conf_prefix.len = p - cycle->conf_file.data + 1;
+            cycle->conf_prefix.data = cycle->conf_file.data;
+            break;
+        }
+    }
+
+    if (ngx_conf_params) {
+        cycle->conf_param.len = ngx_strlen(ngx_conf_params);
+        cycle->conf_param.data = ngx_conf_params;
+    }
+
+    if (ngx_test_config) {
+        cycle->log->log_level = NGX_LOG_INFO;
+    }
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_core_module_create_conf(ngx_cycle_t *cycle)
+{
+    ngx_core_conf_t  *ccf;
+
+    ccf = ngx_pcalloc(cycle->pool, sizeof(ngx_core_conf_t));
+    if (ccf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc()
+     *
+     *     ccf->pid = NULL;
+     *     ccf->oldpid = NULL;
+     *     ccf->priority = 0;
+     *     ccf->cpu_affinity_auto = 0;
+     *     ccf->cpu_affinity_n = 0;
+     *     ccf->cpu_affinity = NULL;
+     */
+
+    ccf->daemon = NGX_CONF_UNSET;
+    ccf->master = NGX_CONF_UNSET;
+    ccf->timer_resolution = NGX_CONF_UNSET_MSEC;
+    ccf->shutdown_timeout = NGX_CONF_UNSET_MSEC;
+
+    ccf->worker_processes = NGX_CONF_UNSET;
+    ccf->debug_points = NGX_CONF_UNSET;
+
+    ccf->rlimit_nofile = NGX_CONF_UNSET;
+    ccf->rlimit_core = NGX_CONF_UNSET;
+
+    ccf->user = (ngx_uid_t) NGX_CONF_UNSET_UINT;
+    ccf->group = (ngx_gid_t) NGX_CONF_UNSET_UINT;
+
+    if (ngx_array_init(&ccf->env, cycle->pool, 1, sizeof(ngx_str_t))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    return ccf;
+}
+
+
+static char *
+ngx_core_module_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+    ngx_core_conf_t  *ccf = conf;
+
+    ngx_conf_init_value(ccf->daemon, 1);
+    ngx_conf_init_value(ccf->master, 1);
+    ngx_conf_init_msec_value(ccf->timer_resolution, 0);
+    ngx_conf_init_msec_value(ccf->shutdown_timeout, 0);
+
+    ngx_conf_init_value(ccf->worker_processes, 1);
+    ngx_conf_init_value(ccf->debug_points, 0);
+
+#if (NGX_HAVE_CPU_AFFINITY)
+
+    if (!ccf->cpu_affinity_auto
+        && ccf->cpu_affinity_n
+        && ccf->cpu_affinity_n != 1
+        && ccf->cpu_affinity_n != (ngx_uint_t) ccf->worker_processes)
+    {
+        ngx_log_error(NGX_LOG_WARN, cycle->log, 0,
+                      "the number of \"worker_processes\" is not equal to "
+                      "the number of \"worker_cpu_affinity\" masks, "
+                      "using last mask for remaining worker processes");
+    }
+
+#endif
+
+
+    if (ccf->pid.len == 0) {
+        ngx_str_set(&ccf->pid, NGX_PID_PATH);
+    }
+
+    if (ngx_conf_full_name(cycle, &ccf->pid, 0) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    ccf->oldpid.len = ccf->pid.len + sizeof(NGX_OLDPID_EXT);
+
+    ccf->oldpid.data = ngx_pnalloc(cycle->pool, ccf->oldpid.len);
+    if (ccf->oldpid.data == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memcpy(ngx_cpymem(ccf->oldpid.data, ccf->pid.data, ccf->pid.len),
+               NGX_OLDPID_EXT, sizeof(NGX_OLDPID_EXT));
+
+
+#if !(NGX_WIN32)
+
+    if (ccf->user == (uid_t) NGX_CONF_UNSET_UINT && geteuid() == 0) {
+        struct group   *grp;
+        struct passwd  *pwd;
+
+        ngx_set_errno(0);
+        pwd = getpwnam(NGX_USER);
+        if (pwd == NULL) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          "getpwnam(\"" NGX_USER "\") failed");
+            return NGX_CONF_ERROR;
+        }
+
+        ccf->username = NGX_USER;
+        ccf->user = pwd->pw_uid;
+
+        ngx_set_errno(0);
+        grp = getgrnam(NGX_GROUP);
+        if (grp == NULL) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          "getgrnam(\"" NGX_GROUP "\") failed");
+            return NGX_CONF_ERROR;
+        }
+
+        ccf->group = grp->gr_gid;
+    }
+
+
+    if (ccf->lock_file.len == 0) {
+        ngx_str_set(&ccf->lock_file, NGX_LOCK_PATH);
+    }
+
+    if (ngx_conf_full_name(cycle, &ccf->lock_file, 0) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    {
+    ngx_str_t  lock_file;
+
+    lock_file = cycle->old_cycle->lock_file;
+
+    if (lock_file.len) {
+        lock_file.len--;
+
+        if (ccf->lock_file.len != lock_file.len
+            || ngx_strncmp(ccf->lock_file.data, lock_file.data, lock_file.len)
+               != 0)
+        {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+                          "\"lock_file\" could not be changed, ignored");
+        }
+
+        cycle->lock_file.len = lock_file.len + 1;
+        lock_file.len += sizeof(".accept");
+
+        cycle->lock_file.data = ngx_pstrdup(cycle->pool, &lock_file);
+        if (cycle->lock_file.data == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+    } else {
+        cycle->lock_file.len = ccf->lock_file.len + 1;
+        cycle->lock_file.data = ngx_pnalloc(cycle->pool,
+                                      ccf->lock_file.len + sizeof(".accept"));
+        if (cycle->lock_file.data == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        ngx_memcpy(ngx_cpymem(cycle->lock_file.data, ccf->lock_file.data,
+                              ccf->lock_file.len),
+                   ".accept", sizeof(".accept"));
+    }
+    }
+
+#endif
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_set_user(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+#if (NGX_WIN32)
+
+    ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                       "\"user\" is not supported, ignored");
+
+    return NGX_CONF_OK;
+
+#else
+
+    ngx_core_conf_t  *ccf = conf;
+
+    char             *group;
+    struct passwd    *pwd;
+    struct group     *grp;
+    ngx_str_t        *value;
+
+    if (ccf->user != (uid_t) NGX_CONF_UNSET_UINT) {
+        return "is duplicate";
+    }
+
+    if (geteuid() != 0) {
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                           "the \"user\" directive makes sense only "
+                           "if the master process runs "
+                           "with super-user privileges, ignored");
+        return NGX_CONF_OK;
+    }
+
+    value = cf->args->elts;
+
+    ccf->username = (char *) value[1].data;
+
+    ngx_set_errno(0);
+    pwd = getpwnam((const char *) value[1].data);
+    if (pwd == NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
+                           "getpwnam(\"%s\") failed", value[1].data);
+        return NGX_CONF_ERROR;
+    }
+
+    ccf->user = pwd->pw_uid;
+
+    group = (char *) ((cf->args->nelts == 2) ? value[1].data : value[2].data);
+
+    ngx_set_errno(0);
+    grp = getgrnam(group);
+    if (grp == NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
+                           "getgrnam(\"%s\") failed", group);
+        return NGX_CONF_ERROR;
+    }
+
+    ccf->group = grp->gr_gid;
+
+    return NGX_CONF_OK;
+
+#endif
+}
+
+
+static char *
+ngx_set_env(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_core_conf_t  *ccf = conf;
+
+    ngx_str_t   *value, *var;
+    ngx_uint_t   i;
+
+    var = ngx_array_push(&ccf->env);
+    if (var == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    value = cf->args->elts;
+    *var = value[1];
+
+    for (i = 0; i < value[1].len; i++) {
+
+        if (value[1].data[i] == '=') {
+
+            var->len = i;
+
+            return NGX_CONF_OK;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_set_priority(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_core_conf_t  *ccf = conf;
+
+    ngx_str_t        *value;
+    ngx_uint_t        n, minus;
+
+    if (ccf->priority != 0) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    if (value[1].data[0] == '-') {
+        n = 1;
+        minus = 1;
+
+    } else if (value[1].data[0] == '+') {
+        n = 1;
+        minus = 0;
+
+    } else {
+        n = 0;
+        minus = 0;
+    }
+
+    ccf->priority = ngx_atoi(&value[1].data[n], value[1].len - n);
+    if (ccf->priority == NGX_ERROR) {
+        return "invalid number";
+    }
+
+    if (minus) {
+        ccf->priority = -ccf->priority;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_set_cpu_affinity(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+#if (NGX_HAVE_CPU_AFFINITY)
+    ngx_core_conf_t  *ccf = conf;
+
+    u_char            ch, *p;
+    ngx_str_t        *value;
+    ngx_uint_t        i, n;
+    ngx_cpuset_t     *mask;
+
+    if (ccf->cpu_affinity) {
+        return "is duplicate";
+    }
+
+    mask = ngx_palloc(cf->pool, (cf->args->nelts - 1) * sizeof(ngx_cpuset_t));
+    if (mask == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ccf->cpu_affinity_n = cf->args->nelts - 1;
+    ccf->cpu_affinity = mask;
+
+    value = cf->args->elts;
+
+    if (ngx_strcmp(value[1].data, "auto") == 0) {
+
+        if (cf->args->nelts > 3) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid number of arguments in "
+                               "\"worker_cpu_affinity\" directive");
+            return NGX_CONF_ERROR;
+        }
+
+        ccf->cpu_affinity_auto = 1;
+
+        CPU_ZERO(&mask[0]);
+        for (i = 0; i < (ngx_uint_t) ngx_min(ngx_ncpu, CPU_SETSIZE); i++) {
+            CPU_SET(i, &mask[0]);
+        }
+
+        n = 2;
+
+    } else {
+        n = 1;
+    }
+
+    for ( /* void */ ; n < cf->args->nelts; n++) {
+
+        if (value[n].len > CPU_SETSIZE) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                         "\"worker_cpu_affinity\" supports up to %d CPUs only",
+                         CPU_SETSIZE);
+            return NGX_CONF_ERROR;
+        }
+
+        i = 0;
+        CPU_ZERO(&mask[n - 1]);
+
+        for (p = value[n].data + value[n].len - 1;
+             p >= value[n].data;
+             p--)
+        {
+            ch = *p;
+
+            if (ch == ' ') {
+                continue;
+            }
+
+            i++;
+
+            if (ch == '0') {
+                continue;
+            }
+
+            if (ch == '1') {
+                CPU_SET(i - 1, &mask[n - 1]);
+                continue;
+            }
+
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                          "invalid character \"%c\" in \"worker_cpu_affinity\"",
+                          ch);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+#else
+
+    ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                       "\"worker_cpu_affinity\" is not supported "
+                       "on this platform, ignored");
+#endif
+
+    return NGX_CONF_OK;
+}
+
+
+ngx_cpuset_t *
+ngx_get_cpu_affinity(ngx_uint_t n)
+{
+#if (NGX_HAVE_CPU_AFFINITY)
+    ngx_uint_t        i, j;
+    ngx_cpuset_t     *mask;
+    ngx_core_conf_t  *ccf;
+
+    static ngx_cpuset_t  result;
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
+                                           ngx_core_module);
+
+    if (ccf->cpu_affinity == NULL) {
+        return NULL;
+    }
+
+    if (ccf->cpu_affinity_auto) {
+        mask = &ccf->cpu_affinity[ccf->cpu_affinity_n - 1];
+
+        for (i = 0, j = n; /* void */ ; i++) {
+
+            if (CPU_ISSET(i % CPU_SETSIZE, mask) && j-- == 0) {
+                break;
+            }
+
+            if (i == CPU_SETSIZE && j == n) {
+                /* empty mask */
+                return NULL;
+            }
+
+            /* void */
+        }
+
+        CPU_ZERO(&result);
+        CPU_SET(i % CPU_SETSIZE, &result);
+
+        return &result;
+    }
+
+    if (ccf->cpu_affinity_n > n) {
+        return &ccf->cpu_affinity[n];
+    }
+
+    return &ccf->cpu_affinity[ccf->cpu_affinity_n - 1];
+
+#else
+
+    return NULL;
+
+#endif
+}
+
+
+static char *
+ngx_set_worker_processes(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_str_t        *value;
+    ngx_core_conf_t  *ccf;
+
+    ccf = (ngx_core_conf_t *) conf;
+
+    if (ccf->worker_processes != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    if (ngx_strcmp(value[1].data, "auto") == 0) {
+        ccf->worker_processes = ngx_ncpu;
+        return NGX_CONF_OK;
+    }
+
+    ccf->worker_processes = ngx_atoi(value[1].data, value[1].len);
+
+    if (ccf->worker_processes == NGX_ERROR) {
+        return "invalid value";
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_load_module(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+#if (NGX_HAVE_DLOPEN)
+    void                *handle;
+    char               **names, **order;
+    ngx_str_t           *value, file;
+    ngx_uint_t           i;
+    ngx_module_t        *module, **modules;
+    ngx_pool_cleanup_t  *cln;
+
+    if (cf->cycle->modules_used) {
+        return "is specified too late";
+    }
+
+    value = cf->args->elts;
+
+    file = value[1];
+
+    if (ngx_conf_full_name(cf->cycle, &file, 0) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    cln = ngx_pool_cleanup_add(cf->cycle->pool, 0);
+    if (cln == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    handle = ngx_dlopen(file.data);
+    if (handle == NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           ngx_dlopen_n " \"%s\" failed (%s)",
+                           file.data, ngx_dlerror());
+        return NGX_CONF_ERROR;
+    }
+
+    cln->handler = ngx_unload_module;
+    cln->data = handle;
+
+    modules = ngx_dlsym(handle, "ngx_modules");
+    if (modules == NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           ngx_dlsym_n " \"%V\", \"%s\" failed (%s)",
+                           &value[1], "ngx_modules", ngx_dlerror());
+        return NGX_CONF_ERROR;
+    }
+
+    names = ngx_dlsym(handle, "ngx_module_names");
+    if (names == NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           ngx_dlsym_n " \"%V\", \"%s\" failed (%s)",
+                           &value[1], "ngx_module_names", ngx_dlerror());
+        return NGX_CONF_ERROR;
+    }
+
+    order = ngx_dlsym(handle, "ngx_module_order");
+
+    for (i = 0; modules[i]; i++) {
+        module = modules[i];
+        module->name = names[i];
+
+        if (ngx_add_module(cf, &file, module, order) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_CORE, cf->log, 0, "module: %s i:%ui",
+                       module->name, module->index);
+    }
+
+    return NGX_CONF_OK;
+
+#else
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "\"load_module\" is not supported "
+                       "on this platform");
+    return NGX_CONF_ERROR;
+
+#endif
+}
+
+
+#if (NGX_HAVE_DLOPEN)
+
+static void
+ngx_unload_module(void *data)
+{
+    void  *handle = data;
+
+    if (ngx_dlclose(handle) != 0) {
+        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+                      ngx_dlclose_n " failed (%s)", ngx_dlerror());
+    }
+}
+
+#endif
diff --git a/nginx/src/core/nginx.h b/nginx/src/core/nginx.h
new file mode 100644 (file)
index 0000000..4fc92ac
--- /dev/null
@@ -0,0 +1,26 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGINX_H_INCLUDED_
+#define _NGINX_H_INCLUDED_
+
+
+#define nginx_version      1014002
+#define NGINX_VERSION      "1.14.2"
+#define NGINX_VER          "nginx/" NGINX_VERSION
+
+#ifdef NGX_BUILD
+#define NGINX_VER_BUILD    NGINX_VER " (" NGX_BUILD ")"
+#else
+#define NGINX_VER_BUILD    NGINX_VER
+#endif
+
+#define NGINX_VAR          "NGINX"
+#define NGX_OLDPID_EXT     ".oldbin"
+
+
+#endif /* _NGINX_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_array.c b/nginx/src/core/ngx_array.c
new file mode 100644 (file)
index 0000000..4ea226f
--- /dev/null
@@ -0,0 +1,141 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+ngx_array_t *
+ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size)
+{
+    ngx_array_t *a;
+
+    a = ngx_palloc(p, sizeof(ngx_array_t));
+    if (a == NULL) {
+        return NULL;
+    }
+
+    if (ngx_array_init(a, p, n, size) != NGX_OK) {
+        return NULL;
+    }
+
+    return a;
+}
+
+
+void
+ngx_array_destroy(ngx_array_t *a)
+{
+    ngx_pool_t  *p;
+
+    p = a->pool;
+
+    if ((u_char *) a->elts + a->size * a->nalloc == p->d.last) {
+        p->d.last -= a->size * a->nalloc;
+    }
+
+    if ((u_char *) a + sizeof(ngx_array_t) == p->d.last) {
+        p->d.last = (u_char *) a;
+    }
+}
+
+
+void *
+ngx_array_push(ngx_array_t *a)
+{
+    void        *elt, *new;
+    size_t       size;
+    ngx_pool_t  *p;
+
+    if (a->nelts == a->nalloc) {
+
+        /* the array is full */
+
+        size = a->size * a->nalloc;
+
+        p = a->pool;
+
+        if ((u_char *) a->elts + size == p->d.last
+            && p->d.last + a->size <= p->d.end)
+        {
+            /*
+             * the array allocation is the last in the pool
+             * and there is space for new allocation
+             */
+
+            p->d.last += a->size;
+            a->nalloc++;
+
+        } else {
+            /* allocate a new array */
+
+            new = ngx_palloc(p, 2 * size);
+            if (new == NULL) {
+                return NULL;
+            }
+
+            ngx_memcpy(new, a->elts, size);
+            a->elts = new;
+            a->nalloc *= 2;
+        }
+    }
+
+    elt = (u_char *) a->elts + a->size * a->nelts;
+    a->nelts++;
+
+    return elt;
+}
+
+
+void *
+ngx_array_push_n(ngx_array_t *a, ngx_uint_t n)
+{
+    void        *elt, *new;
+    size_t       size;
+    ngx_uint_t   nalloc;
+    ngx_pool_t  *p;
+
+    size = n * a->size;
+
+    if (a->nelts + n > a->nalloc) {
+
+        /* the array is full */
+
+        p = a->pool;
+
+        if ((u_char *) a->elts + a->size * a->nalloc == p->d.last
+            && p->d.last + size <= p->d.end)
+        {
+            /*
+             * the array allocation is the last in the pool
+             * and there is space for new allocation
+             */
+
+            p->d.last += size;
+            a->nalloc += n;
+
+        } else {
+            /* allocate a new array */
+
+            nalloc = 2 * ((n >= a->nalloc) ? n : a->nalloc);
+
+            new = ngx_palloc(p, nalloc * a->size);
+            if (new == NULL) {
+                return NULL;
+            }
+
+            ngx_memcpy(new, a->elts, a->nelts * a->size);
+            a->elts = new;
+            a->nalloc = nalloc;
+        }
+    }
+
+    elt = (u_char *) a->elts + a->size * a->nelts;
+    a->nelts += n;
+
+    return elt;
+}
diff --git a/nginx/src/core/ngx_array.h b/nginx/src/core/ngx_array.h
new file mode 100644 (file)
index 0000000..a0f2a74
--- /dev/null
@@ -0,0 +1,53 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_ARRAY_H_INCLUDED_
+#define _NGX_ARRAY_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef struct {
+    void        *elts;
+    ngx_uint_t   nelts;
+    size_t       size;
+    ngx_uint_t   nalloc;
+    ngx_pool_t  *pool;
+} ngx_array_t;
+
+
+ngx_array_t *ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size);
+void ngx_array_destroy(ngx_array_t *a);
+void *ngx_array_push(ngx_array_t *a);
+void *ngx_array_push_n(ngx_array_t *a, ngx_uint_t n);
+
+
+static ngx_inline ngx_int_t
+ngx_array_init(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size)
+{
+    /*
+     * set "array->nelts" before "array->elts", otherwise MSVC thinks
+     * that "array->nelts" may be used without having been initialized
+     */
+
+    array->nelts = 0;
+    array->size = size;
+    array->nalloc = n;
+    array->pool = pool;
+
+    array->elts = ngx_palloc(pool, n * size);
+    if (array->elts == NULL) {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+#endif /* _NGX_ARRAY_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_buf.c b/nginx/src/core/ngx_buf.c
new file mode 100644 (file)
index 0000000..1862a06
--- /dev/null
@@ -0,0 +1,313 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+ngx_buf_t *
+ngx_create_temp_buf(ngx_pool_t *pool, size_t size)
+{
+    ngx_buf_t *b;
+
+    b = ngx_calloc_buf(pool);
+    if (b == NULL) {
+        return NULL;
+    }
+
+    b->start = ngx_palloc(pool, size);
+    if (b->start == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_calloc_buf():
+     *
+     *     b->file_pos = 0;
+     *     b->file_last = 0;
+     *     b->file = NULL;
+     *     b->shadow = NULL;
+     *     b->tag = 0;
+     *     and flags
+     */
+
+    b->pos = b->start;
+    b->last = b->start;
+    b->end = b->last + size;
+    b->temporary = 1;
+
+    return b;
+}
+
+
+ngx_chain_t *
+ngx_alloc_chain_link(ngx_pool_t *pool)
+{
+    ngx_chain_t  *cl;
+
+    cl = pool->chain;
+
+    if (cl) {
+        pool->chain = cl->next;
+        return cl;
+    }
+
+    cl = ngx_palloc(pool, sizeof(ngx_chain_t));
+    if (cl == NULL) {
+        return NULL;
+    }
+
+    return cl;
+}
+
+
+ngx_chain_t *
+ngx_create_chain_of_bufs(ngx_pool_t *pool, ngx_bufs_t *bufs)
+{
+    u_char       *p;
+    ngx_int_t     i;
+    ngx_buf_t    *b;
+    ngx_chain_t  *chain, *cl, **ll;
+
+    p = ngx_palloc(pool, bufs->num * bufs->size);
+    if (p == NULL) {
+        return NULL;
+    }
+
+    ll = &chain;
+
+    for (i = 0; i < bufs->num; i++) {
+
+        b = ngx_calloc_buf(pool);
+        if (b == NULL) {
+            return NULL;
+        }
+
+        /*
+         * set by ngx_calloc_buf():
+         *
+         *     b->file_pos = 0;
+         *     b->file_last = 0;
+         *     b->file = NULL;
+         *     b->shadow = NULL;
+         *     b->tag = 0;
+         *     and flags
+         *
+         */
+
+        b->pos = p;
+        b->last = p;
+        b->temporary = 1;
+
+        b->start = p;
+        p += bufs->size;
+        b->end = p;
+
+        cl = ngx_alloc_chain_link(pool);
+        if (cl == NULL) {
+            return NULL;
+        }
+
+        cl->buf = b;
+        *ll = cl;
+        ll = &cl->next;
+    }
+
+    *ll = NULL;
+
+    return chain;
+}
+
+
+ngx_int_t
+ngx_chain_add_copy(ngx_pool_t *pool, ngx_chain_t **chain, ngx_chain_t *in)
+{
+    ngx_chain_t  *cl, **ll;
+
+    ll = chain;
+
+    for (cl = *chain; cl; cl = cl->next) {
+        ll = &cl->next;
+    }
+
+    while (in) {
+        cl = ngx_alloc_chain_link(pool);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+        cl->buf = in->buf;
+        *ll = cl;
+        ll = &cl->next;
+        in = in->next;
+    }
+
+    *ll = NULL;
+
+    return NGX_OK;
+}
+
+
+ngx_chain_t *
+ngx_chain_get_free_buf(ngx_pool_t *p, ngx_chain_t **free)
+{
+    ngx_chain_t  *cl;
+
+    if (*free) {
+        cl = *free;
+        *free = cl->next;
+        cl->next = NULL;
+        return cl;
+    }
+
+    cl = ngx_alloc_chain_link(p);
+    if (cl == NULL) {
+        return NULL;
+    }
+
+    cl->buf = ngx_calloc_buf(p);
+    if (cl->buf == NULL) {
+        return NULL;
+    }
+
+    cl->next = NULL;
+
+    return cl;
+}
+
+
+void
+ngx_chain_update_chains(ngx_pool_t *p, ngx_chain_t **free, ngx_chain_t **busy,
+    ngx_chain_t **out, ngx_buf_tag_t tag)
+{
+    ngx_chain_t  *cl;
+
+    if (*out) {
+        if (*busy == NULL) {
+            *busy = *out;
+
+        } else {
+            for (cl = *busy; cl->next; cl = cl->next) { /* void */ }
+
+            cl->next = *out;
+        }
+
+        *out = NULL;
+    }
+
+    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;
+        }
+
+        cl->buf->pos = cl->buf->start;
+        cl->buf->last = cl->buf->start;
+
+        *busy = cl->next;
+        cl->next = *free;
+        *free = cl;
+    }
+}
+
+
+off_t
+ngx_chain_coalesce_file(ngx_chain_t **in, off_t limit)
+{
+    off_t         total, size, aligned, fprev;
+    ngx_fd_t      fd;
+    ngx_chain_t  *cl;
+
+    total = 0;
+
+    cl = *in;
+    fd = cl->buf->file->fd;
+
+    do {
+        size = cl->buf->file_last - cl->buf->file_pos;
+
+        if (size > limit - total) {
+            size = limit - total;
+
+            aligned = (cl->buf->file_pos + size + ngx_pagesize - 1)
+                       & ~((off_t) ngx_pagesize - 1);
+
+            if (aligned <= cl->buf->file_last) {
+                size = aligned - cl->buf->file_pos;
+            }
+
+            total += size;
+            break;
+        }
+
+        total += size;
+        fprev = cl->buf->file_pos + size;
+        cl = cl->next;
+
+    } while (cl
+             && cl->buf->in_file
+             && total < limit
+             && fd == cl->buf->file->fd
+             && fprev == cl->buf->file_pos);
+
+    *in = cl;
+
+    return total;
+}
+
+
+ngx_chain_t *
+ngx_chain_update_sent(ngx_chain_t *in, off_t sent)
+{
+    off_t  size;
+
+    for ( /* void */ ; in; in = in->next) {
+
+        if (ngx_buf_special(in->buf)) {
+            continue;
+        }
+
+        if (sent == 0) {
+            break;
+        }
+
+        size = ngx_buf_size(in->buf);
+
+        if (sent >= size) {
+            sent -= size;
+
+            if (ngx_buf_in_memory(in->buf)) {
+                in->buf->pos = in->buf->last;
+            }
+
+            if (in->buf->in_file) {
+                in->buf->file_pos = in->buf->file_last;
+            }
+
+            continue;
+        }
+
+        if (ngx_buf_in_memory(in->buf)) {
+            in->buf->pos += (size_t) sent;
+        }
+
+        if (in->buf->in_file) {
+            in->buf->file_pos += sent;
+        }
+
+        break;
+    }
+
+    return in;
+}
diff --git a/nginx/src/core/ngx_buf.h b/nginx/src/core/ngx_buf.h
new file mode 100644 (file)
index 0000000..12781a7
--- /dev/null
@@ -0,0 +1,170 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_BUF_H_INCLUDED_
+#define _NGX_BUF_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef void *            ngx_buf_tag_t;
+
+typedef struct ngx_buf_s  ngx_buf_t;
+
+struct ngx_buf_s {
+    u_char          *pos;
+    u_char          *last;
+    off_t            file_pos;
+    off_t            file_last;
+
+    u_char          *start;         /* start of buffer */
+    u_char          *end;           /* end of buffer */
+    ngx_buf_tag_t    tag;
+    ngx_file_t      *file;
+    ngx_buf_t       *shadow;
+
+
+    /* the buf's content could be changed */
+    unsigned         temporary:1;
+
+    /*
+     * the buf's content is in a memory cache or in a read only memory
+     * and must not be changed
+     */
+    unsigned         memory:1;
+
+    /* the buf's content is mmap()ed and must not be changed */
+    unsigned         mmap:1;
+
+    unsigned         recycled:1;
+    unsigned         in_file:1;
+    unsigned         flush:1;
+    unsigned         sync:1;
+    unsigned         last_buf:1;
+    unsigned         last_in_chain:1;
+
+    unsigned         last_shadow:1;
+    unsigned         temp_file:1;
+
+    /* STUB */ int   num;
+};
+
+
+struct ngx_chain_s {
+    ngx_buf_t    *buf;
+    ngx_chain_t  *next;
+};
+
+
+typedef struct {
+    ngx_int_t    num;
+    size_t       size;
+} ngx_bufs_t;
+
+
+typedef struct ngx_output_chain_ctx_s  ngx_output_chain_ctx_t;
+
+typedef ngx_int_t (*ngx_output_chain_filter_pt)(void *ctx, ngx_chain_t *in);
+
+typedef void (*ngx_output_chain_aio_pt)(ngx_output_chain_ctx_t *ctx,
+    ngx_file_t *file);
+
+struct ngx_output_chain_ctx_s {
+    ngx_buf_t                   *buf;
+    ngx_chain_t                 *in;
+    ngx_chain_t                 *free;
+    ngx_chain_t                 *busy;
+
+    unsigned                     sendfile:1;
+    unsigned                     directio:1;
+    unsigned                     unaligned:1;
+    unsigned                     need_in_memory:1;
+    unsigned                     need_in_temp:1;
+    unsigned                     aio:1;
+
+#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)
+    ngx_int_t                  (*thread_handler)(ngx_thread_task_t *task,
+                                                 ngx_file_t *file);
+    ngx_thread_task_t           *thread_task;
+#endif
+
+    off_t                        alignment;
+
+    ngx_pool_t                  *pool;
+    ngx_int_t                    allocated;
+    ngx_bufs_t                   bufs;
+    ngx_buf_tag_t                tag;
+
+    ngx_output_chain_filter_pt   output_filter;
+    void                        *filter_ctx;
+};
+
+
+typedef struct {
+    ngx_chain_t                 *out;
+    ngx_chain_t                **last;
+    ngx_connection_t            *connection;
+    ngx_pool_t                  *pool;
+    off_t                        limit;
+} ngx_chain_writer_ctx_t;
+
+
+#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_special(b)                                                   \
+    ((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)
+
+#define ngx_buf_size(b)                                                      \
+    (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);
+
+
+#define ngx_alloc_buf(pool)  ngx_palloc(pool, sizeof(ngx_buf_t))
+#define ngx_calloc_buf(pool) ngx_pcalloc(pool, sizeof(ngx_buf_t))
+
+ngx_chain_t *ngx_alloc_chain_link(ngx_pool_t *pool);
+#define ngx_free_chain(pool, cl)                                             \
+    cl->next = pool->chain;                                                  \
+    pool->chain = cl
+
+
+
+ngx_int_t ngx_output_chain(ngx_output_chain_ctx_t *ctx, ngx_chain_t *in);
+ngx_int_t ngx_chain_writer(void *ctx, ngx_chain_t *in);
+
+ngx_int_t ngx_chain_add_copy(ngx_pool_t *pool, ngx_chain_t **chain,
+    ngx_chain_t *in);
+ngx_chain_t *ngx_chain_get_free_buf(ngx_pool_t *p, ngx_chain_t **free);
+void ngx_chain_update_chains(ngx_pool_t *p, ngx_chain_t **free,
+    ngx_chain_t **busy, ngx_chain_t **out, ngx_buf_tag_t tag);
+
+off_t ngx_chain_coalesce_file(ngx_chain_t **in, off_t limit);
+
+ngx_chain_t *ngx_chain_update_sent(ngx_chain_t *in, off_t sent);
+
+#endif /* _NGX_BUF_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_conf_file.c b/nginx/src/core/ngx_conf_file.c
new file mode 100644 (file)
index 0000000..ba454de
--- /dev/null
@@ -0,0 +1,1485 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+#define NGX_CONF_BUFFER  4096
+
+static ngx_int_t ngx_conf_add_dump(ngx_conf_t *cf, ngx_str_t *filename);
+static ngx_int_t ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last);
+static ngx_int_t ngx_conf_read_token(ngx_conf_t *cf);
+static void ngx_conf_flush_files(ngx_cycle_t *cycle);
+
+
+static ngx_command_t  ngx_conf_commands[] = {
+
+    { ngx_string("include"),
+      NGX_ANY_CONF|NGX_CONF_TAKE1,
+      ngx_conf_include,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+ngx_module_t  ngx_conf_module = {
+    NGX_MODULE_V1,
+    NULL,                                  /* module context */
+    ngx_conf_commands,                     /* module directives */
+    NGX_CONF_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    ngx_conf_flush_files,                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+/* The eight fixed arguments */
+
+static ngx_uint_t argument_number[] = {
+    NGX_CONF_NOARGS,
+    NGX_CONF_TAKE1,
+    NGX_CONF_TAKE2,
+    NGX_CONF_TAKE3,
+    NGX_CONF_TAKE4,
+    NGX_CONF_TAKE5,
+    NGX_CONF_TAKE6,
+    NGX_CONF_TAKE7
+};
+
+
+char *
+ngx_conf_param(ngx_conf_t *cf)
+{
+    char             *rv;
+    ngx_str_t        *param;
+    ngx_buf_t         b;
+    ngx_conf_file_t   conf_file;
+
+    param = &cf->cycle->conf_param;
+
+    if (param->len == 0) {
+        return NGX_CONF_OK;
+    }
+
+    ngx_memzero(&conf_file, sizeof(ngx_conf_file_t));
+
+    ngx_memzero(&b, sizeof(ngx_buf_t));
+
+    b.start = param->data;
+    b.pos = param->data;
+    b.last = param->data + param->len;
+    b.end = b.last;
+    b.temporary = 1;
+
+    conf_file.file.fd = NGX_INVALID_FILE;
+    conf_file.file.name.data = NULL;
+    conf_file.line = 0;
+
+    cf->conf_file = &conf_file;
+    cf->conf_file->buffer = &b;
+
+    rv = ngx_conf_parse(cf, NULL);
+
+    cf->conf_file = NULL;
+
+    return rv;
+}
+
+
+static ngx_int_t
+ngx_conf_add_dump(ngx_conf_t *cf, ngx_str_t *filename)
+{
+    off_t             size;
+    u_char           *p;
+    uint32_t          hash;
+    ngx_buf_t        *buf;
+    ngx_str_node_t   *sn;
+    ngx_conf_dump_t  *cd;
+
+    hash = ngx_crc32_long(filename->data, filename->len);
+
+    sn = ngx_str_rbtree_lookup(&cf->cycle->config_dump_rbtree, filename, hash);
+
+    if (sn) {
+        cf->conf_file->dump = NULL;
+        return NGX_OK;
+    }
+
+    p = ngx_pstrdup(cf->cycle->pool, filename);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    cd = ngx_array_push(&cf->cycle->config_dump);
+    if (cd == NULL) {
+        return NGX_ERROR;
+    }
+
+    size = ngx_file_size(&cf->conf_file->file.info);
+
+    buf = ngx_create_temp_buf(cf->cycle->pool, (size_t) size);
+    if (buf == NULL) {
+        return NGX_ERROR;
+    }
+
+    cd->name.data = p;
+    cd->name.len = filename->len;
+    cd->buffer = buf;
+
+    cf->conf_file->dump = buf;
+
+    sn = ngx_palloc(cf->temp_pool, sizeof(ngx_str_node_t));
+    if (sn == NULL) {
+        return NGX_ERROR;
+    }
+
+    sn->node.key = hash;
+    sn->str = cd->name;
+
+    ngx_rbtree_insert(&cf->cycle->config_dump_rbtree, &sn->node);
+
+    return NGX_OK;
+}
+
+
+char *
+ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename)
+{
+    char             *rv;
+    ngx_fd_t          fd;
+    ngx_int_t         rc;
+    ngx_buf_t         buf;
+    ngx_conf_file_t  *prev, conf_file;
+    enum {
+        parse_file = 0,
+        parse_block,
+        parse_param
+    } type;
+
+#if (NGX_SUPPRESS_WARN)
+    fd = NGX_INVALID_FILE;
+    prev = NULL;
+#endif
+
+    if (filename) {
+
+        /* open configuration file */
+
+        fd = ngx_open_file(filename->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
+
+        if (fd == NGX_INVALID_FILE) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
+                               ngx_open_file_n " \"%s\" failed",
+                               filename->data);
+            return NGX_CONF_ERROR;
+        }
+
+        prev = cf->conf_file;
+
+        cf->conf_file = &conf_file;
+
+        if (ngx_fd_info(fd, &cf->conf_file->file.info) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
+                          ngx_fd_info_n " \"%s\" failed", filename->data);
+        }
+
+        cf->conf_file->buffer = &buf;
+
+        buf.start = ngx_alloc(NGX_CONF_BUFFER, cf->log);
+        if (buf.start == NULL) {
+            goto failed;
+        }
+
+        buf.pos = buf.start;
+        buf.last = buf.start;
+        buf.end = buf.last + NGX_CONF_BUFFER;
+        buf.temporary = 1;
+
+        cf->conf_file->file.fd = fd;
+        cf->conf_file->file.name.len = filename->len;
+        cf->conf_file->file.name.data = filename->data;
+        cf->conf_file->file.offset = 0;
+        cf->conf_file->file.log = cf->log;
+        cf->conf_file->line = 1;
+
+        type = parse_file;
+
+        if (ngx_dump_config
+#if (NGX_DEBUG)
+            || 1
+#endif
+           )
+        {
+            if (ngx_conf_add_dump(cf, filename) != NGX_OK) {
+                goto failed;
+            }
+
+        } else {
+            cf->conf_file->dump = NULL;
+        }
+
+    } else if (cf->conf_file->file.fd != NGX_INVALID_FILE) {
+
+        type = parse_block;
+
+    } else {
+        type = parse_param;
+    }
+
+
+    for ( ;; ) {
+        rc = ngx_conf_read_token(cf);
+
+        /*
+         * ngx_conf_read_token() may return
+         *
+         *    NGX_ERROR             there is error
+         *    NGX_OK                the token terminated by ";" was found
+         *    NGX_CONF_BLOCK_START  the token terminated by "{" was found
+         *    NGX_CONF_BLOCK_DONE   the "}" was found
+         *    NGX_CONF_FILE_DONE    the configuration file is done
+         */
+
+        if (rc == NGX_ERROR) {
+            goto done;
+        }
+
+        if (rc == NGX_CONF_BLOCK_DONE) {
+
+            if (type != parse_block) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"}\"");
+                goto failed;
+            }
+
+            goto done;
+        }
+
+        if (rc == NGX_CONF_FILE_DONE) {
+
+            if (type == parse_block) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "unexpected end of file, expecting \"}\"");
+                goto failed;
+            }
+
+            goto done;
+        }
+
+        if (rc == NGX_CONF_BLOCK_START) {
+
+            if (type == parse_param) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "block directives are not supported "
+                                   "in -g option");
+                goto failed;
+            }
+        }
+
+        /* rc == NGX_OK || rc == NGX_CONF_BLOCK_START */
+
+        if (cf->handler) {
+
+            /*
+             * the custom handler, i.e., that is used in the http's
+             * "types { ... }" directive
+             */
+
+            if (rc == NGX_CONF_BLOCK_START) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"{\"");
+                goto failed;
+            }
+
+            rv = (*cf->handler)(cf, NULL, cf->handler_conf);
+            if (rv == NGX_CONF_OK) {
+                continue;
+            }
+
+            if (rv == NGX_CONF_ERROR) {
+                goto failed;
+            }
+
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, rv);
+
+            goto failed;
+        }
+
+
+        rc = ngx_conf_handler(cf, rc);
+
+        if (rc == NGX_ERROR) {
+            goto failed;
+        }
+    }
+
+failed:
+
+    rc = NGX_ERROR;
+
+done:
+
+    if (filename) {
+        if (cf->conf_file->buffer->start) {
+            ngx_free(cf->conf_file->buffer->start);
+        }
+
+        if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
+                          ngx_close_file_n " %s failed",
+                          filename->data);
+            rc = NGX_ERROR;
+        }
+
+        cf->conf_file = prev;
+    }
+
+    if (rc == NGX_ERROR) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)
+{
+    char           *rv;
+    void           *conf, **confp;
+    ngx_uint_t      i, found;
+    ngx_str_t      *name;
+    ngx_command_t  *cmd;
+
+    name = cf->args->elts;
+
+    found = 0;
+
+    for (i = 0; cf->cycle->modules[i]; i++) {
+
+        cmd = cf->cycle->modules[i]->commands;
+        if (cmd == NULL) {
+            continue;
+        }
+
+        for ( /* void */ ; cmd->name.len; cmd++) {
+
+            if (name->len != cmd->name.len) {
+                continue;
+            }
+
+            if (ngx_strcmp(name->data, cmd->name.data) != 0) {
+                continue;
+            }
+
+            found = 1;
+
+            if (cf->cycle->modules[i]->type != NGX_CONF_MODULE
+                && cf->cycle->modules[i]->type != cf->module_type)
+            {
+                continue;
+            }
+
+            /* is the directive's location right ? */
+
+            if (!(cmd->type & cf->cmd_type)) {
+                continue;
+            }
+
+            if (!(cmd->type & NGX_CONF_BLOCK) && last != NGX_OK) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                  "directive \"%s\" is not terminated by \";\"",
+                                  name->data);
+                return NGX_ERROR;
+            }
+
+            if ((cmd->type & NGX_CONF_BLOCK) && last != NGX_CONF_BLOCK_START) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "directive \"%s\" has no opening \"{\"",
+                                   name->data);
+                return NGX_ERROR;
+            }
+
+            /* is the directive's argument count right ? */
+
+            if (!(cmd->type & NGX_CONF_ANY)) {
+
+                if (cmd->type & NGX_CONF_FLAG) {
+
+                    if (cf->args->nelts != 2) {
+                        goto invalid;
+                    }
+
+                } else if (cmd->type & NGX_CONF_1MORE) {
+
+                    if (cf->args->nelts < 2) {
+                        goto invalid;
+                    }
+
+                } else if (cmd->type & NGX_CONF_2MORE) {
+
+                    if (cf->args->nelts < 3) {
+                        goto invalid;
+                    }
+
+                } else if (cf->args->nelts > NGX_CONF_MAX_ARGS) {
+
+                    goto invalid;
+
+                } else if (!(cmd->type & argument_number[cf->args->nelts - 1]))
+                {
+                    goto invalid;
+                }
+            }
+
+            /* set up the directive's configuration context */
+
+            conf = NULL;
+
+            if (cmd->type & NGX_DIRECT_CONF) {
+                conf = ((void **) cf->ctx)[cf->cycle->modules[i]->index];
+
+            } else if (cmd->type & NGX_MAIN_CONF) {
+                conf = &(((void **) cf->ctx)[cf->cycle->modules[i]->index]);
+
+            } else if (cf->ctx) {
+                confp = *(void **) ((char *) cf->ctx + cmd->conf);
+
+                if (confp) {
+                    conf = confp[cf->cycle->modules[i]->ctx_index];
+                }
+            }
+
+            rv = cmd->set(cf, cmd, conf);
+
+            if (rv == NGX_CONF_OK) {
+                return NGX_OK;
+            }
+
+            if (rv == NGX_CONF_ERROR) {
+                return NGX_ERROR;
+            }
+
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "\"%s\" directive %s", name->data, rv);
+
+            return NGX_ERROR;
+        }
+    }
+
+    if (found) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"%s\" directive is not allowed here", name->data);
+
+        return NGX_ERROR;
+    }
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "unknown directive \"%s\"", name->data);
+
+    return NGX_ERROR;
+
+invalid:
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "invalid number of arguments in \"%s\" directive",
+                       name->data);
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_conf_read_token(ngx_conf_t *cf)
+{
+    u_char      *start, ch, *src, *dst;
+    off_t        file_size;
+    size_t       len;
+    ssize_t      n, size;
+    ngx_uint_t   found, need_space, last_space, sharp_comment, variable;
+    ngx_uint_t   quoted, s_quoted, d_quoted, start_line;
+    ngx_str_t   *word;
+    ngx_buf_t   *b, *dump;
+
+    found = 0;
+    need_space = 0;
+    last_space = 1;
+    sharp_comment = 0;
+    variable = 0;
+    quoted = 0;
+    s_quoted = 0;
+    d_quoted = 0;
+
+    cf->args->nelts = 0;
+    b = cf->conf_file->buffer;
+    dump = cf->conf_file->dump;
+    start = b->pos;
+    start_line = cf->conf_file->line;
+
+    file_size = ngx_file_size(&cf->conf_file->file.info);
+
+    for ( ;; ) {
+
+        if (b->pos >= b->last) {
+
+            if (cf->conf_file->file.offset >= file_size) {
+
+                if (cf->args->nelts > 0 || !last_space) {
+
+                    if (cf->conf_file->file.fd == NGX_INVALID_FILE) {
+                        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                           "unexpected end of parameter, "
+                                           "expecting \";\"");
+                        return NGX_ERROR;
+                    }
+
+                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                  "unexpected end of file, "
+                                  "expecting \";\" or \"}\"");
+                    return NGX_ERROR;
+                }
+
+                return NGX_CONF_FILE_DONE;
+            }
+
+            len = b->pos - start;
+
+            if (len == NGX_CONF_BUFFER) {
+                cf->conf_file->line = start_line;
+
+                if (d_quoted) {
+                    ch = '"';
+
+                } else if (s_quoted) {
+                    ch = '\'';
+
+                } else {
+                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                       "too long parameter \"%*s...\" started",
+                                       10, start);
+                    return NGX_ERROR;
+                }
+
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "too long parameter, probably "
+                                   "missing terminating \"%c\" character", ch);
+                return NGX_ERROR;
+            }
+
+            if (len) {
+                ngx_memmove(b->start, start, len);
+            }
+
+            size = (ssize_t) (file_size - cf->conf_file->file.offset);
+
+            if (size > b->end - (b->start + len)) {
+                size = b->end - (b->start + len);
+            }
+
+            n = ngx_read_file(&cf->conf_file->file, b->start + len, size,
+                              cf->conf_file->file.offset);
+
+            if (n == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            if (n != size) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   ngx_read_file_n " returned "
+                                   "only %z bytes instead of %z",
+                                   n, size);
+                return NGX_ERROR;
+            }
+
+            b->pos = b->start + len;
+            b->last = b->pos + n;
+            start = b->start;
+
+            if (dump) {
+                dump->last = ngx_cpymem(dump->last, b->pos, size);
+            }
+        }
+
+        ch = *b->pos++;
+
+        if (ch == LF) {
+            cf->conf_file->line++;
+
+            if (sharp_comment) {
+                sharp_comment = 0;
+            }
+        }
+
+        if (sharp_comment) {
+            continue;
+        }
+
+        if (quoted) {
+            quoted = 0;
+            continue;
+        }
+
+        if (need_space) {
+            if (ch == ' ' || ch == '\t' || ch == CR || ch == LF) {
+                last_space = 1;
+                need_space = 0;
+                continue;
+            }
+
+            if (ch == ';') {
+                return NGX_OK;
+            }
+
+            if (ch == '{') {
+                return NGX_CONF_BLOCK_START;
+            }
+
+            if (ch == ')') {
+                last_space = 1;
+                need_space = 0;
+
+            } else {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "unexpected \"%c\"", ch);
+                return NGX_ERROR;
+            }
+        }
+
+        if (last_space) {
+            if (ch == ' ' || ch == '\t' || ch == CR || ch == LF) {
+                continue;
+            }
+
+            start = b->pos - 1;
+            start_line = cf->conf_file->line;
+
+            switch (ch) {
+
+            case ';':
+            case '{':
+                if (cf->args->nelts == 0) {
+                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                       "unexpected \"%c\"", ch);
+                    return NGX_ERROR;
+                }
+
+                if (ch == '{') {
+                    return NGX_CONF_BLOCK_START;
+                }
+
+                return NGX_OK;
+
+            case '}':
+                if (cf->args->nelts != 0) {
+                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                       "unexpected \"}\"");
+                    return NGX_ERROR;
+                }
+
+                return NGX_CONF_BLOCK_DONE;
+
+            case '#':
+                sharp_comment = 1;
+                continue;
+
+            case '\\':
+                quoted = 1;
+                last_space = 0;
+                continue;
+
+            case '"':
+                start++;
+                d_quoted = 1;
+                last_space = 0;
+                continue;
+
+            case '\'':
+                start++;
+                s_quoted = 1;
+                last_space = 0;
+                continue;
+
+            case '$':
+                variable = 1;
+                last_space = 0;
+                continue;
+
+            default:
+                last_space = 0;
+            }
+
+        } else {
+            if (ch == '{' && variable) {
+                continue;
+            }
+
+            variable = 0;
+
+            if (ch == '\\') {
+                quoted = 1;
+                continue;
+            }
+
+            if (ch == '$') {
+                variable = 1;
+                continue;
+            }
+
+            if (d_quoted) {
+                if (ch == '"') {
+                    d_quoted = 0;
+                    need_space = 1;
+                    found = 1;
+                }
+
+            } else if (s_quoted) {
+                if (ch == '\'') {
+                    s_quoted = 0;
+                    need_space = 1;
+                    found = 1;
+                }
+
+            } else if (ch == ' ' || ch == '\t' || ch == CR || ch == LF
+                       || ch == ';' || ch == '{')
+            {
+                last_space = 1;
+                found = 1;
+            }
+
+            if (found) {
+                word = ngx_array_push(cf->args);
+                if (word == NULL) {
+                    return NGX_ERROR;
+                }
+
+                word->data = ngx_pnalloc(cf->pool, b->pos - 1 - start + 1);
+                if (word->data == NULL) {
+                    return NGX_ERROR;
+                }
+
+                for (dst = word->data, src = start, len = 0;
+                     src < b->pos - 1;
+                     len++)
+                {
+                    if (*src == '\\') {
+                        switch (src[1]) {
+                        case '"':
+                        case '\'':
+                        case '\\':
+                            src++;
+                            break;
+
+                        case 't':
+                            *dst++ = '\t';
+                            src += 2;
+                            continue;
+
+                        case 'r':
+                            *dst++ = '\r';
+                            src += 2;
+                            continue;
+
+                        case 'n':
+                            *dst++ = '\n';
+                            src += 2;
+                            continue;
+                        }
+
+                    }
+                    *dst++ = *src++;
+                }
+                *dst = '\0';
+                word->len = len;
+
+                if (ch == ';') {
+                    return NGX_OK;
+                }
+
+                if (ch == '{') {
+                    return NGX_CONF_BLOCK_START;
+                }
+
+                found = 0;
+            }
+        }
+    }
+}
+
+
+char *
+ngx_conf_include(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char        *rv;
+    ngx_int_t    n;
+    ngx_str_t   *value, file, name;
+    ngx_glob_t   gl;
+
+    value = cf->args->elts;
+    file = value[1];
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data);
+
+    if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (strpbrk((char *) file.data, "*?[") == NULL) {
+
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data);
+
+        return ngx_conf_parse(cf, &file);
+    }
+
+    ngx_memzero(&gl, sizeof(ngx_glob_t));
+
+    gl.pattern = file.data;
+    gl.log = cf->log;
+    gl.test = 1;
+
+    if (ngx_open_glob(&gl) != NGX_OK) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
+                           ngx_open_glob_n " \"%s\" failed", file.data);
+        return NGX_CONF_ERROR;
+    }
+
+    rv = NGX_CONF_OK;
+
+    for ( ;; ) {
+        n = ngx_read_glob(&gl, &name);
+
+        if (n != NGX_OK) {
+            break;
+        }
+
+        file.len = name.len++;
+        file.data = ngx_pstrdup(cf->pool, &name);
+        if (file.data == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data);
+
+        rv = ngx_conf_parse(cf, &file);
+
+        if (rv != NGX_CONF_OK) {
+            break;
+        }
+    }
+
+    ngx_close_glob(&gl);
+
+    return rv;
+}
+
+
+ngx_int_t
+ngx_conf_full_name(ngx_cycle_t *cycle, ngx_str_t *name, ngx_uint_t conf_prefix)
+{
+    ngx_str_t  *prefix;
+
+    prefix = conf_prefix ? &cycle->conf_prefix : &cycle->prefix;
+
+    return ngx_get_full_name(cycle->pool, prefix, name);
+}
+
+
+ngx_open_file_t *
+ngx_conf_open_file(ngx_cycle_t *cycle, ngx_str_t *name)
+{
+    ngx_str_t         full;
+    ngx_uint_t        i;
+    ngx_list_part_t  *part;
+    ngx_open_file_t  *file;
+
+#if (NGX_SUPPRESS_WARN)
+    ngx_str_null(&full);
+#endif
+
+    if (name->len) {
+        full = *name;
+
+        if (ngx_conf_full_name(cycle, &full, 0) != NGX_OK) {
+            return NULL;
+        }
+
+        part = &cycle->open_files.part;
+        file = part->elts;
+
+        for (i = 0; /* void */ ; i++) {
+
+            if (i >= part->nelts) {
+                if (part->next == NULL) {
+                    break;
+                }
+                part = part->next;
+                file = part->elts;
+                i = 0;
+            }
+
+            if (full.len != file[i].name.len) {
+                continue;
+            }
+
+            if (ngx_strcmp(full.data, file[i].name.data) == 0) {
+                return &file[i];
+            }
+        }
+    }
+
+    file = ngx_list_push(&cycle->open_files);
+    if (file == NULL) {
+        return NULL;
+    }
+
+    if (name->len) {
+        file->fd = NGX_INVALID_FILE;
+        file->name = full;
+
+    } else {
+        file->fd = ngx_stderr;
+        file->name = *name;
+    }
+
+    file->flush = NULL;
+    file->data = NULL;
+
+    return file;
+}
+
+
+static void
+ngx_conf_flush_files(ngx_cycle_t *cycle)
+{
+    ngx_uint_t        i;
+    ngx_list_part_t  *part;
+    ngx_open_file_t  *file;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_CORE, cycle->log, 0, "flush files");
+
+    part = &cycle->open_files.part;
+    file = part->elts;
+
+    for (i = 0; /* void */ ; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+            part = part->next;
+            file = part->elts;
+            i = 0;
+        }
+
+        if (file[i].flush) {
+            file[i].flush(&file[i], cycle->log);
+        }
+    }
+}
+
+
+void ngx_cdecl
+ngx_conf_log_error(ngx_uint_t level, ngx_conf_t *cf, ngx_err_t err,
+    const char *fmt, ...)
+{
+    u_char   errstr[NGX_MAX_CONF_ERRSTR], *p, *last;
+    va_list  args;
+
+    last = errstr + NGX_MAX_CONF_ERRSTR;
+
+    va_start(args, fmt);
+    p = ngx_vslprintf(errstr, last, fmt, args);
+    va_end(args);
+
+    if (err) {
+        p = ngx_log_errno(p, last, err);
+    }
+
+    if (cf->conf_file == NULL) {
+        ngx_log_error(level, cf->log, 0, "%*s", p - errstr, errstr);
+        return;
+    }
+
+    if (cf->conf_file->file.fd == NGX_INVALID_FILE) {
+        ngx_log_error(level, cf->log, 0, "%*s in command line",
+                      p - errstr, errstr);
+        return;
+    }
+
+    ngx_log_error(level, cf->log, 0, "%*s in %s:%ui",
+                  p - errstr, errstr,
+                  cf->conf_file->file.name.data, cf->conf_file->line);
+}
+
+
+char *
+ngx_conf_set_flag_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ngx_str_t        *value;
+    ngx_flag_t       *fp;
+    ngx_conf_post_t  *post;
+
+    fp = (ngx_flag_t *) (p + cmd->offset);
+
+    if (*fp != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    if (ngx_strcasecmp(value[1].data, (u_char *) "on") == 0) {
+        *fp = 1;
+
+    } else if (ngx_strcasecmp(value[1].data, (u_char *) "off") == 0) {
+        *fp = 0;
+
+    } else {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                     "invalid value \"%s\" in \"%s\" directive, "
+                     "it must be \"on\" or \"off\"",
+                     value[1].data, cmd->name.data);
+        return NGX_CONF_ERROR;
+    }
+
+    if (cmd->post) {
+        post = cmd->post;
+        return post->post_handler(cf, post, fp);
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *
+ngx_conf_set_str_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ngx_str_t        *field, *value;
+    ngx_conf_post_t  *post;
+
+    field = (ngx_str_t *) (p + cmd->offset);
+
+    if (field->data) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    *field = value[1];
+
+    if (cmd->post) {
+        post = cmd->post;
+        return post->post_handler(cf, post, field);
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *
+ngx_conf_set_str_array_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ngx_str_t         *value, *s;
+    ngx_array_t      **a;
+    ngx_conf_post_t   *post;
+
+    a = (ngx_array_t **) (p + cmd->offset);
+
+    if (*a == NGX_CONF_UNSET_PTR) {
+        *a = ngx_array_create(cf->pool, 4, sizeof(ngx_str_t));
+        if (*a == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    s = ngx_array_push(*a);
+    if (s == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    value = cf->args->elts;
+
+    *s = value[1];
+
+    if (cmd->post) {
+        post = cmd->post;
+        return post->post_handler(cf, post, s);
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *
+ngx_conf_set_keyval_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ngx_str_t         *value;
+    ngx_array_t      **a;
+    ngx_keyval_t      *kv;
+    ngx_conf_post_t   *post;
+
+    a = (ngx_array_t **) (p + cmd->offset);
+
+    if (*a == NULL) {
+        *a = ngx_array_create(cf->pool, 4, sizeof(ngx_keyval_t));
+        if (*a == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    kv = ngx_array_push(*a);
+    if (kv == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    value = cf->args->elts;
+
+    kv->key = value[1];
+    kv->value = value[2];
+
+    if (cmd->post) {
+        post = cmd->post;
+        return post->post_handler(cf, post, kv);
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *
+ngx_conf_set_num_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ngx_int_t        *np;
+    ngx_str_t        *value;
+    ngx_conf_post_t  *post;
+
+
+    np = (ngx_int_t *) (p + cmd->offset);
+
+    if (*np != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+    *np = ngx_atoi(value[1].data, value[1].len);
+    if (*np == NGX_ERROR) {
+        return "invalid number";
+    }
+
+    if (cmd->post) {
+        post = cmd->post;
+        return post->post_handler(cf, post, np);
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *
+ngx_conf_set_size_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    size_t           *sp;
+    ngx_str_t        *value;
+    ngx_conf_post_t  *post;
+
+
+    sp = (size_t *) (p + cmd->offset);
+    if (*sp != NGX_CONF_UNSET_SIZE) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    *sp = ngx_parse_size(&value[1]);
+    if (*sp == (size_t) NGX_ERROR) {
+        return "invalid value";
+    }
+
+    if (cmd->post) {
+        post = cmd->post;
+        return post->post_handler(cf, post, sp);
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *
+ngx_conf_set_off_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    off_t            *op;
+    ngx_str_t        *value;
+    ngx_conf_post_t  *post;
+
+
+    op = (off_t *) (p + cmd->offset);
+    if (*op != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    *op = ngx_parse_offset(&value[1]);
+    if (*op == (off_t) NGX_ERROR) {
+        return "invalid value";
+    }
+
+    if (cmd->post) {
+        post = cmd->post;
+        return post->post_handler(cf, post, op);
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *
+ngx_conf_set_msec_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ngx_msec_t       *msp;
+    ngx_str_t        *value;
+    ngx_conf_post_t  *post;
+
+
+    msp = (ngx_msec_t *) (p + cmd->offset);
+    if (*msp != NGX_CONF_UNSET_MSEC) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    *msp = ngx_parse_time(&value[1], 0);
+    if (*msp == (ngx_msec_t) NGX_ERROR) {
+        return "invalid value";
+    }
+
+    if (cmd->post) {
+        post = cmd->post;
+        return post->post_handler(cf, post, msp);
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *
+ngx_conf_set_sec_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    time_t           *sp;
+    ngx_str_t        *value;
+    ngx_conf_post_t  *post;
+
+
+    sp = (time_t *) (p + cmd->offset);
+    if (*sp != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    *sp = ngx_parse_time(&value[1], 1);
+    if (*sp == (time_t) NGX_ERROR) {
+        return "invalid value";
+    }
+
+    if (cmd->post) {
+        post = cmd->post;
+        return post->post_handler(cf, post, sp);
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *
+ngx_conf_set_bufs_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char *p = conf;
+
+    ngx_str_t   *value;
+    ngx_bufs_t  *bufs;
+
+
+    bufs = (ngx_bufs_t *) (p + cmd->offset);
+    if (bufs->num) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    bufs->num = ngx_atoi(value[1].data, value[1].len);
+    if (bufs->num == NGX_ERROR || bufs->num == 0) {
+        return "invalid value";
+    }
+
+    bufs->size = ngx_parse_size(&value[2]);
+    if (bufs->size == (size_t) NGX_ERROR || bufs->size == 0) {
+        return "invalid value";
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *
+ngx_conf_set_enum_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ngx_uint_t       *np, i;
+    ngx_str_t        *value;
+    ngx_conf_enum_t  *e;
+
+    np = (ngx_uint_t *) (p + cmd->offset);
+
+    if (*np != NGX_CONF_UNSET_UINT) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+    e = cmd->post;
+
+    for (i = 0; e[i].name.len != 0; i++) {
+        if (e[i].name.len != value[1].len
+            || ngx_strcasecmp(e[i].name.data, value[1].data) != 0)
+        {
+            continue;
+        }
+
+        *np = e[i].value;
+
+        return NGX_CONF_OK;
+    }
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "invalid value \"%s\"", value[1].data);
+
+    return NGX_CONF_ERROR;
+}
+
+
+char *
+ngx_conf_set_bitmask_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ngx_uint_t          *np, i, m;
+    ngx_str_t           *value;
+    ngx_conf_bitmask_t  *mask;
+
+
+    np = (ngx_uint_t *) (p + cmd->offset);
+    value = cf->args->elts;
+    mask = cmd->post;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+        for (m = 0; mask[m].name.len != 0; m++) {
+
+            if (mask[m].name.len != value[i].len
+                || ngx_strcasecmp(mask[m].name.data, value[i].data) != 0)
+            {
+                continue;
+            }
+
+            if (*np & mask[m].mask) {
+                ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                                   "duplicate value \"%s\"", value[i].data);
+
+            } else {
+                *np |= mask[m].mask;
+            }
+
+            break;
+        }
+
+        if (mask[m].name.len == 0) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid value \"%s\"", value[i].data);
+
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+#if 0
+
+char *
+ngx_conf_unsupported(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    return "unsupported on this platform";
+}
+
+#endif
+
+
+char *
+ngx_conf_deprecated(ngx_conf_t *cf, void *post, void *data)
+{
+    ngx_conf_deprecated_t  *d = post;
+
+    ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                       "the \"%s\" directive is deprecated, "
+                       "use the \"%s\" directive instead",
+                       d->old_name, d->new_name);
+
+    return NGX_CONF_OK;
+}
+
+
+char *
+ngx_conf_check_num_bounds(ngx_conf_t *cf, void *post, void *data)
+{
+    ngx_conf_num_bounds_t  *bounds = post;
+    ngx_int_t  *np = data;
+
+    if (bounds->high == -1) {
+        if (*np >= bounds->low) {
+            return NGX_CONF_OK;
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "value must be equal to or greater than %i",
+                           bounds->low);
+
+        return NGX_CONF_ERROR;
+    }
+
+    if (*np >= bounds->low && *np <= bounds->high) {
+        return NGX_CONF_OK;
+    }
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "value must be between %i and %i",
+                       bounds->low, bounds->high);
+
+    return NGX_CONF_ERROR;
+}
diff --git a/nginx/src/core/ngx_conf_file.h b/nginx/src/core/ngx_conf_file.h
new file mode 100644 (file)
index 0000000..9cd5806
--- /dev/null
@@ -0,0 +1,295 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_CONF_FILE_H_INCLUDED_
+#define _NGX_CONF_FILE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/*
+ *        AAAA  number of arguments
+ *      FF      command flags
+ *    TT        command type, i.e. HTTP "location" or "server" command
+ */
+
+#define NGX_CONF_NOARGS      0x00000001
+#define NGX_CONF_TAKE1       0x00000002
+#define NGX_CONF_TAKE2       0x00000004
+#define NGX_CONF_TAKE3       0x00000008
+#define NGX_CONF_TAKE4       0x00000010
+#define NGX_CONF_TAKE5       0x00000020
+#define NGX_CONF_TAKE6       0x00000040
+#define NGX_CONF_TAKE7       0x00000080
+
+#define NGX_CONF_MAX_ARGS    8
+
+#define NGX_CONF_TAKE12      (NGX_CONF_TAKE1|NGX_CONF_TAKE2)
+#define NGX_CONF_TAKE13      (NGX_CONF_TAKE1|NGX_CONF_TAKE3)
+
+#define NGX_CONF_TAKE23      (NGX_CONF_TAKE2|NGX_CONF_TAKE3)
+
+#define NGX_CONF_TAKE123     (NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3)
+#define NGX_CONF_TAKE1234    (NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3   \
+                              |NGX_CONF_TAKE4)
+
+#define NGX_CONF_ARGS_NUMBER 0x000000ff
+#define NGX_CONF_BLOCK       0x00000100
+#define NGX_CONF_FLAG        0x00000200
+#define NGX_CONF_ANY         0x00000400
+#define NGX_CONF_1MORE       0x00000800
+#define NGX_CONF_2MORE       0x00001000
+
+#define NGX_DIRECT_CONF      0x00010000
+
+#define NGX_MAIN_CONF        0x01000000
+#define NGX_ANY_CONF         0x1F000000
+
+
+
+#define NGX_CONF_UNSET       -1
+#define NGX_CONF_UNSET_UINT  (ngx_uint_t) -1
+#define NGX_CONF_UNSET_PTR   (void *) -1
+#define NGX_CONF_UNSET_SIZE  (size_t) -1
+#define NGX_CONF_UNSET_MSEC  (ngx_msec_t) -1
+
+
+#define NGX_CONF_OK          NULL
+#define NGX_CONF_ERROR       (void *) -1
+
+#define NGX_CONF_BLOCK_START 1
+#define NGX_CONF_BLOCK_DONE  2
+#define NGX_CONF_FILE_DONE   3
+
+#define NGX_CORE_MODULE      0x45524F43  /* "CORE" */
+#define NGX_CONF_MODULE      0x464E4F43  /* "CONF" */
+
+
+#define NGX_MAX_CONF_ERRSTR  1024
+
+
+struct ngx_command_s {
+    ngx_str_t             name;
+    ngx_uint_t            type;
+    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+    ngx_uint_t            conf;
+    ngx_uint_t            offset;
+    void                 *post;
+};
+
+#define ngx_null_command  { ngx_null_string, 0, NULL, 0, 0, NULL }
+
+
+struct ngx_open_file_s {
+    ngx_fd_t              fd;
+    ngx_str_t             name;
+
+    void                (*flush)(ngx_open_file_t *file, ngx_log_t *log);
+    void                 *data;
+};
+
+
+typedef struct {
+    ngx_file_t            file;
+    ngx_buf_t            *buffer;
+    ngx_buf_t            *dump;
+    ngx_uint_t            line;
+} ngx_conf_file_t;
+
+
+typedef struct {
+    ngx_str_t             name;
+    ngx_buf_t            *buffer;
+} ngx_conf_dump_t;
+
+
+typedef char *(*ngx_conf_handler_pt)(ngx_conf_t *cf,
+    ngx_command_t *dummy, void *conf);
+
+
+struct ngx_conf_s {
+    char                 *name;
+    ngx_array_t          *args;
+
+    ngx_cycle_t          *cycle;
+    ngx_pool_t           *pool;
+    ngx_pool_t           *temp_pool;
+    ngx_conf_file_t      *conf_file;
+    ngx_log_t            *log;
+
+    void                 *ctx;
+    ngx_uint_t            module_type;
+    ngx_uint_t            cmd_type;
+
+    ngx_conf_handler_pt   handler;
+    void                 *handler_conf;
+};
+
+
+typedef char *(*ngx_conf_post_handler_pt) (ngx_conf_t *cf,
+    void *data, void *conf);
+
+typedef struct {
+    ngx_conf_post_handler_pt  post_handler;
+} ngx_conf_post_t;
+
+
+typedef struct {
+    ngx_conf_post_handler_pt  post_handler;
+    char                     *old_name;
+    char                     *new_name;
+} ngx_conf_deprecated_t;
+
+
+typedef struct {
+    ngx_conf_post_handler_pt  post_handler;
+    ngx_int_t                 low;
+    ngx_int_t                 high;
+} ngx_conf_num_bounds_t;
+
+
+typedef struct {
+    ngx_str_t                 name;
+    ngx_uint_t                value;
+} ngx_conf_enum_t;
+
+
+#define NGX_CONF_BITMASK_SET  1
+
+typedef struct {
+    ngx_str_t                 name;
+    ngx_uint_t                mask;
+} ngx_conf_bitmask_t;
+
+
+
+char * ngx_conf_deprecated(ngx_conf_t *cf, void *post, void *data);
+char *ngx_conf_check_num_bounds(ngx_conf_t *cf, void *post, void *data);
+
+
+#define ngx_get_conf(conf_ctx, module)  conf_ctx[module.index]
+
+
+
+#define ngx_conf_init_value(conf, default)                                   \
+    if (conf == NGX_CONF_UNSET) {                                            \
+        conf = default;                                                      \
+    }
+
+#define ngx_conf_init_ptr_value(conf, default)                               \
+    if (conf == NGX_CONF_UNSET_PTR) {                                        \
+        conf = default;                                                      \
+    }
+
+#define ngx_conf_init_uint_value(conf, default)                              \
+    if (conf == NGX_CONF_UNSET_UINT) {                                       \
+        conf = default;                                                      \
+    }
+
+#define ngx_conf_init_size_value(conf, default)                              \
+    if (conf == NGX_CONF_UNSET_SIZE) {                                       \
+        conf = default;                                                      \
+    }
+
+#define ngx_conf_init_msec_value(conf, default)                              \
+    if (conf == NGX_CONF_UNSET_MSEC) {                                       \
+        conf = default;                                                      \
+    }
+
+#define ngx_conf_merge_value(conf, prev, default)                            \
+    if (conf == NGX_CONF_UNSET) {                                            \
+        conf = (prev == NGX_CONF_UNSET) ? default : prev;                    \
+    }
+
+#define ngx_conf_merge_ptr_value(conf, prev, default)                        \
+    if (conf == NGX_CONF_UNSET_PTR) {                                        \
+        conf = (prev == NGX_CONF_UNSET_PTR) ? default : prev;                \
+    }
+
+#define ngx_conf_merge_uint_value(conf, prev, default)                       \
+    if (conf == NGX_CONF_UNSET_UINT) {                                       \
+        conf = (prev == NGX_CONF_UNSET_UINT) ? default : prev;               \
+    }
+
+#define ngx_conf_merge_msec_value(conf, prev, default)                       \
+    if (conf == NGX_CONF_UNSET_MSEC) {                                       \
+        conf = (prev == NGX_CONF_UNSET_MSEC) ? default : prev;               \
+    }
+
+#define ngx_conf_merge_sec_value(conf, prev, default)                        \
+    if (conf == NGX_CONF_UNSET) {                                            \
+        conf = (prev == NGX_CONF_UNSET) ? default : prev;                    \
+    }
+
+#define ngx_conf_merge_size_value(conf, prev, default)                       \
+    if (conf == NGX_CONF_UNSET_SIZE) {                                       \
+        conf = (prev == NGX_CONF_UNSET_SIZE) ? default : prev;               \
+    }
+
+#define ngx_conf_merge_off_value(conf, prev, default)                        \
+    if (conf == NGX_CONF_UNSET) {                                            \
+        conf = (prev == NGX_CONF_UNSET) ? default : prev;                    \
+    }
+
+#define ngx_conf_merge_str_value(conf, prev, default)                        \
+    if (conf.data == NULL) {                                                 \
+        if (prev.data) {                                                     \
+            conf.len = prev.len;                                             \
+            conf.data = prev.data;                                           \
+        } else {                                                             \
+            conf.len = sizeof(default) - 1;                                  \
+            conf.data = (u_char *) default;                                  \
+        }                                                                    \
+    }
+
+#define ngx_conf_merge_bufs_value(conf, prev, default_num, default_size)     \
+    if (conf.num == 0) {                                                     \
+        if (prev.num) {                                                      \
+            conf.num = prev.num;                                             \
+            conf.size = prev.size;                                           \
+        } else {                                                             \
+            conf.num = default_num;                                          \
+            conf.size = default_size;                                        \
+        }                                                                    \
+    }
+
+#define ngx_conf_merge_bitmask_value(conf, prev, default)                    \
+    if (conf == 0) {                                                         \
+        conf = (prev == 0) ? default : prev;                                 \
+    }
+
+
+char *ngx_conf_param(ngx_conf_t *cf);
+char *ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename);
+char *ngx_conf_include(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+
+ngx_int_t ngx_conf_full_name(ngx_cycle_t *cycle, ngx_str_t *name,
+    ngx_uint_t conf_prefix);
+ngx_open_file_t *ngx_conf_open_file(ngx_cycle_t *cycle, ngx_str_t *name);
+void ngx_cdecl ngx_conf_log_error(ngx_uint_t level, ngx_conf_t *cf,
+    ngx_err_t err, const char *fmt, ...);
+
+
+char *ngx_conf_set_flag_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+char *ngx_conf_set_str_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+char *ngx_conf_set_str_array_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+char *ngx_conf_set_keyval_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+char *ngx_conf_set_num_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+char *ngx_conf_set_size_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+char *ngx_conf_set_off_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+char *ngx_conf_set_msec_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+char *ngx_conf_set_sec_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+char *ngx_conf_set_bufs_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+char *ngx_conf_set_enum_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+char *ngx_conf_set_bitmask_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+
+#endif /* _NGX_CONF_FILE_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_config.h b/nginx/src/core/ngx_config.h
new file mode 100644 (file)
index 0000000..1861be6
--- /dev/null
@@ -0,0 +1,145 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_CONFIG_H_INCLUDED_
+#define _NGX_CONFIG_H_INCLUDED_
+
+
+#include <ngx_auto_headers.h>
+
+
+#if defined __DragonFly__ && !defined __FreeBSD__
+#define __FreeBSD__        4
+#define __FreeBSD_version  480101
+#endif
+
+
+#if (NGX_FREEBSD)
+#include <ngx_freebsd_config.h>
+
+
+#elif (NGX_LINUX)
+#include <ngx_linux_config.h>
+
+
+#elif (NGX_SOLARIS)
+#include <ngx_solaris_config.h>
+
+
+#elif (NGX_DARWIN)
+#include <ngx_darwin_config.h>
+
+
+#elif (NGX_WIN32)
+#include <ngx_win32_config.h>
+
+
+#else /* POSIX */
+#include <ngx_posix_config.h>
+
+#endif
+
+
+#ifndef NGX_HAVE_SO_SNDLOWAT
+#define NGX_HAVE_SO_SNDLOWAT     1
+#endif
+
+
+#if !(NGX_WIN32)
+
+#define ngx_signal_helper(n)     SIG##n
+#define ngx_signal_value(n)      ngx_signal_helper(n)
+
+#define ngx_random               random
+
+/* TODO: #ifndef */
+#define NGX_SHUTDOWN_SIGNAL      QUIT
+#define NGX_TERMINATE_SIGNAL     TERM
+#define NGX_NOACCEPT_SIGNAL      WINCH
+#define NGX_RECONFIGURE_SIGNAL   HUP
+
+#if (NGX_LINUXTHREADS)
+#define NGX_REOPEN_SIGNAL        INFO
+#define NGX_CHANGEBIN_SIGNAL     XCPU
+#else
+#define NGX_REOPEN_SIGNAL        USR1
+#define NGX_CHANGEBIN_SIGNAL     USR2
+#endif
+
+#define ngx_cdecl
+#define ngx_libc_cdecl
+
+#endif
+
+typedef intptr_t        ngx_int_t;
+typedef uintptr_t       ngx_uint_t;
+typedef intptr_t        ngx_flag_t;
+
+
+#define NGX_INT32_LEN   (sizeof("-2147483648") - 1)
+#define NGX_INT64_LEN   (sizeof("-9223372036854775808") - 1)
+
+#if (NGX_PTR_SIZE == 4)
+#define NGX_INT_T_LEN   NGX_INT32_LEN
+#define NGX_MAX_INT_T_VALUE  2147483647
+
+#else
+#define NGX_INT_T_LEN   NGX_INT64_LEN
+#define NGX_MAX_INT_T_VALUE  9223372036854775807
+#endif
+
+
+#ifndef NGX_ALIGNMENT
+#define NGX_ALIGNMENT   sizeof(unsigned long)    /* platform word */
+#endif
+
+#define ngx_align(d, a)     (((d) + (a - 1)) & ~(a - 1))
+#define ngx_align_ptr(p, a)                                                   \
+    (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))
+
+
+#define ngx_abort       abort
+
+
+/* TODO: platform specific: array[NGX_INVALID_ARRAY_INDEX] must cause SIGSEGV */
+#define NGX_INVALID_ARRAY_INDEX 0x80000000
+
+
+/* TODO: auto_conf: ngx_inline   inline __inline __inline__ */
+#ifndef ngx_inline
+#define ngx_inline      inline
+#endif
+
+#ifndef INADDR_NONE  /* Solaris */
+#define INADDR_NONE  ((unsigned int) -1)
+#endif
+
+#ifdef MAXHOSTNAMELEN
+#define NGX_MAXHOSTNAMELEN  MAXHOSTNAMELEN
+#else
+#define NGX_MAXHOSTNAMELEN  256
+#endif
+
+
+#define NGX_MAX_UINT32_VALUE  (uint32_t) 0xffffffff
+#define NGX_MAX_INT32_VALUE   (uint32_t) 0x7fffffff
+
+
+#if (NGX_COMPAT)
+
+#define NGX_COMPAT_BEGIN(slots)  uint64_t spare[slots];
+#define NGX_COMPAT_END
+
+#else
+
+#define NGX_COMPAT_BEGIN(slots)
+#define NGX_COMPAT_END
+
+#endif
+
+
+#endif /* _NGX_CONFIG_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_connection.c b/nginx/src/core/ngx_connection.c
new file mode 100644 (file)
index 0000000..a61a7a3
--- /dev/null
@@ -0,0 +1,1451 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+ngx_os_io_t  ngx_io;
+
+
+static void ngx_drain_connections(ngx_cycle_t *cycle);
+
+
+ngx_listening_t *
+ngx_create_listening(ngx_conf_t *cf, struct sockaddr *sockaddr,
+    socklen_t socklen)
+{
+    size_t            len;
+    ngx_listening_t  *ls;
+    struct sockaddr  *sa;
+    u_char            text[NGX_SOCKADDR_STRLEN];
+
+    ls = ngx_array_push(&cf->cycle->listening);
+    if (ls == NULL) {
+        return NULL;
+    }
+
+    ngx_memzero(ls, sizeof(ngx_listening_t));
+
+    sa = ngx_palloc(cf->pool, socklen);
+    if (sa == NULL) {
+        return NULL;
+    }
+
+    ngx_memcpy(sa, sockaddr, socklen);
+
+    ls->sockaddr = sa;
+    ls->socklen = socklen;
+
+    len = ngx_sock_ntop(sa, socklen, text, NGX_SOCKADDR_STRLEN, 1);
+    ls->addr_text.len = len;
+
+    switch (ls->sockaddr->sa_family) {
+#if (NGX_HAVE_INET6)
+    case AF_INET6:
+        ls->addr_text_max_len = NGX_INET6_ADDRSTRLEN;
+        break;
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+    case AF_UNIX:
+        ls->addr_text_max_len = NGX_UNIX_ADDRSTRLEN;
+        len++;
+        break;
+#endif
+    case AF_INET:
+        ls->addr_text_max_len = NGX_INET_ADDRSTRLEN;
+        break;
+    default:
+        ls->addr_text_max_len = NGX_SOCKADDR_STRLEN;
+        break;
+    }
+
+    ls->addr_text.data = ngx_pnalloc(cf->pool, len);
+    if (ls->addr_text.data == NULL) {
+        return NULL;
+    }
+
+    ngx_memcpy(ls->addr_text.data, text, len);
+
+    ls->fd = (ngx_socket_t) -1;
+    ls->type = SOCK_STREAM;
+
+    ls->backlog = NGX_LISTEN_BACKLOG;
+    ls->rcvbuf = -1;
+    ls->sndbuf = -1;
+
+#if (NGX_HAVE_SETFIB)
+    ls->setfib = -1;
+#endif
+
+#if (NGX_HAVE_TCP_FASTOPEN)
+    ls->fastopen = -1;
+#endif
+
+    return ls;
+}
+
+
+ngx_int_t
+ngx_clone_listening(ngx_conf_t *cf, ngx_listening_t *ls)
+{
+#if (NGX_HAVE_REUSEPORT)
+
+    ngx_int_t         n;
+    ngx_core_conf_t  *ccf;
+    ngx_listening_t   ols;
+
+    if (!ls->reuseport) {
+        return NGX_OK;
+    }
+
+    ols = *ls;
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(cf->cycle->conf_ctx,
+                                           ngx_core_module);
+
+    for (n = 1; n < ccf->worker_processes; n++) {
+
+        /* create a socket for each worker process */
+
+        ls = ngx_array_push(&cf->cycle->listening);
+        if (ls == NULL) {
+            return NGX_ERROR;
+        }
+
+        *ls = ols;
+        ls->worker = n;
+    }
+
+#endif
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_set_inherited_sockets(ngx_cycle_t *cycle)
+{
+    size_t                     len;
+    ngx_uint_t                 i;
+    ngx_listening_t           *ls;
+    socklen_t                  olen;
+#if (NGX_HAVE_DEFERRED_ACCEPT || NGX_HAVE_TCP_FASTOPEN)
+    ngx_err_t                  err;
+#endif
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
+    struct accept_filter_arg   af;
+#endif
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
+    int                        timeout;
+#endif
+#if (NGX_HAVE_REUSEPORT)
+    int                        reuseport;
+#endif
+
+    ls = cycle->listening.elts;
+    for (i = 0; i < cycle->listening.nelts; i++) {
+
+        ls[i].sockaddr = ngx_palloc(cycle->pool, sizeof(ngx_sockaddr_t));
+        if (ls[i].sockaddr == NULL) {
+            return NGX_ERROR;
+        }
+
+        ls[i].socklen = sizeof(ngx_sockaddr_t);
+        if (ngxvcl_kvfd_getsockname(ls[i].fd, ls[i].sockaddr, &ls[i].socklen) == -1) {
+            ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_socket_errno,
+                          "getsockname() of the inherited "
+                          "socket #%d failed", ls[i].fd);
+            ls[i].ignore = 1;
+            continue;
+        }
+
+        if (ls[i].socklen > (socklen_t) sizeof(ngx_sockaddr_t)) {
+            ls[i].socklen = sizeof(ngx_sockaddr_t);
+        }
+
+        switch (ls[i].sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+        case AF_INET6:
+            ls[i].addr_text_max_len = NGX_INET6_ADDRSTRLEN;
+            len = NGX_INET6_ADDRSTRLEN + sizeof("[]:65535") - 1;
+            break;
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+        case AF_UNIX:
+            ls[i].addr_text_max_len = NGX_UNIX_ADDRSTRLEN;
+            len = NGX_UNIX_ADDRSTRLEN;
+            break;
+#endif
+
+        case AF_INET:
+            ls[i].addr_text_max_len = NGX_INET_ADDRSTRLEN;
+            len = NGX_INET_ADDRSTRLEN + sizeof(":65535") - 1;
+            break;
+
+        default:
+            ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_socket_errno,
+                          "the inherited socket #%d has "
+                          "an unsupported protocol family", ls[i].fd);
+            ls[i].ignore = 1;
+            continue;
+        }
+
+        ls[i].addr_text.data = ngx_pnalloc(cycle->pool, len);
+        if (ls[i].addr_text.data == NULL) {
+            return NGX_ERROR;
+        }
+
+        len = ngx_sock_ntop(ls[i].sockaddr, ls[i].socklen,
+                            ls[i].addr_text.data, len, 1);
+        if (len == 0) {
+            return NGX_ERROR;
+        }
+
+        ls[i].addr_text.len = len;
+
+        ls[i].backlog = NGX_LISTEN_BACKLOG;
+
+        olen = sizeof(int);
+
+        if (ngxvcl_kvfd_getsockopt(ls[i].fd, SOL_SOCKET, SO_TYPE, (void *) &ls[i].type,
+                       &olen)
+            == -1)
+        {
+            ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_socket_errno,
+                          "getsockopt(SO_TYPE) %V failed", &ls[i].addr_text);
+            ls[i].ignore = 1;
+            continue;
+        }
+
+        olen = sizeof(int);
+
+        if (ngxvcl_kvfd_getsockopt(ls[i].fd, SOL_SOCKET, SO_RCVBUF, (void *) &ls[i].rcvbuf,
+                       &olen)
+            == -1)
+        {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                          "getsockopt(SO_RCVBUF) %V failed, ignored",
+                          &ls[i].addr_text);
+
+            ls[i].rcvbuf = -1;
+        }
+
+        olen = sizeof(int);
+
+        if (ngxvcl_kvfd_getsockopt(ls[i].fd, SOL_SOCKET, SO_SNDBUF, (void *) &ls[i].sndbuf,
+                       &olen)
+            == -1)
+        {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                          "getsockopt(SO_SNDBUF) %V failed, ignored",
+                          &ls[i].addr_text);
+
+            ls[i].sndbuf = -1;
+        }
+
+#if 0
+        /* SO_SETFIB is currently a set only option */
+
+#if (NGX_HAVE_SETFIB)
+
+        olen = sizeof(int);
+
+        if (ngxvcl_kvfd_getsockopt(ls[i].fd, SOL_SOCKET, SO_SETFIB,
+                       (void *) &ls[i].setfib, &olen)
+            == -1)
+        {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                          "getsockopt(SO_SETFIB) %V failed, ignored",
+                          &ls[i].addr_text);
+
+            ls[i].setfib = -1;
+        }
+
+#endif
+#endif
+
+#if (NGX_HAVE_REUSEPORT)
+
+        reuseport = 0;
+        olen = sizeof(int);
+
+        if (ngxvcl_kvfd_getsockopt(ls[i].fd, SOL_SOCKET, SO_REUSEPORT,
+                       (void *) &reuseport, &olen)
+            == -1)
+        {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                          "getsockopt(SO_REUSEPORT) %V failed, ignored",
+                          &ls[i].addr_text);
+
+        } else {
+            ls[i].reuseport = reuseport ? 1 : 0;
+        }
+
+#endif
+
+        if (ls[i].type != SOCK_STREAM) {
+            continue;
+        }
+
+#if (NGX_HAVE_TCP_FASTOPEN)
+
+        olen = sizeof(int);
+
+        if (ngxvcl_kvfd_getsockopt(ls[i].fd, IPPROTO_TCP, TCP_FASTOPEN,
+                       (void *) &ls[i].fastopen, &olen)
+            == -1)
+        {
+            err = ngx_socket_errno;
+
+            if (err != NGX_EOPNOTSUPP && err != NGX_ENOPROTOOPT) {
+                ngx_log_error(NGX_LOG_NOTICE, cycle->log, err,
+                              "getsockopt(TCP_FASTOPEN) %V failed, ignored",
+                              &ls[i].addr_text);
+            }
+
+            ls[i].fastopen = -1;
+        }
+
+#endif
+
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
+
+        ngx_memzero(&af, sizeof(struct accept_filter_arg));
+        olen = sizeof(struct accept_filter_arg);
+
+        if (ngxvcl_kvfd_getsockopt(ls[i].fd, SOL_SOCKET, SO_ACCEPTFILTER, &af, &olen)
+            == -1)
+        {
+            err = ngx_socket_errno;
+
+            if (err == NGX_EINVAL) {
+                continue;
+            }
+
+            ngx_log_error(NGX_LOG_NOTICE, cycle->log, err,
+                          "getsockopt(SO_ACCEPTFILTER) for %V failed, ignored",
+                          &ls[i].addr_text);
+            continue;
+        }
+
+        if (olen < sizeof(struct accept_filter_arg) || af.af_name[0] == '\0') {
+            continue;
+        }
+
+        ls[i].accept_filter = ngx_palloc(cycle->pool, 16);
+        if (ls[i].accept_filter == NULL) {
+            return NGX_ERROR;
+        }
+
+        (void) ngx_cpystrn((u_char *) ls[i].accept_filter,
+                           (u_char *) af.af_name, 16);
+#endif
+
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
+
+        timeout = 0;
+        olen = sizeof(int);
+
+        if (ngxvcl_kvfd_getsockopt(ls[i].fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &timeout, &olen)
+            == -1)
+        {
+            err = ngx_socket_errno;
+
+            if (err == NGX_EOPNOTSUPP) {
+                continue;
+            }
+
+            ngx_log_error(NGX_LOG_NOTICE, cycle->log, err,
+                          "getsockopt(TCP_DEFER_ACCEPT) for %V failed, ignored",
+                          &ls[i].addr_text);
+            continue;
+        }
+
+        if (olen < sizeof(int) || timeout == 0) {
+            continue;
+        }
+
+        ls[i].deferred_accept = 1;
+#endif
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_open_listening_sockets(ngx_cycle_t *cycle)
+{
+    int               reuseaddr;
+    ngx_uint_t        i, tries, failed;
+    ngx_err_t         err;
+    ngx_log_t        *log;
+    ngx_socket_t      s;
+    ngx_listening_t  *ls;
+
+    reuseaddr = 1;
+#if (NGX_SUPPRESS_WARN)
+    failed = 0;
+#endif
+
+    log = cycle->log;
+
+    /* TODO: configurable try number */
+
+    for (tries = 5; tries; tries--) {
+        failed = 0;
+
+        /* for each listening socket */
+
+        ls = cycle->listening.elts;
+        for (i = 0; i < cycle->listening.nelts; i++) {
+
+            if (ls[i].ignore) {
+                continue;
+            }
+
+#if (NGX_HAVE_REUSEPORT)
+
+            if (ls[i].add_reuseport) {
+
+                /*
+                 * to allow transition from a socket without SO_REUSEPORT
+                 * to multiple sockets with SO_REUSEPORT, we have to set
+                 * SO_REUSEPORT on the old socket before opening new ones
+                 */
+
+                int  reuseport = 1;
+
+                if (ngxvcl_kvfd_setsockopt(ls[i].fd, SOL_SOCKET, SO_REUSEPORT,
+                               (const void *) &reuseport, sizeof(int))
+                    == -1)
+                {
+                    ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                                  "setsockopt(SO_REUSEPORT) %V failed, ignored",
+                                  &ls[i].addr_text);
+                }
+
+                ls[i].add_reuseport = 0;
+            }
+#endif
+
+            if (ls[i].fd != (ngx_socket_t) -1) {
+                continue;
+            }
+
+            if (ls[i].inherited) {
+
+                /* TODO: close on exit */
+                /* TODO: nonblocking */
+                /* TODO: deferred accept */
+
+                continue;
+            }
+
+            s = ngx_socket(ls[i].sockaddr->sa_family, ls[i].type, 0);
+
+            if (s == (ngx_socket_t) -1) {
+                ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
+                              ngx_socket_n " %V failed", &ls[i].addr_text);
+                return NGX_ERROR;
+            }
+
+            if (ngxvcl_kvfd_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 (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)
+
+            if (ls[i].reuseport && !ngx_test_config) {
+                int  reuseport;
+
+                reuseport = 1;
+
+                if (ngxvcl_kvfd_setsockopt(s, SOL_SOCKET, SO_REUSEPORT,
+                               (const void *) &reuseport, sizeof(int))
+                    == -1)
+                {
+                    ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
+                                  "setsockopt(SO_REUSEPORT) %V failed",
+                                  &ls[i].addr_text);
+
+                    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;
+                }
+            }
+#endif
+
+#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
+
+            if (ls[i].sockaddr->sa_family == AF_INET6) {
+                int  ipv6only;
+
+                ipv6only = ls[i].ipv6only;
+
+                if (ngxvcl_kvfd_setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
+                               (const void *) &ipv6only, sizeof(int))
+                    == -1)
+                {
+                    ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
+                                  "setsockopt(IPV6_V6ONLY) %V failed, ignored",
+                                  &ls[i].addr_text);
+                }
+            }
+#endif
+            /* TODO: close on exit */
+
+            if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) {
+                if (ngx_nonblocking(s) == -1) {
+                    ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
+                                  ngx_nonblocking_n " %V failed",
+                                  &ls[i].addr_text);
+
+                    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;
+                }
+            }
+
+            ngx_log_debug2(NGX_LOG_DEBUG_CORE, log, 0,
+                           "bind() %V #%d ", &ls[i].addr_text, s);
+
+            if (ngxvcl_bind(s, ls[i].sockaddr, ls[i].socklen) == -1) {
+                err = ngx_socket_errno;
+
+                if (err != NGX_EADDRINUSE || !ngx_test_config) {
+                    ngx_log_error(NGX_LOG_EMERG, log, err,
+                                  "bind() to %V failed", &ls[i].addr_text);
+                }
+
+                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);
+                }
+
+                if (err != NGX_EADDRINUSE) {
+                    return NGX_ERROR;
+                }
+
+                if (!ngx_test_config) {
+                    failed = 1;
+                }
+
+                continue;
+            }
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+
+            if (ls[i].sockaddr->sa_family == AF_UNIX) {
+                mode_t   mode;
+                u_char  *name;
+
+                name = ls[i].addr_text.data + sizeof("unix:") - 1;
+                mode = (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
+
+                if (chmod((char *) name, mode) == -1) {
+                    ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                                  "chmod() \"%s\" failed", name);
+                }
+
+                if (ngx_test_config) {
+                    if (ngx_delete_file(name) == NGX_FILE_ERROR) {
+                        ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                                      ngx_delete_file_n " %s failed", name);
+                    }
+                }
+            }
+#endif
+
+            if (ls[i].type != SOCK_STREAM) {
+                ls[i].fd = s;
+                continue;
+            }
+
+            if (ngxvcl_listen(s, ls[i].backlog) == -1) {
+                err = ngx_socket_errno;
+
+                /*
+                 * on OpenVZ after suspend/resume EADDRINUSE
+                 * may be returned by listen() instead of bind(), see
+                 * https://bugzilla.openvz.org/show_bug.cgi?id=2470
+                 */
+
+                if (err != NGX_EADDRINUSE || !ngx_test_config) {
+                    ngx_log_error(NGX_LOG_EMERG, log, err,
+                                  "listen() to %V, backlog %d failed",
+                                  &ls[i].addr_text, ls[i].backlog);
+                }
+
+                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);
+                }
+
+                if (err != NGX_EADDRINUSE) {
+                    return NGX_ERROR;
+                }
+
+                if (!ngx_test_config) {
+                    failed = 1;
+                }
+
+                continue;
+            }
+
+            ls[i].listen = 1;
+
+            ls[i].fd = s;
+        }
+
+        if (!failed) {
+            break;
+        }
+
+        /* TODO: delay configurable */
+
+        ngx_log_error(NGX_LOG_NOTICE, log, 0,
+                      "try again to bind() after 500ms");
+
+        ngx_msleep(500);
+    }
+
+    if (failed) {
+        ngx_log_error(NGX_LOG_EMERG, log, 0, "still could not bind()");
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+void
+ngx_configure_listening_sockets(ngx_cycle_t *cycle)
+{
+    int                        value;
+    ngx_uint_t                 i;
+    ngx_listening_t           *ls;
+
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
+    struct accept_filter_arg   af;
+#endif
+
+    ls = cycle->listening.elts;
+    for (i = 0; i < cycle->listening.nelts; i++) {
+
+        ls[i].log = *ls[i].logp;
+
+        if (ls[i].rcvbuf != -1) {
+            if (ngxvcl_kvfd_setsockopt(ls[i].fd, SOL_SOCKET, SO_RCVBUF,
+                           (const void *) &ls[i].rcvbuf, sizeof(int))
+                == -1)
+            {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                              "setsockopt(SO_RCVBUF, %d) %V failed, ignored",
+                              ls[i].rcvbuf, &ls[i].addr_text);
+            }
+        }
+
+        if (ls[i].sndbuf != -1) {
+            if (ngxvcl_kvfd_setsockopt(ls[i].fd, SOL_SOCKET, SO_SNDBUF,
+                           (const void *) &ls[i].sndbuf, sizeof(int))
+                == -1)
+            {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                              "setsockopt(SO_SNDBUF, %d) %V failed, ignored",
+                              ls[i].sndbuf, &ls[i].addr_text);
+            }
+        }
+
+        if (ls[i].keepalive) {
+            value = (ls[i].keepalive == 1) ? 1 : 0;
+
+            if (ngxvcl_kvfd_setsockopt(ls[i].fd, SOL_SOCKET, SO_KEEPALIVE,
+                           (const void *) &value, sizeof(int))
+                == -1)
+            {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                              "setsockopt(SO_KEEPALIVE, %d) %V failed, ignored",
+                              value, &ls[i].addr_text);
+            }
+        }
+
+#if (NGX_HAVE_KEEPALIVE_TUNABLE)
+
+        if (ls[i].keepidle) {
+            value = ls[i].keepidle;
+
+#if (NGX_KEEPALIVE_FACTOR)
+            value *= NGX_KEEPALIVE_FACTOR;
+#endif
+
+            if (ngxvcl_kvfd_setsockopt(ls[i].fd, IPPROTO_TCP, TCP_KEEPIDLE,
+                           (const void *) &value, sizeof(int))
+                == -1)
+            {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                              "setsockopt(TCP_KEEPIDLE, %d) %V failed, ignored",
+                              value, &ls[i].addr_text);
+            }
+        }
+
+        if (ls[i].keepintvl) {
+            value = ls[i].keepintvl;
+
+#if (NGX_KEEPALIVE_FACTOR)
+            value *= NGX_KEEPALIVE_FACTOR;
+#endif
+
+            if (ngxvcl_kvfd_setsockopt(ls[i].fd, IPPROTO_TCP, TCP_KEEPINTVL,
+                           (const void *) &value, sizeof(int))
+                == -1)
+            {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                             "setsockopt(TCP_KEEPINTVL, %d) %V failed, ignored",
+                             value, &ls[i].addr_text);
+            }
+        }
+
+        if (ls[i].keepcnt) {
+            if (ngxvcl_kvfd_setsockopt(ls[i].fd, IPPROTO_TCP, TCP_KEEPCNT,
+                           (const void *) &ls[i].keepcnt, sizeof(int))
+                == -1)
+            {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                              "setsockopt(TCP_KEEPCNT, %d) %V failed, ignored",
+                              ls[i].keepcnt, &ls[i].addr_text);
+            }
+        }
+
+#endif
+
+#if (NGX_HAVE_SETFIB)
+        if (ls[i].setfib != -1) {
+            if (ngxvcl_kvfd_setsockopt(ls[i].fd, SOL_SOCKET, SO_SETFIB,
+                           (const void *) &ls[i].setfib, sizeof(int))
+                == -1)
+            {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                              "setsockopt(SO_SETFIB, %d) %V failed, ignored",
+                              ls[i].setfib, &ls[i].addr_text);
+            }
+        }
+#endif
+
+#if (NGX_HAVE_TCP_FASTOPEN)
+        if (ls[i].fastopen != -1) {
+            if (ngxvcl_kvfd_setsockopt(ls[i].fd, IPPROTO_TCP, TCP_FASTOPEN,
+                           (const void *) &ls[i].fastopen, sizeof(int))
+                == -1)
+            {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                              "setsockopt(TCP_FASTOPEN, %d) %V failed, ignored",
+                              ls[i].fastopen, &ls[i].addr_text);
+            }
+        }
+#endif
+
+#if 0
+        if (1) {
+            int tcp_nodelay = 1;
+
+            if (ngxvcl_kvfd_setsockopt(ls[i].fd, IPPROTO_TCP, TCP_NODELAY,
+                       (const void *) &tcp_nodelay, sizeof(int))
+                == -1)
+            {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                              "setsockopt(TCP_NODELAY) %V failed, ignored",
+                              &ls[i].addr_text);
+            }
+        }
+#endif
+
+        if (ls[i].listen) {
+
+            /* change backlog via listen() */
+
+            if (ngxvcl_listen(ls[i].fd, ls[i].backlog) == -1) {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                              "listen() to %V, backlog %d failed, ignored",
+                              &ls[i].addr_text, ls[i].backlog);
+            }
+        }
+
+        /*
+         * setting deferred mode should be last operation on socket,
+         * because code may prematurely continue cycle on failure
+         */
+
+#if (NGX_HAVE_DEFERRED_ACCEPT)
+
+#ifdef SO_ACCEPTFILTER
+
+        if (ls[i].delete_deferred) {
+            if (ngxvcl_kvfd_setsockopt(ls[i].fd, SOL_SOCKET, SO_ACCEPTFILTER, NULL, 0)
+                == -1)
+            {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                              "setsockopt(SO_ACCEPTFILTER, NULL) "
+                              "for %V failed, ignored",
+                              &ls[i].addr_text);
+
+                if (ls[i].accept_filter) {
+                    ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                                  "could not change the accept filter "
+                                  "to \"%s\" for %V, ignored",
+                                  ls[i].accept_filter, &ls[i].addr_text);
+                }
+
+                continue;
+            }
+
+            ls[i].deferred_accept = 0;
+        }
+
+        if (ls[i].add_deferred) {
+            ngx_memzero(&af, sizeof(struct accept_filter_arg));
+            (void) ngx_cpystrn((u_char *) af.af_name,
+                               (u_char *) ls[i].accept_filter, 16);
+
+            if (ngxvcl_kvfd_setsockopt(ls[i].fd, SOL_SOCKET, SO_ACCEPTFILTER,
+                           &af, sizeof(struct accept_filter_arg))
+                == -1)
+            {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                              "setsockopt(SO_ACCEPTFILTER, \"%s\") "
+                              "for %V failed, ignored",
+                              ls[i].accept_filter, &ls[i].addr_text);
+                continue;
+            }
+
+            ls[i].deferred_accept = 1;
+        }
+
+#endif
+
+#ifdef TCP_DEFER_ACCEPT
+
+        if (ls[i].add_deferred || ls[i].delete_deferred) {
+
+            if (ls[i].add_deferred) {
+                /*
+                 * There is no way to find out how long a connection was
+                 * in queue (and a connection may bypass deferred queue at all
+                 * if syncookies were used), hence we use 1 second timeout
+                 * here.
+                 */
+                value = 1;
+
+            } else {
+                value = 0;
+            }
+
+            if (ngxvcl_kvfd_setsockopt(ls[i].fd, IPPROTO_TCP, TCP_DEFER_ACCEPT,
+                           &value, sizeof(int))
+                == -1)
+            {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                              "setsockopt(TCP_DEFER_ACCEPT, %d) for %V failed, "
+                              "ignored",
+                              value, &ls[i].addr_text);
+
+                continue;
+            }
+        }
+
+        if (ls[i].add_deferred) {
+            ls[i].deferred_accept = 1;
+        }
+
+#endif
+
+#endif /* NGX_HAVE_DEFERRED_ACCEPT */
+
+#if (NGX_HAVE_IP_RECVDSTADDR)
+
+        if (ls[i].wildcard
+            && ls[i].type == SOCK_DGRAM
+            && ls[i].sockaddr->sa_family == AF_INET)
+        {
+            value = 1;
+
+            if (ngxvcl_kvfd_setsockopt(ls[i].fd, IPPROTO_IP, IP_RECVDSTADDR,
+                           (const void *) &value, sizeof(int))
+                == -1)
+            {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                              "setsockopt(IP_RECVDSTADDR) "
+                              "for %V failed, ignored",
+                              &ls[i].addr_text);
+            }
+        }
+
+#elif (NGX_HAVE_IP_PKTINFO)
+
+        if (ls[i].wildcard
+            && ls[i].type == SOCK_DGRAM
+            && ls[i].sockaddr->sa_family == AF_INET)
+        {
+            value = 1;
+
+            if (ngxvcl_kvfd_setsockopt(ls[i].fd, IPPROTO_IP, IP_PKTINFO,
+                           (const void *) &value, sizeof(int))
+                == -1)
+            {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                              "setsockopt(IP_PKTINFO) "
+                              "for %V failed, ignored",
+                              &ls[i].addr_text);
+            }
+        }
+
+#endif
+
+#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO)
+
+        if (ls[i].wildcard
+            && ls[i].type == SOCK_DGRAM
+            && ls[i].sockaddr->sa_family == AF_INET6)
+        {
+            value = 1;
+
+            if (ngxvcl_kvfd_setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+                           (const void *) &value, sizeof(int))
+                == -1)
+            {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                              "setsockopt(IPV6_RECVPKTINFO) "
+                              "for %V failed, ignored",
+                              &ls[i].addr_text);
+            }
+        }
+
+#endif
+    }
+
+    return;
+}
+
+
+void
+ngx_close_listening_sockets(ngx_cycle_t *cycle)
+{
+    ngx_uint_t         i;
+    ngx_listening_t   *ls;
+    ngx_connection_t  *c;
+
+    if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
+        return;
+    }
+
+    ngx_accept_mutex_held = 0;
+    ngx_use_accept_mutex = 0;
+
+    ls = cycle->listening.elts;
+    for (i = 0; i < cycle->listening.nelts; i++) {
+
+        c = ls[i].connection;
+
+        if (c) {
+            if (c->read->active) {
+                if (ngx_event_flags & NGX_USE_EPOLL_EVENT) {
+
+                    /*
+                     * it seems that Linux-2.6.x OpenVZ sends events
+                     * for closed shared listening sockets unless
+                     * the events was explicitly deleted
+                     */
+
+                    ngx_del_event(c->read, NGX_READ_EVENT, 0);
+
+                } else {
+                    ngx_del_event(c->read, NGX_READ_EVENT, NGX_CLOSE_EVENT);
+                }
+            }
+
+            ngx_free_connection(c);
+
+            c->fd = (ngx_socket_t) -1;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+                       "close listening %V #%d ", &ls[i].addr_text, ls[i].fd);
+
+        if (ngx_close_socket(ls[i].fd) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
+                          ngx_close_socket_n " %V failed", &ls[i].addr_text);
+        }
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+
+        if (ls[i].sockaddr->sa_family == AF_UNIX
+            && ngx_process <= NGX_PROCESS_MASTER
+            && ngx_new_binary == 0)
+        {
+            u_char *name = ls[i].addr_text.data + sizeof("unix:") - 1;
+
+            if (ngx_delete_file(name) == NGX_FILE_ERROR) {
+                ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
+                              ngx_delete_file_n " %s failed", name);
+            }
+        }
+
+#endif
+
+        ls[i].fd = (ngx_socket_t) -1;
+    }
+
+    cycle->listening.nelts = 0;
+}
+
+
+ngx_connection_t *
+ngx_get_connection(ngx_socket_t s, ngx_log_t *log)
+{
+    ngx_uint_t         instance;
+    ngx_event_t       *rev, *wev;
+    ngx_connection_t  *c;
+
+    /* disable warning: Win32 SOCKET is u_int while UNIX socket is int */
+
+    if (ngx_cycle->files && (ngx_uint_t) s >= ngx_cycle->files_n) {
+        ngx_log_error(NGX_LOG_ALERT, log, 0,
+                      "the new socket has number %d, "
+                      "but only %ui files are available",
+                      s, ngx_cycle->files_n);
+        return NULL;
+    }
+
+    c = ngx_cycle->free_connections;
+
+    if (c == NULL) {
+        ngx_drain_connections((ngx_cycle_t *) ngx_cycle);
+        c = ngx_cycle->free_connections;
+    }
+
+    if (c == NULL) {
+        ngx_log_error(NGX_LOG_ALERT, log, 0,
+                      "%ui worker_connections are not enough",
+                      ngx_cycle->connection_n);
+
+        return NULL;
+    }
+
+    ngx_cycle->free_connections = c->data;
+    ngx_cycle->free_connection_n--;
+
+    if (ngx_cycle->files && ngx_cycle->files[s] == NULL) {
+        ngx_cycle->files[s] = c;
+    }
+
+    rev = c->read;
+    wev = c->write;
+
+    ngx_memzero(c, sizeof(ngx_connection_t));
+
+    c->read = rev;
+    c->write = wev;
+    c->fd = s;
+    c->log = log;
+
+    instance = rev->instance;
+
+    ngx_memzero(rev, sizeof(ngx_event_t));
+    ngx_memzero(wev, sizeof(ngx_event_t));
+
+    rev->instance = !instance;
+    wev->instance = !instance;
+
+    rev->index = NGX_INVALID_INDEX;
+    wev->index = NGX_INVALID_INDEX;
+
+    rev->data = c;
+    wev->data = c;
+
+    wev->write = 1;
+
+    return c;
+}
+
+
+void
+ngx_free_connection(ngx_connection_t *c)
+{
+    c->data = ngx_cycle->free_connections;
+    ngx_cycle->free_connections = c;
+    ngx_cycle->free_connection_n++;
+
+    if (ngx_cycle->files && ngx_cycle->files[c->fd] == c) {
+        ngx_cycle->files[c->fd] = NULL;
+    }
+}
+
+
+void
+ngx_close_connection(ngx_connection_t *c)
+{
+    ngx_err_t     err;
+    ngx_uint_t    log_error, level;
+    ngx_socket_t  fd;
+
+    if (c->fd == (ngx_socket_t) -1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, 0, "connection already closed");
+        return;
+    }
+
+    if (c->read->timer_set) {
+        ngx_del_timer(c->read);
+    }
+
+    if (c->write->timer_set) {
+        ngx_del_timer(c->write);
+    }
+
+    if (!c->shared) {
+        if (ngx_del_conn) {
+            ngx_del_conn(c, NGX_CLOSE_EVENT);
+
+        } else {
+            if (c->read->active || c->read->disabled) {
+                ngx_del_event(c->read, NGX_READ_EVENT, NGX_CLOSE_EVENT);
+            }
+
+            if (c->write->active || c->write->disabled) {
+                ngx_del_event(c->write, NGX_WRITE_EVENT, NGX_CLOSE_EVENT);
+            }
+        }
+    }
+
+    if (c->read->posted) {
+        ngx_delete_posted_event(c->read);
+    }
+
+    if (c->write->posted) {
+        ngx_delete_posted_event(c->write);
+    }
+
+    c->read->closed = 1;
+    c->write->closed = 1;
+
+    ngx_reusable_connection(c, 0);
+
+    log_error = c->log_error;
+
+    ngx_free_connection(c);
+
+    fd = c->fd;
+    c->fd = (ngx_socket_t) -1;
+
+    if (c->shared) {
+        return;
+    }
+
+    if (ngx_close_socket(fd) == -1) {
+
+        err = ngx_socket_errno;
+
+        if (err == NGX_ECONNRESET || err == NGX_ENOTCONN) {
+
+            switch (log_error) {
+
+            case NGX_ERROR_INFO:
+                level = NGX_LOG_INFO;
+                break;
+
+            case NGX_ERROR_ERR:
+                level = NGX_LOG_ERR;
+                break;
+
+            default:
+                level = NGX_LOG_CRIT;
+            }
+
+        } else {
+            level = NGX_LOG_CRIT;
+        }
+
+        ngx_log_error(level, c->log, err, ngx_close_socket_n " %d failed", fd);
+    }
+}
+
+
+void
+ngx_reusable_connection(ngx_connection_t *c, ngx_uint_t reusable)
+{
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
+                   "reusable connection: %ui", reusable);
+
+    if (c->reusable) {
+        ngx_queue_remove(&c->queue);
+        ngx_cycle->reusable_connections_n--;
+
+#if (NGX_STAT_STUB)
+        (void) ngx_atomic_fetch_add(ngx_stat_waiting, -1);
+#endif
+    }
+
+    c->reusable = reusable;
+
+    if (reusable) {
+        /* need cast as ngx_cycle is volatile */
+
+        ngx_queue_insert_head(
+            (ngx_queue_t *) &ngx_cycle->reusable_connections_queue, &c->queue);
+        ngx_cycle->reusable_connections_n++;
+
+#if (NGX_STAT_STUB)
+        (void) ngx_atomic_fetch_add(ngx_stat_waiting, 1);
+#endif
+    }
+}
+
+
+static void
+ngx_drain_connections(ngx_cycle_t *cycle)
+{
+    ngx_uint_t         i, n;
+    ngx_queue_t       *q;
+    ngx_connection_t  *c;
+
+    n = ngx_max(ngx_min(32, cycle->reusable_connections_n / 8), 1);
+
+    for (i = 0; i < n; i++) {
+        if (ngx_queue_empty(&cycle->reusable_connections_queue)) {
+            break;
+        }
+
+        q = ngx_queue_last(&cycle->reusable_connections_queue);
+        c = ngx_queue_data(q, ngx_connection_t, queue);
+
+        ngx_log_debug0(NGX_LOG_DEBUG_CORE, c->log, 0,
+                       "reusing connection");
+
+        c->close = 1;
+        c->read->handler(c->read);
+    }
+}
+
+
+void
+ngx_close_idle_connections(ngx_cycle_t *cycle)
+{
+    ngx_uint_t         i;
+    ngx_connection_t  *c;
+
+    c = cycle->connections;
+
+    for (i = 0; i < cycle->connection_n; i++) {
+
+        /* THREAD: lock */
+
+        if (c[i].fd != (ngx_socket_t) -1 && c[i].idle) {
+            c[i].close = 1;
+            c[i].read->handler(c[i].read);
+        }
+    }
+}
+
+
+ngx_int_t
+ngx_connection_local_sockaddr(ngx_connection_t *c, ngx_str_t *s,
+    ngx_uint_t port)
+{
+    socklen_t             len;
+    ngx_uint_t            addr;
+    ngx_sockaddr_t        sa;
+    struct sockaddr_in   *sin;
+#if (NGX_HAVE_INET6)
+    ngx_uint_t            i;
+    struct sockaddr_in6  *sin6;
+#endif
+
+    addr = 0;
+
+    if (c->local_socklen) {
+        switch (c->local_sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+        case AF_INET6:
+            sin6 = (struct sockaddr_in6 *) c->local_sockaddr;
+
+            for (i = 0; addr == 0 && i < 16; i++) {
+                addr |= sin6->sin6_addr.s6_addr[i];
+            }
+
+            break;
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+        case AF_UNIX:
+            addr = 1;
+            break;
+#endif
+
+        default: /* AF_INET */
+            sin = (struct sockaddr_in *) c->local_sockaddr;
+            addr = sin->sin_addr.s_addr;
+            break;
+        }
+    }
+
+    if (addr == 0) {
+
+        len = sizeof(ngx_sockaddr_t);
+
+        if (ngxvcl_kvfd_getsockname(c->fd, &sa.sockaddr, &len) == -1) {
+            ngx_connection_error(c, ngx_socket_errno, "getsockname() failed");
+            return NGX_ERROR;
+        }
+
+        c->local_sockaddr = ngx_palloc(c->pool, len);
+        if (c->local_sockaddr == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_memcpy(c->local_sockaddr, &sa, len);
+
+        c->local_socklen = len;
+    }
+
+    if (s == NULL) {
+        return NGX_OK;
+    }
+
+    s->len = ngx_sock_ntop(c->local_sockaddr, c->local_socklen,
+                           s->data, s->len, port);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_tcp_nodelay(ngx_connection_t *c)
+{
+    int  tcp_nodelay;
+
+    if (c->tcp_nodelay != NGX_TCP_NODELAY_UNSET) {
+        return NGX_OK;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_CORE, c->log, 0, "tcp_nodelay");
+
+    tcp_nodelay = 1;
+
+    if (ngxvcl_kvfd_setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY,
+                   (const void *) &tcp_nodelay, sizeof(int))
+        == -1)
+    {
+#if (NGX_SOLARIS)
+        if (c->log_error == NGX_ERROR_INFO) {
+
+            /* Solaris returns EINVAL if a socket has been shut down */
+            c->log_error = NGX_ERROR_IGNORE_EINVAL;
+
+            ngx_connection_error(c, ngx_socket_errno,
+                                 "setsockopt(TCP_NODELAY) failed");
+
+            c->log_error = NGX_ERROR_INFO;
+
+            return NGX_ERROR;
+        }
+#endif
+
+        ngx_connection_error(c, ngx_socket_errno,
+                             "setsockopt(TCP_NODELAY) failed");
+        return NGX_ERROR;
+    }
+
+    c->tcp_nodelay = NGX_TCP_NODELAY_SET;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_connection_error(ngx_connection_t *c, ngx_err_t err, char *text)
+{
+    ngx_uint_t  level;
+
+    /* Winsock may return NGX_ECONNABORTED instead of NGX_ECONNRESET */
+
+    if ((err == NGX_ECONNRESET
+#if (NGX_WIN32)
+         || err == NGX_ECONNABORTED
+#endif
+        ) && c->log_error == NGX_ERROR_IGNORE_ECONNRESET)
+    {
+        return 0;
+    }
+
+#if (NGX_SOLARIS)
+    if (err == NGX_EINVAL && c->log_error == NGX_ERROR_IGNORE_EINVAL) {
+        return 0;
+    }
+#endif
+
+    if (err == 0
+        || err == NGX_ECONNRESET
+#if (NGX_WIN32)
+        || err == NGX_ECONNABORTED
+#else
+        || err == NGX_EPIPE
+#endif
+        || err == NGX_ENOTCONN
+        || err == NGX_ETIMEDOUT
+        || err == NGX_ECONNREFUSED
+        || err == NGX_ENETDOWN
+        || err == NGX_ENETUNREACH
+        || err == NGX_EHOSTDOWN
+        || err == NGX_EHOSTUNREACH)
+    {
+        switch (c->log_error) {
+
+        case NGX_ERROR_IGNORE_EINVAL:
+        case NGX_ERROR_IGNORE_ECONNRESET:
+        case NGX_ERROR_INFO:
+            level = NGX_LOG_INFO;
+            break;
+
+        default:
+            level = NGX_LOG_ERR;
+        }
+
+    } else {
+        level = NGX_LOG_ALERT;
+    }
+
+    ngx_log_error(level, c->log, err, text);
+
+    return NGX_ERROR;
+}
diff --git a/nginx/src/core/ngx_connection.h b/nginx/src/core/ngx_connection.h
new file mode 100644 (file)
index 0000000..e4dfe58
--- /dev/null
@@ -0,0 +1,225 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_CONNECTION_H_INCLUDED_
+#define _NGX_CONNECTION_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef struct ngx_listening_s  ngx_listening_t;
+
+struct ngx_listening_s {
+    ngx_socket_t        fd;
+
+    struct sockaddr    *sockaddr;
+    socklen_t           socklen;    /* size of sockaddr */
+    size_t              addr_text_max_len;
+    ngx_str_t           addr_text;
+
+    int                 type;
+
+    int                 backlog;
+    int                 rcvbuf;
+    int                 sndbuf;
+#if (NGX_HAVE_KEEPALIVE_TUNABLE)
+    int                 keepidle;
+    int                 keepintvl;
+    int                 keepcnt;
+#endif
+
+    /* handler of accepted connection */
+    ngx_connection_handler_pt   handler;
+
+    void               *servers;  /* array of ngx_http_in_addr_t, for example */
+
+    ngx_log_t           log;
+    ngx_log_t          *logp;
+
+    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;
+
+    ngx_uint_t          worker;
+
+    unsigned            open:1;
+    unsigned            remain:1;
+    unsigned            ignore:1;
+
+    unsigned            bound:1;       /* already bound */
+    unsigned            inherited:1;   /* inherited from previous process */
+    unsigned            nonblocking_accept:1;
+    unsigned            listen:1;
+    unsigned            nonblocking:1;
+    unsigned            shared:1;    /* shared between threads or processes */
+    unsigned            addr_ntop:1;
+    unsigned            wildcard:1;
+
+#if (NGX_HAVE_INET6)
+    unsigned            ipv6only:1;
+#endif
+    unsigned            reuseport:1;
+    unsigned            add_reuseport:1;
+    unsigned            keepalive:2;
+
+    unsigned            deferred_accept:1;
+    unsigned            delete_deferred:1;
+    unsigned            add_deferred:1;
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
+    char               *accept_filter;
+#endif
+#if (NGX_HAVE_SETFIB)
+    int                 setfib;
+#endif
+
+#if (NGX_HAVE_TCP_FASTOPEN)
+    int                 fastopen;
+#endif
+
+};
+
+
+typedef enum {
+    NGX_ERROR_ALERT = 0,
+    NGX_ERROR_ERR,
+    NGX_ERROR_INFO,
+    NGX_ERROR_IGNORE_ECONNRESET,
+    NGX_ERROR_IGNORE_EINVAL
+} ngx_connection_log_error_e;
+
+
+typedef enum {
+    NGX_TCP_NODELAY_UNSET = 0,
+    NGX_TCP_NODELAY_SET,
+    NGX_TCP_NODELAY_DISABLED
+} ngx_connection_tcp_nodelay_e;
+
+
+typedef enum {
+    NGX_TCP_NOPUSH_UNSET = 0,
+    NGX_TCP_NOPUSH_SET,
+    NGX_TCP_NOPUSH_DISABLED
+} ngx_connection_tcp_nopush_e;
+
+
+#define NGX_LOWLEVEL_BUFFERED  0x0f
+#define NGX_SSL_BUFFERED       0x01
+#define NGX_HTTP_V2_BUFFERED   0x02
+
+
+struct ngx_connection_s {
+    void               *data;
+    ngx_event_t        *read;
+    ngx_event_t        *write;
+
+    ngx_socket_t        fd;
+
+    ngx_recv_pt         recv;
+    ngx_send_pt         send;
+    ngx_recv_chain_pt   recv_chain;
+    ngx_send_chain_pt   send_chain;
+
+    ngx_listening_t    *listening;
+
+    off_t               sent;
+
+    ngx_log_t          *log;
+
+    ngx_pool_t         *pool;
+
+    int                 type;
+
+    struct sockaddr    *sockaddr;
+    socklen_t           socklen;
+    ngx_str_t           addr_text;
+
+    ngx_str_t           proxy_protocol_addr;
+    in_port_t           proxy_protocol_port;
+
+#if (NGX_SSL || NGX_COMPAT)
+    ngx_ssl_connection_t  *ssl;
+#endif
+
+    struct sockaddr    *local_sockaddr;
+    socklen_t           local_socklen;
+
+    ngx_buf_t          *buffer;
+
+    ngx_queue_t         queue;
+
+    ngx_atomic_uint_t   number;
+
+    ngx_uint_t          requests;
+
+    unsigned            buffered:8;
+
+    unsigned            log_error:3;     /* ngx_connection_log_error_e */
+
+    unsigned            timedout:1;
+    unsigned            error:1;
+    unsigned            destroyed:1;
+
+    unsigned            idle:1;
+    unsigned            reusable:1;
+    unsigned            close:1;
+    unsigned            shared:1;
+
+    unsigned            sendfile:1;
+    unsigned            sndlowat:1;
+    unsigned            tcp_nodelay:2;   /* ngx_connection_tcp_nodelay_e */
+    unsigned            tcp_nopush:2;    /* ngx_connection_tcp_nopush_e */
+
+    unsigned            need_last_buf:1;
+
+#if (NGX_HAVE_AIO_SENDFILE || NGX_COMPAT)
+    unsigned            busy_count:2;
+#endif
+
+#if (NGX_THREADS || NGX_COMPAT)
+    ngx_thread_task_t  *sendfile_task;
+#endif
+};
+
+
+#define ngx_set_connection_log(c, l)                                         \
+                                                                             \
+    c->log->file = l->file;                                                  \
+    c->log->next = l->next;                                                  \
+    c->log->writer = l->writer;                                              \
+    c->log->wdata = l->wdata;                                                \
+    if (!(c->log->log_level & NGX_LOG_DEBUG_CONNECTION)) {                   \
+        c->log->log_level = l->log_level;                                    \
+    }
+
+
+ngx_listening_t *ngx_create_listening(ngx_conf_t *cf, struct sockaddr *sockaddr,
+    socklen_t socklen);
+ngx_int_t ngx_clone_listening(ngx_conf_t *cf, ngx_listening_t *ls);
+ngx_int_t ngx_set_inherited_sockets(ngx_cycle_t *cycle);
+ngx_int_t ngx_open_listening_sockets(ngx_cycle_t *cycle);
+void ngx_configure_listening_sockets(ngx_cycle_t *cycle);
+void ngx_close_listening_sockets(ngx_cycle_t *cycle);
+void ngx_close_connection(ngx_connection_t *c);
+void ngx_close_idle_connections(ngx_cycle_t *cycle);
+ngx_int_t ngx_connection_local_sockaddr(ngx_connection_t *c, ngx_str_t *s,
+    ngx_uint_t port);
+ngx_int_t ngx_tcp_nodelay(ngx_connection_t *c);
+ngx_int_t ngx_connection_error(ngx_connection_t *c, ngx_err_t err, char *text);
+
+ngx_connection_t *ngx_get_connection(ngx_socket_t s, ngx_log_t *log);
+void ngx_free_connection(ngx_connection_t *c);
+
+void ngx_reusable_connection(ngx_connection_t *c, ngx_uint_t reusable);
+
+#endif /* _NGX_CONNECTION_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_core.h b/nginx/src/core/ngx_core.h
new file mode 100644 (file)
index 0000000..03cb15c
--- /dev/null
@@ -0,0 +1,112 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_CORE_H_INCLUDED_
+#define _NGX_CORE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+
+#include <vcl/ngxvcl.h>
+
+typedef struct ngx_module_s          ngx_module_t;
+typedef struct ngx_conf_s            ngx_conf_t;
+typedef struct ngx_cycle_s           ngx_cycle_t;
+typedef struct ngx_pool_s            ngx_pool_t;
+typedef struct ngx_chain_s           ngx_chain_t;
+typedef struct ngx_log_s             ngx_log_t;
+typedef struct ngx_open_file_s       ngx_open_file_t;
+typedef struct ngx_command_s         ngx_command_t;
+typedef struct ngx_file_s            ngx_file_t;
+typedef struct ngx_event_s           ngx_event_t;
+typedef struct ngx_event_aio_s       ngx_event_aio_t;
+typedef struct ngx_connection_s      ngx_connection_t;
+typedef struct ngx_thread_task_s     ngx_thread_task_t;
+typedef struct ngx_ssl_s             ngx_ssl_t;
+typedef struct ngx_ssl_connection_s  ngx_ssl_connection_t;
+
+typedef void (*ngx_event_handler_pt)(ngx_event_t *ev);
+typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c);
+
+
+#define  NGX_OK          0
+#define  NGX_ERROR      -1
+#define  NGX_AGAIN      -2
+#define  NGX_BUSY       -3
+#define  NGX_DONE       -4
+#define  NGX_DECLINED   -5
+#define  NGX_ABORT      -6
+
+
+#include <ngx_errno.h>
+#include <ngx_atomic.h>
+#include <ngx_thread.h>
+#include <ngx_rbtree.h>
+#include <ngx_time.h>
+#include <ngx_socket.h>
+#include <ngx_string.h>
+#include <ngx_files.h>
+#include <ngx_shmem.h>
+#include <ngx_process.h>
+#include <ngx_user.h>
+#include <ngx_dlopen.h>
+#include <ngx_parse.h>
+#include <ngx_parse_time.h>
+#include <ngx_log.h>
+#include <ngx_alloc.h>
+#include <ngx_palloc.h>
+#include <ngx_buf.h>
+#include <ngx_queue.h>
+#include <ngx_array.h>
+#include <ngx_list.h>
+#include <ngx_hash.h>
+#include <ngx_file.h>
+#include <ngx_crc.h>
+#include <ngx_crc32.h>
+#include <ngx_murmurhash.h>
+#if (NGX_PCRE)
+#include <ngx_regex.h>
+#endif
+#include <ngx_radix_tree.h>
+#include <ngx_times.h>
+#include <ngx_rwlock.h>
+#include <ngx_shmtx.h>
+#include <ngx_slab.h>
+#include <ngx_inet.h>
+#include <ngx_cycle.h>
+#include <ngx_resolver.h>
+#if (NGX_OPENSSL)
+#include <ngx_event_openssl.h>
+#endif
+#include <ngx_process_cycle.h>
+#include <ngx_conf_file.h>
+#include <ngx_module.h>
+#include <ngx_open_file_cache.h>
+#include <ngx_os.h>
+#include <ngx_connection.h>
+#include <ngx_syslog.h>
+#include <ngx_proxy_protocol.h>
+
+
+#define LF     (u_char) '\n'
+#define CR     (u_char) '\r'
+#define CRLF   "\r\n"
+
+
+#define ngx_abs(value)       (((value) >= 0) ? (value) : - (value))
+#define ngx_max(val1, val2)  ((val1 < val2) ? (val2) : (val1))
+#define ngx_min(val1, val2)  ((val1 > val2) ? (val2) : (val1))
+
+void ngx_cpuinfo(void);
+
+#if (NGX_HAVE_OPENAT)
+#define NGX_DISABLE_SYMLINKS_OFF        0
+#define NGX_DISABLE_SYMLINKS_ON         1
+#define NGX_DISABLE_SYMLINKS_NOTOWNER   2
+#endif
+
+#endif /* _NGX_CORE_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_cpuinfo.c b/nginx/src/core/ngx_cpuinfo.c
new file mode 100644 (file)
index 0000000..7205319
--- /dev/null
@@ -0,0 +1,139 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#if (( __i386__ || __amd64__ ) && ( __GNUC__ || __INTEL_COMPILER ))
+
+
+static ngx_inline void ngx_cpuid(uint32_t i, uint32_t *buf);
+
+
+#if ( __i386__ )
+
+static ngx_inline void
+ngx_cpuid(uint32_t i, uint32_t *buf)
+{
+
+    /*
+     * we could not use %ebx as output parameter if gcc builds PIC,
+     * and we could not save %ebx on stack, because %esp is used,
+     * when the -fomit-frame-pointer optimization is specified.
+     */
+
+    __asm__ (
+
+    "    mov    %%ebx, %%esi;  "
+
+    "    cpuid;                "
+    "    mov    %%eax, (%1);   "
+    "    mov    %%ebx, 4(%1);  "
+    "    mov    %%edx, 8(%1);  "
+    "    mov    %%ecx, 12(%1); "
+
+    "    mov    %%esi, %%ebx;  "
+
+    : : "a" (i), "D" (buf) : "ecx", "edx", "esi", "memory" );
+}
+
+
+#else /* __amd64__ */
+
+
+static ngx_inline void
+ngx_cpuid(uint32_t i, uint32_t *buf)
+{
+    uint32_t  eax, ebx, ecx, edx;
+
+    __asm__ (
+
+        "cpuid"
+
+    : "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx) : "a" (i) );
+
+    buf[0] = eax;
+    buf[1] = ebx;
+    buf[2] = edx;
+    buf[3] = ecx;
+}
+
+
+#endif
+
+
+/* auto detect the L2 cache line size of modern and widespread CPUs */
+
+void
+ngx_cpuinfo(void)
+{
+    u_char    *vendor;
+    uint32_t   vbuf[5], cpu[4], model;
+
+    vbuf[0] = 0;
+    vbuf[1] = 0;
+    vbuf[2] = 0;
+    vbuf[3] = 0;
+    vbuf[4] = 0;
+
+    ngx_cpuid(0, vbuf);
+
+    vendor = (u_char *) &vbuf[1];
+
+    if (vbuf[0] == 0) {
+        return;
+    }
+
+    ngx_cpuid(1, cpu);
+
+    if (ngx_strcmp(vendor, "GenuineIntel") == 0) {
+
+        switch ((cpu[0] & 0xf00) >> 8) {
+
+        /* Pentium */
+        case 5:
+            ngx_cacheline_size = 32;
+            break;
+
+        /* Pentium Pro, II, III */
+        case 6:
+            ngx_cacheline_size = 32;
+
+            model = ((cpu[0] & 0xf0000) >> 8) | (cpu[0] & 0xf0);
+
+            if (model >= 0xd0) {
+                /* Intel Core, Core 2, Atom */
+                ngx_cacheline_size = 64;
+            }
+
+            break;
+
+        /*
+         * Pentium 4, although its cache line size is 64 bytes,
+         * it prefetches up to two cache lines during memory read
+         */
+        case 15:
+            ngx_cacheline_size = 128;
+            break;
+        }
+
+    } else if (ngx_strcmp(vendor, "AuthenticAMD") == 0) {
+        ngx_cacheline_size = 64;
+    }
+}
+
+#else
+
+
+void
+ngx_cpuinfo(void)
+{
+}
+
+
+#endif
diff --git a/nginx/src/core/ngx_crc.h b/nginx/src/core/ngx_crc.h
new file mode 100644 (file)
index 0000000..35981bc
--- /dev/null
@@ -0,0 +1,39 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_CRC_H_INCLUDED_
+#define _NGX_CRC_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/* 32-bit crc16 */
+
+static ngx_inline uint32_t
+ngx_crc(u_char *data, size_t len)
+{
+    uint32_t  sum;
+
+    for (sum = 0; len; len--) {
+
+        /*
+         * gcc 2.95.2 x86 and icc 7.1.006 compile
+         * that operator into the single "rol" opcode,
+         * msvc 6.0sp2 compiles it into four opcodes.
+         */
+        sum = sum >> 1 | sum << 31;
+
+        sum += *data++;
+    }
+
+    return sum;
+}
+
+
+#endif /* _NGX_CRC_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_crc32.c b/nginx/src/core/ngx_crc32.c
new file mode 100644 (file)
index 0000000..a5b4017
--- /dev/null
@@ -0,0 +1,129 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/*
+ * The code and lookup tables are based on the algorithm
+ * described at http://www.w3.org/TR/PNG/
+ *
+ * The 256 element lookup table takes 1024 bytes, and it may be completely
+ * cached after processing about 30-60 bytes of data.  So for short data
+ * we use the 16 element lookup table that takes only 64 bytes and align it
+ * to CPU cache line size.  Of course, the small table adds code inside
+ * CRC32 loop, but the cache misses overhead is bigger than overhead of
+ * the additional code.  For example, ngx_crc32_short() of 16 bytes of data
+ * takes half as much CPU clocks than ngx_crc32_long().
+ */
+
+
+static uint32_t  ngx_crc32_table16[] = {
+    0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
+    0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
+    0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
+    0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
+};
+
+
+uint32_t  ngx_crc32_table256[] = {
+    0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
+    0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
+    0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+    0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
+    0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
+    0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+    0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
+    0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
+    0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+    0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
+    0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
+    0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+    0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
+    0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
+    0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+    0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
+    0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
+    0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+    0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
+    0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
+    0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+    0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
+    0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
+    0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+    0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
+    0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
+    0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+    0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
+    0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
+    0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+    0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
+    0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
+    0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+    0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
+    0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
+    0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+    0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
+    0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
+    0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+    0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+    0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
+    0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+    0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
+    0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
+    0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+    0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
+    0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
+    0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+    0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
+    0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
+    0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+    0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
+    0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
+    0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+    0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
+    0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
+    0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+    0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
+    0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
+    0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+    0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
+    0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
+    0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+    0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+};
+
+
+uint32_t *ngx_crc32_table_short = ngx_crc32_table16;
+
+
+ngx_int_t
+ngx_crc32_table_init(void)
+{
+    void  *p;
+
+    if (((uintptr_t) ngx_crc32_table_short
+          & ~((uintptr_t) ngx_cacheline_size - 1))
+        == (uintptr_t) ngx_crc32_table_short)
+    {
+        return NGX_OK;
+    }
+
+    p = ngx_alloc(16 * sizeof(uint32_t) + ngx_cacheline_size, ngx_cycle->log);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    p = ngx_align_ptr(p, ngx_cacheline_size);
+
+    ngx_memcpy(p, ngx_crc32_table16, 16 * sizeof(uint32_t));
+
+    ngx_crc32_table_short = p;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/core/ngx_crc32.h b/nginx/src/core/ngx_crc32.h
new file mode 100644 (file)
index 0000000..f6d6865
--- /dev/null
@@ -0,0 +1,79 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_CRC32_H_INCLUDED_
+#define _NGX_CRC32_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+extern uint32_t  *ngx_crc32_table_short;
+extern uint32_t   ngx_crc32_table256[];
+
+
+static ngx_inline uint32_t
+ngx_crc32_short(u_char *p, size_t len)
+{
+    u_char    c;
+    uint32_t  crc;
+
+    crc = 0xffffffff;
+
+    while (len--) {
+        c = *p++;
+        crc = ngx_crc32_table_short[(crc ^ (c & 0xf)) & 0xf] ^ (crc >> 4);
+        crc = ngx_crc32_table_short[(crc ^ (c >> 4)) & 0xf] ^ (crc >> 4);
+    }
+
+    return crc ^ 0xffffffff;
+}
+
+
+static ngx_inline uint32_t
+ngx_crc32_long(u_char *p, size_t len)
+{
+    uint32_t  crc;
+
+    crc = 0xffffffff;
+
+    while (len--) {
+        crc = ngx_crc32_table256[(crc ^ *p++) & 0xff] ^ (crc >> 8);
+    }
+
+    return crc ^ 0xffffffff;
+}
+
+
+#define ngx_crc32_init(crc)                                                   \
+    crc = 0xffffffff
+
+
+static ngx_inline void
+ngx_crc32_update(uint32_t *crc, u_char *p, size_t len)
+{
+    uint32_t  c;
+
+    c = *crc;
+
+    while (len--) {
+        c = ngx_crc32_table256[(c ^ *p++) & 0xff] ^ (c >> 8);
+    }
+
+    *crc = c;
+}
+
+
+#define ngx_crc32_final(crc)                                                  \
+    crc ^= 0xffffffff
+
+
+ngx_int_t ngx_crc32_table_init(void);
+
+
+#endif /* _NGX_CRC32_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_crypt.c b/nginx/src/core/ngx_crypt.c
new file mode 100644 (file)
index 0000000..868dc5d
--- /dev/null
@@ -0,0 +1,270 @@
+
+/*
+ * Copyright (C) Maxim Dounin
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_crypt.h>
+#include <ngx_md5.h>
+#include <ngx_sha1.h>
+
+
+#if (NGX_CRYPT)
+
+static ngx_int_t ngx_crypt_apr1(ngx_pool_t *pool, u_char *key, u_char *salt,
+    u_char **encrypted);
+static ngx_int_t ngx_crypt_plain(ngx_pool_t *pool, u_char *key, u_char *salt,
+    u_char **encrypted);
+static ngx_int_t ngx_crypt_ssha(ngx_pool_t *pool, u_char *key, u_char *salt,
+    u_char **encrypted);
+static ngx_int_t ngx_crypt_sha(ngx_pool_t *pool, u_char *key, u_char *salt,
+    u_char **encrypted);
+
+
+static u_char *ngx_crypt_to64(u_char *p, uint32_t v, size_t n);
+
+
+ngx_int_t
+ngx_crypt(ngx_pool_t *pool, u_char *key, u_char *salt, u_char **encrypted)
+{
+    if (ngx_strncmp(salt, "$apr1$", sizeof("$apr1$") - 1) == 0) {
+        return ngx_crypt_apr1(pool, key, salt, encrypted);
+
+    } else if (ngx_strncmp(salt, "{PLAIN}", sizeof("{PLAIN}") - 1) == 0) {
+        return ngx_crypt_plain(pool, key, salt, encrypted);
+
+    } else if (ngx_strncmp(salt, "{SSHA}", sizeof("{SSHA}") - 1) == 0) {
+        return ngx_crypt_ssha(pool, key, salt, encrypted);
+
+    } else if (ngx_strncmp(salt, "{SHA}", sizeof("{SHA}") - 1) == 0) {
+        return ngx_crypt_sha(pool, key, salt, encrypted);
+    }
+
+    /* fallback to libc crypt() */
+
+    return ngx_libc_crypt(pool, key, salt, encrypted);
+}
+
+
+static ngx_int_t
+ngx_crypt_apr1(ngx_pool_t *pool, u_char *key, u_char *salt, u_char **encrypted)
+{
+    ngx_int_t          n;
+    ngx_uint_t         i;
+    u_char            *p, *last, final[16];
+    size_t             saltlen, keylen;
+    ngx_md5_t          md5, ctx1;
+
+    /* Apache's apr1 crypt is Poul-Henning Kamp's md5 crypt with $apr1$ magic */
+
+    keylen = ngx_strlen(key);
+
+    /* true salt: no magic, max 8 chars, stop at first $ */
+
+    salt += sizeof("$apr1$") - 1;
+    last = salt + 8;
+    for (p = salt; *p && *p != '$' && p < last; p++) { /* void */ }
+    saltlen = p - salt;
+
+    /* hash key and salt */
+
+    ngx_md5_init(&md5);
+    ngx_md5_update(&md5, key, keylen);
+    ngx_md5_update(&md5, (u_char *) "$apr1$", sizeof("$apr1$") - 1);
+    ngx_md5_update(&md5, salt, saltlen);
+
+    ngx_md5_init(&ctx1);
+    ngx_md5_update(&ctx1, key, keylen);
+    ngx_md5_update(&ctx1, salt, saltlen);
+    ngx_md5_update(&ctx1, key, keylen);
+    ngx_md5_final(final, &ctx1);
+
+    for (n = keylen; n > 0; n -= 16) {
+        ngx_md5_update(&md5, final, n > 16 ? 16 : n);
+    }
+
+    ngx_memzero(final, sizeof(final));
+
+    for (i = keylen; i; i >>= 1) {
+        if (i & 1) {
+            ngx_md5_update(&md5, final, 1);
+
+        } else {
+            ngx_md5_update(&md5, key, 1);
+        }
+    }
+
+    ngx_md5_final(final, &md5);
+
+    for (i = 0; i < 1000; i++) {
+        ngx_md5_init(&ctx1);
+
+        if (i & 1) {
+            ngx_md5_update(&ctx1, key, keylen);
+
+        } else {
+            ngx_md5_update(&ctx1, final, 16);
+        }
+
+        if (i % 3) {
+            ngx_md5_update(&ctx1, salt, saltlen);
+        }
+
+        if (i % 7) {
+            ngx_md5_update(&ctx1, key, keylen);
+        }
+
+        if (i & 1) {
+            ngx_md5_update(&ctx1, final, 16);
+
+        } else {
+            ngx_md5_update(&ctx1, key, keylen);
+        }
+
+        ngx_md5_final(final, &ctx1);
+    }
+
+    /* output */
+
+    *encrypted = ngx_pnalloc(pool, sizeof("$apr1$") - 1 + saltlen + 1 + 22 + 1);
+    if (*encrypted == NULL) {
+        return NGX_ERROR;
+    }
+
+    p = ngx_cpymem(*encrypted, "$apr1$", sizeof("$apr1$") - 1);
+    p = ngx_copy(p, salt, saltlen);
+    *p++ = '$';
+
+    p = ngx_crypt_to64(p, (final[ 0]<<16) | (final[ 6]<<8) | final[12], 4);
+    p = ngx_crypt_to64(p, (final[ 1]<<16) | (final[ 7]<<8) | final[13], 4);
+    p = ngx_crypt_to64(p, (final[ 2]<<16) | (final[ 8]<<8) | final[14], 4);
+    p = ngx_crypt_to64(p, (final[ 3]<<16) | (final[ 9]<<8) | final[15], 4);
+    p = ngx_crypt_to64(p, (final[ 4]<<16) | (final[10]<<8) | final[ 5], 4);
+    p = ngx_crypt_to64(p, final[11], 2);
+    *p = '\0';
+
+    return NGX_OK;
+}
+
+
+static u_char *
+ngx_crypt_to64(u_char *p, uint32_t v, size_t n)
+{
+    static u_char   itoa64[] =
+        "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+    while (n--) {
+        *p++ = itoa64[v & 0x3f];
+        v >>= 6;
+    }
+
+    return p;
+}
+
+
+static ngx_int_t
+ngx_crypt_plain(ngx_pool_t *pool, u_char *key, u_char *salt, u_char **encrypted)
+{
+    size_t   len;
+    u_char  *p;
+
+    len = ngx_strlen(key);
+
+    *encrypted = ngx_pnalloc(pool, sizeof("{PLAIN}") - 1 + len + 1);
+    if (*encrypted == NULL) {
+        return NGX_ERROR;
+    }
+
+    p = ngx_cpymem(*encrypted, "{PLAIN}", sizeof("{PLAIN}") - 1);
+    ngx_memcpy(p, key, len + 1);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_crypt_ssha(ngx_pool_t *pool, u_char *key, u_char *salt, u_char **encrypted)
+{
+    size_t       len;
+    ngx_int_t    rc;
+    ngx_str_t    encoded, decoded;
+    ngx_sha1_t   sha1;
+
+    /* "{SSHA}" base64(SHA1(key salt) salt) */
+
+    /* decode base64 salt to find out true salt */
+
+    encoded.data = salt + sizeof("{SSHA}") - 1;
+    encoded.len = ngx_strlen(encoded.data);
+
+    len = ngx_max(ngx_base64_decoded_length(encoded.len), 20);
+
+    decoded.data = ngx_pnalloc(pool, len);
+    if (decoded.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    rc = ngx_decode_base64(&decoded, &encoded);
+
+    if (rc != NGX_OK || decoded.len < 20) {
+        decoded.len = 20;
+    }
+
+    /* update SHA1 from key and salt */
+
+    ngx_sha1_init(&sha1);
+    ngx_sha1_update(&sha1, key, ngx_strlen(key));
+    ngx_sha1_update(&sha1, decoded.data + 20, decoded.len - 20);
+    ngx_sha1_final(decoded.data, &sha1);
+
+    /* encode it back to base64 */
+
+    len = sizeof("{SSHA}") - 1 + ngx_base64_encoded_length(decoded.len) + 1;
+
+    *encrypted = ngx_pnalloc(pool, len);
+    if (*encrypted == NULL) {
+        return NGX_ERROR;
+    }
+
+    encoded.data = ngx_cpymem(*encrypted, "{SSHA}", sizeof("{SSHA}") - 1);
+    ngx_encode_base64(&encoded, &decoded);
+    encoded.data[encoded.len] = '\0';
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_crypt_sha(ngx_pool_t *pool, u_char *key, u_char *salt, u_char **encrypted)
+{
+    size_t      len;
+    ngx_str_t   encoded, decoded;
+    ngx_sha1_t  sha1;
+    u_char      digest[20];
+
+    /* "{SHA}" base64(SHA1(key)) */
+
+    decoded.len = sizeof(digest);
+    decoded.data = digest;
+
+    ngx_sha1_init(&sha1);
+    ngx_sha1_update(&sha1, key, ngx_strlen(key));
+    ngx_sha1_final(digest, &sha1);
+
+    len = sizeof("{SHA}") - 1 + ngx_base64_encoded_length(decoded.len) + 1;
+
+    *encrypted = ngx_pnalloc(pool, len);
+    if (*encrypted == NULL) {
+        return NGX_ERROR;
+    }
+
+    encoded.data = ngx_cpymem(*encrypted, "{SHA}", sizeof("{SHA}") - 1);
+    ngx_encode_base64(&encoded, &decoded);
+    encoded.data[encoded.len] = '\0';
+
+    return NGX_OK;
+}
+
+#endif /* NGX_CRYPT */
diff --git a/nginx/src/core/ngx_crypt.h b/nginx/src/core/ngx_crypt.h
new file mode 100644 (file)
index 0000000..3869114
--- /dev/null
@@ -0,0 +1,20 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_CRYPT_H_INCLUDED_
+#define _NGX_CRYPT_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+ngx_int_t ngx_crypt(ngx_pool_t *pool, u_char *key, u_char *salt,
+    u_char **encrypted);
+
+
+#endif /* _NGX_CRYPT_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_cycle.c b/nginx/src/core/ngx_cycle.c
new file mode 100644 (file)
index 0000000..f3ac24d
--- /dev/null
@@ -0,0 +1,1392 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+static void ngx_destroy_cycle_pools(ngx_conf_t *conf);
+static ngx_int_t ngx_init_zone_pool(ngx_cycle_t *cycle,
+    ngx_shm_zone_t *shm_zone);
+static ngx_int_t ngx_test_lockfile(u_char *file, ngx_log_t *log);
+static void ngx_clean_old_cycles(ngx_event_t *ev);
+static void ngx_shutdown_timer_handler(ngx_event_t *ev);
+
+
+volatile ngx_cycle_t  *ngx_cycle;
+ngx_array_t            ngx_old_cycles;
+
+static ngx_pool_t     *ngx_temp_pool;
+static ngx_event_t     ngx_cleaner_event;
+static ngx_event_t     ngx_shutdown_event;
+
+ngx_uint_t             ngx_test_config;
+ngx_uint_t             ngx_dump_config;
+ngx_uint_t             ngx_quiet_mode;
+
+
+/* STUB NAME */
+static ngx_connection_t  dumb;
+/* STUB */
+
+
+ngx_cycle_t *
+ngx_init_cycle(ngx_cycle_t *old_cycle)
+{
+    void                *rv;
+    char               **senv;
+    ngx_uint_t           i, n;
+    ngx_log_t           *log;
+    ngx_time_t          *tp;
+    ngx_conf_t           conf;
+    ngx_pool_t          *pool;
+    ngx_cycle_t         *cycle, **old;
+    ngx_shm_zone_t      *shm_zone, *oshm_zone;
+    ngx_list_part_t     *part, *opart;
+    ngx_open_file_t     *file;
+    ngx_listening_t     *ls, *nls;
+    ngx_core_conf_t     *ccf, *old_ccf;
+    ngx_core_module_t   *module;
+    char                 hostname[NGX_MAXHOSTNAMELEN];
+
+    ngx_timezone_update();
+
+    /* force localtime update with a new timezone */
+
+    tp = ngx_timeofday();
+    tp->sec = 0;
+
+    ngx_time_update();
+
+
+    log = old_cycle->log;
+
+    pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log);
+    if (pool == NULL) {
+        return NULL;
+    }
+    pool->log = log;
+
+    cycle = ngx_pcalloc(pool, sizeof(ngx_cycle_t));
+    if (cycle == NULL) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+    cycle->pool = pool;
+    cycle->log = log;
+    cycle->old_cycle = old_cycle;
+
+    cycle->conf_prefix.len = old_cycle->conf_prefix.len;
+    cycle->conf_prefix.data = ngx_pstrdup(pool, &old_cycle->conf_prefix);
+    if (cycle->conf_prefix.data == NULL) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+    cycle->prefix.len = old_cycle->prefix.len;
+    cycle->prefix.data = ngx_pstrdup(pool, &old_cycle->prefix);
+    if (cycle->prefix.data == NULL) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+    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) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+    ngx_cpystrn(cycle->conf_file.data, old_cycle->conf_file.data,
+                old_cycle->conf_file.len + 1);
+
+    cycle->conf_param.len = old_cycle->conf_param.len;
+    cycle->conf_param.data = ngx_pstrdup(pool, &old_cycle->conf_param);
+    if (cycle->conf_param.data == NULL) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+
+    n = old_cycle->paths.nelts ? old_cycle->paths.nelts : 10;
+
+    if (ngx_array_init(&cycle->paths, pool, n, sizeof(ngx_path_t *))
+        != NGX_OK)
+    {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+    ngx_memzero(cycle->paths.elts, n * sizeof(ngx_path_t *));
+
+
+    if (ngx_array_init(&cycle->config_dump, pool, 1, sizeof(ngx_conf_dump_t))
+        != NGX_OK)
+    {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+    ngx_rbtree_init(&cycle->config_dump_rbtree, &cycle->config_dump_sentinel,
+                    ngx_str_rbtree_insert_value);
+
+    if (old_cycle->open_files.part.nelts) {
+        n = old_cycle->open_files.part.nelts;
+        for (part = old_cycle->open_files.part.next; part; part = part->next) {
+            n += part->nelts;
+        }
+
+    } else {
+        n = 20;
+    }
+
+    if (ngx_list_init(&cycle->open_files, pool, n, sizeof(ngx_open_file_t))
+        != NGX_OK)
+    {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+
+    if (old_cycle->shared_memory.part.nelts) {
+        n = old_cycle->shared_memory.part.nelts;
+        for (part = old_cycle->shared_memory.part.next; part; part = part->next)
+        {
+            n += part->nelts;
+        }
+
+    } else {
+        n = 1;
+    }
+
+    if (ngx_list_init(&cycle->shared_memory, pool, n, sizeof(ngx_shm_zone_t))
+        != NGX_OK)
+    {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+    n = old_cycle->listening.nelts ? old_cycle->listening.nelts : 10;
+
+    if (ngx_array_init(&cycle->listening, pool, n, sizeof(ngx_listening_t))
+        != NGX_OK)
+    {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+    ngx_memzero(cycle->listening.elts, n * sizeof(ngx_listening_t));
+
+
+    ngx_queue_init(&cycle->reusable_connections_queue);
+
+
+    cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));
+    if (cycle->conf_ctx == NULL) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+
+    if (gethostname(hostname, NGX_MAXHOSTNAMELEN) == -1) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "gethostname() failed");
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+    /* on Linux gethostname() silently truncates name that does not fit */
+
+    hostname[NGX_MAXHOSTNAMELEN - 1] = '\0';
+    cycle->hostname.len = ngx_strlen(hostname);
+
+    cycle->hostname.data = ngx_pnalloc(pool, cycle->hostname.len);
+    if (cycle->hostname.data == NULL) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+    ngx_strlow(cycle->hostname.data, (u_char *) hostname, cycle->hostname.len);
+
+
+    if (ngx_cycle_modules(cycle) != NGX_OK) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+
+    for (i = 0; cycle->modules[i]; i++) {
+        if (cycle->modules[i]->type != NGX_CORE_MODULE) {
+            continue;
+        }
+
+        module = cycle->modules[i]->ctx;
+
+        if (module->create_conf) {
+            rv = module->create_conf(cycle);
+            if (rv == NULL) {
+                ngx_destroy_pool(pool);
+                return NULL;
+            }
+            cycle->conf_ctx[cycle->modules[i]->index] = rv;
+        }
+    }
+
+
+    senv = environ;
+
+
+    ngx_memzero(&conf, sizeof(ngx_conf_t));
+    /* STUB: init array ? */
+    conf.args = ngx_array_create(pool, 10, sizeof(ngx_str_t));
+    if (conf.args == NULL) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+    conf.temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log);
+    if (conf.temp_pool == NULL) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+
+    conf.ctx = cycle->conf_ctx;
+    conf.cycle = cycle;
+    conf.pool = pool;
+    conf.log = log;
+    conf.module_type = NGX_CORE_MODULE;
+    conf.cmd_type = NGX_MAIN_CONF;
+
+#if 0
+    log->log_level = NGX_LOG_DEBUG_ALL;
+#endif
+
+    if (ngx_conf_param(&conf) != NGX_CONF_OK) {
+        environ = senv;
+        ngx_destroy_cycle_pools(&conf);
+        return NULL;
+    }
+
+    if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
+        environ = senv;
+        ngx_destroy_cycle_pools(&conf);
+        return NULL;
+    }
+
+    if (ngx_test_config && !ngx_quiet_mode) {
+        ngx_log_stderr(0, "the configuration file %s syntax is ok",
+                       cycle->conf_file.data);
+    }
+
+    for (i = 0; cycle->modules[i]; i++) {
+        if (cycle->modules[i]->type != NGX_CORE_MODULE) {
+            continue;
+        }
+
+        module = cycle->modules[i]->ctx;
+
+        if (module->init_conf) {
+            if (module->init_conf(cycle,
+                                  cycle->conf_ctx[cycle->modules[i]->index])
+                == NGX_CONF_ERROR)
+            {
+                environ = senv;
+                ngx_destroy_cycle_pools(&conf);
+                return NULL;
+            }
+        }
+    }
+
+    if (ngx_process == NGX_PROCESS_SIGNALLER) {
+        return cycle;
+    }
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+    if (ngx_test_config) {
+
+        if (ngx_create_pidfile(&ccf->pid, log) != NGX_OK) {
+            goto failed;
+        }
+
+    } else if (!ngx_is_init_cycle(old_cycle)) {
+
+        /*
+         * we do not create the pid file in the first ngx_init_cycle() call
+         * because we need to write the demonized process pid
+         */
+
+        old_ccf = (ngx_core_conf_t *) ngx_get_conf(old_cycle->conf_ctx,
+                                                   ngx_core_module);
+        if (ccf->pid.len != old_ccf->pid.len
+            || ngx_strcmp(ccf->pid.data, old_ccf->pid.data) != 0)
+        {
+            /* new pid file name */
+
+            if (ngx_create_pidfile(&ccf->pid, log) != NGX_OK) {
+                goto failed;
+            }
+
+            ngx_delete_pidfile(old_cycle);
+        }
+    }
+
+
+    if (ngx_test_lockfile(cycle->lock_file.data, log) != NGX_OK) {
+        goto failed;
+    }
+
+
+    if (ngx_create_paths(cycle, ccf->user) != NGX_OK) {
+        goto failed;
+    }
+
+
+    if (ngx_log_open_default(cycle) != NGX_OK) {
+        goto failed;
+    }
+
+    /* open the new files */
+
+    part = &cycle->open_files.part;
+    file = part->elts;
+
+    for (i = 0; /* void */ ; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+            part = part->next;
+            file = part->elts;
+            i = 0;
+        }
+
+        if (file[i].name.len == 0) {
+            continue;
+        }
+
+        file[i].fd = ngx_open_file(file[i].name.data,
+                                   NGX_FILE_APPEND,
+                                   NGX_FILE_CREATE_OR_OPEN,
+                                   NGX_FILE_DEFAULT_ACCESS);
+
+        ngx_log_debug3(NGX_LOG_DEBUG_CORE, log, 0,
+                       "log: %p %d \"%s\"",
+                       &file[i], file[i].fd, file[i].name.data);
+
+        if (file[i].fd == NGX_INVALID_FILE) {
+            ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                          ngx_open_file_n " \"%s\" failed",
+                          file[i].name.data);
+            goto failed;
+        }
+
+#if !(NGX_WIN32)
+        if (fcntl(file[i].fd, F_SETFD, FD_CLOEXEC) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                          "fcntl(FD_CLOEXEC) \"%s\" failed",
+                          file[i].name.data);
+            goto failed;
+        }
+#endif
+    }
+
+    cycle->log = &cycle->new_log;
+    pool->log = &cycle->new_log;
+
+
+    /* create shared memory */
+
+    part = &cycle->shared_memory.part;
+    shm_zone = part->elts;
+
+    for (i = 0; /* void */ ; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+            part = part->next;
+            shm_zone = part->elts;
+            i = 0;
+        }
+
+        if (shm_zone[i].shm.size == 0) {
+            ngx_log_error(NGX_LOG_EMERG, log, 0,
+                          "zero size shared memory zone \"%V\"",
+                          &shm_zone[i].shm.name);
+            goto failed;
+        }
+
+        shm_zone[i].shm.log = cycle->log;
+
+        opart = &old_cycle->shared_memory.part;
+        oshm_zone = opart->elts;
+
+        for (n = 0; /* void */ ; n++) {
+
+            if (n >= opart->nelts) {
+                if (opart->next == NULL) {
+                    break;
+                }
+                opart = opart->next;
+                oshm_zone = opart->elts;
+                n = 0;
+            }
+
+            if (shm_zone[i].shm.name.len != oshm_zone[n].shm.name.len) {
+                continue;
+            }
+
+            if (ngx_strncmp(shm_zone[i].shm.name.data,
+                            oshm_zone[n].shm.name.data,
+                            shm_zone[i].shm.name.len)
+                != 0)
+            {
+                continue;
+            }
+
+            if (shm_zone[i].tag == oshm_zone[n].tag
+                && shm_zone[i].shm.size == oshm_zone[n].shm.size
+                && !shm_zone[i].noreuse)
+            {
+                shm_zone[i].shm.addr = oshm_zone[n].shm.addr;
+#if (NGX_WIN32)
+                shm_zone[i].shm.handle = oshm_zone[n].shm.handle;
+#endif
+
+                if (shm_zone[i].init(&shm_zone[i], oshm_zone[n].data)
+                    != NGX_OK)
+                {
+                    goto failed;
+                }
+
+                goto shm_zone_found;
+            }
+
+            break;
+        }
+
+        if (ngx_shm_alloc(&shm_zone[i].shm) != NGX_OK) {
+            goto failed;
+        }
+
+        if (ngx_init_zone_pool(cycle, &shm_zone[i]) != NGX_OK) {
+            goto failed;
+        }
+
+        if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) {
+            goto failed;
+        }
+
+    shm_zone_found:
+
+        continue;
+    }
+
+
+    /* handle the listening sockets */
+
+    if (old_cycle->listening.nelts) {
+        ls = old_cycle->listening.elts;
+        for (i = 0; i < old_cycle->listening.nelts; i++) {
+            ls[i].remain = 0;
+        }
+
+        nls = cycle->listening.elts;
+        for (n = 0; n < cycle->listening.nelts; n++) {
+
+            for (i = 0; i < old_cycle->listening.nelts; i++) {
+                if (ls[i].ignore) {
+                    continue;
+                }
+
+                if (ls[i].remain) {
+                    continue;
+                }
+
+                if (ls[i].type != nls[n].type) {
+                    continue;
+                }
+
+                if (ngx_cmp_sockaddr(nls[n].sockaddr, nls[n].socklen,
+                                     ls[i].sockaddr, ls[i].socklen, 1)
+                    == NGX_OK)
+                {
+                    nls[n].fd = ls[i].fd;
+                    nls[n].previous = &ls[i];
+                    ls[i].remain = 1;
+
+                    if (ls[i].backlog != nls[n].backlog) {
+                        nls[n].listen = 1;
+                    }
+
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
+
+                    /*
+                     * FreeBSD, except the most recent versions,
+                     * could not remove accept filter
+                     */
+                    nls[n].deferred_accept = ls[i].deferred_accept;
+
+                    if (ls[i].accept_filter && nls[n].accept_filter) {
+                        if (ngx_strcmp(ls[i].accept_filter,
+                                       nls[n].accept_filter)
+                            != 0)
+                        {
+                            nls[n].delete_deferred = 1;
+                            nls[n].add_deferred = 1;
+                        }
+
+                    } else if (ls[i].accept_filter) {
+                        nls[n].delete_deferred = 1;
+
+                    } else if (nls[n].accept_filter) {
+                        nls[n].add_deferred = 1;
+                    }
+#endif
+
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
+
+                    if (ls[i].deferred_accept && !nls[n].deferred_accept) {
+                        nls[n].delete_deferred = 1;
+
+                    } else if (ls[i].deferred_accept != nls[n].deferred_accept)
+                    {
+                        nls[n].add_deferred = 1;
+                    }
+#endif
+
+#if (NGX_HAVE_REUSEPORT)
+                    if (nls[n].reuseport && !ls[i].reuseport) {
+                        nls[n].add_reuseport = 1;
+                    }
+#endif
+
+                    break;
+                }
+            }
+
+            if (nls[n].fd == (ngx_socket_t) -1) {
+                nls[n].open = 1;
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
+                if (nls[n].accept_filter) {
+                    nls[n].add_deferred = 1;
+                }
+#endif
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
+                if (nls[n].deferred_accept) {
+                    nls[n].add_deferred = 1;
+                }
+#endif
+            }
+        }
+
+    } else {
+        ls = cycle->listening.elts;
+        for (i = 0; i < cycle->listening.nelts; i++) {
+            ls[i].open = 1;
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
+            if (ls[i].accept_filter) {
+                ls[i].add_deferred = 1;
+            }
+#endif
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
+            if (ls[i].deferred_accept) {
+                ls[i].add_deferred = 1;
+            }
+#endif
+        }
+    }
+
+    if (ngx_open_listening_sockets(cycle) != NGX_OK) {
+        goto failed;
+    }
+
+    if (!ngx_test_config) {
+        ngx_configure_listening_sockets(cycle);
+    }
+
+
+    /* commit the new cycle configuration */
+
+    if (!ngx_use_stderr) {
+        (void) ngx_log_redirect_stderr(cycle);
+    }
+
+    pool->log = cycle->log;
+
+    if (ngx_init_modules(cycle) != NGX_OK) {
+        /* fatal */
+        exit(1);
+    }
+
+
+    /* close and delete stuff that lefts from an old cycle */
+
+    /* free the unnecessary shared memory */
+
+    opart = &old_cycle->shared_memory.part;
+    oshm_zone = opart->elts;
+
+    for (i = 0; /* void */ ; i++) {
+
+        if (i >= opart->nelts) {
+            if (opart->next == NULL) {
+                goto old_shm_zone_done;
+            }
+            opart = opart->next;
+            oshm_zone = opart->elts;
+            i = 0;
+        }
+
+        part = &cycle->shared_memory.part;
+        shm_zone = part->elts;
+
+        for (n = 0; /* void */ ; n++) {
+
+            if (n >= part->nelts) {
+                if (part->next == NULL) {
+                    break;
+                }
+                part = part->next;
+                shm_zone = part->elts;
+                n = 0;
+            }
+
+            if (oshm_zone[i].shm.name.len != shm_zone[n].shm.name.len) {
+                continue;
+            }
+
+            if (ngx_strncmp(oshm_zone[i].shm.name.data,
+                            shm_zone[n].shm.name.data,
+                            oshm_zone[i].shm.name.len)
+                != 0)
+            {
+                continue;
+            }
+
+            if (oshm_zone[i].tag == shm_zone[n].tag
+                && oshm_zone[i].shm.size == shm_zone[n].shm.size
+                && !oshm_zone[i].noreuse)
+            {
+                goto live_shm_zone;
+            }
+
+            break;
+        }
+
+        ngx_shm_free(&oshm_zone[i].shm);
+
+    live_shm_zone:
+
+        continue;
+    }
+
+old_shm_zone_done:
+
+
+    /* close the unnecessary listening sockets */
+
+    ls = old_cycle->listening.elts;
+    for (i = 0; i < old_cycle->listening.nelts; i++) {
+
+        if (ls[i].remain || ls[i].fd == (ngx_socket_t) -1) {
+            continue;
+        }
+
+        if (ngx_close_socket(ls[i].fd) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
+                          ngx_close_socket_n " listening socket on %V failed",
+                          &ls[i].addr_text);
+        }
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+
+        if (ls[i].sockaddr->sa_family == AF_UNIX) {
+            u_char  *name;
+
+            name = ls[i].addr_text.data + sizeof("unix:") - 1;
+
+            ngx_log_error(NGX_LOG_WARN, cycle->log, 0,
+                          "deleting socket %s", name);
+
+            if (ngx_delete_file(name) == NGX_FILE_ERROR) {
+                ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
+                              ngx_delete_file_n " %s failed", name);
+            }
+        }
+
+#endif
+    }
+
+
+    /* close the unnecessary open files */
+
+    part = &old_cycle->open_files.part;
+    file = part->elts;
+
+    for (i = 0; /* void */ ; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+            part = part->next;
+            file = part->elts;
+            i = 0;
+        }
+
+        if (file[i].fd == NGX_INVALID_FILE || file[i].fd == ngx_stderr) {
+            continue;
+        }
+
+        if (ngx_close_file(file[i].fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                          ngx_close_file_n " \"%s\" failed",
+                          file[i].name.data);
+        }
+    }
+
+    ngx_destroy_pool(conf.temp_pool);
+
+    if (ngx_process == NGX_PROCESS_MASTER || ngx_is_init_cycle(old_cycle)) {
+
+        ngx_destroy_pool(old_cycle->pool);
+        cycle->old_cycle = NULL;
+
+        return cycle;
+    }
+
+
+    if (ngx_temp_pool == NULL) {
+        ngx_temp_pool = ngx_create_pool(128, cycle->log);
+        if (ngx_temp_pool == NULL) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+                          "could not create ngx_temp_pool");
+            exit(1);
+        }
+
+        n = 10;
+
+        if (ngx_array_init(&ngx_old_cycles, ngx_temp_pool, n,
+                           sizeof(ngx_cycle_t *))
+            != NGX_OK)
+        {
+            exit(1);
+        }
+
+        ngx_memzero(ngx_old_cycles.elts, n * sizeof(ngx_cycle_t *));
+
+        ngx_cleaner_event.handler = ngx_clean_old_cycles;
+        ngx_cleaner_event.log = cycle->log;
+        ngx_cleaner_event.data = &dumb;
+        dumb.fd = (ngx_socket_t) -1;
+    }
+
+    ngx_temp_pool->log = cycle->log;
+
+    old = ngx_array_push(&ngx_old_cycles);
+    if (old == NULL) {
+        exit(1);
+    }
+    *old = old_cycle;
+
+    if (!ngx_cleaner_event.timer_set) {
+        ngx_add_timer(&ngx_cleaner_event, 30000);
+        ngx_cleaner_event.timer_set = 1;
+    }
+
+    return cycle;
+
+
+failed:
+
+    if (!ngx_is_init_cycle(old_cycle)) {
+        old_ccf = (ngx_core_conf_t *) ngx_get_conf(old_cycle->conf_ctx,
+                                                   ngx_core_module);
+        if (old_ccf->environment) {
+            environ = old_ccf->environment;
+        }
+    }
+
+    /* rollback the new cycle configuration */
+
+    part = &cycle->open_files.part;
+    file = part->elts;
+
+    for (i = 0; /* void */ ; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+            part = part->next;
+            file = part->elts;
+            i = 0;
+        }
+
+        if (file[i].fd == NGX_INVALID_FILE || file[i].fd == ngx_stderr) {
+            continue;
+        }
+
+        if (ngx_close_file(file[i].fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                          ngx_close_file_n " \"%s\" failed",
+                          file[i].name.data);
+        }
+    }
+
+    if (ngx_test_config) {
+        ngx_destroy_cycle_pools(&conf);
+        return NULL;
+    }
+
+    ls = cycle->listening.elts;
+    for (i = 0; i < cycle->listening.nelts; i++) {
+        if (ls[i].fd == (ngx_socket_t) -1 || !ls[i].open) {
+            continue;
+        }
+
+        if (ngx_close_socket(ls[i].fd) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
+                          ngx_close_socket_n " %V failed",
+                          &ls[i].addr_text);
+        }
+    }
+
+    ngx_destroy_cycle_pools(&conf);
+
+    return NULL;
+}
+
+
+static void
+ngx_destroy_cycle_pools(ngx_conf_t *conf)
+{
+    ngx_destroy_pool(conf->temp_pool);
+    ngx_destroy_pool(conf->pool);
+}
+
+
+static ngx_int_t
+ngx_init_zone_pool(ngx_cycle_t *cycle, ngx_shm_zone_t *zn)
+{
+    u_char           *file;
+    ngx_slab_pool_t  *sp;
+
+    sp = (ngx_slab_pool_t *) zn->shm.addr;
+
+    if (zn->shm.exists) {
+
+        if (sp == sp->addr) {
+            return NGX_OK;
+        }
+
+#if (NGX_WIN32)
+
+        /* remap at the required address */
+
+        if (ngx_shm_remap(&zn->shm, sp->addr) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        sp = (ngx_slab_pool_t *) zn->shm.addr;
+
+        if (sp == sp->addr) {
+            return NGX_OK;
+        }
+
+#endif
+
+        ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+                      "shared zone \"%V\" has no equal addresses: %p vs %p",
+                      &zn->shm.name, sp->addr, sp);
+        return NGX_ERROR;
+    }
+
+    sp->end = zn->shm.addr + zn->shm.size;
+    sp->min_shift = 3;
+    sp->addr = zn->shm.addr;
+
+#if (NGX_HAVE_ATOMIC_OPS)
+
+    file = NULL;
+
+#else
+
+    file = ngx_pnalloc(cycle->pool, cycle->lock_file.len + zn->shm.name.len);
+    if (file == NULL) {
+        return NGX_ERROR;
+    }
+
+    (void) ngx_sprintf(file, "%V%V%Z", &cycle->lock_file, &zn->shm.name);
+
+#endif
+
+    if (ngx_shmtx_create(&sp->mutex, &sp->lock, file) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    ngx_slab_init(sp);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_create_pidfile(ngx_str_t *name, ngx_log_t *log)
+{
+    size_t      len;
+    ngx_uint_t  create;
+    ngx_file_t  file;
+    u_char      pid[NGX_INT64_LEN + 2];
+
+    if (ngx_process > NGX_PROCESS_MASTER) {
+        return NGX_OK;
+    }
+
+    ngx_memzero(&file, sizeof(ngx_file_t));
+
+    file.name = *name;
+    file.log = log;
+
+    create = ngx_test_config ? NGX_FILE_CREATE_OR_OPEN : NGX_FILE_TRUNCATE;
+
+    file.fd = ngx_open_file(file.name.data, NGX_FILE_RDWR,
+                            create, NGX_FILE_DEFAULT_ACCESS);
+
+    if (file.fd == NGX_INVALID_FILE) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                      ngx_open_file_n " \"%s\" failed", file.name.data);
+        return NGX_ERROR;
+    }
+
+    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;
+        }
+    }
+
+    if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      ngx_close_file_n " \"%s\" failed", file.name.data);
+    }
+
+    return NGX_OK;
+}
+
+
+void
+ngx_delete_pidfile(ngx_cycle_t *cycle)
+{
+    u_char           *name;
+    ngx_core_conf_t  *ccf;
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+    name = ngx_new_binary ? ccf->oldpid.data : ccf->pid.data;
+
+    if (ngx_delete_file(name) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      ngx_delete_file_n " \"%s\" failed", name);
+    }
+}
+
+
+ngx_int_t
+ngx_signal_process(ngx_cycle_t *cycle, char *sig)
+{
+    ssize_t           n;
+    ngx_pid_t         pid;
+    ngx_file_t        file;
+    ngx_core_conf_t  *ccf;
+    u_char            buf[NGX_INT64_LEN + 2];
+
+    ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "signal process started");
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+    ngx_memzero(&file, sizeof(ngx_file_t));
+
+    file.name = ccf->pid;
+    file.log = cycle->log;
+
+    file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY,
+                            NGX_FILE_OPEN, NGX_FILE_DEFAULT_ACCESS);
+
+    if (file.fd == NGX_INVALID_FILE) {
+        ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno,
+                      ngx_open_file_n " \"%s\" failed", file.name.data);
+        return 1;
+    }
+
+    n = ngx_read_file(&file, buf, NGX_INT64_LEN + 2, 0);
+
+    if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      ngx_close_file_n " \"%s\" failed", file.name.data);
+    }
+
+    if (n == NGX_ERROR) {
+        return 1;
+    }
+
+    while (n-- && (buf[n] == CR || buf[n] == LF)) { /* void */ }
+
+    pid = ngx_atoi(buf, ++n);
+
+    if (pid == (ngx_pid_t) NGX_ERROR) {
+        ngx_log_error(NGX_LOG_ERR, cycle->log, 0,
+                      "invalid PID number \"%*s\" in \"%s\"",
+                      n, buf, file.name.data);
+        return 1;
+    }
+
+    return ngx_os_signal_process(cycle, sig, pid);
+
+}
+
+
+static ngx_int_t
+ngx_test_lockfile(u_char *file, ngx_log_t *log)
+{
+#if !(NGX_HAVE_ATOMIC_OPS)
+    ngx_fd_t  fd;
+
+    fd = ngx_open_file(file, NGX_FILE_RDWR, NGX_FILE_CREATE_OR_OPEN,
+                       NGX_FILE_DEFAULT_ACCESS);
+
+    if (fd == NGX_INVALID_FILE) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                      ngx_open_file_n " \"%s\" failed", file);
+        return NGX_ERROR;
+    }
+
+    if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      ngx_close_file_n " \"%s\" failed", file);
+    }
+
+    if (ngx_delete_file(file) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      ngx_delete_file_n " \"%s\" failed", file);
+    }
+
+#endif
+
+    return NGX_OK;
+}
+
+
+void
+ngx_reopen_files(ngx_cycle_t *cycle, ngx_uid_t user)
+{
+    ngx_fd_t          fd;
+    ngx_uint_t        i;
+    ngx_list_part_t  *part;
+    ngx_open_file_t  *file;
+
+    part = &cycle->open_files.part;
+    file = part->elts;
+
+    for (i = 0; /* void */ ; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+            part = part->next;
+            file = part->elts;
+            i = 0;
+        }
+
+        if (file[i].name.len == 0) {
+            continue;
+        }
+
+        if (file[i].flush) {
+            file[i].flush(&file[i], cycle->log);
+        }
+
+        fd = ngx_open_file(file[i].name.data, NGX_FILE_APPEND,
+                           NGX_FILE_CREATE_OR_OPEN, NGX_FILE_DEFAULT_ACCESS);
+
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "reopen file \"%s\", old:%d new:%d",
+                       file[i].name.data, file[i].fd, fd);
+
+        if (fd == NGX_INVALID_FILE) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          ngx_open_file_n " \"%s\" failed", file[i].name.data);
+            continue;
+        }
+
+#if !(NGX_WIN32)
+        if (user != (ngx_uid_t) NGX_CONF_UNSET_UINT) {
+            ngx_file_info_t  fi;
+
+            if (ngx_file_info(file[i].name.data, &fi) == NGX_FILE_ERROR) {
+                ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                              ngx_file_info_n " \"%s\" failed",
+                              file[i].name.data);
+
+                if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+                    ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                                  ngx_close_file_n " \"%s\" failed",
+                                  file[i].name.data);
+                }
+
+                continue;
+            }
+
+            if (fi.st_uid != user) {
+                if (chown((const char *) file[i].name.data, user, -1) == -1) {
+                    ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                                  "chown(\"%s\", %d) failed",
+                                  file[i].name.data, user);
+
+                    if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+                        ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                                      ngx_close_file_n " \"%s\" failed",
+                                      file[i].name.data);
+                    }
+
+                    continue;
+                }
+            }
+
+            if ((fi.st_mode & (S_IRUSR|S_IWUSR)) != (S_IRUSR|S_IWUSR)) {
+
+                fi.st_mode |= (S_IRUSR|S_IWUSR);
+
+                if (chmod((const char *) file[i].name.data, fi.st_mode) == -1) {
+                    ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                                  "chmod() \"%s\" failed", file[i].name.data);
+
+                    if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+                        ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                                      ngx_close_file_n " \"%s\" failed",
+                                      file[i].name.data);
+                    }
+
+                    continue;
+                }
+            }
+        }
+
+        if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          "fcntl(FD_CLOEXEC) \"%s\" failed",
+                          file[i].name.data);
+
+            if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+                ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                              ngx_close_file_n " \"%s\" failed",
+                              file[i].name.data);
+            }
+
+            continue;
+        }
+#endif
+
+        if (ngx_close_file(file[i].fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          ngx_close_file_n " \"%s\" failed",
+                          file[i].name.data);
+        }
+
+        file[i].fd = fd;
+    }
+
+    (void) ngx_log_redirect_stderr(cycle);
+}
+
+
+ngx_shm_zone_t *
+ngx_shared_memory_add(ngx_conf_t *cf, ngx_str_t *name, size_t size, void *tag)
+{
+    ngx_uint_t        i;
+    ngx_shm_zone_t   *shm_zone;
+    ngx_list_part_t  *part;
+
+    part = &cf->cycle->shared_memory.part;
+    shm_zone = part->elts;
+
+    for (i = 0; /* void */ ; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+            part = part->next;
+            shm_zone = part->elts;
+            i = 0;
+        }
+
+        if (name->len != shm_zone[i].shm.name.len) {
+            continue;
+        }
+
+        if (ngx_strncmp(name->data, shm_zone[i].shm.name.data, name->len)
+            != 0)
+        {
+            continue;
+        }
+
+        if (tag != shm_zone[i].tag) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                            "the shared memory zone \"%V\" is "
+                            "already declared for a different use",
+                            &shm_zone[i].shm.name);
+            return NULL;
+        }
+
+        if (shm_zone[i].shm.size == 0) {
+            shm_zone[i].shm.size = size;
+        }
+
+        if (size && size != shm_zone[i].shm.size) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                            "the size %uz of shared memory zone \"%V\" "
+                            "conflicts with already declared size %uz",
+                            size, &shm_zone[i].shm.name, shm_zone[i].shm.size);
+            return NULL;
+        }
+
+        return &shm_zone[i];
+    }
+
+    shm_zone = ngx_list_push(&cf->cycle->shared_memory);
+
+    if (shm_zone == NULL) {
+        return NULL;
+    }
+
+    shm_zone->data = NULL;
+    shm_zone->shm.log = cf->cycle->log;
+    shm_zone->shm.size = size;
+    shm_zone->shm.name = *name;
+    shm_zone->shm.exists = 0;
+    shm_zone->init = NULL;
+    shm_zone->tag = tag;
+    shm_zone->noreuse = 0;
+
+    return shm_zone;
+}
+
+
+static void
+ngx_clean_old_cycles(ngx_event_t *ev)
+{
+    ngx_uint_t     i, n, found, live;
+    ngx_log_t     *log;
+    ngx_cycle_t  **cycle;
+
+    log = ngx_cycle->log;
+    ngx_temp_pool->log = log;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_CORE, log, 0, "clean old cycles");
+
+    live = 0;
+
+    cycle = ngx_old_cycles.elts;
+    for (i = 0; i < ngx_old_cycles.nelts; i++) {
+
+        if (cycle[i] == NULL) {
+            continue;
+        }
+
+        found = 0;
+
+        for (n = 0; n < cycle[i]->connection_n; n++) {
+            if (cycle[i]->connections[n].fd != (ngx_socket_t) -1) {
+                found = 1;
+
+                ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, "live fd:%ui", n);
+
+                break;
+            }
+        }
+
+        if (found) {
+            live = 1;
+            continue;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, "clean old cycle: %ui", i);
+
+        ngx_destroy_pool(cycle[i]->pool);
+        cycle[i] = NULL;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, "old cycles status: %ui", live);
+
+    if (live) {
+        ngx_add_timer(ev, 30000);
+
+    } else {
+        ngx_destroy_pool(ngx_temp_pool);
+        ngx_temp_pool = NULL;
+        ngx_old_cycles.nelts = 0;
+    }
+}
+
+
+void
+ngx_set_shutdown_timer(ngx_cycle_t *cycle)
+{
+    ngx_core_conf_t  *ccf;
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+    if (ccf->shutdown_timeout) {
+        ngx_shutdown_event.handler = ngx_shutdown_timer_handler;
+        ngx_shutdown_event.data = cycle;
+        ngx_shutdown_event.log = cycle->log;
+        ngx_shutdown_event.cancelable = 1;
+
+        ngx_add_timer(&ngx_shutdown_event, ccf->shutdown_timeout);
+    }
+}
+
+
+static void
+ngx_shutdown_timer_handler(ngx_event_t *ev)
+{
+    ngx_uint_t         i;
+    ngx_cycle_t       *cycle;
+    ngx_connection_t  *c;
+
+    cycle = ev->data;
+
+    c = cycle->connections;
+
+    for (i = 0; i < cycle->connection_n; i++) {
+
+        if (c[i].fd == (ngx_socket_t) -1
+            || c[i].read == NULL
+            || c[i].read->accept
+            || c[i].read->channel
+            || c[i].read->resolver)
+        {
+            continue;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0,
+                       "*%uA shutdown timeout", c[i].number);
+
+        c[i].close = 1;
+        c[i].error = 1;
+
+        c[i].read->handler(c[i].read);
+    }
+}
diff --git a/nginx/src/core/ngx_cycle.h b/nginx/src/core/ngx_cycle.h
new file mode 100644 (file)
index 0000000..54fa2e6
--- /dev/null
@@ -0,0 +1,147 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_CYCLE_H_INCLUDED_
+#define _NGX_CYCLE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#ifndef NGX_CYCLE_POOL_SIZE
+#define NGX_CYCLE_POOL_SIZE     NGX_DEFAULT_POOL_SIZE
+#endif
+
+
+#define NGX_DEBUG_POINTS_STOP   1
+#define NGX_DEBUG_POINTS_ABORT  2
+
+
+typedef struct ngx_shm_zone_s  ngx_shm_zone_t;
+
+typedef ngx_int_t (*ngx_shm_zone_init_pt) (ngx_shm_zone_t *zone, void *data);
+
+struct ngx_shm_zone_s {
+    void                     *data;
+    ngx_shm_t                 shm;
+    ngx_shm_zone_init_pt      init;
+    void                     *tag;
+    void                     *sync;
+    ngx_uint_t                noreuse;  /* unsigned  noreuse:1; */
+};
+
+
+struct ngx_cycle_s {
+    void                  ****conf_ctx;
+    ngx_pool_t               *pool;
+
+    ngx_log_t                *log;
+    ngx_log_t                 new_log;
+
+    ngx_uint_t                log_use_stderr;  /* unsigned  log_use_stderr:1; */
+
+    ngx_connection_t        **files;
+    ngx_connection_t         *free_connections;
+    ngx_uint_t                free_connection_n;
+
+    ngx_module_t            **modules;
+    ngx_uint_t                modules_n;
+    ngx_uint_t                modules_used;    /* unsigned  modules_used:1; */
+
+    ngx_queue_t               reusable_connections_queue;
+    ngx_uint_t                reusable_connections_n;
+
+    ngx_array_t               listening;
+    ngx_array_t               paths;
+
+    ngx_array_t               config_dump;
+    ngx_rbtree_t              config_dump_rbtree;
+    ngx_rbtree_node_t         config_dump_sentinel;
+
+    ngx_list_t                open_files;
+    ngx_list_t                shared_memory;
+
+    ngx_uint_t                connection_n;
+    ngx_uint_t                files_n;
+
+    ngx_connection_t         *connections;
+    ngx_event_t              *read_events;
+    ngx_event_t              *write_events;
+
+    ngx_cycle_t              *old_cycle;
+
+    ngx_str_t                 conf_file;
+    ngx_str_t                 conf_param;
+    ngx_str_t                 conf_prefix;
+    ngx_str_t                 prefix;
+    ngx_str_t                 lock_file;
+    ngx_str_t                 hostname;
+};
+
+
+typedef struct {
+    ngx_flag_t                daemon;
+    ngx_flag_t                master;
+
+    ngx_msec_t                timer_resolution;
+    ngx_msec_t                shutdown_timeout;
+
+    ngx_int_t                 worker_processes;
+    ngx_int_t                 debug_points;
+
+    ngx_int_t                 rlimit_nofile;
+    off_t                     rlimit_core;
+
+    int                       priority;
+
+    ngx_uint_t                cpu_affinity_auto;
+    ngx_uint_t                cpu_affinity_n;
+    ngx_cpuset_t             *cpu_affinity;
+
+    char                     *username;
+    ngx_uid_t                 user;
+    ngx_gid_t                 group;
+
+    ngx_str_t                 working_directory;
+    ngx_str_t                 lock_file;
+
+    ngx_str_t                 pid;
+    ngx_str_t                 oldpid;
+
+    ngx_array_t               env;
+    char                    **environment;
+
+    ngx_uint_t                transparent;  /* unsigned  transparent:1; */
+} ngx_core_conf_t;
+
+
+#define ngx_is_init_cycle(cycle)  (cycle->conf_ctx == NULL)
+
+
+ngx_cycle_t *ngx_init_cycle(ngx_cycle_t *old_cycle);
+ngx_int_t ngx_create_pidfile(ngx_str_t *name, ngx_log_t *log);
+void ngx_delete_pidfile(ngx_cycle_t *cycle);
+ngx_int_t ngx_signal_process(ngx_cycle_t *cycle, char *sig);
+void ngx_reopen_files(ngx_cycle_t *cycle, ngx_uid_t user);
+char **ngx_set_environment(ngx_cycle_t *cycle, ngx_uint_t *last);
+ngx_pid_t ngx_exec_new_binary(ngx_cycle_t *cycle, char *const *argv);
+ngx_cpuset_t *ngx_get_cpu_affinity(ngx_uint_t n);
+ngx_shm_zone_t *ngx_shared_memory_add(ngx_conf_t *cf, ngx_str_t *name,
+    size_t size, void *tag);
+void ngx_set_shutdown_timer(ngx_cycle_t *cycle);
+
+
+extern volatile ngx_cycle_t  *ngx_cycle;
+extern ngx_array_t            ngx_old_cycles;
+extern ngx_module_t           ngx_core_module;
+extern ngx_uint_t             ngx_test_config;
+extern ngx_uint_t             ngx_dump_config;
+extern ngx_uint_t             ngx_quiet_mode;
+
+
+#endif /* _NGX_CYCLE_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_file.c b/nginx/src/core/ngx_file.c
new file mode 100644 (file)
index 0000000..3a94089
--- /dev/null
@@ -0,0 +1,1125 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+static ngx_int_t ngx_test_full_name(ngx_str_t *name);
+
+
+static ngx_atomic_t   temp_number = 0;
+ngx_atomic_t         *ngx_temp_number = &temp_number;
+ngx_atomic_int_t      ngx_random_number = 123456;
+
+
+ngx_int_t
+ngx_get_full_name(ngx_pool_t *pool, ngx_str_t *prefix, ngx_str_t *name)
+{
+    size_t      len;
+    u_char     *p, *n;
+    ngx_int_t   rc;
+
+    rc = ngx_test_full_name(name);
+
+    if (rc == NGX_OK) {
+        return rc;
+    }
+
+    len = prefix->len;
+
+#if (NGX_WIN32)
+
+    if (rc == 2) {
+        len = rc;
+    }
+
+#endif
+
+    n = ngx_pnalloc(pool, len + name->len + 1);
+    if (n == NULL) {
+        return NGX_ERROR;
+    }
+
+    p = ngx_cpymem(n, prefix->data, len);
+    ngx_cpystrn(p, name->data, name->len + 1);
+
+    name->len += len;
+    name->data = n;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_test_full_name(ngx_str_t *name)
+{
+#if (NGX_WIN32)
+    u_char  c0, c1;
+
+    c0 = name->data[0];
+
+    if (name->len < 2) {
+        if (c0 == '/') {
+            return 2;
+        }
+
+        return NGX_DECLINED;
+    }
+
+    c1 = name->data[1];
+
+    if (c1 == ':') {
+        c0 |= 0x20;
+
+        if ((c0 >= 'a' && c0 <= 'z')) {
+            return NGX_OK;
+        }
+
+        return NGX_DECLINED;
+    }
+
+    if (c1 == '/') {
+        return NGX_OK;
+    }
+
+    if (c0 == '/') {
+        return 2;
+    }
+
+    return NGX_DECLINED;
+
+#else
+
+    if (name->data[0] == '/') {
+        return NGX_OK;
+    }
+
+    return NGX_DECLINED;
+
+#endif
+}
+
+
+ssize_t
+ngx_write_chain_to_temp_file(ngx_temp_file_t *tf, ngx_chain_t *chain)
+{
+    ngx_int_t  rc;
+
+    if (tf->file.fd == NGX_INVALID_FILE) {
+        rc = ngx_create_temp_file(&tf->file, tf->path, tf->pool,
+                                  tf->persistent, tf->clean, tf->access);
+
+        if (rc != NGX_OK) {
+            return rc;
+        }
+
+        if (tf->log_level) {
+            ngx_log_error(tf->log_level, tf->file.log, 0, "%s %V",
+                          tf->warn, &tf->file.name);
+        }
+    }
+
+#if (NGX_THREADS && NGX_HAVE_PWRITEV)
+
+    if (tf->thread_write) {
+        return ngx_thread_write_chain_to_file(&tf->file, chain, tf->offset,
+                                              tf->pool);
+    }
+
+#endif
+
+    return ngx_write_chain_to_file(&tf->file, chain, tf->offset, tf->pool);
+}
+
+
+ngx_int_t
+ngx_create_temp_file(ngx_file_t *file, ngx_path_t *path, ngx_pool_t *pool,
+    ngx_uint_t persistent, ngx_uint_t clean, ngx_uint_t access)
+{
+    size_t                    levels;
+    u_char                   *p;
+    uint32_t                  n;
+    ngx_err_t                 err;
+    ngx_str_t                 name;
+    ngx_uint_t                prefix;
+    ngx_pool_cleanup_t       *cln;
+    ngx_pool_cleanup_file_t  *clnf;
+
+    if (file->name.len) {
+        name = file->name;
+        levels = 0;
+        prefix = 1;
+
+    } else {
+        name = path->name;
+        levels = path->len;
+        prefix = 0;
+    }
+
+    file->name.len = name.len + 1 + levels + 10;
+
+    file->name.data = ngx_pnalloc(pool, file->name.len + 1);
+    if (file->name.data == NULL) {
+        return NGX_ERROR;
+    }
+
+#if 0
+    for (i = 0; i < file->name.len; i++) {
+        file->name.data[i] = 'X';
+    }
+#endif
+
+    p = ngx_cpymem(file->name.data, name.data, name.len);
+
+    if (prefix) {
+        *p = '.';
+    }
+
+    p += 1 + levels;
+
+    n = (uint32_t) ngx_next_temp_number(0);
+
+    cln = ngx_pool_cleanup_add(pool, sizeof(ngx_pool_cleanup_file_t));
+    if (cln == NULL) {
+        return NGX_ERROR;
+    }
+
+    for ( ;; ) {
+        (void) ngx_sprintf(p, "%010uD%Z", n);
+
+        if (!prefix) {
+            ngx_create_hashed_filename(path, file->name.data, file->name.len);
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
+                       "hashed path: %s", file->name.data);
+
+        file->fd = ngx_open_tempfile(file->name.data, persistent, access);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
+                       "temp fd:%d", file->fd);
+
+        if (file->fd != NGX_INVALID_FILE) {
+
+            cln->handler = clean ? ngx_pool_delete_file : ngx_pool_cleanup_file;
+            clnf = cln->data;
+
+            clnf->fd = file->fd;
+            clnf->name = file->name.data;
+            clnf->log = pool->log;
+
+            return NGX_OK;
+        }
+
+        err = ngx_errno;
+
+        if (err == NGX_EEXIST_FILE) {
+            n = (uint32_t) ngx_next_temp_number(1);
+            continue;
+        }
+
+        if ((path->level[0] == 0) || (err != NGX_ENOPATH)) {
+            ngx_log_error(NGX_LOG_CRIT, file->log, err,
+                          ngx_open_tempfile_n " \"%s\" failed",
+                          file->name.data);
+            return NGX_ERROR;
+        }
+
+        if (ngx_create_path(file, path) == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+    }
+}
+
+
+void
+ngx_create_hashed_filename(ngx_path_t *path, u_char *file, size_t len)
+{
+    size_t      i, level;
+    ngx_uint_t  n;
+
+    i = path->name.len + 1;
+
+    file[path->name.len + path->len]  = '/';
+
+    for (n = 0; n < NGX_MAX_PATH_LEVEL; n++) {
+        level = path->level[n];
+
+        if (level == 0) {
+            break;
+        }
+
+        len -= level;
+        file[i - 1] = '/';
+        ngx_memcpy(&file[i], &file[len], level);
+        i += level + 1;
+    }
+}
+
+
+ngx_int_t
+ngx_create_path(ngx_file_t *file, ngx_path_t *path)
+{
+    size_t      pos;
+    ngx_err_t   err;
+    ngx_uint_t  i;
+
+    pos = path->name.len;
+
+    for (i = 0; i < NGX_MAX_PATH_LEVEL; i++) {
+        if (path->level[i] == 0) {
+            break;
+        }
+
+        pos += path->level[i] + 1;
+
+        file->name.data[pos] = '\0';
+
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
+                       "temp file: \"%s\"", file->name.data);
+
+        if (ngx_create_dir(file->name.data, 0700) == NGX_FILE_ERROR) {
+            err = ngx_errno;
+            if (err != NGX_EEXIST) {
+                ngx_log_error(NGX_LOG_CRIT, file->log, err,
+                              ngx_create_dir_n " \"%s\" failed",
+                              file->name.data);
+                return NGX_ERROR;
+            }
+        }
+
+        file->name.data[pos] = '/';
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_err_t
+ngx_create_full_path(u_char *dir, ngx_uint_t access)
+{
+    u_char     *p, ch;
+    ngx_err_t   err;
+
+    err = 0;
+
+#if (NGX_WIN32)
+    p = dir + 3;
+#else
+    p = dir + 1;
+#endif
+
+    for ( /* void */ ; *p; p++) {
+        ch = *p;
+
+        if (ch != '/') {
+            continue;
+        }
+
+        *p = '\0';
+
+        if (ngx_create_dir(dir, access) == NGX_FILE_ERROR) {
+            err = ngx_errno;
+
+            switch (err) {
+            case NGX_EEXIST:
+                err = 0;
+            case NGX_EACCES:
+                break;
+
+            default:
+                return err;
+            }
+        }
+
+        *p = '/';
+    }
+
+    return err;
+}
+
+
+ngx_atomic_uint_t
+ngx_next_temp_number(ngx_uint_t collision)
+{
+    ngx_atomic_uint_t  n, add;
+
+    add = collision ? ngx_random_number : 1;
+
+    n = ngx_atomic_fetch_add(ngx_temp_number, add);
+
+    return n + add;
+}
+
+
+char *
+ngx_conf_set_path_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ssize_t      level;
+    ngx_str_t   *value;
+    ngx_uint_t   i, n;
+    ngx_path_t  *path, **slot;
+
+    slot = (ngx_path_t **) (p + cmd->offset);
+
+    if (*slot) {
+        return "is duplicate";
+    }
+
+    path = ngx_pcalloc(cf->pool, sizeof(ngx_path_t));
+    if (path == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    value = cf->args->elts;
+
+    path->name = value[1];
+
+    if (path->name.data[path->name.len - 1] == '/') {
+        path->name.len--;
+    }
+
+    if (ngx_conf_full_name(cf->cycle, &path->name, 0) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    path->conf_file = cf->conf_file->file.name.data;
+    path->line = cf->conf_file->line;
+
+    for (i = 0, n = 2; n < cf->args->nelts; i++, n++) {
+        level = ngx_atoi(value[n].data, value[n].len);
+        if (level == NGX_ERROR || level == 0) {
+            return "invalid value";
+        }
+
+        path->level[i] = level;
+        path->len += level + 1;
+    }
+
+    if (path->len > 10 + i) {
+        return "invalid value";
+    }
+
+    *slot = path;
+
+    if (ngx_add_path(cf, slot) == NGX_ERROR) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *
+ngx_conf_merge_path_value(ngx_conf_t *cf, ngx_path_t **path, ngx_path_t *prev,
+    ngx_path_init_t *init)
+{
+    ngx_uint_t  i;
+
+    if (*path) {
+        return NGX_CONF_OK;
+    }
+
+    if (prev) {
+        *path = prev;
+        return NGX_CONF_OK;
+    }
+
+    *path = ngx_pcalloc(cf->pool, sizeof(ngx_path_t));
+    if (*path == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    (*path)->name = init->name;
+
+    if (ngx_conf_full_name(cf->cycle, &(*path)->name, 0) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    for (i = 0; i < NGX_MAX_PATH_LEVEL; i++) {
+        (*path)->level[i] = init->level[i];
+        (*path)->len += init->level[i] + (init->level[i] ? 1 : 0);
+    }
+
+    if (ngx_add_path(cf, path) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *
+ngx_conf_set_access_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *confp = conf;
+
+    u_char      *p;
+    ngx_str_t   *value;
+    ngx_uint_t   i, right, shift, *access, user;
+
+    access = (ngx_uint_t *) (confp + cmd->offset);
+
+    if (*access != NGX_CONF_UNSET_UINT) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    *access = 0;
+    user = 0600;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+
+        p = value[i].data;
+
+        if (ngx_strncmp(p, "user:", sizeof("user:") - 1) == 0) {
+            shift = 6;
+            p += sizeof("user:") - 1;
+            user = 0;
+
+        } else if (ngx_strncmp(p, "group:", sizeof("group:") - 1) == 0) {
+            shift = 3;
+            p += sizeof("group:") - 1;
+
+        } else if (ngx_strncmp(p, "all:", sizeof("all:") - 1) == 0) {
+            shift = 0;
+            p += sizeof("all:") - 1;
+
+        } else {
+            goto invalid;
+        }
+
+        if (ngx_strcmp(p, "rw") == 0) {
+            right = 6;
+
+        } else if (ngx_strcmp(p, "r") == 0) {
+            right = 4;
+
+        } else {
+            goto invalid;
+        }
+
+        *access |= right << shift;
+    }
+
+    *access |= user;
+
+    return NGX_CONF_OK;
+
+invalid:
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid value \"%V\"", &value[i]);
+
+    return NGX_CONF_ERROR;
+}
+
+
+ngx_int_t
+ngx_add_path(ngx_conf_t *cf, ngx_path_t **slot)
+{
+    ngx_uint_t   i, n;
+    ngx_path_t  *path, **p;
+
+    path = *slot;
+
+    p = cf->cycle->paths.elts;
+    for (i = 0; i < cf->cycle->paths.nelts; i++) {
+        if (p[i]->name.len == path->name.len
+            && ngx_strcmp(p[i]->name.data, path->name.data) == 0)
+        {
+            if (p[i]->data != path->data) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "the same path name \"%V\" "
+                                   "used in %s:%ui and",
+                                   &p[i]->name, p[i]->conf_file, p[i]->line);
+                return NGX_ERROR;
+            }
+
+            for (n = 0; n < NGX_MAX_PATH_LEVEL; n++) {
+                if (p[i]->level[n] != path->level[n]) {
+                    if (path->conf_file == NULL) {
+                        if (p[i]->conf_file == NULL) {
+                            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                                      "the default path name \"%V\" has "
+                                      "the same name as another default path, "
+                                      "but the different levels, you need to "
+                                      "redefine one of them in http section",
+                                      &p[i]->name);
+                            return NGX_ERROR;
+                        }
+
+                        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                                      "the path name \"%V\" in %s:%ui has "
+                                      "the same name as default path, but "
+                                      "the different levels, you need to "
+                                      "define default path in http section",
+                                      &p[i]->name, p[i]->conf_file, p[i]->line);
+                        return NGX_ERROR;
+                    }
+
+                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                      "the same path name \"%V\" in %s:%ui "
+                                      "has the different levels than",
+                                      &p[i]->name, p[i]->conf_file, p[i]->line);
+                    return NGX_ERROR;
+                }
+
+                if (p[i]->level[n] == 0) {
+                    break;
+                }
+            }
+
+            *slot = p[i];
+
+            return NGX_OK;
+        }
+    }
+
+    p = ngx_array_push(&cf->cycle->paths);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    *p = path;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_create_paths(ngx_cycle_t *cycle, ngx_uid_t user)
+{
+    ngx_err_t         err;
+    ngx_uint_t        i;
+    ngx_path_t      **path;
+
+    path = cycle->paths.elts;
+    for (i = 0; i < cycle->paths.nelts; i++) {
+
+        if (ngx_create_dir(path[i]->name.data, 0700) == NGX_FILE_ERROR) {
+            err = ngx_errno;
+            if (err != NGX_EEXIST) {
+                ngx_log_error(NGX_LOG_EMERG, cycle->log, err,
+                              ngx_create_dir_n " \"%s\" failed",
+                              path[i]->name.data);
+                return NGX_ERROR;
+            }
+        }
+
+        if (user == (ngx_uid_t) NGX_CONF_UNSET_UINT) {
+            continue;
+        }
+
+#if !(NGX_WIN32)
+        {
+        ngx_file_info_t   fi;
+
+        if (ngx_file_info(path[i]->name.data, &fi) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          ngx_file_info_n " \"%s\" failed", path[i]->name.data);
+            return NGX_ERROR;
+        }
+
+        if (fi.st_uid != user) {
+            if (chown((const char *) path[i]->name.data, user, -1) == -1) {
+                ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                              "chown(\"%s\", %d) failed",
+                              path[i]->name.data, user);
+                return NGX_ERROR;
+            }
+        }
+
+        if ((fi.st_mode & (S_IRUSR|S_IWUSR|S_IXUSR))
+                                                  != (S_IRUSR|S_IWUSR|S_IXUSR))
+        {
+            fi.st_mode |= (S_IRUSR|S_IWUSR|S_IXUSR);
+
+            if (chmod((const char *) path[i]->name.data, fi.st_mode) == -1) {
+                ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                              "chmod() \"%s\" failed", path[i]->name.data);
+                return NGX_ERROR;
+            }
+        }
+        }
+#endif
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ext_rename_file(ngx_str_t *src, ngx_str_t *to, ngx_ext_rename_file_t *ext)
+{
+    u_char           *name;
+    ngx_err_t         err;
+    ngx_copy_file_t   cf;
+
+#if !(NGX_WIN32)
+
+    if (ext->access) {
+        if (ngx_change_file_access(src->data, ext->access) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_CRIT, ext->log, ngx_errno,
+                          ngx_change_file_access_n " \"%s\" failed", src->data);
+            err = 0;
+            goto failed;
+        }
+    }
+
+#endif
+
+    if (ext->time != -1) {
+        if (ngx_set_file_time(src->data, ext->fd, ext->time) != NGX_OK) {
+            ngx_log_error(NGX_LOG_CRIT, ext->log, ngx_errno,
+                          ngx_set_file_time_n " \"%s\" failed", src->data);
+            err = 0;
+            goto failed;
+        }
+    }
+
+    if (ngx_rename_file(src->data, to->data) != NGX_FILE_ERROR) {
+        return NGX_OK;
+    }
+
+    err = ngx_errno;
+
+    if (err == NGX_ENOPATH) {
+
+        if (!ext->create_path) {
+            goto failed;
+        }
+
+        err = ngx_create_full_path(to->data, ngx_dir_access(ext->path_access));
+
+        if (err) {
+            ngx_log_error(NGX_LOG_CRIT, ext->log, err,
+                          ngx_create_dir_n " \"%s\" failed", to->data);
+            err = 0;
+            goto failed;
+        }
+
+        if (ngx_rename_file(src->data, to->data) != NGX_FILE_ERROR) {
+            return NGX_OK;
+        }
+
+        err = ngx_errno;
+    }
+
+#if (NGX_WIN32)
+
+    if (err == NGX_EEXIST || err == NGX_EEXIST_FILE) {
+        err = ngx_win32_rename_file(src, to, ext->log);
+
+        if (err == 0) {
+            return NGX_OK;
+        }
+    }
+
+#endif
+
+    if (err == NGX_EXDEV) {
+
+        cf.size = -1;
+        cf.buf_size = 0;
+        cf.access = ext->access;
+        cf.time = ext->time;
+        cf.log = ext->log;
+
+        name = ngx_alloc(to->len + 1 + 10 + 1, ext->log);
+        if (name == NULL) {
+            return NGX_ERROR;
+        }
+
+        (void) ngx_sprintf(name, "%*s.%010uD%Z", to->len, to->data,
+                           (uint32_t) ngx_next_temp_number(0));
+
+        if (ngx_copy_file(src->data, name, &cf) == NGX_OK) {
+
+            if (ngx_rename_file(name, to->data) != NGX_FILE_ERROR) {
+                ngx_free(name);
+
+                if (ngx_delete_file(src->data) == NGX_FILE_ERROR) {
+                    ngx_log_error(NGX_LOG_CRIT, ext->log, ngx_errno,
+                                  ngx_delete_file_n " \"%s\" failed",
+                                  src->data);
+                    return NGX_ERROR;
+                }
+
+                return NGX_OK;
+            }
+
+            ngx_log_error(NGX_LOG_CRIT, ext->log, ngx_errno,
+                          ngx_rename_file_n " \"%s\" to \"%s\" failed",
+                          name, to->data);
+
+            if (ngx_delete_file(name) == NGX_FILE_ERROR) {
+                ngx_log_error(NGX_LOG_CRIT, ext->log, ngx_errno,
+                              ngx_delete_file_n " \"%s\" failed", name);
+
+            }
+        }
+
+        ngx_free(name);
+
+        err = 0;
+    }
+
+failed:
+
+    if (ext->delete_file) {
+        if (ngx_delete_file(src->data) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_CRIT, ext->log, ngx_errno,
+                          ngx_delete_file_n " \"%s\" failed", src->data);
+        }
+    }
+
+    if (err) {
+        ngx_log_error(NGX_LOG_CRIT, ext->log, err,
+                      ngx_rename_file_n " \"%s\" to \"%s\" failed",
+                      src->data, to->data);
+    }
+
+    return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_copy_file(u_char *from, u_char *to, ngx_copy_file_t *cf)
+{
+    char             *buf;
+    off_t             size;
+    size_t            len;
+    ssize_t           n;
+    ngx_fd_t          fd, nfd;
+    ngx_int_t         rc;
+    ngx_file_info_t   fi;
+
+    rc = NGX_ERROR;
+    buf = NULL;
+    nfd = NGX_INVALID_FILE;
+
+    fd = ngx_open_file(from, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
+
+    if (fd == NGX_INVALID_FILE) {
+        ngx_log_error(NGX_LOG_CRIT, cf->log, ngx_errno,
+                      ngx_open_file_n " \"%s\" failed", from);
+        goto failed;
+    }
+
+    if (cf->size != -1) {
+        size = cf->size;
+
+    } else {
+        if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
+                          ngx_fd_info_n " \"%s\" failed", from);
+
+            goto failed;
+        }
+
+        size = ngx_file_size(&fi);
+    }
+
+    len = cf->buf_size ? cf->buf_size : 65536;
+
+    if ((off_t) len > size) {
+        len = (size_t) size;
+    }
+
+    buf = ngx_alloc(len, cf->log);
+    if (buf == NULL) {
+        goto failed;
+    }
+
+    nfd = ngx_open_file(to, NGX_FILE_WRONLY, NGX_FILE_CREATE_OR_OPEN,
+                        cf->access);
+
+    if (nfd == NGX_INVALID_FILE) {
+        ngx_log_error(NGX_LOG_CRIT, cf->log, ngx_errno,
+                      ngx_open_file_n " \"%s\" failed", to);
+        goto failed;
+    }
+
+    while (size > 0) {
+
+        if ((off_t) len > size) {
+            len = (size_t) size;
+        }
+
+        n = ngx_read_fd(fd, buf, len);
+
+        if (n == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
+                          ngx_read_fd_n " \"%s\" failed", from);
+            goto failed;
+        }
+
+        if ((size_t) n != len) {
+            ngx_log_error(NGX_LOG_ALERT, cf->log, 0,
+                          ngx_read_fd_n " has read only %z of %O from %s",
+                          n, size, from);
+            goto failed;
+        }
+
+        n = ngx_write_fd(nfd, buf, len);
+
+        if (n == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
+                          ngx_write_fd_n " \"%s\" failed", to);
+            goto failed;
+        }
+
+        if ((size_t) n != len) {
+            ngx_log_error(NGX_LOG_ALERT, cf->log, 0,
+                          ngx_write_fd_n " has written only %z of %O to %s",
+                          n, size, to);
+            goto failed;
+        }
+
+        size -= n;
+    }
+
+    if (cf->time != -1) {
+        if (ngx_set_file_time(to, nfd, cf->time) != NGX_OK) {
+            ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
+                          ngx_set_file_time_n " \"%s\" failed", to);
+            goto failed;
+        }
+    }
+
+    rc = NGX_OK;
+
+failed:
+
+    if (nfd != NGX_INVALID_FILE) {
+        if (ngx_close_file(nfd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
+                          ngx_close_file_n " \"%s\" failed", to);
+        }
+    }
+
+    if (fd != NGX_INVALID_FILE) {
+        if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
+                          ngx_close_file_n " \"%s\" failed", from);
+        }
+    }
+
+    if (buf) {
+        ngx_free(buf);
+    }
+
+    return rc;
+}
+
+
+/*
+ * ctx->init_handler() - see ctx->alloc
+ * ctx->file_handler() - file handler
+ * ctx->pre_tree_handler() - handler is called before entering directory
+ * ctx->post_tree_handler() - handler is called after leaving directory
+ * ctx->spec_handler() - special (socket, FIFO, etc.) file handler
+ *
+ * ctx->data - some data structure, it may be the same on all levels, or
+ *     reallocated if ctx->alloc is nonzero
+ *
+ * ctx->alloc - a size of data structure that is allocated at every level
+ *     and is initialized by ctx->init_handler()
+ *
+ * ctx->log - a log
+ *
+ * on fatal (memory) error handler must return NGX_ABORT to stop walking tree
+ */
+
+ngx_int_t
+ngx_walk_tree(ngx_tree_ctx_t *ctx, ngx_str_t *tree)
+{
+    void       *data, *prev;
+    u_char     *p, *name;
+    size_t      len;
+    ngx_int_t   rc;
+    ngx_err_t   err;
+    ngx_str_t   file, buf;
+    ngx_dir_t   dir;
+
+    ngx_str_null(&buf);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0,
+                   "walk tree \"%V\"", tree);
+
+    if (ngx_open_dir(tree, &dir) == NGX_ERROR) {
+        ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
+                      ngx_open_dir_n " \"%s\" failed", tree->data);
+        return NGX_ERROR;
+    }
+
+    prev = ctx->data;
+
+    if (ctx->alloc) {
+        data = ngx_alloc(ctx->alloc, ctx->log);
+        if (data == NULL) {
+            goto failed;
+        }
+
+        if (ctx->init_handler(data, prev) == NGX_ABORT) {
+            goto failed;
+        }
+
+        ctx->data = data;
+
+    } else {
+        data = NULL;
+    }
+
+    for ( ;; ) {
+
+        ngx_set_errno(0);
+
+        if (ngx_read_dir(&dir) == NGX_ERROR) {
+            err = ngx_errno;
+
+            if (err == NGX_ENOMOREFILES) {
+                rc = NGX_OK;
+
+            } else {
+                ngx_log_error(NGX_LOG_CRIT, ctx->log, err,
+                              ngx_read_dir_n " \"%s\" failed", tree->data);
+                rc = NGX_ERROR;
+            }
+
+            goto done;
+        }
+
+        len = ngx_de_namelen(&dir);
+        name = ngx_de_name(&dir);
+
+        ngx_log_debug2(NGX_LOG_DEBUG_CORE, ctx->log, 0,
+                      "tree name %uz:\"%s\"", len, name);
+
+        if (len == 1 && name[0] == '.') {
+            continue;
+        }
+
+        if (len == 2 && name[0] == '.' && name[1] == '.') {
+            continue;
+        }
+
+        file.len = tree->len + 1 + len;
+
+        if (file.len + NGX_DIR_MASK_LEN > buf.len) {
+
+            if (buf.len) {
+                ngx_free(buf.data);
+            }
+
+            buf.len = tree->len + 1 + len + NGX_DIR_MASK_LEN;
+
+            buf.data = ngx_alloc(buf.len + 1, ctx->log);
+            if (buf.data == NULL) {
+                goto failed;
+            }
+        }
+
+        p = ngx_cpymem(buf.data, tree->data, tree->len);
+        *p++ = '/';
+        ngx_memcpy(p, name, len + 1);
+
+        file.data = buf.data;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0,
+                       "tree path \"%s\"", file.data);
+
+        if (!dir.valid_info) {
+            if (ngx_de_info(file.data, &dir) == NGX_FILE_ERROR) {
+                ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
+                              ngx_de_info_n " \"%s\" failed", file.data);
+                continue;
+            }
+        }
+
+        if (ngx_de_is_file(&dir)) {
+
+            ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0,
+                           "tree file \"%s\"", file.data);
+
+            ctx->size = ngx_de_size(&dir);
+            ctx->fs_size = ngx_de_fs_size(&dir);
+            ctx->access = ngx_de_access(&dir);
+            ctx->mtime = ngx_de_mtime(&dir);
+
+            if (ctx->file_handler(ctx, &file) == NGX_ABORT) {
+                goto failed;
+            }
+
+        } else if (ngx_de_is_dir(&dir)) {
+
+            ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0,
+                           "tree enter dir \"%s\"", file.data);
+
+            ctx->access = ngx_de_access(&dir);
+            ctx->mtime = ngx_de_mtime(&dir);
+
+            rc = ctx->pre_tree_handler(ctx, &file);
+
+            if (rc == NGX_ABORT) {
+                goto failed;
+            }
+
+            if (rc == NGX_DECLINED) {
+                ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0,
+                               "tree skip dir \"%s\"", file.data);
+                continue;
+            }
+
+            if (ngx_walk_tree(ctx, &file) == NGX_ABORT) {
+                goto failed;
+            }
+
+            ctx->access = ngx_de_access(&dir);
+            ctx->mtime = ngx_de_mtime(&dir);
+
+            if (ctx->post_tree_handler(ctx, &file) == NGX_ABORT) {
+                goto failed;
+            }
+
+        } else {
+
+            ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0,
+                           "tree special \"%s\"", file.data);
+
+            if (ctx->spec_handler(ctx, &file) == NGX_ABORT) {
+                goto failed;
+            }
+        }
+    }
+
+failed:
+
+    rc = NGX_ABORT;
+
+done:
+
+    if (buf.len) {
+        ngx_free(buf.data);
+    }
+
+    if (data) {
+        ngx_free(data);
+        ctx->data = prev;
+    }
+
+    if (ngx_close_dir(&dir) == NGX_ERROR) {
+        ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
+                      ngx_close_dir_n " \"%s\" failed", tree->data);
+    }
+
+    return rc;
+}
diff --git a/nginx/src/core/ngx_file.h b/nginx/src/core/ngx_file.h
new file mode 100644 (file)
index 0000000..320adc2
--- /dev/null
@@ -0,0 +1,164 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_FILE_H_INCLUDED_
+#define _NGX_FILE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+struct ngx_file_s {
+    ngx_fd_t                   fd;
+    ngx_str_t                  name;
+    ngx_file_info_t            info;
+
+    off_t                      offset;
+    off_t                      sys_offset;
+
+    ngx_log_t                 *log;
+
+#if (NGX_THREADS || NGX_COMPAT)
+    ngx_int_t                (*thread_handler)(ngx_thread_task_t *task,
+                                               ngx_file_t *file);
+    void                      *thread_ctx;
+    ngx_thread_task_t         *thread_task;
+#endif
+
+#if (NGX_HAVE_FILE_AIO || NGX_COMPAT)
+    ngx_event_aio_t           *aio;
+#endif
+
+    unsigned                   valid_info:1;
+    unsigned                   directio:1;
+};
+
+
+#define NGX_MAX_PATH_LEVEL  3
+
+
+typedef ngx_msec_t (*ngx_path_manager_pt) (void *data);
+typedef ngx_msec_t (*ngx_path_purger_pt) (void *data);
+typedef void (*ngx_path_loader_pt) (void *data);
+
+
+typedef struct {
+    ngx_str_t                  name;
+    size_t                     len;
+    size_t                     level[NGX_MAX_PATH_LEVEL];
+
+    ngx_path_manager_pt        manager;
+    ngx_path_purger_pt         purger;
+    ngx_path_loader_pt         loader;
+    void                      *data;
+
+    u_char                    *conf_file;
+    ngx_uint_t                 line;
+} ngx_path_t;
+
+
+typedef struct {
+    ngx_str_t                  name;
+    size_t                     level[NGX_MAX_PATH_LEVEL];
+} ngx_path_init_t;
+
+
+typedef struct {
+    ngx_file_t                 file;
+    off_t                      offset;
+    ngx_path_t                *path;
+    ngx_pool_t                *pool;
+    char                      *warn;
+
+    ngx_uint_t                 access;
+
+    unsigned                   log_level:8;
+    unsigned                   persistent:1;
+    unsigned                   clean:1;
+    unsigned                   thread_write:1;
+} ngx_temp_file_t;
+
+
+typedef struct {
+    ngx_uint_t                 access;
+    ngx_uint_t                 path_access;
+    time_t                     time;
+    ngx_fd_t                   fd;
+
+    unsigned                   create_path:1;
+    unsigned                   delete_file:1;
+
+    ngx_log_t                 *log;
+} ngx_ext_rename_file_t;
+
+
+typedef struct {
+    off_t                      size;
+    size_t                     buf_size;
+
+    ngx_uint_t                 access;
+    time_t                     time;
+
+    ngx_log_t                 *log;
+} ngx_copy_file_t;
+
+
+typedef struct ngx_tree_ctx_s  ngx_tree_ctx_t;
+
+typedef ngx_int_t (*ngx_tree_init_handler_pt) (void *ctx, void *prev);
+typedef ngx_int_t (*ngx_tree_handler_pt) (ngx_tree_ctx_t *ctx, ngx_str_t *name);
+
+struct ngx_tree_ctx_s {
+    off_t                      size;
+    off_t                      fs_size;
+    ngx_uint_t                 access;
+    time_t                     mtime;
+
+    ngx_tree_init_handler_pt   init_handler;
+    ngx_tree_handler_pt        file_handler;
+    ngx_tree_handler_pt        pre_tree_handler;
+    ngx_tree_handler_pt        post_tree_handler;
+    ngx_tree_handler_pt        spec_handler;
+
+    void                      *data;
+    size_t                     alloc;
+
+    ngx_log_t                 *log;
+};
+
+
+ngx_int_t ngx_get_full_name(ngx_pool_t *pool, ngx_str_t *prefix,
+    ngx_str_t *name);
+
+ssize_t ngx_write_chain_to_temp_file(ngx_temp_file_t *tf, ngx_chain_t *chain);
+ngx_int_t ngx_create_temp_file(ngx_file_t *file, ngx_path_t *path,
+    ngx_pool_t *pool, ngx_uint_t persistent, ngx_uint_t clean,
+    ngx_uint_t access);
+void ngx_create_hashed_filename(ngx_path_t *path, u_char *file, size_t len);
+ngx_int_t ngx_create_path(ngx_file_t *file, ngx_path_t *path);
+ngx_err_t ngx_create_full_path(u_char *dir, ngx_uint_t access);
+ngx_int_t ngx_add_path(ngx_conf_t *cf, ngx_path_t **slot);
+ngx_int_t ngx_create_paths(ngx_cycle_t *cycle, ngx_uid_t user);
+ngx_int_t ngx_ext_rename_file(ngx_str_t *src, ngx_str_t *to,
+    ngx_ext_rename_file_t *ext);
+ngx_int_t ngx_copy_file(u_char *from, u_char *to, ngx_copy_file_t *cf);
+ngx_int_t ngx_walk_tree(ngx_tree_ctx_t *ctx, ngx_str_t *tree);
+
+ngx_atomic_uint_t ngx_next_temp_number(ngx_uint_t collision);
+
+char *ngx_conf_set_path_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+char *ngx_conf_merge_path_value(ngx_conf_t *cf, ngx_path_t **path,
+    ngx_path_t *prev, ngx_path_init_t *init);
+char *ngx_conf_set_access_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+
+extern ngx_atomic_t      *ngx_temp_number;
+extern ngx_atomic_int_t   ngx_random_number;
+
+
+#endif /* _NGX_FILE_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_hash.c b/nginx/src/core/ngx_hash.c
new file mode 100644 (file)
index 0000000..1944c7a
--- /dev/null
@@ -0,0 +1,988 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+void *
+ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len)
+{
+    ngx_uint_t       i;
+    ngx_hash_elt_t  *elt;
+
+#if 0
+    ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "hf:\"%*s\"", len, name);
+#endif
+
+    elt = hash->buckets[key % hash->size];
+
+    if (elt == NULL) {
+        return NULL;
+    }
+
+    while (elt->value) {
+        if (len != (size_t) elt->len) {
+            goto next;
+        }
+
+        for (i = 0; i < len; i++) {
+            if (name[i] != elt->name[i]) {
+                goto next;
+            }
+        }
+
+        return elt->value;
+
+    next:
+
+        elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,
+                                               sizeof(void *));
+        continue;
+    }
+
+    return NULL;
+}
+
+
+void *
+ngx_hash_find_wc_head(ngx_hash_wildcard_t *hwc, u_char *name, size_t len)
+{
+    void        *value;
+    ngx_uint_t   i, n, key;
+
+#if 0
+    ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "wch:\"%*s\"", len, name);
+#endif
+
+    n = len;
+
+    while (n) {
+        if (name[n - 1] == '.') {
+            break;
+        }
+
+        n--;
+    }
+
+    key = 0;
+
+    for (i = n; i < len; i++) {
+        key = ngx_hash(key, name[i]);
+    }
+
+#if 0
+    ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "key:\"%ui\"", key);
+#endif
+
+    value = ngx_hash_find(&hwc->hash, key, &name[n], len - n);
+
+#if 0
+    ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "value:\"%p\"", value);
+#endif
+
+    if (value) {
+
+        /*
+         * the 2 low bits of value have the special meaning:
+         *     00 - value is data pointer for both "example.com"
+         *          and "*.example.com";
+         *     01 - value is data pointer for "*.example.com" only;
+         *     10 - value is pointer to wildcard hash allowing
+         *          both "example.com" and "*.example.com";
+         *     11 - value is pointer to wildcard hash allowing
+         *          "*.example.com" only.
+         */
+
+        if ((uintptr_t) value & 2) {
+
+            if (n == 0) {
+
+                /* "example.com" */
+
+                if ((uintptr_t) value & 1) {
+                    return NULL;
+                }
+
+                hwc = (ngx_hash_wildcard_t *)
+                                          ((uintptr_t) value & (uintptr_t) ~3);
+                return hwc->value;
+            }
+
+            hwc = (ngx_hash_wildcard_t *) ((uintptr_t) value & (uintptr_t) ~3);
+
+            value = ngx_hash_find_wc_head(hwc, name, n - 1);
+
+            if (value) {
+                return value;
+            }
+
+            return hwc->value;
+        }
+
+        if ((uintptr_t) value & 1) {
+
+            if (n == 0) {
+
+                /* "example.com" */
+
+                return NULL;
+            }
+
+            return (void *) ((uintptr_t) value & (uintptr_t) ~3);
+        }
+
+        return value;
+    }
+
+    return hwc->value;
+}
+
+
+void *
+ngx_hash_find_wc_tail(ngx_hash_wildcard_t *hwc, u_char *name, size_t len)
+{
+    void        *value;
+    ngx_uint_t   i, key;
+
+#if 0
+    ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "wct:\"%*s\"", len, name);
+#endif
+
+    key = 0;
+
+    for (i = 0; i < len; i++) {
+        if (name[i] == '.') {
+            break;
+        }
+
+        key = ngx_hash(key, name[i]);
+    }
+
+    if (i == len) {
+        return NULL;
+    }
+
+#if 0
+    ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "key:\"%ui\"", key);
+#endif
+
+    value = ngx_hash_find(&hwc->hash, key, name, i);
+
+#if 0
+    ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "value:\"%p\"", value);
+#endif
+
+    if (value) {
+
+        /*
+         * the 2 low bits of value have the special meaning:
+         *     00 - value is data pointer;
+         *     11 - value is pointer to wildcard hash allowing "example.*".
+         */
+
+        if ((uintptr_t) value & 2) {
+
+            i++;
+
+            hwc = (ngx_hash_wildcard_t *) ((uintptr_t) value & (uintptr_t) ~3);
+
+            value = ngx_hash_find_wc_tail(hwc, &name[i], len - i);
+
+            if (value) {
+                return value;
+            }
+
+            return hwc->value;
+        }
+
+        return value;
+    }
+
+    return hwc->value;
+}
+
+
+void *
+ngx_hash_find_combined(ngx_hash_combined_t *hash, ngx_uint_t key, u_char *name,
+    size_t len)
+{
+    void  *value;
+
+    if (hash->hash.buckets) {
+        value = ngx_hash_find(&hash->hash, key, name, len);
+
+        if (value) {
+            return value;
+        }
+    }
+
+    if (len == 0) {
+        return NULL;
+    }
+
+    if (hash->wc_head && hash->wc_head->hash.buckets) {
+        value = ngx_hash_find_wc_head(hash->wc_head, name, len);
+
+        if (value) {
+            return value;
+        }
+    }
+
+    if (hash->wc_tail && hash->wc_tail->hash.buckets) {
+        value = ngx_hash_find_wc_tail(hash->wc_tail, name, len);
+
+        if (value) {
+            return value;
+        }
+    }
+
+    return NULL;
+}
+
+
+#define NGX_HASH_ELT_SIZE(name)                                               \
+    (sizeof(void *) + ngx_align((name)->key.len + 2, sizeof(void *)))
+
+ngx_int_t
+ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts)
+{
+    u_char          *elts;
+    size_t           len;
+    u_short         *test;
+    ngx_uint_t       i, n, key, size, start, bucket_size;
+    ngx_hash_elt_t  *elt, **buckets;
+
+    if (hinit->max_size == 0) {
+        ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,
+                      "could not build %s, you should "
+                      "increase %s_max_size: %i",
+                      hinit->name, hinit->name, hinit->max_size);
+        return NGX_ERROR;
+    }
+
+    for (n = 0; n < nelts; n++) {
+        if (hinit->bucket_size < NGX_HASH_ELT_SIZE(&names[n]) + sizeof(void *))
+        {
+            ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,
+                          "could not build %s, you should "
+                          "increase %s_bucket_size: %i",
+                          hinit->name, hinit->name, hinit->bucket_size);
+            return NGX_ERROR;
+        }
+    }
+
+    test = ngx_alloc(hinit->max_size * sizeof(u_short), hinit->pool->log);
+    if (test == NULL) {
+        return NGX_ERROR;
+    }
+
+    bucket_size = hinit->bucket_size - sizeof(void *);
+
+    start = nelts / (bucket_size / (2 * sizeof(void *)));
+    start = start ? start : 1;
+
+    if (hinit->max_size > 10000 && nelts && hinit->max_size / nelts < 100) {
+        start = hinit->max_size - 1000;
+    }
+
+    for (size = start; size <= hinit->max_size; size++) {
+
+        ngx_memzero(test, size * sizeof(u_short));
+
+        for (n = 0; n < nelts; n++) {
+            if (names[n].key.data == NULL) {
+                continue;
+            }
+
+            key = names[n].key_hash % size;
+            test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));
+
+#if 0
+            ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
+                          "%ui: %ui %ui \"%V\"",
+                          size, key, test[key], &names[n].key);
+#endif
+
+            if (test[key] > (u_short) bucket_size) {
+                goto next;
+            }
+        }
+
+        goto found;
+
+    next:
+
+        continue;
+    }
+
+    size = hinit->max_size;
+
+    ngx_log_error(NGX_LOG_WARN, hinit->pool->log, 0,
+                  "could not build optimal %s, you should increase "
+                  "either %s_max_size: %i or %s_bucket_size: %i; "
+                  "ignoring %s_bucket_size",
+                  hinit->name, hinit->name, hinit->max_size,
+                  hinit->name, hinit->bucket_size, hinit->name);
+
+found:
+
+    for (i = 0; i < size; i++) {
+        test[i] = sizeof(void *);
+    }
+
+    for (n = 0; n < nelts; n++) {
+        if (names[n].key.data == NULL) {
+            continue;
+        }
+
+        key = names[n].key_hash % size;
+        test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));
+    }
+
+    len = 0;
+
+    for (i = 0; i < size; i++) {
+        if (test[i] == sizeof(void *)) {
+            continue;
+        }
+
+        test[i] = (u_short) (ngx_align(test[i], ngx_cacheline_size));
+
+        len += test[i];
+    }
+
+    if (hinit->hash == NULL) {
+        hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t)
+                                             + size * sizeof(ngx_hash_elt_t *));
+        if (hinit->hash == NULL) {
+            ngx_free(test);
+            return NGX_ERROR;
+        }
+
+        buckets = (ngx_hash_elt_t **)
+                      ((u_char *) hinit->hash + sizeof(ngx_hash_wildcard_t));
+
+    } else {
+        buckets = ngx_pcalloc(hinit->pool, size * sizeof(ngx_hash_elt_t *));
+        if (buckets == NULL) {
+            ngx_free(test);
+            return NGX_ERROR;
+        }
+    }
+
+    elts = ngx_palloc(hinit->pool, len + ngx_cacheline_size);
+    if (elts == NULL) {
+        ngx_free(test);
+        return NGX_ERROR;
+    }
+
+    elts = ngx_align_ptr(elts, ngx_cacheline_size);
+
+    for (i = 0; i < size; i++) {
+        if (test[i] == sizeof(void *)) {
+            continue;
+        }
+
+        buckets[i] = (ngx_hash_elt_t *) elts;
+        elts += test[i];
+    }
+
+    for (i = 0; i < size; i++) {
+        test[i] = 0;
+    }
+
+    for (n = 0; n < nelts; n++) {
+        if (names[n].key.data == NULL) {
+            continue;
+        }
+
+        key = names[n].key_hash % size;
+        elt = (ngx_hash_elt_t *) ((u_char *) buckets[key] + test[key]);
+
+        elt->value = names[n].value;
+        elt->len = (u_short) names[n].key.len;
+
+        ngx_strlow(elt->name, names[n].key.data, names[n].key.len);
+
+        test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));
+    }
+
+    for (i = 0; i < size; i++) {
+        if (buckets[i] == NULL) {
+            continue;
+        }
+
+        elt = (ngx_hash_elt_t *) ((u_char *) buckets[i] + test[i]);
+
+        elt->value = NULL;
+    }
+
+    ngx_free(test);
+
+    hinit->hash->buckets = buckets;
+    hinit->hash->size = size;
+
+#if 0
+
+    for (i = 0; i < size; i++) {
+        ngx_str_t   val;
+        ngx_uint_t  key;
+
+        elt = buckets[i];
+
+        if (elt == NULL) {
+            ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
+                          "%ui: NULL", i);
+            continue;
+        }
+
+        while (elt->value) {
+            val.len = elt->len;
+            val.data = &elt->name[0];
+
+            key = hinit->key(val.data, val.len);
+
+            ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
+                          "%ui: %p \"%V\" %ui", i, elt, &val, key);
+
+            elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,
+                                                   sizeof(void *));
+        }
+    }
+
+#endif
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_hash_wildcard_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,
+    ngx_uint_t nelts)
+{
+    size_t                len, dot_len;
+    ngx_uint_t            i, n, dot;
+    ngx_array_t           curr_names, next_names;
+    ngx_hash_key_t       *name, *next_name;
+    ngx_hash_init_t       h;
+    ngx_hash_wildcard_t  *wdc;
+
+    if (ngx_array_init(&curr_names, hinit->temp_pool, nelts,
+                       sizeof(ngx_hash_key_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&next_names, hinit->temp_pool, nelts,
+                       sizeof(ngx_hash_key_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    for (n = 0; n < nelts; n = i) {
+
+#if 0
+        ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
+                      "wc0: \"%V\"", &names[n].key);
+#endif
+
+        dot = 0;
+
+        for (len = 0; len < names[n].key.len; len++) {
+            if (names[n].key.data[len] == '.') {
+                dot = 1;
+                break;
+            }
+        }
+
+        name = ngx_array_push(&curr_names);
+        if (name == NULL) {
+            return NGX_ERROR;
+        }
+
+        name->key.len = len;
+        name->key.data = names[n].key.data;
+        name->key_hash = hinit->key(name->key.data, name->key.len);
+        name->value = names[n].value;
+
+#if 0
+        ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
+                      "wc1: \"%V\" %ui", &name->key, dot);
+#endif
+
+        dot_len = len + 1;
+
+        if (dot) {
+            len++;
+        }
+
+        next_names.nelts = 0;
+
+        if (names[n].key.len != len) {
+            next_name = ngx_array_push(&next_names);
+            if (next_name == NULL) {
+                return NGX_ERROR;
+            }
+
+            next_name->key.len = names[n].key.len - len;
+            next_name->key.data = names[n].key.data + len;
+            next_name->key_hash = 0;
+            next_name->value = names[n].value;
+
+#if 0
+            ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
+                          "wc2: \"%V\"", &next_name->key);
+#endif
+        }
+
+        for (i = n + 1; i < nelts; i++) {
+            if (ngx_strncmp(names[n].key.data, names[i].key.data, len) != 0) {
+                break;
+            }
+
+            if (!dot
+                && names[i].key.len > len
+                && names[i].key.data[len] != '.')
+            {
+                break;
+            }
+
+            next_name = ngx_array_push(&next_names);
+            if (next_name == NULL) {
+                return NGX_ERROR;
+            }
+
+            next_name->key.len = names[i].key.len - dot_len;
+            next_name->key.data = names[i].key.data + dot_len;
+            next_name->key_hash = 0;
+            next_name->value = names[i].value;
+
+#if 0
+            ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
+                          "wc3: \"%V\"", &next_name->key);
+#endif
+        }
+
+        if (next_names.nelts) {
+
+            h = *hinit;
+            h.hash = NULL;
+
+            if (ngx_hash_wildcard_init(&h, (ngx_hash_key_t *) next_names.elts,
+                                       next_names.nelts)
+                != NGX_OK)
+            {
+                return NGX_ERROR;
+            }
+
+            wdc = (ngx_hash_wildcard_t *) h.hash;
+
+            if (names[n].key.len == len) {
+                wdc->value = names[n].value;
+            }
+
+            name->value = (void *) ((uintptr_t) wdc | (dot ? 3 : 2));
+
+        } else if (dot) {
+            name->value = (void *) ((uintptr_t) name->value | 1);
+        }
+    }
+
+    if (ngx_hash_init(hinit, (ngx_hash_key_t *) curr_names.elts,
+                      curr_names.nelts)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_uint_t
+ngx_hash_key(u_char *data, size_t len)
+{
+    ngx_uint_t  i, key;
+
+    key = 0;
+
+    for (i = 0; i < len; i++) {
+        key = ngx_hash(key, data[i]);
+    }
+
+    return key;
+}
+
+
+ngx_uint_t
+ngx_hash_key_lc(u_char *data, size_t len)
+{
+    ngx_uint_t  i, key;
+
+    key = 0;
+
+    for (i = 0; i < len; i++) {
+        key = ngx_hash(key, ngx_tolower(data[i]));
+    }
+
+    return key;
+}
+
+
+ngx_uint_t
+ngx_hash_strlow(u_char *dst, u_char *src, size_t n)
+{
+    ngx_uint_t  key;
+
+    key = 0;
+
+    while (n--) {
+        *dst = ngx_tolower(*src);
+        key = ngx_hash(key, *dst);
+        dst++;
+        src++;
+    }
+
+    return key;
+}
+
+
+ngx_int_t
+ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type)
+{
+    ngx_uint_t  asize;
+
+    if (type == NGX_HASH_SMALL) {
+        asize = 4;
+        ha->hsize = 107;
+
+    } else {
+        asize = NGX_HASH_LARGE_ASIZE;
+        ha->hsize = NGX_HASH_LARGE_HSIZE;
+    }
+
+    if (ngx_array_init(&ha->keys, ha->temp_pool, asize, sizeof(ngx_hash_key_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&ha->dns_wc_head, ha->temp_pool, asize,
+                       sizeof(ngx_hash_key_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&ha->dns_wc_tail, ha->temp_pool, asize,
+                       sizeof(ngx_hash_key_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    ha->keys_hash = ngx_pcalloc(ha->temp_pool, sizeof(ngx_array_t) * ha->hsize);
+    if (ha->keys_hash == NULL) {
+        return NGX_ERROR;
+    }
+
+    ha->dns_wc_head_hash = ngx_pcalloc(ha->temp_pool,
+                                       sizeof(ngx_array_t) * ha->hsize);
+    if (ha->dns_wc_head_hash == NULL) {
+        return NGX_ERROR;
+    }
+
+    ha->dns_wc_tail_hash = ngx_pcalloc(ha->temp_pool,
+                                       sizeof(ngx_array_t) * ha->hsize);
+    if (ha->dns_wc_tail_hash == NULL) {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_hash_add_key(ngx_hash_keys_arrays_t *ha, ngx_str_t *key, void *value,
+    ngx_uint_t flags)
+{
+    size_t           len;
+    u_char          *p;
+    ngx_str_t       *name;
+    ngx_uint_t       i, k, n, skip, last;
+    ngx_array_t     *keys, *hwc;
+    ngx_hash_key_t  *hk;
+
+    last = key->len;
+
+    if (flags & NGX_HASH_WILDCARD_KEY) {
+
+        /*
+         * supported wildcards:
+         *     "*.example.com", ".example.com", and "www.example.*"
+         */
+
+        n = 0;
+
+        for (i = 0; i < key->len; i++) {
+
+            if (key->data[i] == '*') {
+                if (++n > 1) {
+                    return NGX_DECLINED;
+                }
+            }
+
+            if (key->data[i] == '.' && key->data[i + 1] == '.') {
+                return NGX_DECLINED;
+            }
+
+            if (key->data[i] == '\0') {
+                return NGX_DECLINED;
+            }
+        }
+
+        if (key->len > 1 && key->data[0] == '.') {
+            skip = 1;
+            goto wildcard;
+        }
+
+        if (key->len > 2) {
+
+            if (key->data[0] == '*' && key->data[1] == '.') {
+                skip = 2;
+                goto wildcard;
+            }
+
+            if (key->data[i - 2] == '.' && key->data[i - 1] == '*') {
+                skip = 0;
+                last -= 2;
+                goto wildcard;
+            }
+        }
+
+        if (n) {
+            return NGX_DECLINED;
+        }
+    }
+
+    /* exact hash */
+
+    k = 0;
+
+    for (i = 0; i < last; i++) {
+        if (!(flags & NGX_HASH_READONLY_KEY)) {
+            key->data[i] = ngx_tolower(key->data[i]);
+        }
+        k = ngx_hash(k, key->data[i]);
+    }
+
+    k %= ha->hsize;
+
+    /* check conflicts in exact hash */
+
+    name = ha->keys_hash[k].elts;
+
+    if (name) {
+        for (i = 0; i < ha->keys_hash[k].nelts; i++) {
+            if (last != name[i].len) {
+                continue;
+            }
+
+            if (ngx_strncmp(key->data, name[i].data, last) == 0) {
+                return NGX_BUSY;
+            }
+        }
+
+    } else {
+        if (ngx_array_init(&ha->keys_hash[k], ha->temp_pool, 4,
+                           sizeof(ngx_str_t))
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    name = ngx_array_push(&ha->keys_hash[k]);
+    if (name == NULL) {
+        return NGX_ERROR;
+    }
+
+    *name = *key;
+
+    hk = ngx_array_push(&ha->keys);
+    if (hk == NULL) {
+        return NGX_ERROR;
+    }
+
+    hk->key = *key;
+    hk->key_hash = ngx_hash_key(key->data, last);
+    hk->value = value;
+
+    return NGX_OK;
+
+
+wildcard:
+
+    /* wildcard hash */
+
+    k = ngx_hash_strlow(&key->data[skip], &key->data[skip], last - skip);
+
+    k %= ha->hsize;
+
+    if (skip == 1) {
+
+        /* check conflicts in exact hash for ".example.com" */
+
+        name = ha->keys_hash[k].elts;
+
+        if (name) {
+            len = last - skip;
+
+            for (i = 0; i < ha->keys_hash[k].nelts; i++) {
+                if (len != name[i].len) {
+                    continue;
+                }
+
+                if (ngx_strncmp(&key->data[1], name[i].data, len) == 0) {
+                    return NGX_BUSY;
+                }
+            }
+
+        } else {
+            if (ngx_array_init(&ha->keys_hash[k], ha->temp_pool, 4,
+                               sizeof(ngx_str_t))
+                != NGX_OK)
+            {
+                return NGX_ERROR;
+            }
+        }
+
+        name = ngx_array_push(&ha->keys_hash[k]);
+        if (name == NULL) {
+            return NGX_ERROR;
+        }
+
+        name->len = last - 1;
+        name->data = ngx_pnalloc(ha->temp_pool, name->len);
+        if (name->data == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_memcpy(name->data, &key->data[1], name->len);
+    }
+
+
+    if (skip) {
+
+        /*
+         * convert "*.example.com" to "com.example.\0"
+         *      and ".example.com" to "com.example\0"
+         */
+
+        p = ngx_pnalloc(ha->temp_pool, last);
+        if (p == NULL) {
+            return NGX_ERROR;
+        }
+
+        len = 0;
+        n = 0;
+
+        for (i = last - 1; i; i--) {
+            if (key->data[i] == '.') {
+                ngx_memcpy(&p[n], &key->data[i + 1], len);
+                n += len;
+                p[n++] = '.';
+                len = 0;
+                continue;
+            }
+
+            len++;
+        }
+
+        if (len) {
+            ngx_memcpy(&p[n], &key->data[1], len);
+            n += len;
+        }
+
+        p[n] = '\0';
+
+        hwc = &ha->dns_wc_head;
+        keys = &ha->dns_wc_head_hash[k];
+
+    } else {
+
+        /* convert "www.example.*" to "www.example\0" */
+
+        last++;
+
+        p = ngx_pnalloc(ha->temp_pool, last);
+        if (p == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_cpystrn(p, key->data, last);
+
+        hwc = &ha->dns_wc_tail;
+        keys = &ha->dns_wc_tail_hash[k];
+    }
+
+
+    /* check conflicts in wildcard hash */
+
+    name = keys->elts;
+
+    if (name) {
+        len = last - skip;
+
+        for (i = 0; i < keys->nelts; i++) {
+            if (len != name[i].len) {
+                continue;
+            }
+
+            if (ngx_strncmp(key->data + skip, name[i].data, len) == 0) {
+                return NGX_BUSY;
+            }
+        }
+
+    } else {
+        if (ngx_array_init(keys, ha->temp_pool, 4, sizeof(ngx_str_t)) != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    name = ngx_array_push(keys);
+    if (name == NULL) {
+        return NGX_ERROR;
+    }
+
+    name->len = last - skip;
+    name->data = ngx_pnalloc(ha->temp_pool, name->len);
+    if (name->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(name->data, key->data + skip, name->len);
+
+
+    /* add to wildcard hash */
+
+    hk = ngx_array_push(hwc);
+    if (hk == NULL) {
+        return NGX_ERROR;
+    }
+
+    hk->key.len = last - 1;
+    hk->key.data = p;
+    hk->key_hash = 0;
+    hk->value = value;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/core/ngx_hash.h b/nginx/src/core/ngx_hash.h
new file mode 100644 (file)
index 0000000..abc3cbe
--- /dev/null
@@ -0,0 +1,122 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HASH_H_INCLUDED_
+#define _NGX_HASH_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef struct {
+    void             *value;
+    u_short           len;
+    u_char            name[1];
+} ngx_hash_elt_t;
+
+
+typedef struct {
+    ngx_hash_elt_t  **buckets;
+    ngx_uint_t        size;
+} ngx_hash_t;
+
+
+typedef struct {
+    ngx_hash_t        hash;
+    void             *value;
+} ngx_hash_wildcard_t;
+
+
+typedef struct {
+    ngx_str_t         key;
+    ngx_uint_t        key_hash;
+    void             *value;
+} ngx_hash_key_t;
+
+
+typedef ngx_uint_t (*ngx_hash_key_pt) (u_char *data, size_t len);
+
+
+typedef struct {
+    ngx_hash_t            hash;
+    ngx_hash_wildcard_t  *wc_head;
+    ngx_hash_wildcard_t  *wc_tail;
+} ngx_hash_combined_t;
+
+
+typedef struct {
+    ngx_hash_t       *hash;
+    ngx_hash_key_pt   key;
+
+    ngx_uint_t        max_size;
+    ngx_uint_t        bucket_size;
+
+    char             *name;
+    ngx_pool_t       *pool;
+    ngx_pool_t       *temp_pool;
+} ngx_hash_init_t;
+
+
+#define NGX_HASH_SMALL            1
+#define NGX_HASH_LARGE            2
+
+#define NGX_HASH_LARGE_ASIZE      16384
+#define NGX_HASH_LARGE_HSIZE      10007
+
+#define NGX_HASH_WILDCARD_KEY     1
+#define NGX_HASH_READONLY_KEY     2
+
+
+typedef struct {
+    ngx_uint_t        hsize;
+
+    ngx_pool_t       *pool;
+    ngx_pool_t       *temp_pool;
+
+    ngx_array_t       keys;
+    ngx_array_t      *keys_hash;
+
+    ngx_array_t       dns_wc_head;
+    ngx_array_t      *dns_wc_head_hash;
+
+    ngx_array_t       dns_wc_tail;
+    ngx_array_t      *dns_wc_tail_hash;
+} ngx_hash_keys_arrays_t;
+
+
+typedef struct {
+    ngx_uint_t        hash;
+    ngx_str_t         key;
+    ngx_str_t         value;
+    u_char           *lowcase_key;
+} ngx_table_elt_t;
+
+
+void *ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len);
+void *ngx_hash_find_wc_head(ngx_hash_wildcard_t *hwc, u_char *name, size_t len);
+void *ngx_hash_find_wc_tail(ngx_hash_wildcard_t *hwc, u_char *name, size_t len);
+void *ngx_hash_find_combined(ngx_hash_combined_t *hash, ngx_uint_t key,
+    u_char *name, size_t len);
+
+ngx_int_t ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,
+    ngx_uint_t nelts);
+ngx_int_t ngx_hash_wildcard_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,
+    ngx_uint_t nelts);
+
+#define ngx_hash(key, c)   ((ngx_uint_t) key * 31 + c)
+ngx_uint_t ngx_hash_key(u_char *data, size_t len);
+ngx_uint_t ngx_hash_key_lc(u_char *data, size_t len);
+ngx_uint_t ngx_hash_strlow(u_char *dst, u_char *src, size_t n);
+
+
+ngx_int_t ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type);
+ngx_int_t ngx_hash_add_key(ngx_hash_keys_arrays_t *ha, ngx_str_t *key,
+    void *value, ngx_uint_t flags);
+
+
+#endif /* _NGX_HASH_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_inet.c b/nginx/src/core/ngx_inet.c
new file mode 100644 (file)
index 0000000..db48b93
--- /dev/null
@@ -0,0 +1,1497 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+static ngx_int_t ngx_parse_unix_domain_url(ngx_pool_t *pool, ngx_url_t *u);
+static ngx_int_t ngx_parse_inet_url(ngx_pool_t *pool, ngx_url_t *u);
+static ngx_int_t ngx_parse_inet6_url(ngx_pool_t *pool, ngx_url_t *u);
+
+
+in_addr_t
+ngx_inet_addr(u_char *text, size_t len)
+{
+    u_char      *p, c;
+    in_addr_t    addr;
+    ngx_uint_t   octet, n;
+
+    addr = 0;
+    octet = 0;
+    n = 0;
+
+    for (p = text; p < text + len; p++) {
+        c = *p;
+
+        if (c >= '0' && c <= '9') {
+            octet = octet * 10 + (c - '0');
+
+            if (octet > 255) {
+                return INADDR_NONE;
+            }
+
+            continue;
+        }
+
+        if (c == '.') {
+            addr = (addr << 8) + octet;
+            octet = 0;
+            n++;
+            continue;
+        }
+
+        return INADDR_NONE;
+    }
+
+    if (n == 3) {
+        addr = (addr << 8) + octet;
+        return htonl(addr);
+    }
+
+    return INADDR_NONE;
+}
+
+
+#if (NGX_HAVE_INET6)
+
+ngx_int_t
+ngx_inet6_addr(u_char *p, size_t len, u_char *addr)
+{
+    u_char      c, *zero, *digit, *s, *d;
+    size_t      len4;
+    ngx_uint_t  n, nibbles, word;
+
+    if (len == 0) {
+        return NGX_ERROR;
+    }
+
+    zero = NULL;
+    digit = NULL;
+    len4 = 0;
+    nibbles = 0;
+    word = 0;
+    n = 8;
+
+    if (p[0] == ':') {
+        p++;
+        len--;
+    }
+
+    for (/* void */; len; len--) {
+        c = *p++;
+
+        if (c == ':') {
+            if (nibbles) {
+                digit = p;
+                len4 = len;
+                *addr++ = (u_char) (word >> 8);
+                *addr++ = (u_char) (word & 0xff);
+
+                if (--n) {
+                    nibbles = 0;
+                    word = 0;
+                    continue;
+                }
+
+            } else {
+                if (zero == NULL) {
+                    digit = p;
+                    len4 = len;
+                    zero = addr;
+                    continue;
+                }
+            }
+
+            return NGX_ERROR;
+        }
+
+        if (c == '.' && nibbles) {
+            if (n < 2 || digit == NULL) {
+                return NGX_ERROR;
+            }
+
+            word = ngx_inet_addr(digit, len4 - 1);
+            if (word == INADDR_NONE) {
+                return NGX_ERROR;
+            }
+
+            word = ntohl(word);
+            *addr++ = (u_char) ((word >> 24) & 0xff);
+            *addr++ = (u_char) ((word >> 16) & 0xff);
+            n--;
+            break;
+        }
+
+        if (++nibbles > 4) {
+            return NGX_ERROR;
+        }
+
+        if (c >= '0' && c <= '9') {
+            word = word * 16 + (c - '0');
+            continue;
+        }
+
+        c |= 0x20;
+
+        if (c >= 'a' && c <= 'f') {
+            word = word * 16 + (c - 'a') + 10;
+            continue;
+        }
+
+        return NGX_ERROR;
+    }
+
+    if (nibbles == 0 && zero == NULL) {
+        return NGX_ERROR;
+    }
+
+    *addr++ = (u_char) (word >> 8);
+    *addr++ = (u_char) (word & 0xff);
+
+    if (--n) {
+        if (zero) {
+            n *= 2;
+            s = addr - 1;
+            d = s + n;
+            while (s >= zero) {
+                *d-- = *s--;
+            }
+            ngx_memzero(zero, n);
+            return NGX_OK;
+        }
+
+    } else {
+        if (zero == NULL) {
+            return NGX_OK;
+        }
+    }
+
+    return NGX_ERROR;
+}
+
+#endif
+
+
+size_t
+ngx_sock_ntop(struct sockaddr *sa, socklen_t socklen, u_char *text, size_t len,
+    ngx_uint_t port)
+{
+    u_char               *p;
+#if (NGX_HAVE_INET6 || NGX_HAVE_UNIX_DOMAIN)
+    size_t                n;
+#endif
+    struct sockaddr_in   *sin;
+#if (NGX_HAVE_INET6)
+    struct sockaddr_in6  *sin6;
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+    struct sockaddr_un   *saun;
+#endif
+
+    switch (sa->sa_family) {
+
+    case AF_INET:
+
+        sin = (struct sockaddr_in *) sa;
+        p = (u_char *) &sin->sin_addr;
+
+        if (port) {
+            p = ngx_snprintf(text, len, "%ud.%ud.%ud.%ud:%d",
+                             p[0], p[1], p[2], p[3], ntohs(sin->sin_port));
+        } else {
+            p = ngx_snprintf(text, len, "%ud.%ud.%ud.%ud",
+                             p[0], p[1], p[2], p[3]);
+        }
+
+        return (p - text);
+
+#if (NGX_HAVE_INET6)
+
+    case AF_INET6:
+
+        sin6 = (struct sockaddr_in6 *) sa;
+
+        n = 0;
+
+        if (port) {
+            text[n++] = '[';
+        }
+
+        n = ngx_inet6_ntop(sin6->sin6_addr.s6_addr, &text[n], len);
+
+        if (port) {
+            n = ngx_sprintf(&text[1 + n], "]:%d",
+                            ntohs(sin6->sin6_port)) - text;
+        }
+
+        return n;
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+
+    case AF_UNIX:
+        saun = (struct sockaddr_un *) sa;
+
+        /* on Linux sockaddr might not include sun_path at all */
+
+        if (socklen <= (socklen_t) offsetof(struct sockaddr_un, sun_path)) {
+            p = ngx_snprintf(text, len, "unix:%Z");
+
+        } else {
+            n = ngx_strnlen((u_char *) saun->sun_path,
+                            socklen - offsetof(struct sockaddr_un, sun_path));
+            p = ngx_snprintf(text, len, "unix:%*s%Z", n, saun->sun_path);
+        }
+
+        /* we do not include trailing zero in address length */
+
+        return (p - text - 1);
+
+#endif
+
+    default:
+        return 0;
+    }
+}
+
+
+size_t
+ngx_inet_ntop(int family, void *addr, u_char *text, size_t len)
+{
+    u_char  *p;
+
+    switch (family) {
+
+    case AF_INET:
+
+        p = addr;
+
+        return ngx_snprintf(text, len, "%ud.%ud.%ud.%ud",
+                            p[0], p[1], p[2], p[3])
+               - text;
+
+#if (NGX_HAVE_INET6)
+
+    case AF_INET6:
+        return ngx_inet6_ntop(addr, text, len);
+
+#endif
+
+    default:
+        return 0;
+    }
+}
+
+
+#if (NGX_HAVE_INET6)
+
+size_t
+ngx_inet6_ntop(u_char *p, u_char *text, size_t len)
+{
+    u_char      *dst;
+    size_t       max, n;
+    ngx_uint_t   i, zero, last;
+
+    if (len < NGX_INET6_ADDRSTRLEN) {
+        return 0;
+    }
+
+    zero = (ngx_uint_t) -1;
+    last = (ngx_uint_t) -1;
+    max = 1;
+    n = 0;
+
+    for (i = 0; i < 16; i += 2) {
+
+        if (p[i] || p[i + 1]) {
+
+            if (max < n) {
+                zero = last;
+                max = n;
+            }
+
+            n = 0;
+            continue;
+        }
+
+        if (n++ == 0) {
+            last = i;
+        }
+    }
+
+    if (max < n) {
+        zero = last;
+        max = n;
+    }
+
+    dst = text;
+    n = 16;
+
+    if (zero == 0) {
+
+        if ((max == 5 && p[10] == 0xff && p[11] == 0xff)
+            || (max == 6)
+            || (max == 7 && p[14] != 0 && p[15] != 1))
+        {
+            n = 12;
+        }
+
+        *dst++ = ':';
+    }
+
+    for (i = 0; i < n; i += 2) {
+
+        if (i == zero) {
+            *dst++ = ':';
+            i += (max - 1) * 2;
+            continue;
+        }
+
+        dst = ngx_sprintf(dst, "%xd", p[i] * 256 + p[i + 1]);
+
+        if (i < 14) {
+            *dst++ = ':';
+        }
+    }
+
+    if (n == 12) {
+        dst = ngx_sprintf(dst, "%ud.%ud.%ud.%ud", p[12], p[13], p[14], p[15]);
+    }
+
+    return dst - text;
+}
+
+#endif
+
+
+ngx_int_t
+ngx_ptocidr(ngx_str_t *text, ngx_cidr_t *cidr)
+{
+    u_char      *addr, *mask, *last;
+    size_t       len;
+    ngx_int_t    shift;
+#if (NGX_HAVE_INET6)
+    ngx_int_t    rc;
+    ngx_uint_t   s, i;
+#endif
+
+    addr = text->data;
+    last = addr + text->len;
+
+    mask = ngx_strlchr(addr, last, '/');
+    len = (mask ? mask : last) - addr;
+
+    cidr->u.in.addr = ngx_inet_addr(addr, len);
+
+    if (cidr->u.in.addr != INADDR_NONE) {
+        cidr->family = AF_INET;
+
+        if (mask == NULL) {
+            cidr->u.in.mask = 0xffffffff;
+            return NGX_OK;
+        }
+
+#if (NGX_HAVE_INET6)
+    } else if (ngx_inet6_addr(addr, len, cidr->u.in6.addr.s6_addr) == NGX_OK) {
+        cidr->family = AF_INET6;
+
+        if (mask == NULL) {
+            ngx_memset(cidr->u.in6.mask.s6_addr, 0xff, 16);
+            return NGX_OK;
+        }
+
+#endif
+    } else {
+        return NGX_ERROR;
+    }
+
+    mask++;
+
+    shift = ngx_atoi(mask, last - mask);
+    if (shift == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    switch (cidr->family) {
+
+#if (NGX_HAVE_INET6)
+    case AF_INET6:
+        if (shift > 128) {
+            return NGX_ERROR;
+        }
+
+        addr = cidr->u.in6.addr.s6_addr;
+        mask = cidr->u.in6.mask.s6_addr;
+        rc = NGX_OK;
+
+        for (i = 0; i < 16; i++) {
+
+            s = (shift > 8) ? 8 : shift;
+            shift -= s;
+
+            mask[i] = (u_char) (0xffu << (8 - s));
+
+            if (addr[i] != (addr[i] & mask[i])) {
+                rc = NGX_DONE;
+                addr[i] &= mask[i];
+            }
+        }
+
+        return rc;
+#endif
+
+    default: /* AF_INET */
+        if (shift > 32) {
+            return NGX_ERROR;
+        }
+
+        if (shift) {
+            cidr->u.in.mask = htonl((uint32_t) (0xffffffffu << (32 - shift)));
+
+        } else {
+            /* x86 compilers use a shl instruction that shifts by modulo 32 */
+            cidr->u.in.mask = 0;
+        }
+
+        if (cidr->u.in.addr == (cidr->u.in.addr & cidr->u.in.mask)) {
+            return NGX_OK;
+        }
+
+        cidr->u.in.addr &= cidr->u.in.mask;
+
+        return NGX_DONE;
+    }
+}
+
+
+ngx_int_t
+ngx_cidr_match(struct sockaddr *sa, ngx_array_t *cidrs)
+{
+#if (NGX_HAVE_INET6)
+    u_char           *p;
+#endif
+    in_addr_t         inaddr;
+    ngx_cidr_t       *cidr;
+    ngx_uint_t        family, i;
+#if (NGX_HAVE_INET6)
+    ngx_uint_t        n;
+    struct in6_addr  *inaddr6;
+#endif
+
+#if (NGX_SUPPRESS_WARN)
+    inaddr = 0;
+#if (NGX_HAVE_INET6)
+    inaddr6 = NULL;
+#endif
+#endif
+
+    family = sa->sa_family;
+
+    if (family == AF_INET) {
+        inaddr = ((struct sockaddr_in *) sa)->sin_addr.s_addr;
+    }
+
+#if (NGX_HAVE_INET6)
+    else if (family == AF_INET6) {
+        inaddr6 = &((struct sockaddr_in6 *) sa)->sin6_addr;
+
+        if (IN6_IS_ADDR_V4MAPPED(inaddr6)) {
+            family = AF_INET;
+
+            p = inaddr6->s6_addr;
+
+            inaddr = p[12] << 24;
+            inaddr += p[13] << 16;
+            inaddr += p[14] << 8;
+            inaddr += p[15];
+
+            inaddr = htonl(inaddr);
+        }
+    }
+#endif
+
+    for (cidr = cidrs->elts, i = 0; i < cidrs->nelts; i++) {
+        if (cidr[i].family != family) {
+            goto next;
+        }
+
+        switch (family) {
+
+#if (NGX_HAVE_INET6)
+        case AF_INET6:
+            for (n = 0; n < 16; n++) {
+                if ((inaddr6->s6_addr[n] & cidr[i].u.in6.mask.s6_addr[n])
+                    != cidr[i].u.in6.addr.s6_addr[n])
+                {
+                    goto next;
+                }
+            }
+            break;
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+        case AF_UNIX:
+            break;
+#endif
+
+        default: /* AF_INET */
+            if ((inaddr & cidr[i].u.in.mask) != cidr[i].u.in.addr) {
+                goto next;
+            }
+            break;
+        }
+
+        return NGX_OK;
+
+    next:
+        continue;
+    }
+
+    return NGX_DECLINED;
+}
+
+
+ngx_int_t
+ngx_parse_addr(ngx_pool_t *pool, ngx_addr_t *addr, u_char *text, size_t len)
+{
+    in_addr_t             inaddr;
+    ngx_uint_t            family;
+    struct sockaddr_in   *sin;
+#if (NGX_HAVE_INET6)
+    struct in6_addr       inaddr6;
+    struct sockaddr_in6  *sin6;
+
+    /*
+     * prevent MSVC8 warning:
+     *    potentially uninitialized local variable 'inaddr6' used
+     */
+    ngx_memzero(&inaddr6, sizeof(struct in6_addr));
+#endif
+
+    inaddr = ngx_inet_addr(text, len);
+
+    if (inaddr != INADDR_NONE) {
+        family = AF_INET;
+        len = sizeof(struct sockaddr_in);
+
+#if (NGX_HAVE_INET6)
+    } else if (ngx_inet6_addr(text, len, inaddr6.s6_addr) == NGX_OK) {
+        family = AF_INET6;
+        len = sizeof(struct sockaddr_in6);
+
+#endif
+    } else {
+        return NGX_DECLINED;
+    }
+
+    addr->sockaddr = ngx_pcalloc(pool, len);
+    if (addr->sockaddr == NULL) {
+        return NGX_ERROR;
+    }
+
+    addr->sockaddr->sa_family = (u_char) family;
+    addr->socklen = len;
+
+    switch (family) {
+
+#if (NGX_HAVE_INET6)
+    case AF_INET6:
+        sin6 = (struct sockaddr_in6 *) addr->sockaddr;
+        ngx_memcpy(sin6->sin6_addr.s6_addr, inaddr6.s6_addr, 16);
+        break;
+#endif
+
+    default: /* AF_INET */
+        sin = (struct sockaddr_in *) addr->sockaddr;
+        sin->sin_addr.s_addr = inaddr;
+        break;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_parse_addr_port(ngx_pool_t *pool, ngx_addr_t *addr, u_char *text,
+    size_t len)
+{
+    u_char     *p, *last;
+    size_t      plen;
+    ngx_int_t   rc, port;
+
+    rc = ngx_parse_addr(pool, addr, text, len);
+
+    if (rc != NGX_DECLINED) {
+        return rc;
+    }
+
+    last = text + len;
+
+#if (NGX_HAVE_INET6)
+    if (len && text[0] == '[') {
+
+        p = ngx_strlchr(text, last, ']');
+
+        if (p == NULL || p == last - 1 || *++p != ':') {
+            return NGX_DECLINED;
+        }
+
+        text++;
+        len -= 2;
+
+    } else
+#endif
+
+    {
+        p = ngx_strlchr(text, last, ':');
+
+        if (p == NULL) {
+            return NGX_DECLINED;
+        }
+    }
+
+    p++;
+    plen = last - p;
+
+    port = ngx_atoi(p, plen);
+
+    if (port < 1 || port > 65535) {
+        return NGX_DECLINED;
+    }
+
+    len -= plen + 1;
+
+    rc = ngx_parse_addr(pool, addr, text, len);
+
+    if (rc != NGX_OK) {
+        return rc;
+    }
+
+    ngx_inet_set_port(addr->sockaddr, (in_port_t) port);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_parse_url(ngx_pool_t *pool, ngx_url_t *u)
+{
+    u_char  *p;
+    size_t   len;
+
+    p = u->url.data;
+    len = u->url.len;
+
+    if (len >= 5 && ngx_strncasecmp(p, (u_char *) "unix:", 5) == 0) {
+        return ngx_parse_unix_domain_url(pool, u);
+    }
+
+    if (len && p[0] == '[') {
+        return ngx_parse_inet6_url(pool, u);
+    }
+
+    return ngx_parse_inet_url(pool, u);
+}
+
+
+static ngx_int_t
+ngx_parse_unix_domain_url(ngx_pool_t *pool, ngx_url_t *u)
+{
+#if (NGX_HAVE_UNIX_DOMAIN)
+    u_char              *path, *uri, *last;
+    size_t               len;
+    struct sockaddr_un  *saun;
+
+    len = u->url.len;
+    path = u->url.data;
+
+    path += 5;
+    len -= 5;
+
+    if (u->uri_part) {
+
+        last = path + len;
+        uri = ngx_strlchr(path, last, ':');
+
+        if (uri) {
+            len = uri - path;
+            uri++;
+            u->uri.len = last - uri;
+            u->uri.data = uri;
+        }
+    }
+
+    if (len == 0) {
+        u->err = "no path in the unix domain socket";
+        return NGX_ERROR;
+    }
+
+    u->host.len = len++;
+    u->host.data = path;
+
+    if (len > sizeof(saun->sun_path)) {
+        u->err = "too long path in the unix domain socket";
+        return NGX_ERROR;
+    }
+
+    u->socklen = sizeof(struct sockaddr_un);
+    saun = (struct sockaddr_un *) &u->sockaddr;
+    saun->sun_family = AF_UNIX;
+    (void) ngx_cpystrn((u_char *) saun->sun_path, path, len);
+
+    u->addrs = ngx_pcalloc(pool, sizeof(ngx_addr_t));
+    if (u->addrs == NULL) {
+        return NGX_ERROR;
+    }
+
+    saun = ngx_pcalloc(pool, sizeof(struct sockaddr_un));
+    if (saun == NULL) {
+        return NGX_ERROR;
+    }
+
+    u->family = AF_UNIX;
+    u->naddrs = 1;
+
+    saun->sun_family = AF_UNIX;
+    (void) ngx_cpystrn((u_char *) saun->sun_path, path, len);
+
+    u->addrs[0].sockaddr = (struct sockaddr *) saun;
+    u->addrs[0].socklen = sizeof(struct sockaddr_un);
+    u->addrs[0].name.len = len + 4;
+    u->addrs[0].name.data = u->url.data;
+
+    return NGX_OK;
+
+#else
+
+    u->err = "the unix domain sockets are not supported on this platform";
+
+    return NGX_ERROR;
+
+#endif
+}
+
+
+static ngx_int_t
+ngx_parse_inet_url(ngx_pool_t *pool, ngx_url_t *u)
+{
+    u_char               *p, *host, *port, *last, *uri, *args;
+    size_t                len;
+    ngx_int_t             n;
+    struct sockaddr_in   *sin;
+#if (NGX_HAVE_INET6)
+    struct sockaddr_in6  *sin6;
+#endif
+
+    u->socklen = sizeof(struct sockaddr_in);
+    sin = (struct sockaddr_in *) &u->sockaddr;
+    sin->sin_family = AF_INET;
+
+    u->family = AF_INET;
+
+    host = u->url.data;
+
+    last = host + u->url.len;
+
+    port = ngx_strlchr(host, last, ':');
+
+    uri = ngx_strlchr(host, last, '/');
+
+    args = ngx_strlchr(host, last, '?');
+
+    if (args) {
+        if (uri == NULL || args < uri) {
+            uri = args;
+        }
+    }
+
+    if (uri) {
+        if (u->listen || !u->uri_part) {
+            u->err = "invalid host";
+            return NGX_ERROR;
+        }
+
+        u->uri.len = last - uri;
+        u->uri.data = uri;
+
+        last = uri;
+
+        if (uri < port) {
+            port = NULL;
+        }
+    }
+
+    if (port) {
+        port++;
+
+        len = last - port;
+
+        n = ngx_atoi(port, len);
+
+        if (n < 1 || n > 65535) {
+            u->err = "invalid port";
+            return NGX_ERROR;
+        }
+
+        u->port = (in_port_t) n;
+        sin->sin_port = htons((in_port_t) n);
+
+        u->port_text.len = len;
+        u->port_text.data = port;
+
+        last = port - 1;
+
+    } else {
+        if (uri == NULL) {
+
+            if (u->listen) {
+
+                /* test value as port only */
+
+                n = ngx_atoi(host, last - host);
+
+                if (n != NGX_ERROR) {
+
+                    if (n < 1 || n > 65535) {
+                        u->err = "invalid port";
+                        return NGX_ERROR;
+                    }
+
+                    u->port = (in_port_t) n;
+                    sin->sin_port = htons((in_port_t) n);
+
+                    u->port_text.len = last - host;
+                    u->port_text.data = host;
+
+                    u->wildcard = 1;
+
+                    return NGX_OK;
+                }
+            }
+        }
+
+        u->no_port = 1;
+        u->port = u->default_port;
+        sin->sin_port = htons(u->default_port);
+    }
+
+    len = last - host;
+
+    if (len == 0) {
+        u->err = "no host";
+        return NGX_ERROR;
+    }
+
+    u->host.len = len;
+    u->host.data = host;
+
+    if (u->listen && len == 1 && *host == '*') {
+        sin->sin_addr.s_addr = INADDR_ANY;
+        u->wildcard = 1;
+        return NGX_OK;
+    }
+
+    sin->sin_addr.s_addr = ngx_inet_addr(host, len);
+
+    if (sin->sin_addr.s_addr != INADDR_NONE) {
+
+        if (sin->sin_addr.s_addr == INADDR_ANY) {
+            u->wildcard = 1;
+        }
+
+        u->naddrs = 1;
+
+        u->addrs = ngx_pcalloc(pool, sizeof(ngx_addr_t));
+        if (u->addrs == NULL) {
+            return NGX_ERROR;
+        }
+
+        sin = ngx_pcalloc(pool, sizeof(struct sockaddr_in));
+        if (sin == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_memcpy(sin, &u->sockaddr, sizeof(struct sockaddr_in));
+
+        u->addrs[0].sockaddr = (struct sockaddr *) sin;
+        u->addrs[0].socklen = sizeof(struct sockaddr_in);
+
+        p = ngx_pnalloc(pool, u->host.len + sizeof(":65535") - 1);
+        if (p == NULL) {
+            return NGX_ERROR;
+        }
+
+        u->addrs[0].name.len = ngx_sprintf(p, "%V:%d",
+                                           &u->host, u->port) - p;
+        u->addrs[0].name.data = p;
+
+        return NGX_OK;
+    }
+
+    if (u->no_resolve) {
+        return NGX_OK;
+    }
+
+    if (ngx_inet_resolve_host(pool, u) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    u->family = u->addrs[0].sockaddr->sa_family;
+    u->socklen = u->addrs[0].socklen;
+    ngx_memcpy(&u->sockaddr, u->addrs[0].sockaddr, u->addrs[0].socklen);
+
+    switch (u->family) {
+
+#if (NGX_HAVE_INET6)
+    case AF_INET6:
+        sin6 = (struct sockaddr_in6 *) &u->sockaddr;
+
+        if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+            u->wildcard = 1;
+        }
+
+        break;
+#endif
+
+    default: /* AF_INET */
+        sin = (struct sockaddr_in *) &u->sockaddr;
+
+        if (sin->sin_addr.s_addr == INADDR_ANY) {
+            u->wildcard = 1;
+        }
+
+        break;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_parse_inet6_url(ngx_pool_t *pool, ngx_url_t *u)
+{
+#if (NGX_HAVE_INET6)
+    u_char               *p, *host, *port, *last, *uri;
+    size_t                len;
+    ngx_int_t             n;
+    struct sockaddr_in6  *sin6;
+
+    u->socklen = sizeof(struct sockaddr_in6);
+    sin6 = (struct sockaddr_in6 *) &u->sockaddr;
+    sin6->sin6_family = AF_INET6;
+
+    host = u->url.data + 1;
+
+    last = u->url.data + u->url.len;
+
+    p = ngx_strlchr(host, last, ']');
+
+    if (p == NULL) {
+        u->err = "invalid host";
+        return NGX_ERROR;
+    }
+
+    port = p + 1;
+
+    uri = ngx_strlchr(port, last, '/');
+
+    if (uri) {
+        if (u->listen || !u->uri_part) {
+            u->err = "invalid host";
+            return NGX_ERROR;
+        }
+
+        u->uri.len = last - uri;
+        u->uri.data = uri;
+
+        last = uri;
+    }
+
+    if (port < last) {
+        if (*port != ':') {
+            u->err = "invalid host";
+            return NGX_ERROR;
+        }
+
+        port++;
+
+        len = last - port;
+
+        n = ngx_atoi(port, len);
+
+        if (n < 1 || n > 65535) {
+            u->err = "invalid port";
+            return NGX_ERROR;
+        }
+
+        u->port = (in_port_t) n;
+        sin6->sin6_port = htons((in_port_t) n);
+
+        u->port_text.len = len;
+        u->port_text.data = port;
+
+    } else {
+        u->no_port = 1;
+        u->port = u->default_port;
+        sin6->sin6_port = htons(u->default_port);
+    }
+
+    len = p - host;
+
+    if (len == 0) {
+        u->err = "no host";
+        return NGX_ERROR;
+    }
+
+    u->host.len = len + 2;
+    u->host.data = host - 1;
+
+    if (ngx_inet6_addr(host, len, sin6->sin6_addr.s6_addr) != NGX_OK) {
+        u->err = "invalid IPv6 address";
+        return NGX_ERROR;
+    }
+
+    if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+        u->wildcard = 1;
+    }
+
+    u->family = AF_INET6;
+    u->naddrs = 1;
+
+    u->addrs = ngx_pcalloc(pool, sizeof(ngx_addr_t));
+    if (u->addrs == NULL) {
+        return NGX_ERROR;
+    }
+
+    sin6 = ngx_pcalloc(pool, sizeof(struct sockaddr_in6));
+    if (sin6 == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(sin6, &u->sockaddr, sizeof(struct sockaddr_in6));
+
+    u->addrs[0].sockaddr = (struct sockaddr *) sin6;
+    u->addrs[0].socklen = sizeof(struct sockaddr_in6);
+
+    p = ngx_pnalloc(pool, u->host.len + sizeof(":65535") - 1);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    u->addrs[0].name.len = ngx_sprintf(p, "%V:%d",
+                                       &u->host, u->port) - p;
+    u->addrs[0].name.data = p;
+
+    return NGX_OK;
+
+#else
+
+    u->err = "the INET6 sockets are not supported on this platform";
+
+    return NGX_ERROR;
+
+#endif
+}
+
+
+#if (NGX_HAVE_GETADDRINFO && NGX_HAVE_INET6)
+
+ngx_int_t
+ngx_inet_resolve_host(ngx_pool_t *pool, ngx_url_t *u)
+{
+    u_char               *p, *host;
+    size_t                len;
+    in_port_t             port;
+    ngx_uint_t            i;
+    struct addrinfo       hints, *res, *rp;
+    struct sockaddr_in   *sin;
+    struct sockaddr_in6  *sin6;
+
+    port = htons(u->port);
+
+    host = ngx_alloc(u->host.len + 1, pool->log);
+    if (host == NULL) {
+        return NGX_ERROR;
+    }
+
+    (void) ngx_cpystrn(host, u->host.data, u->host.len + 1);
+
+    ngx_memzero(&hints, sizeof(struct addrinfo));
+    hints.ai_family = AF_UNSPEC;
+    hints.ai_socktype = SOCK_STREAM;
+#ifdef AI_ADDRCONFIG
+    hints.ai_flags = AI_ADDRCONFIG;
+#endif
+
+    if (getaddrinfo((char *) host, NULL, &hints, &res) != 0) {
+        u->err = "host not found";
+        ngx_free(host);
+        return NGX_ERROR;
+    }
+
+    ngx_free(host);
+
+    for (i = 0, rp = res; rp != NULL; rp = rp->ai_next) {
+
+        switch (rp->ai_family) {
+
+        case AF_INET:
+        case AF_INET6:
+            break;
+
+        default:
+            continue;
+        }
+
+        i++;
+    }
+
+    if (i == 0) {
+        u->err = "host not found";
+        goto failed;
+    }
+
+    /* MP: ngx_shared_palloc() */
+
+    u->addrs = ngx_pcalloc(pool, i * sizeof(ngx_addr_t));
+    if (u->addrs == NULL) {
+        goto failed;
+    }
+
+    u->naddrs = i;
+
+    i = 0;
+
+    /* AF_INET addresses first */
+
+    for (rp = res; rp != NULL; rp = rp->ai_next) {
+
+        if (rp->ai_family != AF_INET) {
+            continue;
+        }
+
+        sin = ngx_pcalloc(pool, rp->ai_addrlen);
+        if (sin == NULL) {
+            goto failed;
+        }
+
+        ngx_memcpy(sin, rp->ai_addr, rp->ai_addrlen);
+
+        sin->sin_port = port;
+
+        u->addrs[i].sockaddr = (struct sockaddr *) sin;
+        u->addrs[i].socklen = rp->ai_addrlen;
+
+        len = NGX_INET_ADDRSTRLEN + sizeof(":65535") - 1;
+
+        p = ngx_pnalloc(pool, len);
+        if (p == NULL) {
+            goto failed;
+        }
+
+        len = ngx_sock_ntop((struct sockaddr *) sin, rp->ai_addrlen, p, len, 1);
+
+        u->addrs[i].name.len = len;
+        u->addrs[i].name.data = p;
+
+        i++;
+    }
+
+    for (rp = res; rp != NULL; rp = rp->ai_next) {
+
+        if (rp->ai_family != AF_INET6) {
+            continue;
+        }
+
+        sin6 = ngx_pcalloc(pool, rp->ai_addrlen);
+        if (sin6 == NULL) {
+            goto failed;
+        }
+
+        ngx_memcpy(sin6, rp->ai_addr, rp->ai_addrlen);
+
+        sin6->sin6_port = port;
+
+        u->addrs[i].sockaddr = (struct sockaddr *) sin6;
+        u->addrs[i].socklen = rp->ai_addrlen;
+
+        len = NGX_INET6_ADDRSTRLEN + sizeof("[]:65535") - 1;
+
+        p = ngx_pnalloc(pool, len);
+        if (p == NULL) {
+            goto failed;
+        }
+
+        len = ngx_sock_ntop((struct sockaddr *) sin6, rp->ai_addrlen, p,
+                            len, 1);
+
+        u->addrs[i].name.len = len;
+        u->addrs[i].name.data = p;
+
+        i++;
+    }
+
+    freeaddrinfo(res);
+    return NGX_OK;
+
+failed:
+
+    freeaddrinfo(res);
+    return NGX_ERROR;
+}
+
+#else /* !NGX_HAVE_GETADDRINFO || !NGX_HAVE_INET6 */
+
+ngx_int_t
+ngx_inet_resolve_host(ngx_pool_t *pool, ngx_url_t *u)
+{
+    u_char              *p, *host;
+    size_t               len;
+    in_port_t            port;
+    in_addr_t            in_addr;
+    ngx_uint_t           i;
+    struct hostent      *h;
+    struct sockaddr_in  *sin;
+
+    /* AF_INET only */
+
+    port = htons(u->port);
+
+    in_addr = ngx_inet_addr(u->host.data, u->host.len);
+
+    if (in_addr == INADDR_NONE) {
+        host = ngx_alloc(u->host.len + 1, pool->log);
+        if (host == NULL) {
+            return NGX_ERROR;
+        }
+
+        (void) ngx_cpystrn(host, u->host.data, u->host.len + 1);
+
+        h = gethostbyname((char *) host);
+
+        ngx_free(host);
+
+        if (h == NULL || h->h_addr_list[0] == NULL) {
+            u->err = "host not found";
+            return NGX_ERROR;
+        }
+
+        for (i = 0; h->h_addr_list[i] != NULL; i++) { /* void */ }
+
+        /* MP: ngx_shared_palloc() */
+
+        u->addrs = ngx_pcalloc(pool, i * sizeof(ngx_addr_t));
+        if (u->addrs == NULL) {
+            return NGX_ERROR;
+        }
+
+        u->naddrs = i;
+
+        for (i = 0; i < u->naddrs; i++) {
+
+            sin = ngx_pcalloc(pool, sizeof(struct sockaddr_in));
+            if (sin == NULL) {
+                return NGX_ERROR;
+            }
+
+            sin->sin_family = AF_INET;
+            sin->sin_port = port;
+            sin->sin_addr.s_addr = *(in_addr_t *) (h->h_addr_list[i]);
+
+            u->addrs[i].sockaddr = (struct sockaddr *) sin;
+            u->addrs[i].socklen = sizeof(struct sockaddr_in);
+
+            len = NGX_INET_ADDRSTRLEN + sizeof(":65535") - 1;
+
+            p = ngx_pnalloc(pool, len);
+            if (p == NULL) {
+                return NGX_ERROR;
+            }
+
+            len = ngx_sock_ntop((struct sockaddr *) sin,
+                                sizeof(struct sockaddr_in), p, len, 1);
+
+            u->addrs[i].name.len = len;
+            u->addrs[i].name.data = p;
+        }
+
+    } else {
+
+        /* MP: ngx_shared_palloc() */
+
+        u->addrs = ngx_pcalloc(pool, sizeof(ngx_addr_t));
+        if (u->addrs == NULL) {
+            return NGX_ERROR;
+        }
+
+        sin = ngx_pcalloc(pool, sizeof(struct sockaddr_in));
+        if (sin == NULL) {
+            return NGX_ERROR;
+        }
+
+        u->naddrs = 1;
+
+        sin->sin_family = AF_INET;
+        sin->sin_port = port;
+        sin->sin_addr.s_addr = in_addr;
+
+        u->addrs[0].sockaddr = (struct sockaddr *) sin;
+        u->addrs[0].socklen = sizeof(struct sockaddr_in);
+
+        p = ngx_pnalloc(pool, u->host.len + sizeof(":65535") - 1);
+        if (p == NULL) {
+            return NGX_ERROR;
+        }
+
+        u->addrs[0].name.len = ngx_sprintf(p, "%V:%d",
+                                           &u->host, ntohs(port)) - p;
+        u->addrs[0].name.data = p;
+    }
+
+    return NGX_OK;
+}
+
+#endif /* NGX_HAVE_GETADDRINFO && NGX_HAVE_INET6 */
+
+
+ngx_int_t
+ngx_cmp_sockaddr(struct sockaddr *sa1, socklen_t slen1,
+    struct sockaddr *sa2, socklen_t slen2, ngx_uint_t cmp_port)
+{
+    struct sockaddr_in   *sin1, *sin2;
+#if (NGX_HAVE_INET6)
+    struct sockaddr_in6  *sin61, *sin62;
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+    size_t                len;
+    struct sockaddr_un   *saun1, *saun2;
+#endif
+
+    if (sa1->sa_family != sa2->sa_family) {
+        return NGX_DECLINED;
+    }
+
+    switch (sa1->sa_family) {
+
+#if (NGX_HAVE_INET6)
+    case AF_INET6:
+
+        sin61 = (struct sockaddr_in6 *) sa1;
+        sin62 = (struct sockaddr_in6 *) sa2;
+
+        if (cmp_port && sin61->sin6_port != sin62->sin6_port) {
+            return NGX_DECLINED;
+        }
+
+        if (ngx_memcmp(&sin61->sin6_addr, &sin62->sin6_addr, 16) != 0) {
+            return NGX_DECLINED;
+        }
+
+        break;
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+    case AF_UNIX:
+
+        saun1 = (struct sockaddr_un *) sa1;
+        saun2 = (struct sockaddr_un *) sa2;
+
+        if (slen1 < slen2) {
+            len = slen1 - offsetof(struct sockaddr_un, sun_path);
+
+        } else {
+            len = slen2 - offsetof(struct sockaddr_un, sun_path);
+        }
+
+        if (len > sizeof(saun1->sun_path)) {
+            len = sizeof(saun1->sun_path);
+        }
+
+        if (ngx_memcmp(&saun1->sun_path, &saun2->sun_path, len) != 0) {
+            return NGX_DECLINED;
+        }
+
+        break;
+#endif
+
+    default: /* AF_INET */
+
+        sin1 = (struct sockaddr_in *) sa1;
+        sin2 = (struct sockaddr_in *) sa2;
+
+        if (cmp_port && sin1->sin_port != sin2->sin_port) {
+            return NGX_DECLINED;
+        }
+
+        if (sin1->sin_addr.s_addr != sin2->sin_addr.s_addr) {
+            return NGX_DECLINED;
+        }
+
+        break;
+    }
+
+    return NGX_OK;
+}
+
+
+in_port_t
+ngx_inet_get_port(struct sockaddr *sa)
+{
+    struct sockaddr_in   *sin;
+#if (NGX_HAVE_INET6)
+    struct sockaddr_in6  *sin6;
+#endif
+
+    switch (sa->sa_family) {
+
+#if (NGX_HAVE_INET6)
+    case AF_INET6:
+        sin6 = (struct sockaddr_in6 *) sa;
+        return ntohs(sin6->sin6_port);
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+    case AF_UNIX:
+        return 0;
+#endif
+
+    default: /* AF_INET */
+        sin = (struct sockaddr_in *) sa;
+        return ntohs(sin->sin_port);
+    }
+}
+
+
+void
+ngx_inet_set_port(struct sockaddr *sa, in_port_t port)
+{
+    struct sockaddr_in   *sin;
+#if (NGX_HAVE_INET6)
+    struct sockaddr_in6  *sin6;
+#endif
+
+    switch (sa->sa_family) {
+
+#if (NGX_HAVE_INET6)
+    case AF_INET6:
+        sin6 = (struct sockaddr_in6 *) sa;
+        sin6->sin6_port = htons(port);
+        break;
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+    case AF_UNIX:
+        break;
+#endif
+
+    default: /* AF_INET */
+        sin = (struct sockaddr_in *) sa;
+        sin->sin_port = htons(port);
+        break;
+    }
+}
diff --git a/nginx/src/core/ngx_inet.h b/nginx/src/core/ngx_inet.h
new file mode 100644 (file)
index 0000000..a3b392e
--- /dev/null
@@ -0,0 +1,130 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_INET_H_INCLUDED_
+#define _NGX_INET_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#define NGX_INET_ADDRSTRLEN   (sizeof("255.255.255.255") - 1)
+#define NGX_INET6_ADDRSTRLEN                                                 \
+    (sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") - 1)
+#define NGX_UNIX_ADDRSTRLEN                                                  \
+    (sizeof("unix:") - 1 +                                                   \
+     sizeof(struct sockaddr_un) - offsetof(struct sockaddr_un, sun_path))
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+#define NGX_SOCKADDR_STRLEN   NGX_UNIX_ADDRSTRLEN
+#elif (NGX_HAVE_INET6)
+#define NGX_SOCKADDR_STRLEN   (NGX_INET6_ADDRSTRLEN + sizeof("[]:65535") - 1)
+#else
+#define NGX_SOCKADDR_STRLEN   (NGX_INET_ADDRSTRLEN + sizeof(":65535") - 1)
+#endif
+
+/* compatibility */
+#define NGX_SOCKADDRLEN       sizeof(ngx_sockaddr_t)
+
+
+typedef union {
+    struct sockaddr           sockaddr;
+    struct sockaddr_in        sockaddr_in;
+#if (NGX_HAVE_INET6)
+    struct sockaddr_in6       sockaddr_in6;
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+    struct sockaddr_un        sockaddr_un;
+#endif
+} ngx_sockaddr_t;
+
+
+typedef struct {
+    in_addr_t                 addr;
+    in_addr_t                 mask;
+} ngx_in_cidr_t;
+
+
+#if (NGX_HAVE_INET6)
+
+typedef struct {
+    struct in6_addr           addr;
+    struct in6_addr           mask;
+} ngx_in6_cidr_t;
+
+#endif
+
+
+typedef struct {
+    ngx_uint_t                family;
+    union {
+        ngx_in_cidr_t         in;
+#if (NGX_HAVE_INET6)
+        ngx_in6_cidr_t        in6;
+#endif
+    } u;
+} ngx_cidr_t;
+
+
+typedef struct {
+    struct sockaddr          *sockaddr;
+    socklen_t                 socklen;
+    ngx_str_t                 name;
+} ngx_addr_t;
+
+
+typedef struct {
+    ngx_str_t                 url;
+    ngx_str_t                 host;
+    ngx_str_t                 port_text;
+    ngx_str_t                 uri;
+
+    in_port_t                 port;
+    in_port_t                 default_port;
+    int                       family;
+
+    unsigned                  listen:1;
+    unsigned                  uri_part:1;
+    unsigned                  no_resolve:1;
+
+    unsigned                  no_port:1;
+    unsigned                  wildcard:1;
+
+    socklen_t                 socklen;
+    ngx_sockaddr_t            sockaddr;
+
+    ngx_addr_t               *addrs;
+    ngx_uint_t                naddrs;
+
+    char                     *err;
+} ngx_url_t;
+
+
+in_addr_t ngx_inet_addr(u_char *text, size_t len);
+#if (NGX_HAVE_INET6)
+ngx_int_t ngx_inet6_addr(u_char *p, size_t len, u_char *addr);
+size_t ngx_inet6_ntop(u_char *p, u_char *text, size_t len);
+#endif
+size_t ngx_sock_ntop(struct sockaddr *sa, socklen_t socklen, u_char *text,
+    size_t len, ngx_uint_t port);
+size_t ngx_inet_ntop(int family, void *addr, u_char *text, size_t len);
+ngx_int_t ngx_ptocidr(ngx_str_t *text, ngx_cidr_t *cidr);
+ngx_int_t ngx_cidr_match(struct sockaddr *sa, ngx_array_t *cidrs);
+ngx_int_t ngx_parse_addr(ngx_pool_t *pool, ngx_addr_t *addr, u_char *text,
+    size_t len);
+ngx_int_t ngx_parse_addr_port(ngx_pool_t *pool, ngx_addr_t *addr,
+    u_char *text, size_t len);
+ngx_int_t ngx_parse_url(ngx_pool_t *pool, ngx_url_t *u);
+ngx_int_t ngx_inet_resolve_host(ngx_pool_t *pool, ngx_url_t *u);
+ngx_int_t ngx_cmp_sockaddr(struct sockaddr *sa1, socklen_t slen1,
+    struct sockaddr *sa2, socklen_t slen2, ngx_uint_t cmp_port);
+in_port_t ngx_inet_get_port(struct sockaddr *sa);
+void ngx_inet_set_port(struct sockaddr *sa, in_port_t port);
+
+
+#endif /* _NGX_INET_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_list.c b/nginx/src/core/ngx_list.c
new file mode 100644 (file)
index 0000000..d0eb159
--- /dev/null
@@ -0,0 +1,63 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+ngx_list_t *
+ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size)
+{
+    ngx_list_t  *list;
+
+    list = ngx_palloc(pool, sizeof(ngx_list_t));
+    if (list == NULL) {
+        return NULL;
+    }
+
+    if (ngx_list_init(list, pool, n, size) != NGX_OK) {
+        return NULL;
+    }
+
+    return list;
+}
+
+
+void *
+ngx_list_push(ngx_list_t *l)
+{
+    void             *elt;
+    ngx_list_part_t  *last;
+
+    last = l->last;
+
+    if (last->nelts == l->nalloc) {
+
+        /* the last part is full, allocate a new list part */
+
+        last = ngx_palloc(l->pool, sizeof(ngx_list_part_t));
+        if (last == NULL) {
+            return NULL;
+        }
+
+        last->elts = ngx_palloc(l->pool, l->nalloc * l->size);
+        if (last->elts == NULL) {
+            return NULL;
+        }
+
+        last->nelts = 0;
+        last->next = NULL;
+
+        l->last->next = last;
+        l->last = last;
+    }
+
+    elt = (char *) last->elts + l->size * last->nelts;
+    last->nelts++;
+
+    return elt;
+}
diff --git a/nginx/src/core/ngx_list.h b/nginx/src/core/ngx_list.h
new file mode 100644 (file)
index 0000000..e0fe643
--- /dev/null
@@ -0,0 +1,83 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_LIST_H_INCLUDED_
+#define _NGX_LIST_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef struct ngx_list_part_s  ngx_list_part_t;
+
+struct ngx_list_part_s {
+    void             *elts;
+    ngx_uint_t        nelts;
+    ngx_list_part_t  *next;
+};
+
+
+typedef struct {
+    ngx_list_part_t  *last;
+    ngx_list_part_t   part;
+    size_t            size;
+    ngx_uint_t        nalloc;
+    ngx_pool_t       *pool;
+} ngx_list_t;
+
+
+ngx_list_t *ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size);
+
+static ngx_inline ngx_int_t
+ngx_list_init(ngx_list_t *list, ngx_pool_t *pool, ngx_uint_t n, size_t size)
+{
+    list->part.elts = ngx_palloc(pool, n * size);
+    if (list->part.elts == NULL) {
+        return NGX_ERROR;
+    }
+
+    list->part.nelts = 0;
+    list->part.next = NULL;
+    list->last = &list->part;
+    list->size = size;
+    list->nalloc = n;
+    list->pool = pool;
+
+    return NGX_OK;
+}
+
+
+/*
+ *
+ *  the iteration through the list:
+ *
+ *  part = &list.part;
+ *  data = part->elts;
+ *
+ *  for (i = 0 ;; i++) {
+ *
+ *      if (i >= part->nelts) {
+ *          if (part->next == NULL) {
+ *              break;
+ *          }
+ *
+ *          part = part->next;
+ *          data = part->elts;
+ *          i = 0;
+ *      }
+ *
+ *      ...  data[i] ...
+ *
+ *  }
+ */
+
+
+void *ngx_list_push(ngx_list_t *list);
+
+
+#endif /* _NGX_LIST_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_log.c b/nginx/src/core/ngx_log.c
new file mode 100644 (file)
index 0000000..8e9408d
--- /dev/null
@@ -0,0 +1,755 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+static char *ngx_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_log_set_levels(ngx_conf_t *cf, ngx_log_t *log);
+static void ngx_log_insert(ngx_log_t *log, ngx_log_t *new_log);
+
+
+#if (NGX_DEBUG)
+
+static void ngx_log_memory_writer(ngx_log_t *log, ngx_uint_t level,
+    u_char *buf, size_t len);
+static void ngx_log_memory_cleanup(void *data);
+
+
+typedef struct {
+    u_char        *start;
+    u_char        *end;
+    u_char        *pos;
+    ngx_atomic_t   written;
+} ngx_log_memory_buf_t;
+
+#endif
+
+
+static ngx_command_t  ngx_errlog_commands[] = {
+
+    { ngx_string("error_log"),
+      NGX_MAIN_CONF|NGX_CONF_1MORE,
+      ngx_error_log,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_core_module_t  ngx_errlog_module_ctx = {
+    ngx_string("errlog"),
+    NULL,
+    NULL
+};
+
+
+ngx_module_t  ngx_errlog_module = {
+    NGX_MODULE_V1,
+    &ngx_errlog_module_ctx,                /* module context */
+    ngx_errlog_commands,                   /* module directives */
+    NGX_CORE_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_log_t        ngx_log;
+static ngx_open_file_t  ngx_log_file;
+ngx_uint_t              ngx_use_stderr = 1;
+
+
+static ngx_str_t err_levels[] = {
+    ngx_null_string,
+    ngx_string("emerg"),
+    ngx_string("alert"),
+    ngx_string("crit"),
+    ngx_string("error"),
+    ngx_string("warn"),
+    ngx_string("notice"),
+    ngx_string("info"),
+    ngx_string("debug")
+};
+
+static const char *debug_levels[] = {
+    "debug_core", "debug_alloc", "debug_mutex", "debug_event",
+    "debug_http", "debug_mail", "debug_stream"
+};
+
+
+#if (NGX_HAVE_VARIADIC_MACROS)
+
+void
+ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
+    const char *fmt, ...)
+
+#else
+
+void
+ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
+    const char *fmt, va_list args)
+
+#endif
+{
+#if (NGX_HAVE_VARIADIC_MACROS)
+    va_list      args;
+#endif
+    u_char      *p, *last, *msg;
+    ssize_t      n;
+    ngx_uint_t   wrote_stderr, debug_connection;
+    u_char       errstr[NGX_MAX_ERROR_STR];
+
+    last = errstr + NGX_MAX_ERROR_STR;
+
+    p = ngx_cpymem(errstr, ngx_cached_err_log_time.data,
+                   ngx_cached_err_log_time.len);
+
+    p = ngx_slprintf(p, last, " [%V] ", &err_levels[level]);
+
+    /* pid#tid */
+    p = ngx_slprintf(p, last, "%P#" NGX_TID_T_FMT ": ",
+                    ngx_log_pid, ngx_log_tid);
+
+    if (log->connection) {
+        p = ngx_slprintf(p, last, "*%uA ", log->connection);
+    }
+
+    msg = p;
+
+#if (NGX_HAVE_VARIADIC_MACROS)
+
+    va_start(args, fmt);
+    p = ngx_vslprintf(p, last, fmt, args);
+    va_end(args);
+
+#else
+
+    p = ngx_vslprintf(p, last, fmt, args);
+
+#endif
+
+    if (err) {
+        p = ngx_log_errno(p, last, err);
+    }
+
+    if (level != NGX_LOG_DEBUG && log->handler) {
+        p = log->handler(log, p, last - p);
+    }
+
+    if (p > last - NGX_LINEFEED_SIZE) {
+        p = last - NGX_LINEFEED_SIZE;
+    }
+
+    ngx_linefeed(p);
+
+    wrote_stderr = 0;
+    debug_connection = (log->log_level & NGX_LOG_DEBUG_CONNECTION) != 0;
+
+    while (log) {
+
+        if (log->log_level < level && !debug_connection) {
+            break;
+        }
+
+        if (log->writer) {
+            log->writer(log, level, errstr, p - errstr);
+            goto next;
+        }
+
+        if (ngx_time() == log->disk_full_time) {
+
+            /*
+             * on FreeBSD writing to a full filesystem with enabled softupdates
+             * may block process for much longer time than writing to non-full
+             * filesystem, so we skip writing to a log for one second
+             */
+
+            goto next;
+        }
+
+        n = ngx_write_fd(log->file->fd, errstr, p - errstr);
+
+        if (n == -1 && ngx_errno == NGX_ENOSPC) {
+            log->disk_full_time = ngx_time();
+        }
+
+        if (log->file->fd == ngx_stderr) {
+            wrote_stderr = 1;
+        }
+
+    next:
+
+        log = log->next;
+    }
+
+    if (!ngx_use_stderr
+        || level > NGX_LOG_WARN
+        || wrote_stderr)
+    {
+        return;
+    }
+
+    msg -= (7 + err_levels[level].len + 3);
+
+    (void) ngx_sprintf(msg, "nginx: [%V] ", &err_levels[level]);
+
+    (void) ngx_write_console(ngx_stderr, msg, p - msg);
+}
+
+
+#if !(NGX_HAVE_VARIADIC_MACROS)
+
+void ngx_cdecl
+ngx_log_error(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
+    const char *fmt, ...)
+{
+    va_list  args;
+
+    if (log->log_level >= level) {
+        va_start(args, fmt);
+        ngx_log_error_core(level, log, err, fmt, args);
+        va_end(args);
+    }
+}
+
+
+void ngx_cdecl
+ngx_log_debug_core(ngx_log_t *log, ngx_err_t err, const char *fmt, ...)
+{
+    va_list  args;
+
+    va_start(args, fmt);
+    ngx_log_error_core(NGX_LOG_DEBUG, log, err, fmt, args);
+    va_end(args);
+}
+
+#endif
+
+
+void ngx_cdecl
+ngx_log_abort(ngx_err_t err, const char *fmt, ...)
+{
+    u_char   *p;
+    va_list   args;
+    u_char    errstr[NGX_MAX_CONF_ERRSTR];
+
+    va_start(args, fmt);
+    p = ngx_vsnprintf(errstr, sizeof(errstr) - 1, fmt, args);
+    va_end(args);
+
+    ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, err,
+                  "%*s", p - errstr, errstr);
+}
+
+
+void ngx_cdecl
+ngx_log_stderr(ngx_err_t err, const char *fmt, ...)
+{
+    u_char   *p, *last;
+    va_list   args;
+    u_char    errstr[NGX_MAX_ERROR_STR];
+
+    last = errstr + NGX_MAX_ERROR_STR;
+
+    p = ngx_cpymem(errstr, "nginx: ", 7);
+
+    va_start(args, fmt);
+    p = ngx_vslprintf(p, last, fmt, args);
+    va_end(args);
+
+    if (err) {
+        p = ngx_log_errno(p, last, err);
+    }
+
+    if (p > last - NGX_LINEFEED_SIZE) {
+        p = last - NGX_LINEFEED_SIZE;
+    }
+
+    ngx_linefeed(p);
+
+    (void) ngx_write_console(ngx_stderr, errstr, p - errstr);
+}
+
+
+u_char *
+ngx_log_errno(u_char *buf, u_char *last, ngx_err_t err)
+{
+    if (buf > last - 50) {
+
+        /* leave a space for an error code */
+
+        buf = last - 50;
+        *buf++ = '.';
+        *buf++ = '.';
+        *buf++ = '.';
+    }
+
+#if (NGX_WIN32)
+    buf = ngx_slprintf(buf, last, ((unsigned) err < 0x80000000)
+                                       ? " (%d: " : " (%Xd: ", err);
+#else
+    buf = ngx_slprintf(buf, last, " (%d: ", err);
+#endif
+
+    buf = ngx_strerror(err, buf, last - buf);
+
+    if (buf < last) {
+        *buf++ = ')';
+    }
+
+    return buf;
+}
+
+
+ngx_log_t *
+ngx_log_init(u_char *prefix)
+{
+    u_char  *p, *name;
+    size_t   nlen, plen;
+
+    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
+     */
+
+    nlen = ngx_strlen(name);
+
+    if (nlen == 0) {
+        ngx_log_file.fd = ngx_stderr;
+        return &ngx_log;
+    }
+
+    p = NULL;
+
+#if (NGX_WIN32)
+    if (name[1] != ':') {
+#else
+    if (name[0] != '/') {
+#endif
+
+        if (prefix) {
+            plen = ngx_strlen(prefix);
+
+        } else {
+#ifdef NGX_PREFIX
+            prefix = (u_char *) NGX_PREFIX;
+            plen = ngx_strlen(prefix);
+#else
+            plen = 0;
+#endif
+        }
+
+        if (plen) {
+            name = malloc(plen + nlen + 2);
+            if (name == NULL) {
+                return NULL;
+            }
+
+            p = ngx_cpymem(name, prefix, plen);
+
+            if (!ngx_path_separator(*(p - 1))) {
+                *p++ = '/';
+            }
+
+            ngx_cpystrn(p, (u_char *) NGX_ERROR_LOG_PATH, nlen + 1);
+
+            p = name;
+        }
+    }
+
+    ngx_log_file.fd = ngx_open_file(name, NGX_FILE_APPEND,
+                                    NGX_FILE_CREATE_OR_OPEN,
+                                    NGX_FILE_DEFAULT_ACCESS);
+
+    if (ngx_log_file.fd == NGX_INVALID_FILE) {
+        ngx_log_stderr(ngx_errno,
+                       "[alert] could not open error log file: "
+                       ngx_open_file_n " \"%s\" failed", name);
+#if (NGX_WIN32)
+        ngx_event_log(ngx_errno,
+                       "could not open error log file: "
+                       ngx_open_file_n " \"%s\" failed", name);
+#endif
+
+        ngx_log_file.fd = ngx_stderr;
+    }
+
+    if (p) {
+        ngx_free(p);
+    }
+
+    return &ngx_log;
+}
+
+
+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);
+
+    if (ngx_log_get_file_log(&cycle->new_log) != NULL) {
+        return NGX_OK;
+    }
+
+    if (cycle->new_log.log_level != 0) {
+        /* there are some error logs, but no files */
+
+        log = ngx_pcalloc(cycle->pool, sizeof(ngx_log_t));
+        if (log == NULL) {
+            return NGX_ERROR;
+        }
+
+    } else {
+        /* no error logs at all */
+        log = &cycle->new_log;
+    }
+
+    log->log_level = NGX_LOG_ERR;
+
+    log->file = ngx_conf_open_file(cycle, &error_log);
+    if (log->file == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (log != &cycle->new_log) {
+        ngx_log_insert(&cycle->new_log, log);
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_log_redirect_stderr(ngx_cycle_t *cycle)
+{
+    ngx_fd_t  fd;
+
+    if (cycle->log_use_stderr) {
+        return NGX_OK;
+    }
+
+    /* file log always exists when we are called */
+    fd = ngx_log_get_file_log(cycle->log)->file->fd;
+
+    if (fd != ngx_stderr) {
+        if (ngx_set_stderr(fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          ngx_set_stderr_n " failed");
+
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_log_t *
+ngx_log_get_file_log(ngx_log_t *head)
+{
+    ngx_log_t  *log;
+
+    for (log = head; log; log = log->next) {
+        if (log->file != NULL) {
+            return log;
+        }
+    }
+
+    return NULL;
+}
+
+
+static char *
+ngx_log_set_levels(ngx_conf_t *cf, ngx_log_t *log)
+{
+    ngx_uint_t   i, n, d, found;
+    ngx_str_t   *value;
+
+    if (cf->args->nelts == 2) {
+        log->log_level = NGX_LOG_ERR;
+        return NGX_CONF_OK;
+    }
+
+    value = cf->args->elts;
+
+    for (i = 2; i < cf->args->nelts; i++) {
+        found = 0;
+
+        for (n = 1; n <= NGX_LOG_DEBUG; n++) {
+            if (ngx_strcmp(value[i].data, err_levels[n].data) == 0) {
+
+                if (log->log_level != 0) {
+                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                       "duplicate log level \"%V\"",
+                                       &value[i]);
+                    return NGX_CONF_ERROR;
+                }
+
+                log->log_level = n;
+                found = 1;
+                break;
+            }
+        }
+
+        for (n = 0, d = NGX_LOG_DEBUG_FIRST; d <= NGX_LOG_DEBUG_LAST; d <<= 1) {
+            if (ngx_strcmp(value[i].data, debug_levels[n++]) == 0) {
+                if (log->log_level & ~NGX_LOG_DEBUG_ALL) {
+                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                       "invalid log level \"%V\"",
+                                       &value[i]);
+                    return NGX_CONF_ERROR;
+                }
+
+                log->log_level |= d;
+                found = 1;
+                break;
+            }
+        }
+
+
+        if (!found) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid log level \"%V\"", &value[i]);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (log->log_level == NGX_LOG_DEBUG) {
+        log->log_level = NGX_LOG_DEBUG_ALL;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_log_t  *dummy;
+
+    dummy = &cf->cycle->new_log;
+
+    return ngx_log_set_log(cf, &dummy);
+}
+
+
+char *
+ngx_log_set_log(ngx_conf_t *cf, ngx_log_t **head)
+{
+    ngx_log_t          *new_log;
+    ngx_str_t          *value, name;
+    ngx_syslog_peer_t  *peer;
+
+    if (*head != NULL && (*head)->log_level == 0) {
+        new_log = *head;
+
+    } else {
+
+        new_log = ngx_pcalloc(cf->pool, sizeof(ngx_log_t));
+        if (new_log == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        if (*head == NULL) {
+            *head = new_log;
+        }
+    }
+
+    value = cf->args->elts;
+
+    if (ngx_strcmp(value[1].data, "stderr") == 0) {
+        ngx_str_null(&name);
+        cf->cycle->log_use_stderr = 1;
+
+        new_log->file = ngx_conf_open_file(cf->cycle, &name);
+        if (new_log->file == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+    } else if (ngx_strncmp(value[1].data, "memory:", 7) == 0) {
+
+#if (NGX_DEBUG)
+        size_t                 size, needed;
+        ngx_pool_cleanup_t    *cln;
+        ngx_log_memory_buf_t  *buf;
+
+        value[1].len -= 7;
+        value[1].data += 7;
+
+        needed = sizeof("MEMLOG  :" NGX_LINEFEED)
+                 + cf->conf_file->file.name.len
+                 + NGX_SIZE_T_LEN
+                 + NGX_INT_T_LEN
+                 + NGX_MAX_ERROR_STR;
+
+        size = ngx_parse_size(&value[1]);
+
+        if (size == (size_t) NGX_ERROR || size < needed) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid buffer size \"%V\"", &value[1]);
+            return NGX_CONF_ERROR;
+        }
+
+        buf = ngx_pcalloc(cf->pool, sizeof(ngx_log_memory_buf_t));
+        if (buf == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        buf->start = ngx_pnalloc(cf->pool, size);
+        if (buf->start == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        buf->end = buf->start + size;
+
+        buf->pos = ngx_slprintf(buf->start, buf->end, "MEMLOG %uz %V:%ui%N",
+                                size, &cf->conf_file->file.name,
+                                cf->conf_file->line);
+
+        ngx_memset(buf->pos, ' ', buf->end - buf->pos);
+
+        cln = ngx_pool_cleanup_add(cf->pool, 0);
+        if (cln == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        cln->data = new_log;
+        cln->handler = ngx_log_memory_cleanup;
+
+        new_log->writer = ngx_log_memory_writer;
+        new_log->wdata = buf;
+
+#else
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "nginx was built without debug support");
+        return NGX_CONF_ERROR;
+#endif
+
+    } else if (ngx_strncmp(value[1].data, "syslog:", 7) == 0) {
+        peer = ngx_pcalloc(cf->pool, sizeof(ngx_syslog_peer_t));
+        if (peer == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_syslog_process_conf(cf, peer) != NGX_CONF_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+        new_log->writer = ngx_syslog_writer;
+        new_log->wdata = peer;
+
+    } else {
+        new_log->file = ngx_conf_open_file(cf->cycle, &value[1]);
+        if (new_log->file == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (ngx_log_set_levels(cf, new_log) != NGX_CONF_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (*head != new_log) {
+        ngx_log_insert(*head, new_log);
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static void
+ngx_log_insert(ngx_log_t *log, ngx_log_t *new_log)
+{
+    ngx_log_t  tmp;
+
+    if (new_log->log_level > log->log_level) {
+
+        /*
+         * list head address is permanent, insert new log after
+         * head and swap its contents with head
+         */
+
+        tmp = *log;
+        *log = *new_log;
+        *new_log = tmp;
+
+        log->next = new_log;
+        return;
+    }
+
+    while (log->next) {
+        if (new_log->log_level > log->next->log_level) {
+            new_log->next = log->next;
+            log->next = new_log;
+            return;
+        }
+
+        log = log->next;
+    }
+
+    log->next = new_log;
+}
+
+
+#if (NGX_DEBUG)
+
+static void
+ngx_log_memory_writer(ngx_log_t *log, ngx_uint_t level, u_char *buf,
+    size_t len)
+{
+    u_char                *p;
+    size_t                 avail, written;
+    ngx_log_memory_buf_t  *mem;
+
+    mem = log->wdata;
+
+    if (mem == NULL) {
+        return;
+    }
+
+    written = ngx_atomic_fetch_add(&mem->written, len);
+
+    p = mem->pos + written % (mem->end - mem->pos);
+
+    avail = mem->end - p;
+
+    if (avail >= len) {
+        ngx_memcpy(p, buf, len);
+
+    } else {
+        ngx_memcpy(p, buf, avail);
+        ngx_memcpy(mem->pos, buf + avail, len - avail);
+    }
+}
+
+
+static void
+ngx_log_memory_cleanup(void *data)
+{
+    ngx_log_t *log = data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_CORE, log, 0, "destroy memory log buffer");
+
+    log->wdata = NULL;
+}
+
+#endif
diff --git a/nginx/src/core/ngx_log.h b/nginx/src/core/ngx_log.h
new file mode 100644 (file)
index 0000000..afb73bf
--- /dev/null
@@ -0,0 +1,268 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_LOG_H_INCLUDED_
+#define _NGX_LOG_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#define NGX_LOG_STDERR            0
+#define NGX_LOG_EMERG             1
+#define NGX_LOG_ALERT             2
+#define NGX_LOG_CRIT              3
+#define NGX_LOG_ERR               4
+#define NGX_LOG_WARN              5
+#define NGX_LOG_NOTICE            6
+#define NGX_LOG_INFO              7
+#define NGX_LOG_DEBUG             8
+
+#define NGX_LOG_DEBUG_CORE        0x010
+#define NGX_LOG_DEBUG_ALLOC       0x020
+#define NGX_LOG_DEBUG_MUTEX       0x040
+#define NGX_LOG_DEBUG_EVENT       0x080
+#define NGX_LOG_DEBUG_HTTP        0x100
+#define NGX_LOG_DEBUG_MAIL        0x200
+#define NGX_LOG_DEBUG_STREAM      0x400
+
+/*
+ * do not forget to update debug_levels[] in src/core/ngx_log.c
+ * after the adding a new debug level
+ */
+
+#define NGX_LOG_DEBUG_FIRST       NGX_LOG_DEBUG_CORE
+#define NGX_LOG_DEBUG_LAST        NGX_LOG_DEBUG_STREAM
+#define NGX_LOG_DEBUG_CONNECTION  0x80000000
+#define NGX_LOG_DEBUG_ALL         0x7ffffff0
+
+
+typedef u_char *(*ngx_log_handler_pt) (ngx_log_t *log, u_char *buf, size_t len);
+typedef void (*ngx_log_writer_pt) (ngx_log_t *log, ngx_uint_t level,
+    u_char *buf, size_t len);
+
+
+struct ngx_log_s {
+    ngx_uint_t           log_level;
+    ngx_open_file_t     *file;
+
+    ngx_atomic_uint_t    connection;
+
+    time_t               disk_full_time;
+
+    ngx_log_handler_pt   handler;
+    void                *data;
+
+    ngx_log_writer_pt    writer;
+    void                *wdata;
+
+    /*
+     * we declare "action" as "char *" because the actions are usually
+     * the static strings and in the "u_char *" case we have to override
+     * their types all the time
+     */
+
+    char                *action;
+
+    ngx_log_t           *next;
+};
+
+
+#define NGX_MAX_ERROR_STR   2048
+
+
+/*********************************/
+
+#if (NGX_HAVE_C99_VARIADIC_MACROS)
+
+#define NGX_HAVE_VARIADIC_MACROS  1
+
+#define ngx_log_error(level, log, ...)                                        \
+    if ((log)->log_level >= level) ngx_log_error_core(level, log, __VA_ARGS__)
+
+void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
+    const char *fmt, ...);
+
+#define ngx_log_debug(level, log, ...)                                        \
+    if ((log)->log_level & level)                                             \
+        ngx_log_error_core(NGX_LOG_DEBUG, log, __VA_ARGS__)
+
+/*********************************/
+
+#elif (NGX_HAVE_GCC_VARIADIC_MACROS)
+
+#define NGX_HAVE_VARIADIC_MACROS  1
+
+#define ngx_log_error(level, log, args...)                                    \
+    if ((log)->log_level >= level) ngx_log_error_core(level, log, args)
+
+void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
+    const char *fmt, ...);
+
+#define ngx_log_debug(level, log, args...)                                    \
+    if ((log)->log_level & level)                                             \
+        ngx_log_error_core(NGX_LOG_DEBUG, log, args)
+
+/*********************************/
+
+#else /* no variadic macros */
+
+#define NGX_HAVE_VARIADIC_MACROS  0
+
+void ngx_cdecl ngx_log_error(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
+    const char *fmt, ...);
+void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
+    const char *fmt, va_list args);
+void ngx_cdecl ngx_log_debug_core(ngx_log_t *log, ngx_err_t err,
+    const char *fmt, ...);
+
+
+#endif /* variadic macros */
+
+
+/*********************************/
+
+#if (NGX_DEBUG)
+
+#if (NGX_HAVE_VARIADIC_MACROS)
+
+#define ngx_log_debug0(level, log, err, fmt)                                  \
+        ngx_log_debug(level, log, err, fmt)
+
+#define ngx_log_debug1(level, log, err, fmt, arg1)                            \
+        ngx_log_debug(level, log, err, fmt, arg1)
+
+#define ngx_log_debug2(level, log, err, fmt, arg1, arg2)                      \
+        ngx_log_debug(level, log, err, fmt, arg1, arg2)
+
+#define ngx_log_debug3(level, log, err, fmt, arg1, arg2, arg3)                \
+        ngx_log_debug(level, log, err, fmt, arg1, arg2, arg3)
+
+#define ngx_log_debug4(level, log, err, fmt, arg1, arg2, arg3, arg4)          \
+        ngx_log_debug(level, log, err, fmt, arg1, arg2, arg3, arg4)
+
+#define ngx_log_debug5(level, log, err, fmt, arg1, arg2, arg3, arg4, arg5)    \
+        ngx_log_debug(level, log, err, fmt, arg1, arg2, arg3, arg4, arg5)
+
+#define ngx_log_debug6(level, log, err, fmt,                                  \
+                       arg1, arg2, arg3, arg4, arg5, arg6)                    \
+        ngx_log_debug(level, log, err, fmt,                                   \
+                       arg1, arg2, arg3, arg4, arg5, arg6)
+
+#define ngx_log_debug7(level, log, err, fmt,                                  \
+                       arg1, arg2, arg3, arg4, arg5, arg6, arg7)              \
+        ngx_log_debug(level, log, err, fmt,                                   \
+                       arg1, arg2, arg3, arg4, arg5, arg6, arg7)
+
+#define ngx_log_debug8(level, log, err, fmt,                                  \
+                       arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)        \
+        ngx_log_debug(level, log, err, fmt,                                   \
+                       arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
+
+
+#else /* no variadic macros */
+
+#define ngx_log_debug0(level, log, err, fmt)                                  \
+    if ((log)->log_level & level)                                             \
+        ngx_log_debug_core(log, err, fmt)
+
+#define ngx_log_debug1(level, log, err, fmt, arg1)                            \
+    if ((log)->log_level & level)                                             \
+        ngx_log_debug_core(log, err, fmt, arg1)
+
+#define ngx_log_debug2(level, log, err, fmt, arg1, arg2)                      \
+    if ((log)->log_level & level)                                             \
+        ngx_log_debug_core(log, err, fmt, arg1, arg2)
+
+#define ngx_log_debug3(level, log, err, fmt, arg1, arg2, arg3)                \
+    if ((log)->log_level & level)                                             \
+        ngx_log_debug_core(log, err, fmt, arg1, arg2, arg3)
+
+#define ngx_log_debug4(level, log, err, fmt, arg1, arg2, arg3, arg4)          \
+    if ((log)->log_level & level)                                             \
+        ngx_log_debug_core(log, err, fmt, arg1, arg2, arg3, arg4)
+
+#define ngx_log_debug5(level, log, err, fmt, arg1, arg2, arg3, arg4, arg5)    \
+    if ((log)->log_level & level)                                             \
+        ngx_log_debug_core(log, err, fmt, arg1, arg2, arg3, arg4, arg5)
+
+#define ngx_log_debug6(level, log, err, fmt,                                  \
+                       arg1, arg2, arg3, arg4, arg5, arg6)                    \
+    if ((log)->log_level & level)                                             \
+        ngx_log_debug_core(log, err, fmt, arg1, arg2, arg3, arg4, arg5, arg6)
+
+#define ngx_log_debug7(level, log, err, fmt,                                  \
+                       arg1, arg2, arg3, arg4, arg5, arg6, arg7)              \
+    if ((log)->log_level & level)                                             \
+        ngx_log_debug_core(log, err, fmt,                                     \
+                       arg1, arg2, arg3, arg4, arg5, arg6, arg7)
+
+#define ngx_log_debug8(level, log, err, fmt,                                  \
+                       arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)        \
+    if ((log)->log_level & level)                                             \
+        ngx_log_debug_core(log, err, fmt,                                     \
+                       arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
+
+#endif
+
+#else /* !NGX_DEBUG */
+
+#define ngx_log_debug0(level, log, err, fmt)
+#define ngx_log_debug1(level, log, err, fmt, arg1)
+#define ngx_log_debug2(level, log, err, fmt, arg1, arg2)
+#define ngx_log_debug3(level, log, err, fmt, arg1, arg2, arg3)
+#define ngx_log_debug4(level, log, err, fmt, arg1, arg2, arg3, arg4)
+#define ngx_log_debug5(level, log, err, fmt, arg1, arg2, arg3, arg4, arg5)
+#define ngx_log_debug6(level, log, err, fmt, arg1, arg2, arg3, arg4, arg5, arg6)
+#define ngx_log_debug7(level, log, err, fmt, arg1, arg2, arg3, arg4, arg5,    \
+                       arg6, arg7)
+#define ngx_log_debug8(level, log, err, fmt, arg1, arg2, arg3, arg4, arg5,    \
+                       arg6, arg7, arg8)
+
+#endif
+
+/*********************************/
+
+ngx_log_t *ngx_log_init(u_char *prefix);
+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);
+ngx_int_t ngx_log_open_default(ngx_cycle_t *cycle);
+ngx_int_t ngx_log_redirect_stderr(ngx_cycle_t *cycle);
+ngx_log_t *ngx_log_get_file_log(ngx_log_t *head);
+char *ngx_log_set_log(ngx_conf_t *cf, ngx_log_t **head);
+
+
+/*
+ * ngx_write_stderr() cannot be implemented as macro, since
+ * MSVC does not allow to use #ifdef inside macro parameters.
+ *
+ * ngx_write_fd() is used instead of ngx_write_console(), since
+ * CharToOemBuff() inside ngx_write_console() cannot be used with
+ * read only buffer as destination and CharToOemBuff() is not needed
+ * for ngx_write_stderr() anyway.
+ */
+static ngx_inline void
+ngx_write_stderr(char *text)
+{
+    (void) ngx_write_fd(ngx_stderr, text, ngx_strlen(text));
+}
+
+
+static ngx_inline void
+ngx_write_stdout(char *text)
+{
+    (void) ngx_write_fd(ngx_stdout, text, ngx_strlen(text));
+}
+
+
+extern ngx_module_t  ngx_errlog_module;
+extern ngx_uint_t    ngx_use_stderr;
+
+
+#endif /* _NGX_LOG_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_md5.c b/nginx/src/core/ngx_md5.c
new file mode 100644 (file)
index 0000000..c25d002
--- /dev/null
@@ -0,0 +1,283 @@
+
+/*
+ * An internal implementation, based on Alexander Peslyak's
+ * public domain implementation:
+ * http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_md5.h>
+
+
+static const u_char *ngx_md5_body(ngx_md5_t *ctx, const u_char *data,
+    size_t size);
+
+
+void
+ngx_md5_init(ngx_md5_t *ctx)
+{
+    ctx->a = 0x67452301;
+    ctx->b = 0xefcdab89;
+    ctx->c = 0x98badcfe;
+    ctx->d = 0x10325476;
+
+    ctx->bytes = 0;
+}
+
+
+void
+ngx_md5_update(ngx_md5_t *ctx, const void *data, size_t size)
+{
+    size_t  used, free;
+
+    used = (size_t) (ctx->bytes & 0x3f);
+    ctx->bytes += size;
+
+    if (used) {
+        free = 64 - used;
+
+        if (size < free) {
+            ngx_memcpy(&ctx->buffer[used], data, size);
+            return;
+        }
+
+        ngx_memcpy(&ctx->buffer[used], data, free);
+        data = (u_char *) data + free;
+        size -= free;
+        (void) ngx_md5_body(ctx, ctx->buffer, 64);
+    }
+
+    if (size >= 64) {
+        data = ngx_md5_body(ctx, data, size & ~(size_t) 0x3f);
+        size &= 0x3f;
+    }
+
+    ngx_memcpy(ctx->buffer, data, size);
+}
+
+
+void
+ngx_md5_final(u_char result[16], ngx_md5_t *ctx)
+{
+    size_t  used, free;
+
+    used = (size_t) (ctx->bytes & 0x3f);
+
+    ctx->buffer[used++] = 0x80;
+
+    free = 64 - used;
+
+    if (free < 8) {
+        ngx_memzero(&ctx->buffer[used], free);
+        (void) ngx_md5_body(ctx, ctx->buffer, 64);
+        used = 0;
+        free = 64;
+    }
+
+    ngx_memzero(&ctx->buffer[used], free - 8);
+
+    ctx->bytes <<= 3;
+    ctx->buffer[56] = (u_char) ctx->bytes;
+    ctx->buffer[57] = (u_char) (ctx->bytes >> 8);
+    ctx->buffer[58] = (u_char) (ctx->bytes >> 16);
+    ctx->buffer[59] = (u_char) (ctx->bytes >> 24);
+    ctx->buffer[60] = (u_char) (ctx->bytes >> 32);
+    ctx->buffer[61] = (u_char) (ctx->bytes >> 40);
+    ctx->buffer[62] = (u_char) (ctx->bytes >> 48);
+    ctx->buffer[63] = (u_char) (ctx->bytes >> 56);
+
+    (void) ngx_md5_body(ctx, ctx->buffer, 64);
+
+    result[0] = (u_char) ctx->a;
+    result[1] = (u_char) (ctx->a >> 8);
+    result[2] = (u_char) (ctx->a >> 16);
+    result[3] = (u_char) (ctx->a >> 24);
+    result[4] = (u_char) ctx->b;
+    result[5] = (u_char) (ctx->b >> 8);
+    result[6] = (u_char) (ctx->b >> 16);
+    result[7] = (u_char) (ctx->b >> 24);
+    result[8] = (u_char) ctx->c;
+    result[9] = (u_char) (ctx->c >> 8);
+    result[10] = (u_char) (ctx->c >> 16);
+    result[11] = (u_char) (ctx->c >> 24);
+    result[12] = (u_char) ctx->d;
+    result[13] = (u_char) (ctx->d >> 8);
+    result[14] = (u_char) (ctx->d >> 16);
+    result[15] = (u_char) (ctx->d >> 24);
+
+    ngx_memzero(ctx, sizeof(*ctx));
+}
+
+
+/*
+ * The basic MD5 functions.
+ *
+ * F and G are optimized compared to their RFC 1321 definitions for
+ * architectures that lack an AND-NOT instruction, just like in
+ * Colin Plumb's implementation.
+ */
+
+#define F(x, y, z)  ((z) ^ ((x) & ((y) ^ (z))))
+#define G(x, y, z)  ((y) ^ ((z) & ((x) ^ (y))))
+#define H(x, y, z)  ((x) ^ (y) ^ (z))
+#define I(x, y, z)  ((y) ^ ((x) | ~(z)))
+
+/*
+ * The MD5 transformation for all four rounds.
+ */
+
+#define STEP(f, a, b, c, d, x, t, s)                                          \
+    (a) += f((b), (c), (d)) + (x) + (t);                                      \
+    (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s))));                \
+    (a) += (b)
+
+/*
+ * SET() reads 4 input bytes in little-endian byte order and stores them
+ * in a properly aligned word in host byte order.
+ *
+ * The check for little-endian architectures that tolerate unaligned
+ * memory accesses is just an optimization.  Nothing will break if it
+ * does not work.
+ */
+
+#if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED)
+
+#define SET(n)      (*(uint32_t *) &p[n * 4])
+#define GET(n)      (*(uint32_t *) &p[n * 4])
+
+#else
+
+#define SET(n)                                                                \
+    (block[n] =                                                               \
+    (uint32_t) p[n * 4] |                                                     \
+    ((uint32_t) p[n * 4 + 1] << 8) |                                          \
+    ((uint32_t) p[n * 4 + 2] << 16) |                                         \
+    ((uint32_t) p[n * 4 + 3] << 24))
+
+#define GET(n)      block[n]
+
+#endif
+
+
+/*
+ * This processes one or more 64-byte data blocks, but does not update
+ * the bit counters.  There are no alignment requirements.
+ */
+
+static const u_char *
+ngx_md5_body(ngx_md5_t *ctx, const u_char *data, size_t size)
+{
+    uint32_t       a, b, c, d;
+    uint32_t       saved_a, saved_b, saved_c, saved_d;
+    const u_char  *p;
+#if !(NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED)
+    uint32_t       block[16];
+#endif
+
+    p = data;
+
+    a = ctx->a;
+    b = ctx->b;
+    c = ctx->c;
+    d = ctx->d;
+
+    do {
+        saved_a = a;
+        saved_b = b;
+        saved_c = c;
+        saved_d = d;
+
+        /* Round 1 */
+
+        STEP(F, a, b, c, d, SET(0),  0xd76aa478, 7);
+        STEP(F, d, a, b, c, SET(1),  0xe8c7b756, 12);
+        STEP(F, c, d, a, b, SET(2),  0x242070db, 17);
+        STEP(F, b, c, d, a, SET(3),  0xc1bdceee, 22);
+        STEP(F, a, b, c, d, SET(4),  0xf57c0faf, 7);
+        STEP(F, d, a, b, c, SET(5),  0x4787c62a, 12);
+        STEP(F, c, d, a, b, SET(6),  0xa8304613, 17);
+        STEP(F, b, c, d, a, SET(7),  0xfd469501, 22);
+        STEP(F, a, b, c, d, SET(8),  0x698098d8, 7);
+        STEP(F, d, a, b, c, SET(9),  0x8b44f7af, 12);
+        STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17);
+        STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22);
+        STEP(F, a, b, c, d, SET(12), 0x6b901122, 7);
+        STEP(F, d, a, b, c, SET(13), 0xfd987193, 12);
+        STEP(F, c, d, a, b, SET(14), 0xa679438e, 17);
+        STEP(F, b, c, d, a, SET(15), 0x49b40821, 22);
+
+        /* Round 2 */
+
+        STEP(G, a, b, c, d, GET(1),  0xf61e2562, 5);
+        STEP(G, d, a, b, c, GET(6),  0xc040b340, 9);
+        STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14);
+        STEP(G, b, c, d, a, GET(0),  0xe9b6c7aa, 20);
+        STEP(G, a, b, c, d, GET(5),  0xd62f105d, 5);
+        STEP(G, d, a, b, c, GET(10), 0x02441453, 9);
+        STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14);
+        STEP(G, b, c, d, a, GET(4),  0xe7d3fbc8, 20);
+        STEP(G, a, b, c, d, GET(9),  0x21e1cde6, 5);
+        STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9);
+        STEP(G, c, d, a, b, GET(3),  0xf4d50d87, 14);
+        STEP(G, b, c, d, a, GET(8),  0x455a14ed, 20);
+        STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5);
+        STEP(G, d, a, b, c, GET(2),  0xfcefa3f8, 9);
+        STEP(G, c, d, a, b, GET(7),  0x676f02d9, 14);
+        STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20);
+
+        /* Round 3 */
+
+        STEP(H, a, b, c, d, GET(5),  0xfffa3942, 4);
+        STEP(H, d, a, b, c, GET(8),  0x8771f681, 11);
+        STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16);
+        STEP(H, b, c, d, a, GET(14), 0xfde5380c, 23);
+        STEP(H, a, b, c, d, GET(1),  0xa4beea44, 4);
+        STEP(H, d, a, b, c, GET(4),  0x4bdecfa9, 11);
+        STEP(H, c, d, a, b, GET(7),  0xf6bb4b60, 16);
+        STEP(H, b, c, d, a, GET(10), 0xbebfbc70, 23);
+        STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4);
+        STEP(H, d, a, b, c, GET(0),  0xeaa127fa, 11);
+        STEP(H, c, d, a, b, GET(3),  0xd4ef3085, 16);
+        STEP(H, b, c, d, a, GET(6),  0x04881d05, 23);
+        STEP(H, a, b, c, d, GET(9),  0xd9d4d039, 4);
+        STEP(H, d, a, b, c, GET(12), 0xe6db99e5, 11);
+        STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16);
+        STEP(H, b, c, d, a, GET(2),  0xc4ac5665, 23);
+
+        /* Round 4 */
+
+        STEP(I, a, b, c, d, GET(0),  0xf4292244, 6);
+        STEP(I, d, a, b, c, GET(7),  0x432aff97, 10);
+        STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15);
+        STEP(I, b, c, d, a, GET(5),  0xfc93a039, 21);
+        STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6);
+        STEP(I, d, a, b, c, GET(3),  0x8f0ccc92, 10);
+        STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15);
+        STEP(I, b, c, d, a, GET(1),  0x85845dd1, 21);
+        STEP(I, a, b, c, d, GET(8),  0x6fa87e4f, 6);
+        STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10);
+        STEP(I, c, d, a, b, GET(6),  0xa3014314, 15);
+        STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21);
+        STEP(I, a, b, c, d, GET(4),  0xf7537e82, 6);
+        STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10);
+        STEP(I, c, d, a, b, GET(2),  0x2ad7d2bb, 15);
+        STEP(I, b, c, d, a, GET(9),  0xeb86d391, 21);
+
+        a += saved_a;
+        b += saved_b;
+        c += saved_c;
+        d += saved_d;
+
+        p += 64;
+
+    } while (size -= 64);
+
+    ctx->a = a;
+    ctx->b = b;
+    ctx->c = c;
+    ctx->d = d;
+
+    return p;
+}
diff --git a/nginx/src/core/ngx_md5.h b/nginx/src/core/ngx_md5.h
new file mode 100644 (file)
index 0000000..713b614
--- /dev/null
@@ -0,0 +1,28 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_MD5_H_INCLUDED_
+#define _NGX_MD5_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef struct {
+    uint64_t  bytes;
+    uint32_t  a, b, c, d;
+    u_char    buffer[64];
+} ngx_md5_t;
+
+
+void ngx_md5_init(ngx_md5_t *ctx);
+void ngx_md5_update(ngx_md5_t *ctx, const void *data, size_t size);
+void ngx_md5_final(u_char result[16], ngx_md5_t *ctx);
+
+
+#endif /* _NGX_MD5_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_module.c b/nginx/src/core/ngx_module.c
new file mode 100644 (file)
index 0000000..3e3c506
--- /dev/null
@@ -0,0 +1,360 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Maxim Dounin
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#define NGX_MAX_DYNAMIC_MODULES  128
+
+
+static ngx_uint_t ngx_module_index(ngx_cycle_t *cycle);
+static ngx_uint_t ngx_module_ctx_index(ngx_cycle_t *cycle, ngx_uint_t type,
+    ngx_uint_t index);
+
+
+ngx_uint_t         ngx_max_module;
+static ngx_uint_t  ngx_modules_n;
+
+
+ngx_int_t
+ngx_preinit_modules(void)
+{
+    ngx_uint_t  i;
+
+    for (i = 0; ngx_modules[i]; i++) {
+        ngx_modules[i]->index = i;
+        ngx_modules[i]->name = ngx_module_names[i];
+    }
+
+    ngx_modules_n = i;
+    ngx_max_module = ngx_modules_n + NGX_MAX_DYNAMIC_MODULES;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_cycle_modules(ngx_cycle_t *cycle)
+{
+    /*
+     * create a list of modules to be used for this cycle,
+     * copy static modules to it
+     */
+
+    cycle->modules = ngx_pcalloc(cycle->pool, (ngx_max_module + 1)
+                                              * sizeof(ngx_module_t *));
+    if (cycle->modules == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(cycle->modules, ngx_modules,
+               ngx_modules_n * sizeof(ngx_module_t *));
+
+    cycle->modules_n = ngx_modules_n;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_init_modules(ngx_cycle_t *cycle)
+{
+    ngx_uint_t  i;
+
+    for (i = 0; cycle->modules[i]; i++) {
+        if (cycle->modules[i]->init_module) {
+            if (cycle->modules[i]->init_module(cycle) != NGX_OK) {
+                return NGX_ERROR;
+            }
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_count_modules(ngx_cycle_t *cycle, ngx_uint_t type)
+{
+    ngx_uint_t     i, next, max;
+    ngx_module_t  *module;
+
+    next = 0;
+    max = 0;
+
+    /* count appropriate modules, set up their indices */
+
+    for (i = 0; cycle->modules[i]; i++) {
+        module = cycle->modules[i];
+
+        if (module->type != type) {
+            continue;
+        }
+
+        if (module->ctx_index != NGX_MODULE_UNSET_INDEX) {
+
+            /* if ctx_index was assigned, preserve it */
+
+            if (module->ctx_index > max) {
+                max = module->ctx_index;
+            }
+
+            if (module->ctx_index == next) {
+                next++;
+            }
+
+            continue;
+        }
+
+        /* search for some free index */
+
+        module->ctx_index = ngx_module_ctx_index(cycle, type, next);
+
+        if (module->ctx_index > max) {
+            max = module->ctx_index;
+        }
+
+        next = module->ctx_index + 1;
+    }
+
+    /*
+     * make sure the number returned is big enough for previous
+     * cycle as well, else there will be problems if the number
+     * will be stored in a global variable (as it's used to be)
+     * and we'll have to roll back to the previous cycle
+     */
+
+    if (cycle->old_cycle && cycle->old_cycle->modules) {
+
+        for (i = 0; cycle->old_cycle->modules[i]; i++) {
+            module = cycle->old_cycle->modules[i];
+
+            if (module->type != type) {
+                continue;
+            }
+
+            if (module->ctx_index > max) {
+                max = module->ctx_index;
+            }
+        }
+    }
+
+    /* prevent loading of additional modules */
+
+    cycle->modules_used = 1;
+
+    return max + 1;
+}
+
+
+ngx_int_t
+ngx_add_module(ngx_conf_t *cf, ngx_str_t *file, ngx_module_t *module,
+    char **order)
+{
+    void               *rv;
+    ngx_uint_t          i, m, before;
+    ngx_core_module_t  *core_module;
+
+    if (cf->cycle->modules_n >= ngx_max_module) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "too many modules loaded");
+        return NGX_ERROR;
+    }
+
+    if (module->version != nginx_version) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "module \"%V\" version %ui instead of %ui",
+                           file, module->version, (ngx_uint_t) nginx_version);
+        return NGX_ERROR;
+    }
+
+    if (ngx_strcmp(module->signature, NGX_MODULE_SIGNATURE) != 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "module \"%V\" is not binary compatible",
+                           file);
+        return NGX_ERROR;
+    }
+
+    for (m = 0; cf->cycle->modules[m]; m++) {
+        if (ngx_strcmp(cf->cycle->modules[m]->name, module->name) == 0) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "module \"%s\" is already loaded",
+                               module->name);
+            return NGX_ERROR;
+        }
+    }
+
+    /*
+     * if the module wasn't previously loaded, assign an index
+     */
+
+    if (module->index == NGX_MODULE_UNSET_INDEX) {
+        module->index = ngx_module_index(cf->cycle);
+
+        if (module->index >= ngx_max_module) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "too many modules loaded");
+            return NGX_ERROR;
+        }
+    }
+
+    /*
+     * put the module into the cycle->modules array
+     */
+
+    before = cf->cycle->modules_n;
+
+    if (order) {
+        for (i = 0; order[i]; i++) {
+            if (ngx_strcmp(order[i], module->name) == 0) {
+                i++;
+                break;
+            }
+        }
+
+        for ( /* void */ ; order[i]; i++) {
+
+#if 0
+            ngx_log_debug2(NGX_LOG_DEBUG_CORE, cf->log, 0,
+                           "module: %s before %s",
+                           module->name, order[i]);
+#endif
+
+            for (m = 0; m < before; m++) {
+                if (ngx_strcmp(cf->cycle->modules[m]->name, order[i]) == 0) {
+
+                    ngx_log_debug3(NGX_LOG_DEBUG_CORE, cf->log, 0,
+                                   "module: %s before %s:%i",
+                                   module->name, order[i], m);
+
+                    before = m;
+                    break;
+                }
+            }
+        }
+    }
+
+    /* put the module before modules[before] */
+
+    if (before != cf->cycle->modules_n) {
+        ngx_memmove(&cf->cycle->modules[before + 1],
+                    &cf->cycle->modules[before],
+                    (cf->cycle->modules_n - before) * sizeof(ngx_module_t *));
+    }
+
+    cf->cycle->modules[before] = module;
+    cf->cycle->modules_n++;
+
+    if (module->type == NGX_CORE_MODULE) {
+
+        /*
+         * we are smart enough to initialize core modules;
+         * other modules are expected to be loaded before
+         * initialization - e.g., http modules must be loaded
+         * before http{} block
+         */
+
+        core_module = module->ctx;
+
+        if (core_module->create_conf) {
+            rv = core_module->create_conf(cf->cycle);
+            if (rv == NULL) {
+                return NGX_ERROR;
+            }
+
+            cf->cycle->conf_ctx[module->index] = rv;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_uint_t
+ngx_module_index(ngx_cycle_t *cycle)
+{
+    ngx_uint_t     i, index;
+    ngx_module_t  *module;
+
+    index = 0;
+
+again:
+
+    /* find an unused index */
+
+    for (i = 0; cycle->modules[i]; i++) {
+        module = cycle->modules[i];
+
+        if (module->index == index) {
+            index++;
+            goto again;
+        }
+    }
+
+    /* check previous cycle */
+
+    if (cycle->old_cycle && cycle->old_cycle->modules) {
+
+        for (i = 0; cycle->old_cycle->modules[i]; i++) {
+            module = cycle->old_cycle->modules[i];
+
+            if (module->index == index) {
+                index++;
+                goto again;
+            }
+        }
+    }
+
+    return index;
+}
+
+
+static ngx_uint_t
+ngx_module_ctx_index(ngx_cycle_t *cycle, ngx_uint_t type, ngx_uint_t index)
+{
+    ngx_uint_t     i;
+    ngx_module_t  *module;
+
+again:
+
+    /* find an unused ctx_index */
+
+    for (i = 0; cycle->modules[i]; i++) {
+        module = cycle->modules[i];
+
+        if (module->type != type) {
+            continue;
+        }
+
+        if (module->ctx_index == index) {
+            index++;
+            goto again;
+        }
+    }
+
+    /* check previous cycle */
+
+    if (cycle->old_cycle && cycle->old_cycle->modules) {
+
+        for (i = 0; cycle->old_cycle->modules[i]; i++) {
+            module = cycle->old_cycle->modules[i];
+
+            if (module->type != type) {
+                continue;
+            }
+
+            if (module->ctx_index == index) {
+                index++;
+                goto again;
+            }
+        }
+    }
+
+    return index;
+}
diff --git a/nginx/src/core/ngx_module.h b/nginx/src/core/ngx_module.h
new file mode 100644 (file)
index 0000000..8cf3210
--- /dev/null
@@ -0,0 +1,283 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Maxim Dounin
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_MODULE_H_INCLUDED_
+#define _NGX_MODULE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <nginx.h>
+
+
+#define NGX_MODULE_UNSET_INDEX  (ngx_uint_t) -1
+
+
+#define NGX_MODULE_SIGNATURE_0                                                \
+    ngx_value(NGX_PTR_SIZE) ","                                               \
+    ngx_value(NGX_SIG_ATOMIC_T_SIZE) ","                                      \
+    ngx_value(NGX_TIME_T_SIZE) ","
+
+#if (NGX_HAVE_KQUEUE)
+#define NGX_MODULE_SIGNATURE_1   "1"
+#else
+#define NGX_MODULE_SIGNATURE_1   "0"
+#endif
+
+#if (NGX_HAVE_IOCP)
+#define NGX_MODULE_SIGNATURE_2   "1"
+#else
+#define NGX_MODULE_SIGNATURE_2   "0"
+#endif
+
+#if (NGX_HAVE_FILE_AIO || NGX_COMPAT)
+#define NGX_MODULE_SIGNATURE_3   "1"
+#else
+#define NGX_MODULE_SIGNATURE_3   "0"
+#endif
+
+#if (NGX_HAVE_AIO_SENDFILE || NGX_COMPAT)
+#define NGX_MODULE_SIGNATURE_4   "1"
+#else
+#define NGX_MODULE_SIGNATURE_4   "0"
+#endif
+
+#if (NGX_HAVE_EVENTFD)
+#define NGX_MODULE_SIGNATURE_5   "1"
+#else
+#define NGX_MODULE_SIGNATURE_5   "0"
+#endif
+
+#if (NGX_HAVE_EPOLL)
+#define NGX_MODULE_SIGNATURE_6   "1"
+#else
+#define NGX_MODULE_SIGNATURE_6   "0"
+#endif
+
+#if (NGX_HAVE_KEEPALIVE_TUNABLE)
+#define NGX_MODULE_SIGNATURE_7   "1"
+#else
+#define NGX_MODULE_SIGNATURE_7   "0"
+#endif
+
+#if (NGX_HAVE_INET6)
+#define NGX_MODULE_SIGNATURE_8   "1"
+#else
+#define NGX_MODULE_SIGNATURE_8   "0"
+#endif
+
+#define NGX_MODULE_SIGNATURE_9   "1"
+#define NGX_MODULE_SIGNATURE_10  "1"
+
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
+#define NGX_MODULE_SIGNATURE_11  "1"
+#else
+#define NGX_MODULE_SIGNATURE_11  "0"
+#endif
+
+#define NGX_MODULE_SIGNATURE_12  "1"
+
+#if (NGX_HAVE_SETFIB)
+#define NGX_MODULE_SIGNATURE_13  "1"
+#else
+#define NGX_MODULE_SIGNATURE_13  "0"
+#endif
+
+#if (NGX_HAVE_TCP_FASTOPEN)
+#define NGX_MODULE_SIGNATURE_14  "1"
+#else
+#define NGX_MODULE_SIGNATURE_14  "0"
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+#define NGX_MODULE_SIGNATURE_15  "1"
+#else
+#define NGX_MODULE_SIGNATURE_15  "0"
+#endif
+
+#if (NGX_HAVE_VARIADIC_MACROS)
+#define NGX_MODULE_SIGNATURE_16  "1"
+#else
+#define NGX_MODULE_SIGNATURE_16  "0"
+#endif
+
+#define NGX_MODULE_SIGNATURE_17  "0"
+#define NGX_MODULE_SIGNATURE_18  "0"
+
+#if (NGX_HAVE_OPENAT)
+#define NGX_MODULE_SIGNATURE_19  "1"
+#else
+#define NGX_MODULE_SIGNATURE_19  "0"
+#endif
+
+#if (NGX_HAVE_ATOMIC_OPS)
+#define NGX_MODULE_SIGNATURE_20  "1"
+#else
+#define NGX_MODULE_SIGNATURE_20  "0"
+#endif
+
+#if (NGX_HAVE_POSIX_SEM)
+#define NGX_MODULE_SIGNATURE_21  "1"
+#else
+#define NGX_MODULE_SIGNATURE_21  "0"
+#endif
+
+#if (NGX_THREADS || NGX_COMPAT)
+#define NGX_MODULE_SIGNATURE_22  "1"
+#else
+#define NGX_MODULE_SIGNATURE_22  "0"
+#endif
+
+#if (NGX_PCRE)
+#define NGX_MODULE_SIGNATURE_23  "1"
+#else
+#define NGX_MODULE_SIGNATURE_23  "0"
+#endif
+
+#if (NGX_HTTP_SSL || NGX_COMPAT)
+#define NGX_MODULE_SIGNATURE_24  "1"
+#else
+#define NGX_MODULE_SIGNATURE_24  "0"
+#endif
+
+#define NGX_MODULE_SIGNATURE_25  "1"
+
+#if (NGX_HTTP_GZIP)
+#define NGX_MODULE_SIGNATURE_26  "1"
+#else
+#define NGX_MODULE_SIGNATURE_26  "0"
+#endif
+
+#define NGX_MODULE_SIGNATURE_27  "1"
+
+#if (NGX_HTTP_X_FORWARDED_FOR)
+#define NGX_MODULE_SIGNATURE_28  "1"
+#else
+#define NGX_MODULE_SIGNATURE_28  "0"
+#endif
+
+#if (NGX_HTTP_REALIP)
+#define NGX_MODULE_SIGNATURE_29  "1"
+#else
+#define NGX_MODULE_SIGNATURE_29  "0"
+#endif
+
+#if (NGX_HTTP_HEADERS)
+#define NGX_MODULE_SIGNATURE_30  "1"
+#else
+#define NGX_MODULE_SIGNATURE_30  "0"
+#endif
+
+#if (NGX_HTTP_DAV)
+#define NGX_MODULE_SIGNATURE_31  "1"
+#else
+#define NGX_MODULE_SIGNATURE_31  "0"
+#endif
+
+#if (NGX_HTTP_CACHE)
+#define NGX_MODULE_SIGNATURE_32  "1"
+#else
+#define NGX_MODULE_SIGNATURE_32  "0"
+#endif
+
+#if (NGX_HTTP_UPSTREAM_ZONE)
+#define NGX_MODULE_SIGNATURE_33  "1"
+#else
+#define NGX_MODULE_SIGNATURE_33  "0"
+#endif
+
+#if (NGX_COMPAT)
+#define NGX_MODULE_SIGNATURE_34  "1"
+#else
+#define NGX_MODULE_SIGNATURE_34  "0"
+#endif
+
+#define NGX_MODULE_SIGNATURE                                                  \
+    NGX_MODULE_SIGNATURE_0 NGX_MODULE_SIGNATURE_1 NGX_MODULE_SIGNATURE_2      \
+    NGX_MODULE_SIGNATURE_3 NGX_MODULE_SIGNATURE_4 NGX_MODULE_SIGNATURE_5      \
+    NGX_MODULE_SIGNATURE_6 NGX_MODULE_SIGNATURE_7 NGX_MODULE_SIGNATURE_8      \
+    NGX_MODULE_SIGNATURE_9 NGX_MODULE_SIGNATURE_10 NGX_MODULE_SIGNATURE_11    \
+    NGX_MODULE_SIGNATURE_12 NGX_MODULE_SIGNATURE_13 NGX_MODULE_SIGNATURE_14   \
+    NGX_MODULE_SIGNATURE_15 NGX_MODULE_SIGNATURE_16 NGX_MODULE_SIGNATURE_17   \
+    NGX_MODULE_SIGNATURE_18 NGX_MODULE_SIGNATURE_19 NGX_MODULE_SIGNATURE_20   \
+    NGX_MODULE_SIGNATURE_21 NGX_MODULE_SIGNATURE_22 NGX_MODULE_SIGNATURE_23   \
+    NGX_MODULE_SIGNATURE_24 NGX_MODULE_SIGNATURE_25 NGX_MODULE_SIGNATURE_26   \
+    NGX_MODULE_SIGNATURE_27 NGX_MODULE_SIGNATURE_28 NGX_MODULE_SIGNATURE_29   \
+    NGX_MODULE_SIGNATURE_30 NGX_MODULE_SIGNATURE_31 NGX_MODULE_SIGNATURE_32   \
+    NGX_MODULE_SIGNATURE_33 NGX_MODULE_SIGNATURE_34
+
+
+#define NGX_MODULE_V1                                                         \
+    NGX_MODULE_UNSET_INDEX, NGX_MODULE_UNSET_INDEX,                           \
+    NULL, 0, 0, nginx_version, NGX_MODULE_SIGNATURE
+
+#define NGX_MODULE_V1_PADDING  0, 0, 0, 0, 0, 0, 0, 0
+
+
+struct ngx_module_s {
+    ngx_uint_t            ctx_index;
+    ngx_uint_t            index;
+
+    char                 *name;
+
+    ngx_uint_t            spare0;
+    ngx_uint_t            spare1;
+
+    ngx_uint_t            version;
+    const char           *signature;
+
+    void                 *ctx;
+    ngx_command_t        *commands;
+    ngx_uint_t            type;
+
+    ngx_int_t           (*init_master)(ngx_log_t *log);
+
+    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);
+
+    ngx_int_t           (*init_process)(ngx_cycle_t *cycle);
+    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);
+    void                (*exit_thread)(ngx_cycle_t *cycle);
+    void                (*exit_process)(ngx_cycle_t *cycle);
+
+    void                (*exit_master)(ngx_cycle_t *cycle);
+
+    uintptr_t             spare_hook0;
+    uintptr_t             spare_hook1;
+    uintptr_t             spare_hook2;
+    uintptr_t             spare_hook3;
+    uintptr_t             spare_hook4;
+    uintptr_t             spare_hook5;
+    uintptr_t             spare_hook6;
+    uintptr_t             spare_hook7;
+};
+
+
+typedef struct {
+    ngx_str_t             name;
+    void               *(*create_conf)(ngx_cycle_t *cycle);
+    char               *(*init_conf)(ngx_cycle_t *cycle, void *conf);
+} ngx_core_module_t;
+
+
+ngx_int_t ngx_preinit_modules(void);
+ngx_int_t ngx_cycle_modules(ngx_cycle_t *cycle);
+ngx_int_t ngx_init_modules(ngx_cycle_t *cycle);
+ngx_int_t ngx_count_modules(ngx_cycle_t *cycle, ngx_uint_t type);
+
+
+ngx_int_t ngx_add_module(ngx_conf_t *cf, ngx_str_t *file,
+    ngx_module_t *module, char **order);
+
+
+extern ngx_module_t  *ngx_modules[];
+extern ngx_uint_t     ngx_max_module;
+
+extern char          *ngx_module_names[];
+
+
+#endif /* _NGX_MODULE_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_murmurhash.c b/nginx/src/core/ngx_murmurhash.c
new file mode 100644 (file)
index 0000000..5ade658
--- /dev/null
@@ -0,0 +1,52 @@
+
+/*
+ * Copyright (C) Austin Appleby
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+uint32_t
+ngx_murmur_hash2(u_char *data, size_t len)
+{
+    uint32_t  h, k;
+
+    h = 0 ^ len;
+
+    while (len >= 4) {
+        k  = data[0];
+        k |= data[1] << 8;
+        k |= data[2] << 16;
+        k |= data[3] << 24;
+
+        k *= 0x5bd1e995;
+        k ^= k >> 24;
+        k *= 0x5bd1e995;
+
+        h *= 0x5bd1e995;
+        h ^= k;
+
+        data += 4;
+        len -= 4;
+    }
+
+    switch (len) {
+    case 3:
+        h ^= data[2] << 16;
+        /* fall through */
+    case 2:
+        h ^= data[1] << 8;
+        /* fall through */
+    case 1:
+        h ^= data[0];
+        h *= 0x5bd1e995;
+    }
+
+    h ^= h >> 13;
+    h *= 0x5bd1e995;
+    h ^= h >> 15;
+
+    return h;
+}
diff --git a/nginx/src/core/ngx_murmurhash.h b/nginx/src/core/ngx_murmurhash.h
new file mode 100644 (file)
index 0000000..54e867d
--- /dev/null
@@ -0,0 +1,19 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_MURMURHASH_H_INCLUDED_
+#define _NGX_MURMURHASH_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+uint32_t ngx_murmur_hash2(u_char *data, size_t len);
+
+
+#endif /* _NGX_MURMURHASH_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_open_file_cache.c b/nginx/src/core/ngx_open_file_cache.c
new file mode 100644 (file)
index 0000000..b23ee78
--- /dev/null
@@ -0,0 +1,1253 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+/*
+ * open file cache caches
+ *    open file handles with stat() info;
+ *    directories stat() info;
+ *    files and directories errors: not found, access denied, etc.
+ */
+
+
+#define NGX_MIN_READ_AHEAD  (128 * 1024)
+
+
+static void ngx_open_file_cache_cleanup(void *data);
+#if (NGX_HAVE_OPENAT)
+static ngx_fd_t ngx_openat_file_owner(ngx_fd_t at_fd, const u_char *name,
+    ngx_int_t mode, ngx_int_t create, ngx_int_t access, ngx_log_t *log);
+#if (NGX_HAVE_O_PATH)
+static ngx_int_t ngx_file_o_path_info(ngx_fd_t fd, ngx_file_info_t *fi,
+    ngx_log_t *log);
+#endif
+#endif
+static ngx_fd_t ngx_open_file_wrapper(ngx_str_t *name,
+    ngx_open_file_info_t *of, ngx_int_t mode, ngx_int_t create,
+    ngx_int_t access, ngx_log_t *log);
+static ngx_int_t ngx_file_info_wrapper(ngx_str_t *name,
+    ngx_open_file_info_t *of, ngx_file_info_t *fi, ngx_log_t *log);
+static ngx_int_t ngx_open_and_stat_file(ngx_str_t *name,
+    ngx_open_file_info_t *of, ngx_log_t *log);
+static void ngx_open_file_add_event(ngx_open_file_cache_t *cache,
+    ngx_cached_open_file_t *file, ngx_open_file_info_t *of, ngx_log_t *log);
+static void ngx_open_file_cleanup(void *data);
+static void ngx_close_cached_file(ngx_open_file_cache_t *cache,
+    ngx_cached_open_file_t *file, ngx_uint_t min_uses, ngx_log_t *log);
+static void ngx_open_file_del_event(ngx_cached_open_file_t *file);
+static void ngx_expire_old_cached_files(ngx_open_file_cache_t *cache,
+    ngx_uint_t n, ngx_log_t *log);
+static void ngx_open_file_cache_rbtree_insert_value(ngx_rbtree_node_t *temp,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
+static ngx_cached_open_file_t *
+    ngx_open_file_lookup(ngx_open_file_cache_t *cache, ngx_str_t *name,
+    uint32_t hash);
+static void ngx_open_file_cache_remove(ngx_event_t *ev);
+
+
+ngx_open_file_cache_t *
+ngx_open_file_cache_init(ngx_pool_t *pool, ngx_uint_t max, time_t inactive)
+{
+    ngx_pool_cleanup_t     *cln;
+    ngx_open_file_cache_t  *cache;
+
+    cache = ngx_palloc(pool, sizeof(ngx_open_file_cache_t));
+    if (cache == NULL) {
+        return NULL;
+    }
+
+    ngx_rbtree_init(&cache->rbtree, &cache->sentinel,
+                    ngx_open_file_cache_rbtree_insert_value);
+
+    ngx_queue_init(&cache->expire_queue);
+
+    cache->current = 0;
+    cache->max = max;
+    cache->inactive = inactive;
+
+    cln = ngx_pool_cleanup_add(pool, 0);
+    if (cln == NULL) {
+        return NULL;
+    }
+
+    cln->handler = ngx_open_file_cache_cleanup;
+    cln->data = cache;
+
+    return cache;
+}
+
+
+static void
+ngx_open_file_cache_cleanup(void *data)
+{
+    ngx_open_file_cache_t  *cache = data;
+
+    ngx_queue_t             *q;
+    ngx_cached_open_file_t  *file;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
+                   "open file cache cleanup");
+
+    for ( ;; ) {
+
+        if (ngx_queue_empty(&cache->expire_queue)) {
+            break;
+        }
+
+        q = ngx_queue_last(&cache->expire_queue);
+
+        file = ngx_queue_data(q, ngx_cached_open_file_t, queue);
+
+        ngx_queue_remove(q);
+
+        ngx_rbtree_delete(&cache->rbtree, &file->node);
+
+        cache->current--;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
+                       "delete cached open file: %s", file->name);
+
+        if (!file->err && !file->is_dir) {
+            file->close = 1;
+            file->count = 0;
+            ngx_close_cached_file(cache, file, 0, ngx_cycle->log);
+
+        } else {
+            ngx_free(file->name);
+            ngx_free(file);
+        }
+    }
+
+    if (cache->current) {
+        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+                      "%ui items still left in open file cache",
+                      cache->current);
+    }
+
+    if (cache->rbtree.root != cache->rbtree.sentinel) {
+        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+                      "rbtree still is not empty in open file cache");
+
+    }
+}
+
+
+ngx_int_t
+ngx_open_cached_file(ngx_open_file_cache_t *cache, ngx_str_t *name,
+    ngx_open_file_info_t *of, ngx_pool_t *pool)
+{
+    time_t                          now;
+    uint32_t                        hash;
+    ngx_int_t                       rc;
+    ngx_file_info_t                 fi;
+    ngx_pool_cleanup_t             *cln;
+    ngx_cached_open_file_t         *file;
+    ngx_pool_cleanup_file_t        *clnf;
+    ngx_open_file_cache_cleanup_t  *ofcln;
+
+    of->fd = NGX_INVALID_FILE;
+    of->err = 0;
+
+    if (cache == NULL) {
+
+        if (of->test_only) {
+
+            if (ngx_file_info_wrapper(name, of, &fi, pool->log)
+                == NGX_FILE_ERROR)
+            {
+                return NGX_ERROR;
+            }
+
+            of->uniq = ngx_file_uniq(&fi);
+            of->mtime = ngx_file_mtime(&fi);
+            of->size = ngx_file_size(&fi);
+            of->fs_size = ngx_file_fs_size(&fi);
+            of->is_dir = ngx_is_dir(&fi);
+            of->is_file = ngx_is_file(&fi);
+            of->is_link = ngx_is_link(&fi);
+            of->is_exec = ngx_is_exec(&fi);
+
+            return NGX_OK;
+        }
+
+        cln = ngx_pool_cleanup_add(pool, sizeof(ngx_pool_cleanup_file_t));
+        if (cln == NULL) {
+            return NGX_ERROR;
+        }
+
+        rc = ngx_open_and_stat_file(name, of, pool->log);
+
+        if (rc == NGX_OK && !of->is_dir) {
+            cln->handler = ngx_pool_cleanup_file;
+            clnf = cln->data;
+
+            clnf->fd = of->fd;
+            clnf->name = name->data;
+            clnf->log = pool->log;
+        }
+
+        return rc;
+    }
+
+    cln = ngx_pool_cleanup_add(pool, sizeof(ngx_open_file_cache_cleanup_t));
+    if (cln == NULL) {
+        return NGX_ERROR;
+    }
+
+    now = ngx_time();
+
+    hash = ngx_crc32_long(name->data, name->len);
+
+    file = ngx_open_file_lookup(cache, name, hash);
+
+    if (file) {
+
+        file->uses++;
+
+        ngx_queue_remove(&file->queue);
+
+        if (file->fd == NGX_INVALID_FILE && file->err == 0 && !file->is_dir) {
+
+            /* file was not used often enough to keep open */
+
+            rc = ngx_open_and_stat_file(name, of, pool->log);
+
+            if (rc != NGX_OK && (of->err == 0 || !of->errors)) {
+                goto failed;
+            }
+
+            goto add_event;
+        }
+
+        if (file->use_event
+            || (file->event == NULL
+                && (of->uniq == 0 || of->uniq == file->uniq)
+                && now - file->created < of->valid
+#if (NGX_HAVE_OPENAT)
+                && of->disable_symlinks == file->disable_symlinks
+                && of->disable_symlinks_from == file->disable_symlinks_from
+#endif
+            ))
+        {
+            if (file->err == 0) {
+
+                of->fd = file->fd;
+                of->uniq = file->uniq;
+                of->mtime = file->mtime;
+                of->size = file->size;
+
+                of->is_dir = file->is_dir;
+                of->is_file = file->is_file;
+                of->is_link = file->is_link;
+                of->is_exec = file->is_exec;
+                of->is_directio = file->is_directio;
+
+                if (!file->is_dir) {
+                    file->count++;
+                    ngx_open_file_add_event(cache, file, of, pool->log);
+                }
+
+            } else {
+                of->err = file->err;
+#if (NGX_HAVE_OPENAT)
+                of->failed = file->disable_symlinks ? ngx_openat_file_n
+                                                    : ngx_open_file_n;
+#else
+                of->failed = ngx_open_file_n;
+#endif
+            }
+
+            goto found;
+        }
+
+        ngx_log_debug4(NGX_LOG_DEBUG_CORE, pool->log, 0,
+                       "retest open file: %s, fd:%d, c:%d, e:%d",
+                       file->name, file->fd, file->count, file->err);
+
+        if (file->is_dir) {
+
+            /*
+             * chances that directory became file are very small
+             * so test_dir flag allows to use a single syscall
+             * in ngx_file_info() instead of three syscalls
+             */
+
+            of->test_dir = 1;
+        }
+
+        of->fd = file->fd;
+        of->uniq = file->uniq;
+
+        rc = ngx_open_and_stat_file(name, of, pool->log);
+
+        if (rc != NGX_OK && (of->err == 0 || !of->errors)) {
+            goto failed;
+        }
+
+        if (of->is_dir) {
+
+            if (file->is_dir || file->err) {
+                goto update;
+            }
+
+            /* file became directory */
+
+        } else if (of->err == 0) {  /* file */
+
+            if (file->is_dir || file->err) {
+                goto add_event;
+            }
+
+            if (of->uniq == file->uniq) {
+
+                if (file->event) {
+                    file->use_event = 1;
+                }
+
+                of->is_directio = file->is_directio;
+
+                goto update;
+            }
+
+            /* file was changed */
+
+        } else { /* error to cache */
+
+            if (file->err || file->is_dir) {
+                goto update;
+            }
+
+            /* file was removed, etc. */
+        }
+
+        if (file->count == 0) {
+
+            ngx_open_file_del_event(file);
+
+            if (ngx_close_file(file->fd) == NGX_FILE_ERROR) {
+                ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno,
+                              ngx_close_file_n " \"%V\" failed", name);
+            }
+
+            goto add_event;
+        }
+
+        ngx_rbtree_delete(&cache->rbtree, &file->node);
+
+        cache->current--;
+
+        file->close = 1;
+
+        goto create;
+    }
+
+    /* not found */
+
+    rc = ngx_open_and_stat_file(name, of, pool->log);
+
+    if (rc != NGX_OK && (of->err == 0 || !of->errors)) {
+        goto failed;
+    }
+
+create:
+
+    if (cache->current >= cache->max) {
+        ngx_expire_old_cached_files(cache, 0, pool->log);
+    }
+
+    file = ngx_alloc(sizeof(ngx_cached_open_file_t), pool->log);
+
+    if (file == NULL) {
+        goto failed;
+    }
+
+    file->name = ngx_alloc(name->len + 1, pool->log);
+
+    if (file->name == NULL) {
+        ngx_free(file);
+        file = NULL;
+        goto failed;
+    }
+
+    ngx_cpystrn(file->name, name->data, name->len + 1);
+
+    file->node.key = hash;
+
+    ngx_rbtree_insert(&cache->rbtree, &file->node);
+
+    cache->current++;
+
+    file->uses = 1;
+    file->count = 0;
+    file->use_event = 0;
+    file->event = NULL;
+
+add_event:
+
+    ngx_open_file_add_event(cache, file, of, pool->log);
+
+update:
+
+    file->fd = of->fd;
+    file->err = of->err;
+#if (NGX_HAVE_OPENAT)
+    file->disable_symlinks = of->disable_symlinks;
+    file->disable_symlinks_from = of->disable_symlinks_from;
+#endif
+
+    if (of->err == 0) {
+        file->uniq = of->uniq;
+        file->mtime = of->mtime;
+        file->size = of->size;
+
+        file->close = 0;
+
+        file->is_dir = of->is_dir;
+        file->is_file = of->is_file;
+        file->is_link = of->is_link;
+        file->is_exec = of->is_exec;
+        file->is_directio = of->is_directio;
+
+        if (!of->is_dir) {
+            file->count++;
+        }
+    }
+
+    file->created = now;
+
+found:
+
+    file->accessed = now;
+
+    ngx_queue_insert_head(&cache->expire_queue, &file->queue);
+
+    ngx_log_debug5(NGX_LOG_DEBUG_CORE, pool->log, 0,
+                   "cached open file: %s, fd:%d, c:%d, e:%d, u:%d",
+                   file->name, file->fd, file->count, file->err, file->uses);
+
+    if (of->err == 0) {
+
+        if (!of->is_dir) {
+            cln->handler = ngx_open_file_cleanup;
+            ofcln = cln->data;
+
+            ofcln->cache = cache;
+            ofcln->file = file;
+            ofcln->min_uses = of->min_uses;
+            ofcln->log = pool->log;
+        }
+
+        return NGX_OK;
+    }
+
+    return NGX_ERROR;
+
+failed:
+
+    if (file) {
+        ngx_rbtree_delete(&cache->rbtree, &file->node);
+
+        cache->current--;
+
+        if (file->count == 0) {
+
+            if (file->fd != NGX_INVALID_FILE) {
+                if (ngx_close_file(file->fd) == NGX_FILE_ERROR) {
+                    ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno,
+                                  ngx_close_file_n " \"%s\" failed",
+                                  file->name);
+                }
+            }
+
+            ngx_free(file->name);
+            ngx_free(file);
+
+        } else {
+            file->close = 1;
+        }
+    }
+
+    if (of->fd != NGX_INVALID_FILE) {
+        if (ngx_close_file(of->fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno,
+                          ngx_close_file_n " \"%V\" failed", name);
+        }
+    }
+
+    return NGX_ERROR;
+}
+
+
+#if (NGX_HAVE_OPENAT)
+
+static ngx_fd_t
+ngx_openat_file_owner(ngx_fd_t at_fd, const u_char *name,
+    ngx_int_t mode, ngx_int_t create, ngx_int_t access, ngx_log_t *log)
+{
+    ngx_fd_t         fd;
+    ngx_err_t        err;
+    ngx_file_info_t  fi, atfi;
+
+    /*
+     * To allow symlinks with the same owner, use openat() (followed
+     * by fstat()) and fstatat(AT_SYMLINK_NOFOLLOW), and then compare
+     * uids between fstat() and fstatat().
+     *
+     * As there is a race between openat() and fstatat() we don't
+     * know if openat() in fact opened symlink or not.  Therefore,
+     * we have to compare uids even if fstatat() reports the opened
+     * component isn't a symlink (as we don't know whether it was
+     * symlink during openat() or not).
+     */
+
+    fd = ngx_openat_file(at_fd, name, mode, create, access);
+
+    if (fd == NGX_INVALID_FILE) {
+        return NGX_INVALID_FILE;
+    }
+
+    if (ngx_file_at_info(at_fd, name, &atfi, AT_SYMLINK_NOFOLLOW)
+        == NGX_FILE_ERROR)
+    {
+        err = ngx_errno;
+        goto failed;
+    }
+
+#if (NGX_HAVE_O_PATH)
+    if (ngx_file_o_path_info(fd, &fi, log) == NGX_ERROR) {
+        err = ngx_errno;
+        goto failed;
+    }
+#else
+    if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
+        err = ngx_errno;
+        goto failed;
+    }
+#endif
+
+    if (fi.st_uid != atfi.st_uid) {
+        err = NGX_ELOOP;
+        goto failed;
+    }
+
+    return fd;
+
+failed:
+
+    if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      ngx_close_file_n " \"%s\" failed", name);
+    }
+
+    ngx_set_errno(err);
+
+    return NGX_INVALID_FILE;
+}
+
+
+#if (NGX_HAVE_O_PATH)
+
+static ngx_int_t
+ngx_file_o_path_info(ngx_fd_t fd, ngx_file_info_t *fi, ngx_log_t *log)
+{
+    static ngx_uint_t  use_fstat = 1;
+
+    /*
+     * In Linux 2.6.39 the O_PATH flag was introduced that allows to obtain
+     * a descriptor without actually opening file or directory.  It requires
+     * less permissions for path components, but till Linux 3.6 fstat() returns
+     * EBADF on such descriptors, and fstatat() with the AT_EMPTY_PATH flag
+     * should be used instead.
+     *
+     * Three scenarios are handled in this function:
+     *
+     * 1) The kernel is newer than 3.6 or fstat() with O_PATH support was
+     *    backported by vendor.  Then fstat() is used.
+     *
+     * 2) The kernel is newer than 2.6.39 but older than 3.6.  In this case
+     *    the first call of fstat() returns EBADF and we fallback to fstatat()
+     *    with AT_EMPTY_PATH which was introduced at the same time as O_PATH.
+     *
+     * 3) The kernel is older than 2.6.39 but nginx was build with O_PATH
+     *    support.  Since descriptors are opened with O_PATH|O_RDONLY flags
+     *    and O_PATH is ignored by the kernel then the O_RDONLY flag is
+     *    actually used.  In this case fstat() just works.
+     */
+
+    if (use_fstat) {
+        if (ngx_fd_info(fd, fi) != NGX_FILE_ERROR) {
+            return NGX_OK;
+        }
+
+        if (ngx_errno != NGX_EBADF) {
+            return NGX_ERROR;
+        }
+
+        ngx_log_error(NGX_LOG_NOTICE, log, 0,
+                      "fstat(O_PATH) failed with EBADF, "
+                      "switching to fstatat(AT_EMPTY_PATH)");
+
+        use_fstat = 0;
+    }
+
+    if (ngx_file_at_info(fd, "", fi, AT_EMPTY_PATH) != NGX_FILE_ERROR) {
+        return NGX_OK;
+    }
+
+    return NGX_ERROR;
+}
+
+#endif
+
+#endif /* NGX_HAVE_OPENAT */
+
+
+static ngx_fd_t
+ngx_open_file_wrapper(ngx_str_t *name, ngx_open_file_info_t *of,
+    ngx_int_t mode, ngx_int_t create, ngx_int_t access, ngx_log_t *log)
+{
+    ngx_fd_t  fd;
+
+#if !(NGX_HAVE_OPENAT)
+
+    fd = ngx_open_file(name->data, mode, create, access);
+
+    if (fd == NGX_INVALID_FILE) {
+        of->err = ngx_errno;
+        of->failed = ngx_open_file_n;
+        return NGX_INVALID_FILE;
+    }
+
+    return fd;
+
+#else
+
+    u_char           *p, *cp, *end;
+    ngx_fd_t          at_fd;
+    ngx_str_t         at_name;
+
+    if (of->disable_symlinks == NGX_DISABLE_SYMLINKS_OFF) {
+        fd = ngx_open_file(name->data, mode, create, access);
+
+        if (fd == NGX_INVALID_FILE) {
+            of->err = ngx_errno;
+            of->failed = ngx_open_file_n;
+            return NGX_INVALID_FILE;
+        }
+
+        return fd;
+    }
+
+    p = name->data;
+    end = p + name->len;
+
+    at_name = *name;
+
+    if (of->disable_symlinks_from) {
+
+        cp = p + of->disable_symlinks_from;
+
+        *cp = '\0';
+
+        at_fd = ngx_open_file(p, NGX_FILE_SEARCH|NGX_FILE_NONBLOCK,
+                              NGX_FILE_OPEN, 0);
+
+        *cp = '/';
+
+        if (at_fd == NGX_INVALID_FILE) {
+            of->err = ngx_errno;
+            of->failed = ngx_open_file_n;
+            return NGX_INVALID_FILE;
+        }
+
+        at_name.len = of->disable_symlinks_from;
+        p = cp + 1;
+
+    } else if (*p == '/') {
+
+        at_fd = ngx_open_file("/",
+                              NGX_FILE_SEARCH|NGX_FILE_NONBLOCK,
+                              NGX_FILE_OPEN, 0);
+
+        if (at_fd == NGX_INVALID_FILE) {
+            of->err = ngx_errno;
+            of->failed = ngx_openat_file_n;
+            return NGX_INVALID_FILE;
+        }
+
+        at_name.len = 1;
+        p++;
+
+    } else {
+        at_fd = NGX_AT_FDCWD;
+    }
+
+    for ( ;; ) {
+        cp = ngx_strlchr(p, end, '/');
+        if (cp == NULL) {
+            break;
+        }
+
+        if (cp == p) {
+            p++;
+            continue;
+        }
+
+        *cp = '\0';
+
+        if (of->disable_symlinks == NGX_DISABLE_SYMLINKS_NOTOWNER) {
+            fd = ngx_openat_file_owner(at_fd, p,
+                                       NGX_FILE_SEARCH|NGX_FILE_NONBLOCK,
+                                       NGX_FILE_OPEN, 0, log);
+
+        } else {
+            fd = ngx_openat_file(at_fd, p,
+                           NGX_FILE_SEARCH|NGX_FILE_NONBLOCK|NGX_FILE_NOFOLLOW,
+                           NGX_FILE_OPEN, 0);
+        }
+
+        *cp = '/';
+
+        if (fd == NGX_INVALID_FILE) {
+            of->err = ngx_errno;
+            of->failed = ngx_openat_file_n;
+            goto failed;
+        }
+
+        if (at_fd != NGX_AT_FDCWD && ngx_close_file(at_fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                          ngx_close_file_n " \"%V\" failed", &at_name);
+        }
+
+        p = cp + 1;
+        at_fd = fd;
+        at_name.len = cp - at_name.data;
+    }
+
+    if (p == end) {
+
+        /*
+         * If pathname ends with a trailing slash, assume the last path
+         * component is a directory and reopen it with requested flags;
+         * if not, fail with ENOTDIR as per POSIX.
+         *
+         * We cannot rely on O_DIRECTORY in the loop above to check
+         * that the last path component is a directory because
+         * O_DIRECTORY doesn't work on FreeBSD 8.  Fortunately, by
+         * reopening a directory, we don't depend on it at all.
+         */
+
+        fd = ngx_openat_file(at_fd, ".", mode, create, access);
+        goto done;
+    }
+
+    if (of->disable_symlinks == NGX_DISABLE_SYMLINKS_NOTOWNER
+        && !(create & (NGX_FILE_CREATE_OR_OPEN|NGX_FILE_TRUNCATE)))
+    {
+        fd = ngx_openat_file_owner(at_fd, p, mode, create, access, log);
+
+    } else {
+        fd = ngx_openat_file(at_fd, p, mode|NGX_FILE_NOFOLLOW, create, access);
+    }
+
+done:
+
+    if (fd == NGX_INVALID_FILE) {
+        of->err = ngx_errno;
+        of->failed = ngx_openat_file_n;
+    }
+
+failed:
+
+    if (at_fd != NGX_AT_FDCWD && ngx_close_file(at_fd) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      ngx_close_file_n " \"%V\" failed", &at_name);
+    }
+
+    return fd;
+#endif
+}
+
+
+static ngx_int_t
+ngx_file_info_wrapper(ngx_str_t *name, ngx_open_file_info_t *of,
+    ngx_file_info_t *fi, ngx_log_t *log)
+{
+    ngx_int_t  rc;
+
+#if !(NGX_HAVE_OPENAT)
+
+    rc = ngx_file_info(name->data, fi);
+
+    if (rc == NGX_FILE_ERROR) {
+        of->err = ngx_errno;
+        of->failed = ngx_file_info_n;
+        return NGX_FILE_ERROR;
+    }
+
+    return rc;
+
+#else
+
+    ngx_fd_t  fd;
+
+    if (of->disable_symlinks == NGX_DISABLE_SYMLINKS_OFF) {
+
+        rc = ngx_file_info(name->data, fi);
+
+        if (rc == NGX_FILE_ERROR) {
+            of->err = ngx_errno;
+            of->failed = ngx_file_info_n;
+            return NGX_FILE_ERROR;
+        }
+
+        return rc;
+    }
+
+    fd = ngx_open_file_wrapper(name, of, NGX_FILE_RDONLY|NGX_FILE_NONBLOCK,
+                               NGX_FILE_OPEN, 0, log);
+
+    if (fd == NGX_INVALID_FILE) {
+        return NGX_FILE_ERROR;
+    }
+
+    rc = ngx_fd_info(fd, fi);
+
+    if (rc == NGX_FILE_ERROR) {
+        of->err = ngx_errno;
+        of->failed = ngx_fd_info_n;
+    }
+
+    if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      ngx_close_file_n " \"%V\" failed", name);
+    }
+
+    return rc;
+#endif
+}
+
+
+static ngx_int_t
+ngx_open_and_stat_file(ngx_str_t *name, ngx_open_file_info_t *of,
+    ngx_log_t *log)
+{
+    ngx_fd_t         fd;
+    ngx_file_info_t  fi;
+
+    if (of->fd != NGX_INVALID_FILE) {
+
+        if (ngx_file_info_wrapper(name, of, &fi, log) == NGX_FILE_ERROR) {
+            of->fd = NGX_INVALID_FILE;
+            return NGX_ERROR;
+        }
+
+        if (of->uniq == ngx_file_uniq(&fi)) {
+            goto done;
+        }
+
+    } else if (of->test_dir) {
+
+        if (ngx_file_info_wrapper(name, of, &fi, log) == NGX_FILE_ERROR) {
+            of->fd = NGX_INVALID_FILE;
+            return NGX_ERROR;
+        }
+
+        if (ngx_is_dir(&fi)) {
+            goto done;
+        }
+    }
+
+    if (!of->log) {
+
+        /*
+         * Use non-blocking open() not to hang on FIFO files, etc.
+         * This flag has no effect on a regular files.
+         */
+
+        fd = ngx_open_file_wrapper(name, of, NGX_FILE_RDONLY|NGX_FILE_NONBLOCK,
+                                   NGX_FILE_OPEN, 0, log);
+
+    } else {
+        fd = ngx_open_file_wrapper(name, of, NGX_FILE_APPEND,
+                                   NGX_FILE_CREATE_OR_OPEN,
+                                   NGX_FILE_DEFAULT_ACCESS, log);
+    }
+
+    if (fd == NGX_INVALID_FILE) {
+        of->fd = NGX_INVALID_FILE;
+        return NGX_ERROR;
+    }
+
+    if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_CRIT, log, ngx_errno,
+                      ngx_fd_info_n " \"%V\" failed", name);
+
+        if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                          ngx_close_file_n " \"%V\" failed", name);
+        }
+
+        of->fd = NGX_INVALID_FILE;
+
+        return NGX_ERROR;
+    }
+
+    if (ngx_is_dir(&fi)) {
+        if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                          ngx_close_file_n " \"%V\" failed", name);
+        }
+
+        of->fd = NGX_INVALID_FILE;
+
+    } else {
+        of->fd = fd;
+
+        if (of->read_ahead && ngx_file_size(&fi) > NGX_MIN_READ_AHEAD) {
+            if (ngx_read_ahead(fd, of->read_ahead) == NGX_ERROR) {
+                ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                              ngx_read_ahead_n " \"%V\" failed", name);
+            }
+        }
+
+        if (of->directio <= ngx_file_size(&fi)) {
+            if (ngx_directio_on(fd) == NGX_FILE_ERROR) {
+                ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                              ngx_directio_on_n " \"%V\" failed", name);
+
+            } else {
+                of->is_directio = 1;
+            }
+        }
+    }
+
+done:
+
+    of->uniq = ngx_file_uniq(&fi);
+    of->mtime = ngx_file_mtime(&fi);
+    of->size = ngx_file_size(&fi);
+    of->fs_size = ngx_file_fs_size(&fi);
+    of->is_dir = ngx_is_dir(&fi);
+    of->is_file = ngx_is_file(&fi);
+    of->is_link = ngx_is_link(&fi);
+    of->is_exec = ngx_is_exec(&fi);
+
+    return NGX_OK;
+}
+
+
+/*
+ * we ignore any possible event setting error and
+ * fallback to usual periodic file retests
+ */
+
+static void
+ngx_open_file_add_event(ngx_open_file_cache_t *cache,
+    ngx_cached_open_file_t *file, ngx_open_file_info_t *of, ngx_log_t *log)
+{
+    ngx_open_file_cache_event_t  *fev;
+
+    if (!(ngx_event_flags & NGX_USE_VNODE_EVENT)
+        || !of->events
+        || file->event
+        || of->fd == NGX_INVALID_FILE
+        || file->uses < of->min_uses)
+    {
+        return;
+    }
+
+    file->use_event = 0;
+
+    file->event = ngx_calloc(sizeof(ngx_event_t), log);
+    if (file->event== NULL) {
+        return;
+    }
+
+    fev = ngx_alloc(sizeof(ngx_open_file_cache_event_t), log);
+    if (fev == NULL) {
+        ngx_free(file->event);
+        file->event = NULL;
+        return;
+    }
+
+    fev->fd = of->fd;
+    fev->file = file;
+    fev->cache = cache;
+
+    file->event->handler = ngx_open_file_cache_remove;
+    file->event->data = fev;
+
+    /*
+     * although vnode event may be called while ngx_cycle->poll
+     * destruction, however, cleanup procedures are run before any
+     * memory freeing and events will be canceled.
+     */
+
+    file->event->log = ngx_cycle->log;
+
+    if (ngx_add_event(file->event, NGX_VNODE_EVENT, NGX_ONESHOT_EVENT)
+        != NGX_OK)
+    {
+        ngx_free(file->event->data);
+        ngx_free(file->event);
+        file->event = NULL;
+        return;
+    }
+
+    /*
+     * we do not set file->use_event here because there may be a race
+     * condition: a file may be deleted between opening the file and
+     * adding event, so we rely upon event notification only after
+     * one file revalidation on next file access
+     */
+
+    return;
+}
+
+
+static void
+ngx_open_file_cleanup(void *data)
+{
+    ngx_open_file_cache_cleanup_t  *c = data;
+
+    c->file->count--;
+
+    ngx_close_cached_file(c->cache, c->file, c->min_uses, c->log);
+
+    /* drop one or two expired open files */
+    ngx_expire_old_cached_files(c->cache, 1, c->log);
+}
+
+
+static void
+ngx_close_cached_file(ngx_open_file_cache_t *cache,
+    ngx_cached_open_file_t *file, ngx_uint_t min_uses, ngx_log_t *log)
+{
+    ngx_log_debug5(NGX_LOG_DEBUG_CORE, log, 0,
+                   "close cached open file: %s, fd:%d, c:%d, u:%d, %d",
+                   file->name, file->fd, file->count, file->uses, file->close);
+
+    if (!file->close) {
+
+        file->accessed = ngx_time();
+
+        ngx_queue_remove(&file->queue);
+
+        ngx_queue_insert_head(&cache->expire_queue, &file->queue);
+
+        if (file->uses >= min_uses || file->count) {
+            return;
+        }
+    }
+
+    ngx_open_file_del_event(file);
+
+    if (file->count) {
+        return;
+    }
+
+    if (file->fd != NGX_INVALID_FILE) {
+
+        if (ngx_close_file(file->fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                          ngx_close_file_n " \"%s\" failed", file->name);
+        }
+
+        file->fd = NGX_INVALID_FILE;
+    }
+
+    if (!file->close) {
+        return;
+    }
+
+    ngx_free(file->name);
+    ngx_free(file);
+}
+
+
+static void
+ngx_open_file_del_event(ngx_cached_open_file_t *file)
+{
+    if (file->event == NULL) {
+        return;
+    }
+
+    (void) ngx_del_event(file->event, NGX_VNODE_EVENT,
+                         file->count ? NGX_FLUSH_EVENT : NGX_CLOSE_EVENT);
+
+    ngx_free(file->event->data);
+    ngx_free(file->event);
+    file->event = NULL;
+    file->use_event = 0;
+}
+
+
+static void
+ngx_expire_old_cached_files(ngx_open_file_cache_t *cache, ngx_uint_t n,
+    ngx_log_t *log)
+{
+    time_t                   now;
+    ngx_queue_t             *q;
+    ngx_cached_open_file_t  *file;
+
+    now = ngx_time();
+
+    /*
+     * n == 1 deletes one or two inactive files
+     * n == 0 deletes least recently used file by force
+     *        and one or two inactive files
+     */
+
+    while (n < 3) {
+
+        if (ngx_queue_empty(&cache->expire_queue)) {
+            return;
+        }
+
+        q = ngx_queue_last(&cache->expire_queue);
+
+        file = ngx_queue_data(q, ngx_cached_open_file_t, queue);
+
+        if (n++ != 0 && now - file->accessed <= cache->inactive) {
+            return;
+        }
+
+        ngx_queue_remove(q);
+
+        ngx_rbtree_delete(&cache->rbtree, &file->node);
+
+        cache->current--;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0,
+                       "expire cached open file: %s", file->name);
+
+        if (!file->err && !file->is_dir) {
+            file->close = 1;
+            ngx_close_cached_file(cache, file, 0, log);
+
+        } else {
+            ngx_free(file->name);
+            ngx_free(file);
+        }
+    }
+}
+
+
+static void
+ngx_open_file_cache_rbtree_insert_value(ngx_rbtree_node_t *temp,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
+{
+    ngx_rbtree_node_t       **p;
+    ngx_cached_open_file_t    *file, *file_temp;
+
+    for ( ;; ) {
+
+        if (node->key < temp->key) {
+
+            p = &temp->left;
+
+        } else if (node->key > temp->key) {
+
+            p = &temp->right;
+
+        } else { /* node->key == temp->key */
+
+            file = (ngx_cached_open_file_t *) node;
+            file_temp = (ngx_cached_open_file_t *) temp;
+
+            p = (ngx_strcmp(file->name, file_temp->name) < 0)
+                    ? &temp->left : &temp->right;
+        }
+
+        if (*p == sentinel) {
+            break;
+        }
+
+        temp = *p;
+    }
+
+    *p = node;
+    node->parent = temp;
+    node->left = sentinel;
+    node->right = sentinel;
+    ngx_rbt_red(node);
+}
+
+
+static ngx_cached_open_file_t *
+ngx_open_file_lookup(ngx_open_file_cache_t *cache, ngx_str_t *name,
+    uint32_t hash)
+{
+    ngx_int_t                rc;
+    ngx_rbtree_node_t       *node, *sentinel;
+    ngx_cached_open_file_t  *file;
+
+    node = cache->rbtree.root;
+    sentinel = cache->rbtree.sentinel;
+
+    while (node != sentinel) {
+
+        if (hash < node->key) {
+            node = node->left;
+            continue;
+        }
+
+        if (hash > node->key) {
+            node = node->right;
+            continue;
+        }
+
+        /* hash == node->key */
+
+        file = (ngx_cached_open_file_t *) node;
+
+        rc = ngx_strcmp(name->data, file->name);
+
+        if (rc == 0) {
+            return file;
+        }
+
+        node = (rc < 0) ? node->left : node->right;
+    }
+
+    return NULL;
+}
+
+
+static void
+ngx_open_file_cache_remove(ngx_event_t *ev)
+{
+    ngx_cached_open_file_t       *file;
+    ngx_open_file_cache_event_t  *fev;
+
+    fev = ev->data;
+    file = fev->file;
+
+    ngx_queue_remove(&file->queue);
+
+    ngx_rbtree_delete(&fev->cache->rbtree, &file->node);
+
+    fev->cache->current--;
+
+    /* NGX_ONESHOT_EVENT was already deleted */
+    file->event = NULL;
+    file->use_event = 0;
+
+    file->close = 1;
+
+    ngx_close_cached_file(fev->cache, file, 0, ev->log);
+
+    /* free memory only when fev->cache and fev->file are already not needed */
+
+    ngx_free(ev->data);
+    ngx_free(ev);
+}
diff --git a/nginx/src/core/ngx_open_file_cache.h b/nginx/src/core/ngx_open_file_cache.h
new file mode 100644 (file)
index 0000000..d119c12
--- /dev/null
@@ -0,0 +1,129 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#ifndef _NGX_OPEN_FILE_CACHE_H_INCLUDED_
+#define _NGX_OPEN_FILE_CACHE_H_INCLUDED_
+
+
+#define NGX_OPEN_FILE_DIRECTIO_OFF  NGX_MAX_OFF_T_VALUE
+
+
+typedef struct {
+    ngx_fd_t                 fd;
+    ngx_file_uniq_t          uniq;
+    time_t                   mtime;
+    off_t                    size;
+    off_t                    fs_size;
+    off_t                    directio;
+    size_t                   read_ahead;
+
+    ngx_err_t                err;
+    char                    *failed;
+
+    time_t                   valid;
+
+    ngx_uint_t               min_uses;
+
+#if (NGX_HAVE_OPENAT)
+    size_t                   disable_symlinks_from;
+    unsigned                 disable_symlinks:2;
+#endif
+
+    unsigned                 test_dir:1;
+    unsigned                 test_only:1;
+    unsigned                 log:1;
+    unsigned                 errors:1;
+    unsigned                 events:1;
+
+    unsigned                 is_dir:1;
+    unsigned                 is_file:1;
+    unsigned                 is_link:1;
+    unsigned                 is_exec:1;
+    unsigned                 is_directio:1;
+} ngx_open_file_info_t;
+
+
+typedef struct ngx_cached_open_file_s  ngx_cached_open_file_t;
+
+struct ngx_cached_open_file_s {
+    ngx_rbtree_node_t        node;
+    ngx_queue_t              queue;
+
+    u_char                  *name;
+    time_t                   created;
+    time_t                   accessed;
+
+    ngx_fd_t                 fd;
+    ngx_file_uniq_t          uniq;
+    time_t                   mtime;
+    off_t                    size;
+    ngx_err_t                err;
+
+    uint32_t                 uses;
+
+#if (NGX_HAVE_OPENAT)
+    size_t                   disable_symlinks_from;
+    unsigned                 disable_symlinks:2;
+#endif
+
+    unsigned                 count:24;
+    unsigned                 close:1;
+    unsigned                 use_event:1;
+
+    unsigned                 is_dir:1;
+    unsigned                 is_file:1;
+    unsigned                 is_link:1;
+    unsigned                 is_exec:1;
+    unsigned                 is_directio:1;
+
+    ngx_event_t             *event;
+};
+
+
+typedef struct {
+    ngx_rbtree_t             rbtree;
+    ngx_rbtree_node_t        sentinel;
+    ngx_queue_t              expire_queue;
+
+    ngx_uint_t               current;
+    ngx_uint_t               max;
+    time_t                   inactive;
+} ngx_open_file_cache_t;
+
+
+typedef struct {
+    ngx_open_file_cache_t   *cache;
+    ngx_cached_open_file_t  *file;
+    ngx_uint_t               min_uses;
+    ngx_log_t               *log;
+} ngx_open_file_cache_cleanup_t;
+
+
+typedef struct {
+
+    /* ngx_connection_t stub to allow use c->fd as event ident */
+    void                    *data;
+    ngx_event_t             *read;
+    ngx_event_t             *write;
+    ngx_fd_t                 fd;
+
+    ngx_cached_open_file_t  *file;
+    ngx_open_file_cache_t   *cache;
+} ngx_open_file_cache_event_t;
+
+
+ngx_open_file_cache_t *ngx_open_file_cache_init(ngx_pool_t *pool,
+    ngx_uint_t max, time_t inactive);
+ngx_int_t ngx_open_cached_file(ngx_open_file_cache_t *cache, ngx_str_t *name,
+    ngx_open_file_info_t *of, ngx_pool_t *pool);
+
+
+#endif /* _NGX_OPEN_FILE_CACHE_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_output_chain.c b/nginx/src/core/ngx_output_chain.c
new file mode 100644 (file)
index 0000000..7f5dc78
--- /dev/null
@@ -0,0 +1,767 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if 0
+#define NGX_SENDFILE_LIMIT  4096
+#endif
+
+/*
+ * When DIRECTIO is enabled FreeBSD, Solaris, and MacOSX read directly
+ * to an application memory from a device if parameters are aligned
+ * to device sector boundary (512 bytes).  They fallback to usual read
+ * operation if the parameters are not aligned.
+ * Linux allows DIRECTIO only if the parameters are aligned to a filesystem
+ * sector boundary, otherwise it returns EINVAL.  The sector size is
+ * usually 512 bytes, however, on XFS it may be 4096 bytes.
+ */
+
+#define NGX_NONE            1
+
+
+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,
+    off_t bsize);
+static ngx_int_t ngx_output_chain_get_buf(ngx_output_chain_ctx_t *ctx,
+    off_t bsize);
+static ngx_int_t ngx_output_chain_copy_buf(ngx_output_chain_ctx_t *ctx);
+
+
+ngx_int_t
+ngx_output_chain(ngx_output_chain_ctx_t *ctx, ngx_chain_t *in)
+{
+    off_t         bsize;
+    ngx_int_t     rc, last;
+    ngx_chain_t  *cl, *out, **last_out;
+
+    if (ctx->in == NULL && ctx->busy == NULL
+#if (NGX_HAVE_FILE_AIO || NGX_THREADS)
+        && !ctx->aio
+#endif
+       )
+    {
+        /*
+         * the short path for the case when the ctx->in and ctx->busy chains
+         * are empty, the incoming chain is empty too or has the single buf
+         * that does not require the copy
+         */
+
+        if (in == NULL) {
+            return ctx->output_filter(ctx->filter_ctx, in);
+        }
+
+        if (in->next == NULL
+#if (NGX_SENDFILE_LIMIT)
+            && !(in->buf->in_file && in->buf->file_last > NGX_SENDFILE_LIMIT)
+#endif
+            && ngx_output_chain_as_is(ctx, in->buf))
+        {
+            return ctx->output_filter(ctx->filter_ctx, in);
+        }
+    }
+
+    /* add the incoming buf to the chain ctx->in */
+
+    if (in) {
+        if (ngx_output_chain_add_copy(ctx->pool, &ctx->in, in) == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+    }
+
+    out = NULL;
+    last_out = &out;
+    last = NGX_NONE;
+
+    for ( ;; ) {
+
+#if (NGX_HAVE_FILE_AIO || NGX_THREADS)
+        if (ctx->aio) {
+            return NGX_AGAIN;
+        }
+#endif
+
+        while (ctx->in) {
+
+            /*
+             * cycle while there are the ctx->in bufs
+             * and there are the free output bufs to copy in
+             */
+
+            bsize = ngx_buf_size(ctx->in->buf);
+
+            if (bsize == 0 && !ngx_buf_special(ctx->in->buf)) {
+
+                ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, 0,
+                              "zero size buf in output "
+                              "t:%d r:%d f:%d %p %p-%p %p %O-%O",
+                              ctx->in->buf->temporary,
+                              ctx->in->buf->recycled,
+                              ctx->in->buf->in_file,
+                              ctx->in->buf->start,
+                              ctx->in->buf->pos,
+                              ctx->in->buf->last,
+                              ctx->in->buf->file,
+                              ctx->in->buf->file_pos,
+                              ctx->in->buf->file_last);
+
+                ngx_debug_point();
+
+                ctx->in = ctx->in->next;
+
+                continue;
+            }
+
+            if (ngx_output_chain_as_is(ctx, ctx->in->buf)) {
+
+                /* move the chain link to the output chain */
+
+                cl = ctx->in;
+                ctx->in = cl->next;
+
+                *last_out = cl;
+                last_out = &cl->next;
+                cl->next = NULL;
+
+                continue;
+            }
+
+            if (ctx->buf == NULL) {
+
+                rc = ngx_output_chain_align_file_buf(ctx, bsize);
+
+                if (rc == NGX_ERROR) {
+                    return NGX_ERROR;
+                }
+
+                if (rc != NGX_OK) {
+
+                    if (ctx->free) {
+
+                        /* get the free buf */
+
+                        cl = ctx->free;
+                        ctx->buf = cl->buf;
+                        ctx->free = cl->next;
+
+                        ngx_free_chain(ctx->pool, cl);
+
+                    } else if (out || ctx->allocated == ctx->bufs.num) {
+
+                        break;
+
+                    } else if (ngx_output_chain_get_buf(ctx, bsize) != NGX_OK) {
+                        return NGX_ERROR;
+                    }
+                }
+            }
+
+            rc = ngx_output_chain_copy_buf(ctx);
+
+            if (rc == NGX_ERROR) {
+                return rc;
+            }
+
+            if (rc == NGX_AGAIN) {
+                if (out) {
+                    break;
+                }
+
+                return rc;
+            }
+
+            /* delete the completed buf from the ctx->in chain */
+
+            if (ngx_buf_size(ctx->in->buf) == 0) {
+                ctx->in = ctx->in->next;
+            }
+
+            cl = ngx_alloc_chain_link(ctx->pool);
+            if (cl == NULL) {
+                return NGX_ERROR;
+            }
+
+            cl->buf = ctx->buf;
+            cl->next = NULL;
+            *last_out = cl;
+            last_out = &cl->next;
+            ctx->buf = NULL;
+        }
+
+        if (out == NULL && last != NGX_NONE) {
+
+            if (ctx->in) {
+                return NGX_AGAIN;
+            }
+
+            return last;
+        }
+
+        last = ctx->output_filter(ctx->filter_ctx, out);
+
+        if (last == NGX_ERROR || last == NGX_DONE) {
+            return last;
+        }
+
+        ngx_chain_update_chains(ctx->pool, &ctx->free, &ctx->busy, &out,
+                                ctx->tag);
+        last_out = &out;
+    }
+}
+
+
+static ngx_inline ngx_int_t
+ngx_output_chain_as_is(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf)
+{
+    ngx_uint_t  sendfile;
+
+    if (ngx_buf_special(buf)) {
+        return 1;
+    }
+
+#if (NGX_THREADS)
+    if (buf->in_file) {
+        buf->file->thread_handler = ctx->thread_handler;
+        buf->file->thread_ctx = ctx->filter_ctx;
+    }
+#endif
+
+    if (buf->in_file && buf->file->directio) {
+        return 0;
+    }
+
+    sendfile = ctx->sendfile;
+
+#if (NGX_SENDFILE_LIMIT)
+
+    if (buf->in_file && buf->file_pos >= NGX_SENDFILE_LIMIT) {
+        sendfile = 0;
+    }
+
+#endif
+
+    if (!sendfile) {
+
+        if (!ngx_buf_in_memory(buf)) {
+            return 0;
+        }
+
+        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;
+    }
+
+    if (ctx->need_in_temp && (buf->memory || buf->mmap)) {
+        return 0;
+    }
+
+    return 1;
+}
+
+
+#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)
+{
+    ngx_chain_t  *cl, **ll;
+#if (NGX_SENDFILE_LIMIT)
+    ngx_buf_t    *b, *buf;
+#endif
+
+    ll = chain;
+
+    for (cl = *chain; cl; cl = cl->next) {
+        ll = &cl->next;
+    }
+
+    while (in) {
+
+        cl = ngx_alloc_chain_link(pool);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+#if (NGX_SENDFILE_LIMIT)
+
+        buf = in->buf;
+
+        if (buf->in_file
+            && buf->file_pos < NGX_SENDFILE_LIMIT
+            && buf->file_last > NGX_SENDFILE_LIMIT)
+        {
+            /* split a file buf on two bufs by the sendfile limit */
+
+            b = ngx_calloc_buf(pool);
+            if (b == NULL) {
+                return NGX_ERROR;
+            }
+
+            ngx_memcpy(b, buf, sizeof(ngx_buf_t));
+
+            if (ngx_buf_in_memory(buf)) {
+                buf->pos += (ssize_t) (NGX_SENDFILE_LIMIT - buf->file_pos);
+                b->last = buf->pos;
+            }
+
+            buf->file_pos = NGX_SENDFILE_LIMIT;
+            b->file_last = NGX_SENDFILE_LIMIT;
+
+            cl->buf = b;
+
+        } else {
+            cl->buf = buf;
+            in = in->next;
+        }
+
+#else
+        cl->buf = in->buf;
+        in = in->next;
+
+#endif
+
+        cl->next = NULL;
+        *ll = cl;
+        ll = &cl->next;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_output_chain_align_file_buf(ngx_output_chain_ctx_t *ctx, off_t bsize)
+{
+    size_t      size;
+    ngx_buf_t  *in;
+
+    in = ctx->in->buf;
+
+    if (in->file == NULL || !in->file->directio) {
+        return NGX_DECLINED;
+    }
+
+    ctx->directio = 1;
+
+    size = (size_t) (in->file_pos - (in->file_pos & ~(ctx->alignment - 1)));
+
+    if (size == 0) {
+
+        if (bsize >= (off_t) ctx->bufs.size) {
+            return NGX_DECLINED;
+        }
+
+        size = (size_t) bsize;
+
+    } else {
+        size = (size_t) ctx->alignment - size;
+
+        if ((off_t) size > bsize) {
+            size = (size_t) bsize;
+        }
+    }
+
+    ctx->buf = ngx_create_temp_buf(ctx->pool, size);
+    if (ctx->buf == NULL) {
+        return NGX_ERROR;
+    }
+
+    /*
+     * we do not set ctx->buf->tag, because we do not want
+     * to reuse the buf via ctx->free list
+     */
+
+#if (NGX_HAVE_ALIGNED_DIRECTIO)
+    ctx->unaligned = 1;
+#endif
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_output_chain_get_buf(ngx_output_chain_ctx_t *ctx, off_t bsize)
+{
+    size_t       size;
+    ngx_buf_t   *b, *in;
+    ngx_uint_t   recycled;
+
+    in = ctx->in->buf;
+    size = ctx->bufs.size;
+    recycled = 1;
+
+    if (in->last_in_chain) {
+
+        if (bsize < (off_t) size) {
+
+            /*
+             * allocate a small temp buf for a small last buf
+             * or its small last part
+             */
+
+            size = (size_t) bsize;
+            recycled = 0;
+
+        } else if (!ctx->directio
+                   && ctx->bufs.num == 1
+                   && (bsize < (off_t) (size + size / 4)))
+        {
+            /*
+             * allocate a temp buf that equals to a last buf,
+             * if there is no directio, the last buf size is lesser
+             * than 1.25 of bufs.size and the temp buf is single
+             */
+
+            size = (size_t) bsize;
+            recycled = 0;
+        }
+    }
+
+    b = ngx_calloc_buf(ctx->pool);
+    if (b == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (ctx->directio) {
+
+        /*
+         * allocate block aligned to a disk sector size to enable
+         * userland buffer direct usage conjunctly with directio
+         */
+
+        b->start = ngx_pmemalign(ctx->pool, size, (size_t) ctx->alignment);
+        if (b->start == NULL) {
+            return NGX_ERROR;
+        }
+
+    } else {
+        b->start = ngx_palloc(ctx->pool, size);
+        if (b->start == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    b->pos = b->start;
+    b->last = b->start;
+    b->end = b->last + size;
+    b->temporary = 1;
+    b->tag = ctx->tag;
+    b->recycled = recycled;
+
+    ctx->buf = b;
+    ctx->allocated++;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_output_chain_copy_buf(ngx_output_chain_ctx_t *ctx)
+{
+    off_t        size;
+    ssize_t      n;
+    ngx_buf_t   *src, *dst;
+    ngx_uint_t   sendfile;
+
+    src = ctx->in->buf;
+    dst = ctx->buf;
+
+    size = ngx_buf_size(src);
+    size = ngx_min(size, dst->end - dst->pos);
+
+    sendfile = ctx->sendfile && !ctx->directio;
+
+#if (NGX_SENDFILE_LIMIT)
+
+    if (src->in_file && src->file_pos >= NGX_SENDFILE_LIMIT) {
+        sendfile = 0;
+    }
+
+#endif
+
+    if (ngx_buf_in_memory(src)) {
+        ngx_memcpy(dst->pos, src->pos, (size_t) size);
+        src->pos += (size_t) size;
+        dst->last += (size_t) size;
+
+        if (src->in_file) {
+
+            if (sendfile) {
+                dst->in_file = 1;
+                dst->file = src->file;
+                dst->file_pos = src->file_pos;
+                dst->file_last = src->file_pos + size;
+
+            } else {
+                dst->in_file = 0;
+            }
+
+            src->file_pos += size;
+
+        } else {
+            dst->in_file = 0;
+        }
+
+        if (src->pos == src->last) {
+            dst->flush = src->flush;
+            dst->last_buf = src->last_buf;
+            dst->last_in_chain = src->last_in_chain;
+        }
+
+    } else {
+
+#if (NGX_HAVE_ALIGNED_DIRECTIO)
+
+        if (ctx->unaligned) {
+            if (ngx_directio_off(src->file->fd) == NGX_FILE_ERROR) {
+                ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, ngx_errno,
+                              ngx_directio_off_n " \"%s\" failed",
+                              src->file->name.data);
+            }
+        }
+
+#endif
+
+#if (NGX_HAVE_FILE_AIO)
+        if (ctx->aio_handler) {
+            n = ngx_file_aio_read(src->file, dst->pos, (size_t) size,
+                                  src->file_pos, ctx->pool);
+            if (n == NGX_AGAIN) {
+                ctx->aio_handler(ctx, src->file);
+                return NGX_AGAIN;
+            }
+
+        } else
+#endif
+#if (NGX_THREADS)
+        if (ctx->thread_handler) {
+            src->file->thread_task = ctx->thread_task;
+            src->file->thread_handler = ctx->thread_handler;
+            src->file->thread_ctx = ctx->filter_ctx;
+
+            n = ngx_thread_read(src->file, dst->pos, (size_t) size,
+                                src->file_pos, ctx->pool);
+            if (n == NGX_AGAIN) {
+                ctx->thread_task = src->file->thread_task;
+                return NGX_AGAIN;
+            }
+
+        } else
+#endif
+        {
+            n = ngx_read_file(src->file, dst->pos, (size_t) size,
+                              src->file_pos);
+        }
+
+#if (NGX_HAVE_ALIGNED_DIRECTIO)
+
+        if (ctx->unaligned) {
+            ngx_err_t  err;
+
+            err = ngx_errno;
+
+            if (ngx_directio_on(src->file->fd) == NGX_FILE_ERROR) {
+                ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, ngx_errno,
+                              ngx_directio_on_n " \"%s\" failed",
+                              src->file->name.data);
+            }
+
+            ngx_set_errno(err);
+
+            ctx->unaligned = 0;
+        }
+
+#endif
+
+        if (n == NGX_ERROR) {
+            return (ngx_int_t) n;
+        }
+
+        if (n != size) {
+            ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, 0,
+                          ngx_read_file_n " read only %z of %O from \"%s\"",
+                          n, size, src->file->name.data);
+            return NGX_ERROR;
+        }
+
+        dst->last += n;
+
+        if (sendfile) {
+            dst->in_file = 1;
+            dst->file = src->file;
+            dst->file_pos = src->file_pos;
+            dst->file_last = src->file_pos + n;
+
+        } else {
+            dst->in_file = 0;
+        }
+
+        src->file_pos += n;
+
+        if (src->file_pos == src->file_last) {
+            dst->flush = src->flush;
+            dst->last_buf = src->last_buf;
+            dst->last_in_chain = src->last_in_chain;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_chain_writer(void *data, ngx_chain_t *in)
+{
+    ngx_chain_writer_ctx_t *ctx = data;
+
+    off_t              size;
+    ngx_chain_t       *cl, *ln, *chain;
+    ngx_connection_t  *c;
+
+    c = ctx->connection;
+
+    for (size = 0; in; in = in->next) {
+
+#if 1
+        if (ngx_buf_size(in->buf) == 0 && !ngx_buf_special(in->buf)) {
+
+            ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, 0,
+                          "zero size buf in chain writer "
+                          "t:%d r:%d f:%d %p %p-%p %p %O-%O",
+                          in->buf->temporary,
+                          in->buf->recycled,
+                          in->buf->in_file,
+                          in->buf->start,
+                          in->buf->pos,
+                          in->buf->last,
+                          in->buf->file,
+                          in->buf->file_pos,
+                          in->buf->file_last);
+
+            ngx_debug_point();
+
+            continue;
+        }
+#endif
+
+        size += ngx_buf_size(in->buf);
+
+        ngx_log_debug2(NGX_LOG_DEBUG_CORE, c->log, 0,
+                       "chain writer buf fl:%d s:%uO",
+                       in->buf->flush, ngx_buf_size(in->buf));
+
+        cl = ngx_alloc_chain_link(ctx->pool);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+        cl->buf = in->buf;
+        cl->next = NULL;
+        *ctx->last = cl;
+        ctx->last = &cl->next;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
+                   "chain writer in: %p", ctx->out);
+
+    for (cl = ctx->out; cl; cl = cl->next) {
+
+#if 1
+        if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) {
+
+            ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, 0,
+                          "zero size buf in chain writer "
+                          "t:%d r:%d f:%d %p %p-%p %p %O-%O",
+                          cl->buf->temporary,
+                          cl->buf->recycled,
+                          cl->buf->in_file,
+                          cl->buf->start,
+                          cl->buf->pos,
+                          cl->buf->last,
+                          cl->buf->file,
+                          cl->buf->file_pos,
+                          cl->buf->file_last);
+
+            ngx_debug_point();
+
+            continue;
+        }
+#endif
+
+        size += ngx_buf_size(cl->buf);
+    }
+
+    if (size == 0 && !c->buffered) {
+        return NGX_OK;
+    }
+
+    chain = c->send_chain(c, ctx->out, ctx->limit);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
+                   "chain writer out: %p", chain);
+
+    if (chain == NGX_CHAIN_ERROR) {
+        return NGX_ERROR;
+    }
+
+    for (cl = ctx->out; cl && cl != chain; /* void */) {
+        ln = cl;
+        cl = cl->next;
+        ngx_free_chain(ctx->pool, ln);
+    }
+
+    ctx->out = chain;
+
+    if (ctx->out == NULL) {
+        ctx->last = &ctx->out;
+
+        if (!c->buffered) {
+            return NGX_OK;
+        }
+    }
+
+    return NGX_AGAIN;
+}
diff --git a/nginx/src/core/ngx_palloc.c b/nginx/src/core/ngx_palloc.c
new file mode 100644 (file)
index 0000000..d3044ac
--- /dev/null
@@ -0,0 +1,430 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+static ngx_inline void *ngx_palloc_small(ngx_pool_t *pool, size_t size,
+    ngx_uint_t align);
+static void *ngx_palloc_block(ngx_pool_t *pool, size_t size);
+static void *ngx_palloc_large(ngx_pool_t *pool, size_t size);
+
+
+ngx_pool_t *
+ngx_create_pool(size_t size, ngx_log_t *log)
+{
+    ngx_pool_t  *p;
+
+    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
+    if (p == NULL) {
+        return NULL;
+    }
+
+    p->d.last = (u_char *) p + sizeof(ngx_pool_t);
+    p->d.end = (u_char *) p + size;
+    p->d.next = NULL;
+    p->d.failed = 0;
+
+    size = size - sizeof(ngx_pool_t);
+    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
+
+    p->current = p;
+    p->chain = NULL;
+    p->large = NULL;
+    p->cleanup = NULL;
+    p->log = log;
+
+    return p;
+}
+
+
+void
+ngx_destroy_pool(ngx_pool_t *pool)
+{
+    ngx_pool_t          *p, *n;
+    ngx_pool_large_t    *l;
+    ngx_pool_cleanup_t  *c;
+
+    for (c = pool->cleanup; c; c = c->next) {
+        if (c->handler) {
+            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
+                           "run cleanup: %p", c);
+            c->handler(c->data);
+        }
+    }
+
+#if (NGX_DEBUG)
+
+    /*
+     * we could allocate the pool->log from this pool
+     * so we cannot use this log while free()ing the pool
+     */
+
+    for (l = pool->large; l; l = l->next) {
+        ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
+    }
+
+    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
+        ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
+                       "free: %p, unused: %uz", p, p->d.end - p->d.last);
+
+        if (n == NULL) {
+            break;
+        }
+    }
+
+#endif
+
+    for (l = pool->large; l; l = l->next) {
+        if (l->alloc) {
+            ngx_free(l->alloc);
+        }
+    }
+
+    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
+        ngx_free(p);
+
+        if (n == NULL) {
+            break;
+        }
+    }
+}
+
+
+void
+ngx_reset_pool(ngx_pool_t *pool)
+{
+    ngx_pool_t        *p;
+    ngx_pool_large_t  *l;
+
+    for (l = pool->large; l; l = l->next) {
+        if (l->alloc) {
+            ngx_free(l->alloc);
+        }
+    }
+
+    for (p = pool; p; p = p->d.next) {
+        p->d.last = (u_char *) p + sizeof(ngx_pool_t);
+        p->d.failed = 0;
+    }
+
+    pool->current = pool;
+    pool->chain = NULL;
+    pool->large = NULL;
+}
+
+
+void *
+ngx_palloc(ngx_pool_t *pool, size_t size)
+{
+#if !(NGX_DEBUG_PALLOC)
+    if (size <= pool->max) {
+        return ngx_palloc_small(pool, size, 1);
+    }
+#endif
+
+    return ngx_palloc_large(pool, size);
+}
+
+
+void *
+ngx_pnalloc(ngx_pool_t *pool, size_t size)
+{
+#if !(NGX_DEBUG_PALLOC)
+    if (size <= pool->max) {
+        return ngx_palloc_small(pool, size, 0);
+    }
+#endif
+
+    return ngx_palloc_large(pool, size);
+}
+
+
+static ngx_inline void *
+ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
+{
+    u_char      *m;
+    ngx_pool_t  *p;
+
+    p = pool->current;
+
+    do {
+        m = p->d.last;
+
+        if (align) {
+            m = ngx_align_ptr(m, NGX_ALIGNMENT);
+        }
+
+        if ((size_t) (p->d.end - m) >= size) {
+            p->d.last = m + size;
+
+            return m;
+        }
+
+        p = p->d.next;
+
+    } while (p);
+
+    return ngx_palloc_block(pool, size);
+}
+
+
+static void *
+ngx_palloc_block(ngx_pool_t *pool, size_t size)
+{
+    u_char      *m;
+    size_t       psize;
+    ngx_pool_t  *p, *new;
+
+    psize = (size_t) (pool->d.end - (u_char *) pool);
+
+    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
+    if (m == NULL) {
+        return NULL;
+    }
+
+    new = (ngx_pool_t *) m;
+
+    new->d.end = m + psize;
+    new->d.next = NULL;
+    new->d.failed = 0;
+
+    m += sizeof(ngx_pool_data_t);
+    m = ngx_align_ptr(m, NGX_ALIGNMENT);
+    new->d.last = m + size;
+
+    for (p = pool->current; p->d.next; p = p->d.next) {
+        if (p->d.failed++ > 4) {
+            pool->current = p->d.next;
+        }
+    }
+
+    p->d.next = new;
+
+    return m;
+}
+
+
+static void *
+ngx_palloc_large(ngx_pool_t *pool, size_t size)
+{
+    void              *p;
+    ngx_uint_t         n;
+    ngx_pool_large_t  *large;
+
+    p = ngx_alloc(size, pool->log);
+    if (p == NULL) {
+        return NULL;
+    }
+
+    n = 0;
+
+    for (large = pool->large; large; large = large->next) {
+        if (large->alloc == NULL) {
+            large->alloc = p;
+            return p;
+        }
+
+        if (n++ > 3) {
+            break;
+        }
+    }
+
+    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
+    if (large == NULL) {
+        ngx_free(p);
+        return NULL;
+    }
+
+    large->alloc = p;
+    large->next = pool->large;
+    pool->large = large;
+
+    return p;
+}
+
+
+void *
+ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment)
+{
+    void              *p;
+    ngx_pool_large_t  *large;
+
+    p = ngx_memalign(alignment, size, pool->log);
+    if (p == NULL) {
+        return NULL;
+    }
+
+    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
+    if (large == NULL) {
+        ngx_free(p);
+        return NULL;
+    }
+
+    large->alloc = p;
+    large->next = pool->large;
+    pool->large = large;
+
+    return p;
+}
+
+
+ngx_int_t
+ngx_pfree(ngx_pool_t *pool, void *p)
+{
+    ngx_pool_large_t  *l;
+
+    for (l = pool->large; l; l = l->next) {
+        if (p == l->alloc) {
+            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
+                           "free: %p", l->alloc);
+            ngx_free(l->alloc);
+            l->alloc = NULL;
+
+            return NGX_OK;
+        }
+    }
+
+    return NGX_DECLINED;
+}
+
+
+void *
+ngx_pcalloc(ngx_pool_t *pool, size_t size)
+{
+    void *p;
+
+    p = ngx_palloc(pool, size);
+    if (p) {
+        ngx_memzero(p, size);
+    }
+
+    return p;
+}
+
+
+ngx_pool_cleanup_t *
+ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
+{
+    ngx_pool_cleanup_t  *c;
+
+    c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
+    if (c == NULL) {
+        return NULL;
+    }
+
+    if (size) {
+        c->data = ngx_palloc(p, size);
+        if (c->data == NULL) {
+            return NULL;
+        }
+
+    } else {
+        c->data = NULL;
+    }
+
+    c->handler = NULL;
+    c->next = p->cleanup;
+
+    p->cleanup = c;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);
+
+    return c;
+}
+
+
+void
+ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd)
+{
+    ngx_pool_cleanup_t       *c;
+    ngx_pool_cleanup_file_t  *cf;
+
+    for (c = p->cleanup; c; c = c->next) {
+        if (c->handler == ngx_pool_cleanup_file) {
+
+            cf = c->data;
+
+            if (cf->fd == fd) {
+                c->handler(cf);
+                c->handler = NULL;
+                return;
+            }
+        }
+    }
+}
+
+
+void
+ngx_pool_cleanup_file(void *data)
+{
+    ngx_pool_cleanup_file_t  *c = data;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d",
+                   c->fd);
+
+    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+                      ngx_close_file_n " \"%s\" failed", c->name);
+    }
+}
+
+
+void
+ngx_pool_delete_file(void *data)
+{
+    ngx_pool_cleanup_file_t  *c = data;
+
+    ngx_err_t  err;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d %s",
+                   c->fd, c->name);
+
+    if (ngx_delete_file(c->name) == NGX_FILE_ERROR) {
+        err = ngx_errno;
+
+        if (err != NGX_ENOENT) {
+            ngx_log_error(NGX_LOG_CRIT, c->log, err,
+                          ngx_delete_file_n " \"%s\" failed", c->name);
+        }
+    }
+
+    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+                      ngx_close_file_n " \"%s\" failed", c->name);
+    }
+}
+
+
+#if 0
+
+static void *
+ngx_get_cached_block(size_t size)
+{
+    void                     *p;
+    ngx_cached_block_slot_t  *slot;
+
+    if (ngx_cycle->cache == NULL) {
+        return NULL;
+    }
+
+    slot = &ngx_cycle->cache[(size + ngx_pagesize - 1) / ngx_pagesize];
+
+    slot->tries++;
+
+    if (slot->number) {
+        p = slot->block;
+        slot->block = slot->block->next;
+        slot->number--;
+        return p;
+    }
+
+    return NULL;
+}
+
+#endif
diff --git a/nginx/src/core/ngx_palloc.h b/nginx/src/core/ngx_palloc.h
new file mode 100644 (file)
index 0000000..376e012
--- /dev/null
@@ -0,0 +1,92 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_PALLOC_H_INCLUDED_
+#define _NGX_PALLOC_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/*
+ * NGX_MAX_ALLOC_FROM_POOL should be (ngx_pagesize - 1), i.e. 4095 on x86.
+ * On Windows NT it decreases a number of locked pages in a kernel.
+ */
+#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)
+
+#define NGX_DEFAULT_POOL_SIZE    (16 * 1024)
+
+#define NGX_POOL_ALIGNMENT       16
+#define NGX_MIN_POOL_SIZE                                                     \
+    ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)),            \
+              NGX_POOL_ALIGNMENT)
+
+
+typedef void (*ngx_pool_cleanup_pt)(void *data);
+
+typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;
+
+struct ngx_pool_cleanup_s {
+    ngx_pool_cleanup_pt   handler;
+    void                 *data;
+    ngx_pool_cleanup_t   *next;
+};
+
+
+typedef struct ngx_pool_large_s  ngx_pool_large_t;
+
+struct ngx_pool_large_s {
+    ngx_pool_large_t     *next;
+    void                 *alloc;
+};
+
+
+typedef struct {
+    u_char               *last;
+    u_char               *end;
+    ngx_pool_t           *next;
+    ngx_uint_t            failed;
+} ngx_pool_data_t;
+
+
+struct ngx_pool_s {
+    ngx_pool_data_t       d;
+    size_t                max;
+    ngx_pool_t           *current;
+    ngx_chain_t          *chain;
+    ngx_pool_large_t     *large;
+    ngx_pool_cleanup_t   *cleanup;
+    ngx_log_t            *log;
+};
+
+
+typedef struct {
+    ngx_fd_t              fd;
+    u_char               *name;
+    ngx_log_t            *log;
+} ngx_pool_cleanup_file_t;
+
+
+ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);
+void ngx_destroy_pool(ngx_pool_t *pool);
+void ngx_reset_pool(ngx_pool_t *pool);
+
+void *ngx_palloc(ngx_pool_t *pool, size_t size);
+void *ngx_pnalloc(ngx_pool_t *pool, size_t size);
+void *ngx_pcalloc(ngx_pool_t *pool, size_t size);
+void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);
+ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);
+
+
+ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);
+void ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd);
+void ngx_pool_cleanup_file(void *data);
+void ngx_pool_delete_file(void *data);
+
+
+#endif /* _NGX_PALLOC_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_parse.c b/nginx/src/core/ngx_parse.c
new file mode 100644 (file)
index 0000000..d35e60f
--- /dev/null
@@ -0,0 +1,283 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+ssize_t
+ngx_parse_size(ngx_str_t *line)
+{
+    u_char   unit;
+    size_t   len;
+    ssize_t  size, scale, max;
+
+    len = line->len;
+
+    if (len == 0) {
+        return NGX_ERROR;
+    }
+
+    unit = line->data[len - 1];
+
+    switch (unit) {
+    case 'K':
+    case 'k':
+        len--;
+        max = NGX_MAX_SIZE_T_VALUE / 1024;
+        scale = 1024;
+        break;
+
+    case 'M':
+    case 'm':
+        len--;
+        max = NGX_MAX_SIZE_T_VALUE / (1024 * 1024);
+        scale = 1024 * 1024;
+        break;
+
+    default:
+        max = NGX_MAX_SIZE_T_VALUE;
+        scale = 1;
+    }
+
+    size = ngx_atosz(line->data, len);
+    if (size == NGX_ERROR || size > max) {
+        return NGX_ERROR;
+    }
+
+    size *= scale;
+
+    return size;
+}
+
+
+off_t
+ngx_parse_offset(ngx_str_t *line)
+{
+    u_char  unit;
+    off_t   offset, scale, max;
+    size_t  len;
+
+    len = line->len;
+
+    if (len == 0) {
+        return NGX_ERROR;
+    }
+
+    unit = line->data[len - 1];
+
+    switch (unit) {
+    case 'K':
+    case 'k':
+        len--;
+        max = NGX_MAX_OFF_T_VALUE / 1024;
+        scale = 1024;
+        break;
+
+    case 'M':
+    case 'm':
+        len--;
+        max = NGX_MAX_OFF_T_VALUE / (1024 * 1024);
+        scale = 1024 * 1024;
+        break;
+
+    case 'G':
+    case 'g':
+        len--;
+        max = NGX_MAX_OFF_T_VALUE / (1024 * 1024 * 1024);
+        scale = 1024 * 1024 * 1024;
+        break;
+
+    default:
+        max = NGX_MAX_OFF_T_VALUE;
+        scale = 1;
+    }
+
+    offset = ngx_atoof(line->data, len);
+    if (offset == NGX_ERROR || offset > max) {
+        return NGX_ERROR;
+    }
+
+    offset *= scale;
+
+    return offset;
+}
+
+
+ngx_int_t
+ngx_parse_time(ngx_str_t *line, ngx_uint_t is_sec)
+{
+    u_char      *p, *last;
+    ngx_int_t    value, total, scale;
+    ngx_int_t    max, cutoff, cutlim;
+    ngx_uint_t   valid;
+    enum {
+        st_start = 0,
+        st_year,
+        st_month,
+        st_week,
+        st_day,
+        st_hour,
+        st_min,
+        st_sec,
+        st_msec,
+        st_last
+    } step;
+
+    valid = 0;
+    value = 0;
+    total = 0;
+    cutoff = NGX_MAX_INT_T_VALUE / 10;
+    cutlim = NGX_MAX_INT_T_VALUE % 10;
+    step = is_sec ? st_start : st_month;
+
+    p = line->data;
+    last = p + line->len;
+
+    while (p < last) {
+
+        if (*p >= '0' && *p <= '9') {
+            if (value >= cutoff && (value > cutoff || *p - '0' > cutlim)) {
+                return NGX_ERROR;
+            }
+
+            value = value * 10 + (*p++ - '0');
+            valid = 1;
+            continue;
+        }
+
+        switch (*p++) {
+
+        case 'y':
+            if (step > st_start) {
+                return NGX_ERROR;
+            }
+            step = st_year;
+            max = NGX_MAX_INT_T_VALUE / (60 * 60 * 24 * 365);
+            scale = 60 * 60 * 24 * 365;
+            break;
+
+        case 'M':
+            if (step >= st_month) {
+                return NGX_ERROR;
+            }
+            step = st_month;
+            max = NGX_MAX_INT_T_VALUE / (60 * 60 * 24 * 30);
+            scale = 60 * 60 * 24 * 30;
+            break;
+
+        case 'w':
+            if (step >= st_week) {
+                return NGX_ERROR;
+            }
+            step = st_week;
+            max = NGX_MAX_INT_T_VALUE / (60 * 60 * 24 * 7);
+            scale = 60 * 60 * 24 * 7;
+            break;
+
+        case 'd':
+            if (step >= st_day) {
+                return NGX_ERROR;
+            }
+            step = st_day;
+            max = NGX_MAX_INT_T_VALUE / (60 * 60 * 24);
+            scale = 60 * 60 * 24;
+            break;
+
+        case 'h':
+            if (step >= st_hour) {
+                return NGX_ERROR;
+            }
+            step = st_hour;
+            max = NGX_MAX_INT_T_VALUE / (60 * 60);
+            scale = 60 * 60;
+            break;
+
+        case 'm':
+            if (p < last && *p == 's') {
+                if (is_sec || step >= st_msec) {
+                    return NGX_ERROR;
+                }
+                p++;
+                step = st_msec;
+                max = NGX_MAX_INT_T_VALUE;
+                scale = 1;
+                break;
+            }
+
+            if (step >= st_min) {
+                return NGX_ERROR;
+            }
+            step = st_min;
+            max = NGX_MAX_INT_T_VALUE / 60;
+            scale = 60;
+            break;
+
+        case 's':
+            if (step >= st_sec) {
+                return NGX_ERROR;
+            }
+            step = st_sec;
+            max = NGX_MAX_INT_T_VALUE;
+            scale = 1;
+            break;
+
+        case ' ':
+            if (step >= st_sec) {
+                return NGX_ERROR;
+            }
+            step = st_last;
+            max = NGX_MAX_INT_T_VALUE;
+            scale = 1;
+            break;
+
+        default:
+            return NGX_ERROR;
+        }
+
+        if (step != st_msec && !is_sec) {
+            scale *= 1000;
+            max /= 1000;
+        }
+
+        if (value > max) {
+            return NGX_ERROR;
+        }
+
+        value *= scale;
+
+        if (total > NGX_MAX_INT_T_VALUE - value) {
+            return NGX_ERROR;
+        }
+
+        total += value;
+
+        value = 0;
+
+        while (p < last && *p == ' ') {
+            p++;
+        }
+    }
+
+    if (!valid) {
+        return NGX_ERROR;
+    }
+
+    if (!is_sec) {
+        if (value > NGX_MAX_INT_T_VALUE / 1000) {
+            return NGX_ERROR;
+        }
+
+        value *= 1000;
+    }
+
+    if (total > NGX_MAX_INT_T_VALUE - value) {
+        return NGX_ERROR;
+    }
+
+    return total + value;
+}
diff --git a/nginx/src/core/ngx_parse.h b/nginx/src/core/ngx_parse.h
new file mode 100644 (file)
index 0000000..ec093b5
--- /dev/null
@@ -0,0 +1,21 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_PARSE_H_INCLUDED_
+#define _NGX_PARSE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+ssize_t ngx_parse_size(ngx_str_t *line);
+off_t ngx_parse_offset(ngx_str_t *line);
+ngx_int_t ngx_parse_time(ngx_str_t *line, ngx_uint_t is_sec);
+
+
+#endif /* _NGX_PARSE_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_parse_time.c b/nginx/src/core/ngx_parse_time.c
new file mode 100644 (file)
index 0000000..232ac91
--- /dev/null
@@ -0,0 +1,277 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+static ngx_uint_t  mday[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+
+time_t
+ngx_parse_http_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/nginx/src/core/ngx_parse_time.h b/nginx/src/core/ngx_parse_time.h
new file mode 100644 (file)
index 0000000..aa542eb
--- /dev/null
@@ -0,0 +1,22 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_PARSE_TIME_H_INCLUDED_
+#define _NGX_PARSE_TIME_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+time_t ngx_parse_http_time(u_char *value, size_t len);
+
+/* compatibility */
+#define ngx_http_parse_time(value, len)  ngx_parse_http_time(value, len)
+
+
+#endif /* _NGX_PARSE_TIME_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_proxy_protocol.c b/nginx/src/core/ngx_proxy_protocol.c
new file mode 100644 (file)
index 0000000..c3d7fd3
--- /dev/null
@@ -0,0 +1,343 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#define NGX_PROXY_PROTOCOL_AF_INET          1
+#define NGX_PROXY_PROTOCOL_AF_INET6         2
+
+
+#define ngx_proxy_protocol_parse_uint16(p)  ((p)[0] << 8 | (p)[1])
+
+
+typedef struct {
+    u_char                                  signature[12];
+    u_char                                  version_command;
+    u_char                                  family_transport;
+    u_char                                  len[2];
+} ngx_proxy_protocol_header_t;
+
+
+typedef struct {
+    u_char                                  src_addr[4];
+    u_char                                  dst_addr[4];
+    u_char                                  src_port[2];
+    u_char                                  dst_port[2];
+} ngx_proxy_protocol_inet_addrs_t;
+
+
+typedef struct {
+    u_char                                  src_addr[16];
+    u_char                                  dst_addr[16];
+    u_char                                  src_port[2];
+    u_char                                  dst_port[2];
+} ngx_proxy_protocol_inet6_addrs_t;
+
+
+static u_char *ngx_proxy_protocol_v2_read(ngx_connection_t *c, u_char *buf,
+    u_char *last);
+
+
+u_char *
+ngx_proxy_protocol_read(ngx_connection_t *c, u_char *buf, u_char *last)
+{
+    size_t     len;
+    u_char     ch, *p, *addr, *port;
+    ngx_int_t  n;
+
+    static const u_char signature[] = "\r\n\r\n\0\r\nQUIT\n";
+
+    p = buf;
+    len = last - buf;
+
+    if (len >= sizeof(ngx_proxy_protocol_header_t)
+        && memcmp(p, signature, sizeof(signature) - 1) == 0)
+    {
+        return ngx_proxy_protocol_v2_read(c, buf, last);
+    }
+
+    if (len < 8 || ngx_strncmp(p, "PROXY ", 6) != 0) {
+        goto invalid;
+    }
+
+    p += 6;
+    len -= 6;
+
+    if (len >= 7 && ngx_strncmp(p, "UNKNOWN", 7) == 0) {
+        ngx_log_debug0(NGX_LOG_DEBUG_CORE, c->log, 0,
+                       "PROXY protocol unknown protocol");
+        p += 7;
+        goto skip;
+    }
+
+    if (len < 5 || ngx_strncmp(p, "TCP", 3) != 0
+        || (p[3] != '4' && p[3] != '6') || p[4] != ' ')
+    {
+        goto invalid;
+    }
+
+    p += 5;
+    addr = p;
+
+    for ( ;; ) {
+        if (p == last) {
+            goto invalid;
+        }
+
+        ch = *p++;
+
+        if (ch == ' ') {
+            break;
+        }
+
+        if (ch != ':' && ch != '.'
+            && (ch < 'a' || ch > 'f')
+            && (ch < 'A' || ch > 'F')
+            && (ch < '0' || ch > '9'))
+        {
+            goto invalid;
+        }
+    }
+
+    len = p - addr - 1;
+    c->proxy_protocol_addr.data = ngx_pnalloc(c->pool, len);
+
+    if (c->proxy_protocol_addr.data == NULL) {
+        return NULL;
+    }
+
+    ngx_memcpy(c->proxy_protocol_addr.data, addr, len);
+    c->proxy_protocol_addr.len = len;
+
+    for ( ;; ) {
+        if (p == last) {
+            goto invalid;
+        }
+
+        if (*p++ == ' ') {
+            break;
+        }
+    }
+
+    port = p;
+
+    for ( ;; ) {
+        if (p == last) {
+            goto invalid;
+        }
+
+        if (*p++ == ' ') {
+            break;
+        }
+    }
+
+    len = p - port - 1;
+
+    n = ngx_atoi(port, len);
+
+    if (n < 0 || n > 65535) {
+        goto invalid;
+    }
+
+    c->proxy_protocol_port = (in_port_t) n;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_CORE, c->log, 0,
+                   "PROXY protocol address: %V %d", &c->proxy_protocol_addr,
+                   c->proxy_protocol_port);
+
+skip:
+
+    for ( /* void */ ; p < last - 1; p++) {
+        if (p[0] == CR && p[1] == LF) {
+            return p + 2;
+        }
+    }
+
+invalid:
+
+    ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                  "broken header: \"%*s\"", (size_t) (last - buf), buf);
+
+    return NULL;
+}
+
+
+u_char *
+ngx_proxy_protocol_write(ngx_connection_t *c, u_char *buf, u_char *last)
+{
+    ngx_uint_t  port, lport;
+
+    if (last - buf < NGX_PROXY_PROTOCOL_MAX_HEADER) {
+        return NULL;
+    }
+
+    if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
+        return NULL;
+    }
+
+    switch (c->sockaddr->sa_family) {
+
+    case AF_INET:
+        buf = ngx_cpymem(buf, "PROXY TCP4 ", sizeof("PROXY TCP4 ") - 1);
+        break;
+
+#if (NGX_HAVE_INET6)
+    case AF_INET6:
+        buf = ngx_cpymem(buf, "PROXY TCP6 ", sizeof("PROXY TCP6 ") - 1);
+        break;
+#endif
+
+    default:
+        return ngx_cpymem(buf, "PROXY UNKNOWN" CRLF,
+                          sizeof("PROXY UNKNOWN" CRLF) - 1);
+    }
+
+    buf += ngx_sock_ntop(c->sockaddr, c->socklen, buf, last - buf, 0);
+
+    *buf++ = ' ';
+
+    buf += ngx_sock_ntop(c->local_sockaddr, c->local_socklen, buf, last - buf,
+                         0);
+
+    port = ngx_inet_get_port(c->sockaddr);
+    lport = ngx_inet_get_port(c->local_sockaddr);
+
+    return ngx_slprintf(buf, last, " %ui %ui" CRLF, port, lport);
+}
+
+
+static u_char *
+ngx_proxy_protocol_v2_read(ngx_connection_t *c, u_char *buf, u_char *last)
+{
+    u_char                             *end;
+    size_t                              len;
+    socklen_t                           socklen;
+    ngx_uint_t                          version, command, family, transport;
+    ngx_sockaddr_t                      sockaddr;
+    ngx_proxy_protocol_header_t        *header;
+    ngx_proxy_protocol_inet_addrs_t    *in;
+#if (NGX_HAVE_INET6)
+    ngx_proxy_protocol_inet6_addrs_t   *in6;
+#endif
+
+    header = (ngx_proxy_protocol_header_t *) buf;
+
+    buf += sizeof(ngx_proxy_protocol_header_t);
+
+    version = header->version_command >> 4;
+
+    if (version != 2) {
+        ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                      "unknown PROXY protocol version: %ui", version);
+        return NULL;
+    }
+
+    len = ngx_proxy_protocol_parse_uint16(header->len);
+
+    if ((size_t) (last - buf) < len) {
+        ngx_log_error(NGX_LOG_ERR, c->log, 0, "header is too large");
+        return NULL;
+    }
+
+    end = buf + len;
+
+    command = header->version_command & 0x0f;
+
+    /* only PROXY is supported */
+    if (command != 1) {
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
+                       "PROXY protocol v2 unsupported command %ui", command);
+        return end;
+    }
+
+    transport = header->family_transport & 0x0f;
+
+    /* only STREAM is supported */
+    if (transport != 1) {
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
+                       "PROXY protocol v2 unsupported transport %ui",
+                       transport);
+        return end;
+    }
+
+    family = header->family_transport >> 4;
+
+    switch (family) {
+
+    case NGX_PROXY_PROTOCOL_AF_INET:
+
+        if ((size_t) (end - buf) < sizeof(ngx_proxy_protocol_inet_addrs_t)) {
+            return NULL;
+        }
+
+        in = (ngx_proxy_protocol_inet_addrs_t *) buf;
+
+        sockaddr.sockaddr_in.sin_family = AF_INET;
+        sockaddr.sockaddr_in.sin_port = 0;
+        memcpy(&sockaddr.sockaddr_in.sin_addr, in->src_addr, 4);
+
+        c->proxy_protocol_port = ngx_proxy_protocol_parse_uint16(in->src_port);
+
+        socklen = sizeof(struct sockaddr_in);
+
+        buf += sizeof(ngx_proxy_protocol_inet_addrs_t);
+
+        break;
+
+#if (NGX_HAVE_INET6)
+
+    case NGX_PROXY_PROTOCOL_AF_INET6:
+
+        if ((size_t) (end - buf) < sizeof(ngx_proxy_protocol_inet6_addrs_t)) {
+            return NULL;
+        }
+
+        in6 = (ngx_proxy_protocol_inet6_addrs_t *) buf;
+
+        sockaddr.sockaddr_in6.sin6_family = AF_INET6;
+        sockaddr.sockaddr_in6.sin6_port = 0;
+        memcpy(&sockaddr.sockaddr_in6.sin6_addr, in6->src_addr, 16);
+
+        c->proxy_protocol_port = ngx_proxy_protocol_parse_uint16(in6->src_port);
+
+        socklen = sizeof(struct sockaddr_in6);
+
+        buf += sizeof(ngx_proxy_protocol_inet6_addrs_t);
+
+        break;
+
+#endif
+
+    default:
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
+                       "PROXY protocol v2 unsupported address family %ui",
+                       family);
+        return end;
+    }
+
+    c->proxy_protocol_addr.data = ngx_pnalloc(c->pool, NGX_SOCKADDR_STRLEN);
+    if (c->proxy_protocol_addr.data == NULL) {
+        return NULL;
+    }
+
+    c->proxy_protocol_addr.len = ngx_sock_ntop(&sockaddr.sockaddr, socklen,
+                                               c->proxy_protocol_addr.data,
+                                               NGX_SOCKADDR_STRLEN, 0);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_CORE, c->log, 0,
+                   "PROXY protocol v2 address: %V %d", &c->proxy_protocol_addr,
+                   c->proxy_protocol_port);
+
+    if (buf < end) {
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
+                       "PROXY protocol v2 %z bytes of tlv ignored", end - buf);
+    }
+
+    return end;
+}
diff --git a/nginx/src/core/ngx_proxy_protocol.h b/nginx/src/core/ngx_proxy_protocol.h
new file mode 100644 (file)
index 0000000..fb848f6
--- /dev/null
@@ -0,0 +1,25 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_PROXY_PROTOCOL_H_INCLUDED_
+#define _NGX_PROXY_PROTOCOL_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#define NGX_PROXY_PROTOCOL_MAX_HEADER  107
+
+
+u_char *ngx_proxy_protocol_read(ngx_connection_t *c, u_char *buf,
+    u_char *last);
+u_char *ngx_proxy_protocol_write(ngx_connection_t *c, u_char *buf,
+    u_char *last);
+
+
+#endif /* _NGX_PROXY_PROTOCOL_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_queue.c b/nginx/src/core/ngx_queue.c
new file mode 100644 (file)
index 0000000..3cacaf3
--- /dev/null
@@ -0,0 +1,80 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/*
+ * find the middle queue element if the queue has odd number of elements
+ * or the first element of the queue's second part otherwise
+ */
+
+ngx_queue_t *
+ngx_queue_middle(ngx_queue_t *queue)
+{
+    ngx_queue_t  *middle, *next;
+
+    middle = ngx_queue_head(queue);
+
+    if (middle == ngx_queue_last(queue)) {
+        return middle;
+    }
+
+    next = ngx_queue_head(queue);
+
+    for ( ;; ) {
+        middle = ngx_queue_next(middle);
+
+        next = ngx_queue_next(next);
+
+        if (next == ngx_queue_last(queue)) {
+            return middle;
+        }
+
+        next = ngx_queue_next(next);
+
+        if (next == ngx_queue_last(queue)) {
+            return middle;
+        }
+    }
+}
+
+
+/* the stable insertion sort */
+
+void
+ngx_queue_sort(ngx_queue_t *queue,
+    ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *))
+{
+    ngx_queue_t  *q, *prev, *next;
+
+    q = ngx_queue_head(queue);
+
+    if (q == ngx_queue_last(queue)) {
+        return;
+    }
+
+    for (q = ngx_queue_next(q); q != ngx_queue_sentinel(queue); q = next) {
+
+        prev = ngx_queue_prev(q);
+        next = ngx_queue_next(q);
+
+        ngx_queue_remove(q);
+
+        do {
+            if (cmp(prev, q) <= 0) {
+                break;
+            }
+
+            prev = ngx_queue_prev(prev);
+
+        } while (prev != ngx_queue_sentinel(queue));
+
+        ngx_queue_insert_after(prev, q);
+    }
+}
diff --git a/nginx/src/core/ngx_queue.h b/nginx/src/core/ngx_queue.h
new file mode 100644 (file)
index 0000000..038bf12
--- /dev/null
@@ -0,0 +1,112 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#ifndef _NGX_QUEUE_H_INCLUDED_
+#define _NGX_QUEUE_H_INCLUDED_
+
+
+typedef struct ngx_queue_s  ngx_queue_t;
+
+struct ngx_queue_s {
+    ngx_queue_t  *prev;
+    ngx_queue_t  *next;
+};
+
+
+#define ngx_queue_init(q)                                                     \
+    (q)->prev = q;                                                            \
+    (q)->next = q
+
+
+#define ngx_queue_empty(h)                                                    \
+    (h == (h)->prev)
+
+
+#define ngx_queue_insert_head(h, x)                                           \
+    (x)->next = (h)->next;                                                    \
+    (x)->next->prev = x;                                                      \
+    (x)->prev = h;                                                            \
+    (h)->next = x
+
+
+#define ngx_queue_insert_after   ngx_queue_insert_head
+
+
+#define ngx_queue_insert_tail(h, x)                                           \
+    (x)->prev = (h)->prev;                                                    \
+    (x)->prev->next = x;                                                      \
+    (x)->next = h;                                                            \
+    (h)->prev = x
+
+
+#define ngx_queue_head(h)                                                     \
+    (h)->next
+
+
+#define ngx_queue_last(h)                                                     \
+    (h)->prev
+
+
+#define ngx_queue_sentinel(h)                                                 \
+    (h)
+
+
+#define ngx_queue_next(q)                                                     \
+    (q)->next
+
+
+#define ngx_queue_prev(q)                                                     \
+    (q)->prev
+
+
+#if (NGX_DEBUG)
+
+#define ngx_queue_remove(x)                                                   \
+    (x)->next->prev = (x)->prev;                                              \
+    (x)->prev->next = (x)->next;                                              \
+    (x)->prev = NULL;                                                         \
+    (x)->next = NULL
+
+#else
+
+#define ngx_queue_remove(x)                                                   \
+    (x)->next->prev = (x)->prev;                                              \
+    (x)->prev->next = (x)->next
+
+#endif
+
+
+#define ngx_queue_split(h, q, n)                                              \
+    (n)->prev = (h)->prev;                                                    \
+    (n)->prev->next = n;                                                      \
+    (n)->next = q;                                                            \
+    (h)->prev = (q)->prev;                                                    \
+    (h)->prev->next = h;                                                      \
+    (q)->prev = n;
+
+
+#define ngx_queue_add(h, n)                                                   \
+    (h)->prev->next = (n)->next;                                              \
+    (n)->next->prev = (h)->prev;                                              \
+    (h)->prev = (n)->prev;                                                    \
+    (h)->prev->next = h;
+
+
+#define ngx_queue_data(q, type, link)                                         \
+    (type *) ((u_char *) q - offsetof(type, link))
+
+
+ngx_queue_t *ngx_queue_middle(ngx_queue_t *queue);
+void ngx_queue_sort(ngx_queue_t *queue,
+    ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *));
+
+
+#endif /* _NGX_QUEUE_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_radix_tree.c b/nginx/src/core/ngx_radix_tree.c
new file mode 100644 (file)
index 0000000..c1d8737
--- /dev/null
@@ -0,0 +1,488 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+static ngx_radix_node_t *ngx_radix_alloc(ngx_radix_tree_t *tree);
+
+
+ngx_radix_tree_t *
+ngx_radix_tree_create(ngx_pool_t *pool, ngx_int_t preallocate)
+{
+    uint32_t           key, mask, inc;
+    ngx_radix_tree_t  *tree;
+
+    tree = ngx_palloc(pool, sizeof(ngx_radix_tree_t));
+    if (tree == NULL) {
+        return NULL;
+    }
+
+    tree->pool = pool;
+    tree->free = NULL;
+    tree->start = NULL;
+    tree->size = 0;
+
+    tree->root = ngx_radix_alloc(tree);
+    if (tree->root == NULL) {
+        return NULL;
+    }
+
+    tree->root->right = NULL;
+    tree->root->left = NULL;
+    tree->root->parent = NULL;
+    tree->root->value = NGX_RADIX_NO_VALUE;
+
+    if (preallocate == 0) {
+        return tree;
+    }
+
+    /*
+     * Preallocation of first nodes : 0, 1, 00, 01, 10, 11, 000, 001, etc.
+     * increases TLB hits even if for first lookup iterations.
+     * On 32-bit platforms the 7 preallocated bits takes continuous 4K,
+     * 8 - 8K, 9 - 16K, etc.  On 64-bit platforms the 6 preallocated bits
+     * takes continuous 4K, 7 - 8K, 8 - 16K, etc.  There is no sense to
+     * to preallocate more than one page, because further preallocation
+     * distributes the only bit per page.  Instead, a random insertion
+     * may distribute several bits per page.
+     *
+     * Thus, by default we preallocate maximum
+     *     6 bits on amd64 (64-bit platform and 4K pages)
+     *     7 bits on i386 (32-bit platform and 4K pages)
+     *     7 bits on sparc64 in 64-bit mode (8K pages)
+     *     8 bits on sparc64 in 32-bit mode (8K pages)
+     */
+
+    if (preallocate == -1) {
+        switch (ngx_pagesize / sizeof(ngx_radix_node_t)) {
+
+        /* amd64 */
+        case 128:
+            preallocate = 6;
+            break;
+
+        /* i386, sparc64 */
+        case 256:
+            preallocate = 7;
+            break;
+
+        /* sparc64 in 32-bit mode */
+        default:
+            preallocate = 8;
+        }
+    }
+
+    mask = 0;
+    inc = 0x80000000;
+
+    while (preallocate--) {
+
+        key = 0;
+        mask >>= 1;
+        mask |= 0x80000000;
+
+        do {
+            if (ngx_radix32tree_insert(tree, key, mask, NGX_RADIX_NO_VALUE)
+                != NGX_OK)
+            {
+                return NULL;
+            }
+
+            key += inc;
+
+        } while (key);
+
+        inc >>= 1;
+    }
+
+    return tree;
+}
+
+
+ngx_int_t
+ngx_radix32tree_insert(ngx_radix_tree_t *tree, uint32_t key, uint32_t mask,
+    uintptr_t value)
+{
+    uint32_t           bit;
+    ngx_radix_node_t  *node, *next;
+
+    bit = 0x80000000;
+
+    node = tree->root;
+    next = tree->root;
+
+    while (bit & mask) {
+        if (key & bit) {
+            next = node->right;
+
+        } else {
+            next = node->left;
+        }
+
+        if (next == NULL) {
+            break;
+        }
+
+        bit >>= 1;
+        node = next;
+    }
+
+    if (next) {
+        if (node->value != NGX_RADIX_NO_VALUE) {
+            return NGX_BUSY;
+        }
+
+        node->value = value;
+        return NGX_OK;
+    }
+
+    while (bit & mask) {
+        next = ngx_radix_alloc(tree);
+        if (next == NULL) {
+            return NGX_ERROR;
+        }
+
+        next->right = NULL;
+        next->left = NULL;
+        next->parent = node;
+        next->value = NGX_RADIX_NO_VALUE;
+
+        if (key & bit) {
+            node->right = next;
+
+        } else {
+            node->left = next;
+        }
+
+        bit >>= 1;
+        node = next;
+    }
+
+    node->value = value;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_radix32tree_delete(ngx_radix_tree_t *tree, uint32_t key, uint32_t mask)
+{
+    uint32_t           bit;
+    ngx_radix_node_t  *node;
+
+    bit = 0x80000000;
+    node = tree->root;
+
+    while (node && (bit & mask)) {
+        if (key & bit) {
+            node = node->right;
+
+        } else {
+            node = node->left;
+        }
+
+        bit >>= 1;
+    }
+
+    if (node == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (node->right || node->left) {
+        if (node->value != NGX_RADIX_NO_VALUE) {
+            node->value = NGX_RADIX_NO_VALUE;
+            return NGX_OK;
+        }
+
+        return NGX_ERROR;
+    }
+
+    for ( ;; ) {
+        if (node->parent->right == node) {
+            node->parent->right = NULL;
+
+        } else {
+            node->parent->left = NULL;
+        }
+
+        node->right = tree->free;
+        tree->free = node;
+
+        node = node->parent;
+
+        if (node->right || node->left) {
+            break;
+        }
+
+        if (node->value != NGX_RADIX_NO_VALUE) {
+            break;
+        }
+
+        if (node->parent == NULL) {
+            break;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+uintptr_t
+ngx_radix32tree_find(ngx_radix_tree_t *tree, uint32_t key)
+{
+    uint32_t           bit;
+    uintptr_t          value;
+    ngx_radix_node_t  *node;
+
+    bit = 0x80000000;
+    value = NGX_RADIX_NO_VALUE;
+    node = tree->root;
+
+    while (node) {
+        if (node->value != NGX_RADIX_NO_VALUE) {
+            value = node->value;
+        }
+
+        if (key & bit) {
+            node = node->right;
+
+        } else {
+            node = node->left;
+        }
+
+        bit >>= 1;
+    }
+
+    return value;
+}
+
+
+#if (NGX_HAVE_INET6)
+
+ngx_int_t
+ngx_radix128tree_insert(ngx_radix_tree_t *tree, u_char *key, u_char *mask,
+    uintptr_t value)
+{
+    u_char             bit;
+    ngx_uint_t         i;
+    ngx_radix_node_t  *node, *next;
+
+    i = 0;
+    bit = 0x80;
+
+    node = tree->root;
+    next = tree->root;
+
+    while (bit & mask[i]) {
+        if (key[i] & bit) {
+            next = node->right;
+
+        } else {
+            next = node->left;
+        }
+
+        if (next == NULL) {
+            break;
+        }
+
+        bit >>= 1;
+        node = next;
+
+        if (bit == 0) {
+            if (++i == 16) {
+                break;
+            }
+
+            bit = 0x80;
+        }
+    }
+
+    if (next) {
+        if (node->value != NGX_RADIX_NO_VALUE) {
+            return NGX_BUSY;
+        }
+
+        node->value = value;
+        return NGX_OK;
+    }
+
+    while (bit & mask[i]) {
+        next = ngx_radix_alloc(tree);
+        if (next == NULL) {
+            return NGX_ERROR;
+        }
+
+        next->right = NULL;
+        next->left = NULL;
+        next->parent = node;
+        next->value = NGX_RADIX_NO_VALUE;
+
+        if (key[i] & bit) {
+            node->right = next;
+
+        } else {
+            node->left = next;
+        }
+
+        bit >>= 1;
+        node = next;
+
+        if (bit == 0) {
+            if (++i == 16) {
+                break;
+            }
+
+            bit = 0x80;
+        }
+    }
+
+    node->value = value;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_radix128tree_delete(ngx_radix_tree_t *tree, u_char *key, u_char *mask)
+{
+    u_char             bit;
+    ngx_uint_t         i;
+    ngx_radix_node_t  *node;
+
+    i = 0;
+    bit = 0x80;
+    node = tree->root;
+
+    while (node && (bit & mask[i])) {
+        if (key[i] & bit) {
+            node = node->right;
+
+        } else {
+            node = node->left;
+        }
+
+        bit >>= 1;
+
+        if (bit == 0) {
+            if (++i == 16) {
+                break;
+            }
+
+            bit = 0x80;
+        }
+    }
+
+    if (node == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (node->right || node->left) {
+        if (node->value != NGX_RADIX_NO_VALUE) {
+            node->value = NGX_RADIX_NO_VALUE;
+            return NGX_OK;
+        }
+
+        return NGX_ERROR;
+    }
+
+    for ( ;; ) {
+        if (node->parent->right == node) {
+            node->parent->right = NULL;
+
+        } else {
+            node->parent->left = NULL;
+        }
+
+        node->right = tree->free;
+        tree->free = node;
+
+        node = node->parent;
+
+        if (node->right || node->left) {
+            break;
+        }
+
+        if (node->value != NGX_RADIX_NO_VALUE) {
+            break;
+        }
+
+        if (node->parent == NULL) {
+            break;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+uintptr_t
+ngx_radix128tree_find(ngx_radix_tree_t *tree, u_char *key)
+{
+    u_char             bit;
+    uintptr_t          value;
+    ngx_uint_t         i;
+    ngx_radix_node_t  *node;
+
+    i = 0;
+    bit = 0x80;
+    value = NGX_RADIX_NO_VALUE;
+    node = tree->root;
+
+    while (node) {
+        if (node->value != NGX_RADIX_NO_VALUE) {
+            value = node->value;
+        }
+
+        if (key[i] & bit) {
+            node = node->right;
+
+        } else {
+            node = node->left;
+        }
+
+        bit >>= 1;
+
+        if (bit == 0) {
+            i++;
+            bit = 0x80;
+        }
+    }
+
+    return value;
+}
+
+#endif
+
+
+static ngx_radix_node_t *
+ngx_radix_alloc(ngx_radix_tree_t *tree)
+{
+    ngx_radix_node_t  *p;
+
+    if (tree->free) {
+        p = tree->free;
+        tree->free = tree->free->right;
+        return p;
+    }
+
+    if (tree->size < sizeof(ngx_radix_node_t)) {
+        tree->start = ngx_pmemalign(tree->pool, ngx_pagesize, ngx_pagesize);
+        if (tree->start == NULL) {
+            return NULL;
+        }
+
+        tree->size = ngx_pagesize;
+    }
+
+    p = (ngx_radix_node_t *) tree->start;
+    tree->start += sizeof(ngx_radix_node_t);
+    tree->size -= sizeof(ngx_radix_node_t);
+
+    return p;
+}
diff --git a/nginx/src/core/ngx_radix_tree.h b/nginx/src/core/ngx_radix_tree.h
new file mode 100644 (file)
index 0000000..4fe06e0
--- /dev/null
@@ -0,0 +1,55 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_RADIX_TREE_H_INCLUDED_
+#define _NGX_RADIX_TREE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#define NGX_RADIX_NO_VALUE   (uintptr_t) -1
+
+typedef struct ngx_radix_node_s  ngx_radix_node_t;
+
+struct ngx_radix_node_s {
+    ngx_radix_node_t  *right;
+    ngx_radix_node_t  *left;
+    ngx_radix_node_t  *parent;
+    uintptr_t          value;
+};
+
+
+typedef struct {
+    ngx_radix_node_t  *root;
+    ngx_pool_t        *pool;
+    ngx_radix_node_t  *free;
+    char              *start;
+    size_t             size;
+} ngx_radix_tree_t;
+
+
+ngx_radix_tree_t *ngx_radix_tree_create(ngx_pool_t *pool,
+    ngx_int_t preallocate);
+
+ngx_int_t ngx_radix32tree_insert(ngx_radix_tree_t *tree,
+    uint32_t key, uint32_t mask, uintptr_t value);
+ngx_int_t ngx_radix32tree_delete(ngx_radix_tree_t *tree,
+    uint32_t key, uint32_t mask);
+uintptr_t ngx_radix32tree_find(ngx_radix_tree_t *tree, uint32_t key);
+
+#if (NGX_HAVE_INET6)
+ngx_int_t ngx_radix128tree_insert(ngx_radix_tree_t *tree,
+    u_char *key, u_char *mask, uintptr_t value);
+ngx_int_t ngx_radix128tree_delete(ngx_radix_tree_t *tree,
+    u_char *key, u_char *mask);
+uintptr_t ngx_radix128tree_find(ngx_radix_tree_t *tree, u_char *key);
+#endif
+
+
+#endif /* _NGX_RADIX_TREE_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_rbtree.c b/nginx/src/core/ngx_rbtree.c
new file mode 100644 (file)
index 0000000..969d549
--- /dev/null
@@ -0,0 +1,409 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/*
+ * The red-black tree code is based on the algorithm described in
+ * the "Introduction to Algorithms" by Cormen, Leiserson and Rivest.
+ */
+
+
+static ngx_inline void ngx_rbtree_left_rotate(ngx_rbtree_node_t **root,
+    ngx_rbtree_node_t *sentinel, ngx_rbtree_node_t *node);
+static ngx_inline void ngx_rbtree_right_rotate(ngx_rbtree_node_t **root,
+    ngx_rbtree_node_t *sentinel, ngx_rbtree_node_t *node);
+
+
+void
+ngx_rbtree_insert(ngx_rbtree_t *tree, ngx_rbtree_node_t *node)
+{
+    ngx_rbtree_node_t  **root, *temp, *sentinel;
+
+    /* a binary tree insert */
+
+    root = &tree->root;
+    sentinel = tree->sentinel;
+
+    if (*root == sentinel) {
+        node->parent = NULL;
+        node->left = sentinel;
+        node->right = sentinel;
+        ngx_rbt_black(node);
+        *root = node;
+
+        return;
+    }
+
+    tree->insert(*root, node, sentinel);
+
+    /* re-balance tree */
+
+    while (node != *root && ngx_rbt_is_red(node->parent)) {
+
+        if (node->parent == node->parent->parent->left) {
+            temp = node->parent->parent->right;
+
+            if (ngx_rbt_is_red(temp)) {
+                ngx_rbt_black(node->parent);
+                ngx_rbt_black(temp);
+                ngx_rbt_red(node->parent->parent);
+                node = node->parent->parent;
+
+            } else {
+                if (node == node->parent->right) {
+                    node = node->parent;
+                    ngx_rbtree_left_rotate(root, sentinel, node);
+                }
+
+                ngx_rbt_black(node->parent);
+                ngx_rbt_red(node->parent->parent);
+                ngx_rbtree_right_rotate(root, sentinel, node->parent->parent);
+            }
+
+        } else {
+            temp = node->parent->parent->left;
+
+            if (ngx_rbt_is_red(temp)) {
+                ngx_rbt_black(node->parent);
+                ngx_rbt_black(temp);
+                ngx_rbt_red(node->parent->parent);
+                node = node->parent->parent;
+
+            } else {
+                if (node == node->parent->left) {
+                    node = node->parent;
+                    ngx_rbtree_right_rotate(root, sentinel, node);
+                }
+
+                ngx_rbt_black(node->parent);
+                ngx_rbt_red(node->parent->parent);
+                ngx_rbtree_left_rotate(root, sentinel, node->parent->parent);
+            }
+        }
+    }
+
+    ngx_rbt_black(*root);
+}
+
+
+void
+ngx_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node,
+    ngx_rbtree_node_t *sentinel)
+{
+    ngx_rbtree_node_t  **p;
+
+    for ( ;; ) {
+
+        p = (node->key < temp->key) ? &temp->left : &temp->right;
+
+        if (*p == sentinel) {
+            break;
+        }
+
+        temp = *p;
+    }
+
+    *p = node;
+    node->parent = temp;
+    node->left = sentinel;
+    node->right = sentinel;
+    ngx_rbt_red(node);
+}
+
+
+void
+ngx_rbtree_insert_timer_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node,
+    ngx_rbtree_node_t *sentinel)
+{
+    ngx_rbtree_node_t  **p;
+
+    for ( ;; ) {
+
+        /*
+         * Timer values
+         * 1) are spread in small range, usually several minutes,
+         * 2) and overflow each 49 days, if milliseconds are stored in 32 bits.
+         * The comparison takes into account that overflow.
+         */
+
+        /*  node->key < temp->key */
+
+        p = ((ngx_rbtree_key_int_t) (node->key - temp->key) < 0)
+            ? &temp->left : &temp->right;
+
+        if (*p == sentinel) {
+            break;
+        }
+
+        temp = *p;
+    }
+
+    *p = node;
+    node->parent = temp;
+    node->left = sentinel;
+    node->right = sentinel;
+    ngx_rbt_red(node);
+}
+
+
+void
+ngx_rbtree_delete(ngx_rbtree_t *tree, ngx_rbtree_node_t *node)
+{
+    ngx_uint_t           red;
+    ngx_rbtree_node_t  **root, *sentinel, *subst, *temp, *w;
+
+    /* a binary tree delete */
+
+    root = &tree->root;
+    sentinel = tree->sentinel;
+
+    if (node->left == sentinel) {
+        temp = node->right;
+        subst = node;
+
+    } else if (node->right == sentinel) {
+        temp = node->left;
+        subst = node;
+
+    } else {
+        subst = ngx_rbtree_min(node->right, sentinel);
+
+        if (subst->left != sentinel) {
+            temp = subst->left;
+        } else {
+            temp = subst->right;
+        }
+    }
+
+    if (subst == *root) {
+        *root = temp;
+        ngx_rbt_black(temp);
+
+        /* DEBUG stuff */
+        node->left = NULL;
+        node->right = NULL;
+        node->parent = NULL;
+        node->key = 0;
+
+        return;
+    }
+
+    red = ngx_rbt_is_red(subst);
+
+    if (subst == subst->parent->left) {
+        subst->parent->left = temp;
+
+    } else {
+        subst->parent->right = temp;
+    }
+
+    if (subst == node) {
+
+        temp->parent = subst->parent;
+
+    } else {
+
+        if (subst->parent == node) {
+            temp->parent = subst;
+
+        } else {
+            temp->parent = subst->parent;
+        }
+
+        subst->left = node->left;
+        subst->right = node->right;
+        subst->parent = node->parent;
+        ngx_rbt_copy_color(subst, node);
+
+        if (node == *root) {
+            *root = subst;
+
+        } else {
+            if (node == node->parent->left) {
+                node->parent->left = subst;
+            } else {
+                node->parent->right = subst;
+            }
+        }
+
+        if (subst->left != sentinel) {
+            subst->left->parent = subst;
+        }
+
+        if (subst->right != sentinel) {
+            subst->right->parent = subst;
+        }
+    }
+
+    /* DEBUG stuff */
+    node->left = NULL;
+    node->right = NULL;
+    node->parent = NULL;
+    node->key = 0;
+
+    if (red) {
+        return;
+    }
+
+    /* a delete fixup */
+
+    while (temp != *root && ngx_rbt_is_black(temp)) {
+
+        if (temp == temp->parent->left) {
+            w = temp->parent->right;
+
+            if (ngx_rbt_is_red(w)) {
+                ngx_rbt_black(w);
+                ngx_rbt_red(temp->parent);
+                ngx_rbtree_left_rotate(root, sentinel, temp->parent);
+                w = temp->parent->right;
+            }
+
+            if (ngx_rbt_is_black(w->left) && ngx_rbt_is_black(w->right)) {
+                ngx_rbt_red(w);
+                temp = temp->parent;
+
+            } else {
+                if (ngx_rbt_is_black(w->right)) {
+                    ngx_rbt_black(w->left);
+                    ngx_rbt_red(w);
+                    ngx_rbtree_right_rotate(root, sentinel, w);
+                    w = temp->parent->right;
+                }
+
+                ngx_rbt_copy_color(w, temp->parent);
+                ngx_rbt_black(temp->parent);
+                ngx_rbt_black(w->right);
+                ngx_rbtree_left_rotate(root, sentinel, temp->parent);
+                temp = *root;
+            }
+
+        } else {
+            w = temp->parent->left;
+
+            if (ngx_rbt_is_red(w)) {
+                ngx_rbt_black(w);
+                ngx_rbt_red(temp->parent);
+                ngx_rbtree_right_rotate(root, sentinel, temp->parent);
+                w = temp->parent->left;
+            }
+
+            if (ngx_rbt_is_black(w->left) && ngx_rbt_is_black(w->right)) {
+                ngx_rbt_red(w);
+                temp = temp->parent;
+
+            } else {
+                if (ngx_rbt_is_black(w->left)) {
+                    ngx_rbt_black(w->right);
+                    ngx_rbt_red(w);
+                    ngx_rbtree_left_rotate(root, sentinel, w);
+                    w = temp->parent->left;
+                }
+
+                ngx_rbt_copy_color(w, temp->parent);
+                ngx_rbt_black(temp->parent);
+                ngx_rbt_black(w->left);
+                ngx_rbtree_right_rotate(root, sentinel, temp->parent);
+                temp = *root;
+            }
+        }
+    }
+
+    ngx_rbt_black(temp);
+}
+
+
+static ngx_inline void
+ngx_rbtree_left_rotate(ngx_rbtree_node_t **root, ngx_rbtree_node_t *sentinel,
+    ngx_rbtree_node_t *node)
+{
+    ngx_rbtree_node_t  *temp;
+
+    temp = node->right;
+    node->right = temp->left;
+
+    if (temp->left != sentinel) {
+        temp->left->parent = node;
+    }
+
+    temp->parent = node->parent;
+
+    if (node == *root) {
+        *root = temp;
+
+    } else if (node == node->parent->left) {
+        node->parent->left = temp;
+
+    } else {
+        node->parent->right = temp;
+    }
+
+    temp->left = node;
+    node->parent = temp;
+}
+
+
+static ngx_inline void
+ngx_rbtree_right_rotate(ngx_rbtree_node_t **root, ngx_rbtree_node_t *sentinel,
+    ngx_rbtree_node_t *node)
+{
+    ngx_rbtree_node_t  *temp;
+
+    temp = node->left;
+    node->left = temp->right;
+
+    if (temp->right != sentinel) {
+        temp->right->parent = node;
+    }
+
+    temp->parent = node->parent;
+
+    if (node == *root) {
+        *root = temp;
+
+    } else if (node == node->parent->right) {
+        node->parent->right = temp;
+
+    } else {
+        node->parent->left = temp;
+    }
+
+    temp->right = node;
+    node->parent = temp;
+}
+
+
+ngx_rbtree_node_t *
+ngx_rbtree_next(ngx_rbtree_t *tree, ngx_rbtree_node_t *node)
+{
+    ngx_rbtree_node_t  *root, *sentinel, *parent;
+
+    sentinel = tree->sentinel;
+
+    if (node->right != sentinel) {
+        return ngx_rbtree_min(node->right, sentinel);
+    }
+
+    root = tree->root;
+
+    for ( ;; ) {
+        parent = node->parent;
+
+        if (node == root) {
+            return NULL;
+        }
+
+        if (node == parent->left) {
+            return parent;
+        }
+
+        node = parent;
+    }
+}
diff --git a/nginx/src/core/ngx_rbtree.h b/nginx/src/core/ngx_rbtree.h
new file mode 100644 (file)
index 0000000..97f0e3e
--- /dev/null
@@ -0,0 +1,84 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_RBTREE_H_INCLUDED_
+#define _NGX_RBTREE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef ngx_uint_t  ngx_rbtree_key_t;
+typedef ngx_int_t   ngx_rbtree_key_int_t;
+
+
+typedef struct ngx_rbtree_node_s  ngx_rbtree_node_t;
+
+struct ngx_rbtree_node_s {
+    ngx_rbtree_key_t       key;
+    ngx_rbtree_node_t     *left;
+    ngx_rbtree_node_t     *right;
+    ngx_rbtree_node_t     *parent;
+    u_char                 color;
+    u_char                 data;
+};
+
+
+typedef struct ngx_rbtree_s  ngx_rbtree_t;
+
+typedef void (*ngx_rbtree_insert_pt) (ngx_rbtree_node_t *root,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
+
+struct ngx_rbtree_s {
+    ngx_rbtree_node_t     *root;
+    ngx_rbtree_node_t     *sentinel;
+    ngx_rbtree_insert_pt   insert;
+};
+
+
+#define ngx_rbtree_init(tree, s, i)                                           \
+    ngx_rbtree_sentinel_init(s);                                              \
+    (tree)->root = s;                                                         \
+    (tree)->sentinel = s;                                                     \
+    (tree)->insert = i
+
+
+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);
+void ngx_rbtree_insert_value(ngx_rbtree_node_t *root, ngx_rbtree_node_t *node,
+    ngx_rbtree_node_t *sentinel);
+void ngx_rbtree_insert_timer_value(ngx_rbtree_node_t *root,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
+ngx_rbtree_node_t *ngx_rbtree_next(ngx_rbtree_t *tree,
+    ngx_rbtree_node_t *node);
+
+
+#define ngx_rbt_red(node)               ((node)->color = 1)
+#define ngx_rbt_black(node)             ((node)->color = 0)
+#define ngx_rbt_is_red(node)            ((node)->color)
+#define ngx_rbt_is_black(node)          (!ngx_rbt_is_red(node))
+#define ngx_rbt_copy_color(n1, n2)      (n1->color = n2->color)
+
+
+/* a sentinel must be black */
+
+#define ngx_rbtree_sentinel_init(node)  ngx_rbt_black(node)
+
+
+static ngx_inline ngx_rbtree_node_t *
+ngx_rbtree_min(ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
+{
+    while (node->left != sentinel) {
+        node = node->left;
+    }
+
+    return node;
+}
+
+
+#endif /* _NGX_RBTREE_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_regex.c b/nginx/src/core/ngx_regex.c
new file mode 100644 (file)
index 0000000..52169f6
--- /dev/null
@@ -0,0 +1,435 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef struct {
+    ngx_flag_t  pcre_jit;
+} ngx_regex_conf_t;
+
+
+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 ngx_int_t ngx_regex_module_init(ngx_cycle_t *cycle);
+
+static void *ngx_regex_create_conf(ngx_cycle_t *cycle);
+static char *ngx_regex_init_conf(ngx_cycle_t *cycle, void *conf);
+
+static char *ngx_regex_pcre_jit(ngx_conf_t *cf, void *post, void *data);
+static ngx_conf_post_t  ngx_regex_pcre_jit_post = { ngx_regex_pcre_jit };
+
+
+static ngx_command_t  ngx_regex_commands[] = {
+
+    { ngx_string("pcre_jit"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      0,
+      offsetof(ngx_regex_conf_t, pcre_jit),
+      &ngx_regex_pcre_jit_post },
+
+      ngx_null_command
+};
+
+
+static ngx_core_module_t  ngx_regex_module_ctx = {
+    ngx_string("regex"),
+    ngx_regex_create_conf,
+    ngx_regex_init_conf
+};
+
+
+ngx_module_t  ngx_regex_module = {
+    NGX_MODULE_V1,
+    &ngx_regex_module_ctx,                 /* module context */
+    ngx_regex_commands,                    /* module directives */
+    NGX_CORE_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    ngx_regex_module_init,                 /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_pool_t  *ngx_pcre_pool;
+static ngx_list_t  *ngx_pcre_studies;
+
+
+void
+ngx_regex_init(void)
+{
+    pcre_malloc = ngx_regex_malloc;
+    pcre_free = ngx_regex_free;
+}
+
+
+static ngx_inline void
+ngx_regex_malloc_init(ngx_pool_t *pool)
+{
+    ngx_pcre_pool = pool;
+}
+
+
+static ngx_inline void
+ngx_regex_malloc_done(void)
+{
+    ngx_pcre_pool = NULL;
+}
+
+
+ngx_int_t
+ngx_regex_compile(ngx_regex_compile_t *rc)
+{
+    int               n, erroff;
+    char             *p;
+    pcre             *re;
+    const char       *errstr;
+    ngx_regex_elt_t  *elt;
+
+    ngx_regex_malloc_init(rc->pool);
+
+    re = pcre_compile((const char *) rc->pattern.data, (int) rc->options,
+                      &errstr, &erroff, NULL);
+
+    /* ensure that there is no current pool */
+    ngx_regex_malloc_done();
+
+    if (re == NULL) {
+        if ((size_t) erroff == rc->pattern.len) {
+           rc->err.len = ngx_snprintf(rc->err.data, rc->err.len,
+                              "pcre_compile() failed: %s in \"%V\"",
+                               errstr, &rc->pattern)
+                      - 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;
+        }
+
+        return NGX_ERROR;
+    }
+
+    rc->regex = ngx_pcalloc(rc->pool, sizeof(ngx_regex_t));
+    if (rc->regex == NULL) {
+        goto nomem;
+    }
+
+    rc->regex->code = re;
+
+    /* do not study at runtime */
+
+    if (ngx_pcre_studies != NULL) {
+        elt = ngx_list_push(ngx_pcre_studies);
+        if (elt == NULL) {
+            goto nomem;
+        }
+
+        elt->regex = rc->regex;
+        elt->name = rc->pattern.data;
+    }
+
+    n = pcre_fullinfo(re, NULL, PCRE_INFO_CAPTURECOUNT, &rc->captures);
+    if (n < 0) {
+        p = "pcre_fullinfo(\"%V\", PCRE_INFO_CAPTURECOUNT) failed: %d";
+        goto failed;
+    }
+
+    if (rc->captures == 0) {
+        return NGX_OK;
+    }
+
+    n = pcre_fullinfo(re, NULL, PCRE_INFO_NAMECOUNT, &rc->named_captures);
+    if (n < 0) {
+        p = "pcre_fullinfo(\"%V\", PCRE_INFO_NAMECOUNT) failed: %d";
+        goto failed;
+    }
+
+    if (rc->named_captures == 0) {
+        return NGX_OK;
+    }
+
+    n = pcre_fullinfo(re, NULL, PCRE_INFO_NAMEENTRYSIZE, &rc->name_size);
+    if (n < 0) {
+        p = "pcre_fullinfo(\"%V\", PCRE_INFO_NAMEENTRYSIZE) failed: %d";
+        goto failed;
+    }
+
+    n = pcre_fullinfo(re, NULL, PCRE_INFO_NAMETABLE, &rc->names);
+    if (n < 0) {
+        p = "pcre_fullinfo(\"%V\", PCRE_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;
+}
+
+
+ngx_int_t
+ngx_regex_exec_array(ngx_array_t *a, ngx_str_t *s, ngx_log_t *log)
+{
+    ngx_int_t         n;
+    ngx_uint_t        i;
+    ngx_regex_elt_t  *re;
+
+    re = a->elts;
+
+    for (i = 0; i < a->nelts; i++) {
+
+        n = ngx_regex_exec(re[i].regex, s, NULL, 0);
+
+        if (n == NGX_REGEX_NO_MATCHED) {
+            continue;
+        }
+
+        if (n < 0) {
+            ngx_log_error(NGX_LOG_ALERT, log, 0,
+                          ngx_regex_exec_n " failed: %i on \"%V\" using \"%s\"",
+                          n, s, re[i].name);
+            return NGX_ERROR;
+        }
+
+        /* match */
+
+        return NGX_OK;
+    }
+
+    return NGX_DECLINED;
+}
+
+
+static void * ngx_libc_cdecl
+ngx_regex_malloc(size_t size)
+{
+    ngx_pool_t      *pool;
+    pool = ngx_pcre_pool;
+
+    if (pool) {
+        return ngx_palloc(pool, size);
+    }
+
+    return NULL;
+}
+
+
+static void ngx_libc_cdecl
+ngx_regex_free(void *p)
+{
+    return;
+}
+
+
+#if (NGX_HAVE_PCRE_JIT)
+
+static void
+ngx_pcre_free_studies(void *data)
+{
+    ngx_list_t *studies = data;
+
+    ngx_uint_t        i;
+    ngx_list_part_t  *part;
+    ngx_regex_elt_t  *elts;
+
+    part = &studies->part;
+    elts = part->elts;
+
+    for (i = 0; /* void */ ; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            elts = part->elts;
+            i = 0;
+        }
+
+        if (elts[i].regex->extra != NULL) {
+            pcre_free_study(elts[i].regex->extra);
+        }
+    }
+}
+
+#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;
+
+    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 (rcf->pcre_jit) {
+        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
+
+    ngx_regex_malloc_init(cycle->pool);
+
+    part = &ngx_pcre_studies->part;
+    elts = part->elts;
+
+    for (i = 0; /* void */ ; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            elts = part->elts;
+            i = 0;
+        }
+
+        elts[i].regex->extra = pcre_study(elts[i].regex->code, opt, &errstr);
+
+        if (errstr != NULL) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                          "pcre_study() failed: %s in \"%s\"",
+                          errstr, elts[i].name);
+        }
+
+#if (NGX_HAVE_PCRE_JIT)
+        if (opt & PCRE_STUDY_JIT_COMPILE) {
+            int jit, n;
+
+            jit = 0;
+            n = pcre_fullinfo(elts[i].regex->code, elts[i].regex->extra,
+                              PCRE_INFO_JIT, &jit);
+
+            if (n != 0 || jit != 1) {
+                ngx_log_error(NGX_LOG_INFO, cycle->log, 0,
+                              "JIT compiler does not support pattern: \"%s\"",
+                              elts[i].name);
+            }
+        }
+#endif
+    }
+
+    ngx_regex_malloc_done();
+
+    ngx_pcre_studies = NULL;
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_regex_create_conf(ngx_cycle_t *cycle)
+{
+    ngx_regex_conf_t  *rcf;
+
+    rcf = ngx_pcalloc(cycle->pool, sizeof(ngx_regex_conf_t));
+    if (rcf == NULL) {
+        return NULL;
+    }
+
+    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) {
+        return NULL;
+    }
+
+    return rcf;
+}
+
+
+static char *
+ngx_regex_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+    ngx_regex_conf_t *rcf = conf;
+
+    ngx_conf_init_value(rcf->pcre_jit, 0);
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_regex_pcre_jit(ngx_conf_t *cf, void *post, void *data)
+{
+    ngx_flag_t  *fp = data;
+
+    if (*fp == 0) {
+        return NGX_CONF_OK;
+    }
+
+#if (NGX_HAVE_PCRE_JIT)
+    {
+    int  jit, r;
+
+    jit = 0;
+    r = pcre_config(PCRE_CONFIG_JIT, &jit);
+
+    if (r != 0 || jit != 1) {
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                           "PCRE library does not support JIT");
+        *fp = 0;
+    }
+    }
+#else
+    ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                       "nginx was built without PCRE JIT support");
+    *fp = 0;
+#endif
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/core/ngx_regex.h b/nginx/src/core/ngx_regex.h
new file mode 100644 (file)
index 0000000..680486c
--- /dev/null
@@ -0,0 +1,60 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_REGEX_H_INCLUDED_
+#define _NGX_REGEX_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+#include <pcre.h>
+
+
+#define NGX_REGEX_NO_MATCHED  PCRE_ERROR_NOMATCH   /* -1 */
+
+#define NGX_REGEX_CASELESS    PCRE_CASELESS
+
+
+typedef struct {
+    pcre        *code;
+    pcre_extra  *extra;
+} ngx_regex_t;
+
+
+typedef struct {
+    ngx_str_t     pattern;
+    ngx_pool_t   *pool;
+    ngx_int_t     options;
+
+    ngx_regex_t  *regex;
+    int           captures;
+    int           named_captures;
+    int           name_size;
+    u_char       *names;
+    ngx_str_t     err;
+} ngx_regex_compile_t;
+
+
+typedef struct {
+    ngx_regex_t  *regex;
+    u_char       *name;
+} ngx_regex_elt_t;
+
+
+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_array(ngx_array_t *a, ngx_str_t *s, ngx_log_t *log);
+
+
+#endif /* _NGX_REGEX_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_resolver.c b/nginx/src/core/ngx_resolver.c
new file mode 100644 (file)
index 0000000..88add4d
--- /dev/null
@@ -0,0 +1,4654 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#define NGX_RESOLVER_UDP_SIZE   4096
+
+#define NGX_RESOLVER_TCP_RSIZE  (2 + 65535)
+#define NGX_RESOLVER_TCP_WSIZE  8192
+
+
+typedef struct {
+    u_char  ident_hi;
+    u_char  ident_lo;
+    u_char  flags_hi;
+    u_char  flags_lo;
+    u_char  nqs_hi;
+    u_char  nqs_lo;
+    u_char  nan_hi;
+    u_char  nan_lo;
+    u_char  nns_hi;
+    u_char  nns_lo;
+    u_char  nar_hi;
+    u_char  nar_lo;
+} ngx_resolver_hdr_t;
+
+
+typedef struct {
+    u_char  type_hi;
+    u_char  type_lo;
+    u_char  class_hi;
+    u_char  class_lo;
+} ngx_resolver_qs_t;
+
+
+typedef struct {
+    u_char  type_hi;
+    u_char  type_lo;
+    u_char  class_hi;
+    u_char  class_lo;
+    u_char  ttl[4];
+    u_char  len_hi;
+    u_char  len_lo;
+} ngx_resolver_an_t;
+
+
+#define ngx_resolver_node(n)                                                 \
+    (ngx_resolver_node_t *)                                                  \
+        ((u_char *) (n) - offsetof(ngx_resolver_node_t, node))
+
+
+static ngx_int_t ngx_udp_connect(ngx_resolver_connection_t *rec);
+static ngx_int_t ngx_tcp_connect(ngx_resolver_connection_t *rec);
+
+
+static void ngx_resolver_cleanup(void *data);
+static void ngx_resolver_cleanup_tree(ngx_resolver_t *r, ngx_rbtree_t *tree);
+static ngx_int_t ngx_resolve_name_locked(ngx_resolver_t *r,
+    ngx_resolver_ctx_t *ctx, ngx_str_t *name);
+static void ngx_resolver_expire(ngx_resolver_t *r, ngx_rbtree_t *tree,
+    ngx_queue_t *queue);
+static ngx_int_t ngx_resolver_send_query(ngx_resolver_t *r,
+    ngx_resolver_node_t *rn);
+static ngx_int_t ngx_resolver_send_udp_query(ngx_resolver_t *r,
+    ngx_resolver_connection_t *rec, u_char *query, u_short qlen);
+static ngx_int_t ngx_resolver_send_tcp_query(ngx_resolver_t *r,
+    ngx_resolver_connection_t *rec, u_char *query, u_short qlen);
+static ngx_int_t ngx_resolver_create_name_query(ngx_resolver_t *r,
+    ngx_resolver_node_t *rn, ngx_str_t *name);
+static ngx_int_t ngx_resolver_create_srv_query(ngx_resolver_t *r,
+    ngx_resolver_node_t *rn, ngx_str_t *name);
+static ngx_int_t ngx_resolver_create_addr_query(ngx_resolver_t *r,
+    ngx_resolver_node_t *rn, ngx_resolver_addr_t *addr);
+static void ngx_resolver_resend_handler(ngx_event_t *ev);
+static time_t ngx_resolver_resend(ngx_resolver_t *r, ngx_rbtree_t *tree,
+    ngx_queue_t *queue);
+static ngx_uint_t ngx_resolver_resend_empty(ngx_resolver_t *r);
+static void ngx_resolver_udp_read(ngx_event_t *rev);
+static void ngx_resolver_tcp_write(ngx_event_t *wev);
+static void ngx_resolver_tcp_read(ngx_event_t *rev);
+static void ngx_resolver_process_response(ngx_resolver_t *r, u_char *buf,
+    size_t n, ngx_uint_t tcp);
+static void ngx_resolver_process_a(ngx_resolver_t *r, u_char *buf, size_t n,
+    ngx_uint_t ident, ngx_uint_t code, ngx_uint_t qtype,
+    ngx_uint_t nan, ngx_uint_t trunc, ngx_uint_t ans);
+static void ngx_resolver_process_srv(ngx_resolver_t *r, u_char *buf, size_t n,
+    ngx_uint_t ident, ngx_uint_t code, ngx_uint_t nan,
+    ngx_uint_t trunc, ngx_uint_t ans);
+static void ngx_resolver_process_ptr(ngx_resolver_t *r, u_char *buf, size_t n,
+    ngx_uint_t ident, ngx_uint_t code, ngx_uint_t nan);
+static ngx_resolver_node_t *ngx_resolver_lookup_name(ngx_resolver_t *r,
+    ngx_str_t *name, uint32_t hash);
+static ngx_resolver_node_t *ngx_resolver_lookup_srv(ngx_resolver_t *r,
+    ngx_str_t *name, uint32_t hash);
+static ngx_resolver_node_t *ngx_resolver_lookup_addr(ngx_resolver_t *r,
+    in_addr_t addr);
+static void ngx_resolver_rbtree_insert_value(ngx_rbtree_node_t *temp,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
+static ngx_int_t ngx_resolver_copy(ngx_resolver_t *r, ngx_str_t *name,
+    u_char *buf, u_char *src, u_char *last);
+static ngx_int_t ngx_resolver_set_timeout(ngx_resolver_t *r,
+    ngx_resolver_ctx_t *ctx);
+static void ngx_resolver_timeout_handler(ngx_event_t *ev);
+static void ngx_resolver_free_node(ngx_resolver_t *r, ngx_resolver_node_t *rn);
+static void *ngx_resolver_alloc(ngx_resolver_t *r, size_t size);
+static void *ngx_resolver_calloc(ngx_resolver_t *r, size_t size);
+static void ngx_resolver_free(ngx_resolver_t *r, void *p);
+static void ngx_resolver_free_locked(ngx_resolver_t *r, void *p);
+static void *ngx_resolver_dup(ngx_resolver_t *r, void *src, size_t size);
+static ngx_resolver_addr_t *ngx_resolver_export(ngx_resolver_t *r,
+    ngx_resolver_node_t *rn, ngx_uint_t rotate);
+static void ngx_resolver_report_srv(ngx_resolver_t *r, ngx_resolver_ctx_t *ctx);
+static u_char *ngx_resolver_log_error(ngx_log_t *log, u_char *buf, size_t len);
+static void ngx_resolver_resolve_srv_names(ngx_resolver_ctx_t *ctx,
+    ngx_resolver_node_t *rn);
+static void ngx_resolver_srv_names_handler(ngx_resolver_ctx_t *ctx);
+static ngx_int_t ngx_resolver_cmp_srvs(const void *one, const void *two);
+
+#if (NGX_HAVE_INET6)
+static void ngx_resolver_rbtree_insert_addr6_value(ngx_rbtree_node_t *temp,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
+static ngx_resolver_node_t *ngx_resolver_lookup_addr6(ngx_resolver_t *r,
+    struct in6_addr *addr, uint32_t hash);
+#endif
+
+
+ngx_resolver_t *
+ngx_resolver_create(ngx_conf_t *cf, ngx_str_t *names, ngx_uint_t n)
+{
+    ngx_str_t                   s;
+    ngx_url_t                   u;
+    ngx_uint_t                  i, j;
+    ngx_resolver_t             *r;
+    ngx_pool_cleanup_t         *cln;
+    ngx_resolver_connection_t  *rec;
+
+    cln = ngx_pool_cleanup_add(cf->pool, 0);
+    if (cln == NULL) {
+        return NULL;
+    }
+
+    cln->handler = ngx_resolver_cleanup;
+
+    r = ngx_calloc(sizeof(ngx_resolver_t), cf->log);
+    if (r == NULL) {
+        return NULL;
+    }
+
+    cln->data = r;
+
+    r->event = ngx_calloc(sizeof(ngx_event_t), cf->log);
+    if (r->event == NULL) {
+        return NULL;
+    }
+
+    ngx_rbtree_init(&r->name_rbtree, &r->name_sentinel,
+                    ngx_resolver_rbtree_insert_value);
+
+    ngx_rbtree_init(&r->srv_rbtree, &r->srv_sentinel,
+                    ngx_resolver_rbtree_insert_value);
+
+    ngx_rbtree_init(&r->addr_rbtree, &r->addr_sentinel,
+                    ngx_rbtree_insert_value);
+
+    ngx_queue_init(&r->name_resend_queue);
+    ngx_queue_init(&r->srv_resend_queue);
+    ngx_queue_init(&r->addr_resend_queue);
+
+    ngx_queue_init(&r->name_expire_queue);
+    ngx_queue_init(&r->srv_expire_queue);
+    ngx_queue_init(&r->addr_expire_queue);
+
+#if (NGX_HAVE_INET6)
+    r->ipv6 = 1;
+
+    ngx_rbtree_init(&r->addr6_rbtree, &r->addr6_sentinel,
+                    ngx_resolver_rbtree_insert_addr6_value);
+
+    ngx_queue_init(&r->addr6_resend_queue);
+
+    ngx_queue_init(&r->addr6_expire_queue);
+#endif
+
+    r->event->handler = ngx_resolver_resend_handler;
+    r->event->data = r;
+    r->event->log = &cf->cycle->new_log;
+    r->event->cancelable = 1;
+    r->ident = -1;
+
+    r->resend_timeout = 5;
+    r->tcp_timeout = 5;
+    r->expire = 30;
+    r->valid = 0;
+
+    r->log = &cf->cycle->new_log;
+    r->log_level = NGX_LOG_ERR;
+
+    if (n) {
+        if (ngx_array_init(&r->connections, cf->pool, n,
+                           sizeof(ngx_resolver_connection_t))
+            != NGX_OK)
+        {
+            return NULL;
+        }
+    }
+
+    for (i = 0; i < n; i++) {
+        if (ngx_strncmp(names[i].data, "valid=", 6) == 0) {
+            s.len = names[i].len - 6;
+            s.data = names[i].data + 6;
+
+            r->valid = ngx_parse_time(&s, 1);
+
+            if (r->valid == (time_t) NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid parameter: %V", &names[i]);
+                return NULL;
+            }
+
+            continue;
+        }
+
+#if (NGX_HAVE_INET6)
+        if (ngx_strncmp(names[i].data, "ipv6=", 5) == 0) {
+
+            if (ngx_strcmp(&names[i].data[5], "on") == 0) {
+                r->ipv6 = 1;
+
+            } else if (ngx_strcmp(&names[i].data[5], "off") == 0) {
+                r->ipv6 = 0;
+
+            } else {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid parameter: %V", &names[i]);
+                return NULL;
+            }
+
+            continue;
+        }
+#endif
+
+        ngx_memzero(&u, sizeof(ngx_url_t));
+
+        u.url = names[i];
+        u.default_port = 53;
+
+        if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
+            if (u.err) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "%s in resolver \"%V\"",
+                                   u.err, &u.url);
+            }
+
+            return NULL;
+        }
+
+        rec = ngx_array_push_n(&r->connections, u.naddrs);
+        if (rec == NULL) {
+            return NULL;
+        }
+
+        ngx_memzero(rec, u.naddrs * sizeof(ngx_resolver_connection_t));
+
+        for (j = 0; j < u.naddrs; j++) {
+            rec[j].sockaddr = u.addrs[j].sockaddr;
+            rec[j].socklen = u.addrs[j].socklen;
+            rec[j].server = u.addrs[j].name;
+            rec[j].resolver = r;
+        }
+    }
+
+    return r;
+}
+
+
+static void
+ngx_resolver_cleanup(void *data)
+{
+    ngx_resolver_t  *r = data;
+
+    ngx_uint_t                  i;
+    ngx_resolver_connection_t  *rec;
+
+    if (r) {
+        ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
+                       "cleanup resolver");
+
+        ngx_resolver_cleanup_tree(r, &r->name_rbtree);
+
+        ngx_resolver_cleanup_tree(r, &r->srv_rbtree);
+
+        ngx_resolver_cleanup_tree(r, &r->addr_rbtree);
+
+#if (NGX_HAVE_INET6)
+        ngx_resolver_cleanup_tree(r, &r->addr6_rbtree);
+#endif
+
+        if (r->event) {
+            if (r->event->timer_set) {
+                ngx_del_timer(r->event);
+            }
+
+            ngx_free(r->event);
+        }
+
+
+        rec = r->connections.elts;
+
+        for (i = 0; i < r->connections.nelts; i++) {
+            if (rec[i].udp) {
+                ngx_close_connection(rec[i].udp);
+            }
+
+            if (rec[i].tcp) {
+                ngx_close_connection(rec[i].tcp);
+            }
+
+            if (rec[i].read_buf) {
+                ngx_resolver_free(r, rec[i].read_buf->start);
+                ngx_resolver_free(r, rec[i].read_buf);
+            }
+
+            if (rec[i].write_buf) {
+                ngx_resolver_free(r, rec[i].write_buf->start);
+                ngx_resolver_free(r, rec[i].write_buf);
+            }
+        }
+
+        ngx_free(r);
+    }
+}
+
+
+static void
+ngx_resolver_cleanup_tree(ngx_resolver_t *r, ngx_rbtree_t *tree)
+{
+    ngx_resolver_ctx_t   *ctx, *next;
+    ngx_resolver_node_t  *rn;
+
+    while (tree->root != tree->sentinel) {
+
+        rn = ngx_resolver_node(ngx_rbtree_min(tree->root, tree->sentinel));
+
+        ngx_queue_remove(&rn->queue);
+
+        for (ctx = rn->waiting; ctx; ctx = next) {
+            next = ctx->next;
+
+            if (ctx->event) {
+                if (ctx->event->timer_set) {
+                    ngx_del_timer(ctx->event);
+                }
+
+                ngx_resolver_free(r, ctx->event);
+            }
+
+            ngx_resolver_free(r, ctx);
+        }
+
+        ngx_rbtree_delete(tree, &rn->node);
+
+        ngx_resolver_free_node(r, rn);
+    }
+}
+
+
+ngx_resolver_ctx_t *
+ngx_resolve_start(ngx_resolver_t *r, ngx_resolver_ctx_t *temp)
+{
+    in_addr_t            addr;
+    ngx_resolver_ctx_t  *ctx;
+
+    if (temp) {
+        addr = ngx_inet_addr(temp->name.data, temp->name.len);
+
+        if (addr != INADDR_NONE) {
+            temp->resolver = r;
+            temp->state = NGX_OK;
+            temp->naddrs = 1;
+            temp->addrs = &temp->addr;
+            temp->addr.sockaddr = (struct sockaddr *) &temp->sin;
+            temp->addr.socklen = sizeof(struct sockaddr_in);
+            ngx_memzero(&temp->sin, sizeof(struct sockaddr_in));
+            temp->sin.sin_family = AF_INET;
+            temp->sin.sin_addr.s_addr = addr;
+            temp->quick = 1;
+
+            return temp;
+        }
+    }
+
+    if (r->connections.nelts == 0) {
+        return NGX_NO_RESOLVER;
+    }
+
+    ctx = ngx_resolver_calloc(r, sizeof(ngx_resolver_ctx_t));
+
+    if (ctx) {
+        ctx->resolver = r;
+    }
+
+    return ctx;
+}
+
+
+ngx_int_t
+ngx_resolve_name(ngx_resolver_ctx_t *ctx)
+{
+    size_t           slen;
+    ngx_int_t        rc;
+    ngx_str_t        name;
+    ngx_resolver_t  *r;
+
+    r = ctx->resolver;
+
+    if (ctx->name.len > 0 && ctx->name.data[ctx->name.len - 1] == '.') {
+        ctx->name.len--;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, r->log, 0,
+                   "resolve: \"%V\"", &ctx->name);
+
+    if (ctx->quick) {
+        ctx->handler(ctx);
+        return NGX_OK;
+    }
+
+    if (ctx->service.len) {
+        slen = ctx->service.len;
+
+        if (ngx_strlchr(ctx->service.data,
+                        ctx->service.data + ctx->service.len, '.')
+            == NULL)
+        {
+            slen += sizeof("_._tcp") - 1;
+        }
+
+        name.len = slen + 1 + ctx->name.len;
+
+        name.data = ngx_resolver_alloc(r, name.len);
+        if (name.data == NULL) {
+            goto failed;
+        }
+
+        if (slen == ctx->service.len) {
+            ngx_sprintf(name.data, "%V.%V", &ctx->service, &ctx->name);
+
+        } else {
+            ngx_sprintf(name.data, "_%V._tcp.%V", &ctx->service, &ctx->name);
+        }
+
+        /* lock name mutex */
+
+        rc = ngx_resolve_name_locked(r, ctx, &name);
+
+        ngx_resolver_free(r, name.data);
+
+    } else {
+        /* lock name mutex */
+
+        rc = ngx_resolve_name_locked(r, ctx, &ctx->name);
+    }
+
+    if (rc == NGX_OK) {
+        return NGX_OK;
+    }
+
+    /* unlock name mutex */
+
+    if (rc == NGX_AGAIN) {
+        return NGX_OK;
+    }
+
+    /* NGX_ERROR */
+
+    if (ctx->event) {
+        ngx_resolver_free(r, ctx->event);
+    }
+
+failed:
+
+    ngx_resolver_free(r, ctx);
+
+    return NGX_ERROR;
+}
+
+
+void
+ngx_resolve_name_done(ngx_resolver_ctx_t *ctx)
+{
+    ngx_uint_t            i;
+    ngx_resolver_t       *r;
+    ngx_resolver_ctx_t   *w, **p;
+    ngx_resolver_node_t  *rn;
+
+    r = ctx->resolver;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, r->log, 0,
+                   "resolve name done: %i", ctx->state);
+
+    if (ctx->quick) {
+        return;
+    }
+
+    if (ctx->event && ctx->event->timer_set) {
+        ngx_del_timer(ctx->event);
+    }
+
+    /* lock name mutex */
+
+    if (ctx->nsrvs) {
+        for (i = 0; i < ctx->nsrvs; i++) {
+            if (ctx->srvs[i].ctx) {
+                ngx_resolve_name_done(ctx->srvs[i].ctx);
+            }
+
+            if (ctx->srvs[i].addrs) {
+                ngx_resolver_free(r, ctx->srvs[i].addrs->sockaddr);
+                ngx_resolver_free(r, ctx->srvs[i].addrs);
+            }
+
+            ngx_resolver_free(r, ctx->srvs[i].name.data);
+        }
+
+        ngx_resolver_free(r, ctx->srvs);
+    }
+
+    if (ctx->state == NGX_AGAIN || ctx->state == NGX_RESOLVE_TIMEDOUT) {
+
+        rn = ctx->node;
+
+        if (rn) {
+            p = &rn->waiting;
+            w = rn->waiting;
+
+            while (w) {
+                if (w == ctx) {
+                    *p = w->next;
+
+                    goto done;
+                }
+
+                p = &w->next;
+                w = w->next;
+            }
+
+            ngx_log_error(NGX_LOG_ALERT, r->log, 0,
+                          "could not cancel %V resolving", &ctx->name);
+        }
+    }
+
+done:
+
+    if (ctx->service.len) {
+        ngx_resolver_expire(r, &r->srv_rbtree, &r->srv_expire_queue);
+
+    } else {
+        ngx_resolver_expire(r, &r->name_rbtree, &r->name_expire_queue);
+    }
+
+    /* unlock name mutex */
+
+    /* lock alloc mutex */
+
+    if (ctx->event) {
+        ngx_resolver_free_locked(r, ctx->event);
+    }
+
+    ngx_resolver_free_locked(r, ctx);
+
+    /* unlock alloc mutex */
+
+    if (r->event->timer_set && ngx_resolver_resend_empty(r)) {
+        ngx_del_timer(r->event);
+    }
+}
+
+
+static ngx_int_t
+ngx_resolve_name_locked(ngx_resolver_t *r, ngx_resolver_ctx_t *ctx,
+    ngx_str_t *name)
+{
+    uint32_t              hash;
+    ngx_int_t             rc;
+    ngx_str_t             cname;
+    ngx_uint_t            i, naddrs;
+    ngx_queue_t          *resend_queue, *expire_queue;
+    ngx_rbtree_t         *tree;
+    ngx_resolver_ctx_t   *next, *last;
+    ngx_resolver_addr_t  *addrs;
+    ngx_resolver_node_t  *rn;
+
+    ngx_strlow(name->data, name->data, name->len);
+
+    hash = ngx_crc32_short(name->data, name->len);
+
+    if (ctx->service.len) {
+        rn = ngx_resolver_lookup_srv(r, name, hash);
+
+        tree = &r->srv_rbtree;
+        resend_queue = &r->srv_resend_queue;
+        expire_queue = &r->srv_expire_queue;
+
+    } else {
+        rn = ngx_resolver_lookup_name(r, name, hash);
+
+        tree = &r->name_rbtree;
+        resend_queue = &r->name_resend_queue;
+        expire_queue = &r->name_expire_queue;
+    }
+
+    if (rn) {
+
+        /* ctx can be a list after NGX_RESOLVE_CNAME */
+        for (last = ctx; last->next; last = last->next);
+
+        if (rn->valid >= ngx_time()) {
+
+            ngx_log_debug0(NGX_LOG_DEBUG_CORE, r->log, 0, "resolve cached");
+
+            ngx_queue_remove(&rn->queue);
+
+            rn->expire = ngx_time() + r->expire;
+
+            ngx_queue_insert_head(expire_queue, &rn->queue);
+
+            naddrs = (rn->naddrs == (u_short) -1) ? 0 : rn->naddrs;
+#if (NGX_HAVE_INET6)
+            naddrs += (rn->naddrs6 == (u_short) -1) ? 0 : rn->naddrs6;
+#endif
+
+            if (naddrs) {
+
+                if (naddrs == 1 && rn->naddrs == 1) {
+                    addrs = NULL;
+
+                } else {
+                    addrs = ngx_resolver_export(r, rn, 1);
+                    if (addrs == NULL) {
+                        return NGX_ERROR;
+                    }
+                }
+
+                last->next = rn->waiting;
+                rn->waiting = NULL;
+
+                /* unlock name mutex */
+
+                do {
+                    ctx->state = NGX_OK;
+                    ctx->valid = rn->valid;
+                    ctx->naddrs = naddrs;
+
+                    if (addrs == NULL) {
+                        ctx->addrs = &ctx->addr;
+                        ctx->addr.sockaddr = (struct sockaddr *) &ctx->sin;
+                        ctx->addr.socklen = sizeof(struct sockaddr_in);
+                        ngx_memzero(&ctx->sin, sizeof(struct sockaddr_in));
+                        ctx->sin.sin_family = AF_INET;
+                        ctx->sin.sin_addr.s_addr = rn->u.addr;
+
+                    } else {
+                        ctx->addrs = addrs;
+                    }
+
+                    next = ctx->next;
+
+                    ctx->handler(ctx);
+
+                    ctx = next;
+                } while (ctx);
+
+                if (addrs != NULL) {
+                    ngx_resolver_free(r, addrs->sockaddr);
+                    ngx_resolver_free(r, addrs);
+                }
+
+                return NGX_OK;
+            }
+
+            if (rn->nsrvs) {
+                last->next = rn->waiting;
+                rn->waiting = NULL;
+
+                /* unlock name mutex */
+
+                do {
+                    next = ctx->next;
+
+                    ngx_resolver_resolve_srv_names(ctx, rn);
+
+                    ctx = next;
+                } while (ctx);
+
+                return NGX_OK;
+            }
+
+            /* NGX_RESOLVE_CNAME */
+
+            if (ctx->recursion++ < NGX_RESOLVER_MAX_RECURSION) {
+
+                cname.len = rn->cnlen;
+                cname.data = rn->u.cname;
+
+                return ngx_resolve_name_locked(r, ctx, &cname);
+            }
+
+            last->next = rn->waiting;
+            rn->waiting = NULL;
+
+            /* unlock name mutex */
+
+            do {
+                ctx->state = NGX_RESOLVE_NXDOMAIN;
+                ctx->valid = ngx_time() + (r->valid ? r->valid : 10);
+                next = ctx->next;
+
+                ctx->handler(ctx);
+
+                ctx = next;
+            } while (ctx);
+
+            return NGX_OK;
+        }
+
+        if (rn->waiting) {
+            if (ngx_resolver_set_timeout(r, ctx) != NGX_OK) {
+                return NGX_ERROR;
+            }
+
+            last->next = rn->waiting;
+            rn->waiting = ctx;
+            ctx->state = NGX_AGAIN;
+            ctx->async = 1;
+
+            do {
+                ctx->node = rn;
+                ctx = ctx->next;
+            } while (ctx);
+
+            return NGX_AGAIN;
+        }
+
+        ngx_queue_remove(&rn->queue);
+
+        /* lock alloc mutex */
+
+        if (rn->query) {
+            ngx_resolver_free_locked(r, rn->query);
+            rn->query = NULL;
+#if (NGX_HAVE_INET6)
+            rn->query6 = NULL;
+#endif
+        }
+
+        if (rn->cnlen) {
+            ngx_resolver_free_locked(r, rn->u.cname);
+        }
+
+        if (rn->naddrs > 1 && rn->naddrs != (u_short) -1) {
+            ngx_resolver_free_locked(r, rn->u.addrs);
+        }
+
+#if (NGX_HAVE_INET6)
+        if (rn->naddrs6 > 1 && rn->naddrs6 != (u_short) -1) {
+            ngx_resolver_free_locked(r, rn->u6.addrs6);
+        }
+#endif
+
+        if (rn->nsrvs) {
+            for (i = 0; i < (ngx_uint_t) rn->nsrvs; i++) {
+                if (rn->u.srvs[i].name.data) {
+                    ngx_resolver_free_locked(r, rn->u.srvs[i].name.data);
+                }
+            }
+
+            ngx_resolver_free_locked(r, rn->u.srvs);
+        }
+
+        /* unlock alloc mutex */
+
+    } else {
+
+        rn = ngx_resolver_alloc(r, sizeof(ngx_resolver_node_t));
+        if (rn == NULL) {
+            return NGX_ERROR;
+        }
+
+        rn->name = ngx_resolver_dup(r, name->data, name->len);
+        if (rn->name == NULL) {
+            ngx_resolver_free(r, rn);
+            return NGX_ERROR;
+        }
+
+        rn->node.key = hash;
+        rn->nlen = (u_short) name->len;
+        rn->query = NULL;
+#if (NGX_HAVE_INET6)
+        rn->query6 = NULL;
+#endif
+
+        ngx_rbtree_insert(tree, &rn->node);
+    }
+
+    if (ctx->service.len) {
+        rc = ngx_resolver_create_srv_query(r, rn, name);
+
+    } else {
+        rc = ngx_resolver_create_name_query(r, rn, name);
+    }
+
+    if (rc == NGX_ERROR) {
+        goto failed;
+    }
+
+    if (rc == NGX_DECLINED) {
+        ngx_rbtree_delete(tree, &rn->node);
+
+        ngx_resolver_free(r, rn->query);
+        ngx_resolver_free(r, rn->name);
+        ngx_resolver_free(r, rn);
+
+        do {
+            ctx->state = NGX_RESOLVE_NXDOMAIN;
+            next = ctx->next;
+
+            ctx->handler(ctx);
+
+            ctx = next;
+        } while (ctx);
+
+        return NGX_OK;
+    }
+
+    rn->last_connection = r->last_connection++;
+    if (r->last_connection == r->connections.nelts) {
+        r->last_connection = 0;
+    }
+
+    rn->naddrs = (u_short) -1;
+    rn->tcp = 0;
+#if (NGX_HAVE_INET6)
+    rn->naddrs6 = r->ipv6 ? (u_short) -1 : 0;
+    rn->tcp6 = 0;
+#endif
+    rn->nsrvs = 0;
+
+    if (ngx_resolver_send_query(r, rn) != NGX_OK) {
+        goto failed;
+    }
+
+    if (ngx_resolver_set_timeout(r, ctx) != NGX_OK) {
+        goto failed;
+    }
+
+    if (ngx_resolver_resend_empty(r)) {
+        ngx_add_timer(r->event, (ngx_msec_t) (r->resend_timeout * 1000));
+    }
+
+    rn->expire = ngx_time() + r->resend_timeout;
+
+    ngx_queue_insert_head(resend_queue, &rn->queue);
+
+    rn->code = 0;
+    rn->cnlen = 0;
+    rn->valid = 0;
+    rn->ttl = NGX_MAX_UINT32_VALUE;
+    rn->waiting = ctx;
+
+    ctx->state = NGX_AGAIN;
+    ctx->async = 1;
+
+    do {
+        ctx->node = rn;
+        ctx = ctx->next;
+    } while (ctx);
+
+    return NGX_AGAIN;
+
+failed:
+
+    ngx_rbtree_delete(tree, &rn->node);
+
+    if (rn->query) {
+        ngx_resolver_free(r, rn->query);
+    }
+
+    ngx_resolver_free(r, rn->name);
+
+    ngx_resolver_free(r, rn);
+
+    return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_resolve_addr(ngx_resolver_ctx_t *ctx)
+{
+    u_char               *name;
+    in_addr_t             addr;
+    ngx_queue_t          *resend_queue, *expire_queue;
+    ngx_rbtree_t         *tree;
+    ngx_resolver_t       *r;
+    struct sockaddr_in   *sin;
+    ngx_resolver_node_t  *rn;
+#if (NGX_HAVE_INET6)
+    uint32_t              hash;
+    struct sockaddr_in6  *sin6;
+#endif
+
+#if (NGX_SUPPRESS_WARN)
+    addr = 0;
+#if (NGX_HAVE_INET6)
+    hash = 0;
+    sin6 = NULL;
+#endif
+#endif
+
+    r = ctx->resolver;
+
+    switch (ctx->addr.sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+    case AF_INET6:
+        sin6 = (struct sockaddr_in6 *) ctx->addr.sockaddr;
+        hash = ngx_crc32_short(sin6->sin6_addr.s6_addr, 16);
+
+        /* lock addr mutex */
+
+        rn = ngx_resolver_lookup_addr6(r, &sin6->sin6_addr, hash);
+
+        tree = &r->addr6_rbtree;
+        resend_queue = &r->addr6_resend_queue;
+        expire_queue = &r->addr6_expire_queue;
+
+        break;
+#endif
+
+    default: /* AF_INET */
+        sin = (struct sockaddr_in *) ctx->addr.sockaddr;
+        addr = ntohl(sin->sin_addr.s_addr);
+
+        /* lock addr mutex */
+
+        rn = ngx_resolver_lookup_addr(r, addr);
+
+        tree = &r->addr_rbtree;
+        resend_queue = &r->addr_resend_queue;
+        expire_queue = &r->addr_expire_queue;
+    }
+
+    if (rn) {
+
+        if (rn->valid >= ngx_time()) {
+
+            ngx_log_debug0(NGX_LOG_DEBUG_CORE, r->log, 0, "resolve cached");
+
+            ngx_queue_remove(&rn->queue);
+
+            rn->expire = ngx_time() + r->expire;
+
+            ngx_queue_insert_head(expire_queue, &rn->queue);
+
+            name = ngx_resolver_dup(r, rn->name, rn->nlen);
+            if (name == NULL) {
+                goto failed;
+            }
+
+            ctx->name.len = rn->nlen;
+            ctx->name.data = name;
+
+            /* unlock addr mutex */
+
+            ctx->state = NGX_OK;
+            ctx->valid = rn->valid;
+
+            ctx->handler(ctx);
+
+            ngx_resolver_free(r, name);
+
+            return NGX_OK;
+        }
+
+        if (rn->waiting) {
+            if (ngx_resolver_set_timeout(r, ctx) != NGX_OK) {
+                return NGX_ERROR;
+            }
+
+            ctx->next = rn->waiting;
+            rn->waiting = ctx;
+            ctx->state = NGX_AGAIN;
+            ctx->async = 1;
+            ctx->node = rn;
+
+            /* unlock addr mutex */
+
+            return NGX_OK;
+        }
+
+        ngx_queue_remove(&rn->queue);
+
+        ngx_resolver_free(r, rn->query);
+        rn->query = NULL;
+#if (NGX_HAVE_INET6)
+        rn->query6 = NULL;
+#endif
+
+    } else {
+        rn = ngx_resolver_alloc(r, sizeof(ngx_resolver_node_t));
+        if (rn == NULL) {
+            goto failed;
+        }
+
+        switch (ctx->addr.sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+        case AF_INET6:
+            rn->addr6 = sin6->sin6_addr;
+            rn->node.key = hash;
+            break;
+#endif
+
+        default: /* AF_INET */
+            rn->node.key = addr;
+        }
+
+        rn->query = NULL;
+#if (NGX_HAVE_INET6)
+        rn->query6 = NULL;
+#endif
+
+        ngx_rbtree_insert(tree, &rn->node);
+    }
+
+    if (ngx_resolver_create_addr_query(r, rn, &ctx->addr) != NGX_OK) {
+        goto failed;
+    }
+
+    rn->last_connection = r->last_connection++;
+    if (r->last_connection == r->connections.nelts) {
+        r->last_connection = 0;
+    }
+
+    rn->naddrs = (u_short) -1;
+    rn->tcp = 0;
+#if (NGX_HAVE_INET6)
+    rn->naddrs6 = (u_short) -1;
+    rn->tcp6 = 0;
+#endif
+    rn->nsrvs = 0;
+
+    if (ngx_resolver_send_query(r, rn) != NGX_OK) {
+        goto failed;
+    }
+
+    if (ngx_resolver_set_timeout(r, ctx) != NGX_OK) {
+        goto failed;
+    }
+
+    if (ngx_resolver_resend_empty(r)) {
+        ngx_add_timer(r->event, (ngx_msec_t) (r->resend_timeout * 1000));
+    }
+
+    rn->expire = ngx_time() + r->resend_timeout;
+
+    ngx_queue_insert_head(resend_queue, &rn->queue);
+
+    rn->code = 0;
+    rn->cnlen = 0;
+    rn->name = NULL;
+    rn->nlen = 0;
+    rn->valid = 0;
+    rn->ttl = NGX_MAX_UINT32_VALUE;
+    rn->waiting = ctx;
+
+    /* unlock addr mutex */
+
+    ctx->state = NGX_AGAIN;
+    ctx->async = 1;
+    ctx->node = rn;
+
+    return NGX_OK;
+
+failed:
+
+    if (rn) {
+        ngx_rbtree_delete(tree, &rn->node);
+
+        if (rn->query) {
+            ngx_resolver_free(r, rn->query);
+        }
+
+        ngx_resolver_free(r, rn);
+    }
+
+    /* unlock addr mutex */
+
+    if (ctx->event) {
+        ngx_resolver_free(r, ctx->event);
+    }
+
+    ngx_resolver_free(r, ctx);
+
+    return NGX_ERROR;
+}
+
+
+void
+ngx_resolve_addr_done(ngx_resolver_ctx_t *ctx)
+{
+    ngx_queue_t          *expire_queue;
+    ngx_rbtree_t         *tree;
+    ngx_resolver_t       *r;
+    ngx_resolver_ctx_t   *w, **p;
+    ngx_resolver_node_t  *rn;
+
+    r = ctx->resolver;
+
+    switch (ctx->addr.sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+    case AF_INET6:
+        tree = &r->addr6_rbtree;
+        expire_queue = &r->addr6_expire_queue;
+        break;
+#endif
+
+    default: /* AF_INET */
+        tree = &r->addr_rbtree;
+        expire_queue = &r->addr_expire_queue;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, r->log, 0,
+                   "resolve addr done: %i", ctx->state);
+
+    if (ctx->event && ctx->event->timer_set) {
+        ngx_del_timer(ctx->event);
+    }
+
+    /* lock addr mutex */
+
+    if (ctx->state == NGX_AGAIN || ctx->state == NGX_RESOLVE_TIMEDOUT) {
+
+        rn = ctx->node;
+
+        if (rn) {
+            p = &rn->waiting;
+            w = rn->waiting;
+
+            while (w) {
+                if (w == ctx) {
+                    *p = w->next;
+
+                    goto done;
+                }
+
+                p = &w->next;
+                w = w->next;
+            }
+        }
+
+        {
+            u_char     text[NGX_SOCKADDR_STRLEN];
+            ngx_str_t  addrtext;
+
+            addrtext.data = text;
+            addrtext.len = ngx_sock_ntop(ctx->addr.sockaddr, ctx->addr.socklen,
+                                         text, NGX_SOCKADDR_STRLEN, 0);
+
+            ngx_log_error(NGX_LOG_ALERT, r->log, 0,
+                          "could not cancel %V resolving", &addrtext);
+        }
+    }
+
+done:
+
+    ngx_resolver_expire(r, tree, expire_queue);
+
+    /* unlock addr mutex */
+
+    /* lock alloc mutex */
+
+    if (ctx->event) {
+        ngx_resolver_free_locked(r, ctx->event);
+    }
+
+    ngx_resolver_free_locked(r, ctx);
+
+    /* unlock alloc mutex */
+
+    if (r->event->timer_set && ngx_resolver_resend_empty(r)) {
+        ngx_del_timer(r->event);
+    }
+}
+
+
+static void
+ngx_resolver_expire(ngx_resolver_t *r, ngx_rbtree_t *tree, ngx_queue_t *queue)
+{
+    time_t                now;
+    ngx_uint_t            i;
+    ngx_queue_t          *q;
+    ngx_resolver_node_t  *rn;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_CORE, r->log, 0, "resolver expire");
+
+    now = ngx_time();
+
+    for (i = 0; i < 2; i++) {
+        if (ngx_queue_empty(queue)) {
+            return;
+        }
+
+        q = ngx_queue_last(queue);
+
+        rn = ngx_queue_data(q, ngx_resolver_node_t, queue);
+
+        if (now <= rn->expire) {
+            return;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_CORE, r->log, 0,
+                       "resolver expire \"%*s\"", (size_t) rn->nlen, rn->name);
+
+        ngx_queue_remove(q);
+
+        ngx_rbtree_delete(tree, &rn->node);
+
+        ngx_resolver_free_node(r, rn);
+    }
+}
+
+
+static ngx_int_t
+ngx_resolver_send_query(ngx_resolver_t *r, ngx_resolver_node_t *rn)
+{
+    ngx_int_t                   rc;
+    ngx_resolver_connection_t  *rec;
+
+    rec = r->connections.elts;
+    rec = &rec[rn->last_connection];
+
+    if (rec->log.handler == NULL) {
+        rec->log = *r->log;
+        rec->log.handler = ngx_resolver_log_error;
+        rec->log.data = rec;
+        rec->log.action = "resolving";
+    }
+
+    if (rn->naddrs == (u_short) -1) {
+        rc = rn->tcp ? ngx_resolver_send_tcp_query(r, rec, rn->query, rn->qlen)
+                     : ngx_resolver_send_udp_query(r, rec, rn->query, rn->qlen);
+
+        if (rc != NGX_OK) {
+            return rc;
+        }
+    }
+
+#if (NGX_HAVE_INET6)
+
+    if (rn->query6 && rn->naddrs6 == (u_short) -1) {
+        rc = rn->tcp6
+                    ? ngx_resolver_send_tcp_query(r, rec, rn->query6, rn->qlen)
+                    : ngx_resolver_send_udp_query(r, rec, rn->query6, rn->qlen);
+
+        if (rc != NGX_OK) {
+            return rc;
+        }
+    }
+
+#endif
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_resolver_send_udp_query(ngx_resolver_t *r, ngx_resolver_connection_t  *rec,
+    u_char *query, u_short qlen)
+{
+    ssize_t  n;
+
+    if (rec->udp == NULL) {
+        if (ngx_udp_connect(rec) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        rec->udp->data = rec;
+        rec->udp->read->handler = ngx_resolver_udp_read;
+        rec->udp->read->resolver = 1;
+    }
+
+    n = ngx_send(rec->udp, query, qlen);
+
+    if (n == -1) {
+        return NGX_ERROR;
+    }
+
+    if ((size_t) n != (size_t) qlen) {
+        ngx_log_error(NGX_LOG_CRIT, &rec->log, 0, "send() incomplete");
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_resolver_send_tcp_query(ngx_resolver_t *r, ngx_resolver_connection_t *rec,
+    u_char *query, u_short qlen)
+{
+    ngx_buf_t  *b;
+    ngx_int_t   rc;
+
+    rc = NGX_OK;
+
+    if (rec->tcp == NULL) {
+        b = rec->read_buf;
+
+        if (b == NULL) {
+            b = ngx_resolver_calloc(r, sizeof(ngx_buf_t));
+            if (b == NULL) {
+                return NGX_ERROR;
+            }
+
+            b->start = ngx_resolver_alloc(r, NGX_RESOLVER_TCP_RSIZE);
+            if (b->start == NULL) {
+                ngx_resolver_free(r, b);
+                return NGX_ERROR;
+            }
+
+            b->end = b->start + NGX_RESOLVER_TCP_RSIZE;
+
+            rec->read_buf = b;
+        }
+
+        b->pos = b->start;
+        b->last = b->start;
+
+        b = rec->write_buf;
+
+        if (b == NULL) {
+            b = ngx_resolver_calloc(r, sizeof(ngx_buf_t));
+            if (b == NULL) {
+                return NGX_ERROR;
+            }
+
+            b->start = ngx_resolver_alloc(r, NGX_RESOLVER_TCP_WSIZE);
+            if (b->start == NULL) {
+                ngx_resolver_free(r, b);
+                return NGX_ERROR;
+            }
+
+            b->end = b->start + NGX_RESOLVER_TCP_WSIZE;
+
+            rec->write_buf = b;
+        }
+
+        b->pos = b->start;
+        b->last = b->start;
+
+        rc = ngx_tcp_connect(rec);
+        if (rc == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+
+        rec->tcp->data = rec;
+        rec->tcp->write->handler = ngx_resolver_tcp_write;
+        rec->tcp->read->handler = ngx_resolver_tcp_read;
+        rec->tcp->read->resolver = 1;
+
+        ngx_add_timer(rec->tcp->write, (ngx_msec_t) (r->tcp_timeout * 1000));
+    }
+
+    b = rec->write_buf;
+
+    if (b->end - b->last <  2 + qlen) {
+        ngx_log_error(NGX_LOG_CRIT, &rec->log, 0, "buffer overflow");
+        return NGX_ERROR;
+    }
+
+    *b->last++ = (u_char) (qlen >> 8);
+    *b->last++ = (u_char) qlen;
+    b->last = ngx_cpymem(b->last, query, qlen);
+
+    if (rc == NGX_OK) {
+        ngx_resolver_tcp_write(rec->tcp->write);
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_resolver_resend_handler(ngx_event_t *ev)
+{
+    time_t           timer, atimer, stimer, ntimer;
+#if (NGX_HAVE_INET6)
+    time_t           a6timer;
+#endif
+    ngx_resolver_t  *r;
+
+    r = ev->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_CORE, r->log, 0,
+                   "resolver resend handler");
+
+    /* lock name mutex */
+
+    ntimer = ngx_resolver_resend(r, &r->name_rbtree, &r->name_resend_queue);
+
+    stimer = ngx_resolver_resend(r, &r->srv_rbtree, &r->srv_resend_queue);
+
+    /* unlock name mutex */
+
+    /* lock addr mutex */
+
+    atimer = ngx_resolver_resend(r, &r->addr_rbtree, &r->addr_resend_queue);
+
+    /* unlock addr mutex */
+
+#if (NGX_HAVE_INET6)
+
+    /* lock addr6 mutex */
+
+    a6timer = ngx_resolver_resend(r, &r->addr6_rbtree, &r->addr6_resend_queue);
+
+    /* unlock addr6 mutex */
+
+#endif
+
+    timer = ntimer;
+
+    if (timer == 0) {
+        timer = atimer;
+
+    } else if (atimer) {
+        timer = ngx_min(timer, atimer);
+    }
+
+    if (timer == 0) {
+        timer = stimer;
+
+    } else if (stimer) {
+        timer = ngx_min(timer, stimer);
+    }
+
+#if (NGX_HAVE_INET6)
+
+    if (timer == 0) {
+        timer = a6timer;
+
+    } else if (a6timer) {
+        timer = ngx_min(timer, a6timer);
+    }
+
+#endif
+
+    if (timer) {
+        ngx_add_timer(r->event, (ngx_msec_t) (timer * 1000));
+    }
+}
+
+
+static time_t
+ngx_resolver_resend(ngx_resolver_t *r, ngx_rbtree_t *tree, ngx_queue_t *queue)
+{
+    time_t                now;
+    ngx_queue_t          *q;
+    ngx_resolver_node_t  *rn;
+
+    now = ngx_time();
+
+    for ( ;; ) {
+        if (ngx_queue_empty(queue)) {
+            return 0;
+        }
+
+        q = ngx_queue_last(queue);
+
+        rn = ngx_queue_data(q, ngx_resolver_node_t, queue);
+
+        if (now < rn->expire) {
+            return rn->expire - now;
+        }
+
+        ngx_log_debug3(NGX_LOG_DEBUG_CORE, r->log, 0,
+                       "resolver resend \"%*s\" %p",
+                       (size_t) rn->nlen, rn->name, rn->waiting);
+
+        ngx_queue_remove(q);
+
+        if (rn->waiting) {
+
+            if (++rn->last_connection == r->connections.nelts) {
+                rn->last_connection = 0;
+            }
+
+            (void) ngx_resolver_send_query(r, rn);
+
+            rn->expire = now + r->resend_timeout;
+
+            ngx_queue_insert_head(queue, q);
+
+            continue;
+        }
+
+        ngx_rbtree_delete(tree, &rn->node);
+
+        ngx_resolver_free_node(r, rn);
+    }
+}
+
+
+static ngx_uint_t
+ngx_resolver_resend_empty(ngx_resolver_t *r)
+{
+    return ngx_queue_empty(&r->name_resend_queue)
+           && ngx_queue_empty(&r->srv_resend_queue)
+#if (NGX_HAVE_INET6)
+           && ngx_queue_empty(&r->addr6_resend_queue)
+#endif
+           && ngx_queue_empty(&r->addr_resend_queue);
+}
+
+
+static void
+ngx_resolver_udp_read(ngx_event_t *rev)
+{
+    ssize_t                     n;
+    ngx_connection_t           *c;
+    ngx_resolver_connection_t  *rec;
+    u_char                      buf[NGX_RESOLVER_UDP_SIZE];
+
+    c = rev->data;
+    rec = c->data;
+
+    do {
+        n = ngx_udp_recv(c, buf, NGX_RESOLVER_UDP_SIZE);
+
+        if (n < 0) {
+            return;
+        }
+
+        ngx_resolver_process_response(rec->resolver, buf, n, 0);
+
+    } while (rev->ready);
+}
+
+
+static void
+ngx_resolver_tcp_write(ngx_event_t *wev)
+{
+    off_t                       sent;
+    ssize_t                     n;
+    ngx_buf_t                  *b;
+    ngx_resolver_t             *r;
+    ngx_connection_t           *c;
+    ngx_resolver_connection_t  *rec;
+
+    c = wev->data;
+    rec = c->data;
+    b = rec->write_buf;
+    r = rec->resolver;
+
+    if (wev->timedout) {
+        goto failed;
+    }
+
+    sent = c->sent;
+
+    while (wev->ready && b->pos < b->last) {
+        n = ngx_send(c, b->pos, b->last - b->pos);
+
+        if (n == NGX_AGAIN) {
+            break;
+        }
+
+        if (n == NGX_ERROR) {
+            goto failed;
+        }
+
+        b->pos += n;
+    }
+
+    if (b->pos != b->start) {
+        b->last = ngx_movemem(b->start, b->pos, b->last - b->pos);
+        b->pos = b->start;
+    }
+
+    if (c->sent != sent) {
+        ngx_add_timer(wev, (ngx_msec_t) (r->tcp_timeout * 1000));
+    }
+
+    if (ngx_handle_write_event(wev, 0) != NGX_OK) {
+        goto failed;
+    }
+
+    return;
+
+failed:
+
+    ngx_close_connection(c);
+    rec->tcp = NULL;
+}
+
+
+static void
+ngx_resolver_tcp_read(ngx_event_t *rev)
+{
+    u_char                     *p;
+    size_t                      size;
+    ssize_t                     n;
+    u_short                     qlen;
+    ngx_buf_t                  *b;
+    ngx_resolver_t             *r;
+    ngx_connection_t           *c;
+    ngx_resolver_connection_t  *rec;
+
+    c = rev->data;
+    rec = c->data;
+    b = rec->read_buf;
+    r = rec->resolver;
+
+    while (rev->ready) {
+        n = ngx_recv(c, b->last, b->end - b->last);
+
+        if (n == NGX_AGAIN) {
+            break;
+        }
+
+        if (n == NGX_ERROR || n == 0) {
+            goto failed;
+        }
+
+        b->last += n;
+
+        for ( ;; ) {
+            p = b->pos;
+            size = b->last - p;
+
+            if (size < 2) {
+                break;
+            }
+
+            qlen = (u_short) *p++ << 8;
+            qlen += *p++;
+
+            if (size < (size_t) (2 + qlen)) {
+                break;
+            }
+
+            ngx_resolver_process_response(r, p, qlen, 1);
+
+            b->pos += 2 + qlen;
+        }
+
+        if (b->pos != b->start) {
+            b->last = ngx_movemem(b->start, b->pos, b->last - b->pos);
+            b->pos = b->start;
+        }
+    }
+
+    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+        goto failed;
+    }
+
+    return;
+
+failed:
+
+    ngx_close_connection(c);
+    rec->tcp = NULL;
+}
+
+
+static void
+ngx_resolver_process_response(ngx_resolver_t *r, u_char *buf, size_t n,
+    ngx_uint_t tcp)
+{
+    char                 *err;
+    ngx_uint_t            i, times, ident, qident, flags, code, nqs, nan, trunc,
+                          qtype, qclass;
+#if (NGX_HAVE_INET6)
+    ngx_uint_t            qident6;
+#endif
+    ngx_queue_t          *q;
+    ngx_resolver_qs_t    *qs;
+    ngx_resolver_hdr_t   *response;
+    ngx_resolver_node_t  *rn;
+
+    if (n < sizeof(ngx_resolver_hdr_t)) {
+        goto short_response;
+    }
+
+    response = (ngx_resolver_hdr_t *) buf;
+
+    ident = (response->ident_hi << 8) + response->ident_lo;
+    flags = (response->flags_hi << 8) + response->flags_lo;
+    nqs = (response->nqs_hi << 8) + response->nqs_lo;
+    nan = (response->nan_hi << 8) + response->nan_lo;
+    trunc = flags & 0x0200;
+
+    ngx_log_debug6(NGX_LOG_DEBUG_CORE, r->log, 0,
+                   "resolver DNS response %ui fl:%04Xi %ui/%ui/%ud/%ud",
+                   ident, flags, nqs, nan,
+                   (response->nns_hi << 8) + response->nns_lo,
+                   (response->nar_hi << 8) + response->nar_lo);
+
+    /* response to a standard query */
+    if ((flags & 0xf870) != 0x8000 || (trunc && tcp)) {
+        ngx_log_error(r->log_level, r->log, 0,
+                      "invalid %s DNS response %ui fl:%04Xi",
+                      tcp ? "TCP" : "UDP", ident, flags);
+        return;
+    }
+
+    code = flags & 0xf;
+
+    if (code == NGX_RESOLVE_FORMERR) {
+
+        times = 0;
+
+        for (q = ngx_queue_head(&r->name_resend_queue);
+             q != ngx_queue_sentinel(&r->name_resend_queue) && times++ < 100;
+             q = ngx_queue_next(q))
+        {
+            rn = ngx_queue_data(q, ngx_resolver_node_t, queue);
+            qident = (rn->query[0] << 8) + rn->query[1];
+
+            if (qident == ident) {
+                goto dns_error_name;
+            }
+
+#if (NGX_HAVE_INET6)
+            if (rn->query6) {
+                qident6 = (rn->query6[0] << 8) + rn->query6[1];
+
+                if (qident6 == ident) {
+                    goto dns_error_name;
+                }
+            }
+#endif
+        }
+
+        goto dns_error;
+    }
+
+    if (code > NGX_RESOLVE_REFUSED) {
+        goto dns_error;
+    }
+
+    if (nqs != 1) {
+        err = "invalid number of questions in DNS response";
+        goto done;
+    }
+
+    i = sizeof(ngx_resolver_hdr_t);
+
+    while (i < (ngx_uint_t) n) {
+        if (buf[i] == '\0') {
+            goto found;
+        }
+
+        i += 1 + buf[i];
+    }
+
+    goto short_response;
+
+found:
+
+    if (i++ == sizeof(ngx_resolver_hdr_t)) {
+        err = "zero-length domain name in DNS response";
+        goto done;
+    }
+
+    if (i + sizeof(ngx_resolver_qs_t) + nan * (2 + sizeof(ngx_resolver_an_t))
+        > (ngx_uint_t) n)
+    {
+        goto short_response;
+    }
+
+    qs = (ngx_resolver_qs_t *) &buf[i];
+
+    qtype = (qs->type_hi << 8) + qs->type_lo;
+    qclass = (qs->class_hi << 8) + qs->class_lo;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_CORE, r->log, 0,
+                   "resolver DNS response qt:%ui cl:%ui", qtype, qclass);
+
+    if (qclass != 1) {
+        ngx_log_error(r->log_level, r->log, 0,
+                      "unknown query class %ui in DNS response", qclass);
+        return;
+    }
+
+    switch (qtype) {
+
+    case NGX_RESOLVE_A:
+#if (NGX_HAVE_INET6)
+    case NGX_RESOLVE_AAAA:
+#endif
+
+        ngx_resolver_process_a(r, buf, n, ident, code, qtype, nan, trunc,
+                               i + sizeof(ngx_resolver_qs_t));
+
+        break;
+
+    case NGX_RESOLVE_SRV:
+
+        ngx_resolver_process_srv(r, buf, n, ident, code, nan, trunc,
+                                 i + sizeof(ngx_resolver_qs_t));
+
+        break;
+
+    case NGX_RESOLVE_PTR:
+
+        ngx_resolver_process_ptr(r, buf, n, ident, code, nan);
+
+        break;
+
+    default:
+        ngx_log_error(r->log_level, r->log, 0,
+                      "unknown query type %ui in DNS response", qtype);
+        return;
+    }
+
+    return;
+
+short_response:
+
+    err = "short DNS response";
+
+done:
+
+    ngx_log_error(r->log_level, r->log, 0, err);
+
+    return;
+
+dns_error_name:
+
+    ngx_log_error(r->log_level, r->log, 0,
+                  "DNS error (%ui: %s), query id:%ui, name:\"%*s\"",
+                  code, ngx_resolver_strerror(code), ident,
+                  (size_t) rn->nlen, rn->name);
+    return;
+
+dns_error:
+
+    ngx_log_error(r->log_level, r->log, 0,
+                  "DNS error (%ui: %s), query id:%ui",
+                  code, ngx_resolver_strerror(code), ident);
+    return;
+}
+
+
+static void
+ngx_resolver_process_a(ngx_resolver_t *r, u_char *buf, size_t n,
+    ngx_uint_t ident, ngx_uint_t code, ngx_uint_t qtype,
+    ngx_uint_t nan, ngx_uint_t trunc, ngx_uint_t ans)
+{
+    char                       *err;
+    u_char                     *cname;
+    size_t                      len;
+    int32_t                     ttl;
+    uint32_t                    hash;
+    in_addr_t                  *addr;
+    ngx_str_t                   name;
+    ngx_uint_t                  type, class, qident, naddrs, a, i, j, start;
+#if (NGX_HAVE_INET6)
+    struct in6_addr            *addr6;
+#endif
+    ngx_resolver_an_t          *an;
+    ngx_resolver_ctx_t         *ctx, *next;
+    ngx_resolver_node_t        *rn;
+    ngx_resolver_addr_t        *addrs;
+    ngx_resolver_connection_t  *rec;
+
+    if (ngx_resolver_copy(r, &name, buf,
+                          buf + sizeof(ngx_resolver_hdr_t), buf + n)
+        != NGX_OK)
+    {
+        return;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, r->log, 0, "resolver qs:%V", &name);
+
+    hash = ngx_crc32_short(name.data, name.len);
+
+    /* lock name mutex */
+
+    rn = ngx_resolver_lookup_name(r, &name, hash);
+
+    if (rn == NULL) {
+        ngx_log_error(r->log_level, r->log, 0,
+                      "unexpected response for %V", &name);
+        ngx_resolver_free(r, name.data);
+        goto failed;
+    }
+
+    switch (qtype) {
+
+#if (NGX_HAVE_INET6)
+    case NGX_RESOLVE_AAAA:
+
+        if (rn->query6 == NULL || rn->naddrs6 != (u_short) -1) {
+            ngx_log_error(r->log_level, r->log, 0,
+                          "unexpected response for %V", &name);
+            ngx_resolver_free(r, name.data);
+            goto failed;
+        }
+
+        if (trunc && rn->tcp6) {
+            ngx_resolver_free(r, name.data);
+            goto failed;
+        }
+
+        qident = (rn->query6[0] << 8) + rn->query6[1];
+
+        break;
+#endif
+
+    default: /* NGX_RESOLVE_A */
+
+        if (rn->query == NULL || rn->naddrs != (u_short) -1) {
+            ngx_log_error(r->log_level, r->log, 0,
+                          "unexpected response for %V", &name);
+            ngx_resolver_free(r, name.data);
+            goto failed;
+        }
+
+        if (trunc && rn->tcp) {
+            ngx_resolver_free(r, name.data);
+            goto failed;
+        }
+
+        qident = (rn->query[0] << 8) + rn->query[1];
+    }
+
+    if (ident != qident) {
+        ngx_log_error(r->log_level, r->log, 0,
+                      "wrong ident %ui response for %V, expect %ui",
+                      ident, &name, qident);
+        ngx_resolver_free(r, name.data);
+        goto failed;
+    }
+
+    ngx_resolver_free(r, name.data);
+
+    if (trunc) {
+
+        ngx_queue_remove(&rn->queue);
+
+        if (rn->waiting == NULL) {
+            ngx_rbtree_delete(&r->name_rbtree, &rn->node);
+            ngx_resolver_free_node(r, rn);
+            goto next;
+        }
+
+        rec = r->connections.elts;
+        rec = &rec[rn->last_connection];
+
+        switch (qtype) {
+
+#if (NGX_HAVE_INET6)
+        case NGX_RESOLVE_AAAA:
+
+            rn->tcp6 = 1;
+
+            (void) ngx_resolver_send_tcp_query(r, rec, rn->query6, rn->qlen);
+
+            break;
+#endif
+
+        default: /* NGX_RESOLVE_A */
+
+            rn->tcp = 1;
+
+            (void) ngx_resolver_send_tcp_query(r, rec, rn->query, rn->qlen);
+        }
+
+        rn->expire = ngx_time() + r->resend_timeout;
+
+        ngx_queue_insert_head(&r->name_resend_queue, &rn->queue);
+
+        goto next;
+    }
+
+    if (code == 0 && rn->code) {
+        code = rn->code;
+    }
+
+    if (code == 0 && nan == 0) {
+
+#if (NGX_HAVE_INET6)
+        switch (qtype) {
+
+        case NGX_RESOLVE_AAAA:
+
+            rn->naddrs6 = 0;
+
+            if (rn->naddrs == (u_short) -1) {
+                goto next;
+            }
+
+            if (rn->naddrs) {
+                goto export;
+            }
+
+            break;
+
+        default: /* NGX_RESOLVE_A */
+
+            rn->naddrs = 0;
+
+            if (rn->naddrs6 == (u_short) -1) {
+                goto next;
+            }
+
+            if (rn->naddrs6) {
+                goto export;
+            }
+        }
+#endif
+
+        code = NGX_RESOLVE_NXDOMAIN;
+    }
+
+    if (code) {
+
+#if (NGX_HAVE_INET6)
+        switch (qtype) {
+
+        case NGX_RESOLVE_AAAA:
+
+            rn->naddrs6 = 0;
+
+            if (rn->naddrs == (u_short) -1) {
+                rn->code = (u_char) code;
+                goto next;
+            }
+
+            break;
+
+        default: /* NGX_RESOLVE_A */
+
+            rn->naddrs = 0;
+
+            if (rn->naddrs6 == (u_short) -1) {
+                rn->code = (u_char) code;
+                goto next;
+            }
+        }
+#endif
+
+        next = rn->waiting;
+        rn->waiting = NULL;
+
+        ngx_queue_remove(&rn->queue);
+
+        ngx_rbtree_delete(&r->name_rbtree, &rn->node);
+
+        /* unlock name mutex */
+
+        while (next) {
+            ctx = next;
+            ctx->state = code;
+            ctx->valid = ngx_time() + (r->valid ? r->valid : 10);
+            next = ctx->next;
+
+            ctx->handler(ctx);
+        }
+
+        ngx_resolver_free_node(r, rn);
+
+        return;
+    }
+
+    i = ans;
+    naddrs = 0;
+    cname = NULL;
+
+    for (a = 0; a < nan; a++) {
+
+        start = i;
+
+        while (i < n) {
+
+            if (buf[i] & 0xc0) {
+                i += 2;
+                goto found;
+            }
+
+            if (buf[i] == 0) {
+                i++;
+                goto test_length;
+            }
+
+            i += 1 + buf[i];
+        }
+
+        goto short_response;
+
+    test_length:
+
+        if (i - start < 2) {
+            err = "invalid name in DNS response";
+            goto invalid;
+        }
+
+    found:
+
+        if (i + sizeof(ngx_resolver_an_t) >= n) {
+            goto short_response;
+        }
+
+        an = (ngx_resolver_an_t *) &buf[i];
+
+        type = (an->type_hi << 8) + an->type_lo;
+        class = (an->class_hi << 8) + an->class_lo;
+        len = (an->len_hi << 8) + an->len_lo;
+        ttl = (an->ttl[0] << 24) + (an->ttl[1] << 16)
+            + (an->ttl[2] << 8) + (an->ttl[3]);
+
+        if (class != 1) {
+            ngx_log_error(r->log_level, r->log, 0,
+                          "unexpected RR class %ui", class);
+            goto failed;
+        }
+
+        if (ttl < 0) {
+            ttl = 0;
+        }
+
+        rn->ttl = ngx_min(rn->ttl, (uint32_t) ttl);
+
+        i += sizeof(ngx_resolver_an_t);
+
+        switch (type) {
+
+        case NGX_RESOLVE_A:
+
+            if (qtype != NGX_RESOLVE_A) {
+                err = "unexpected A record in DNS response";
+                goto invalid;
+            }
+
+            if (len != 4) {
+                err = "invalid A record in DNS response";
+                goto invalid;
+            }
+
+            if (i + 4 > n) {
+                goto short_response;
+            }
+
+            naddrs++;
+
+            break;
+
+#if (NGX_HAVE_INET6)
+        case NGX_RESOLVE_AAAA:
+
+            if (qtype != NGX_RESOLVE_AAAA) {
+                err = "unexpected AAAA record in DNS response";
+                goto invalid;
+            }
+
+            if (len != 16) {
+                err = "invalid AAAA record in DNS response";
+                goto invalid;
+            }
+
+            if (i + 16 > n) {
+                goto short_response;
+            }
+
+            naddrs++;
+
+            break;
+#endif
+
+        case NGX_RESOLVE_CNAME:
+
+            cname = &buf[i];
+
+            break;
+
+        case NGX_RESOLVE_DNAME:
+
+            break;
+
+        default:
+
+            ngx_log_error(r->log_level, r->log, 0,
+                          "unexpected RR type %ui", type);
+        }
+
+        i += len;
+    }
+
+    ngx_log_debug3(NGX_LOG_DEBUG_CORE, r->log, 0,
+                   "resolver naddrs:%ui cname:%p ttl:%uD",
+                   naddrs, cname, rn->ttl);
+
+    if (naddrs) {
+
+        switch (qtype) {
+
+#if (NGX_HAVE_INET6)
+        case NGX_RESOLVE_AAAA:
+
+            if (naddrs == 1) {
+                addr6 = &rn->u6.addr6;
+                rn->naddrs6 = 1;
+
+            } else {
+                addr6 = ngx_resolver_alloc(r, naddrs * sizeof(struct in6_addr));
+                if (addr6 == NULL) {
+                    goto failed;
+                }
+
+                rn->u6.addrs6 = addr6;
+                rn->naddrs6 = (u_short) naddrs;
+            }
+
+#if (NGX_SUPPRESS_WARN)
+            addr = NULL;
+#endif
+
+            break;
+#endif
+
+        default: /* NGX_RESOLVE_A */
+
+            if (naddrs == 1) {
+                addr = &rn->u.addr;
+                rn->naddrs = 1;
+
+            } else {
+                addr = ngx_resolver_alloc(r, naddrs * sizeof(in_addr_t));
+                if (addr == NULL) {
+                    goto failed;
+                }
+
+                rn->u.addrs = addr;
+                rn->naddrs = (u_short) naddrs;
+            }
+
+#if (NGX_HAVE_INET6 && NGX_SUPPRESS_WARN)
+            addr6 = NULL;
+#endif
+        }
+
+        j = 0;
+        i = ans;
+
+        for (a = 0; a < nan; a++) {
+
+            for ( ;; ) {
+
+                if (buf[i] & 0xc0) {
+                    i += 2;
+                    break;
+                }
+
+                if (buf[i] == 0) {
+                    i++;
+                    break;
+                }
+
+                i += 1 + buf[i];
+            }
+
+            an = (ngx_resolver_an_t *) &buf[i];
+
+            type = (an->type_hi << 8) + an->type_lo;
+            len = (an->len_hi << 8) + an->len_lo;
+
+            i += sizeof(ngx_resolver_an_t);
+
+            if (type == NGX_RESOLVE_A) {
+
+                addr[j] = htonl((buf[i] << 24) + (buf[i + 1] << 16)
+                                + (buf[i + 2] << 8) + (buf[i + 3]));
+
+                if (++j == naddrs) {
+
+#if (NGX_HAVE_INET6)
+                    if (rn->naddrs6 == (u_short) -1) {
+                        goto next;
+                    }
+#endif
+
+                    break;
+                }
+            }
+
+#if (NGX_HAVE_INET6)
+            else if (type == NGX_RESOLVE_AAAA) {
+
+                ngx_memcpy(addr6[j].s6_addr, &buf[i], 16);
+
+                if (++j == naddrs) {
+
+                    if (rn->naddrs == (u_short) -1) {
+                        goto next;
+                    }
+
+                    break;
+                }
+            }
+#endif
+
+            i += len;
+        }
+    }
+
+    switch (qtype) {
+
+#if (NGX_HAVE_INET6)
+    case NGX_RESOLVE_AAAA:
+
+        if (rn->naddrs6 == (u_short) -1) {
+            rn->naddrs6 = 0;
+        }
+
+        break;
+#endif
+
+    default: /* NGX_RESOLVE_A */
+
+        if (rn->naddrs == (u_short) -1) {
+            rn->naddrs = 0;
+        }
+    }
+
+    if (rn->naddrs != (u_short) -1
+#if (NGX_HAVE_INET6)
+        && rn->naddrs6 != (u_short) -1
+#endif
+        && rn->naddrs
+#if (NGX_HAVE_INET6)
+           + rn->naddrs6
+#endif
+           > 0)
+    {
+
+#if (NGX_HAVE_INET6)
+    export:
+#endif
+
+        naddrs = rn->naddrs;
+#if (NGX_HAVE_INET6)
+        naddrs += rn->naddrs6;
+#endif
+
+        if (naddrs == 1 && rn->naddrs == 1) {
+            addrs = NULL;
+
+        } else {
+            addrs = ngx_resolver_export(r, rn, 0);
+            if (addrs == NULL) {
+                goto failed;
+            }
+        }
+
+        ngx_queue_remove(&rn->queue);
+
+        rn->valid = ngx_time() + (r->valid ? r->valid : (time_t) rn->ttl);
+        rn->expire = ngx_time() + r->expire;
+
+        ngx_queue_insert_head(&r->name_expire_queue, &rn->queue);
+
+        next = rn->waiting;
+        rn->waiting = NULL;
+
+        /* unlock name mutex */
+
+        while (next) {
+            ctx = next;
+            ctx->state = NGX_OK;
+            ctx->valid = rn->valid;
+            ctx->naddrs = naddrs;
+
+            if (addrs == NULL) {
+                ctx->addrs = &ctx->addr;
+                ctx->addr.sockaddr = (struct sockaddr *) &ctx->sin;
+                ctx->addr.socklen = sizeof(struct sockaddr_in);
+                ngx_memzero(&ctx->sin, sizeof(struct sockaddr_in));
+                ctx->sin.sin_family = AF_INET;
+                ctx->sin.sin_addr.s_addr = rn->u.addr;
+
+            } else {
+                ctx->addrs = addrs;
+            }
+
+            next = ctx->next;
+
+            ctx->handler(ctx);
+        }
+
+        if (addrs != NULL) {
+            ngx_resolver_free(r, addrs->sockaddr);
+            ngx_resolver_free(r, addrs);
+        }
+
+        ngx_resolver_free(r, rn->query);
+        rn->query = NULL;
+#if (NGX_HAVE_INET6)
+        rn->query6 = NULL;
+#endif
+
+        return;
+    }
+
+    if (cname) {
+
+        /* CNAME only */
+
+        if (rn->naddrs == (u_short) -1
+#if (NGX_HAVE_INET6)
+            || rn->naddrs6 == (u_short) -1
+#endif
+            )
+        {
+            goto next;
+        }
+
+        if (ngx_resolver_copy(r, &name, buf, cname, buf + n) != NGX_OK) {
+            goto failed;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, r->log, 0,
+                       "resolver cname:\"%V\"", &name);
+
+        ngx_queue_remove(&rn->queue);
+
+        rn->cnlen = (u_short) name.len;
+        rn->u.cname = name.data;
+
+        rn->valid = ngx_time() + (r->valid ? r->valid : (time_t) rn->ttl);
+        rn->expire = ngx_time() + r->expire;
+
+        ngx_queue_insert_head(&r->name_expire_queue, &rn->queue);
+
+        ngx_resolver_free(r, rn->query);
+        rn->query = NULL;
+#if (NGX_HAVE_INET6)
+        rn->query6 = NULL;
+#endif
+
+        ctx = rn->waiting;
+        rn->waiting = NULL;
+
+        if (ctx) {
+
+            if (ctx->recursion++ >= NGX_RESOLVER_MAX_RECURSION) {
+
+                /* unlock name mutex */
+
+                do {
+                    ctx->state = NGX_RESOLVE_NXDOMAIN;
+                    next = ctx->next;
+
+                    ctx->handler(ctx);
+
+                    ctx = next;
+                } while (ctx);
+
+                return;
+            }
+
+            for (next = ctx; next; next = next->next) {
+                next->node = NULL;
+            }
+
+            (void) ngx_resolve_name_locked(r, ctx, &name);
+        }
+
+        /* unlock name mutex */
+
+        return;
+    }
+
+    ngx_log_error(r->log_level, r->log, 0,
+                  "no A or CNAME types in DNS response");
+    return;
+
+short_response:
+
+    err = "short DNS response";
+
+invalid:
+
+    /* unlock name mutex */
+
+    ngx_log_error(r->log_level, r->log, 0, err);
+
+    return;
+
+failed:
+
+next:
+
+    /* unlock name mutex */
+
+    return;
+}
+
+
+static void
+ngx_resolver_process_srv(ngx_resolver_t *r, u_char *buf, size_t n,
+    ngx_uint_t ident, ngx_uint_t code, ngx_uint_t nan,
+    ngx_uint_t trunc, ngx_uint_t ans)
+{
+    char                       *err;
+    u_char                     *cname;
+    size_t                      len;
+    int32_t                     ttl;
+    uint32_t                    hash;
+    ngx_str_t                   name;
+    ngx_uint_t                  type, qident, class, start, nsrvs, a, i, j;
+    ngx_resolver_an_t          *an;
+    ngx_resolver_ctx_t         *ctx, *next;
+    ngx_resolver_srv_t         *srvs;
+    ngx_resolver_node_t        *rn;
+    ngx_resolver_connection_t  *rec;
+
+    if (ngx_resolver_copy(r, &name, buf,
+                          buf + sizeof(ngx_resolver_hdr_t), buf + n)
+        != NGX_OK)
+    {
+        return;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, r->log, 0, "resolver qs:%V", &name);
+
+    hash = ngx_crc32_short(name.data, name.len);
+
+    rn = ngx_resolver_lookup_srv(r, &name, hash);
+
+    if (rn == NULL || rn->query == NULL) {
+        ngx_log_error(r->log_level, r->log, 0,
+                      "unexpected response for %V", &name);
+        ngx_resolver_free(r, name.data);
+        goto failed;
+    }
+
+    if (trunc && rn->tcp) {
+        ngx_resolver_free(r, name.data);
+        goto failed;
+    }
+
+    qident = (rn->query[0] << 8) + rn->query[1];
+
+    if (ident != qident) {
+        ngx_log_error(r->log_level, r->log, 0,
+                      "wrong ident %ui response for %V, expect %ui",
+                      ident, &name, qident);
+        ngx_resolver_free(r, name.data);
+        goto failed;
+    }
+
+    ngx_resolver_free(r, name.data);
+
+    if (trunc) {
+
+        ngx_queue_remove(&rn->queue);
+
+        if (rn->waiting == NULL) {
+            ngx_rbtree_delete(&r->srv_rbtree, &rn->node);
+            ngx_resolver_free_node(r, rn);
+            return;
+        }
+
+        rec = r->connections.elts;
+        rec = &rec[rn->last_connection];
+
+        rn->tcp = 1;
+
+        (void) ngx_resolver_send_tcp_query(r, rec, rn->query, rn->qlen);
+
+        rn->expire = ngx_time() + r->resend_timeout;
+
+        ngx_queue_insert_head(&r->srv_resend_queue, &rn->queue);
+
+        return;
+    }
+
+    if (code == 0 && rn->code) {
+        code = rn->code;
+    }
+
+    if (code == 0 && nan == 0) {
+        code = NGX_RESOLVE_NXDOMAIN;
+    }
+
+    if (code) {
+        next = rn->waiting;
+        rn->waiting = NULL;
+
+        ngx_queue_remove(&rn->queue);
+
+        ngx_rbtree_delete(&r->srv_rbtree, &rn->node);
+
+        while (next) {
+            ctx = next;
+            ctx->state = code;
+            ctx->valid = ngx_time() + (r->valid ? r->valid : 10);
+            next = ctx->next;
+
+            ctx->handler(ctx);
+        }
+
+        ngx_resolver_free_node(r, rn);
+
+        return;
+    }
+
+    i = ans;
+    nsrvs = 0;
+    cname = NULL;
+
+    for (a = 0; a < nan; a++) {
+
+        start = i;
+
+        while (i < n) {
+
+            if (buf[i] & 0xc0) {
+                i += 2;
+                goto found;
+            }
+
+            if (buf[i] == 0) {
+                i++;
+                goto test_length;
+            }
+
+            i += 1 + buf[i];
+        }
+
+        goto short_response;
+
+    test_length:
+
+        if (i - start < 2) {
+            err = "invalid name DNS response";
+            goto invalid;
+        }
+
+    found:
+
+        if (i + sizeof(ngx_resolver_an_t) >= n) {
+            goto short_response;
+        }
+
+        an = (ngx_resolver_an_t *) &buf[i];
+
+        type = (an->type_hi << 8) + an->type_lo;
+        class = (an->class_hi << 8) + an->class_lo;
+        len = (an->len_hi << 8) + an->len_lo;
+        ttl = (an->ttl[0] << 24) + (an->ttl[1] << 16)
+            + (an->ttl[2] << 8) + (an->ttl[3]);
+
+        if (class != 1) {
+            ngx_log_error(r->log_level, r->log, 0,
+                          "unexpected RR class %ui", class);
+            goto failed;
+        }
+
+        if (ttl < 0) {
+            ttl = 0;
+        }
+
+        rn->ttl = ngx_min(rn->ttl, (uint32_t) ttl);
+
+        i += sizeof(ngx_resolver_an_t);
+
+        switch (type) {
+
+        case NGX_RESOLVE_SRV:
+
+            if (i + 6 > n) {
+                goto short_response;
+            }
+
+            if (ngx_resolver_copy(r, NULL, buf, &buf[i + 6], buf + n)
+                != NGX_OK)
+            {
+                goto failed;
+            }
+
+            nsrvs++;
+
+            break;
+
+        case NGX_RESOLVE_CNAME:
+
+            cname = &buf[i];
+
+            break;
+
+        case NGX_RESOLVE_DNAME:
+
+            break;
+
+        default:
+
+            ngx_log_error(r->log_level, r->log, 0,
+                          "unexpected RR type %ui", type);
+        }
+
+        i += len;
+    }
+
+    ngx_log_debug3(NGX_LOG_DEBUG_CORE, r->log, 0,
+                   "resolver nsrvs:%ui cname:%p ttl:%uD",
+                   nsrvs, cname, rn->ttl);
+
+    if (nsrvs) {
+
+        srvs = ngx_resolver_calloc(r, nsrvs * sizeof(ngx_resolver_srv_t));
+        if (srvs == NULL) {
+            goto failed;
+        }
+
+        rn->u.srvs = srvs;
+        rn->nsrvs = (u_short) nsrvs;
+
+        j = 0;
+        i = ans;
+
+        for (a = 0; a < nan; a++) {
+
+            for ( ;; ) {
+
+                if (buf[i] & 0xc0) {
+                    i += 2;
+                    break;
+                }
+
+                if (buf[i] == 0) {
+                    i++;
+                    break;
+                }
+
+                i += 1 + buf[i];
+            }
+
+            an = (ngx_resolver_an_t *) &buf[i];
+
+            type = (an->type_hi << 8) + an->type_lo;
+            len = (an->len_hi << 8) + an->len_lo;
+
+            i += sizeof(ngx_resolver_an_t);
+
+            if (type == NGX_RESOLVE_SRV) {
+
+                srvs[j].priority = (buf[i] << 8) + buf[i + 1];
+                srvs[j].weight = (buf[i + 2] << 8) + buf[i + 3];
+
+                if (srvs[j].weight == 0) {
+                    srvs[j].weight = 1;
+                }
+
+                srvs[j].port = (buf[i + 4] << 8) + buf[i + 5];
+
+                if (ngx_resolver_copy(r, &srvs[j].name, buf, &buf[i + 6],
+                                      buf + n)
+                    != NGX_OK)
+                {
+                    goto failed;
+                }
+
+                j++;
+            }
+
+            i += len;
+        }
+
+        ngx_sort(srvs, nsrvs, sizeof(ngx_resolver_srv_t),
+                 ngx_resolver_cmp_srvs);
+
+        ngx_resolver_free(r, rn->query);
+        rn->query = NULL;
+
+        ngx_queue_remove(&rn->queue);
+
+        rn->valid = ngx_time() + (r->valid ? r->valid : (time_t) rn->ttl);
+        rn->expire = ngx_time() + r->expire;
+
+        ngx_queue_insert_head(&r->srv_expire_queue, &rn->queue);
+
+        next = rn->waiting;
+        rn->waiting = NULL;
+
+        while (next) {
+            ctx = next;
+            next = ctx->next;
+
+            ngx_resolver_resolve_srv_names(ctx, rn);
+        }
+
+        return;
+    }
+
+    rn->nsrvs = 0;
+
+    if (cname) {
+
+        /* CNAME only */
+
+        if (ngx_resolver_copy(r, &name, buf, cname, buf + n) != NGX_OK) {
+            goto failed;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, r->log, 0,
+                       "resolver cname:\"%V\"", &name);
+
+        ngx_queue_remove(&rn->queue);
+
+        rn->cnlen = (u_short) name.len;
+        rn->u.cname = name.data;
+
+        rn->valid = ngx_time() + (r->valid ? r->valid : (time_t) rn->ttl);
+        rn->expire = ngx_time() + r->expire;
+
+        ngx_queue_insert_head(&r->srv_expire_queue, &rn->queue);
+
+        ngx_resolver_free(r, rn->query);
+        rn->query = NULL;
+#if (NGX_HAVE_INET6)
+        rn->query6 = NULL;
+#endif
+
+        ctx = rn->waiting;
+        rn->waiting = NULL;
+
+        if (ctx) {
+
+            if (ctx->recursion++ >= NGX_RESOLVER_MAX_RECURSION) {
+
+                /* unlock name mutex */
+
+                do {
+                    ctx->state = NGX_RESOLVE_NXDOMAIN;
+                    next = ctx->next;
+
+                    ctx->handler(ctx);
+
+                    ctx = next;
+                } while (ctx);
+
+                return;
+            }
+
+            for (next = ctx; next; next = next->next) {
+                next->node = NULL;
+            }
+
+            (void) ngx_resolve_name_locked(r, ctx, &name);
+        }
+
+        /* unlock name mutex */
+
+        return;
+    }
+
+    ngx_log_error(r->log_level, r->log, 0, "no SRV type in DNS response");
+
+    return;
+
+short_response:
+
+    err = "short DNS response";
+
+invalid:
+
+    /* unlock name mutex */
+
+    ngx_log_error(r->log_level, r->log, 0, err);
+
+    return;
+
+failed:
+
+    /* unlock name mutex */
+
+    return;
+}
+
+
+static void
+ngx_resolver_resolve_srv_names(ngx_resolver_ctx_t *ctx, ngx_resolver_node_t *rn)
+{
+    ngx_uint_t                i;
+    ngx_resolver_t           *r;
+    ngx_resolver_ctx_t       *cctx;
+    ngx_resolver_srv_name_t  *srvs;
+
+    r = ctx->resolver;
+
+    ctx->node = NULL;
+    ctx->state = NGX_OK;
+    ctx->valid = rn->valid;
+    ctx->count = rn->nsrvs;
+
+    srvs = ngx_resolver_calloc(r, rn->nsrvs * sizeof(ngx_resolver_srv_name_t));
+    if (srvs == NULL) {
+        goto failed;
+    }
+
+    ctx->srvs = srvs;
+    ctx->nsrvs = rn->nsrvs;
+
+    if (ctx->event && ctx->event->timer_set) {
+        ngx_del_timer(ctx->event);
+    }
+
+    for (i = 0; i < (ngx_uint_t) rn->nsrvs; i++) {
+        srvs[i].name.data = ngx_resolver_alloc(r, rn->u.srvs[i].name.len);
+        if (srvs[i].name.data == NULL) {
+            goto failed;
+        }
+
+        srvs[i].name.len = rn->u.srvs[i].name.len;
+        ngx_memcpy(srvs[i].name.data, rn->u.srvs[i].name.data,
+                   srvs[i].name.len);
+
+        cctx = ngx_resolve_start(r, NULL);
+        if (cctx == NULL) {
+            goto failed;
+        }
+
+        cctx->name = srvs[i].name;
+        cctx->handler = ngx_resolver_srv_names_handler;
+        cctx->data = ctx;
+        cctx->srvs = &srvs[i];
+        cctx->timeout = ctx->timeout;
+
+        srvs[i].priority = rn->u.srvs[i].priority;
+        srvs[i].weight = rn->u.srvs[i].weight;
+        srvs[i].port = rn->u.srvs[i].port;
+        srvs[i].ctx = cctx;
+
+        if (ngx_resolve_name(cctx) == NGX_ERROR) {
+            srvs[i].ctx = NULL;
+            goto failed;
+        }
+    }
+
+    return;
+
+failed:
+
+    ctx->state = NGX_ERROR;
+    ctx->valid = ngx_time() + (r->valid ? r->valid : 10);
+
+    ctx->handler(ctx);
+}
+
+
+static void
+ngx_resolver_srv_names_handler(ngx_resolver_ctx_t *cctx)
+{
+    ngx_uint_t                i;
+    ngx_addr_t               *addrs;
+    ngx_resolver_t           *r;
+    ngx_sockaddr_t           *sockaddr;
+    ngx_resolver_ctx_t       *ctx;
+    ngx_resolver_srv_name_t  *srv;
+
+    r = cctx->resolver;
+    ctx = cctx->data;
+    srv = cctx->srvs;
+
+    ctx->count--;
+    ctx->async |= cctx->async;
+
+    srv->ctx = NULL;
+    srv->state = cctx->state;
+
+    if (cctx->naddrs) {
+
+        ctx->valid = ngx_min(ctx->valid, cctx->valid);
+
+        addrs = ngx_resolver_calloc(r, cctx->naddrs * sizeof(ngx_addr_t));
+        if (addrs == NULL) {
+            srv->state = NGX_ERROR;
+            goto done;
+        }
+
+        sockaddr = ngx_resolver_alloc(r, cctx->naddrs * sizeof(ngx_sockaddr_t));
+        if (sockaddr == NULL) {
+            ngx_resolver_free(r, addrs);
+            srv->state = NGX_ERROR;
+            goto done;
+        }
+
+        for (i = 0; i < cctx->naddrs; i++) {
+            addrs[i].sockaddr = &sockaddr[i].sockaddr;
+            addrs[i].socklen = cctx->addrs[i].socklen;
+
+            ngx_memcpy(&sockaddr[i], cctx->addrs[i].sockaddr,
+                       addrs[i].socklen);
+
+            ngx_inet_set_port(addrs[i].sockaddr, srv->port);
+        }
+
+        srv->addrs = addrs;
+        srv->naddrs = cctx->naddrs;
+    }
+
+done:
+
+    ngx_resolve_name_done(cctx);
+
+    if (ctx->count == 0) {
+        ngx_resolver_report_srv(r, ctx);
+    }
+}
+
+
+static void
+ngx_resolver_process_ptr(ngx_resolver_t *r, u_char *buf, size_t n,
+    ngx_uint_t ident, ngx_uint_t code, ngx_uint_t nan)
+{
+    char                 *err;
+    size_t                len;
+    in_addr_t             addr;
+    int32_t               ttl;
+    ngx_int_t             octet;
+    ngx_str_t             name;
+    ngx_uint_t            mask, type, class, qident, a, i, start;
+    ngx_queue_t          *expire_queue;
+    ngx_rbtree_t         *tree;
+    ngx_resolver_an_t    *an;
+    ngx_resolver_ctx_t   *ctx, *next;
+    ngx_resolver_node_t  *rn;
+#if (NGX_HAVE_INET6)
+    uint32_t              hash;
+    ngx_int_t             digit;
+    struct in6_addr       addr6;
+#endif
+
+    if (ngx_resolver_copy(r, &name, buf,
+                          buf + sizeof(ngx_resolver_hdr_t), buf + n)
+        != NGX_OK)
+    {
+        return;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, r->log, 0, "resolver qs:%V", &name);
+
+    /* AF_INET */
+
+    addr = 0;
+    i = sizeof(ngx_resolver_hdr_t);
+
+    for (mask = 0; mask < 32; mask += 8) {
+        len = buf[i++];
+
+        octet = ngx_atoi(&buf[i], len);
+        if (octet == NGX_ERROR || octet > 255) {
+            goto invalid_in_addr_arpa;
+        }
+
+        addr += octet << mask;
+        i += len;
+    }
+
+    if (ngx_strcasecmp(&buf[i], (u_char *) "\7in-addr\4arpa") == 0) {
+        i += sizeof("\7in-addr\4arpa");
+
+        /* lock addr mutex */
+
+        rn = ngx_resolver_lookup_addr(r, addr);
+
+        tree = &r->addr_rbtree;
+        expire_queue = &r->addr_expire_queue;
+
+        goto valid;
+    }
+
+invalid_in_addr_arpa:
+
+#if (NGX_HAVE_INET6)
+
+    i = sizeof(ngx_resolver_hdr_t);
+
+    for (octet = 15; octet >= 0; octet--) {
+        if (buf[i++] != '\1') {
+            goto invalid_ip6_arpa;
+        }
+
+        digit = ngx_hextoi(&buf[i++], 1);
+        if (digit == NGX_ERROR) {
+            goto invalid_ip6_arpa;
+        }
+
+        addr6.s6_addr[octet] = (u_char) digit;
+
+        if (buf[i++] != '\1') {
+            goto invalid_ip6_arpa;
+        }
+
+        digit = ngx_hextoi(&buf[i++], 1);
+        if (digit == NGX_ERROR) {
+            goto invalid_ip6_arpa;
+        }
+
+        addr6.s6_addr[octet] += (u_char) (digit * 16);
+    }
+
+    if (ngx_strcasecmp(&buf[i], (u_char *) "\3ip6\4arpa") == 0) {
+        i += sizeof("\3ip6\4arpa");
+
+        /* lock addr mutex */
+
+        hash = ngx_crc32_short(addr6.s6_addr, 16);
+        rn = ngx_resolver_lookup_addr6(r, &addr6, hash);
+
+        tree = &r->addr6_rbtree;
+        expire_queue = &r->addr6_expire_queue;
+
+        goto valid;
+    }
+
+invalid_ip6_arpa:
+#endif
+
+    ngx_log_error(r->log_level, r->log, 0,
+                  "invalid in-addr.arpa or ip6.arpa name in DNS response");
+    ngx_resolver_free(r, name.data);
+    return;
+
+valid:
+
+    if (rn == NULL || rn->query == NULL) {
+        ngx_log_error(r->log_level, r->log, 0,
+                      "unexpected response for %V", &name);
+        ngx_resolver_free(r, name.data);
+        goto failed;
+    }
+
+    qident = (rn->query[0] << 8) + rn->query[1];
+
+    if (ident != qident) {
+        ngx_log_error(r->log_level, r->log, 0,
+                      "wrong ident %ui response for %V, expect %ui",
+                      ident, &name, qident);
+        ngx_resolver_free(r, name.data);
+        goto failed;
+    }
+
+    ngx_resolver_free(r, name.data);
+
+    if (code == 0 && nan == 0) {
+        code = NGX_RESOLVE_NXDOMAIN;
+    }
+
+    if (code) {
+        next = rn->waiting;
+        rn->waiting = NULL;
+
+        ngx_queue_remove(&rn->queue);
+
+        ngx_rbtree_delete(tree, &rn->node);
+
+        /* unlock addr mutex */
+
+        while (next) {
+            ctx = next;
+            ctx->state = code;
+            ctx->valid = ngx_time() + (r->valid ? r->valid : 10);
+            next = ctx->next;
+
+            ctx->handler(ctx);
+        }
+
+        ngx_resolver_free_node(r, rn);
+
+        return;
+    }
+
+    i += sizeof(ngx_resolver_qs_t);
+
+    for (a = 0; a < nan; a++) {
+
+        start = i;
+
+        while (i < n) {
+
+            if (buf[i] & 0xc0) {
+                i += 2;
+                goto found;
+            }
+
+            if (buf[i] == 0) {
+                i++;
+                goto test_length;
+            }
+
+            i += 1 + buf[i];
+        }
+
+        goto short_response;
+
+    test_length:
+
+        if (i - start < 2) {
+            err = "invalid name in DNS response";
+            goto invalid;
+        }
+
+    found:
+
+        if (i + sizeof(ngx_resolver_an_t) >= n) {
+            goto short_response;
+        }
+
+        an = (ngx_resolver_an_t *) &buf[i];
+
+        type = (an->type_hi << 8) + an->type_lo;
+        class = (an->class_hi << 8) + an->class_lo;
+        len = (an->len_hi << 8) + an->len_lo;
+        ttl = (an->ttl[0] << 24) + (an->ttl[1] << 16)
+            + (an->ttl[2] << 8) + (an->ttl[3]);
+
+        if (class != 1) {
+            ngx_log_error(r->log_level, r->log, 0,
+                          "unexpected RR class %ui", class);
+            goto failed;
+        }
+
+        if (ttl < 0) {
+            ttl = 0;
+        }
+
+        ngx_log_debug3(NGX_LOG_DEBUG_CORE, r->log, 0,
+                      "resolver qt:%ui cl:%ui len:%uz",
+                      type, class, len);
+
+        i += sizeof(ngx_resolver_an_t);
+
+        switch (type) {
+
+        case NGX_RESOLVE_PTR:
+
+            goto ptr;
+
+        case NGX_RESOLVE_CNAME:
+
+            break;
+
+        default:
+
+            ngx_log_error(r->log_level, r->log, 0,
+                          "unexpected RR type %ui", type);
+        }
+
+        i += len;
+    }
+
+    /* unlock addr mutex */
+
+    ngx_log_error(r->log_level, r->log, 0,
+                  "no PTR type in DNS response");
+    return;
+
+ptr:
+
+    if (ngx_resolver_copy(r, &name, buf, buf + i, buf + n) != NGX_OK) {
+        goto failed;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, r->log, 0, "resolver an:%V", &name);
+
+    if (name.len != (size_t) rn->nlen
+        || ngx_strncmp(name.data, rn->name, name.len) != 0)
+    {
+        if (rn->nlen) {
+            ngx_resolver_free(r, rn->name);
+        }
+
+        rn->nlen = (u_short) name.len;
+        rn->name = name.data;
+
+        name.data = ngx_resolver_dup(r, rn->name, name.len);
+        if (name.data == NULL) {
+            goto failed;
+        }
+    }
+
+    ngx_queue_remove(&rn->queue);
+
+    rn->valid = ngx_time() + (r->valid ? r->valid : ttl);
+    rn->expire = ngx_time() + r->expire;
+
+    ngx_queue_insert_head(expire_queue, &rn->queue);
+
+    next = rn->waiting;
+    rn->waiting = NULL;
+
+    /* unlock addr mutex */
+
+    while (next) {
+        ctx = next;
+        ctx->state = NGX_OK;
+        ctx->valid = rn->valid;
+        ctx->name = name;
+        next = ctx->next;
+
+        ctx->handler(ctx);
+    }
+
+    ngx_resolver_free(r, name.data);
+
+    return;
+
+short_response:
+
+    err = "short DNS response";
+
+invalid:
+
+    /* unlock addr mutex */
+
+    ngx_log_error(r->log_level, r->log, 0, err);
+
+    return;
+
+failed:
+
+    /* unlock addr mutex */
+
+    return;
+}
+
+
+static ngx_resolver_node_t *
+ngx_resolver_lookup_name(ngx_resolver_t *r, ngx_str_t *name, uint32_t hash)
+{
+    ngx_int_t             rc;
+    ngx_rbtree_node_t    *node, *sentinel;
+    ngx_resolver_node_t  *rn;
+
+    node = r->name_rbtree.root;
+    sentinel = r->name_rbtree.sentinel;
+
+    while (node != sentinel) {
+
+        if (hash < node->key) {
+            node = node->left;
+            continue;
+        }
+
+        if (hash > node->key) {
+            node = node->right;
+            continue;
+        }
+
+        /* hash == node->key */
+
+        rn = ngx_resolver_node(node);
+
+        rc = ngx_memn2cmp(name->data, rn->name, name->len, rn->nlen);
+
+        if (rc == 0) {
+            return rn;
+        }
+
+        node = (rc < 0) ? node->left : node->right;
+    }
+
+    /* not found */
+
+    return NULL;
+}
+
+
+static ngx_resolver_node_t *
+ngx_resolver_lookup_srv(ngx_resolver_t *r, ngx_str_t *name, uint32_t hash)
+{
+    ngx_int_t             rc;
+    ngx_rbtree_node_t    *node, *sentinel;
+    ngx_resolver_node_t  *rn;
+
+    node = r->srv_rbtree.root;
+    sentinel = r->srv_rbtree.sentinel;
+
+    while (node != sentinel) {
+
+        if (hash < node->key) {
+            node = node->left;
+            continue;
+        }
+
+        if (hash > node->key) {
+            node = node->right;
+            continue;
+        }
+
+        /* hash == node->key */
+
+        rn = ngx_resolver_node(node);
+
+        rc = ngx_memn2cmp(name->data, rn->name, name->len, rn->nlen);
+
+        if (rc == 0) {
+            return rn;
+        }
+
+        node = (rc < 0) ? node->left : node->right;
+    }
+
+    /* not found */
+
+    return NULL;
+}
+
+
+static ngx_resolver_node_t *
+ngx_resolver_lookup_addr(ngx_resolver_t *r, in_addr_t addr)
+{
+    ngx_rbtree_node_t  *node, *sentinel;
+
+    node = r->addr_rbtree.root;
+    sentinel = r->addr_rbtree.sentinel;
+
+    while (node != sentinel) {
+
+        if (addr < node->key) {
+            node = node->left;
+            continue;
+        }
+
+        if (addr > node->key) {
+            node = node->right;
+            continue;
+        }
+
+        /* addr == node->key */
+
+        return ngx_resolver_node(node);
+    }
+
+    /* not found */
+
+    return NULL;
+}
+
+
+#if (NGX_HAVE_INET6)
+
+static ngx_resolver_node_t *
+ngx_resolver_lookup_addr6(ngx_resolver_t *r, struct in6_addr *addr,
+    uint32_t hash)
+{
+    ngx_int_t             rc;
+    ngx_rbtree_node_t    *node, *sentinel;
+    ngx_resolver_node_t  *rn;
+
+    node = r->addr6_rbtree.root;
+    sentinel = r->addr6_rbtree.sentinel;
+
+    while (node != sentinel) {
+
+        if (hash < node->key) {
+            node = node->left;
+            continue;
+        }
+
+        if (hash > node->key) {
+            node = node->right;
+            continue;
+        }
+
+        /* hash == node->key */
+
+        rn = ngx_resolver_node(node);
+
+        rc = ngx_memcmp(addr, &rn->addr6, 16);
+
+        if (rc == 0) {
+            return rn;
+        }
+
+        node = (rc < 0) ? node->left : node->right;
+    }
+
+    /* not found */
+
+    return NULL;
+}
+
+#endif
+
+
+static void
+ngx_resolver_rbtree_insert_value(ngx_rbtree_node_t *temp,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
+{
+    ngx_rbtree_node_t    **p;
+    ngx_resolver_node_t   *rn, *rn_temp;
+
+    for ( ;; ) {
+
+        if (node->key < temp->key) {
+
+            p = &temp->left;
+
+        } else if (node->key > temp->key) {
+
+            p = &temp->right;
+
+        } else { /* node->key == temp->key */
+
+            rn = ngx_resolver_node(node);
+            rn_temp = ngx_resolver_node(temp);
+
+            p = (ngx_memn2cmp(rn->name, rn_temp->name, rn->nlen, rn_temp->nlen)
+                 < 0) ? &temp->left : &temp->right;
+        }
+
+        if (*p == sentinel) {
+            break;
+        }
+
+        temp = *p;
+    }
+
+    *p = node;
+    node->parent = temp;
+    node->left = sentinel;
+    node->right = sentinel;
+    ngx_rbt_red(node);
+}
+
+
+#if (NGX_HAVE_INET6)
+
+static void
+ngx_resolver_rbtree_insert_addr6_value(ngx_rbtree_node_t *temp,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
+{
+    ngx_rbtree_node_t    **p;
+    ngx_resolver_node_t   *rn, *rn_temp;
+
+    for ( ;; ) {
+
+        if (node->key < temp->key) {
+
+            p = &temp->left;
+
+        } else if (node->key > temp->key) {
+
+            p = &temp->right;
+
+        } else { /* node->key == temp->key */
+
+            rn = ngx_resolver_node(node);
+            rn_temp = ngx_resolver_node(temp);
+
+            p = (ngx_memcmp(&rn->addr6, &rn_temp->addr6, 16)
+                 < 0) ? &temp->left : &temp->right;
+        }
+
+        if (*p == sentinel) {
+            break;
+        }
+
+        temp = *p;
+    }
+
+    *p = node;
+    node->parent = temp;
+    node->left = sentinel;
+    node->right = sentinel;
+    ngx_rbt_red(node);
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_resolver_create_name_query(ngx_resolver_t *r, ngx_resolver_node_t *rn,
+    ngx_str_t *name)
+{
+    u_char              *p, *s;
+    size_t               len, nlen;
+    ngx_uint_t           ident;
+    ngx_resolver_qs_t   *qs;
+    ngx_resolver_hdr_t  *query;
+
+    nlen = name->len ? (1 + name->len + 1) : 1;
+
+    len = sizeof(ngx_resolver_hdr_t) + nlen + sizeof(ngx_resolver_qs_t);
+
+#if (NGX_HAVE_INET6)
+    p = ngx_resolver_alloc(r, r->ipv6 ? len * 2 : len);
+#else
+    p = ngx_resolver_alloc(r, len);
+#endif
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    rn->qlen = (u_short) len;
+    rn->query = p;
+
+#if (NGX_HAVE_INET6)
+    if (r->ipv6) {
+        rn->query6 = p + len;
+    }
+#endif
+
+    query = (ngx_resolver_hdr_t *) p;
+
+    ident = ngx_random();
+
+    ngx_log_debug2(NGX_LOG_DEBUG_CORE, r->log, 0,
+                   "resolve: \"%V\" A %i", name, ident & 0xffff);
+
+    query->ident_hi = (u_char) ((ident >> 8) & 0xff);
+    query->ident_lo = (u_char) (ident & 0xff);
+
+    /* recursion query */
+    query->flags_hi = 1; query->flags_lo = 0;
+
+    /* one question */
+    query->nqs_hi = 0; query->nqs_lo = 1;
+    query->nan_hi = 0; query->nan_lo = 0;
+    query->nns_hi = 0; query->nns_lo = 0;
+    query->nar_hi = 0; query->nar_lo = 0;
+
+    p += sizeof(ngx_resolver_hdr_t) + nlen;
+
+    qs = (ngx_resolver_qs_t *) p;
+
+    /* query type */
+    qs->type_hi = 0; qs->type_lo = NGX_RESOLVE_A;
+
+    /* IN query class */
+    qs->class_hi = 0; qs->class_lo = 1;
+
+    /* convert "www.example.com" to "\3www\7example\3com\0" */
+
+    len = 0;
+    p--;
+    *p-- = '\0';
+
+    if (name->len == 0)  {
+        return NGX_DECLINED;
+    }
+
+    for (s = name->data + name->len - 1; s >= name->data; s--) {
+        if (*s != '.') {
+            *p = *s;
+            len++;
+
+        } else {
+            if (len == 0 || len > 255) {
+                return NGX_DECLINED;
+            }
+
+            *p = (u_char) len;
+            len = 0;
+        }
+
+        p--;
+    }
+
+    if (len == 0 || len > 255) {
+        return NGX_DECLINED;
+    }
+
+    *p = (u_char) len;
+
+#if (NGX_HAVE_INET6)
+    if (!r->ipv6) {
+        return NGX_OK;
+    }
+
+    p = rn->query6;
+
+    ngx_memcpy(p, rn->query, rn->qlen);
+
+    query = (ngx_resolver_hdr_t *) p;
+
+    ident = ngx_random();
+
+    ngx_log_debug2(NGX_LOG_DEBUG_CORE, r->log, 0,
+                   "resolve: \"%V\" AAAA %i", name, ident & 0xffff);
+
+    query->ident_hi = (u_char) ((ident >> 8) & 0xff);
+    query->ident_lo = (u_char) (ident & 0xff);
+
+    p += sizeof(ngx_resolver_hdr_t) + nlen;
+
+    qs = (ngx_resolver_qs_t *) p;
+
+    qs->type_lo = NGX_RESOLVE_AAAA;
+#endif
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_resolver_create_srv_query(ngx_resolver_t *r, ngx_resolver_node_t *rn,
+    ngx_str_t *name)
+{
+    u_char              *p, *s;
+    size_t               len, nlen;
+    ngx_uint_t           ident;
+    ngx_resolver_qs_t   *qs;
+    ngx_resolver_hdr_t  *query;
+
+    nlen = name->len ? (1 + name->len + 1) : 1;
+
+    len = sizeof(ngx_resolver_hdr_t) + nlen + sizeof(ngx_resolver_qs_t);
+
+    p = ngx_resolver_alloc(r, len);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    rn->qlen = (u_short) len;
+    rn->query = p;
+
+    query = (ngx_resolver_hdr_t *) p;
+
+    ident = ngx_random();
+
+    ngx_log_debug2(NGX_LOG_DEBUG_CORE, r->log, 0,
+                   "resolve: \"%V\" SRV %i", name, ident & 0xffff);
+
+    query->ident_hi = (u_char) ((ident >> 8) & 0xff);
+    query->ident_lo = (u_char) (ident & 0xff);
+
+    /* recursion query */
+    query->flags_hi = 1; query->flags_lo = 0;
+
+    /* one question */
+    query->nqs_hi = 0; query->nqs_lo = 1;
+    query->nan_hi = 0; query->nan_lo = 0;
+    query->nns_hi = 0; query->nns_lo = 0;
+    query->nar_hi = 0; query->nar_lo = 0;
+
+    p += sizeof(ngx_resolver_hdr_t) + nlen;
+
+    qs = (ngx_resolver_qs_t *) p;
+
+    /* query type */
+    qs->type_hi = 0; qs->type_lo = NGX_RESOLVE_SRV;
+
+    /* IN query class */
+    qs->class_hi = 0; qs->class_lo = 1;
+
+    /* converts "www.example.com" to "\3www\7example\3com\0" */
+
+    len = 0;
+    p--;
+    *p-- = '\0';
+
+    if (name->len == 0)  {
+        return NGX_DECLINED;
+    }
+
+    for (s = name->data + name->len - 1; s >= name->data; s--) {
+        if (*s != '.') {
+            *p = *s;
+            len++;
+
+        } else {
+            if (len == 0 || len > 255) {
+                return NGX_DECLINED;
+            }
+
+            *p = (u_char) len;
+            len = 0;
+        }
+
+        p--;
+    }
+
+    if (len == 0 || len > 255) {
+        return NGX_DECLINED;
+    }
+
+    *p = (u_char) len;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_resolver_create_addr_query(ngx_resolver_t *r, ngx_resolver_node_t *rn,
+    ngx_resolver_addr_t *addr)
+{
+    u_char               *p, *d;
+    size_t                len;
+    in_addr_t             inaddr;
+    ngx_int_t             n;
+    ngx_uint_t            ident;
+    ngx_resolver_hdr_t   *query;
+    struct sockaddr_in   *sin;
+#if (NGX_HAVE_INET6)
+    struct sockaddr_in6  *sin6;
+#endif
+
+    switch (addr->sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+    case AF_INET6:
+        len = sizeof(ngx_resolver_hdr_t)
+              + 64 + sizeof(".ip6.arpa.") - 1
+              + sizeof(ngx_resolver_qs_t);
+
+        break;
+#endif
+
+    default: /* AF_INET */
+        len = sizeof(ngx_resolver_hdr_t)
+              + sizeof(".255.255.255.255.in-addr.arpa.") - 1
+              + sizeof(ngx_resolver_qs_t);
+    }
+
+    p = ngx_resolver_alloc(r, len);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    rn->query = p;
+    query = (ngx_resolver_hdr_t *) p;
+
+    ident = ngx_random();
+
+    query->ident_hi = (u_char) ((ident >> 8) & 0xff);
+    query->ident_lo = (u_char) (ident & 0xff);
+
+    /* recursion query */
+    query->flags_hi = 1; query->flags_lo = 0;
+
+    /* one question */
+    query->nqs_hi = 0; query->nqs_lo = 1;
+    query->nan_hi = 0; query->nan_lo = 0;
+    query->nns_hi = 0; query->nns_lo = 0;
+    query->nar_hi = 0; query->nar_lo = 0;
+
+    p += sizeof(ngx_resolver_hdr_t);
+
+    switch (addr->sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+    case AF_INET6:
+        sin6 = (struct sockaddr_in6 *) addr->sockaddr;
+
+        for (n = 15; n >= 0; n--) {
+            p = ngx_sprintf(p, "\1%xd\1%xd",
+                            sin6->sin6_addr.s6_addr[n] & 0xf,
+                            (sin6->sin6_addr.s6_addr[n] >> 4) & 0xf);
+        }
+
+        p = ngx_cpymem(p, "\3ip6\4arpa\0", 10);
+
+        break;
+#endif
+
+    default: /* AF_INET */
+
+        sin = (struct sockaddr_in *) addr->sockaddr;
+        inaddr = ntohl(sin->sin_addr.s_addr);
+
+        for (n = 0; n < 32; n += 8) {
+            d = ngx_sprintf(&p[1], "%ud", (inaddr >> n) & 0xff);
+            *p = (u_char) (d - &p[1]);
+            p = d;
+        }
+
+        p = ngx_cpymem(p, "\7in-addr\4arpa\0", 14);
+    }
+
+    /* query type "PTR", IN query class */
+    p = ngx_cpymem(p, "\0\14\0\1", 4);
+
+    rn->qlen = (u_short) (p - rn->query);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_resolver_copy(ngx_resolver_t *r, ngx_str_t *name, u_char *buf, u_char *src,
+    u_char *last)
+{
+    char        *err;
+    u_char      *p, *dst;
+    ssize_t      len;
+    ngx_uint_t   i, n;
+
+    p = src;
+    len = -1;
+
+    /*
+     * compression pointers allow to create endless loop, so we set limit;
+     * 128 pointers should be enough to store 255-byte name
+     */
+
+    for (i = 0; i < 128; i++) {
+        n = *p++;
+
+        if (n == 0) {
+            goto done;
+        }
+
+        if (n & 0xc0) {
+            n = ((n & 0x3f) << 8) + *p;
+            p = &buf[n];
+
+        } else {
+            len += 1 + n;
+            p = &p[n];
+        }
+
+        if (p >= last) {
+            err = "name is out of response";
+            goto invalid;
+        }
+    }
+
+    err = "compression pointers loop";
+
+invalid:
+
+    ngx_log_error(r->log_level, r->log, 0, err);
+
+    return NGX_ERROR;
+
+done:
+
+    if (name == NULL) {
+        return NGX_OK;
+    }
+
+    if (len == -1) {
+        ngx_str_null(name);
+        return NGX_OK;
+    }
+
+    dst = ngx_resolver_alloc(r, len);
+    if (dst == NULL) {
+        return NGX_ERROR;
+    }
+
+    name->data = dst;
+
+    n = *src++;
+
+    for ( ;; ) {
+        if (n & 0xc0) {
+            n = ((n & 0x3f) << 8) + *src;
+            src = &buf[n];
+
+            n = *src++;
+
+        } else {
+            ngx_strlow(dst, src, n);
+            dst += n;
+            src += n;
+
+            n = *src++;
+
+            if (n != 0) {
+                *dst++ = '.';
+            }
+        }
+
+        if (n == 0) {
+            name->len = dst - name->data;
+            return NGX_OK;
+        }
+    }
+}
+
+
+static ngx_int_t
+ngx_resolver_set_timeout(ngx_resolver_t *r, ngx_resolver_ctx_t *ctx)
+{
+    if (ctx->event || ctx->timeout == 0) {
+        return NGX_OK;
+    }
+
+    ctx->event = ngx_resolver_calloc(r, sizeof(ngx_event_t));
+    if (ctx->event == NULL) {
+        return NGX_ERROR;
+    }
+
+    ctx->event->handler = ngx_resolver_timeout_handler;
+    ctx->event->data = ctx;
+    ctx->event->log = r->log;
+    ctx->event->cancelable = ctx->cancelable;
+    ctx->ident = -1;
+
+    ngx_add_timer(ctx->event, ctx->timeout);
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_resolver_timeout_handler(ngx_event_t *ev)
+{
+    ngx_resolver_ctx_t  *ctx;
+
+    ctx = ev->data;
+
+    ctx->state = NGX_RESOLVE_TIMEDOUT;
+
+    ctx->handler(ctx);
+}
+
+
+static void
+ngx_resolver_free_node(ngx_resolver_t *r, ngx_resolver_node_t *rn)
+{
+    ngx_uint_t  i;
+
+    /* lock alloc mutex */
+
+    if (rn->query) {
+        ngx_resolver_free_locked(r, rn->query);
+    }
+
+    if (rn->name) {
+        ngx_resolver_free_locked(r, rn->name);
+    }
+
+    if (rn->cnlen) {
+        ngx_resolver_free_locked(r, rn->u.cname);
+    }
+
+    if (rn->naddrs > 1 && rn->naddrs != (u_short) -1) {
+        ngx_resolver_free_locked(r, rn->u.addrs);
+    }
+
+#if (NGX_HAVE_INET6)
+    if (rn->naddrs6 > 1 && rn->naddrs6 != (u_short) -1) {
+        ngx_resolver_free_locked(r, rn->u6.addrs6);
+    }
+#endif
+
+    if (rn->nsrvs) {
+        for (i = 0; i < (ngx_uint_t) rn->nsrvs; i++) {
+            if (rn->u.srvs[i].name.data) {
+                ngx_resolver_free_locked(r, rn->u.srvs[i].name.data);
+            }
+        }
+
+        ngx_resolver_free_locked(r, rn->u.srvs);
+    }
+
+    ngx_resolver_free_locked(r, rn);
+
+    /* unlock alloc mutex */
+}
+
+
+static void *
+ngx_resolver_alloc(ngx_resolver_t *r, size_t size)
+{
+    u_char  *p;
+
+    /* lock alloc mutex */
+
+    p = ngx_alloc(size, r->log);
+
+    /* unlock alloc mutex */
+
+    return p;
+}
+
+
+static void *
+ngx_resolver_calloc(ngx_resolver_t *r, size_t size)
+{
+    u_char  *p;
+
+    p = ngx_resolver_alloc(r, size);
+
+    if (p) {
+        ngx_memzero(p, size);
+    }
+
+    return p;
+}
+
+
+static void
+ngx_resolver_free(ngx_resolver_t *r, void *p)
+{
+    /* lock alloc mutex */
+
+    ngx_free(p);
+
+    /* unlock alloc mutex */
+}
+
+
+static void
+ngx_resolver_free_locked(ngx_resolver_t *r, void *p)
+{
+    ngx_free(p);
+}
+
+
+static void *
+ngx_resolver_dup(ngx_resolver_t *r, void *src, size_t size)
+{
+    void  *dst;
+
+    dst = ngx_resolver_alloc(r, size);
+
+    if (dst == NULL) {
+        return dst;
+    }
+
+    ngx_memcpy(dst, src, size);
+
+    return dst;
+}
+
+
+static ngx_resolver_addr_t *
+ngx_resolver_export(ngx_resolver_t *r, ngx_resolver_node_t *rn,
+    ngx_uint_t rotate)
+{
+    ngx_uint_t            d, i, j, n;
+    in_addr_t            *addr;
+    ngx_sockaddr_t       *sockaddr;
+    struct sockaddr_in   *sin;
+    ngx_resolver_addr_t  *dst;
+#if (NGX_HAVE_INET6)
+    struct in6_addr      *addr6;
+    struct sockaddr_in6  *sin6;
+#endif
+
+    n = rn->naddrs;
+#if (NGX_HAVE_INET6)
+    n += rn->naddrs6;
+#endif
+
+    dst = ngx_resolver_calloc(r, n * sizeof(ngx_resolver_addr_t));
+    if (dst == NULL) {
+        return NULL;
+    }
+
+    sockaddr = ngx_resolver_calloc(r, n * sizeof(ngx_sockaddr_t));
+    if (sockaddr == NULL) {
+        ngx_resolver_free(r, dst);
+        return NULL;
+    }
+
+    i = 0;
+    d = rotate ? ngx_random() % n : 0;
+
+    if (rn->naddrs) {
+        j = rotate ? ngx_random() % rn->naddrs : 0;
+
+        addr = (rn->naddrs == 1) ? &rn->u.addr : rn->u.addrs;
+
+        do {
+            sin = &sockaddr[d].sockaddr_in;
+            sin->sin_family = AF_INET;
+            sin->sin_addr.s_addr = addr[j++];
+            dst[d].sockaddr = (struct sockaddr *) sin;
+            dst[d++].socklen = sizeof(struct sockaddr_in);
+
+            if (d == n) {
+                d = 0;
+            }
+
+            if (j == (ngx_uint_t) rn->naddrs) {
+                j = 0;
+            }
+        } while (++i < (ngx_uint_t) rn->naddrs);
+    }
+
+#if (NGX_HAVE_INET6)
+    if (rn->naddrs6) {
+        j = rotate ? ngx_random() % rn->naddrs6 : 0;
+
+        addr6 = (rn->naddrs6 == 1) ? &rn->u6.addr6 : rn->u6.addrs6;
+
+        do {
+            sin6 = &sockaddr[d].sockaddr_in6;
+            sin6->sin6_family = AF_INET6;
+            ngx_memcpy(sin6->sin6_addr.s6_addr, addr6[j++].s6_addr, 16);
+            dst[d].sockaddr = (struct sockaddr *) sin6;
+            dst[d++].socklen = sizeof(struct sockaddr_in6);
+
+            if (d == n) {
+                d = 0;
+            }
+
+            if (j == rn->naddrs6) {
+                j = 0;
+            }
+        } while (++i < n);
+    }
+#endif
+
+    return dst;
+}
+
+
+static void
+ngx_resolver_report_srv(ngx_resolver_t *r, ngx_resolver_ctx_t *ctx)
+{
+    ngx_uint_t                naddrs, nsrvs, nw, i, j, k, l, m, n, w;
+    ngx_resolver_addr_t      *addrs;
+    ngx_resolver_srv_name_t  *srvs;
+
+    srvs = ctx->srvs;
+    nsrvs = ctx->nsrvs;
+
+    naddrs = 0;
+
+    for (i = 0; i < nsrvs; i++) {
+        if (srvs[i].state == NGX_ERROR) {
+            ctx->state = NGX_ERROR;
+            ctx->valid = ngx_time() + (r->valid ? r->valid : 10);
+
+            ctx->handler(ctx);
+            return;
+        }
+
+        naddrs += srvs[i].naddrs;
+    }
+
+    if (naddrs == 0) {
+        ctx->state = NGX_RESOLVE_NXDOMAIN;
+        ctx->valid = ngx_time() + (r->valid ? r->valid : 10);
+
+        ctx->handler(ctx);
+        return;
+    }
+
+    addrs = ngx_resolver_calloc(r, naddrs * sizeof(ngx_resolver_addr_t));
+    if (addrs == NULL) {
+        ctx->state = NGX_ERROR;
+        ctx->valid = ngx_time() + (r->valid ? r->valid : 10);
+
+        ctx->handler(ctx);
+        return;
+    }
+
+    i = 0;
+    n = 0;
+
+    do {
+        nw = 0;
+
+        for (j = i; j < nsrvs; j++) {
+            if (srvs[j].priority != srvs[i].priority) {
+                break;
+            }
+
+            nw += srvs[j].naddrs * srvs[j].weight;
+        }
+
+        if (nw == 0) {
+            goto next_srv;
+        }
+
+        w = ngx_random() % nw;
+
+        for (k = i; k < j; k++) {
+            if (w < srvs[k].naddrs * srvs[k].weight) {
+                break;
+            }
+
+            w -= srvs[k].naddrs * srvs[k].weight;
+        }
+
+        for (l = i; l < j; l++) {
+
+            for (m = 0; m < srvs[k].naddrs; m++) {
+                addrs[n].socklen = srvs[k].addrs[m].socklen;
+                addrs[n].sockaddr = srvs[k].addrs[m].sockaddr;
+                addrs[n].name = srvs[k].name;
+                addrs[n].priority = srvs[k].priority;
+                addrs[n].weight = srvs[k].weight;
+                n++;
+            }
+
+            if (++k == j) {
+                k = i;
+            }
+        }
+
+next_srv:
+
+        i = j;
+
+    } while (i < ctx->nsrvs);
+
+    ctx->state = NGX_OK;
+    ctx->addrs = addrs;
+    ctx->naddrs = naddrs;
+
+    ctx->handler(ctx);
+
+    ngx_resolver_free(r, addrs);
+}
+
+
+char *
+ngx_resolver_strerror(ngx_int_t err)
+{
+    static char *errors[] = {
+        "Format error",     /* FORMERR */
+        "Server failure",   /* SERVFAIL */
+        "Host not found",   /* NXDOMAIN */
+        "Unimplemented",    /* NOTIMP */
+        "Operation refused" /* REFUSED */
+    };
+
+    if (err > 0 && err < 6) {
+        return errors[err - 1];
+    }
+
+    if (err == NGX_RESOLVE_TIMEDOUT) {
+        return "Operation timed out";
+    }
+
+    return "Unknown error";
+}
+
+
+static u_char *
+ngx_resolver_log_error(ngx_log_t *log, u_char *buf, size_t len)
+{
+    u_char                     *p;
+    ngx_resolver_connection_t  *rec;
+
+    p = buf;
+
+    if (log->action) {
+        p = ngx_snprintf(buf, len, " while %s", log->action);
+        len -= p - buf;
+    }
+
+    rec = log->data;
+
+    if (rec) {
+        p = ngx_snprintf(p, len, ", resolver: %V", &rec->server);
+    }
+
+    return p;
+}
+
+
+static ngx_int_t
+ngx_udp_connect(ngx_resolver_connection_t *rec)
+{
+    int                rc;
+    ngx_int_t          event;
+    ngx_event_t       *rev, *wev;
+    ngx_socket_t       s;
+    ngx_connection_t  *c;
+
+    s = ngx_socket(rec->sockaddr->sa_family, SOCK_DGRAM, 0);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, &rec->log, 0, "UDP socket %d", s);
+
+    if (s == (ngx_socket_t) -1) {
+        ngx_log_error(NGX_LOG_ALERT, &rec->log, ngx_socket_errno,
+                      ngx_socket_n " failed");
+        return NGX_ERROR;
+    }
+
+    c = ngx_get_connection(s, &rec->log);
+
+    if (c == NULL) {
+        if (ngx_close_socket(s) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, &rec->log, ngx_socket_errno,
+                          ngx_close_socket_n "failed");
+        }
+
+        return NGX_ERROR;
+    }
+
+    if (ngx_nonblocking(s) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, &rec->log, ngx_socket_errno,
+                      ngx_nonblocking_n " failed");
+
+        goto failed;
+    }
+
+    rev = c->read;
+    wev = c->write;
+
+    rev->log = &rec->log;
+    wev->log = &rec->log;
+
+    rec->udp = c;
+
+    c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
+
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, &rec->log, 0,
+                   "connect to %V, fd:%d #%uA", &rec->server, s, c->number);
+
+    rc = ngxvcl_connect(s, rec->sockaddr, rec->socklen);
+
+    /* TODO: iocp */
+
+    if (rc == -1) {
+        ngx_log_error(NGX_LOG_CRIT, &rec->log, ngx_socket_errno,
+                      "connect() failed");
+
+        goto failed;
+    }
+
+    /* UDP sockets are always ready to write */
+    wev->ready = 1;
+
+    event = (ngx_event_flags & NGX_USE_CLEAR_EVENT) ?
+                /* kqueue, epoll */                 NGX_CLEAR_EVENT:
+                /* select, poll, /dev/poll */       NGX_LEVEL_EVENT;
+                /* eventport event type has no meaning: oneshot only */
+
+    if (ngx_add_event(rev, NGX_READ_EVENT, event) != NGX_OK) {
+        goto failed;
+    }
+
+    return NGX_OK;
+
+failed:
+
+    ngx_close_connection(c);
+    rec->udp = NULL;
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_tcp_connect(ngx_resolver_connection_t *rec)
+{
+    int                rc;
+    ngx_int_t          event;
+    ngx_err_t          err;
+    ngx_uint_t         level;
+    ngx_socket_t       s;
+    ngx_event_t       *rev, *wev;
+    ngx_connection_t  *c;
+
+    s = ngx_socket(rec->sockaddr->sa_family, SOCK_STREAM, 0);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, &rec->log, 0, "TCP socket %d", s);
+
+    if (s == (ngx_socket_t) -1) {
+        ngx_log_error(NGX_LOG_ALERT, &rec->log, ngx_socket_errno,
+                      ngx_socket_n " failed");
+        return NGX_ERROR;
+    }
+
+    c = ngx_get_connection(s, &rec->log);
+
+    if (c == NULL) {
+        if (ngx_close_socket(s) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, &rec->log, ngx_socket_errno,
+                          ngx_close_socket_n "failed");
+        }
+
+        return NGX_ERROR;
+    }
+
+    if (ngx_nonblocking(s) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, &rec->log, ngx_socket_errno,
+                      ngx_nonblocking_n " failed");
+
+        goto failed;
+    }
+
+    rev = c->read;
+    wev = c->write;
+
+    rev->log = &rec->log;
+    wev->log = &rec->log;
+
+    rec->tcp = c;
+
+    c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
+
+    if (ngx_add_conn) {
+        if (ngx_add_conn(c) == NGX_ERROR) {
+            goto failed;
+        }
+    }
+
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, &rec->log, 0,
+                   "connect to %V, fd:%d #%uA", &rec->server, s, c->number);
+
+    rc = ngxvcl_connect(s, rec->sockaddr, rec->socklen);
+
+    if (rc == -1) {
+        err = ngx_socket_errno;
+
+
+        if (err != NGX_EINPROGRESS
+#if (NGX_WIN32)
+            /* Winsock returns WSAEWOULDBLOCK (NGX_EAGAIN) */
+            && err != NGX_EAGAIN
+#endif
+            )
+        {
+            if (err == NGX_ECONNREFUSED
+#if (NGX_LINUX)
+                /*
+                 * Linux returns EAGAIN instead of ECONNREFUSED
+                 * for unix sockets if listen queue is full
+                 */
+                || err == NGX_EAGAIN
+#endif
+                || err == NGX_ECONNRESET
+                || err == NGX_ENETDOWN
+                || err == NGX_ENETUNREACH
+                || err == NGX_EHOSTDOWN
+                || err == NGX_EHOSTUNREACH)
+            {
+                level = NGX_LOG_ERR;
+
+            } else {
+                level = NGX_LOG_CRIT;
+            }
+
+            ngx_log_error(level, c->log, err, "connect() to %V failed",
+                          &rec->server);
+
+            ngx_close_connection(c);
+            rec->tcp = NULL;
+
+            return NGX_ERROR;
+        }
+    }
+
+    if (ngx_add_conn) {
+        if (rc == -1) {
+
+            /* NGX_EINPROGRESS */
+
+            return NGX_AGAIN;
+        }
+
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, &rec->log, 0, "connected");
+
+        wev->ready = 1;
+
+        return NGX_OK;
+    }
+
+    if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, &rec->log, ngx_socket_errno,
+                       "connect(): %d", rc);
+
+        if (ngx_blocking(s) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, &rec->log, ngx_socket_errno,
+                          ngx_blocking_n " failed");
+            goto failed;
+        }
+
+        /*
+         * FreeBSD's aio allows to post an operation on non-connected socket.
+         * NT does not support it.
+         *
+         * TODO: check in Win32, etc. As workaround we can use NGX_ONESHOT_EVENT
+         */
+
+        rev->ready = 1;
+        wev->ready = 1;
+
+        return NGX_OK;
+    }
+
+    if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
+
+        /* kqueue */
+
+        event = NGX_CLEAR_EVENT;
+
+    } else {
+
+        /* select, poll, /dev/poll */
+
+        event = NGX_LEVEL_EVENT;
+    }
+
+    if (ngx_add_event(rev, NGX_READ_EVENT, event) != NGX_OK) {
+        goto failed;
+    }
+
+    if (rc == -1) {
+
+        /* NGX_EINPROGRESS */
+
+        if (ngx_add_event(wev, NGX_WRITE_EVENT, event) != NGX_OK) {
+            goto failed;
+        }
+
+        return NGX_AGAIN;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, &rec->log, 0, "connected");
+
+    wev->ready = 1;
+
+    return NGX_OK;
+
+failed:
+
+    ngx_close_connection(c);
+    rec->tcp = NULL;
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_resolver_cmp_srvs(const void *one, const void *two)
+{
+    ngx_int_t            p1, p2;
+    ngx_resolver_srv_t  *first, *second;
+
+    first = (ngx_resolver_srv_t *) one;
+    second = (ngx_resolver_srv_t *) two;
+
+    p1 = first->priority;
+    p2 = second->priority;
+
+    return p1 - p2;
+}
diff --git a/nginx/src/core/ngx_resolver.h b/nginx/src/core/ngx_resolver.h
new file mode 100644 (file)
index 0000000..0bd3921
--- /dev/null
@@ -0,0 +1,240 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#ifndef _NGX_RESOLVER_H_INCLUDED_
+#define _NGX_RESOLVER_H_INCLUDED_
+
+
+#define NGX_RESOLVE_A         1
+#define NGX_RESOLVE_CNAME     5
+#define NGX_RESOLVE_PTR       12
+#define NGX_RESOLVE_MX        15
+#define NGX_RESOLVE_TXT       16
+#if (NGX_HAVE_INET6)
+#define NGX_RESOLVE_AAAA      28
+#endif
+#define NGX_RESOLVE_SRV       33
+#define NGX_RESOLVE_DNAME     39
+
+#define NGX_RESOLVE_FORMERR   1
+#define NGX_RESOLVE_SERVFAIL  2
+#define NGX_RESOLVE_NXDOMAIN  3
+#define NGX_RESOLVE_NOTIMP    4
+#define NGX_RESOLVE_REFUSED   5
+#define NGX_RESOLVE_TIMEDOUT  NGX_ETIMEDOUT
+
+
+#define NGX_NO_RESOLVER       (void *) -1
+
+#define NGX_RESOLVER_MAX_RECURSION    50
+
+
+typedef struct ngx_resolver_s  ngx_resolver_t;
+
+
+typedef struct {
+    ngx_connection_t         *udp;
+    ngx_connection_t         *tcp;
+    struct sockaddr          *sockaddr;
+    socklen_t                 socklen;
+    ngx_str_t                 server;
+    ngx_log_t                 log;
+    ngx_buf_t                *read_buf;
+    ngx_buf_t                *write_buf;
+    ngx_resolver_t           *resolver;
+} ngx_resolver_connection_t;
+
+
+typedef struct ngx_resolver_ctx_s  ngx_resolver_ctx_t;
+
+typedef void (*ngx_resolver_handler_pt)(ngx_resolver_ctx_t *ctx);
+
+
+typedef struct {
+    struct sockaddr          *sockaddr;
+    socklen_t                 socklen;
+    ngx_str_t                 name;
+    u_short                   priority;
+    u_short                   weight;
+} ngx_resolver_addr_t;
+
+
+typedef struct {
+    ngx_str_t                 name;
+    u_short                   priority;
+    u_short                   weight;
+    u_short                   port;
+} ngx_resolver_srv_t;
+
+
+typedef struct {
+    ngx_str_t                 name;
+    u_short                   priority;
+    u_short                   weight;
+    u_short                   port;
+
+    ngx_resolver_ctx_t       *ctx;
+    ngx_int_t                 state;
+
+    ngx_uint_t                naddrs;
+    ngx_addr_t               *addrs;
+} ngx_resolver_srv_name_t;
+
+
+typedef struct {
+    ngx_rbtree_node_t         node;
+    ngx_queue_t               queue;
+
+    /* PTR: resolved name, A: name to resolve */
+    u_char                   *name;
+
+#if (NGX_HAVE_INET6)
+    /* PTR: IPv6 address to resolve (IPv4 address is in rbtree node key) */
+    struct in6_addr           addr6;
+#endif
+
+    u_short                   nlen;
+    u_short                   qlen;
+
+    u_char                   *query;
+#if (NGX_HAVE_INET6)
+    u_char                   *query6;
+#endif
+
+    union {
+        in_addr_t             addr;
+        in_addr_t            *addrs;
+        u_char               *cname;
+        ngx_resolver_srv_t   *srvs;
+    } u;
+
+    u_char                    code;
+    u_short                   naddrs;
+    u_short                   nsrvs;
+    u_short                   cnlen;
+
+#if (NGX_HAVE_INET6)
+    union {
+        struct in6_addr       addr6;
+        struct in6_addr      *addrs6;
+    } u6;
+
+    u_short                   naddrs6;
+#endif
+
+    time_t                    expire;
+    time_t                    valid;
+    uint32_t                  ttl;
+
+    unsigned                  tcp:1;
+#if (NGX_HAVE_INET6)
+    unsigned                  tcp6:1;
+#endif
+
+    ngx_uint_t                last_connection;
+
+    ngx_resolver_ctx_t       *waiting;
+} ngx_resolver_node_t;
+
+
+struct ngx_resolver_s {
+    /* has to be pointer because of "incomplete type" */
+    ngx_event_t              *event;
+    void                     *dummy;
+    ngx_log_t                *log;
+
+    /* event ident must be after 3 pointers as in ngx_connection_t */
+    ngx_int_t                 ident;
+
+    /* simple round robin DNS peers balancer */
+    ngx_array_t               connections;
+    ngx_uint_t                last_connection;
+
+    ngx_rbtree_t              name_rbtree;
+    ngx_rbtree_node_t         name_sentinel;
+
+    ngx_rbtree_t              srv_rbtree;
+    ngx_rbtree_node_t         srv_sentinel;
+
+    ngx_rbtree_t              addr_rbtree;
+    ngx_rbtree_node_t         addr_sentinel;
+
+    ngx_queue_t               name_resend_queue;
+    ngx_queue_t               srv_resend_queue;
+    ngx_queue_t               addr_resend_queue;
+
+    ngx_queue_t               name_expire_queue;
+    ngx_queue_t               srv_expire_queue;
+    ngx_queue_t               addr_expire_queue;
+
+#if (NGX_HAVE_INET6)
+    ngx_uint_t                ipv6;                 /* unsigned  ipv6:1; */
+    ngx_rbtree_t              addr6_rbtree;
+    ngx_rbtree_node_t         addr6_sentinel;
+    ngx_queue_t               addr6_resend_queue;
+    ngx_queue_t               addr6_expire_queue;
+#endif
+
+    time_t                    resend_timeout;
+    time_t                    tcp_timeout;
+    time_t                    expire;
+    time_t                    valid;
+
+    ngx_uint_t                log_level;
+};
+
+
+struct ngx_resolver_ctx_s {
+    ngx_resolver_ctx_t       *next;
+    ngx_resolver_t           *resolver;
+    ngx_resolver_node_t      *node;
+
+    /* event ident must be after 3 pointers as in ngx_connection_t */
+    ngx_int_t                 ident;
+
+    ngx_int_t                 state;
+    ngx_str_t                 name;
+    ngx_str_t                 service;
+
+    time_t                    valid;
+    ngx_uint_t                naddrs;
+    ngx_resolver_addr_t      *addrs;
+    ngx_resolver_addr_t       addr;
+    struct sockaddr_in        sin;
+
+    ngx_uint_t                count;
+    ngx_uint_t                nsrvs;
+    ngx_resolver_srv_name_t  *srvs;
+
+    ngx_resolver_handler_pt   handler;
+    void                     *data;
+    ngx_msec_t                timeout;
+
+    unsigned                  quick:1;
+    unsigned                  async:1;
+    unsigned                  cancelable:1;
+    ngx_uint_t                recursion;
+    ngx_event_t              *event;
+};
+
+
+ngx_resolver_t *ngx_resolver_create(ngx_conf_t *cf, ngx_str_t *names,
+    ngx_uint_t n);
+ngx_resolver_ctx_t *ngx_resolve_start(ngx_resolver_t *r,
+    ngx_resolver_ctx_t *temp);
+ngx_int_t ngx_resolve_name(ngx_resolver_ctx_t *ctx);
+void ngx_resolve_name_done(ngx_resolver_ctx_t *ctx);
+ngx_int_t ngx_resolve_addr(ngx_resolver_ctx_t *ctx);
+void ngx_resolve_addr_done(ngx_resolver_ctx_t *ctx);
+char *ngx_resolver_strerror(ngx_int_t err);
+
+
+#endif /* _NGX_RESOLVER_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_rwlock.c b/nginx/src/core/ngx_rwlock.c
new file mode 100644 (file)
index 0000000..ed2b0f8
--- /dev/null
@@ -0,0 +1,129 @@
+
+/*
+ * Copyright (C) Ruslan Ermilov
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#if (NGX_HAVE_ATOMIC_OPS)
+
+
+#define NGX_RWLOCK_SPIN   2048
+#define NGX_RWLOCK_WLOCK  ((ngx_atomic_uint_t) -1)
+
+
+void
+ngx_rwlock_wlock(ngx_atomic_t *lock)
+{
+    ngx_uint_t  i, n;
+
+    for ( ;; ) {
+
+        if (*lock == 0 && ngx_atomic_cmp_set(lock, 0, NGX_RWLOCK_WLOCK)) {
+            return;
+        }
+
+        if (ngx_ncpu > 1) {
+
+            for (n = 1; n < NGX_RWLOCK_SPIN; n <<= 1) {
+
+                for (i = 0; i < n; i++) {
+                    ngx_cpu_pause();
+                }
+
+                if (*lock == 0
+                    && ngx_atomic_cmp_set(lock, 0, NGX_RWLOCK_WLOCK))
+                {
+                    return;
+                }
+            }
+        }
+
+        ngx_sched_yield();
+    }
+}
+
+
+void
+ngx_rwlock_rlock(ngx_atomic_t *lock)
+{
+    ngx_uint_t         i, n;
+    ngx_atomic_uint_t  readers;
+
+    for ( ;; ) {
+        readers = *lock;
+
+        if (readers != NGX_RWLOCK_WLOCK
+            && ngx_atomic_cmp_set(lock, readers, readers + 1))
+        {
+            return;
+        }
+
+        if (ngx_ncpu > 1) {
+
+            for (n = 1; n < NGX_RWLOCK_SPIN; n <<= 1) {
+
+                for (i = 0; i < n; i++) {
+                    ngx_cpu_pause();
+                }
+
+                readers = *lock;
+
+                if (readers != NGX_RWLOCK_WLOCK
+                    && ngx_atomic_cmp_set(lock, readers, readers + 1))
+                {
+                    return;
+                }
+            }
+        }
+
+        ngx_sched_yield();
+    }
+}
+
+
+void
+ngx_rwlock_unlock(ngx_atomic_t *lock)
+{
+    ngx_atomic_uint_t  readers;
+
+    readers = *lock;
+
+    if (readers == 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;
+    }
+}
+
+
+void
+ngx_rwlock_downgrade(ngx_atomic_t *lock)
+{
+    if (*lock == NGX_RWLOCK_WLOCK) {
+        *lock = 1;
+    }
+}
+
+
+#else
+
+#if (NGX_HTTP_UPSTREAM_ZONE || NGX_STREAM_UPSTREAM_ZONE)
+
+#error ngx_atomic_cmp_set() is not defined!
+
+#endif
+
+#endif
diff --git a/nginx/src/core/ngx_rwlock.h b/nginx/src/core/ngx_rwlock.h
new file mode 100644 (file)
index 0000000..41b42aa
--- /dev/null
@@ -0,0 +1,22 @@
+
+/*
+ * Copyright (C) Ruslan Ermilov
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_RWLOCK_H_INCLUDED_
+#define _NGX_RWLOCK_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+void ngx_rwlock_wlock(ngx_atomic_t *lock);
+void ngx_rwlock_rlock(ngx_atomic_t *lock);
+void ngx_rwlock_unlock(ngx_atomic_t *lock);
+void ngx_rwlock_downgrade(ngx_atomic_t *lock);
+
+
+#endif /* _NGX_RWLOCK_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_sha1.c b/nginx/src/core/ngx_sha1.c
new file mode 100644 (file)
index 0000000..f00dc52
--- /dev/null
@@ -0,0 +1,294 @@
+
+/*
+ * Copyright (C) Maxim Dounin
+ * Copyright (C) Nginx, Inc.
+ *
+ * An internal SHA1 implementation.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_sha1.h>
+
+
+static const u_char *ngx_sha1_body(ngx_sha1_t *ctx, const u_char *data,
+    size_t size);
+
+
+void
+ngx_sha1_init(ngx_sha1_t *ctx)
+{
+    ctx->a = 0x67452301;
+    ctx->b = 0xefcdab89;
+    ctx->c = 0x98badcfe;
+    ctx->d = 0x10325476;
+    ctx->e = 0xc3d2e1f0;
+
+    ctx->bytes = 0;
+}
+
+
+void
+ngx_sha1_update(ngx_sha1_t *ctx, const void *data, size_t size)
+{
+    size_t  used, free;
+
+    used = (size_t) (ctx->bytes & 0x3f);
+    ctx->bytes += size;
+
+    if (used) {
+        free = 64 - used;
+
+        if (size < free) {
+            ngx_memcpy(&ctx->buffer[used], data, size);
+            return;
+        }
+
+        ngx_memcpy(&ctx->buffer[used], data, free);
+        data = (u_char *) data + free;
+        size -= free;
+        (void) ngx_sha1_body(ctx, ctx->buffer, 64);
+    }
+
+    if (size >= 64) {
+        data = ngx_sha1_body(ctx, data, size & ~(size_t) 0x3f);
+        size &= 0x3f;
+    }
+
+    ngx_memcpy(ctx->buffer, data, size);
+}
+
+
+void
+ngx_sha1_final(u_char result[20], ngx_sha1_t *ctx)
+{
+    size_t  used, free;
+
+    used = (size_t) (ctx->bytes & 0x3f);
+
+    ctx->buffer[used++] = 0x80;
+
+    free = 64 - used;
+
+    if (free < 8) {
+        ngx_memzero(&ctx->buffer[used], free);
+        (void) ngx_sha1_body(ctx, ctx->buffer, 64);
+        used = 0;
+        free = 64;
+    }
+
+    ngx_memzero(&ctx->buffer[used], free - 8);
+
+    ctx->bytes <<= 3;
+    ctx->buffer[56] = (u_char) (ctx->bytes >> 56);
+    ctx->buffer[57] = (u_char) (ctx->bytes >> 48);
+    ctx->buffer[58] = (u_char) (ctx->bytes >> 40);
+    ctx->buffer[59] = (u_char) (ctx->bytes >> 32);
+    ctx->buffer[60] = (u_char) (ctx->bytes >> 24);
+    ctx->buffer[61] = (u_char) (ctx->bytes >> 16);
+    ctx->buffer[62] = (u_char) (ctx->bytes >> 8);
+    ctx->buffer[63] = (u_char) ctx->bytes;
+
+    (void) ngx_sha1_body(ctx, ctx->buffer, 64);
+
+    result[0] = (u_char) (ctx->a >> 24);
+    result[1] = (u_char) (ctx->a >> 16);
+    result[2] = (u_char) (ctx->a >> 8);
+    result[3] = (u_char) ctx->a;
+    result[4] = (u_char) (ctx->b >> 24);
+    result[5] = (u_char) (ctx->b >> 16);
+    result[6] = (u_char) (ctx->b >> 8);
+    result[7] = (u_char) ctx->b;
+    result[8] = (u_char) (ctx->c >> 24);
+    result[9] = (u_char) (ctx->c >> 16);
+    result[10] = (u_char) (ctx->c >> 8);
+    result[11] = (u_char) ctx->c;
+    result[12] = (u_char) (ctx->d >> 24);
+    result[13] = (u_char) (ctx->d >> 16);
+    result[14] = (u_char) (ctx->d >> 8);
+    result[15] = (u_char) ctx->d;
+    result[16] = (u_char) (ctx->e >> 24);
+    result[17] = (u_char) (ctx->e >> 16);
+    result[18] = (u_char) (ctx->e >> 8);
+    result[19] = (u_char) ctx->e;
+
+    ngx_memzero(ctx, sizeof(*ctx));
+}
+
+
+/*
+ * Helper functions.
+ */
+
+#define ROTATE(bits, word)  (((word) << (bits)) | ((word) >> (32 - (bits))))
+
+#define F1(b, c, d)  (((b) & (c)) | ((~(b)) & (d)))
+#define F2(b, c, d)  ((b) ^ (c) ^ (d))
+#define F3(b, c, d)  (((b) & (c)) | ((b) & (d)) | ((c) & (d)))
+
+#define STEP(f, a, b, c, d, e, w, t)                                          \
+    temp = ROTATE(5, (a)) + f((b), (c), (d)) + (e) + (w) + (t);               \
+    (e) = (d);                                                                \
+    (d) = (c);                                                                \
+    (c) = ROTATE(30, (b));                                                    \
+    (b) = (a);                                                                \
+    (a) = temp;
+
+
+/*
+ * GET() reads 4 input bytes in big-endian byte order and returns
+ * them as uint32_t.
+ */
+
+#define GET(n)                                                                \
+    ((uint32_t) p[n * 4 + 3] |                                                \
+    ((uint32_t) p[n * 4 + 2] << 8) |                                          \
+    ((uint32_t) p[n * 4 + 1] << 16) |                                         \
+    ((uint32_t) p[n * 4] << 24))
+
+
+/*
+ * This processes one or more 64-byte data blocks, but does not update
+ * the bit counters.  There are no alignment requirements.
+ */
+
+static const u_char *
+ngx_sha1_body(ngx_sha1_t *ctx, const u_char *data, size_t size)
+{
+    uint32_t       a, b, c, d, e, temp;
+    uint32_t       saved_a, saved_b, saved_c, saved_d, saved_e;
+    uint32_t       words[80];
+    ngx_uint_t     i;
+    const u_char  *p;
+
+    p = data;
+
+    a = ctx->a;
+    b = ctx->b;
+    c = ctx->c;
+    d = ctx->d;
+    e = ctx->e;
+
+    do {
+        saved_a = a;
+        saved_b = b;
+        saved_c = c;
+        saved_d = d;
+        saved_e = e;
+
+        /* Load data block into the words array */
+
+        for (i = 0; i < 16; i++) {
+            words[i] = GET(i);
+        }
+
+        for (i = 16; i < 80; i++) {
+            words[i] = ROTATE(1, words[i - 3] ^ words[i - 8] ^ words[i - 14]
+                                 ^ words[i - 16]);
+        }
+
+        /* Transformations */
+
+        STEP(F1, a, b, c, d, e, words[0],  0x5a827999);
+        STEP(F1, a, b, c, d, e, words[1],  0x5a827999);
+        STEP(F1, a, b, c, d, e, words[2],  0x5a827999);
+        STEP(F1, a, b, c, d, e, words[3],  0x5a827999);
+        STEP(F1, a, b, c, d, e, words[4],  0x5a827999);
+        STEP(F1, a, b, c, d, e, words[5],  0x5a827999);
+        STEP(F1, a, b, c, d, e, words[6],  0x5a827999);
+        STEP(F1, a, b, c, d, e, words[7],  0x5a827999);
+        STEP(F1, a, b, c, d, e, words[8],  0x5a827999);
+        STEP(F1, a, b, c, d, e, words[9],  0x5a827999);
+        STEP(F1, a, b, c, d, e, words[10], 0x5a827999);
+        STEP(F1, a, b, c, d, e, words[11], 0x5a827999);
+        STEP(F1, a, b, c, d, e, words[12], 0x5a827999);
+        STEP(F1, a, b, c, d, e, words[13], 0x5a827999);
+        STEP(F1, a, b, c, d, e, words[14], 0x5a827999);
+        STEP(F1, a, b, c, d, e, words[15], 0x5a827999);
+        STEP(F1, a, b, c, d, e, words[16], 0x5a827999);
+        STEP(F1, a, b, c, d, e, words[17], 0x5a827999);
+        STEP(F1, a, b, c, d, e, words[18], 0x5a827999);
+        STEP(F1, a, b, c, d, e, words[19], 0x5a827999);
+
+        STEP(F2, a, b, c, d, e, words[20], 0x6ed9eba1);
+        STEP(F2, a, b, c, d, e, words[21], 0x6ed9eba1);
+        STEP(F2, a, b, c, d, e, words[22], 0x6ed9eba1);
+        STEP(F2, a, b, c, d, e, words[23], 0x6ed9eba1);
+        STEP(F2, a, b, c, d, e, words[24], 0x6ed9eba1);
+        STEP(F2, a, b, c, d, e, words[25], 0x6ed9eba1);
+        STEP(F2, a, b, c, d, e, words[26], 0x6ed9eba1);
+        STEP(F2, a, b, c, d, e, words[27], 0x6ed9eba1);
+        STEP(F2, a, b, c, d, e, words[28], 0x6ed9eba1);
+        STEP(F2, a, b, c, d, e, words[29], 0x6ed9eba1);
+        STEP(F2, a, b, c, d, e, words[30], 0x6ed9eba1);
+        STEP(F2, a, b, c, d, e, words[31], 0x6ed9eba1);
+        STEP(F2, a, b, c, d, e, words[32], 0x6ed9eba1);
+        STEP(F2, a, b, c, d, e, words[33], 0x6ed9eba1);
+        STEP(F2, a, b, c, d, e, words[34], 0x6ed9eba1);
+        STEP(F2, a, b, c, d, e, words[35], 0x6ed9eba1);
+        STEP(F2, a, b, c, d, e, words[36], 0x6ed9eba1);
+        STEP(F2, a, b, c, d, e, words[37], 0x6ed9eba1);
+        STEP(F2, a, b, c, d, e, words[38], 0x6ed9eba1);
+        STEP(F2, a, b, c, d, e, words[39], 0x6ed9eba1);
+
+        STEP(F3, a, b, c, d, e, words[40], 0x8f1bbcdc);
+        STEP(F3, a, b, c, d, e, words[41], 0x8f1bbcdc);
+        STEP(F3, a, b, c, d, e, words[42], 0x8f1bbcdc);
+        STEP(F3, a, b, c, d, e, words[43], 0x8f1bbcdc);
+        STEP(F3, a, b, c, d, e, words[44], 0x8f1bbcdc);
+        STEP(F3, a, b, c, d, e, words[45], 0x8f1bbcdc);
+        STEP(F3, a, b, c, d, e, words[46], 0x8f1bbcdc);
+        STEP(F3, a, b, c, d, e, words[47], 0x8f1bbcdc);
+        STEP(F3, a, b, c, d, e, words[48], 0x8f1bbcdc);
+        STEP(F3, a, b, c, d, e, words[49], 0x8f1bbcdc);
+        STEP(F3, a, b, c, d, e, words[50], 0x8f1bbcdc);
+        STEP(F3, a, b, c, d, e, words[51], 0x8f1bbcdc);
+        STEP(F3, a, b, c, d, e, words[52], 0x8f1bbcdc);
+        STEP(F3, a, b, c, d, e, words[53], 0x8f1bbcdc);
+        STEP(F3, a, b, c, d, e, words[54], 0x8f1bbcdc);
+        STEP(F3, a, b, c, d, e, words[55], 0x8f1bbcdc);
+        STEP(F3, a, b, c, d, e, words[56], 0x8f1bbcdc);
+        STEP(F3, a, b, c, d, e, words[57], 0x8f1bbcdc);
+        STEP(F3, a, b, c, d, e, words[58], 0x8f1bbcdc);
+        STEP(F3, a, b, c, d, e, words[59], 0x8f1bbcdc);
+
+        STEP(F2, a, b, c, d, e, words[60], 0xca62c1d6);
+        STEP(F2, a, b, c, d, e, words[61], 0xca62c1d6);
+        STEP(F2, a, b, c, d, e, words[62], 0xca62c1d6);
+        STEP(F2, a, b, c, d, e, words[63], 0xca62c1d6);
+        STEP(F2, a, b, c, d, e, words[64], 0xca62c1d6);
+        STEP(F2, a, b, c, d, e, words[65], 0xca62c1d6);
+        STEP(F2, a, b, c, d, e, words[66], 0xca62c1d6);
+        STEP(F2, a, b, c, d, e, words[67], 0xca62c1d6);
+        STEP(F2, a, b, c, d, e, words[68], 0xca62c1d6);
+        STEP(F2, a, b, c, d, e, words[69], 0xca62c1d6);
+        STEP(F2, a, b, c, d, e, words[70], 0xca62c1d6);
+        STEP(F2, a, b, c, d, e, words[71], 0xca62c1d6);
+        STEP(F2, a, b, c, d, e, words[72], 0xca62c1d6);
+        STEP(F2, a, b, c, d, e, words[73], 0xca62c1d6);
+        STEP(F2, a, b, c, d, e, words[74], 0xca62c1d6);
+        STEP(F2, a, b, c, d, e, words[75], 0xca62c1d6);
+        STEP(F2, a, b, c, d, e, words[76], 0xca62c1d6);
+        STEP(F2, a, b, c, d, e, words[77], 0xca62c1d6);
+        STEP(F2, a, b, c, d, e, words[78], 0xca62c1d6);
+        STEP(F2, a, b, c, d, e, words[79], 0xca62c1d6);
+
+        a += saved_a;
+        b += saved_b;
+        c += saved_c;
+        d += saved_d;
+        e += saved_e;
+
+        p += 64;
+
+    } while (size -= 64);
+
+    ctx->a = a;
+    ctx->b = b;
+    ctx->c = c;
+    ctx->d = d;
+    ctx->e = e;
+
+    return p;
+}
diff --git a/nginx/src/core/ngx_sha1.h b/nginx/src/core/ngx_sha1.h
new file mode 100644 (file)
index 0000000..4a98f71
--- /dev/null
@@ -0,0 +1,28 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_SHA1_H_INCLUDED_
+#define _NGX_SHA1_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef struct {
+    uint64_t  bytes;
+    uint32_t  a, b, c, d, e, f;
+    u_char    buffer[64];
+} ngx_sha1_t;
+
+
+void ngx_sha1_init(ngx_sha1_t *ctx);
+void ngx_sha1_update(ngx_sha1_t *ctx, const void *data, size_t size);
+void ngx_sha1_final(u_char result[20], ngx_sha1_t *ctx);
+
+
+#endif /* _NGX_SHA1_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_shmtx.c b/nginx/src/core/ngx_shmtx.c
new file mode 100644 (file)
index 0000000..a255903
--- /dev/null
@@ -0,0 +1,310 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#if (NGX_HAVE_ATOMIC_OPS)
+
+
+static void ngx_shmtx_wakeup(ngx_shmtx_t *mtx);
+
+
+ngx_int_t
+ngx_shmtx_create(ngx_shmtx_t *mtx, ngx_shmtx_sh_t *addr, u_char *name)
+{
+    mtx->lock = &addr->lock;
+
+    if (mtx->spin == (ngx_uint_t) -1) {
+        return NGX_OK;
+    }
+
+    mtx->spin = 2048;
+
+#if (NGX_HAVE_POSIX_SEM)
+
+    mtx->wait = &addr->wait;
+
+    if (sem_init(&mtx->sem, 1, 0) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno,
+                      "sem_init() failed");
+    } else {
+        mtx->semaphore = 1;
+    }
+
+#endif
+
+    return NGX_OK;
+}
+
+
+void
+ngx_shmtx_destroy(ngx_shmtx_t *mtx)
+{
+#if (NGX_HAVE_POSIX_SEM)
+
+    if (mtx->semaphore) {
+        if (sem_destroy(&mtx->sem) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno,
+                          "sem_destroy() failed");
+        }
+    }
+
+#endif
+}
+
+
+ngx_uint_t
+ngx_shmtx_trylock(ngx_shmtx_t *mtx)
+{
+    return (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid));
+}
+
+
+void
+ngx_shmtx_lock(ngx_shmtx_t *mtx)
+{
+    ngx_uint_t         i, n;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, "shmtx lock");
+
+    for ( ;; ) {
+
+        if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) {
+            return;
+        }
+
+        if (ngx_ncpu > 1) {
+
+            for (n = 1; n < mtx->spin; n <<= 1) {
+
+                for (i = 0; i < n; i++) {
+                    ngx_cpu_pause();
+                }
+
+                if (*mtx->lock == 0
+                    && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid))
+                {
+                    return;
+                }
+            }
+        }
+
+#if (NGX_HAVE_POSIX_SEM)
+
+        if (mtx->semaphore) {
+            (void) ngx_atomic_fetch_add(mtx->wait, 1);
+
+            if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) {
+                (void) ngx_atomic_fetch_add(mtx->wait, -1);
+                return;
+            }
+
+            ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
+                           "shmtx wait %uA", *mtx->wait);
+
+            while (sem_wait(&mtx->sem) == -1) {
+                ngx_err_t  err;
+
+                err = ngx_errno;
+
+                if (err != NGX_EINTR) {
+                    ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, err,
+                                  "sem_wait() failed while waiting on shmtx");
+                    break;
+                }
+            }
+
+            ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
+                           "shmtx awoke");
+
+            continue;
+        }
+
+#endif
+
+        ngx_sched_yield();
+    }
+}
+
+
+void
+ngx_shmtx_unlock(ngx_shmtx_t *mtx)
+{
+    if (mtx->spin != (ngx_uint_t) -1) {
+        ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, "shmtx unlock");
+    }
+
+    if (ngx_atomic_cmp_set(mtx->lock, ngx_pid, 0)) {
+        ngx_shmtx_wakeup(mtx);
+    }
+}
+
+
+ngx_uint_t
+ngx_shmtx_force_unlock(ngx_shmtx_t *mtx, ngx_pid_t pid)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
+                   "shmtx forced unlock");
+
+    if (ngx_atomic_cmp_set(mtx->lock, pid, 0)) {
+        ngx_shmtx_wakeup(mtx);
+        return 1;
+    }
+
+    return 0;
+}
+
+
+static void
+ngx_shmtx_wakeup(ngx_shmtx_t *mtx)
+{
+#if (NGX_HAVE_POSIX_SEM)
+    ngx_atomic_uint_t  wait;
+
+    if (!mtx->semaphore) {
+        return;
+    }
+
+    for ( ;; ) {
+
+        wait = *mtx->wait;
+
+        if ((ngx_atomic_int_t) wait <= 0) {
+            return;
+        }
+
+        if (ngx_atomic_cmp_set(mtx->wait, wait, wait - 1)) {
+            break;
+        }
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
+                   "shmtx wake %uA", wait);
+
+    if (sem_post(&mtx->sem) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno,
+                      "sem_post() failed while wake shmtx");
+    }
+
+#endif
+}
+
+
+#else
+
+
+ngx_int_t
+ngx_shmtx_create(ngx_shmtx_t *mtx, ngx_shmtx_sh_t *addr, u_char *name)
+{
+    if (mtx->name) {
+
+        if (ngx_strcmp(name, mtx->name) == 0) {
+            mtx->name = name;
+            return NGX_OK;
+        }
+
+        ngx_shmtx_destroy(mtx);
+    }
+
+    mtx->fd = ngx_open_file(name, NGX_FILE_RDWR, NGX_FILE_CREATE_OR_OPEN,
+                            NGX_FILE_DEFAULT_ACCESS);
+
+    if (mtx->fd == NGX_INVALID_FILE) {
+        ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno,
+                      ngx_open_file_n " \"%s\" failed", name);
+        return NGX_ERROR;
+    }
+
+    if (ngx_delete_file(name) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno,
+                      ngx_delete_file_n " \"%s\" failed", name);
+    }
+
+    mtx->name = name;
+
+    return NGX_OK;
+}
+
+
+void
+ngx_shmtx_destroy(ngx_shmtx_t *mtx)
+{
+    if (ngx_close_file(mtx->fd) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno,
+                      ngx_close_file_n " \"%s\" failed", mtx->name);
+    }
+}
+
+
+ngx_uint_t
+ngx_shmtx_trylock(ngx_shmtx_t *mtx)
+{
+    ngx_err_t  err;
+
+    err = ngx_trylock_fd(mtx->fd);
+
+    if (err == 0) {
+        return 1;
+    }
+
+    if (err == NGX_EAGAIN) {
+        return 0;
+    }
+
+#if __osf__ /* Tru64 UNIX */
+
+    if (err == NGX_EACCES) {
+        return 0;
+    }
+
+#endif
+
+    ngx_log_abort(err, ngx_trylock_fd_n " %s failed", mtx->name);
+
+    return 0;
+}
+
+
+void
+ngx_shmtx_lock(ngx_shmtx_t *mtx)
+{
+    ngx_err_t  err;
+
+    err = ngx_lock_fd(mtx->fd);
+
+    if (err == 0) {
+        return;
+    }
+
+    ngx_log_abort(err, ngx_lock_fd_n " %s failed", mtx->name);
+}
+
+
+void
+ngx_shmtx_unlock(ngx_shmtx_t *mtx)
+{
+    ngx_err_t  err;
+
+    err = ngx_unlock_fd(mtx->fd);
+
+    if (err == 0) {
+        return;
+    }
+
+    ngx_log_abort(err, ngx_unlock_fd_n " %s failed", mtx->name);
+}
+
+
+ngx_uint_t
+ngx_shmtx_force_unlock(ngx_shmtx_t *mtx, ngx_pid_t pid)
+{
+    return 0;
+}
+
+#endif
diff --git a/nginx/src/core/ngx_shmtx.h b/nginx/src/core/ngx_shmtx.h
new file mode 100644 (file)
index 0000000..91e11be
--- /dev/null
@@ -0,0 +1,49 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_SHMTX_H_INCLUDED_
+#define _NGX_SHMTX_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef struct {
+    ngx_atomic_t   lock;
+#if (NGX_HAVE_POSIX_SEM)
+    ngx_atomic_t   wait;
+#endif
+} ngx_shmtx_sh_t;
+
+
+typedef struct {
+#if (NGX_HAVE_ATOMIC_OPS)
+    ngx_atomic_t  *lock;
+#if (NGX_HAVE_POSIX_SEM)
+    ngx_atomic_t  *wait;
+    ngx_uint_t     semaphore;
+    sem_t          sem;
+#endif
+#else
+    ngx_fd_t       fd;
+    u_char        *name;
+#endif
+    ngx_uint_t     spin;
+} ngx_shmtx_t;
+
+
+ngx_int_t ngx_shmtx_create(ngx_shmtx_t *mtx, ngx_shmtx_sh_t *addr,
+    u_char *name);
+void ngx_shmtx_destroy(ngx_shmtx_t *mtx);
+ngx_uint_t ngx_shmtx_trylock(ngx_shmtx_t *mtx);
+void ngx_shmtx_lock(ngx_shmtx_t *mtx);
+void ngx_shmtx_unlock(ngx_shmtx_t *mtx);
+ngx_uint_t ngx_shmtx_force_unlock(ngx_shmtx_t *mtx, ngx_pid_t pid);
+
+
+#endif /* _NGX_SHMTX_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_slab.c b/nginx/src/core/ngx_slab.c
new file mode 100644 (file)
index 0000000..4023870
--- /dev/null
@@ -0,0 +1,818 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#define NGX_SLAB_PAGE_MASK   3
+#define NGX_SLAB_PAGE        0
+#define NGX_SLAB_BIG         1
+#define NGX_SLAB_EXACT       2
+#define NGX_SLAB_SMALL       3
+
+#if (NGX_PTR_SIZE == 4)
+
+#define NGX_SLAB_PAGE_FREE   0
+#define NGX_SLAB_PAGE_BUSY   0xffffffff
+#define NGX_SLAB_PAGE_START  0x80000000
+
+#define NGX_SLAB_SHIFT_MASK  0x0000000f
+#define NGX_SLAB_MAP_MASK    0xffff0000
+#define NGX_SLAB_MAP_SHIFT   16
+
+#define NGX_SLAB_BUSY        0xffffffff
+
+#else /* (NGX_PTR_SIZE == 8) */
+
+#define NGX_SLAB_PAGE_FREE   0
+#define NGX_SLAB_PAGE_BUSY   0xffffffffffffffff
+#define NGX_SLAB_PAGE_START  0x8000000000000000
+
+#define NGX_SLAB_SHIFT_MASK  0x000000000000000f
+#define NGX_SLAB_MAP_MASK    0xffffffff00000000
+#define NGX_SLAB_MAP_SHIFT   32
+
+#define NGX_SLAB_BUSY        0xffffffffffffffff
+
+#endif
+
+
+#define ngx_slab_slots(pool)                                                  \
+    (ngx_slab_page_t *) ((u_char *) (pool) + sizeof(ngx_slab_pool_t))
+
+#define ngx_slab_page_type(page)   ((page)->prev & NGX_SLAB_PAGE_MASK)
+
+#define ngx_slab_page_prev(page)                                              \
+    (ngx_slab_page_t *) ((page)->prev & ~NGX_SLAB_PAGE_MASK)
+
+#define ngx_slab_page_addr(pool, page)                                        \
+    ((((page) - (pool)->pages) << ngx_pagesize_shift)                         \
+     + (uintptr_t) (pool)->start)
+
+
+#if (NGX_DEBUG_MALLOC)
+
+#define ngx_slab_junk(p, size)     ngx_memset(p, 0xA5, size)
+
+#elif (NGX_HAVE_DEBUG_MALLOC)
+
+#define ngx_slab_junk(p, size)                                                \
+    if (ngx_debug_malloc)          ngx_memset(p, 0xA5, size)
+
+#else
+
+#define ngx_slab_junk(p, size)
+
+#endif
+
+static ngx_slab_page_t *ngx_slab_alloc_pages(ngx_slab_pool_t *pool,
+    ngx_uint_t pages);
+static void ngx_slab_free_pages(ngx_slab_pool_t *pool, ngx_slab_page_t *page,
+    ngx_uint_t pages);
+static void ngx_slab_error(ngx_slab_pool_t *pool, ngx_uint_t level,
+    char *text);
+
+
+static ngx_uint_t  ngx_slab_max_size;
+static ngx_uint_t  ngx_slab_exact_size;
+static ngx_uint_t  ngx_slab_exact_shift;
+
+
+void
+ngx_slab_sizes_init(void)
+{
+    ngx_uint_t  n;
+
+    ngx_slab_max_size = ngx_pagesize / 2;
+    ngx_slab_exact_size = ngx_pagesize / (8 * sizeof(uintptr_t));
+    for (n = ngx_slab_exact_size; n >>= 1; ngx_slab_exact_shift++) {
+        /* void */
+    }
+}
+
+
+void
+ngx_slab_init(ngx_slab_pool_t *pool)
+{
+    u_char           *p;
+    size_t            size;
+    ngx_int_t         m;
+    ngx_uint_t        i, n, pages;
+    ngx_slab_page_t  *slots, *page;
+
+    pool->min_size = (size_t) 1 << pool->min_shift;
+
+    slots = ngx_slab_slots(pool);
+
+    p = (u_char *) slots;
+    size = pool->end - p;
+
+    ngx_slab_junk(p, size);
+
+    n = ngx_pagesize_shift - pool->min_shift;
+
+    for (i = 0; i < n; i++) {
+        /* only "next" is used in list head */
+        slots[i].slab = 0;
+        slots[i].next = &slots[i];
+        slots[i].prev = 0;
+    }
+
+    p += n * sizeof(ngx_slab_page_t);
+
+    pool->stats = (ngx_slab_stat_t *) p;
+    ngx_memzero(pool->stats, n * sizeof(ngx_slab_stat_t));
+
+    p += n * sizeof(ngx_slab_stat_t);
+
+    size -= n * (sizeof(ngx_slab_page_t) + sizeof(ngx_slab_stat_t));
+
+    pages = (ngx_uint_t) (size / (ngx_pagesize + sizeof(ngx_slab_page_t)));
+
+    pool->pages = (ngx_slab_page_t *) p;
+    ngx_memzero(pool->pages, pages * sizeof(ngx_slab_page_t));
+
+    page = pool->pages;
+
+    /* only "next" is used in list head */
+    pool->free.slab = 0;
+    pool->free.next = page;
+    pool->free.prev = 0;
+
+    page->slab = pages;
+    page->next = &pool->free;
+    page->prev = (uintptr_t) &pool->free;
+
+    pool->start = ngx_align_ptr(p + pages * sizeof(ngx_slab_page_t),
+                                ngx_pagesize);
+
+    m = pages - (pool->end - pool->start) / ngx_pagesize;
+    if (m > 0) {
+        pages -= m;
+        page->slab = pages;
+    }
+
+    pool->last = pool->pages + pages;
+    pool->pfree = pages;
+
+    pool->log_nomem = 1;
+    pool->log_ctx = &pool->zero;
+    pool->zero = '\0';
+}
+
+
+void *
+ngx_slab_alloc(ngx_slab_pool_t *pool, size_t size)
+{
+    void  *p;
+
+    ngx_shmtx_lock(&pool->mutex);
+
+    p = ngx_slab_alloc_locked(pool, size);
+
+    ngx_shmtx_unlock(&pool->mutex);
+
+    return p;
+}
+
+
+void *
+ngx_slab_alloc_locked(ngx_slab_pool_t *pool, size_t size)
+{
+    size_t            s;
+    uintptr_t         p, m, mask, *bitmap;
+    ngx_uint_t        i, n, slot, shift, map;
+    ngx_slab_page_t  *page, *prev, *slots;
+
+    if (size > ngx_slab_max_size) {
+
+        ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0,
+                       "slab alloc: %uz", size);
+
+        page = ngx_slab_alloc_pages(pool, (size >> ngx_pagesize_shift)
+                                          + ((size % ngx_pagesize) ? 1 : 0));
+        if (page) {
+            p = ngx_slab_page_addr(pool, page);
+
+        } else {
+            p = 0;
+        }
+
+        goto done;
+    }
+
+    if (size > pool->min_size) {
+        shift = 1;
+        for (s = size - 1; s >>= 1; shift++) { /* void */ }
+        slot = shift - pool->min_shift;
+
+    } else {
+        shift = pool->min_shift;
+        slot = 0;
+    }
+
+    pool->stats[slot].reqs++;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0,
+                   "slab alloc: %uz slot: %ui", size, slot);
+
+    slots = ngx_slab_slots(pool);
+    page = slots[slot].next;
+
+    if (page->next != page) {
+
+        if (shift < ngx_slab_exact_shift) {
+
+            bitmap = (uintptr_t *) ngx_slab_page_addr(pool, page);
+
+            map = (ngx_pagesize >> shift) / (8 * sizeof(uintptr_t));
+
+            for (n = 0; n < map; n++) {
+
+                if (bitmap[n] != NGX_SLAB_BUSY) {
+
+                    for (m = 1, i = 0; m; m <<= 1, i++) {
+                        if (bitmap[n] & m) {
+                            continue;
+                        }
+
+                        bitmap[n] |= m;
+
+                        i = (n * 8 * sizeof(uintptr_t) + i) << shift;
+
+                        p = (uintptr_t) bitmap + i;
+
+                        pool->stats[slot].used++;
+
+                        if (bitmap[n] == NGX_SLAB_BUSY) {
+                            for (n = n + 1; n < map; n++) {
+                                if (bitmap[n] != NGX_SLAB_BUSY) {
+                                    goto done;
+                                }
+                            }
+
+                            prev = ngx_slab_page_prev(page);
+                            prev->next = page->next;
+                            page->next->prev = page->prev;
+
+                            page->next = NULL;
+                            page->prev = NGX_SLAB_SMALL;
+                        }
+
+                        goto done;
+                    }
+                }
+            }
+
+        } else if (shift == ngx_slab_exact_shift) {
+
+            for (m = 1, i = 0; m; m <<= 1, i++) {
+                if (page->slab & m) {
+                    continue;
+                }
+
+                page->slab |= m;
+
+                if (page->slab == NGX_SLAB_BUSY) {
+                    prev = ngx_slab_page_prev(page);
+                    prev->next = page->next;
+                    page->next->prev = page->prev;
+
+                    page->next = NULL;
+                    page->prev = NGX_SLAB_EXACT;
+                }
+
+                p = ngx_slab_page_addr(pool, page) + (i << shift);
+
+                pool->stats[slot].used++;
+
+                goto done;
+            }
+
+        } else { /* shift > ngx_slab_exact_shift */
+
+            mask = ((uintptr_t) 1 << (ngx_pagesize >> shift)) - 1;
+            mask <<= NGX_SLAB_MAP_SHIFT;
+
+            for (m = (uintptr_t) 1 << NGX_SLAB_MAP_SHIFT, i = 0;
+                 m & mask;
+                 m <<= 1, i++)
+            {
+                if (page->slab & m) {
+                    continue;
+                }
+
+                page->slab |= m;
+
+                if ((page->slab & NGX_SLAB_MAP_MASK) == mask) {
+                    prev = ngx_slab_page_prev(page);
+                    prev->next = page->next;
+                    page->next->prev = page->prev;
+
+                    page->next = NULL;
+                    page->prev = NGX_SLAB_BIG;
+                }
+
+                p = ngx_slab_page_addr(pool, page) + (i << shift);
+
+                pool->stats[slot].used++;
+
+                goto done;
+            }
+        }
+
+        ngx_slab_error(pool, NGX_LOG_ALERT, "ngx_slab_alloc(): page is busy");
+        ngx_debug_point();
+    }
+
+    page = ngx_slab_alloc_pages(pool, 1);
+
+    if (page) {
+        if (shift < ngx_slab_exact_shift) {
+            bitmap = (uintptr_t *) ngx_slab_page_addr(pool, page);
+
+            n = (ngx_pagesize >> shift) / ((1 << shift) * 8);
+
+            if (n == 0) {
+                n = 1;
+            }
+
+            /* "n" elements for bitmap, plus one requested */
+
+            for (i = 0; i < (n + 1) / (8 * sizeof(uintptr_t)); i++) {
+                bitmap[i] = NGX_SLAB_BUSY;
+            }
+
+            m = ((uintptr_t) 1 << ((n + 1) % (8 * sizeof(uintptr_t)))) - 1;
+            bitmap[i] = m;
+
+            map = (ngx_pagesize >> shift) / (8 * sizeof(uintptr_t));
+
+            for (i = i + 1; i < map; i++) {
+                bitmap[i] = 0;
+            }
+
+            page->slab = shift;
+            page->next = &slots[slot];
+            page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_SMALL;
+
+            slots[slot].next = page;
+
+            pool->stats[slot].total += (ngx_pagesize >> shift) - n;
+
+            p = ngx_slab_page_addr(pool, page) + (n << shift);
+
+            pool->stats[slot].used++;
+
+            goto done;
+
+        } else if (shift == ngx_slab_exact_shift) {
+
+            page->slab = 1;
+            page->next = &slots[slot];
+            page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_EXACT;
+
+            slots[slot].next = page;
+
+            pool->stats[slot].total += 8 * sizeof(uintptr_t);
+
+            p = ngx_slab_page_addr(pool, page);
+
+            pool->stats[slot].used++;
+
+            goto done;
+
+        } else { /* shift > ngx_slab_exact_shift */
+
+            page->slab = ((uintptr_t) 1 << NGX_SLAB_MAP_SHIFT) | shift;
+            page->next = &slots[slot];
+            page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_BIG;
+
+            slots[slot].next = page;
+
+            pool->stats[slot].total += ngx_pagesize >> shift;
+
+            p = ngx_slab_page_addr(pool, page);
+
+            pool->stats[slot].used++;
+
+            goto done;
+        }
+    }
+
+    p = 0;
+
+    pool->stats[slot].fails++;
+
+done:
+
+    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0,
+                   "slab alloc: %p", (void *) p);
+
+    return (void *) p;
+}
+
+
+void *
+ngx_slab_calloc(ngx_slab_pool_t *pool, size_t size)
+{
+    void  *p;
+
+    ngx_shmtx_lock(&pool->mutex);
+
+    p = ngx_slab_calloc_locked(pool, size);
+
+    ngx_shmtx_unlock(&pool->mutex);
+
+    return p;
+}
+
+
+void *
+ngx_slab_calloc_locked(ngx_slab_pool_t *pool, size_t size)
+{
+    void  *p;
+
+    p = ngx_slab_alloc_locked(pool, size);
+    if (p) {
+        ngx_memzero(p, size);
+    }
+
+    return p;
+}
+
+
+void
+ngx_slab_free(ngx_slab_pool_t *pool, void *p)
+{
+    ngx_shmtx_lock(&pool->mutex);
+
+    ngx_slab_free_locked(pool, p);
+
+    ngx_shmtx_unlock(&pool->mutex);
+}
+
+
+void
+ngx_slab_free_locked(ngx_slab_pool_t *pool, void *p)
+{
+    size_t            size;
+    uintptr_t         slab, m, *bitmap;
+    ngx_uint_t        i, n, type, slot, shift, map;
+    ngx_slab_page_t  *slots, *page;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0, "slab free: %p", p);
+
+    if ((u_char *) p < pool->start || (u_char *) p > pool->end) {
+        ngx_slab_error(pool, NGX_LOG_ALERT, "ngx_slab_free(): outside of pool");
+        goto fail;
+    }
+
+    n = ((u_char *) p - pool->start) >> ngx_pagesize_shift;
+    page = &pool->pages[n];
+    slab = page->slab;
+    type = ngx_slab_page_type(page);
+
+    switch (type) {
+
+    case NGX_SLAB_SMALL:
+
+        shift = slab & NGX_SLAB_SHIFT_MASK;
+        size = (size_t) 1 << shift;
+
+        if ((uintptr_t) p & (size - 1)) {
+            goto wrong_chunk;
+        }
+
+        n = ((uintptr_t) p & (ngx_pagesize - 1)) >> shift;
+        m = (uintptr_t) 1 << (n % (8 * sizeof(uintptr_t)));
+        n /= 8 * sizeof(uintptr_t);
+        bitmap = (uintptr_t *)
+                             ((uintptr_t) p & ~((uintptr_t) ngx_pagesize - 1));
+
+        if (bitmap[n] & m) {
+            slot = shift - pool->min_shift;
+
+            if (page->next == NULL) {
+                slots = ngx_slab_slots(pool);
+
+                page->next = slots[slot].next;
+                slots[slot].next = page;
+
+                page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_SMALL;
+                page->next->prev = (uintptr_t) page | NGX_SLAB_SMALL;
+            }
+
+            bitmap[n] &= ~m;
+
+            n = (ngx_pagesize >> shift) / ((1 << shift) * 8);
+
+            if (n == 0) {
+                n = 1;
+            }
+
+            i = n / (8 * sizeof(uintptr_t));
+            m = ((uintptr_t) 1 << (n % (8 * sizeof(uintptr_t)))) - 1;
+
+            if (bitmap[i] & ~m) {
+                goto done;
+            }
+
+            map = (ngx_pagesize >> shift) / (8 * sizeof(uintptr_t));
+
+            for (i = i + 1; i < map; i++) {
+                if (bitmap[i]) {
+                    goto done;
+                }
+            }
+
+            ngx_slab_free_pages(pool, page, 1);
+
+            pool->stats[slot].total -= (ngx_pagesize >> shift) - n;
+
+            goto done;
+        }
+
+        goto chunk_already_free;
+
+    case NGX_SLAB_EXACT:
+
+        m = (uintptr_t) 1 <<
+                (((uintptr_t) p & (ngx_pagesize - 1)) >> ngx_slab_exact_shift);
+        size = ngx_slab_exact_size;
+
+        if ((uintptr_t) p & (size - 1)) {
+            goto wrong_chunk;
+        }
+
+        if (slab & m) {
+            slot = ngx_slab_exact_shift - pool->min_shift;
+
+            if (slab == NGX_SLAB_BUSY) {
+                slots = ngx_slab_slots(pool);
+
+                page->next = slots[slot].next;
+                slots[slot].next = page;
+
+                page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_EXACT;
+                page->next->prev = (uintptr_t) page | NGX_SLAB_EXACT;
+            }
+
+            page->slab &= ~m;
+
+            if (page->slab) {
+                goto done;
+            }
+
+            ngx_slab_free_pages(pool, page, 1);
+
+            pool->stats[slot].total -= 8 * sizeof(uintptr_t);
+
+            goto done;
+        }
+
+        goto chunk_already_free;
+
+    case NGX_SLAB_BIG:
+
+        shift = slab & NGX_SLAB_SHIFT_MASK;
+        size = (size_t) 1 << shift;
+
+        if ((uintptr_t) p & (size - 1)) {
+            goto wrong_chunk;
+        }
+
+        m = (uintptr_t) 1 << ((((uintptr_t) p & (ngx_pagesize - 1)) >> shift)
+                              + NGX_SLAB_MAP_SHIFT);
+
+        if (slab & m) {
+            slot = shift - pool->min_shift;
+
+            if (page->next == NULL) {
+                slots = ngx_slab_slots(pool);
+
+                page->next = slots[slot].next;
+                slots[slot].next = page;
+
+                page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_BIG;
+                page->next->prev = (uintptr_t) page | NGX_SLAB_BIG;
+            }
+
+            page->slab &= ~m;
+
+            if (page->slab & NGX_SLAB_MAP_MASK) {
+                goto done;
+            }
+
+            ngx_slab_free_pages(pool, page, 1);
+
+            pool->stats[slot].total -= ngx_pagesize >> shift;
+
+            goto done;
+        }
+
+        goto chunk_already_free;
+
+    case NGX_SLAB_PAGE:
+
+        if ((uintptr_t) p & (ngx_pagesize - 1)) {
+            goto wrong_chunk;
+        }
+
+        if (!(slab & NGX_SLAB_PAGE_START)) {
+            ngx_slab_error(pool, NGX_LOG_ALERT,
+                           "ngx_slab_free(): page is already free");
+            goto fail;
+        }
+
+        if (slab == NGX_SLAB_PAGE_BUSY) {
+            ngx_slab_error(pool, NGX_LOG_ALERT,
+                           "ngx_slab_free(): pointer to wrong page");
+            goto fail;
+        }
+
+        n = ((u_char *) p - pool->start) >> ngx_pagesize_shift;
+        size = slab & ~NGX_SLAB_PAGE_START;
+
+        ngx_slab_free_pages(pool, &pool->pages[n], size);
+
+        ngx_slab_junk(p, size << ngx_pagesize_shift);
+
+        return;
+    }
+
+    /* not reached */
+
+    return;
+
+done:
+
+    pool->stats[slot].used--;
+
+    ngx_slab_junk(p, size);
+
+    return;
+
+wrong_chunk:
+
+    ngx_slab_error(pool, NGX_LOG_ALERT,
+                   "ngx_slab_free(): pointer to wrong chunk");
+
+    goto fail;
+
+chunk_already_free:
+
+    ngx_slab_error(pool, NGX_LOG_ALERT,
+                   "ngx_slab_free(): chunk is already free");
+
+fail:
+
+    return;
+}
+
+
+static ngx_slab_page_t *
+ngx_slab_alloc_pages(ngx_slab_pool_t *pool, ngx_uint_t pages)
+{
+    ngx_slab_page_t  *page, *p;
+
+    for (page = pool->free.next; page != &pool->free; page = page->next) {
+
+        if (page->slab >= pages) {
+
+            if (page->slab > pages) {
+                page[page->slab - 1].prev = (uintptr_t) &page[pages];
+
+                page[pages].slab = page->slab - pages;
+                page[pages].next = page->next;
+                page[pages].prev = page->prev;
+
+                p = (ngx_slab_page_t *) page->prev;
+                p->next = &page[pages];
+                page->next->prev = (uintptr_t) &page[pages];
+
+            } else {
+                p = (ngx_slab_page_t *) page->prev;
+                p->next = page->next;
+                page->next->prev = page->prev;
+            }
+
+            page->slab = pages | NGX_SLAB_PAGE_START;
+            page->next = NULL;
+            page->prev = NGX_SLAB_PAGE;
+
+            pool->pfree -= pages;
+
+            if (--pages == 0) {
+                return page;
+            }
+
+            for (p = page + 1; pages; pages--) {
+                p->slab = NGX_SLAB_PAGE_BUSY;
+                p->next = NULL;
+                p->prev = NGX_SLAB_PAGE;
+                p++;
+            }
+
+            return page;
+        }
+    }
+
+    if (pool->log_nomem) {
+        ngx_slab_error(pool, NGX_LOG_CRIT,
+                       "ngx_slab_alloc() failed: no memory");
+    }
+
+    return NULL;
+}
+
+
+static void
+ngx_slab_free_pages(ngx_slab_pool_t *pool, ngx_slab_page_t *page,
+    ngx_uint_t pages)
+{
+    ngx_slab_page_t  *prev, *join;
+
+    pool->pfree += pages;
+
+    page->slab = pages--;
+
+    if (pages) {
+        ngx_memzero(&page[1], pages * sizeof(ngx_slab_page_t));
+    }
+
+    if (page->next) {
+        prev = ngx_slab_page_prev(page);
+        prev->next = page->next;
+        page->next->prev = page->prev;
+    }
+
+    join = page + page->slab;
+
+    if (join < pool->last) {
+
+        if (ngx_slab_page_type(join) == NGX_SLAB_PAGE) {
+
+            if (join->next != NULL) {
+                pages += join->slab;
+                page->slab += join->slab;
+
+                prev = ngx_slab_page_prev(join);
+                prev->next = join->next;
+                join->next->prev = join->prev;
+
+                join->slab = NGX_SLAB_PAGE_FREE;
+                join->next = NULL;
+                join->prev = NGX_SLAB_PAGE;
+            }
+        }
+    }
+
+    if (page > pool->pages) {
+        join = page - 1;
+
+        if (ngx_slab_page_type(join) == NGX_SLAB_PAGE) {
+
+            if (join->slab == NGX_SLAB_PAGE_FREE) {
+                join = ngx_slab_page_prev(join);
+            }
+
+            if (join->next != NULL) {
+                pages += join->slab;
+                join->slab += page->slab;
+
+                prev = ngx_slab_page_prev(join);
+                prev->next = join->next;
+                join->next->prev = join->prev;
+
+                page->slab = NGX_SLAB_PAGE_FREE;
+                page->next = NULL;
+                page->prev = NGX_SLAB_PAGE;
+
+                page = join;
+            }
+        }
+    }
+
+    if (pages) {
+        page[pages].prev = (uintptr_t) page;
+    }
+
+    page->prev = (uintptr_t) &pool->free;
+    page->next = pool->free.next;
+
+    page->next->prev = (uintptr_t) page;
+
+    pool->free.next = page;
+}
+
+
+static void
+ngx_slab_error(ngx_slab_pool_t *pool, ngx_uint_t level, char *text)
+{
+    ngx_log_error(level, ngx_cycle->log, 0, "%s%s", text, pool->log_ctx);
+}
diff --git a/nginx/src/core/ngx_slab.h b/nginx/src/core/ngx_slab.h
new file mode 100644 (file)
index 0000000..d1876bb
--- /dev/null
@@ -0,0 +1,72 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_SLAB_H_INCLUDED_
+#define _NGX_SLAB_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef struct ngx_slab_page_s  ngx_slab_page_t;
+
+struct ngx_slab_page_s {
+    uintptr_t         slab;
+    ngx_slab_page_t  *next;
+    uintptr_t         prev;
+};
+
+
+typedef struct {
+    ngx_uint_t        total;
+    ngx_uint_t        used;
+
+    ngx_uint_t        reqs;
+    ngx_uint_t        fails;
+} ngx_slab_stat_t;
+
+
+typedef struct {
+    ngx_shmtx_sh_t    lock;
+
+    size_t            min_size;
+    size_t            min_shift;
+
+    ngx_slab_page_t  *pages;
+    ngx_slab_page_t  *last;
+    ngx_slab_page_t   free;
+
+    ngx_slab_stat_t  *stats;
+    ngx_uint_t        pfree;
+
+    u_char           *start;
+    u_char           *end;
+
+    ngx_shmtx_t       mutex;
+
+    u_char           *log_ctx;
+    u_char            zero;
+
+    unsigned          log_nomem:1;
+
+    void             *data;
+    void             *addr;
+} ngx_slab_pool_t;
+
+
+void ngx_slab_sizes_init(void);
+void ngx_slab_init(ngx_slab_pool_t *pool);
+void *ngx_slab_alloc(ngx_slab_pool_t *pool, size_t size);
+void *ngx_slab_alloc_locked(ngx_slab_pool_t *pool, size_t size);
+void *ngx_slab_calloc(ngx_slab_pool_t *pool, size_t size);
+void *ngx_slab_calloc_locked(ngx_slab_pool_t *pool, size_t size);
+void ngx_slab_free(ngx_slab_pool_t *pool, void *p);
+void ngx_slab_free_locked(ngx_slab_pool_t *pool, void *p);
+
+
+#endif /* _NGX_SLAB_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_spinlock.c b/nginx/src/core/ngx_spinlock.c
new file mode 100644 (file)
index 0000000..9c93afa
--- /dev/null
@@ -0,0 +1,53 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+void
+ngx_spinlock(ngx_atomic_t *lock, ngx_atomic_int_t value, ngx_uint_t spin)
+{
+
+#if (NGX_HAVE_ATOMIC_OPS)
+
+    ngx_uint_t  i, n;
+
+    for ( ;; ) {
+
+        if (*lock == 0 && ngx_atomic_cmp_set(lock, 0, value)) {
+            return;
+        }
+
+        if (ngx_ncpu > 1) {
+
+            for (n = 1; n < spin; n <<= 1) {
+
+                for (i = 0; i < n; i++) {
+                    ngx_cpu_pause();
+                }
+
+                if (*lock == 0 && ngx_atomic_cmp_set(lock, 0, value)) {
+                    return;
+                }
+            }
+        }
+
+        ngx_sched_yield();
+    }
+
+#else
+
+#if (NGX_THREADS)
+
+#error ngx_spinlock() or ngx_atomic_cmp_set() are not defined !
+
+#endif
+
+#endif
+
+}
diff --git a/nginx/src/core/ngx_string.c b/nginx/src/core/ngx_string.c
new file mode 100644 (file)
index 0000000..2ee07bf
--- /dev/null
@@ -0,0 +1,2029 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+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 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,
+    const u_char *basis);
+
+
+void
+ngx_strlow(u_char *dst, u_char *src, size_t n)
+{
+    while (n) {
+        *dst = ngx_tolower(*src);
+        dst++;
+        src++;
+        n--;
+    }
+}
+
+
+size_t
+ngx_strnlen(u_char *p, size_t n)
+{
+    size_t  i;
+
+    for (i = 0; i < n; i++) {
+
+        if (p[i] == '\0') {
+            return i;
+        }
+    }
+
+    return n;
+}
+
+
+u_char *
+ngx_cpystrn(u_char *dst, u_char *src, size_t n)
+{
+    if (n == 0) {
+        return dst;
+    }
+
+    while (--n) {
+        *dst = *src;
+
+        if (*dst == '\0') {
+            return dst;
+        }
+
+        dst++;
+        src++;
+    }
+
+    *dst = '\0';
+
+    return dst;
+}
+
+
+u_char *
+ngx_pstrdup(ngx_pool_t *pool, ngx_str_t *src)
+{
+    u_char  *dst;
+
+    dst = ngx_pnalloc(pool, src->len);
+    if (dst == NULL) {
+        return NULL;
+    }
+
+    ngx_memcpy(dst, src->data, src->len);
+
+    return dst;
+}
+
+
+/*
+ * supported formats:
+ *    %[0][width][x][X]O        off_t
+ *    %[0][width]T              time_t
+ *    %[0][width][u][x|X]z      ssize_t/size_t
+ *    %[0][width][u][x|X]d      int/u_int
+ *    %[0][width][u][x|X]l      long
+ *    %[0][width|m][u][x|X]i    ngx_int_t/ngx_uint_t
+ *    %[0][width][u][x|X]D      int32_t/uint32_t
+ *    %[0][width][u][x|X]L      int64_t/uint64_t
+ *    %[0][width|m][u][x|X]A    ngx_atomic_int_t/ngx_atomic_uint_t
+ *    %[0][width][.width]f      double, max valid number fits to %18.15f
+ *    %P                        ngx_pid_t
+ *    %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
+ *    %Z                        '\0'
+ *    %N                        '\n'
+ *    %c                        char
+ *    %%                        %
+ *
+ *  reserved:
+ *    %t                        ptrdiff_t
+ *    %S                        null-terminated wchar string
+ *    %C                        wchar
+ */
+
+
+u_char * ngx_cdecl
+ngx_sprintf(u_char *buf, const char *fmt, ...)
+{
+    u_char   *p;
+    va_list   args;
+
+    va_start(args, fmt);
+    p = ngx_vslprintf(buf, (void *) -1, fmt, args);
+    va_end(args);
+
+    return p;
+}
+
+
+u_char * ngx_cdecl
+ngx_snprintf(u_char *buf, size_t max, const char *fmt, ...)
+{
+    u_char   *p;
+    va_list   args;
+
+    va_start(args, fmt);
+    p = ngx_vslprintf(buf, buf + max, fmt, args);
+    va_end(args);
+
+    return p;
+}
+
+
+u_char * ngx_cdecl
+ngx_slprintf(u_char *buf, u_char *last, const char *fmt, ...)
+{
+    u_char   *p;
+    va_list   args;
+
+    va_start(args, fmt);
+    p = ngx_vslprintf(buf, last, fmt, args);
+    va_end(args);
+
+    return p;
+}
+
+
+u_char *
+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;
+    int64_t                i64;
+    uint64_t               ui64, frac;
+    ngx_msec_t             ms;
+    ngx_uint_t             width, sign, hex, max_width, frac_width, scale, n;
+    ngx_str_t             *v;
+    ngx_variable_value_t  *vv;
+
+    while (*fmt && buf < last) {
+
+        /*
+         * "buf < last" means that we could copy at least one character:
+         * the plain character, "%%", "%c", and minus without the checking
+         */
+
+        if (*fmt == '%') {
+
+            i64 = 0;
+            ui64 = 0;
+
+            zero = (u_char) ((*++fmt == '0') ? '0' : ' ');
+            width = 0;
+            sign = 1;
+            hex = 0;
+            max_width = 0;
+            frac_width = 0;
+            slen = (size_t) -1;
+
+            while (*fmt >= '0' && *fmt <= '9') {
+                width = width * 10 + (*fmt++ - '0');
+            }
+
+
+            for ( ;; ) {
+                switch (*fmt) {
+
+                case 'u':
+                    sign = 0;
+                    fmt++;
+                    continue;
+
+                case 'm':
+                    max_width = 1;
+                    fmt++;
+                    continue;
+
+                case 'X':
+                    hex = 2;
+                    sign = 0;
+                    fmt++;
+                    continue;
+
+                case 'x':
+                    hex = 1;
+                    sign = 0;
+                    fmt++;
+                    continue;
+
+                case '.':
+                    fmt++;
+
+                    while (*fmt >= '0' && *fmt <= '9') {
+                        frac_width = frac_width * 10 + (*fmt++ - '0');
+                    }
+
+                    break;
+
+                case '*':
+                    slen = va_arg(args, size_t);
+                    fmt++;
+                    continue;
+
+                default:
+                    break;
+                }
+
+                break;
+            }
+
+
+            switch (*fmt) {
+
+            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);
+                fmt++;
+
+                continue;
+
+            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);
+                fmt++;
+
+                continue;
+
+            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);
+                }
+
+                fmt++;
+
+                continue;
+
+            case 'O':
+                i64 = (int64_t) va_arg(args, off_t);
+                sign = 1;
+                break;
+
+            case 'P':
+                i64 = (int64_t) va_arg(args, ngx_pid_t);
+                sign = 1;
+                break;
+
+            case 'T':
+                i64 = (int64_t) va_arg(args, time_t);
+                sign = 1;
+                break;
+
+            case 'M':
+                ms = (ngx_msec_t) va_arg(args, ngx_msec_t);
+                if ((ngx_msec_int_t) ms == -1) {
+                    sign = 1;
+                    i64 = -1;
+                } else {
+                    sign = 0;
+                    ui64 = (uint64_t) ms;
+                }
+                break;
+
+            case 'z':
+                if (sign) {
+                    i64 = (int64_t) va_arg(args, ssize_t);
+                } else {
+                    ui64 = (uint64_t) va_arg(args, size_t);
+                }
+                break;
+
+            case 'i':
+                if (sign) {
+                    i64 = (int64_t) va_arg(args, ngx_int_t);
+                } else {
+                    ui64 = (uint64_t) va_arg(args, ngx_uint_t);
+                }
+
+                if (max_width) {
+                    width = NGX_INT_T_LEN;
+                }
+
+                break;
+
+            case 'd':
+                if (sign) {
+                    i64 = (int64_t) va_arg(args, int);
+                } else {
+                    ui64 = (uint64_t) va_arg(args, u_int);
+                }
+                break;
+
+            case 'l':
+                if (sign) {
+                    i64 = (int64_t) va_arg(args, long);
+                } else {
+                    ui64 = (uint64_t) va_arg(args, u_long);
+                }
+                break;
+
+            case 'D':
+                if (sign) {
+                    i64 = (int64_t) va_arg(args, int32_t);
+                } else {
+                    ui64 = (uint64_t) va_arg(args, uint32_t);
+                }
+                break;
+
+            case 'L':
+                if (sign) {
+                    i64 = va_arg(args, int64_t);
+                } else {
+                    ui64 = va_arg(args, uint64_t);
+                }
+                break;
+
+            case 'A':
+                if (sign) {
+                    i64 = (int64_t) va_arg(args, ngx_atomic_int_t);
+                } else {
+                    ui64 = (uint64_t) va_arg(args, ngx_atomic_uint_t);
+                }
+
+                if (max_width) {
+                    width = NGX_ATOMIC_T_LEN;
+                }
+
+                break;
+
+            case 'f':
+                f = va_arg(args, double);
+
+                if (f < 0) {
+                    *buf++ = '-';
+                    f = -f;
+                }
+
+                ui64 = (int64_t) f;
+                frac = 0;
+
+                if (frac_width) {
+
+                    scale = 1;
+                    for (n = frac_width; n; n--) {
+                        scale *= 10;
+                    }
+
+                    frac = (uint64_t) ((f - (double) ui64) * scale + 0.5);
+
+                    if (frac == scale) {
+                        ui64++;
+                        frac = 0;
+                    }
+                }
+
+                buf = ngx_sprintf_num(buf, last, ui64, zero, 0, width);
+
+                if (frac_width) {
+                    if (buf < last) {
+                        *buf++ = '.';
+                    }
+
+                    buf = ngx_sprintf_num(buf, last, frac, '0', 0, frac_width);
+                }
+
+                fmt++;
+
+                continue;
+
+#if !(NGX_WIN32)
+            case 'r':
+                i64 = (int64_t) va_arg(args, rlim_t);
+                sign = 1;
+                break;
+#endif
+
+            case 'p':
+                ui64 = (uintptr_t) va_arg(args, void *);
+                hex = 2;
+                sign = 0;
+                zero = '0';
+                width = 2 * sizeof(void *);
+                break;
+
+            case 'c':
+                d = va_arg(args, int);
+                *buf++ = (u_char) (d & 0xff);
+                fmt++;
+
+                continue;
+
+            case 'Z':
+                *buf++ = '\0';
+                fmt++;
+
+                continue;
+
+            case 'N':
+#if (NGX_WIN32)
+                *buf++ = CR;
+                if (buf < last) {
+                    *buf++ = LF;
+                }
+#else
+                *buf++ = LF;
+#endif
+                fmt++;
+
+                continue;
+
+            case '%':
+                *buf++ = '%';
+                fmt++;
+
+                continue;
+
+            default:
+                *buf++ = *fmt++;
+
+                continue;
+            }
+
+            if (sign) {
+                if (i64 < 0) {
+                    *buf++ = '-';
+                    ui64 = (uint64_t) -i64;
+
+                } else {
+                    ui64 = (uint64_t) i64;
+                }
+            }
+
+            buf = ngx_sprintf_num(buf, last, ui64, zero, hex, width);
+
+            fmt++;
+
+        } else {
+            *buf++ = *fmt++;
+        }
+    }
+
+    return buf;
+}
+
+
+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)
+{
+    u_char         *p, temp[NGX_INT64_LEN + 1];
+                       /*
+                        * we need temp[NGX_INT64_LEN] only,
+                        * but icc issues the warning
+                        */
+    size_t          len;
+    uint32_t        ui32;
+    static u_char   hex[] = "0123456789abcdef";
+    static u_char   HEX[] = "0123456789ABCDEF";
+
+    p = temp + NGX_INT64_LEN;
+
+    if (hexadecimal == 0) {
+
+        if (ui64 <= (uint64_t) NGX_MAX_UINT32_VALUE) {
+
+            /*
+             * To divide 64-bit numbers and to find remainders
+             * on the x86 platform gcc and icc call the libc functions
+             * [u]divdi3() and [u]moddi3(), they call another function
+             * in its turn.  On FreeBSD it is the qdivrem() function,
+             * its source code is about 170 lines of the code.
+             * The glibc counterpart is about 150 lines of the code.
+             *
+             * For 32-bit numbers and some divisors gcc and icc use
+             * a inlined multiplication and shifts.  For example,
+             * unsigned "i32 / 10" is compiled to
+             *
+             *     (i32 * 0xCCCCCCCD) >> 35
+             */
+
+            ui32 = (uint32_t) ui64;
+
+            do {
+                *--p = (u_char) (ui32 % 10 + '0');
+            } while (ui32 /= 10);
+
+        } else {
+            do {
+                *--p = (u_char) (ui64 % 10 + '0');
+            } while (ui64 /= 10);
+        }
+
+    } else if (hexadecimal == 1) {
+
+        do {
+
+            /* the "(uint32_t)" cast disables the BCC's warning */
+            *--p = hex[(uint32_t) (ui64 & 0xf)];
+
+        } while (ui64 >>= 4);
+
+    } else { /* hexadecimal == 2 */
+
+        do {
+
+            /* the "(uint32_t)" cast disables the BCC's warning */
+            *--p = HEX[(uint32_t) (ui64 & 0xf)];
+
+        } while (ui64 >>= 4);
+    }
+
+    /* zero or space padding */
+
+    len = (temp + NGX_INT64_LEN) - p;
+
+    while (len++ < width && buf < last) {
+        *buf++ = zero;
+    }
+
+    /* number safe copy */
+
+    len = (temp + NGX_INT64_LEN) - p;
+
+    if (buf + len > last) {
+        len = last - buf;
+    }
+
+    return ngx_cpymem(buf, p, len);
+}
+
+
+/*
+ * We use ngx_strcasecmp()/ngx_strncasecmp() for 7-bit ASCII strings only,
+ * and implement our own ngx_strcasecmp()/ngx_strncasecmp()
+ * to avoid libc locale overhead.  Besides, we use the ngx_uint_t's
+ * instead of the u_char's, because they are slightly faster.
+ */
+
+ngx_int_t
+ngx_strcasecmp(u_char *s1, u_char *s2)
+{
+    ngx_uint_t  c1, c2;
+
+    for ( ;; ) {
+        c1 = (ngx_uint_t) *s1++;
+        c2 = (ngx_uint_t) *s2++;
+
+        c1 = (c1 >= 'A' && c1 <= 'Z') ? (c1 | 0x20) : c1;
+        c2 = (c2 >= 'A' && c2 <= 'Z') ? (c2 | 0x20) : c2;
+
+        if (c1 == c2) {
+
+            if (c1) {
+                continue;
+            }
+
+            return 0;
+        }
+
+        return c1 - c2;
+    }
+}
+
+
+ngx_int_t
+ngx_strncasecmp(u_char *s1, u_char *s2, size_t n)
+{
+    ngx_uint_t  c1, c2;
+
+    while (n) {
+        c1 = (ngx_uint_t) *s1++;
+        c2 = (ngx_uint_t) *s2++;
+
+        c1 = (c1 >= 'A' && c1 <= 'Z') ? (c1 | 0x20) : c1;
+        c2 = (c2 >= 'A' && c2 <= 'Z') ? (c2 | 0x20) : c2;
+
+        if (c1 == c2) {
+
+            if (c1) {
+                n--;
+                continue;
+            }
+
+            return 0;
+        }
+
+        return c1 - c2;
+    }
+
+    return 0;
+}
+
+
+u_char *
+ngx_strnstr(u_char *s1, char *s2, size_t len)
+{
+    u_char  c1, c2;
+    size_t  n;
+
+    c2 = *(u_char *) s2++;
+
+    n = ngx_strlen(s2);
+
+    do {
+        do {
+            if (len-- == 0) {
+                return NULL;
+            }
+
+            c1 = *s1++;
+
+            if (c1 == 0) {
+                return NULL;
+            }
+
+        } while (c1 != c2);
+
+        if (n > len) {
+            return NULL;
+        }
+
+    } while (ngx_strncmp(s1, (u_char *) s2, n) != 0);
+
+    return --s1;
+}
+
+
+/*
+ * ngx_strstrn() and ngx_strcasestrn() are intended to search for static
+ * substring with known length in null-terminated string. The argument n
+ * must be length of the second substring - 1.
+ */
+
+u_char *
+ngx_strstrn(u_char *s1, char *s2, size_t n)
+{
+    u_char  c1, c2;
+
+    c2 = *(u_char *) s2++;
+
+    do {
+        do {
+            c1 = *s1++;
+
+            if (c1 == 0) {
+                return NULL;
+            }
+
+        } while (c1 != c2);
+
+    } while (ngx_strncmp(s1, (u_char *) s2, n) != 0);
+
+    return --s1;
+}
+
+
+u_char *
+ngx_strcasestrn(u_char *s1, char *s2, size_t n)
+{
+    ngx_uint_t  c1, c2;
+
+    c2 = (ngx_uint_t) *s2++;
+    c2 = (c2 >= 'A' && c2 <= 'Z') ? (c2 | 0x20) : c2;
+
+    do {
+        do {
+            c1 = (ngx_uint_t) *s1++;
+
+            if (c1 == 0) {
+                return NULL;
+            }
+
+            c1 = (c1 >= 'A' && c1 <= 'Z') ? (c1 | 0x20) : c1;
+
+        } while (c1 != c2);
+
+    } while (ngx_strncasecmp(s1, (u_char *) s2, n) != 0);
+
+    return --s1;
+}
+
+
+/*
+ * ngx_strlcasestrn() is intended to search for static substring
+ * with known length in string until the argument last. The argument n
+ * must be length of the second substring - 1.
+ */
+
+u_char *
+ngx_strlcasestrn(u_char *s1, u_char *last, u_char *s2, size_t n)
+{
+    ngx_uint_t  c1, c2;
+
+    c2 = (ngx_uint_t) *s2++;
+    c2 = (c2 >= 'A' && c2 <= 'Z') ? (c2 | 0x20) : c2;
+    last -= n;
+
+    do {
+        do {
+            if (s1 >= last) {
+                return NULL;
+            }
+
+            c1 = (ngx_uint_t) *s1++;
+
+            c1 = (c1 >= 'A' && c1 <= 'Z') ? (c1 | 0x20) : c1;
+
+        } while (c1 != c2);
+
+    } while (ngx_strncasecmp(s1, s2, n) != 0);
+
+    return --s1;
+}
+
+
+ngx_int_t
+ngx_rstrncmp(u_char *s1, u_char *s2, size_t n)
+{
+    if (n == 0) {
+        return 0;
+    }
+
+    n--;
+
+    for ( ;; ) {
+        if (s1[n] != s2[n]) {
+            return s1[n] - s2[n];
+        }
+
+        if (n == 0) {
+            return 0;
+        }
+
+        n--;
+    }
+}
+
+
+ngx_int_t
+ngx_rstrncasecmp(u_char *s1, u_char *s2, size_t n)
+{
+    u_char  c1, c2;
+
+    if (n == 0) {
+        return 0;
+    }
+
+    n--;
+
+    for ( ;; ) {
+        c1 = s1[n];
+        if (c1 >= 'a' && c1 <= 'z') {
+            c1 -= 'a' - 'A';
+        }
+
+        c2 = s2[n];
+        if (c2 >= 'a' && c2 <= 'z') {
+            c2 -= 'a' - 'A';
+        }
+
+        if (c1 != c2) {
+            return c1 - c2;
+        }
+
+        if (n == 0) {
+            return 0;
+        }
+
+        n--;
+    }
+}
+
+
+ngx_int_t
+ngx_memn2cmp(u_char *s1, u_char *s2, size_t n1, size_t n2)
+{
+    size_t     n;
+    ngx_int_t  m, z;
+
+    if (n1 <= n2) {
+        n = n1;
+        z = -1;
+
+    } else {
+        n = n2;
+        z = 1;
+    }
+
+    m = ngx_memcmp(s1, s2, n);
+
+    if (m || n1 == n2) {
+        return m;
+    }
+
+    return z;
+}
+
+
+ngx_int_t
+ngx_dns_strcmp(u_char *s1, u_char *s2)
+{
+    ngx_uint_t  c1, c2;
+
+    for ( ;; ) {
+        c1 = (ngx_uint_t) *s1++;
+        c2 = (ngx_uint_t) *s2++;
+
+        c1 = (c1 >= 'A' && c1 <= 'Z') ? (c1 | 0x20) : c1;
+        c2 = (c2 >= 'A' && c2 <= 'Z') ? (c2 | 0x20) : c2;
+
+        if (c1 == c2) {
+
+            if (c1) {
+                continue;
+            }
+
+            return 0;
+        }
+
+        /* in ASCII '.' > '-', but we need '.' to be the lowest character */
+
+        c1 = (c1 == '.') ? ' ' : c1;
+        c2 = (c2 == '.') ? ' ' : c2;
+
+        return c1 - c2;
+    }
+}
+
+
+ngx_int_t
+ngx_filename_cmp(u_char *s1, u_char *s2, size_t n)
+{
+    ngx_uint_t  c1, c2;
+
+    while (n) {
+        c1 = (ngx_uint_t) *s1++;
+        c2 = (ngx_uint_t) *s2++;
+
+#if (NGX_HAVE_CASELESS_FILESYSTEM)
+        c1 = tolower(c1);
+        c2 = tolower(c2);
+#endif
+
+        if (c1 == c2) {
+
+            if (c1) {
+                n--;
+                continue;
+            }
+
+            return 0;
+        }
+
+        /* we need '/' to be the lowest character */
+
+        if (c1 == 0 || c2 == 0) {
+            return c1 - c2;
+        }
+
+        c1 = (c1 == '/') ? 0 : c1;
+        c2 = (c2 == '/') ? 0 : c2;
+
+        return c1 - c2;
+    }
+
+    return 0;
+}
+
+
+ngx_int_t
+ngx_atoi(u_char *line, size_t n)
+{
+    ngx_int_t  value, cutoff, cutlim;
+
+    if (n == 0) {
+        return NGX_ERROR;
+    }
+
+    cutoff = NGX_MAX_INT_T_VALUE / 10;
+    cutlim = NGX_MAX_INT_T_VALUE % 10;
+
+    for (value = 0; n--; line++) {
+        if (*line < '0' || *line > '9') {
+            return NGX_ERROR;
+        }
+
+        if (value >= cutoff && (value > cutoff || *line - '0' > cutlim)) {
+            return NGX_ERROR;
+        }
+
+        value = value * 10 + (*line - '0');
+    }
+
+    return value;
+}
+
+
+/* parse a fixed point number, e.g., ngx_atofp("10.5", 4, 2) returns 1050 */
+
+ngx_int_t
+ngx_atofp(u_char *line, size_t n, size_t point)
+{
+    ngx_int_t   value, cutoff, cutlim;
+    ngx_uint_t  dot;
+
+    if (n == 0) {
+        return NGX_ERROR;
+    }
+
+    cutoff = NGX_MAX_INT_T_VALUE / 10;
+    cutlim = NGX_MAX_INT_T_VALUE % 10;
+
+    dot = 0;
+
+    for (value = 0; n--; line++) {
+
+        if (point == 0) {
+            return NGX_ERROR;
+        }
+
+        if (*line == '.') {
+            if (dot) {
+                return NGX_ERROR;
+            }
+
+            dot = 1;
+            continue;
+        }
+
+        if (*line < '0' || *line > '9') {
+            return NGX_ERROR;
+        }
+
+        if (value >= cutoff && (value > cutoff || *line - '0' > cutlim)) {
+            return NGX_ERROR;
+        }
+
+        value = value * 10 + (*line - '0');
+        point -= dot;
+    }
+
+    while (point--) {
+        if (value > cutoff) {
+            return NGX_ERROR;
+        }
+
+        value = value * 10;
+    }
+
+    return value;
+}
+
+
+ssize_t
+ngx_atosz(u_char *line, size_t n)
+{
+    ssize_t  value, cutoff, cutlim;
+
+    if (n == 0) {
+        return NGX_ERROR;
+    }
+
+    cutoff = NGX_MAX_SIZE_T_VALUE / 10;
+    cutlim = NGX_MAX_SIZE_T_VALUE % 10;
+
+    for (value = 0; n--; line++) {
+        if (*line < '0' || *line > '9') {
+            return NGX_ERROR;
+        }
+
+        if (value >= cutoff && (value > cutoff || *line - '0' > cutlim)) {
+            return NGX_ERROR;
+        }
+
+        value = value * 10 + (*line - '0');
+    }
+
+    return value;
+}
+
+
+off_t
+ngx_atoof(u_char *line, size_t n)
+{
+    off_t  value, cutoff, cutlim;
+
+    if (n == 0) {
+        return NGX_ERROR;
+    }
+
+    cutoff = NGX_MAX_OFF_T_VALUE / 10;
+    cutlim = NGX_MAX_OFF_T_VALUE % 10;
+
+    for (value = 0; n--; line++) {
+        if (*line < '0' || *line > '9') {
+            return NGX_ERROR;
+        }
+
+        if (value >= cutoff && (value > cutoff || *line - '0' > cutlim)) {
+            return NGX_ERROR;
+        }
+
+        value = value * 10 + (*line - '0');
+    }
+
+    return value;
+}
+
+
+time_t
+ngx_atotm(u_char *line, size_t n)
+{
+    time_t  value, cutoff, cutlim;
+
+    if (n == 0) {
+        return NGX_ERROR;
+    }
+
+    cutoff = NGX_MAX_TIME_T_VALUE / 10;
+    cutlim = NGX_MAX_TIME_T_VALUE % 10;
+
+    for (value = 0; n--; line++) {
+        if (*line < '0' || *line > '9') {
+            return NGX_ERROR;
+        }
+
+        if (value >= cutoff && (value > cutoff || *line - '0' > cutlim)) {
+            return NGX_ERROR;
+        }
+
+        value = value * 10 + (*line - '0');
+    }
+
+    return value;
+}
+
+
+ngx_int_t
+ngx_hextoi(u_char *line, size_t n)
+{
+    u_char     c, ch;
+    ngx_int_t  value, cutoff;
+
+    if (n == 0) {
+        return NGX_ERROR;
+    }
+
+    cutoff = NGX_MAX_INT_T_VALUE / 16;
+
+    for (value = 0; n--; line++) {
+        if (value > cutoff) {
+            return NGX_ERROR;
+        }
+
+        ch = *line;
+
+        if (ch >= '0' && ch <= '9') {
+            value = value * 16 + (ch - '0');
+            continue;
+        }
+
+        c = (u_char) (ch | 0x20);
+
+        if (c >= 'a' && c <= 'f') {
+            value = value * 16 + (c - 'a' + 10);
+            continue;
+        }
+
+        return NGX_ERROR;
+    }
+
+    return value;
+}
+
+
+u_char *
+ngx_hex_dump(u_char *dst, u_char *src, size_t len)
+{
+    static u_char  hex[] = "0123456789abcdef";
+
+    while (len--) {
+        *dst++ = hex[*src >> 4];
+        *dst++ = hex[*src++ & 0xf];
+    }
+
+    return dst;
+}
+
+
+void
+ngx_encode_base64(ngx_str_t *dst, ngx_str_t *src)
+{
+    static u_char   basis64[] =
+            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+    ngx_encode_base64_internal(dst, src, basis64, 1);
+}
+
+
+void
+ngx_encode_base64url(ngx_str_t *dst, ngx_str_t *src)
+{
+    static u_char   basis64[] =
+            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+
+    ngx_encode_base64_internal(dst, src, basis64, 0);
+}
+
+
+static void
+ngx_encode_base64_internal(ngx_str_t *dst, ngx_str_t *src, const u_char *basis,
+    ngx_uint_t padding)
+{
+    u_char         *d, *s;
+    size_t          len;
+
+    len = src->len;
+    s = src->data;
+    d = dst->data;
+
+    while (len > 2) {
+        *d++ = basis[(s[0] >> 2) & 0x3f];
+        *d++ = basis[((s[0] & 3) << 4) | (s[1] >> 4)];
+        *d++ = basis[((s[1] & 0x0f) << 2) | (s[2] >> 6)];
+        *d++ = basis[s[2] & 0x3f];
+
+        s += 3;
+        len -= 3;
+    }
+
+    if (len) {
+        *d++ = basis[(s[0] >> 2) & 0x3f];
+
+        if (len == 1) {
+            *d++ = basis[(s[0] & 3) << 4];
+            if (padding) {
+                *d++ = '=';
+            }
+
+        } else {
+            *d++ = basis[((s[0] & 3) << 4) | (s[1] >> 4)];
+            *d++ = basis[(s[1] & 0x0f) << 2];
+        }
+
+        if (padding) {
+            *d++ = '=';
+        }
+    }
+
+    dst->len = d - dst->data;
+}
+
+
+ngx_int_t
+ngx_decode_base64(ngx_str_t *dst, ngx_str_t *src)
+{
+    static u_char   basis64[] = {
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 62, 77, 77, 77, 63,
+        52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 77, 77, 77, 77, 77, 77,
+        77,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+        15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 77, 77, 77, 77, 77,
+        77, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+        41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 77, 77, 77, 77, 77,
+
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77
+    };
+
+    return ngx_decode_base64_internal(dst, src, basis64);
+}
+
+
+ngx_int_t
+ngx_decode_base64url(ngx_str_t *dst, ngx_str_t *src)
+{
+    static u_char   basis64[] = {
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 62, 77, 77,
+        52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 77, 77, 77, 77, 77, 77,
+        77,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+        15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 77, 77, 77, 77, 63,
+        77, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+        41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 77, 77, 77, 77, 77,
+
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77
+    };
+
+    return ngx_decode_base64_internal(dst, src, basis64);
+}
+
+
+static ngx_int_t
+ngx_decode_base64_internal(ngx_str_t *dst, ngx_str_t *src, const u_char *basis)
+{
+    size_t          len;
+    u_char         *d, *s;
+
+    for (len = 0; len < src->len; len++) {
+        if (src->data[len] == '=') {
+            break;
+        }
+
+        if (basis[src->data[len]] == 77) {
+            return NGX_ERROR;
+        }
+    }
+
+    if (len % 4 == 1) {
+        return NGX_ERROR;
+    }
+
+    s = src->data;
+    d = dst->data;
+
+    while (len > 3) {
+        *d++ = (u_char) (basis[s[0]] << 2 | basis[s[1]] >> 4);
+        *d++ = (u_char) (basis[s[1]] << 4 | basis[s[2]] >> 2);
+        *d++ = (u_char) (basis[s[2]] << 6 | basis[s[3]]);
+
+        s += 4;
+        len -= 4;
+    }
+
+    if (len > 1) {
+        *d++ = (u_char) (basis[s[0]] << 2 | basis[s[1]] >> 4);
+    }
+
+    if (len > 2) {
+        *d++ = (u_char) (basis[s[1]] << 4 | basis[s[2]] >> 2);
+    }
+
+    dst->len = d - dst->data;
+
+    return NGX_OK;
+}
+
+
+/*
+ * ngx_utf8_decode() decodes two and more bytes UTF sequences only
+ * the return values:
+ *    0x80 - 0x10ffff         valid character
+ *    0x110000 - 0xfffffffd   invalid sequence
+ *    0xfffffffe              incomplete sequence
+ *    0xffffffff              error
+ */
+
+uint32_t
+ngx_utf8_decode(u_char **p, size_t n)
+{
+    size_t    len;
+    uint32_t  u, i, valid;
+
+    u = **p;
+
+    if (u >= 0xf0) {
+
+        u &= 0x07;
+        valid = 0xffff;
+        len = 3;
+
+    } else if (u >= 0xe0) {
+
+        u &= 0x0f;
+        valid = 0x7ff;
+        len = 2;
+
+    } else if (u >= 0xc2) {
+
+        u &= 0x1f;
+        valid = 0x7f;
+        len = 1;
+
+    } else {
+        (*p)++;
+        return 0xffffffff;
+    }
+
+    if (n - 1 < len) {
+        return 0xfffffffe;
+    }
+
+    (*p)++;
+
+    while (len) {
+        i = *(*p)++;
+
+        if (i < 0x80) {
+            return 0xffffffff;
+        }
+
+        u = (u << 6) | (i & 0x3f);
+
+        len--;
+    }
+
+    if (u > valid) {
+        return u;
+    }
+
+    return 0xffffffff;
+}
+
+
+size_t
+ngx_utf8_length(u_char *p, size_t n)
+{
+    u_char  c, *last;
+    size_t  len;
+
+    last = p + n;
+
+    for (len = 0; p < last; len++) {
+
+        c = *p;
+
+        if (c < 0x80) {
+            p++;
+            continue;
+        }
+
+        if (ngx_utf8_decode(&p, n) > 0x10ffff) {
+            /* invalid UTF-8 */
+            return n;
+        }
+    }
+
+    return len;
+}
+
+
+u_char *
+ngx_utf8_cpystrn(u_char *dst, u_char *src, size_t n, size_t len)
+{
+    u_char  c, *next;
+
+    if (n == 0) {
+        return dst;
+    }
+
+    while (--n) {
+
+        c = *src;
+        *dst = c;
+
+        if (c < 0x80) {
+
+            if (c != '\0') {
+                dst++;
+                src++;
+                len--;
+
+                continue;
+            }
+
+            return dst;
+        }
+
+        next = src;
+
+        if (ngx_utf8_decode(&next, len) > 0x10ffff) {
+            /* invalid UTF-8 */
+            break;
+        }
+
+        while (src < next) {
+            *dst++ = *src++;
+            len--;
+        }
+    }
+
+    *dst = '\0';
+
+    return dst;
+}
+
+
+uintptr_t
+ngx_escape_uri(u_char *dst, u_char *src, size_t size, ngx_uint_t type)
+{
+    ngx_uint_t      n;
+    uint32_t       *escape;
+    static u_char   hex[] = "0123456789ABCDEF";
+
+                    /* " ", "#", "%", "?", %00-%1F, %7F-%FF */
+
+    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 */
+
+                    /* _^]\ [ZYX WVUT SRQP  ONML KJIH GFED CBA@ */
+        0x00000000, /* 0000 0000 0000 0000  0000 0000 0000 0000 */
+
+                    /*  ~}| {zyx wvut srqp  onml kjih gfed cba` */
+        0x80000000, /* 1000 0000 0000 0000  0000 0000 0000 0000 */
+
+        0xffffffff, /* 1111 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 */
+        0xffffffff  /* 1111 1111 1111 1111  1111 1111 1111 1111 */
+    };
+
+                    /* " ", "#", "%", "&", "+", "?", %00-%1F, %7F-%FF */
+
+    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 */
+
+                    /* _^]\ [ZYX WVUT SRQP  ONML KJIH GFED CBA@ */
+        0x00000000, /* 0000 0000 0000 0000  0000 0000 0000 0000 */
+
+                    /*  ~}| {zyx wvut srqp  onml kjih gfed cba` */
+        0x80000000, /* 1000 0000 0000 0000  0000 0000 0000 0000 */
+
+        0xffffffff, /* 1111 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 */
+        0xffffffff  /* 1111 1111 1111 1111  1111 1111 1111 1111 */
+    };
+
+                    /* not ALPHA, DIGIT, "-", ".", "_", "~" */
+
+    static uint32_t   uri_component[] = {
+        0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
+
+                    /* ?>=< ;:98 7654 3210  /.-, +*)( '&%$ #"!  */
+        0xfc009fff, /* 1111 1100 0000 0000  1001 1111 1111 1111 */
+
+                    /* _^]\ [ZYX WVUT SRQP  ONML KJIH GFED CBA@ */
+        0x78000001, /* 0111 1000 0000 0000  0000 0000 0000 0001 */
+
+                    /*  ~}| {zyx wvut srqp  onml kjih gfed cba` */
+        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 */
+        0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
+        0xffffffff  /* 1111 1111 1111 1111  1111 1111 1111 1111 */
+    };
+
+                    /* " ", "#", """, "%", "'", %00-%1F, %7F-%FF */
+
+    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 */
+
+                    /* _^]\ [ZYX WVUT SRQP  ONML KJIH GFED CBA@ */
+        0x00000000, /* 0000 0000 0000 0000  0000 0000 0000 0000 */
+
+                    /*  ~}| {zyx wvut srqp  onml kjih gfed cba` */
+        0x80000000, /* 1000 0000 0000 0000  0000 0000 0000 0000 */
+
+        0xffffffff, /* 1111 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 */
+        0xffffffff  /* 1111 1111 1111 1111  1111 1111 1111 1111 */
+    };
+
+                    /* " ", """, "%", "'", %00-%1F, %7F-%FF */
+
+    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 */
+
+                    /* _^]\ [ZYX WVUT SRQP  ONML KJIH GFED CBA@ */
+        0x00000000, /* 0000 0000 0000 0000  0000 0000 0000 0000 */
+
+                    /*  ~}| {zyx wvut srqp  onml kjih gfed cba` */
+        0x80000000, /* 1000 0000 0000 0000  0000 0000 0000 0000 */
+
+        0xffffffff, /* 1111 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 */
+        0xffffffff  /* 1111 1111 1111 1111  1111 1111 1111 1111 */
+    };
+
+                    /* " ", "%", %00-%1F */
+
+    static uint32_t   memcached[] = {
+        0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
+
+                    /* ?>=< ;:98 7654 3210  /.-, +*)( '&%$ #"!  */
+        0x00000021, /* 0000 0000 0000 0000  0000 0000 0010 0001 */
+
+                    /* _^]\ [ZYX WVUT SRQP  ONML KJIH GFED CBA@ */
+        0x00000000, /* 0000 0000 0000 0000  0000 0000 0000 0000 */
+
+                    /*  ~}| {zyx wvut srqp  onml kjih gfed cba` */
+        0x00000000, /* 0000 0000 0000 0000  0000 0000 0000 0000 */
+
+        0x00000000, /* 0000 0000 0000 0000  0000 0000 0000 0000 */
+        0x00000000, /* 0000 0000 0000 0000  0000 0000 0000 0000 */
+        0x00000000, /* 0000 0000 0000 0000  0000 0000 0000 0000 */
+        0x00000000, /* 0000 0000 0000 0000  0000 0000 0000 0000 */
+    };
+
+                    /* mail_auth is the same as memcached */
+
+    static uint32_t  *map[] =
+        { uri, args, uri_component, html, refresh, memcached, memcached };
+
+
+    escape = map[type];
+
+    if (dst == NULL) {
+
+        /* find the number of the characters to be escaped */
+
+        n = 0;
+
+        while (size) {
+            if (escape[*src >> 5] & (1U << (*src & 0x1f))) {
+                n++;
+            }
+            src++;
+            size--;
+        }
+
+        return (uintptr_t) n;
+    }
+
+    while (size) {
+        if (escape[*src >> 5] & (1U << (*src & 0x1f))) {
+            *dst++ = '%';
+            *dst++ = hex[*src >> 4];
+            *dst++ = hex[*src & 0xf];
+            src++;
+
+        } else {
+            *dst++ = *src++;
+        }
+        size--;
+    }
+
+    return (uintptr_t) dst;
+}
+
+
+void
+ngx_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type)
+{
+    u_char  *d, *s, ch, c, decoded;
+    enum {
+        sw_usual = 0,
+        sw_quoted,
+        sw_quoted_second
+    } state;
+
+    d = *dst;
+    s = *src;
+
+    state = 0;
+    decoded = 0;
+
+    while (size--) {
+
+        ch = *s++;
+
+        switch (state) {
+        case sw_usual:
+            if (ch == '?'
+                && (type & (NGX_UNESCAPE_URI|NGX_UNESCAPE_REDIRECT)))
+            {
+                *d++ = ch;
+                goto done;
+            }
+
+            if (ch == '%') {
+                state = sw_quoted;
+                break;
+            }
+
+            *d++ = ch;
+            break;
+
+        case sw_quoted:
+
+            if (ch >= '0' && ch <= '9') {
+                decoded = (u_char) (ch - '0');
+                state = sw_quoted_second;
+                break;
+            }
+
+            c = (u_char) (ch | 0x20);
+            if (c >= 'a' && c <= 'f') {
+                decoded = (u_char) (c - 'a' + 10);
+                state = sw_quoted_second;
+                break;
+            }
+
+            /* the invalid quoted character */
+
+            state = sw_usual;
+
+            *d++ = ch;
+
+            break;
+
+        case sw_quoted_second:
+
+            state = sw_usual;
+
+            if (ch >= '0' && ch <= '9') {
+                ch = (u_char) ((decoded << 4) + (ch - '0'));
+
+                if (type & NGX_UNESCAPE_REDIRECT) {
+                    if (ch > '%' && ch < 0x7f) {
+                        *d++ = ch;
+                        break;
+                    }
+
+                    *d++ = '%'; *d++ = *(s - 2); *d++ = *(s - 1);
+
+                    break;
+                }
+
+                *d++ = ch;
+
+                break;
+            }
+
+            c = (u_char) (ch | 0x20);
+            if (c >= 'a' && c <= 'f') {
+                ch = (u_char) ((decoded << 4) + (c - 'a') + 10);
+
+                if (type & NGX_UNESCAPE_URI) {
+                    if (ch == '?') {
+                        *d++ = ch;
+                        goto done;
+                    }
+
+                    *d++ = ch;
+                    break;
+                }
+
+                if (type & NGX_UNESCAPE_REDIRECT) {
+                    if (ch == '?') {
+                        *d++ = ch;
+                        goto done;
+                    }
+
+                    if (ch > '%' && ch < 0x7f) {
+                        *d++ = ch;
+                        break;
+                    }
+
+                    *d++ = '%'; *d++ = *(s - 2); *d++ = *(s - 1);
+                    break;
+                }
+
+                *d++ = ch;
+
+                break;
+            }
+
+            /* the invalid quoted character */
+
+            break;
+        }
+    }
+
+done:
+
+    *dst = d;
+    *src = s;
+}
+
+
+uintptr_t
+ngx_escape_html(u_char *dst, u_char *src, size_t size)
+{
+    u_char      ch;
+    ngx_uint_t  len;
+
+    if (dst == NULL) {
+
+        len = 0;
+
+        while (size) {
+            switch (*src++) {
+
+            case '<':
+                len += sizeof("&lt;") - 2;
+                break;
+
+            case '>':
+                len += sizeof("&gt;") - 2;
+                break;
+
+            case '&':
+                len += sizeof("&amp;") - 2;
+                break;
+
+            case '"':
+                len += sizeof("&quot;") - 2;
+                break;
+
+            default:
+                break;
+            }
+            size--;
+        }
+
+        return (uintptr_t) len;
+    }
+
+    while (size) {
+        ch = *src++;
+
+        switch (ch) {
+
+        case '<':
+            *dst++ = '&'; *dst++ = 'l'; *dst++ = 't'; *dst++ = ';';
+            break;
+
+        case '>':
+            *dst++ = '&'; *dst++ = 'g'; *dst++ = 't'; *dst++ = ';';
+            break;
+
+        case '&':
+            *dst++ = '&'; *dst++ = 'a'; *dst++ = 'm'; *dst++ = 'p';
+            *dst++ = ';';
+            break;
+
+        case '"':
+            *dst++ = '&'; *dst++ = 'q'; *dst++ = 'u'; *dst++ = 'o';
+            *dst++ = 't'; *dst++ = ';';
+            break;
+
+        default:
+            *dst++ = ch;
+            break;
+        }
+        size--;
+    }
+
+    return (uintptr_t) dst;
+}
+
+
+uintptr_t
+ngx_escape_json(u_char *dst, u_char *src, size_t size)
+{
+    u_char      ch;
+    ngx_uint_t  len;
+
+    if (dst == NULL) {
+        len = 0;
+
+        while (size) {
+            ch = *src++;
+
+            if (ch == '\\' || ch == '"') {
+                len++;
+
+            } else if (ch <= 0x1f) {
+
+                switch (ch) {
+                case '\n':
+                case '\r':
+                case '\t':
+                case '\b':
+                case '\f':
+                    len++;
+                    break;
+
+                default:
+                    len += sizeof("\\u001F") - 2;
+                }
+            }
+
+            size--;
+        }
+
+        return (uintptr_t) len;
+    }
+
+    while (size) {
+        ch = *src++;
+
+        if (ch > 0x1f) {
+
+            if (ch == '\\' || ch == '"') {
+                *dst++ = '\\';
+            }
+
+            *dst++ = ch;
+
+        } else {
+            *dst++ = '\\';
+
+            switch (ch) {
+            case '\n':
+                *dst++ = 'n';
+                break;
+
+            case '\r':
+                *dst++ = 'r';
+                break;
+
+            case '\t':
+                *dst++ = 't';
+                break;
+
+            case '\b':
+                *dst++ = 'b';
+                break;
+
+            case '\f':
+                *dst++ = 'f';
+                break;
+
+            default:
+                *dst++ = 'u'; *dst++ = '0'; *dst++ = '0';
+                *dst++ = '0' + (ch >> 4);
+
+                ch &= 0xf;
+
+                *dst++ = (ch < 10) ? ('0' + ch) : ('A' + ch - 10);
+            }
+        }
+
+        size--;
+    }
+
+    return (uintptr_t) dst;
+}
+
+
+void
+ngx_str_rbtree_insert_value(ngx_rbtree_node_t *temp,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
+{
+    ngx_str_node_t      *n, *t;
+    ngx_rbtree_node_t  **p;
+
+    for ( ;; ) {
+
+        n = (ngx_str_node_t *) node;
+        t = (ngx_str_node_t *) temp;
+
+        if (node->key != temp->key) {
+
+            p = (node->key < temp->key) ? &temp->left : &temp->right;
+
+        } else if (n->str.len != t->str.len) {
+
+            p = (n->str.len < t->str.len) ? &temp->left : &temp->right;
+
+        } else {
+            p = (ngx_memcmp(n->str.data, t->str.data, n->str.len) < 0)
+                 ? &temp->left : &temp->right;
+        }
+
+        if (*p == sentinel) {
+            break;
+        }
+
+        temp = *p;
+    }
+
+    *p = node;
+    node->parent = temp;
+    node->left = sentinel;
+    node->right = sentinel;
+    ngx_rbt_red(node);
+}
+
+
+ngx_str_node_t *
+ngx_str_rbtree_lookup(ngx_rbtree_t *rbtree, ngx_str_t *val, uint32_t hash)
+{
+    ngx_int_t           rc;
+    ngx_str_node_t     *n;
+    ngx_rbtree_node_t  *node, *sentinel;
+
+    node = rbtree->root;
+    sentinel = rbtree->sentinel;
+
+    while (node != sentinel) {
+
+        n = (ngx_str_node_t *) node;
+
+        if (hash != node->key) {
+            node = (hash < node->key) ? node->left : node->right;
+            continue;
+        }
+
+        if (val->len != n->str.len) {
+            node = (val->len < n->str.len) ? node->left : node->right;
+            continue;
+        }
+
+        rc = ngx_memcmp(val->data, n->str.data, val->len);
+
+        if (rc < 0) {
+            node = node->left;
+            continue;
+        }
+
+        if (rc > 0) {
+            node = node->right;
+            continue;
+        }
+
+        return n;
+    }
+
+    return NULL;
+}
+
+
+/* ngx_sort() is implemented as insertion sort because we need stable sort */
+
+void
+ngx_sort(void *base, size_t n, size_t size,
+    ngx_int_t (*cmp)(const void *, const void *))
+{
+    u_char  *p1, *p2, *p;
+
+    p = ngx_alloc(size, ngx_cycle->log);
+    if (p == NULL) {
+        return;
+    }
+
+    for (p1 = (u_char *) base + size;
+         p1 < (u_char *) base + n * size;
+         p1 += size)
+    {
+        ngx_memcpy(p, p1, size);
+
+        for (p2 = p1;
+             p2 > (u_char *) base && cmp(p2 - size, p) > 0;
+             p2 -= size)
+        {
+            ngx_memcpy(p2, p2 - size, size);
+        }
+
+        ngx_memcpy(p2, p, size);
+    }
+
+    ngx_free(p);
+}
+
+
+#if (NGX_MEMCPY_LIMIT)
+
+void *
+ngx_memcpy(void *dst, const void *src, size_t n)
+{
+    if (n > NGX_MEMCPY_LIMIT) {
+        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "memcpy %uz bytes", n);
+        ngx_debug_point();
+    }
+
+    return memcpy(dst, src, n);
+}
+
+#endif
diff --git a/nginx/src/core/ngx_string.h b/nginx/src/core/ngx_string.h
new file mode 100644 (file)
index 0000000..882ae7c
--- /dev/null
@@ -0,0 +1,236 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_STRING_H_INCLUDED_
+#define _NGX_STRING_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef struct {
+    size_t      len;
+    u_char     *data;
+} ngx_str_t;
+
+
+typedef struct {
+    ngx_str_t   key;
+    ngx_str_t   value;
+} ngx_keyval_t;
+
+
+typedef struct {
+    unsigned    len:28;
+
+    unsigned    valid:1;
+    unsigned    no_cacheable:1;
+    unsigned    not_found:1;
+    unsigned    escape:1;
+
+    u_char     *data;
+} ngx_variable_value_t;
+
+
+#define ngx_string(str)     { sizeof(str) - 1, (u_char *) str }
+#define ngx_null_string     { 0, NULL }
+#define ngx_str_set(str, text)                                               \
+    (str)->len = sizeof(text) - 1; (str)->data = (u_char *) text
+#define ngx_str_null(str)   (str)->len = 0; (str)->data = NULL
+
+
+#define ngx_tolower(c)      (u_char) ((c >= 'A' && c <= 'Z') ? (c | 0x20) : c)
+#define ngx_toupper(c)      (u_char) ((c >= 'a' && c <= 'z') ? (c & ~0x20) : c)
+
+void ngx_strlow(u_char *dst, u_char *src, size_t n);
+
+
+#define ngx_strncmp(s1, s2, n)  strncmp((const char *) s1, (const char *) s2, n)
+
+
+/* msvc and icc7 compile strcmp() to inline loop */
+#define ngx_strcmp(s1, s2)  strcmp((const char *) s1, (const char *) s2)
+
+
+#define ngx_strstr(s1, s2)  strstr((const char *) s1, (const char *) s2)
+#define ngx_strlen(s)       strlen((const char *) s)
+
+size_t ngx_strnlen(u_char *p, size_t n);
+
+#define ngx_strchr(s1, c)   strchr((const char *) s1, (int) c)
+
+static ngx_inline u_char *
+ngx_strlchr(u_char *p, u_char *last, u_char c)
+{
+    while (p < last) {
+
+        if (*p == c) {
+            return p;
+        }
+
+        p++;
+    }
+
+    return NULL;
+}
+
+
+/*
+ * msvc and icc7 compile memset() to the inline "rep stos"
+ * while ZeroMemory() and bzero() are the calls.
+ * icc7 may also inline several mov's of a zeroed register for small blocks.
+ */
+#define ngx_memzero(buf, n)       (void) memset(buf, 0, n)
+#define ngx_memset(buf, c, n)     (void) memset(buf, c, n)
+
+
+#if (NGX_MEMCPY_LIMIT)
+
+void *ngx_memcpy(void *dst, const void *src, size_t n);
+#define ngx_cpymem(dst, src, n)   (((u_char *) ngx_memcpy(dst, src, n)) + (n))
+
+#else
+
+/*
+ * gcc3, msvc, and icc7 compile memcpy() to the inline "rep movs".
+ * gcc3 compiles memcpy(d, s, 4) to the inline "mov"es.
+ * icc8 compile memcpy(d, s, 4) to the inline "mov"es or XMM moves.
+ */
+#define ngx_memcpy(dst, src, n)   (void) memcpy(dst, src, n)
+#define ngx_cpymem(dst, src, n)   (((u_char *) memcpy(dst, src, n)) + (n))
+
+#endif
+
+
+#if ( __INTEL_COMPILER >= 800 )
+
+/*
+ * the simple inline cycle copies the variable length strings up to 16
+ * bytes faster than icc8 autodetecting _intel_fast_memcpy()
+ */
+
+static ngx_inline u_char *
+ngx_copy(u_char *dst, u_char *src, size_t len)
+{
+    if (len < 17) {
+
+        while (len) {
+            *dst++ = *src++;
+            len--;
+        }
+
+        return dst;
+
+    } else {
+        return ngx_cpymem(dst, src, len);
+    }
+}
+
+#else
+
+#define ngx_copy                  ngx_cpymem
+
+#endif
+
+
+#define ngx_memmove(dst, src, n)   (void) memmove(dst, src, n)
+#define ngx_movemem(dst, src, n)   (((u_char *) memmove(dst, src, n)) + (n))
+
+
+/* msvc and icc7 compile memcmp() to the inline loop */
+#define ngx_memcmp(s1, s2, n)  memcmp((const char *) s1, (const char *) s2, n)
+
+
+u_char *ngx_cpystrn(u_char *dst, u_char *src, size_t n);
+u_char *ngx_pstrdup(ngx_pool_t *pool, ngx_str_t *src);
+u_char * ngx_cdecl ngx_sprintf(u_char *buf, const char *fmt, ...);
+u_char * ngx_cdecl ngx_snprintf(u_char *buf, size_t max, const char *fmt, ...);
+u_char * ngx_cdecl ngx_slprintf(u_char *buf, u_char *last, const char *fmt,
+    ...);
+u_char *ngx_vslprintf(u_char *buf, u_char *last, const char *fmt, va_list args);
+#define ngx_vsnprintf(buf, max, fmt, args)                                   \
+    ngx_vslprintf(buf, buf + (max), fmt, args)
+
+ngx_int_t ngx_strcasecmp(u_char *s1, u_char *s2);
+ngx_int_t ngx_strncasecmp(u_char *s1, u_char *s2, size_t n);
+
+u_char *ngx_strnstr(u_char *s1, char *s2, size_t n);
+
+u_char *ngx_strstrn(u_char *s1, char *s2, size_t n);
+u_char *ngx_strcasestrn(u_char *s1, char *s2, size_t n);
+u_char *ngx_strlcasestrn(u_char *s1, u_char *last, u_char *s2, size_t n);
+
+ngx_int_t ngx_rstrncmp(u_char *s1, u_char *s2, size_t n);
+ngx_int_t ngx_rstrncasecmp(u_char *s1, u_char *s2, size_t n);
+ngx_int_t ngx_memn2cmp(u_char *s1, u_char *s2, size_t n1, size_t n2);
+ngx_int_t ngx_dns_strcmp(u_char *s1, u_char *s2);
+ngx_int_t ngx_filename_cmp(u_char *s1, u_char *s2, size_t n);
+
+ngx_int_t ngx_atoi(u_char *line, size_t n);
+ngx_int_t ngx_atofp(u_char *line, size_t n, size_t point);
+ssize_t ngx_atosz(u_char *line, size_t n);
+off_t ngx_atoof(u_char *line, size_t n);
+time_t ngx_atotm(u_char *line, size_t n);
+ngx_int_t ngx_hextoi(u_char *line, size_t n);
+
+u_char *ngx_hex_dump(u_char *dst, u_char *src, size_t len);
+
+
+#define ngx_base64_encoded_length(len)  (((len + 2) / 3) * 4)
+#define ngx_base64_decoded_length(len)  (((len + 3) / 4) * 3)
+
+void ngx_encode_base64(ngx_str_t *dst, ngx_str_t *src);
+void ngx_encode_base64url(ngx_str_t *dst, ngx_str_t *src);
+ngx_int_t ngx_decode_base64(ngx_str_t *dst, ngx_str_t *src);
+ngx_int_t ngx_decode_base64url(ngx_str_t *dst, ngx_str_t *src);
+
+uint32_t ngx_utf8_decode(u_char **p, size_t n);
+size_t ngx_utf8_length(u_char *p, size_t n);
+u_char *ngx_utf8_cpystrn(u_char *dst, u_char *src, size_t n, size_t len);
+
+
+#define NGX_ESCAPE_URI            0
+#define NGX_ESCAPE_ARGS           1
+#define NGX_ESCAPE_URI_COMPONENT  2
+#define NGX_ESCAPE_HTML           3
+#define NGX_ESCAPE_REFRESH        4
+#define NGX_ESCAPE_MEMCACHED      5
+#define NGX_ESCAPE_MAIL_AUTH      6
+
+#define NGX_UNESCAPE_URI       1
+#define NGX_UNESCAPE_REDIRECT  2
+
+uintptr_t ngx_escape_uri(u_char *dst, u_char *src, size_t size,
+    ngx_uint_t type);
+void ngx_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type);
+uintptr_t ngx_escape_html(u_char *dst, u_char *src, size_t size);
+uintptr_t ngx_escape_json(u_char *dst, u_char *src, size_t size);
+
+
+typedef struct {
+    ngx_rbtree_node_t         node;
+    ngx_str_t                 str;
+} ngx_str_node_t;
+
+
+void ngx_str_rbtree_insert_value(ngx_rbtree_node_t *temp,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
+ngx_str_node_t *ngx_str_rbtree_lookup(ngx_rbtree_t *rbtree, ngx_str_t *name,
+    uint32_t hash);
+
+
+void ngx_sort(void *base, size_t n, size_t size,
+    ngx_int_t (*cmp)(const void *, const void *));
+#define ngx_qsort             qsort
+
+
+#define ngx_value_helper(n)   #n
+#define ngx_value(n)          ngx_value_helper(n)
+
+
+#endif /* _NGX_STRING_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_syslog.c b/nginx/src/core/ngx_syslog.c
new file mode 100644 (file)
index 0000000..285cc0c
--- /dev/null
@@ -0,0 +1,382 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#define NGX_SYSLOG_MAX_STR                                                    \
+    NGX_MAX_ERROR_STR + sizeof("<255>Jan 01 00:00:00 ") - 1                   \
+    + (NGX_MAXHOSTNAMELEN - 1) + 1 /* space */                                \
+    + 32 /* tag */ + 2 /* colon, space */
+
+
+static char *ngx_syslog_parse_args(ngx_conf_t *cf, ngx_syslog_peer_t *peer);
+static ngx_int_t ngx_syslog_init_peer(ngx_syslog_peer_t *peer);
+static void ngx_syslog_cleanup(void *data);
+
+
+static char  *facilities[] = {
+    "kern", "user", "mail", "daemon", "auth", "intern", "lpr", "news", "uucp",
+    "clock", "authpriv", "ftp", "ntp", "audit", "alert", "cron", "local0",
+    "local1", "local2", "local3", "local4", "local5", "local6", "local7",
+    NULL
+};
+
+/* note 'error/warn' like in nginx.conf, not 'err/warning' */
+static char  *severities[] = {
+    "emerg", "alert", "crit", "error", "warn", "notice", "info", "debug", NULL
+};
+
+static ngx_log_t    ngx_syslog_dummy_log;
+static ngx_event_t  ngx_syslog_dummy_event;
+
+
+char *
+ngx_syslog_process_conf(ngx_conf_t *cf, ngx_syslog_peer_t *peer)
+{
+    peer->pool = cf->pool;
+    peer->facility = NGX_CONF_UNSET_UINT;
+    peer->severity = NGX_CONF_UNSET_UINT;
+
+    if (ngx_syslog_parse_args(cf, peer) != NGX_CONF_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (peer->server.sockaddr == NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "no syslog server specified");
+        return NGX_CONF_ERROR;
+    }
+
+    if (peer->facility == NGX_CONF_UNSET_UINT) {
+        peer->facility = 23; /* local7 */
+    }
+
+    if (peer->severity == NGX_CONF_UNSET_UINT) {
+        peer->severity = 6; /* info */
+    }
+
+    if (peer->tag.data == NULL) {
+        ngx_str_set(&peer->tag, "nginx");
+    }
+
+    peer->conn.fd = (ngx_socket_t) -1;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_syslog_parse_args(ngx_conf_t *cf, ngx_syslog_peer_t *peer)
+{
+    u_char      *p, *comma, c;
+    size_t       len;
+    ngx_str_t   *value;
+    ngx_url_t    u;
+    ngx_uint_t   i;
+
+    value = cf->args->elts;
+
+    p = value[1].data + sizeof("syslog:") - 1;
+
+    for ( ;; ) {
+        comma = (u_char *) ngx_strchr(p, ',');
+
+        if (comma != NULL) {
+            len = comma - p;
+            *comma = '\0';
+
+        } else {
+            len = value[1].data + value[1].len - p;
+        }
+
+        if (ngx_strncmp(p, "server=", 7) == 0) {
+
+            if (peer->server.sockaddr != NULL) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "duplicate syslog \"server\"");
+                return NGX_CONF_ERROR;
+            }
+
+            ngx_memzero(&u, sizeof(ngx_url_t));
+
+            u.url.data = p + 7;
+            u.url.len = len - 7;
+            u.default_port = 514;
+
+            if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
+                if (u.err) {
+                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                       "%s in syslog server \"%V\"",
+                                       u.err, &u.url);
+                }
+
+                return NGX_CONF_ERROR;
+            }
+
+            peer->server = u.addrs[0];
+
+        } else if (ngx_strncmp(p, "facility=", 9) == 0) {
+
+            if (peer->facility != NGX_CONF_UNSET_UINT) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "duplicate syslog \"facility\"");
+                return NGX_CONF_ERROR;
+            }
+
+            for (i = 0; facilities[i] != NULL; i++) {
+
+                if (ngx_strcmp(p + 9, facilities[i]) == 0) {
+                    peer->facility = i;
+                    goto next;
+                }
+            }
+
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "unknown syslog facility \"%s\"", p + 9);
+            return NGX_CONF_ERROR;
+
+        } else if (ngx_strncmp(p, "severity=", 9) == 0) {
+
+            if (peer->severity != NGX_CONF_UNSET_UINT) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "duplicate syslog \"severity\"");
+                return NGX_CONF_ERROR;
+            }
+
+            for (i = 0; severities[i] != NULL; i++) {
+
+                if (ngx_strcmp(p + 9, severities[i]) == 0) {
+                    peer->severity = i;
+                    goto next;
+                }
+            }
+
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "unknown syslog severity \"%s\"", p + 9);
+            return NGX_CONF_ERROR;
+
+        } else if (ngx_strncmp(p, "tag=", 4) == 0) {
+
+            if (peer->tag.data != NULL) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "duplicate syslog \"tag\"");
+                return NGX_CONF_ERROR;
+            }
+
+            /*
+             * RFC 3164: the TAG is a string of ABNF alphanumeric characters
+             * that MUST NOT exceed 32 characters.
+             */
+            if (len - 4 > 32) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "syslog tag length exceeds 32");
+                return NGX_CONF_ERROR;
+            }
+
+            for (i = 4; i < len; i++) {
+                c = ngx_tolower(p[i]);
+
+                if (c < '0' || (c > '9' && c < 'a' && c != '_') || c > 'z') {
+                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                       "syslog \"tag\" only allows "
+                                       "alphanumeric characters "
+                                       "and underscore");
+                    return NGX_CONF_ERROR;
+                }
+            }
+
+            peer->tag.data = p + 4;
+            peer->tag.len = len - 4;
+
+        } else if (len == 10 && ngx_strncmp(p, "nohostname", 10) == 0) {
+            peer->nohostname = 1;
+
+        } else {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "unknown syslog parameter \"%s\"", p);
+            return NGX_CONF_ERROR;
+        }
+
+    next:
+
+        if (comma == NULL) {
+            break;
+        }
+
+        p = comma + 1;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+u_char *
+ngx_syslog_add_header(ngx_syslog_peer_t *peer, u_char *buf)
+{
+    ngx_uint_t  pri;
+
+    pri = peer->facility * 8 + peer->severity;
+
+    if (peer->nohostname) {
+        return ngx_sprintf(buf, "<%ui>%V %V: ", pri, &ngx_cached_syslog_time,
+                           &peer->tag);
+    }
+
+    return ngx_sprintf(buf, "<%ui>%V %V %V: ", pri, &ngx_cached_syslog_time,
+                       &ngx_cycle->hostname, &peer->tag);
+}
+
+
+void
+ngx_syslog_writer(ngx_log_t *log, ngx_uint_t level, u_char *buf,
+    size_t len)
+{
+    u_char             *p, msg[NGX_SYSLOG_MAX_STR];
+    ngx_uint_t          head_len;
+    ngx_syslog_peer_t  *peer;
+
+    peer = log->wdata;
+
+    if (peer->busy) {
+        return;
+    }
+
+    peer->busy = 1;
+    peer->severity = level - 1;
+
+    p = ngx_syslog_add_header(peer, msg);
+    head_len = p - msg;
+
+    len -= NGX_LINEFEED_SIZE;
+
+    if (len > NGX_SYSLOG_MAX_STR - head_len) {
+        len = NGX_SYSLOG_MAX_STR - head_len;
+    }
+
+    p = ngx_snprintf(p, len, "%s", buf);
+
+    (void) ngx_syslog_send(peer, msg, p - msg);
+
+    peer->busy = 0;
+}
+
+
+ssize_t
+ngx_syslog_send(ngx_syslog_peer_t *peer, u_char *buf, size_t len)
+{
+    ssize_t  n;
+
+    if (peer->conn.fd == (ngx_socket_t) -1) {
+        if (ngx_syslog_init_peer(peer) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    /* log syslog socket events with valid log */
+    peer->conn.log = ngx_cycle->log;
+
+    if (ngx_send) {
+        n = ngx_send(&peer->conn, buf, len);
+
+    } else {
+        /* event module has not yet set ngx_io */
+        n = ngx_os_io.send(&peer->conn, buf, len);
+    }
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+
+    if (n == NGX_ERROR && peer->server.sockaddr->sa_family == AF_UNIX) {
+
+        if (ngx_close_socket(peer->conn.fd) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_socket_errno,
+                          ngx_close_socket_n " failed");
+        }
+
+        peer->conn.fd = (ngx_socket_t) -1;
+    }
+
+#endif
+
+    return n;
+}
+
+
+static ngx_int_t
+ngx_syslog_init_peer(ngx_syslog_peer_t *peer)
+{
+    ngx_socket_t         fd;
+    ngx_pool_cleanup_t  *cln;
+
+    peer->conn.read = &ngx_syslog_dummy_event;
+    peer->conn.write = &ngx_syslog_dummy_event;
+
+    ngx_syslog_dummy_event.log = &ngx_syslog_dummy_log;
+
+    fd = ngx_socket(peer->server.sockaddr->sa_family, SOCK_DGRAM, 0);
+    if (fd == (ngx_socket_t) -1) {
+        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_socket_errno,
+                      ngx_socket_n " failed");
+        return NGX_ERROR;
+    }
+
+    if (ngx_nonblocking(fd) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_socket_errno,
+                      ngx_nonblocking_n " failed");
+        goto failed;
+    }
+
+    if (ngxvcl_connect(fd, peer->server.sockaddr, peer->server.socklen) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_socket_errno,
+                      "connect() failed");
+        goto failed;
+    }
+
+    cln = ngx_pool_cleanup_add(peer->pool, 0);
+    if (cln == NULL) {
+        goto failed;
+    }
+
+    cln->data = peer;
+    cln->handler = ngx_syslog_cleanup;
+
+    peer->conn.fd = fd;
+
+    /* UDP sockets are always ready to write */
+    peer->conn.write->ready = 1;
+
+    return NGX_OK;
+
+failed:
+
+    if (ngx_close_socket(fd) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_socket_errno,
+                      ngx_close_socket_n " failed");
+    }
+
+    return NGX_ERROR;
+}
+
+
+static void
+ngx_syslog_cleanup(void *data)
+{
+    ngx_syslog_peer_t  *peer = data;
+
+    /* prevents further use of this peer */
+    peer->busy = 1;
+
+    if (peer->conn.fd == (ngx_socket_t) -1) {
+        return;
+    }
+
+    if (ngx_close_socket(peer->conn.fd) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_socket_errno,
+                      ngx_close_socket_n " failed");
+    }
+}
diff --git a/nginx/src/core/ngx_syslog.h b/nginx/src/core/ngx_syslog.h
new file mode 100644 (file)
index 0000000..cc4c842
--- /dev/null
@@ -0,0 +1,31 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_SYSLOG_H_INCLUDED_
+#define _NGX_SYSLOG_H_INCLUDED_
+
+
+typedef struct {
+    ngx_pool_t       *pool;
+    ngx_uint_t        facility;
+    ngx_uint_t        severity;
+    ngx_str_t         tag;
+
+    ngx_addr_t        server;
+    ngx_connection_t  conn;
+    unsigned          busy:1;
+    unsigned          nohostname:1;
+} ngx_syslog_peer_t;
+
+
+char *ngx_syslog_process_conf(ngx_conf_t *cf, ngx_syslog_peer_t *peer);
+u_char *ngx_syslog_add_header(ngx_syslog_peer_t *peer, u_char *buf);
+void ngx_syslog_writer(ngx_log_t *log, ngx_uint_t level, u_char *buf,
+    size_t len);
+ssize_t ngx_syslog_send(ngx_syslog_peer_t *peer, u_char *buf, size_t len);
+
+
+#endif /* _NGX_SYSLOG_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_thread_pool.c b/nginx/src/core/ngx_thread_pool.c
new file mode 100644 (file)
index 0000000..7fb0f7f
--- /dev/null
@@ -0,0 +1,641 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Valentin V. Bartenev
+ * Copyright (C) Ruslan Ermilov
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_thread_pool.h>
+
+
+typedef struct {
+    ngx_array_t               pools;
+} ngx_thread_pool_conf_t;
+
+
+typedef struct {
+    ngx_thread_task_t        *first;
+    ngx_thread_task_t       **last;
+} ngx_thread_pool_queue_t;
+
+#define ngx_thread_pool_queue_init(q)                                         \
+    (q)->first = NULL;                                                        \
+    (q)->last = &(q)->first
+
+
+struct ngx_thread_pool_s {
+    ngx_thread_mutex_t        mtx;
+    ngx_thread_pool_queue_t   queue;
+    ngx_int_t                 waiting;
+    ngx_thread_cond_t         cond;
+
+    ngx_log_t                *log;
+
+    ngx_str_t                 name;
+    ngx_uint_t                threads;
+    ngx_int_t                 max_queue;
+
+    u_char                   *file;
+    ngx_uint_t                line;
+};
+
+
+static ngx_int_t ngx_thread_pool_init(ngx_thread_pool_t *tp, ngx_log_t *log,
+    ngx_pool_t *pool);
+static void ngx_thread_pool_destroy(ngx_thread_pool_t *tp);
+static void ngx_thread_pool_exit_handler(void *data, ngx_log_t *log);
+
+static void *ngx_thread_pool_cycle(void *data);
+static void ngx_thread_pool_handler(ngx_event_t *ev);
+
+static char *ngx_thread_pool(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+static void *ngx_thread_pool_create_conf(ngx_cycle_t *cycle);
+static char *ngx_thread_pool_init_conf(ngx_cycle_t *cycle, void *conf);
+
+static ngx_int_t ngx_thread_pool_init_worker(ngx_cycle_t *cycle);
+static void ngx_thread_pool_exit_worker(ngx_cycle_t *cycle);
+
+
+static ngx_command_t  ngx_thread_pool_commands[] = {
+
+    { ngx_string("thread_pool"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE23,
+      ngx_thread_pool,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_core_module_t  ngx_thread_pool_module_ctx = {
+    ngx_string("thread_pool"),
+    ngx_thread_pool_create_conf,
+    ngx_thread_pool_init_conf
+};
+
+
+ngx_module_t  ngx_thread_pool_module = {
+    NGX_MODULE_V1,
+    &ngx_thread_pool_module_ctx,           /* module context */
+    ngx_thread_pool_commands,              /* module directives */
+    NGX_CORE_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    ngx_thread_pool_init_worker,           /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    ngx_thread_pool_exit_worker,           /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t  ngx_thread_pool_default = ngx_string("default");
+
+static ngx_uint_t               ngx_thread_pool_task_id;
+static ngx_atomic_t             ngx_thread_pool_done_lock;
+static ngx_thread_pool_queue_t  ngx_thread_pool_done;
+
+
+static ngx_int_t
+ngx_thread_pool_init(ngx_thread_pool_t *tp, ngx_log_t *log, ngx_pool_t *pool)
+{
+    int             err;
+    pthread_t       tid;
+    ngx_uint_t      n;
+    pthread_attr_t  attr;
+
+    if (ngx_notify == NULL) {
+        ngx_log_error(NGX_LOG_ALERT, log, 0,
+               "the configured event method cannot be used with thread pools");
+        return NGX_ERROR;
+    }
+
+    ngx_thread_pool_queue_init(&tp->queue);
+
+    if (ngx_thread_mutex_create(&tp->mtx, log) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_thread_cond_create(&tp->cond, log) != NGX_OK) {
+        (void) ngx_thread_mutex_destroy(&tp->mtx, log);
+        return NGX_ERROR;
+    }
+
+    tp->log = log;
+
+    err = pthread_attr_init(&attr);
+    if (err) {
+        ngx_log_error(NGX_LOG_ALERT, log, err,
+                      "pthread_attr_init() failed");
+        return NGX_ERROR;
+    }
+
+    err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+    if (err) {
+        ngx_log_error(NGX_LOG_ALERT, log, err,
+                      "pthread_attr_setdetachstate() failed");
+        return NGX_ERROR;
+    }
+
+#if 0
+    err = pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN);
+    if (err) {
+        ngx_log_error(NGX_LOG_ALERT, log, err,
+                      "pthread_attr_setstacksize() failed");
+        return NGX_ERROR;
+    }
+#endif
+
+    for (n = 0; n < tp->threads; n++) {
+        err = pthread_create(&tid, &attr, ngx_thread_pool_cycle, tp);
+        if (err) {
+            ngx_log_error(NGX_LOG_ALERT, log, err,
+                          "pthread_create() failed");
+            return NGX_ERROR;
+        }
+    }
+
+    (void) pthread_attr_destroy(&attr);
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_thread_pool_destroy(ngx_thread_pool_t *tp)
+{
+    ngx_uint_t           n;
+    ngx_thread_task_t    task;
+    volatile ngx_uint_t  lock;
+
+    ngx_memzero(&task, sizeof(ngx_thread_task_t));
+
+    task.handler = ngx_thread_pool_exit_handler;
+    task.ctx = (void *) &lock;
+
+    for (n = 0; n < tp->threads; n++) {
+        lock = 1;
+
+        if (ngx_thread_task_post(tp, &task) != NGX_OK) {
+            return;
+        }
+
+        while (lock) {
+            ngx_sched_yield();
+        }
+
+        task.event.active = 0;
+    }
+
+    (void) ngx_thread_cond_destroy(&tp->cond, tp->log);
+
+    (void) ngx_thread_mutex_destroy(&tp->mtx, tp->log);
+}
+
+
+static void
+ngx_thread_pool_exit_handler(void *data, ngx_log_t *log)
+{
+    ngx_uint_t *lock = data;
+
+    *lock = 0;
+
+    pthread_exit(0);
+}
+
+
+ngx_thread_task_t *
+ngx_thread_task_alloc(ngx_pool_t *pool, size_t size)
+{
+    ngx_thread_task_t  *task;
+
+    task = ngx_pcalloc(pool, sizeof(ngx_thread_task_t) + size);
+    if (task == NULL) {
+        return NULL;
+    }
+
+    task->ctx = task + 1;
+
+    return task;
+}
+
+
+ngx_int_t
+ngx_thread_task_post(ngx_thread_pool_t *tp, ngx_thread_task_t *task)
+{
+    if (task->event.active) {
+        ngx_log_error(NGX_LOG_ALERT, tp->log, 0,
+                      "task #%ui already active", task->id);
+        return NGX_ERROR;
+    }
+
+    if (ngx_thread_mutex_lock(&tp->mtx, tp->log) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (tp->waiting >= tp->max_queue) {
+        (void) ngx_thread_mutex_unlock(&tp->mtx, tp->log);
+
+        ngx_log_error(NGX_LOG_ERR, tp->log, 0,
+                      "thread pool \"%V\" queue overflow: %i tasks waiting",
+                      &tp->name, tp->waiting);
+        return NGX_ERROR;
+    }
+
+    task->event.active = 1;
+
+    task->id = ngx_thread_pool_task_id++;
+    task->next = NULL;
+
+    if (ngx_thread_cond_signal(&tp->cond, tp->log) != NGX_OK) {
+        (void) ngx_thread_mutex_unlock(&tp->mtx, tp->log);
+        return NGX_ERROR;
+    }
+
+    *tp->queue.last = task;
+    tp->queue.last = &task->next;
+
+    tp->waiting++;
+
+    (void) ngx_thread_mutex_unlock(&tp->mtx, tp->log);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_CORE, tp->log, 0,
+                   "task #%ui added to thread pool \"%V\"",
+                   task->id, &tp->name);
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_thread_pool_cycle(void *data)
+{
+    ngx_thread_pool_t *tp = data;
+
+    int                 err;
+    sigset_t            set;
+    ngx_thread_task_t  *task;
+
+#if 0
+    ngx_time_update();
+#endif
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, tp->log, 0,
+                   "thread in pool \"%V\" started", &tp->name);
+
+    sigfillset(&set);
+
+    sigdelset(&set, SIGILL);
+    sigdelset(&set, SIGFPE);
+    sigdelset(&set, SIGSEGV);
+    sigdelset(&set, SIGBUS);
+
+    err = pthread_sigmask(SIG_BLOCK, &set, NULL);
+    if (err) {
+        ngx_log_error(NGX_LOG_ALERT, tp->log, err, "pthread_sigmask() failed");
+        return NULL;
+    }
+
+    for ( ;; ) {
+        if (ngx_thread_mutex_lock(&tp->mtx, tp->log) != NGX_OK) {
+            return NULL;
+        }
+
+        /* the number may become negative */
+        tp->waiting--;
+
+        while (tp->queue.first == NULL) {
+            if (ngx_thread_cond_wait(&tp->cond, &tp->mtx, tp->log)
+                != NGX_OK)
+            {
+                (void) ngx_thread_mutex_unlock(&tp->mtx, tp->log);
+                return NULL;
+            }
+        }
+
+        task = tp->queue.first;
+        tp->queue.first = task->next;
+
+        if (tp->queue.first == NULL) {
+            tp->queue.last = &tp->queue.first;
+        }
+
+        if (ngx_thread_mutex_unlock(&tp->mtx, tp->log) != NGX_OK) {
+            return NULL;
+        }
+
+#if 0
+        ngx_time_update();
+#endif
+
+        ngx_log_debug2(NGX_LOG_DEBUG_CORE, tp->log, 0,
+                       "run task #%ui in thread pool \"%V\"",
+                       task->id, &tp->name);
+
+        task->handler(task->ctx, tp->log);
+
+        ngx_log_debug2(NGX_LOG_DEBUG_CORE, tp->log, 0,
+                       "complete task #%ui in thread pool \"%V\"",
+                       task->id, &tp->name);
+
+        task->next = NULL;
+
+        ngx_spinlock(&ngx_thread_pool_done_lock, 1, 2048);
+
+        *ngx_thread_pool_done.last = task;
+        ngx_thread_pool_done.last = &task->next;
+
+        ngx_memory_barrier();
+
+        ngx_unlock(&ngx_thread_pool_done_lock);
+
+        (void) ngx_notify(ngx_thread_pool_handler);
+    }
+}
+
+
+static void
+ngx_thread_pool_handler(ngx_event_t *ev)
+{
+    ngx_event_t        *event;
+    ngx_thread_task_t  *task;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_CORE, ev->log, 0, "thread pool handler");
+
+    ngx_spinlock(&ngx_thread_pool_done_lock, 1, 2048);
+
+    task = ngx_thread_pool_done.first;
+    ngx_thread_pool_done.first = NULL;
+    ngx_thread_pool_done.last = &ngx_thread_pool_done.first;
+
+    ngx_memory_barrier();
+
+    ngx_unlock(&ngx_thread_pool_done_lock);
+
+    while (task) {
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0,
+                       "run completion handler for task #%ui", task->id);
+
+        event = &task->event;
+        task = task->next;
+
+        event->complete = 1;
+        event->active = 0;
+
+        event->handler(event);
+    }
+}
+
+
+static void *
+ngx_thread_pool_create_conf(ngx_cycle_t *cycle)
+{
+    ngx_thread_pool_conf_t  *tcf;
+
+    tcf = ngx_pcalloc(cycle->pool, sizeof(ngx_thread_pool_conf_t));
+    if (tcf == NULL) {
+        return NULL;
+    }
+
+    if (ngx_array_init(&tcf->pools, cycle->pool, 4,
+                       sizeof(ngx_thread_pool_t *))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    return tcf;
+}
+
+
+static char *
+ngx_thread_pool_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+    ngx_thread_pool_conf_t *tcf = conf;
+
+    ngx_uint_t           i;
+    ngx_thread_pool_t  **tpp;
+
+    tpp = tcf->pools.elts;
+
+    for (i = 0; i < tcf->pools.nelts; i++) {
+
+        if (tpp[i]->threads) {
+            continue;
+        }
+
+        if (tpp[i]->name.len == ngx_thread_pool_default.len
+            && ngx_strncmp(tpp[i]->name.data, ngx_thread_pool_default.data,
+                           ngx_thread_pool_default.len)
+               == 0)
+        {
+            tpp[i]->threads = 32;
+            tpp[i]->max_queue = 65536;
+            continue;
+        }
+
+        ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+                      "unknown thread pool \"%V\" in %s:%ui",
+                      &tpp[i]->name, tpp[i]->file, tpp[i]->line);
+
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_thread_pool(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_str_t          *value;
+    ngx_uint_t          i;
+    ngx_thread_pool_t  *tp;
+
+    value = cf->args->elts;
+
+    tp = ngx_thread_pool_add(cf, &value[1]);
+
+    if (tp == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (tp->threads) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "duplicate thread pool \"%V\"", &tp->name);
+        return NGX_CONF_ERROR;
+    }
+
+    tp->max_queue = 65536;
+
+    for (i = 2; i < cf->args->nelts; i++) {
+
+        if (ngx_strncmp(value[i].data, "threads=", 8) == 0) {
+
+            tp->threads = ngx_atoi(value[i].data + 8, value[i].len - 8);
+
+            if (tp->threads == (ngx_uint_t) NGX_ERROR || tp->threads == 0) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid threads value \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "max_queue=", 10) == 0) {
+
+            tp->max_queue = ngx_atoi(value[i].data + 10, value[i].len - 10);
+
+            if (tp->max_queue == NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid max_queue value \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+    }
+
+    if (tp->threads == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"%V\" must have \"threads\" parameter",
+                           &cmd->name);
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+ngx_thread_pool_t *
+ngx_thread_pool_add(ngx_conf_t *cf, ngx_str_t *name)
+{
+    ngx_thread_pool_t       *tp, **tpp;
+    ngx_thread_pool_conf_t  *tcf;
+
+    if (name == NULL) {
+        name = &ngx_thread_pool_default;
+    }
+
+    tp = ngx_thread_pool_get(cf->cycle, name);
+
+    if (tp) {
+        return tp;
+    }
+
+    tp = ngx_pcalloc(cf->pool, sizeof(ngx_thread_pool_t));
+    if (tp == NULL) {
+        return NULL;
+    }
+
+    tp->name = *name;
+    tp->file = cf->conf_file->file.name.data;
+    tp->line = cf->conf_file->line;
+
+    tcf = (ngx_thread_pool_conf_t *) ngx_get_conf(cf->cycle->conf_ctx,
+                                                  ngx_thread_pool_module);
+
+    tpp = ngx_array_push(&tcf->pools);
+    if (tpp == NULL) {
+        return NULL;
+    }
+
+    *tpp = tp;
+
+    return tp;
+}
+
+
+ngx_thread_pool_t *
+ngx_thread_pool_get(ngx_cycle_t *cycle, ngx_str_t *name)
+{
+    ngx_uint_t                i;
+    ngx_thread_pool_t       **tpp;
+    ngx_thread_pool_conf_t   *tcf;
+
+    tcf = (ngx_thread_pool_conf_t *) ngx_get_conf(cycle->conf_ctx,
+                                                  ngx_thread_pool_module);
+
+    tpp = tcf->pools.elts;
+
+    for (i = 0; i < tcf->pools.nelts; i++) {
+
+        if (tpp[i]->name.len == name->len
+            && ngx_strncmp(tpp[i]->name.data, name->data, name->len) == 0)
+        {
+            return tpp[i];
+        }
+    }
+
+    return NULL;
+}
+
+
+static ngx_int_t
+ngx_thread_pool_init_worker(ngx_cycle_t *cycle)
+{
+    ngx_uint_t                i;
+    ngx_thread_pool_t       **tpp;
+    ngx_thread_pool_conf_t   *tcf;
+
+    if (ngx_process != NGX_PROCESS_WORKER
+        && ngx_process != NGX_PROCESS_SINGLE)
+    {
+        return NGX_OK;
+    }
+
+    tcf = (ngx_thread_pool_conf_t *) ngx_get_conf(cycle->conf_ctx,
+                                                  ngx_thread_pool_module);
+
+    if (tcf == NULL) {
+        return NGX_OK;
+    }
+
+    ngx_thread_pool_queue_init(&ngx_thread_pool_done);
+
+    tpp = tcf->pools.elts;
+
+    for (i = 0; i < tcf->pools.nelts; i++) {
+        if (ngx_thread_pool_init(tpp[i], cycle->log, cycle->pool) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_thread_pool_exit_worker(ngx_cycle_t *cycle)
+{
+    ngx_uint_t                i;
+    ngx_thread_pool_t       **tpp;
+    ngx_thread_pool_conf_t   *tcf;
+
+    if (ngx_process != NGX_PROCESS_WORKER
+        && ngx_process != NGX_PROCESS_SINGLE)
+    {
+        return;
+    }
+
+    tcf = (ngx_thread_pool_conf_t *) ngx_get_conf(cycle->conf_ctx,
+                                                  ngx_thread_pool_module);
+
+    if (tcf == NULL) {
+        return;
+    }
+
+    tpp = tcf->pools.elts;
+
+    for (i = 0; i < tcf->pools.nelts; i++) {
+        ngx_thread_pool_destroy(tpp[i]);
+    }
+}
diff --git a/nginx/src/core/ngx_thread_pool.h b/nginx/src/core/ngx_thread_pool.h
new file mode 100644 (file)
index 0000000..5e5adf6
--- /dev/null
@@ -0,0 +1,36 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Valentin V. Bartenev
+ */
+
+
+#ifndef _NGX_THREAD_POOL_H_INCLUDED_
+#define _NGX_THREAD_POOL_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+struct ngx_thread_task_s {
+    ngx_thread_task_t   *next;
+    ngx_uint_t           id;
+    void                *ctx;
+    void               (*handler)(void *data, ngx_log_t *log);
+    ngx_event_t          event;
+};
+
+
+typedef struct ngx_thread_pool_s  ngx_thread_pool_t;
+
+
+ngx_thread_pool_t *ngx_thread_pool_add(ngx_conf_t *cf, ngx_str_t *name);
+ngx_thread_pool_t *ngx_thread_pool_get(ngx_cycle_t *cycle, ngx_str_t *name);
+
+ngx_thread_task_t *ngx_thread_task_alloc(ngx_pool_t *pool, size_t size);
+ngx_int_t ngx_thread_task_post(ngx_thread_pool_t *tp, ngx_thread_task_t *task);
+
+
+#endif /* _NGX_THREAD_POOL_H_INCLUDED_ */
diff --git a/nginx/src/core/ngx_times.c b/nginx/src/core/ngx_times.c
new file mode 100644 (file)
index 0000000..7964b00
--- /dev/null
@@ -0,0 +1,468 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+static ngx_msec_t ngx_monotonic_time(time_t sec, ngx_uint_t msec);
+
+
+/*
+ * The time may be updated by signal handler or by several threads.
+ * The time update operations are rare and require to hold the ngx_time_lock.
+ * The time read operations are frequent, so they are lock-free and get time
+ * values and strings from the current slot.  Thus thread may get the corrupted
+ * values only if it is preempted while copying and then it is not scheduled
+ * to run more than NGX_TIME_SLOTS seconds.
+ */
+
+#define NGX_TIME_SLOTS   64
+
+static ngx_uint_t        slot;
+static ngx_atomic_t      ngx_time_lock;
+
+volatile ngx_msec_t      ngx_current_msec;
+volatile ngx_time_t     *ngx_cached_time;
+volatile ngx_str_t       ngx_cached_err_log_time;
+volatile ngx_str_t       ngx_cached_http_time;
+volatile ngx_str_t       ngx_cached_http_log_time;
+volatile ngx_str_t       ngx_cached_http_log_iso8601;
+volatile ngx_str_t       ngx_cached_syslog_time;
+
+#if !(NGX_WIN32)
+
+/*
+ * localtime() and localtime_r() are not Async-Signal-Safe functions, therefore,
+ * they must not be called by a signal handler, so we use the cached
+ * GMT offset value. Fortunately the value is changed only two times a year.
+ */
+
+static ngx_int_t         cached_gmtoff;
+#endif
+
+static ngx_time_t        cached_time[NGX_TIME_SLOTS];
+static u_char            cached_err_log_time[NGX_TIME_SLOTS]
+                                    [sizeof("1970/09/28 12:00:00")];
+static u_char            cached_http_time[NGX_TIME_SLOTS]
+                                    [sizeof("Mon, 28 Sep 1970 06:00:00 GMT")];
+static u_char            cached_http_log_time[NGX_TIME_SLOTS]
+                                    [sizeof("28/Sep/1970:12:00:00 +0600")];
+static u_char            cached_http_log_iso8601[NGX_TIME_SLOTS]
+                                    [sizeof("1970-09-28T12:00:00+06:00")];
+static u_char            cached_syslog_time[NGX_TIME_SLOTS]
+                                    [sizeof("Sep 28 12:00:00")];
+
+
+static char  *week[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+static char  *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+                           "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+void
+ngx_time_init(void)
+{
+    ngx_cached_err_log_time.len = sizeof("1970/09/28 12:00:00") - 1;
+    ngx_cached_http_time.len = sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1;
+    ngx_cached_http_log_time.len = sizeof("28/Sep/1970:12:00:00 +0600") - 1;
+    ngx_cached_http_log_iso8601.len = sizeof("1970-09-28T12:00:00+06:00") - 1;
+    ngx_cached_syslog_time.len = sizeof("Sep 28 12:00:00") - 1;
+
+    ngx_cached_time = &cached_time[0];
+
+    ngx_time_update();
+}
+
+
+void
+ngx_time_update(void)
+{
+    u_char          *p0, *p1, *p2, *p3, *p4;
+    ngx_tm_t         tm, gmt;
+    time_t           sec;
+    ngx_uint_t       msec;
+    ngx_time_t      *tp;
+    struct timeval   tv;
+
+    if (!ngx_trylock(&ngx_time_lock)) {
+        return;
+    }
+
+    ngx_gettimeofday(&tv);
+
+    sec = tv.tv_sec;
+    msec = tv.tv_usec / 1000;
+
+    ngx_current_msec = ngx_monotonic_time(sec, msec);
+
+    tp = &cached_time[slot];
+
+    if (tp->sec == sec) {
+        tp->msec = msec;
+        ngx_unlock(&ngx_time_lock);
+        return;
+    }
+
+    if (slot == NGX_TIME_SLOTS - 1) {
+        slot = 0;
+    } else {
+        slot++;
+    }
+
+    tp = &cached_time[slot];
+
+    tp->sec = sec;
+    tp->msec = msec;
+
+    ngx_gmtime(sec, &gmt);
+
+
+    p0 = &cached_http_time[slot][0];
+
+    (void) ngx_sprintf(p0, "%s, %02d %s %4d %02d:%02d:%02d GMT",
+                       week[gmt.ngx_tm_wday], gmt.ngx_tm_mday,
+                       months[gmt.ngx_tm_mon - 1], gmt.ngx_tm_year,
+                       gmt.ngx_tm_hour, gmt.ngx_tm_min, gmt.ngx_tm_sec);
+
+#if (NGX_HAVE_GETTIMEZONE)
+
+    tp->gmtoff = ngx_gettimezone();
+    ngx_gmtime(sec + tp->gmtoff * 60, &tm);
+
+#elif (NGX_HAVE_GMTOFF)
+
+    ngx_localtime(sec, &tm);
+    cached_gmtoff = (ngx_int_t) (tm.ngx_tm_gmtoff / 60);
+    tp->gmtoff = cached_gmtoff;
+
+#else
+
+    ngx_localtime(sec, &tm);
+    cached_gmtoff = ngx_timezone(tm.ngx_tm_isdst);
+    tp->gmtoff = cached_gmtoff;
+
+#endif
+
+
+    p1 = &cached_err_log_time[slot][0];
+
+    (void) ngx_sprintf(p1, "%4d/%02d/%02d %02d:%02d:%02d",
+                       tm.ngx_tm_year, tm.ngx_tm_mon,
+                       tm.ngx_tm_mday, tm.ngx_tm_hour,
+                       tm.ngx_tm_min, tm.ngx_tm_sec);
+
+
+    p2 = &cached_http_log_time[slot][0];
+
+    (void) ngx_sprintf(p2, "%02d/%s/%d:%02d:%02d:%02d %c%02i%02i",
+                       tm.ngx_tm_mday, months[tm.ngx_tm_mon - 1],
+                       tm.ngx_tm_year, tm.ngx_tm_hour,
+                       tm.ngx_tm_min, tm.ngx_tm_sec,
+                       tp->gmtoff < 0 ? '-' : '+',
+                       ngx_abs(tp->gmtoff / 60), ngx_abs(tp->gmtoff % 60));
+
+    p3 = &cached_http_log_iso8601[slot][0];
+
+    (void) ngx_sprintf(p3, "%4d-%02d-%02dT%02d:%02d:%02d%c%02i:%02i",
+                       tm.ngx_tm_year, tm.ngx_tm_mon,
+                       tm.ngx_tm_mday, tm.ngx_tm_hour,
+                       tm.ngx_tm_min, tm.ngx_tm_sec,
+                       tp->gmtoff < 0 ? '-' : '+',
+                       ngx_abs(tp->gmtoff / 60), ngx_abs(tp->gmtoff % 60));
+
+    p4 = &cached_syslog_time[slot][0];
+
+    (void) ngx_sprintf(p4, "%s %2d %02d:%02d:%02d",
+                       months[tm.ngx_tm_mon - 1], tm.ngx_tm_mday,
+                       tm.ngx_tm_hour, tm.ngx_tm_min, tm.ngx_tm_sec);
+
+    ngx_memory_barrier();
+
+    ngx_cached_time = tp;
+    ngx_cached_http_time.data = p0;
+    ngx_cached_err_log_time.data = p1;
+    ngx_cached_http_log_time.data = p2;
+    ngx_cached_http_log_iso8601.data = p3;
+    ngx_cached_syslog_time.data = p4;
+
+    ngx_unlock(&ngx_time_lock);
+}
+
+
+static ngx_msec_t
+ngx_monotonic_time(time_t sec, ngx_uint_t msec)
+{
+#if (NGX_HAVE_CLOCK_MONOTONIC)
+    struct timespec  ts;
+
+#if defined(CLOCK_MONOTONIC_FAST)
+    clock_gettime(CLOCK_MONOTONIC_FAST, &ts);
+
+#elif defined(CLOCK_MONOTONIC_COARSE)
+    clock_gettime(CLOCK_MONOTONIC_COARSE, &ts);
+
+#else
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+#endif
+
+    sec = ts.tv_sec;
+    msec = ts.tv_nsec / 1000000;
+
+#endif
+
+    return (ngx_msec_t) sec * 1000 + msec;
+}
+
+
+#if !(NGX_WIN32)
+
+void
+ngx_time_sigsafe_update(void)
+{
+    u_char          *p, *p2;
+    ngx_tm_t         tm;
+    time_t           sec;
+    ngx_time_t      *tp;
+    struct timeval   tv;
+
+    if (!ngx_trylock(&ngx_time_lock)) {
+        return;
+    }
+
+    ngx_gettimeofday(&tv);
+
+    sec = tv.tv_sec;
+
+    tp = &cached_time[slot];
+
+    if (tp->sec == sec) {
+        ngx_unlock(&ngx_time_lock);
+        return;
+    }
+
+    if (slot == NGX_TIME_SLOTS - 1) {
+        slot = 0;
+    } else {
+        slot++;
+    }
+
+    tp = &cached_time[slot];
+
+    tp->sec = 0;
+
+    ngx_gmtime(sec + cached_gmtoff * 60, &tm);
+
+    p = &cached_err_log_time[slot][0];
+
+    (void) ngx_sprintf(p, "%4d/%02d/%02d %02d:%02d:%02d",
+                       tm.ngx_tm_year, tm.ngx_tm_mon,
+                       tm.ngx_tm_mday, tm.ngx_tm_hour,
+                       tm.ngx_tm_min, tm.ngx_tm_sec);
+
+    p2 = &cached_syslog_time[slot][0];
+
+    (void) ngx_sprintf(p2, "%s %2d %02d:%02d:%02d",
+                       months[tm.ngx_tm_mon - 1], tm.ngx_tm_mday,
+                       tm.ngx_tm_hour, tm.ngx_tm_min, tm.ngx_tm_sec);
+
+    ngx_memory_barrier();
+
+    ngx_cached_err_log_time.data = p;
+    ngx_cached_syslog_time.data = p2;
+
+    ngx_unlock(&ngx_time_lock);
+}
+
+#endif
+
+
+u_char *
+ngx_http_time(u_char *buf, time_t t)
+{
+    ngx_tm_t  tm;
+
+    ngx_gmtime(t, &tm);
+
+    return ngx_sprintf(buf, "%s, %02d %s %4d %02d:%02d:%02d GMT",
+                       week[tm.ngx_tm_wday],
+                       tm.ngx_tm_mday,
+                       months[tm.ngx_tm_mon - 1],
+                       tm.ngx_tm_year,
+                       tm.ngx_tm_hour,
+                       tm.ngx_tm_min,
+                       tm.ngx_tm_sec);
+}
+
+
+u_char *
+ngx_http_cookie_time(u_char *buf, time_t t)
+{
+    ngx_tm_t  tm;
+
+    ngx_gmtime(t, &tm);
+
+    /*
+     * Netscape 3.x does not understand 4-digit years at all and
+     * 2-digit years more than "37"
+     */
+
+    return ngx_sprintf(buf,
+                       (tm.ngx_tm_year > 2037) ?
+                                         "%s, %02d-%s-%d %02d:%02d:%02d GMT":
+                                         "%s, %02d-%s-%02d %02d:%02d:%02d GMT",
+                       week[tm.ngx_tm_wday],
+                       tm.ngx_tm_mday,
+                       months[tm.ngx_tm_mon - 1],
+                       (tm.ngx_tm_year > 2037) ? tm.ngx_tm_year:
+                                                 tm.ngx_tm_year % 100,
+                       tm.ngx_tm_hour,
+                       tm.ngx_tm_min,
+                       tm.ngx_tm_sec);
+}
+
+
+void
+ngx_gmtime(time_t t, ngx_tm_t *tp)
+{
+    ngx_int_t   yday;
+    ngx_uint_t  sec, min, hour, mday, mon, year, wday, days, leap;
+
+    /* the calculation is valid for positive time_t only */
+
+    if (t < 0) {
+        t = 0;
+    }
+
+    days = t / 86400;
+    sec = t % 86400;
+
+    /*
+     * no more than 4 year digits supported,
+     * truncate to December 31, 9999, 23:59:59
+     */
+
+    if (days > 2932896) {
+        days = 2932896;
+        sec = 86399;
+    }
+
+    /* January 1, 1970 was Thursday */
+
+    wday = (4 + days) % 7;
+
+    hour = sec / 3600;
+    sec %= 3600;
+    min = sec / 60;
+    sec %= 60;
+
+    /*
+     * the algorithm based on Gauss' formula,
+     * see src/core/ngx_parse_time.c
+     */
+
+    /* days since March 1, 1 BC */
+    days = days - (31 + 28) + 719527;
+
+    /*
+     * The "days" should be adjusted to 1 only, however, some March 1st's go
+     * to previous year, so we adjust them to 2.  This causes also shift of the
+     * last February days to next year, but we catch the case when "yday"
+     * becomes negative.
+     */
+
+    year = (days + 2) * 400 / (365 * 400 + 100 - 4 + 1);
+
+    yday = days - (365 * year + year / 4 - year / 100 + year / 400);
+
+    if (yday < 0) {
+        leap = (year % 4 == 0) && (year % 100 || (year % 400 == 0));
+        yday = 365 + leap + yday;
+        year--;
+    }
+
+    /*
+     * The empirical formula that maps "yday" to month.
+     * There are at least 10 variants, some of them are:
+     *     mon = (yday + 31) * 15 / 459
+     *     mon = (yday + 31) * 17 / 520
+     *     mon = (yday + 31) * 20 / 612
+     */
+
+    mon = (yday + 31) * 10 / 306;
+
+    /* the Gauss' formula that evaluates days before the month */
+
+    mday = yday - (367 * mon / 12 - 30) + 1;
+
+    if (yday >= 306) {
+
+        year++;
+        mon -= 10;
+
+        /*
+         * there is no "yday" in Win32 SYSTEMTIME
+         *
+         * yday -= 306;
+         */
+
+    } else {
+
+        mon += 2;
+
+        /*
+         * there is no "yday" in Win32 SYSTEMTIME
+         *
+         * yday += 31 + 28 + leap;
+         */
+    }
+
+    tp->ngx_tm_sec = (ngx_tm_sec_t) sec;
+    tp->ngx_tm_min = (ngx_tm_min_t) min;
+    tp->ngx_tm_hour = (ngx_tm_hour_t) hour;
+    tp->ngx_tm_mday = (ngx_tm_mday_t) mday;
+    tp->ngx_tm_mon = (ngx_tm_mon_t) mon;
+    tp->ngx_tm_year = (ngx_tm_year_t) year;
+    tp->ngx_tm_wday = (ngx_tm_wday_t) wday;
+}
+
+
+time_t
+ngx_next_time(time_t when)
+{
+    time_t     now, next;
+    struct tm  tm;
+
+    now = ngx_time();
+
+    ngx_libc_localtime(now, &tm);
+
+    tm.tm_hour = (int) (when / 3600);
+    when %= 3600;
+    tm.tm_min = (int) (when / 60);
+    tm.tm_sec = (int) (when % 60);
+
+    next = mktime(&tm);
+
+    if (next == -1) {
+        return -1;
+    }
+
+    if (next - now > 0) {
+        return next;
+    }
+
+    tm.tm_mday++;
+
+    /* mktime() should normalize a date (Jan 32, etc) */
+
+    next = mktime(&tm);
+
+    if (next != -1) {
+        return next;
+    }
+
+    return -1;
+}
diff --git a/nginx/src/core/ngx_times.h b/nginx/src/core/ngx_times.h
new file mode 100644 (file)
index 0000000..94aedcd
--- /dev/null
@@ -0,0 +1,52 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_TIMES_H_INCLUDED_
+#define _NGX_TIMES_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef struct {
+    time_t      sec;
+    ngx_uint_t  msec;
+    ngx_int_t   gmtoff;
+} ngx_time_t;
+
+
+void ngx_time_init(void);
+void ngx_time_update(void);
+void ngx_time_sigsafe_update(void);
+u_char *ngx_http_time(u_char *buf, time_t t);
+u_char *ngx_http_cookie_time(u_char *buf, time_t t);
+void ngx_gmtime(time_t t, ngx_tm_t *tp);
+
+time_t ngx_next_time(time_t when);
+#define ngx_next_time_n      "mktime()"
+
+
+extern volatile ngx_time_t  *ngx_cached_time;
+
+#define ngx_time()           ngx_cached_time->sec
+#define ngx_timeofday()      (ngx_time_t *) ngx_cached_time
+
+extern volatile ngx_str_t    ngx_cached_err_log_time;
+extern volatile ngx_str_t    ngx_cached_http_time;
+extern volatile ngx_str_t    ngx_cached_http_log_time;
+extern volatile ngx_str_t    ngx_cached_http_log_iso8601;
+extern volatile ngx_str_t    ngx_cached_syslog_time;
+
+/*
+ * milliseconds elapsed since epoch and truncated to ngx_msec_t,
+ * used in event timers
+ */
+extern volatile ngx_msec_t  ngx_current_msec;
+
+
+#endif /* _NGX_TIMES_H_INCLUDED_ */
diff --git a/nginx/src/event/modules/ngx_devpoll_module.c b/nginx/src/event/modules/ngx_devpoll_module.c
new file mode 100644 (file)
index 0000000..ee9f854
--- /dev/null
@@ -0,0 +1,560 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if (NGX_TEST_BUILD_DEVPOLL)
+
+/* Solaris declarations */
+
+#ifndef POLLREMOVE
+#define POLLREMOVE   0x0800
+#endif
+#define DP_POLL      0xD001
+#define DP_ISPOLLED  0xD002
+
+struct dvpoll {
+    struct pollfd  *dp_fds;
+    int             dp_nfds;
+    int             dp_timeout;
+};
+
+#endif
+
+
+typedef struct {
+    ngx_uint_t      changes;
+    ngx_uint_t      events;
+} ngx_devpoll_conf_t;
+
+
+static ngx_int_t ngx_devpoll_init(ngx_cycle_t *cycle, ngx_msec_t timer);
+static void ngx_devpoll_done(ngx_cycle_t *cycle);
+static ngx_int_t ngx_devpoll_add_event(ngx_event_t *ev, ngx_int_t event,
+    ngx_uint_t flags);
+static ngx_int_t ngx_devpoll_del_event(ngx_event_t *ev, ngx_int_t event,
+    ngx_uint_t flags);
+static ngx_int_t ngx_devpoll_set_event(ngx_event_t *ev, ngx_int_t event,
+    ngx_uint_t flags);
+static ngx_int_t ngx_devpoll_process_events(ngx_cycle_t *cycle,
+    ngx_msec_t timer, ngx_uint_t flags);
+
+static void *ngx_devpoll_create_conf(ngx_cycle_t *cycle);
+static char *ngx_devpoll_init_conf(ngx_cycle_t *cycle, void *conf);
+
+static int              dp = -1;
+static struct pollfd   *change_list, *event_list;
+static ngx_uint_t       nchanges, max_changes, nevents;
+
+static ngx_event_t    **change_index;
+
+
+static ngx_str_t      devpoll_name = ngx_string("/dev/poll");
+
+static ngx_command_t  ngx_devpoll_commands[] = {
+
+    { ngx_string("devpoll_changes"),
+      NGX_EVENT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      0,
+      offsetof(ngx_devpoll_conf_t, changes),
+      NULL },
+
+    { ngx_string("devpoll_events"),
+      NGX_EVENT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      0,
+      offsetof(ngx_devpoll_conf_t, events),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_event_module_t  ngx_devpoll_module_ctx = {
+    &devpoll_name,
+    ngx_devpoll_create_conf,               /* create configuration */
+    ngx_devpoll_init_conf,                 /* init configuration */
+
+    {
+        ngx_devpoll_add_event,             /* add an event */
+        ngx_devpoll_del_event,             /* delete an event */
+        ngx_devpoll_add_event,             /* enable an event */
+        ngx_devpoll_del_event,             /* disable an event */
+        NULL,                              /* add an connection */
+        NULL,                              /* delete an connection */
+        NULL,                              /* trigger a notify */
+        ngx_devpoll_process_events,        /* process the events */
+        ngx_devpoll_init,                  /* init the events */
+        ngx_devpoll_done,                  /* done the events */
+    }
+
+};
+
+ngx_module_t  ngx_devpoll_module = {
+    NGX_MODULE_V1,
+    &ngx_devpoll_module_ctx,               /* module context */
+    ngx_devpoll_commands,                  /* module directives */
+    NGX_EVENT_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_devpoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
+{
+    size_t               n;
+    ngx_devpoll_conf_t  *dpcf;
+
+    dpcf = ngx_event_get_conf(cycle->conf_ctx, ngx_devpoll_module);
+
+    if (dp == -1) {
+        dp = open("/dev/poll", O_RDWR);
+
+        if (dp == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          "open(/dev/poll) failed");
+            return NGX_ERROR;
+        }
+    }
+
+    if (max_changes < dpcf->changes) {
+        if (nchanges) {
+            n = nchanges * sizeof(struct pollfd);
+            if (write(dp, change_list, n) != (ssize_t) n) {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                              "write(/dev/poll) failed");
+                return NGX_ERROR;
+            }
+
+            nchanges = 0;
+        }
+
+        if (change_list) {
+            ngx_free(change_list);
+        }
+
+        change_list = ngx_alloc(sizeof(struct pollfd) * dpcf->changes,
+                                cycle->log);
+        if (change_list == NULL) {
+            return NGX_ERROR;
+        }
+
+        if (change_index) {
+            ngx_free(change_index);
+        }
+
+        change_index = ngx_alloc(sizeof(ngx_event_t *) * dpcf->changes,
+                                 cycle->log);
+        if (change_index == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    max_changes = dpcf->changes;
+
+    if (nevents < dpcf->events) {
+        if (event_list) {
+            ngx_free(event_list);
+        }
+
+        event_list = ngx_alloc(sizeof(struct pollfd) * dpcf->events,
+                               cycle->log);
+        if (event_list == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    nevents = dpcf->events;
+
+    ngx_io = ngx_os_io;
+
+    ngx_event_actions = ngx_devpoll_module_ctx.actions;
+
+    ngx_event_flags = NGX_USE_LEVEL_EVENT|NGX_USE_FD_EVENT;
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_devpoll_done(ngx_cycle_t *cycle)
+{
+    if (close(dp) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "close(/dev/poll) failed");
+    }
+
+    dp = -1;
+
+    ngx_free(change_list);
+    ngx_free(event_list);
+    ngx_free(change_index);
+
+    change_list = NULL;
+    event_list = NULL;
+    change_index = NULL;
+    max_changes = 0;
+    nchanges = 0;
+    nevents = 0;
+}
+
+
+static ngx_int_t
+ngx_devpoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+#if (NGX_DEBUG)
+    ngx_connection_t *c;
+#endif
+
+#if (NGX_READ_EVENT != POLLIN)
+    event = (event == NGX_READ_EVENT) ? POLLIN : POLLOUT;
+#endif
+
+#if (NGX_DEBUG)
+    c = ev->data;
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "devpoll add event: fd:%d ev:%04Xi", c->fd, event);
+#endif
+
+    ev->active = 1;
+
+    return ngx_devpoll_set_event(ev, event, 0);
+}
+
+
+static ngx_int_t
+ngx_devpoll_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+    ngx_event_t       *e;
+    ngx_connection_t  *c;
+
+    c = ev->data;
+
+#if (NGX_READ_EVENT != POLLIN)
+    event = (event == NGX_READ_EVENT) ? POLLIN : POLLOUT;
+#endif
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "devpoll del event: fd:%d ev:%04Xi", c->fd, event);
+
+    if (ngx_devpoll_set_event(ev, POLLREMOVE, flags) == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    ev->active = 0;
+
+    if (flags & NGX_CLOSE_EVENT) {
+        e = (event == POLLIN) ? c->write : c->read;
+
+        if (e) {
+            e->active = 0;
+        }
+
+        return NGX_OK;
+    }
+
+    /* restore the pair event if it exists */
+
+    if (event == POLLIN) {
+        e = c->write;
+        event = POLLOUT;
+
+    } else {
+        e = c->read;
+        event = POLLIN;
+    }
+
+    if (e && e->active) {
+        return ngx_devpoll_set_event(e, event, 0);
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_devpoll_set_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+    size_t             n;
+    ngx_connection_t  *c;
+
+    c = ev->data;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "devpoll fd:%d ev:%04Xi fl:%04Xi", c->fd, event, flags);
+
+    if (nchanges >= max_changes) {
+        ngx_log_error(NGX_LOG_WARN, ev->log, 0,
+                      "/dev/pool change list is filled up");
+
+        n = nchanges * sizeof(struct pollfd);
+        if (write(dp, change_list, n) != (ssize_t) n) {
+            ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+                          "write(/dev/poll) failed");
+            return NGX_ERROR;
+        }
+
+        nchanges = 0;
+    }
+
+    change_list[nchanges].fd = c->fd;
+    change_list[nchanges].events = (short) event;
+    change_list[nchanges].revents = 0;
+
+    change_index[nchanges] = ev;
+    ev->index = nchanges;
+
+    nchanges++;
+
+    if (flags & NGX_CLOSE_EVENT) {
+        n = nchanges * sizeof(struct pollfd);
+        if (write(dp, change_list, n) != (ssize_t) n) {
+            ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+                          "write(/dev/poll) failed");
+            return NGX_ERROR;
+        }
+
+        nchanges = 0;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_devpoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
+    ngx_uint_t flags)
+{
+    int                 events, revents, rc;
+    size_t              n;
+    ngx_fd_t            fd;
+    ngx_err_t           err;
+    ngx_int_t           i;
+    ngx_uint_t          level, instance;
+    ngx_event_t        *rev, *wev;
+    ngx_queue_t        *queue;
+    ngx_connection_t   *c;
+    struct pollfd       pfd;
+    struct dvpoll       dvp;
+
+    /* NGX_TIMER_INFINITE == INFTIM */
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "devpoll timer: %M", timer);
+
+    if (nchanges) {
+        n = nchanges * sizeof(struct pollfd);
+        if (write(dp, change_list, n) != (ssize_t) n) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "write(/dev/poll) failed");
+            return NGX_ERROR;
+        }
+
+        nchanges = 0;
+    }
+
+    dvp.dp_fds = event_list;
+    dvp.dp_nfds = (int) nevents;
+    dvp.dp_timeout = timer;
+    events = ioctl(dp, DP_POLL, &dvp);
+
+    err = (events == -1) ? ngx_errno : 0;
+
+    if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
+        ngx_time_update();
+    }
+
+    if (err) {
+        if (err == NGX_EINTR) {
+
+            if (ngx_event_timer_alarm) {
+                ngx_event_timer_alarm = 0;
+                return NGX_OK;
+            }
+
+            level = NGX_LOG_INFO;
+
+        } else {
+            level = NGX_LOG_ALERT;
+        }
+
+        ngx_log_error(level, cycle->log, err, "ioctl(DP_POLL) failed");
+        return NGX_ERROR;
+    }
+
+    if (events == 0) {
+        if (timer != NGX_TIMER_INFINITE) {
+            return NGX_OK;
+        }
+
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                      "ioctl(DP_POLL) returned no events without timeout");
+        return NGX_ERROR;
+    }
+
+    for (i = 0; i < events; i++) {
+
+        fd = event_list[i].fd;
+        revents = event_list[i].revents;
+
+        c = ngx_cycle->files[fd];
+
+        if (c == NULL || c->fd == -1) {
+
+            pfd.fd = fd;
+            pfd.events = 0;
+            pfd.revents = 0;
+
+            rc = ioctl(dp, DP_ISPOLLED, &pfd);
+
+            switch (rc) {
+
+            case -1:
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                    "ioctl(DP_ISPOLLED) failed for socket %d, event %04Xd",
+                    fd, revents);
+                break;
+
+            case 0:
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                    "phantom event %04Xd for closed and removed socket %d",
+                    revents, fd);
+                break;
+
+            default:
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                    "unexpected event %04Xd for closed and removed socket %d, "
+                    "ioctl(DP_ISPOLLED) returned rc:%d, fd:%d, event %04Xd",
+                    revents, fd, rc, pfd.fd, pfd.revents);
+
+                pfd.fd = fd;
+                pfd.events = POLLREMOVE;
+                pfd.revents = 0;
+
+                if (write(dp, &pfd, sizeof(struct pollfd))
+                    != (ssize_t) sizeof(struct pollfd))
+                {
+                    ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                                  "write(/dev/poll) for %d failed", fd);
+                }
+
+                if (close(fd) == -1) {
+                    ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                                  "close(%d) failed", fd);
+                }
+
+                break;
+            }
+
+            continue;
+        }
+
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "devpoll: fd:%d, ev:%04Xd, rev:%04Xd",
+                       fd, event_list[i].events, revents);
+
+        if (revents & (POLLERR|POLLHUP|POLLNVAL)) {
+            ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                          "ioctl(DP_POLL) error fd:%d ev:%04Xd rev:%04Xd",
+                          fd, event_list[i].events, revents);
+        }
+
+        if (revents & ~(POLLIN|POLLOUT|POLLERR|POLLHUP|POLLNVAL)) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                          "strange ioctl(DP_POLL) events "
+                          "fd:%d ev:%04Xd rev:%04Xd",
+                          fd, event_list[i].events, revents);
+        }
+
+        if (revents & (POLLERR|POLLHUP|POLLNVAL)) {
+
+            /*
+             * if the error events were returned, add POLLIN and POLLOUT
+             * to handle the events at least in one active handler
+             */
+
+            revents |= POLLIN|POLLOUT;
+        }
+
+        rev = c->read;
+
+        if ((revents & POLLIN) && rev->active) {
+            rev->ready = 1;
+
+            if (flags & NGX_POST_EVENTS) {
+                queue = rev->accept ? &ngx_posted_accept_events
+                                    : &ngx_posted_events;
+
+                ngx_post_event(rev, queue);
+
+            } else {
+                instance = rev->instance;
+
+                rev->handler(rev);
+
+                if (c->fd == -1 || rev->instance != instance) {
+                    continue;
+                }
+            }
+        }
+
+        wev = c->write;
+
+        if ((revents & POLLOUT) && wev->active) {
+            wev->ready = 1;
+
+            if (flags & NGX_POST_EVENTS) {
+                ngx_post_event(wev, &ngx_posted_events);
+
+            } else {
+                wev->handler(wev);
+            }
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_devpoll_create_conf(ngx_cycle_t *cycle)
+{
+    ngx_devpoll_conf_t  *dpcf;
+
+    dpcf = ngx_palloc(cycle->pool, sizeof(ngx_devpoll_conf_t));
+    if (dpcf == NULL) {
+        return NULL;
+    }
+
+    dpcf->changes = NGX_CONF_UNSET;
+    dpcf->events = NGX_CONF_UNSET;
+
+    return dpcf;
+}
+
+
+static char *
+ngx_devpoll_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+    ngx_devpoll_conf_t *dpcf = conf;
+
+    ngx_conf_init_uint_value(dpcf->changes, 32);
+    ngx_conf_init_uint_value(dpcf->events, 32);
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/event/modules/ngx_epoll_module.c b/nginx/src/event/modules/ngx_epoll_module.c
new file mode 100644 (file)
index 0000000..2e320ec
--- /dev/null
@@ -0,0 +1,1052 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if (NGX_TEST_BUILD_EPOLL)
+
+/* epoll declarations */
+
+#define EPOLLIN        0x001
+#define EPOLLPRI       0x002
+#define EPOLLOUT       0x004
+#define EPOLLERR       0x008
+#define EPOLLHUP       0x010
+#define EPOLLRDNORM    0x040
+#define EPOLLRDBAND    0x080
+#define EPOLLWRNORM    0x100
+#define EPOLLWRBAND    0x200
+#define EPOLLMSG       0x400
+
+#define EPOLLRDHUP     0x2000
+
+#define EPOLLEXCLUSIVE 0x10000000
+#define EPOLLONESHOT   0x40000000
+#define EPOLLET        0x80000000
+
+#define EPOLL_CTL_ADD  1
+#define EPOLL_CTL_DEL  2
+#define EPOLL_CTL_MOD  3
+
+typedef union epoll_data {
+    void         *ptr;
+    int           fd;
+    uint32_t      u32;
+    uint64_t      u64;
+} epoll_data_t;
+
+struct epoll_event {
+    uint32_t      events;
+    epoll_data_t  data;
+};
+
+
+int epoll_create(int size);
+
+int epoll_create(int size)
+{
+    return -1;
+}
+
+
+int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
+
+int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
+{
+    return -1;
+}
+
+
+int epoll_wait(int epfd, struct epoll_event *events, int nevents, int timeout);
+
+int epoll_wait(int epfd, struct epoll_event *events, int nevents, int timeout)
+{
+    return -1;
+}
+
+#if (NGX_HAVE_EVENTFD)
+#define SYS_eventfd       323
+#endif
+
+#if (NGX_HAVE_FILE_AIO)
+
+#define SYS_io_setup      245
+#define SYS_io_destroy    246
+#define SYS_io_getevents  247
+
+typedef u_int  aio_context_t;
+
+struct io_event {
+    uint64_t  data;  /* the data field from the iocb */
+    uint64_t  obj;   /* what iocb this event came from */
+    int64_t   res;   /* result code for this event */
+    int64_t   res2;  /* secondary result */
+};
+
+
+#endif
+#endif /* NGX_TEST_BUILD_EPOLL */
+
+
+typedef struct {
+    ngx_uint_t  events;
+    ngx_uint_t  aio_requests;
+} ngx_epoll_conf_t;
+
+
+static ngx_int_t ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer);
+#if (NGX_HAVE_EVENTFD)
+static ngx_int_t ngx_epoll_notify_init(ngx_log_t *log);
+static void ngx_epoll_notify_handler(ngx_event_t *ev);
+#endif
+#if (NGX_HAVE_EPOLLRDHUP)
+static void ngx_epoll_test_rdhup(ngx_cycle_t *cycle);
+#endif
+static void ngx_epoll_done(ngx_cycle_t *cycle);
+static ngx_int_t ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event,
+    ngx_uint_t flags);
+static ngx_int_t ngx_epoll_del_event(ngx_event_t *ev, ngx_int_t event,
+    ngx_uint_t flags);
+static ngx_int_t ngx_epoll_add_connection(ngx_connection_t *c);
+static ngx_int_t ngx_epoll_del_connection(ngx_connection_t *c,
+    ngx_uint_t flags);
+#if (NGX_HAVE_EVENTFD)
+static ngx_int_t ngx_epoll_notify(ngx_event_handler_pt handler);
+#endif
+static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
+    ngx_uint_t flags);
+
+#if (NGX_HAVE_FILE_AIO)
+static void ngx_epoll_eventfd_handler(ngx_event_t *ev);
+#endif
+
+static void *ngx_epoll_create_conf(ngx_cycle_t *cycle);
+static char *ngx_epoll_init_conf(ngx_cycle_t *cycle, void *conf);
+
+static int                  ep = -1;
+static struct epoll_event  *event_list;
+static ngx_uint_t           nevents;
+
+#if (NGX_HAVE_EVENTFD)
+static int                  notify_fd = -1;
+static ngx_event_t          notify_event;
+static ngx_connection_t     notify_conn;
+#endif
+
+#if (NGX_HAVE_FILE_AIO)
+
+int                         ngx_eventfd = -1;
+aio_context_t               ngx_aio_ctx = 0;
+
+static ngx_event_t          ngx_eventfd_event;
+static ngx_connection_t     ngx_eventfd_conn;
+
+#endif
+
+#if (NGX_HAVE_EPOLLRDHUP)
+ngx_uint_t                  ngx_use_epoll_rdhup;
+#endif
+
+static ngx_str_t      epoll_name = ngx_string("epoll");
+
+static ngx_command_t  ngx_epoll_commands[] = {
+
+    { ngx_string("epoll_events"),
+      NGX_EVENT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      0,
+      offsetof(ngx_epoll_conf_t, events),
+      NULL },
+
+    { ngx_string("worker_aio_requests"),
+      NGX_EVENT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      0,
+      offsetof(ngx_epoll_conf_t, aio_requests),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_event_module_t  ngx_epoll_module_ctx = {
+    &epoll_name,
+    ngx_epoll_create_conf,               /* create configuration */
+    ngx_epoll_init_conf,                 /* init configuration */
+
+    {
+        ngx_epoll_add_event,             /* add an event */
+        ngx_epoll_del_event,             /* delete an event */
+        ngx_epoll_add_event,             /* enable an event */
+        ngx_epoll_del_event,             /* disable an event */
+        ngx_epoll_add_connection,        /* add an connection */
+        ngx_epoll_del_connection,        /* delete an connection */
+#if (NGX_HAVE_EVENTFD)
+        ngx_epoll_notify,                /* trigger a notify */
+#else
+        NULL,                            /* trigger a notify */
+#endif
+        ngx_epoll_process_events,        /* process the events */
+        ngx_epoll_init,                  /* init the events */
+        ngx_epoll_done,                  /* done the events */
+    }
+};
+
+ngx_module_t  ngx_epoll_module = {
+    NGX_MODULE_V1,
+    &ngx_epoll_module_ctx,               /* module context */
+    ngx_epoll_commands,                  /* module directives */
+    NGX_EVENT_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
+};
+
+
+#if (NGX_HAVE_FILE_AIO)
+
+/*
+ * We call io_setup(), io_destroy() io_submit(), and io_getevents() directly
+ * as syscalls instead of libaio usage, because the library header file
+ * supports eventfd() since 0.3.107 version only.
+ */
+
+static int
+io_setup(u_int nr_reqs, aio_context_t *ctx)
+{
+    return syscall(SYS_io_setup, nr_reqs, ctx);
+}
+
+
+static int
+io_destroy(aio_context_t ctx)
+{
+    return syscall(SYS_io_destroy, ctx);
+}
+
+
+static int
+io_getevents(aio_context_t ctx, long min_nr, long nr, struct io_event *events,
+    struct timespec *tmo)
+{
+    return syscall(SYS_io_getevents, ctx, min_nr, nr, events, tmo);
+}
+
+
+static void
+ngx_epoll_aio_init(ngx_cycle_t *cycle, ngx_epoll_conf_t *epcf)
+{
+    int                 n;
+    struct epoll_event  ee;
+
+#if (NGX_HAVE_SYS_EVENTFD_H)
+    ngx_eventfd = eventfd(0, 0);
+#else
+    ngx_eventfd = syscall(SYS_eventfd, 0);
+#endif
+
+    if (ngx_eventfd == -1) {
+        ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                      "eventfd() failed");
+        ngx_file_aio = 0;
+        return;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "eventfd: %d", ngx_eventfd);
+
+    n = 1;
+
+    if (ioctl(ngx_eventfd, FIONBIO, &n) == -1) {
+        ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                      "ioctl(eventfd, FIONBIO) failed");
+        goto failed;
+    }
+
+    if (io_setup(epcf->aio_requests, &ngx_aio_ctx) == -1) {
+        ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                      "io_setup() failed");
+        goto failed;
+    }
+
+    ngx_eventfd_event.data = &ngx_eventfd_conn;
+    ngx_eventfd_event.handler = ngx_epoll_eventfd_handler;
+    ngx_eventfd_event.log = cycle->log;
+    ngx_eventfd_event.active = 1;
+    ngx_eventfd_conn.fd = ngx_eventfd;
+    ngx_eventfd_conn.read = &ngx_eventfd_event;
+    ngx_eventfd_conn.log = cycle->log;
+
+    ee.events = EPOLLIN|EPOLLET;
+    ee.data.ptr = &ngx_eventfd_conn;
+
+    if (ngxvcl_kvfd_epoll_ctl(ep, EPOLL_CTL_ADD, ngx_eventfd, &ee) != -1) {
+        return;
+    }
+
+    ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                  "epoll_ctl(EPOLL_CTL_ADD, eventfd) failed");
+
+    if (io_destroy(ngx_aio_ctx) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "io_destroy() failed");
+    }
+
+failed:
+
+    if (close(ngx_eventfd) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "eventfd close() failed");
+    }
+
+    ngx_eventfd = -1;
+    ngx_aio_ctx = 0;
+    ngx_file_aio = 0;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
+{
+    ngx_epoll_conf_t  *epcf;
+
+    epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module);
+
+    if (ep == -1) {
+        ep = ngxvcl_epoll_create(cycle->connection_n / 2);
+
+        if (ep == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          "epoll_create() failed");
+            return NGX_ERROR;
+        }
+
+#if (NGX_HAVE_EVENTFD)
+        if (ngx_epoll_notify_init(cycle->log) != NGX_OK) {
+            ngx_epoll_module_ctx.actions.notify = NULL;
+        }
+#endif
+
+#if (NGX_HAVE_FILE_AIO)
+        ngx_epoll_aio_init(cycle, epcf);
+#endif
+
+#if (NGX_HAVE_EPOLLRDHUP)
+        ngx_epoll_test_rdhup(cycle);
+#endif
+    }
+
+    if (nevents < epcf->events) {
+        if (event_list) {
+            ngx_free(event_list);
+        }
+
+        event_list = ngx_alloc(sizeof(struct epoll_event) * epcf->events,
+                               cycle->log);
+        if (event_list == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    nevents = epcf->events;
+
+    ngx_io = ngx_os_io;
+
+    ngx_event_actions = ngx_epoll_module_ctx.actions;
+
+#if (NGX_HAVE_CLEAR_EVENT)
+    ngx_event_flags = NGX_USE_CLEAR_EVENT
+#else
+    ngx_event_flags = NGX_USE_LEVEL_EVENT
+#endif
+                      |NGX_USE_GREEDY_EVENT
+                      |NGX_USE_EPOLL_EVENT;
+
+    return NGX_OK;
+}
+
+
+#if (NGX_HAVE_EVENTFD)
+
+static ngx_int_t
+ngx_epoll_notify_init(ngx_log_t *log)
+{
+    struct epoll_event  ee;
+
+#if (NGX_HAVE_SYS_EVENTFD_H)
+    notify_fd = eventfd(0, 0);
+#else
+    notify_fd = syscall(SYS_eventfd, 0);
+#endif
+
+    if (notify_fd == -1) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "eventfd() failed");
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
+                   "notify eventfd: %d", notify_fd);
+
+    notify_event.handler = ngx_epoll_notify_handler;
+    notify_event.log = log;
+    notify_event.active = 1;
+
+    notify_conn.fd = notify_fd;
+    notify_conn.read = &notify_event;
+    notify_conn.log = log;
+
+    ee.events = EPOLLIN|EPOLLET;
+    ee.data.ptr = &notify_conn;
+
+    if (ngxvcl_kvfd_epoll_ctl(ep, EPOLL_CTL_ADD, notify_fd, &ee) == -1) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                      "epoll_ctl(EPOLL_CTL_ADD, eventfd) failed");
+
+        if (close(notify_fd) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                            "eventfd close() failed");
+        }
+
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_epoll_notify_handler(ngx_event_t *ev)
+{
+    ssize_t               n;
+    uint64_t              count;
+    ngx_err_t             err;
+    ngx_event_handler_pt  handler;
+
+    if (++ev->index == NGX_MAX_UINT32_VALUE) {
+        ev->index = 0;
+
+        n = read(notify_fd, &count, sizeof(uint64_t));
+
+        err = ngx_errno;
+
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                       "read() eventfd %d: %z count:%uL", notify_fd, n, count);
+
+        if ((size_t) n != sizeof(uint64_t)) {
+            ngx_log_error(NGX_LOG_ALERT, ev->log, err,
+                          "read() eventfd %d failed", notify_fd);
+        }
+    }
+
+    handler = ev->data;
+    handler(ev);
+}
+
+#endif
+
+
+#if (NGX_HAVE_EPOLLRDHUP)
+
+static void
+ngx_epoll_test_rdhup(ngx_cycle_t *cycle)
+{
+    int                 s[2], events;
+    struct epoll_event  ee;
+
+    if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "socketpair() failed");
+        return;
+    }
+
+    ee.events = EPOLLET|EPOLLIN|EPOLLRDHUP;
+
+    if (ngxvcl_kvfd_epoll_ctl(ep, EPOLL_CTL_ADD, s[0], &ee) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "epoll_ctl() failed");
+        goto failed;
+    }
+
+    if (close(s[1]) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "close() failed");
+        s[1] = -1;
+        goto failed;
+    }
+
+    s[1] = -1;
+
+    events = ngxvcl_kvfd_epoll_wait(ep, &ee, 1, 5000);
+
+    if (events == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "epoll_wait() failed");
+        goto failed;
+    }
+
+    if (events) {
+        ngx_use_epoll_rdhup = ee.events & EPOLLRDHUP;
+
+    } else {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, NGX_ETIMEDOUT,
+                      "epoll_wait() timed out");
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "testing the EPOLLRDHUP flag: %s",
+                   ngx_use_epoll_rdhup ? "success" : "fail");
+
+failed:
+
+    if (s[1] != -1 && close(s[1]) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "close() failed");
+    }
+
+    if (close(s[0]) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "close() failed");
+    }
+}
+
+#endif
+
+
+static void
+ngx_epoll_done(ngx_cycle_t *cycle)
+{
+    if (ngxvcl_close(ep) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "epoll close() failed");
+    }
+
+    ep = -1;
+
+#if (NGX_HAVE_EVENTFD)
+
+    if (close(notify_fd) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "eventfd close() failed");
+    }
+
+    notify_fd = -1;
+
+#endif
+
+#if (NGX_HAVE_FILE_AIO)
+
+    if (ngx_eventfd != -1) {
+
+        if (io_destroy(ngx_aio_ctx) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "io_destroy() failed");
+        }
+
+        if (close(ngx_eventfd) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "eventfd close() failed");
+        }
+
+        ngx_eventfd = -1;
+    }
+
+    ngx_aio_ctx = 0;
+
+#endif
+
+    ngx_free(event_list);
+
+    event_list = NULL;
+    nevents = 0;
+}
+
+
+static ngx_int_t
+ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+    int                  op;
+    uint32_t             events, prev;
+    ngx_event_t         *e;
+    ngx_connection_t    *c;
+    struct epoll_event   ee;
+
+    c = ev->data;
+
+    events = (uint32_t) event;
+
+    if (event == NGX_READ_EVENT) {
+        e = c->write;
+        prev = EPOLLOUT;
+#if (NGX_READ_EVENT != EPOLLIN|EPOLLRDHUP)
+        events = EPOLLIN|EPOLLRDHUP;
+#endif
+
+    } else {
+        e = c->read;
+        prev = EPOLLIN|EPOLLRDHUP;
+#if (NGX_WRITE_EVENT != EPOLLOUT)
+        events = EPOLLOUT;
+#endif
+    }
+
+    if (e->active) {
+        op = EPOLL_CTL_MOD;
+        events |= prev;
+
+    } else {
+        op = EPOLL_CTL_ADD;
+    }
+
+#if (NGX_HAVE_EPOLLEXCLUSIVE && NGX_HAVE_EPOLLRDHUP)
+    if (flags & NGX_EXCLUSIVE_EVENT) {
+        events &= ~EPOLLRDHUP;
+    }
+#endif
+
+    ee.events = events | (uint32_t) flags;
+    ee.data.ptr = (void *) ((uintptr_t) c | ev->instance);
+
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "epoll add event: fd:%d op:%d ev:%08XD",
+                   c->fd, op, ee.events);
+
+    if (ngxvcl_kvfd_epoll_ctl(ep, op, c->fd, &ee) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+                      "epoll_ctl(%d, %d) failed", op, c->fd);
+        return NGX_ERROR;
+    }
+
+    ev->active = 1;
+#if 0
+    ev->oneshot = (flags & NGX_ONESHOT_EVENT) ? 1 : 0;
+#endif
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_epoll_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+    int                  op;
+    uint32_t             prev;
+    ngx_event_t         *e;
+    ngx_connection_t    *c;
+    struct epoll_event   ee;
+
+    /*
+     * when the file descriptor is closed, the epoll automatically deletes
+     * it from its queue, so we do not need to delete explicitly the event
+     * before the closing the file descriptor
+     */
+
+    if (flags & NGX_CLOSE_EVENT) {
+        ev->active = 0;
+        return NGX_OK;
+    }
+
+    c = ev->data;
+
+    if (event == NGX_READ_EVENT) {
+        e = c->write;
+        prev = EPOLLOUT;
+
+    } else {
+        e = c->read;
+        prev = EPOLLIN|EPOLLRDHUP;
+    }
+
+    if (e->active) {
+        op = EPOLL_CTL_MOD;
+        ee.events = prev | (uint32_t) flags;
+        ee.data.ptr = (void *) ((uintptr_t) c | ev->instance);
+
+    } else {
+        op = EPOLL_CTL_DEL;
+        ee.events = 0;
+        ee.data.ptr = NULL;
+    }
+
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "epoll del event: fd:%d op:%d ev:%08XD",
+                   c->fd, op, ee.events);
+
+    if (ngxvcl_kvfd_epoll_ctl(ep, op, c->fd, &ee) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+                      "epoll_ctl(%d, %d) failed", op, c->fd);
+        return NGX_ERROR;
+    }
+
+    ev->active = 0;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_epoll_add_connection(ngx_connection_t *c)
+{
+    struct epoll_event  ee;
+
+    ee.events = EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP;
+    ee.data.ptr = (void *) ((uintptr_t) c | c->read->instance);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "epoll add connection: fd:%d ev:%08XD", c->fd, ee.events);
+
+    if (ngxvcl_kvfd_epoll_ctl(ep, EPOLL_CTL_ADD, c->fd, &ee) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+                      "epoll_ctl(EPOLL_CTL_ADD, %d) failed", c->fd);
+        return NGX_ERROR;
+    }
+
+    c->read->active = 1;
+    c->write->active = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_epoll_del_connection(ngx_connection_t *c, ngx_uint_t flags)
+{
+    int                 op;
+    struct epoll_event  ee;
+
+    /*
+     * when the file descriptor is closed the epoll automatically deletes
+     * it from its queue so we do not need to delete explicitly the event
+     * before the closing the file descriptor
+     */
+
+    if (flags & NGX_CLOSE_EVENT) {
+        c->read->active = 0;
+        c->write->active = 0;
+        return NGX_OK;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "epoll del connection: fd:%d", c->fd);
+
+    op = EPOLL_CTL_DEL;
+    ee.events = 0;
+    ee.data.ptr = NULL;
+
+    if (ngxvcl_kvfd_epoll_ctl(ep, op, c->fd, &ee) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+                      "epoll_ctl(%d, %d) failed", op, c->fd);
+        return NGX_ERROR;
+    }
+
+    c->read->active = 0;
+    c->write->active = 0;
+
+    return NGX_OK;
+}
+
+
+#if (NGX_HAVE_EVENTFD)
+
+static ngx_int_t
+ngx_epoll_notify(ngx_event_handler_pt handler)
+{
+    static uint64_t inc = 1;
+
+    notify_event.data = handler;
+
+    if ((size_t) write(notify_fd, &inc, sizeof(uint64_t)) != sizeof(uint64_t)) {
+        ngx_log_error(NGX_LOG_ALERT, notify_event.log, ngx_errno,
+                      "write() to eventfd %d failed", notify_fd);
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
+{
+    int                events;
+    uint32_t           revents;
+    ngx_int_t          instance, i;
+    ngx_uint_t         level;
+    ngx_err_t          err;
+    ngx_event_t       *rev, *wev;
+    ngx_queue_t       *queue;
+    ngx_connection_t  *c;
+
+    /* NGX_TIMER_INFINITE == INFTIM */
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "epoll timer: %M", timer);
+
+    events = ngxvcl_kvfd_epoll_wait(ep, event_list, (int) nevents, timer);
+
+    err = (events == -1) ? ngx_errno : 0;
+
+    if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
+        ngx_time_update();
+    }
+
+    if (err) {
+        if (err == NGX_EINTR) {
+
+            if (ngx_event_timer_alarm) {
+                ngx_event_timer_alarm = 0;
+                return NGX_OK;
+            }
+
+            level = NGX_LOG_INFO;
+
+        } else {
+            level = NGX_LOG_ALERT;
+        }
+
+        ngx_log_error(level, cycle->log, err, "epoll_wait() failed");
+        return NGX_ERROR;
+    }
+
+    if (events == 0) {
+        if (timer != NGX_TIMER_INFINITE) {
+            return NGX_OK;
+        }
+
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                      "epoll_wait() returned no events without timeout");
+        return NGX_ERROR;
+    }
+
+    for (i = 0; i < events; i++) {
+        c = event_list[i].data.ptr;
+
+        instance = (uintptr_t) c & 1;
+        c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
+
+        rev = c->read;
+
+        if (c->fd == -1 || rev->instance != instance) {
+
+            /*
+             * the stale event from a file descriptor
+             * that was just closed in this iteration
+             */
+
+            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                           "epoll: stale event %p", c);
+            continue;
+        }
+
+        revents = event_list[i].events;
+
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "epoll: fd:%d ev:%04XD d:%p",
+                       c->fd, revents, event_list[i].data.ptr);
+
+        if (revents & (EPOLLERR|EPOLLHUP)) {
+            ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                           "epoll_wait() error on fd:%d ev:%04XD",
+                           c->fd, revents);
+
+            /*
+             * if the error events were returned, add EPOLLIN and EPOLLOUT
+             * to handle the events at least in one active handler
+             */
+
+            revents |= EPOLLIN|EPOLLOUT;
+        }
+
+#if 0
+        if (revents & ~(EPOLLIN|EPOLLOUT|EPOLLERR|EPOLLHUP)) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                          "strange epoll_wait() events fd:%d ev:%04XD",
+                          c->fd, revents);
+        }
+#endif
+
+        if ((revents & EPOLLIN) && rev->active) {
+
+#if (NGX_HAVE_EPOLLRDHUP)
+            if (revents & EPOLLRDHUP) {
+                rev->pending_eof = 1;
+            }
+
+            rev->available = 1;
+#endif
+
+            rev->ready = 1;
+
+            if (flags & NGX_POST_EVENTS) {
+                queue = rev->accept ? &ngx_posted_accept_events
+                                    : &ngx_posted_events;
+
+                ngx_post_event(rev, queue);
+
+            } else {
+                rev->handler(rev);
+            }
+        }
+
+        wev = c->write;
+
+        if ((revents & EPOLLOUT) && wev->active) {
+
+            if (c->fd == -1 || wev->instance != instance) {
+
+                /*
+                 * the stale event from a file descriptor
+                 * that was just closed in this iteration
+                 */
+
+                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                               "epoll: stale event %p", c);
+                continue;
+            }
+
+            wev->ready = 1;
+#if (NGX_THREADS)
+            wev->complete = 1;
+#endif
+
+            if (flags & NGX_POST_EVENTS) {
+                ngx_post_event(wev, &ngx_posted_events);
+
+            } else {
+                wev->handler(wev);
+            }
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+#if (NGX_HAVE_FILE_AIO)
+
+static void
+ngx_epoll_eventfd_handler(ngx_event_t *ev)
+{
+    int               n, events;
+    long              i;
+    uint64_t          ready;
+    ngx_err_t         err;
+    ngx_event_t      *e;
+    ngx_event_aio_t  *aio;
+    struct io_event   event[64];
+    struct timespec   ts;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "eventfd handler");
+
+    n = read(ngx_eventfd, &ready, 8);
+
+    err = ngx_errno;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0, "eventfd: %d", n);
+
+    if (n != 8) {
+        if (n == -1) {
+            if (err == NGX_EAGAIN) {
+                return;
+            }
+
+            ngx_log_error(NGX_LOG_ALERT, ev->log, err, "read(eventfd) failed");
+            return;
+        }
+
+        ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+                      "read(eventfd) returned only %d bytes", n);
+        return;
+    }
+
+    ts.tv_sec = 0;
+    ts.tv_nsec = 0;
+
+    while (ready) {
+
+        events = io_getevents(ngx_aio_ctx, 1, 64, event, &ts);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                       "io_getevents: %d", events);
+
+        if (events > 0) {
+            ready -= events;
+
+            for (i = 0; i < events; i++) {
+
+                ngx_log_debug4(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                               "io_event: %XL %XL %L %L",
+                                event[i].data, event[i].obj,
+                                event[i].res, event[i].res2);
+
+                e = (ngx_event_t *) (uintptr_t) event[i].data;
+
+                e->complete = 1;
+                e->active = 0;
+                e->ready = 1;
+
+                aio = e->data;
+                aio->res = event[i].res;
+
+                ngx_post_event(e, &ngx_posted_events);
+            }
+
+            continue;
+        }
+
+        if (events == 0) {
+            return;
+        }
+
+        /* events == -1 */
+        ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+                      "io_getevents() failed");
+        return;
+    }
+}
+
+#endif
+
+
+static void *
+ngx_epoll_create_conf(ngx_cycle_t *cycle)
+{
+    ngx_epoll_conf_t  *epcf;
+
+    epcf = ngx_palloc(cycle->pool, sizeof(ngx_epoll_conf_t));
+    if (epcf == NULL) {
+        return NULL;
+    }
+
+    epcf->events = NGX_CONF_UNSET;
+    epcf->aio_requests = NGX_CONF_UNSET;
+
+    return epcf;
+}
+
+
+static char *
+ngx_epoll_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+    ngx_epoll_conf_t *epcf = conf;
+
+    ngx_conf_init_uint_value(epcf->events, 512);
+    ngx_conf_init_uint_value(epcf->aio_requests, 32);
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/event/modules/ngx_eventport_module.c b/nginx/src/event/modules/ngx_eventport_module.c
new file mode 100644 (file)
index 0000000..01cfc97
--- /dev/null
@@ -0,0 +1,651 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if (NGX_TEST_BUILD_EVENTPORT)
+
+#define ushort_t  u_short
+#define uint_t    u_int
+
+#ifndef CLOCK_REALTIME
+#define CLOCK_REALTIME          0
+typedef int     clockid_t;
+typedef void *  timer_t;
+#elif (NGX_DARWIN)
+typedef void *  timer_t;
+#endif
+
+/* Solaris declarations */
+
+#define PORT_SOURCE_AIO         1
+#define PORT_SOURCE_TIMER       2
+#define PORT_SOURCE_USER        3
+#define PORT_SOURCE_FD          4
+#define PORT_SOURCE_ALERT       5
+#define PORT_SOURCE_MQ          6
+
+#ifndef ETIME
+#define ETIME                   64
+#endif
+
+#define SIGEV_PORT              4
+
+typedef struct {
+    int         portev_events;  /* event data is source specific */
+    ushort_t    portev_source;  /* event source */
+    ushort_t    portev_pad;     /* port internal use */
+    uintptr_t   portev_object;  /* source specific object */
+    void       *portev_user;    /* user cookie */
+} port_event_t;
+
+typedef struct  port_notify {
+    int         portnfy_port;   /* bind request(s) to port */
+    void       *portnfy_user;   /* user defined */
+} port_notify_t;
+
+#if (__FreeBSD__ && __FreeBSD_version < 700005) || (NGX_DARWIN)
+
+typedef struct itimerspec {     /* definition per POSIX.4 */
+    struct timespec it_interval;/* timer period */
+    struct timespec it_value;   /* timer expiration */
+} itimerspec_t;
+
+#endif
+
+int port_create(void);
+
+int port_create(void)
+{
+    return -1;
+}
+
+
+int port_associate(int port, int source, uintptr_t object, int events,
+    void *user);
+
+int port_associate(int port, int source, uintptr_t object, int events,
+    void *user)
+{
+    return -1;
+}
+
+
+int port_dissociate(int port, int source, uintptr_t object);
+
+int port_dissociate(int port, int source, uintptr_t object)
+{
+    return -1;
+}
+
+
+int port_getn(int port, port_event_t list[], uint_t max, uint_t *nget,
+    struct timespec *timeout);
+
+int port_getn(int port, port_event_t list[], uint_t max, uint_t *nget,
+    struct timespec *timeout)
+{
+    return -1;
+}
+
+int port_send(int port, int events, void *user);
+
+int port_send(int port, int events, void *user)
+{
+    return -1;
+}
+
+
+int timer_create(clockid_t clock_id, struct sigevent *evp, timer_t *timerid);
+
+int timer_create(clockid_t clock_id, struct sigevent *evp, timer_t *timerid)
+{
+    return -1;
+}
+
+
+int timer_settime(timer_t timerid, int flags, const struct itimerspec *value,
+    struct itimerspec *ovalue);
+
+int timer_settime(timer_t timerid, int flags, const struct itimerspec *value,
+    struct itimerspec *ovalue)
+{
+    return -1;
+}
+
+
+int timer_delete(timer_t timerid);
+
+int timer_delete(timer_t timerid)
+{
+    return -1;
+}
+
+#endif
+
+
+typedef struct {
+    ngx_uint_t  events;
+} ngx_eventport_conf_t;
+
+
+static ngx_int_t ngx_eventport_init(ngx_cycle_t *cycle, ngx_msec_t timer);
+static void ngx_eventport_done(ngx_cycle_t *cycle);
+static ngx_int_t ngx_eventport_add_event(ngx_event_t *ev, ngx_int_t event,
+    ngx_uint_t flags);
+static ngx_int_t ngx_eventport_del_event(ngx_event_t *ev, ngx_int_t event,
+    ngx_uint_t flags);
+static ngx_int_t ngx_eventport_notify(ngx_event_handler_pt handler);
+static ngx_int_t ngx_eventport_process_events(ngx_cycle_t *cycle,
+    ngx_msec_t timer, ngx_uint_t flags);
+
+static void *ngx_eventport_create_conf(ngx_cycle_t *cycle);
+static char *ngx_eventport_init_conf(ngx_cycle_t *cycle, void *conf);
+
+static int            ep = -1;
+static port_event_t  *event_list;
+static ngx_uint_t     nevents;
+static timer_t        event_timer = (timer_t) -1;
+static ngx_event_t    notify_event;
+
+static ngx_str_t      eventport_name = ngx_string("eventport");
+
+
+static ngx_command_t  ngx_eventport_commands[] = {
+
+    { ngx_string("eventport_events"),
+      NGX_EVENT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      0,
+      offsetof(ngx_eventport_conf_t, events),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_event_module_t  ngx_eventport_module_ctx = {
+    &eventport_name,
+    ngx_eventport_create_conf,             /* create configuration */
+    ngx_eventport_init_conf,               /* init configuration */
+
+    {
+        ngx_eventport_add_event,           /* add an event */
+        ngx_eventport_del_event,           /* delete an event */
+        ngx_eventport_add_event,           /* enable an event */
+        ngx_eventport_del_event,           /* disable an event */
+        NULL,                              /* add an connection */
+        NULL,                              /* delete an connection */
+        ngx_eventport_notify,              /* trigger a notify */
+        ngx_eventport_process_events,      /* process the events */
+        ngx_eventport_init,                /* init the events */
+        ngx_eventport_done,                /* done the events */
+    }
+
+};
+
+ngx_module_t  ngx_eventport_module = {
+    NGX_MODULE_V1,
+    &ngx_eventport_module_ctx,             /* module context */
+    ngx_eventport_commands,                /* module directives */
+    NGX_EVENT_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_eventport_init(ngx_cycle_t *cycle, ngx_msec_t timer)
+{
+    port_notify_t          pn;
+    struct itimerspec      its;
+    struct sigevent        sev;
+    ngx_eventport_conf_t  *epcf;
+
+    epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_eventport_module);
+
+    if (ep == -1) {
+        ep = port_create();
+
+        if (ep == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          "port_create() failed");
+            return NGX_ERROR;
+        }
+
+        notify_event.active = 1;
+        notify_event.log = cycle->log;
+    }
+
+    if (nevents < epcf->events) {
+        if (event_list) {
+            ngx_free(event_list);
+        }
+
+        event_list = ngx_alloc(sizeof(port_event_t) * epcf->events,
+                               cycle->log);
+        if (event_list == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    ngx_event_flags = NGX_USE_EVENTPORT_EVENT;
+
+    if (timer) {
+        ngx_memzero(&pn, sizeof(port_notify_t));
+        pn.portnfy_port = ep;
+
+        ngx_memzero(&sev, sizeof(struct sigevent));
+        sev.sigev_notify = SIGEV_PORT;
+#if !(NGX_TEST_BUILD_EVENTPORT)
+        sev.sigev_value.sival_ptr = &pn;
+#endif
+
+        if (timer_create(CLOCK_REALTIME, &sev, &event_timer) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          "timer_create() failed");
+            return NGX_ERROR;
+        }
+
+        its.it_interval.tv_sec = timer / 1000;
+        its.it_interval.tv_nsec = (timer % 1000) * 1000000;
+        its.it_value.tv_sec = timer / 1000;
+        its.it_value.tv_nsec = (timer % 1000) * 1000000;
+
+        if (timer_settime(event_timer, 0, &its, NULL) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          "timer_settime() failed");
+            return NGX_ERROR;
+        }
+
+        ngx_event_flags |= NGX_USE_TIMER_EVENT;
+    }
+
+    nevents = epcf->events;
+
+    ngx_io = ngx_os_io;
+
+    ngx_event_actions = ngx_eventport_module_ctx.actions;
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_eventport_done(ngx_cycle_t *cycle)
+{
+    if (event_timer != (timer_t) -1) {
+        if (timer_delete(event_timer) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "timer_delete() failed");
+        }
+
+        event_timer = (timer_t) -1;
+    }
+
+    if (close(ep) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "close() event port failed");
+    }
+
+    ep = -1;
+
+    ngx_free(event_list);
+
+    event_list = NULL;
+    nevents = 0;
+}
+
+
+static ngx_int_t
+ngx_eventport_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+    ngx_int_t          events, prev;
+    ngx_event_t       *e;
+    ngx_connection_t  *c;
+
+    c = ev->data;
+
+    events = event;
+
+    if (event == NGX_READ_EVENT) {
+        e = c->write;
+        prev = POLLOUT;
+#if (NGX_READ_EVENT != POLLIN)
+        events = POLLIN;
+#endif
+
+    } else {
+        e = c->read;
+        prev = POLLIN;
+#if (NGX_WRITE_EVENT != POLLOUT)
+        events = POLLOUT;
+#endif
+    }
+
+    if (e->oneshot) {
+        events |= prev;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "eventport add event: fd:%d ev:%04Xi", c->fd, events);
+
+    if (port_associate(ep, PORT_SOURCE_FD, c->fd, events,
+                       (void *) ((uintptr_t) ev | ev->instance))
+        == -1)
+    {
+        ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+                      "port_associate() failed");
+        return NGX_ERROR;
+    }
+
+    ev->active = 1;
+    ev->oneshot = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_eventport_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+    ngx_event_t       *e;
+    ngx_connection_t  *c;
+
+    /*
+     * when the file descriptor is closed, the event port automatically
+     * dissociates it from the port, so we do not need to dissociate explicitly
+     * the event before the closing the file descriptor
+     */
+
+    if (flags & NGX_CLOSE_EVENT) {
+        ev->active = 0;
+        ev->oneshot = 0;
+        return NGX_OK;
+    }
+
+    c = ev->data;
+
+    if (event == NGX_READ_EVENT) {
+        e = c->write;
+        event = POLLOUT;
+
+    } else {
+        e = c->read;
+        event = POLLIN;
+    }
+
+    if (e->oneshot) {
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                       "eventport change event: fd:%d ev:%04Xi", c->fd, event);
+
+        if (port_associate(ep, PORT_SOURCE_FD, c->fd, event,
+                           (void *) ((uintptr_t) ev | ev->instance))
+            == -1)
+        {
+            ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+                          "port_associate() failed");
+            return NGX_ERROR;
+        }
+
+    } else {
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                       "eventport del event: fd:%d", c->fd);
+
+        if (port_dissociate(ep, PORT_SOURCE_FD, c->fd) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+                          "port_dissociate() failed");
+            return NGX_ERROR;
+        }
+    }
+
+    ev->active = 0;
+    ev->oneshot = 0;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_eventport_notify(ngx_event_handler_pt handler)
+{
+    notify_event.handler = handler;
+
+    if (port_send(ep, 0, &notify_event) != 0) {
+        ngx_log_error(NGX_LOG_ALERT, notify_event.log, ngx_errno,
+                      "port_send() failed");
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_eventport_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
+    ngx_uint_t flags)
+{
+    int                 n, revents;
+    u_int               events;
+    ngx_err_t           err;
+    ngx_int_t           instance;
+    ngx_uint_t          i, level;
+    ngx_event_t        *ev, *rev, *wev;
+    ngx_queue_t        *queue;
+    ngx_connection_t   *c;
+    struct timespec     ts, *tp;
+
+    if (timer == NGX_TIMER_INFINITE) {
+        tp = NULL;
+
+    } else {
+        ts.tv_sec = timer / 1000;
+        ts.tv_nsec = (timer % 1000) * 1000000;
+        tp = &ts;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "eventport timer: %M", timer);
+
+    events = 1;
+
+    n = port_getn(ep, event_list, (u_int) nevents, &events, tp);
+
+    err = ngx_errno;
+
+    if (flags & NGX_UPDATE_TIME) {
+        ngx_time_update();
+    }
+
+    if (n == -1) {
+        if (err == ETIME) {
+            if (timer != NGX_TIMER_INFINITE) {
+                return NGX_OK;
+            }
+
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                          "port_getn() returned no events without timeout");
+            return NGX_ERROR;
+        }
+
+        level = (err == NGX_EINTR) ? NGX_LOG_INFO : NGX_LOG_ALERT;
+        ngx_log_error(level, cycle->log, err, "port_getn() failed");
+        return NGX_ERROR;
+    }
+
+    if (events == 0) {
+        if (timer != NGX_TIMER_INFINITE) {
+            return NGX_OK;
+        }
+
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                      "port_getn() returned no events without timeout");
+        return NGX_ERROR;
+    }
+
+    for (i = 0; i < events; i++) {
+
+        if (event_list[i].portev_source == PORT_SOURCE_TIMER) {
+            ngx_time_update();
+            continue;
+        }
+
+        ev = event_list[i].portev_user;
+
+        switch (event_list[i].portev_source) {
+
+        case PORT_SOURCE_FD:
+
+            instance = (uintptr_t) ev & 1;
+            ev = (ngx_event_t *) ((uintptr_t) ev & (uintptr_t) ~1);
+
+            if (ev->closed || ev->instance != instance) {
+
+                /*
+                 * the stale event from a file descriptor
+                 * that was just closed in this iteration
+                 */
+
+                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                               "eventport: stale event %p", ev);
+                continue;
+            }
+
+            revents = event_list[i].portev_events;
+
+            ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                           "eventport: fd:%d, ev:%04Xd",
+                           (int) event_list[i].portev_object, revents);
+
+            if (revents & (POLLERR|POLLHUP|POLLNVAL)) {
+                ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                               "port_getn() error fd:%d ev:%04Xd",
+                               (int) event_list[i].portev_object, revents);
+            }
+
+            if (revents & ~(POLLIN|POLLOUT|POLLERR|POLLHUP|POLLNVAL)) {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                              "strange port_getn() events fd:%d ev:%04Xd",
+                              (int) event_list[i].portev_object, revents);
+            }
+
+            if (revents & (POLLERR|POLLHUP|POLLNVAL)) {
+
+                /*
+                 * if the error events were returned, add POLLIN and POLLOUT
+                 * to handle the events at least in one active handler
+                 */
+
+                revents |= POLLIN|POLLOUT;
+            }
+
+            c = ev->data;
+            rev = c->read;
+            wev = c->write;
+
+            rev->active = 0;
+            wev->active = 0;
+
+            if (revents & POLLIN) {
+                rev->ready = 1;
+
+                if (flags & NGX_POST_EVENTS) {
+                    queue = rev->accept ? &ngx_posted_accept_events
+                                        : &ngx_posted_events;
+
+                    ngx_post_event(rev, queue);
+
+                } else {
+                    rev->handler(rev);
+
+                    if (ev->closed || ev->instance != instance) {
+                        continue;
+                    }
+                }
+
+                if (rev->accept) {
+                    if (ngx_use_accept_mutex) {
+                        ngx_accept_events = 1;
+                        continue;
+                    }
+
+                    if (port_associate(ep, PORT_SOURCE_FD, c->fd, POLLIN,
+                                       (void *) ((uintptr_t) ev | ev->instance))
+                        == -1)
+                    {
+                        ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+                                      "port_associate() failed");
+                        return NGX_ERROR;
+                    }
+                }
+            }
+
+            if (revents & POLLOUT) {
+                wev->ready = 1;
+
+                if (flags & NGX_POST_EVENTS) {
+                    ngx_post_event(wev, &ngx_posted_events);
+
+                } else {
+                    wev->handler(wev);
+                }
+            }
+
+            continue;
+
+        case PORT_SOURCE_USER:
+
+            ev->handler(ev);
+
+            continue;
+
+        default:
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                          "unexpected eventport object %d",
+                          (int) event_list[i].portev_object);
+            continue;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_eventport_create_conf(ngx_cycle_t *cycle)
+{
+    ngx_eventport_conf_t  *epcf;
+
+    epcf = ngx_palloc(cycle->pool, sizeof(ngx_eventport_conf_t));
+    if (epcf == NULL) {
+        return NULL;
+    }
+
+    epcf->events = NGX_CONF_UNSET;
+
+    return epcf;
+}
+
+
+static char *
+ngx_eventport_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+    ngx_eventport_conf_t *epcf = conf;
+
+    ngx_conf_init_uint_value(epcf->events, 32);
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/event/modules/ngx_kqueue_module.c b/nginx/src/event/modules/ngx_kqueue_module.c
new file mode 100644 (file)
index 0000000..9c7244c
--- /dev/null
@@ -0,0 +1,722 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+typedef struct {
+    ngx_uint_t  changes;
+    ngx_uint_t  events;
+} ngx_kqueue_conf_t;
+
+
+static ngx_int_t ngx_kqueue_init(ngx_cycle_t *cycle, ngx_msec_t timer);
+#ifdef EVFILT_USER
+static ngx_int_t ngx_kqueue_notify_init(ngx_log_t *log);
+#endif
+static void ngx_kqueue_done(ngx_cycle_t *cycle);
+static ngx_int_t ngx_kqueue_add_event(ngx_event_t *ev, ngx_int_t event,
+    ngx_uint_t flags);
+static ngx_int_t ngx_kqueue_del_event(ngx_event_t *ev, ngx_int_t event,
+    ngx_uint_t flags);
+static ngx_int_t ngx_kqueue_set_event(ngx_event_t *ev, ngx_int_t filter,
+    ngx_uint_t flags);
+#ifdef EVFILT_USER
+static ngx_int_t ngx_kqueue_notify(ngx_event_handler_pt handler);
+#endif
+static ngx_int_t ngx_kqueue_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
+    ngx_uint_t flags);
+static ngx_inline void ngx_kqueue_dump_event(ngx_log_t *log,
+    struct kevent *kev);
+
+static void *ngx_kqueue_create_conf(ngx_cycle_t *cycle);
+static char *ngx_kqueue_init_conf(ngx_cycle_t *cycle, void *conf);
+
+
+int                    ngx_kqueue = -1;
+
+static struct kevent  *change_list;
+static struct kevent  *event_list;
+static ngx_uint_t      max_changes, nchanges, nevents;
+
+#ifdef EVFILT_USER
+static ngx_event_t     notify_event;
+static struct kevent   notify_kev;
+#endif
+
+
+static ngx_str_t      kqueue_name = ngx_string("kqueue");
+
+static ngx_command_t  ngx_kqueue_commands[] = {
+
+    { ngx_string("kqueue_changes"),
+      NGX_EVENT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      0,
+      offsetof(ngx_kqueue_conf_t, changes),
+      NULL },
+
+    { ngx_string("kqueue_events"),
+      NGX_EVENT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      0,
+      offsetof(ngx_kqueue_conf_t, events),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_event_module_t  ngx_kqueue_module_ctx = {
+    &kqueue_name,
+    ngx_kqueue_create_conf,                /* create configuration */
+    ngx_kqueue_init_conf,                  /* init configuration */
+
+    {
+        ngx_kqueue_add_event,              /* add an event */
+        ngx_kqueue_del_event,              /* delete an event */
+        ngx_kqueue_add_event,              /* enable an event */
+        ngx_kqueue_del_event,              /* disable an event */
+        NULL,                              /* add an connection */
+        NULL,                              /* delete an connection */
+#ifdef EVFILT_USER
+        ngx_kqueue_notify,                 /* trigger a notify */
+#else
+        NULL,                              /* trigger a notify */
+#endif
+        ngx_kqueue_process_events,         /* process the events */
+        ngx_kqueue_init,                   /* init the events */
+        ngx_kqueue_done                    /* done the events */
+    }
+
+};
+
+ngx_module_t  ngx_kqueue_module = {
+    NGX_MODULE_V1,
+    &ngx_kqueue_module_ctx,                /* module context */
+    ngx_kqueue_commands,                   /* module directives */
+    NGX_EVENT_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_kqueue_init(ngx_cycle_t *cycle, ngx_msec_t timer)
+{
+    ngx_kqueue_conf_t  *kcf;
+    struct timespec     ts;
+#if (NGX_HAVE_TIMER_EVENT)
+    struct kevent       kev;
+#endif
+
+    kcf = ngx_event_get_conf(cycle->conf_ctx, ngx_kqueue_module);
+
+    if (ngx_kqueue == -1) {
+        ngx_kqueue = kqueue();
+
+        if (ngx_kqueue == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          "kqueue() failed");
+            return NGX_ERROR;
+        }
+
+#ifdef EVFILT_USER
+        if (ngx_kqueue_notify_init(cycle->log) != NGX_OK) {
+            return NGX_ERROR;
+        }
+#endif
+    }
+
+    if (max_changes < kcf->changes) {
+        if (nchanges) {
+            ts.tv_sec = 0;
+            ts.tv_nsec = 0;
+
+            if (kevent(ngx_kqueue, change_list, (int) nchanges, NULL, 0, &ts)
+                == -1)
+            {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                              "kevent() failed");
+                return NGX_ERROR;
+            }
+            nchanges = 0;
+        }
+
+        if (change_list) {
+            ngx_free(change_list);
+        }
+
+        change_list = ngx_alloc(kcf->changes * sizeof(struct kevent),
+                                cycle->log);
+        if (change_list == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    max_changes = kcf->changes;
+
+    if (nevents < kcf->events) {
+        if (event_list) {
+            ngx_free(event_list);
+        }
+
+        event_list = ngx_alloc(kcf->events * sizeof(struct kevent), cycle->log);
+        if (event_list == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    ngx_event_flags = NGX_USE_ONESHOT_EVENT
+                      |NGX_USE_KQUEUE_EVENT
+                      |NGX_USE_VNODE_EVENT;
+
+#if (NGX_HAVE_TIMER_EVENT)
+
+    if (timer) {
+        kev.ident = 0;
+        kev.filter = EVFILT_TIMER;
+        kev.flags = EV_ADD|EV_ENABLE;
+        kev.fflags = 0;
+        kev.data = timer;
+        kev.udata = 0;
+
+        ts.tv_sec = 0;
+        ts.tv_nsec = 0;
+
+        if (kevent(ngx_kqueue, &kev, 1, NULL, 0, &ts) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "kevent(EVFILT_TIMER) failed");
+            return NGX_ERROR;
+        }
+
+        ngx_event_flags |= NGX_USE_TIMER_EVENT;
+    }
+
+#endif
+
+#if (NGX_HAVE_CLEAR_EVENT)
+    ngx_event_flags |= NGX_USE_CLEAR_EVENT;
+#else
+    ngx_event_flags |= NGX_USE_LEVEL_EVENT;
+#endif
+
+#if (NGX_HAVE_LOWAT_EVENT)
+    ngx_event_flags |= NGX_USE_LOWAT_EVENT;
+#endif
+
+    nevents = kcf->events;
+
+    ngx_io = ngx_os_io;
+
+    ngx_event_actions = ngx_kqueue_module_ctx.actions;
+
+    return NGX_OK;
+}
+
+
+#ifdef EVFILT_USER
+
+static ngx_int_t
+ngx_kqueue_notify_init(ngx_log_t *log)
+{
+    notify_kev.ident = 0;
+    notify_kev.filter = EVFILT_USER;
+    notify_kev.data = 0;
+    notify_kev.flags = EV_ADD|EV_CLEAR;
+    notify_kev.fflags = 0;
+    notify_kev.udata = 0;
+
+    if (kevent(ngx_kqueue, &notify_kev, 1, NULL, 0, NULL) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "kevent(EVFILT_USER, EV_ADD) failed");
+        return NGX_ERROR;
+    }
+
+    notify_event.active = 1;
+    notify_event.log = log;
+
+    notify_kev.flags = 0;
+    notify_kev.fflags = NOTE_TRIGGER;
+    notify_kev.udata = NGX_KQUEUE_UDATA_T ((uintptr_t) &notify_event);
+
+    return NGX_OK;
+}
+
+#endif
+
+
+static void
+ngx_kqueue_done(ngx_cycle_t *cycle)
+{
+    if (close(ngx_kqueue) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "kqueue close() failed");
+    }
+
+    ngx_kqueue = -1;
+
+    ngx_free(change_list);
+    ngx_free(event_list);
+
+    change_list = NULL;
+    event_list = NULL;
+    max_changes = 0;
+    nchanges = 0;
+    nevents = 0;
+}
+
+
+static ngx_int_t
+ngx_kqueue_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+    ngx_int_t          rc;
+#if 0
+    ngx_event_t       *e;
+    ngx_connection_t  *c;
+#endif
+
+    ev->active = 1;
+    ev->disabled = 0;
+    ev->oneshot = (flags & NGX_ONESHOT_EVENT) ? 1 : 0;
+
+#if 0
+
+    if (ev->index < nchanges
+        && ((uintptr_t) change_list[ev->index].udata & (uintptr_t) ~1)
+            == (uintptr_t) ev)
+    {
+        if (change_list[ev->index].flags == EV_DISABLE) {
+
+            /*
+             * if the EV_DISABLE is still not passed to a kernel
+             * we will not pass it
+             */
+
+            ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                           "kevent activated: %d: ft:%i",
+                           ngx_event_ident(ev->data), event);
+
+            if (ev->index < --nchanges) {
+                e = (ngx_event_t *)
+                    ((uintptr_t) change_list[nchanges].udata & (uintptr_t) ~1);
+                change_list[ev->index] = change_list[nchanges];
+                e->index = ev->index;
+            }
+
+            return NGX_OK;
+        }
+
+        c = ev->data;
+
+        ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+                      "previous event on #%d were not passed in kernel", c->fd);
+
+        return NGX_ERROR;
+    }
+
+#endif
+
+    rc = ngx_kqueue_set_event(ev, event, EV_ADD|EV_ENABLE|flags);
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_kqueue_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+    ngx_int_t     rc;
+    ngx_event_t  *e;
+
+    ev->active = 0;
+    ev->disabled = 0;
+
+    if (ev->index < nchanges
+        && ((uintptr_t) change_list[ev->index].udata & (uintptr_t) ~1)
+            == (uintptr_t) ev)
+    {
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                       "kevent deleted: %d: ft:%i",
+                       ngx_event_ident(ev->data), event);
+
+        /* if the event is still not passed to a kernel we will not pass it */
+
+        nchanges--;
+
+        if (ev->index < nchanges) {
+            e = (ngx_event_t *)
+                    ((uintptr_t) change_list[nchanges].udata & (uintptr_t) ~1);
+            change_list[ev->index] = change_list[nchanges];
+            e->index = ev->index;
+        }
+
+        return NGX_OK;
+    }
+
+    /*
+     * when the file descriptor is closed the kqueue automatically deletes
+     * its filters so we do not need to delete explicitly the event
+     * before the closing the file descriptor.
+     */
+
+    if (flags & NGX_CLOSE_EVENT) {
+        return NGX_OK;
+    }
+
+    if (flags & NGX_DISABLE_EVENT) {
+        ev->disabled = 1;
+
+    } else {
+        flags |= EV_DELETE;
+    }
+
+    rc = ngx_kqueue_set_event(ev, event, flags);
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_kqueue_set_event(ngx_event_t *ev, ngx_int_t filter, ngx_uint_t flags)
+{
+    struct kevent     *kev;
+    struct timespec    ts;
+    ngx_connection_t  *c;
+
+    c = ev->data;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "kevent set event: %d: ft:%i fl:%04Xi",
+                   c->fd, filter, flags);
+
+    if (nchanges >= max_changes) {
+        ngx_log_error(NGX_LOG_WARN, ev->log, 0,
+                      "kqueue change list is filled up");
+
+        ts.tv_sec = 0;
+        ts.tv_nsec = 0;
+
+        if (kevent(ngx_kqueue, change_list, (int) nchanges, NULL, 0, &ts)
+            == -1)
+        {
+            ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno, "kevent() failed");
+            return NGX_ERROR;
+        }
+
+        nchanges = 0;
+    }
+
+    kev = &change_list[nchanges];
+
+    kev->ident = c->fd;
+    kev->filter = (short) filter;
+    kev->flags = (u_short) flags;
+    kev->udata = NGX_KQUEUE_UDATA_T ((uintptr_t) ev | ev->instance);
+
+    if (filter == EVFILT_VNODE) {
+        kev->fflags = NOTE_DELETE|NOTE_WRITE|NOTE_EXTEND
+                                 |NOTE_ATTRIB|NOTE_RENAME
+#if (__FreeBSD__ == 4 && __FreeBSD_version >= 430000) \
+    || __FreeBSD_version >= 500018
+                                 |NOTE_REVOKE
+#endif
+                      ;
+        kev->data = 0;
+
+    } else {
+#if (NGX_HAVE_LOWAT_EVENT)
+        if (flags & NGX_LOWAT_EVENT) {
+            kev->fflags = NOTE_LOWAT;
+            kev->data = ev->available;
+
+        } else {
+            kev->fflags = 0;
+            kev->data = 0;
+        }
+#else
+        kev->fflags = 0;
+        kev->data = 0;
+#endif
+    }
+
+    ev->index = nchanges;
+    nchanges++;
+
+    if (flags & NGX_FLUSH_EVENT) {
+        ts.tv_sec = 0;
+        ts.tv_nsec = 0;
+
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "kevent flush");
+
+        if (kevent(ngx_kqueue, change_list, (int) nchanges, NULL, 0, &ts)
+            == -1)
+        {
+            ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno, "kevent() failed");
+            return NGX_ERROR;
+        }
+
+        nchanges = 0;
+    }
+
+    return NGX_OK;
+}
+
+
+#ifdef EVFILT_USER
+
+static ngx_int_t
+ngx_kqueue_notify(ngx_event_handler_pt handler)
+{
+    notify_event.handler = handler;
+
+    if (kevent(ngx_kqueue, &notify_kev, 1, NULL, 0, NULL) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, notify_event.log, ngx_errno,
+                      "kevent(EVFILT_USER, NOTE_TRIGGER) failed");
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_kqueue_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
+    ngx_uint_t flags)
+{
+    int               events, n;
+    ngx_int_t         i, instance;
+    ngx_uint_t        level;
+    ngx_err_t         err;
+    ngx_event_t      *ev;
+    ngx_queue_t      *queue;
+    struct timespec   ts, *tp;
+
+    n = (int) nchanges;
+    nchanges = 0;
+
+    if (timer == NGX_TIMER_INFINITE) {
+        tp = NULL;
+
+    } else {
+
+        ts.tv_sec = timer / 1000;
+        ts.tv_nsec = (timer % 1000) * 1000000;
+
+        /*
+         * 64-bit Darwin kernel has the bug: kernel level ts.tv_nsec is
+         * the int32_t while user level ts.tv_nsec is the long (64-bit),
+         * so on the big endian PowerPC all nanoseconds are lost.
+         */
+
+#if (NGX_DARWIN_KEVENT_BUG)
+        ts.tv_nsec <<= 32;
+#endif
+
+        tp = &ts;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "kevent timer: %M, changes: %d", timer, n);
+
+    events = kevent(ngx_kqueue, change_list, n, event_list, (int) nevents, tp);
+
+    err = (events == -1) ? ngx_errno : 0;
+
+    if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
+        ngx_time_update();
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "kevent events: %d", events);
+
+    if (err) {
+        if (err == NGX_EINTR) {
+
+            if (ngx_event_timer_alarm) {
+                ngx_event_timer_alarm = 0;
+                return NGX_OK;
+            }
+
+            level = NGX_LOG_INFO;
+
+        } else {
+            level = NGX_LOG_ALERT;
+        }
+
+        ngx_log_error(level, cycle->log, err, "kevent() failed");
+        return NGX_ERROR;
+    }
+
+    if (events == 0) {
+        if (timer != NGX_TIMER_INFINITE) {
+            return NGX_OK;
+        }
+
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                      "kevent() returned no events without timeout");
+        return NGX_ERROR;
+    }
+
+    for (i = 0; i < events; i++) {
+
+        ngx_kqueue_dump_event(cycle->log, &event_list[i]);
+
+        if (event_list[i].flags & EV_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, event_list[i].data,
+                          "kevent() error on %d filter:%d flags:%04Xd",
+                          (int) event_list[i].ident, event_list[i].filter,
+                          event_list[i].flags);
+            continue;
+        }
+
+#if (NGX_HAVE_TIMER_EVENT)
+
+        if (event_list[i].filter == EVFILT_TIMER) {
+            ngx_time_update();
+            continue;
+        }
+
+#endif
+
+        ev = (ngx_event_t *) event_list[i].udata;
+
+        switch (event_list[i].filter) {
+
+        case EVFILT_READ:
+        case EVFILT_WRITE:
+
+            instance = (uintptr_t) ev & 1;
+            ev = (ngx_event_t *) ((uintptr_t) ev & (uintptr_t) ~1);
+
+            if (ev->closed || ev->instance != instance) {
+
+                /*
+                 * the stale event from a file descriptor
+                 * that was just closed in this iteration
+                 */
+
+                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                               "kevent: stale event %p", ev);
+                continue;
+            }
+
+            if (ev->log && (ev->log->log_level & NGX_LOG_DEBUG_CONNECTION)) {
+                ngx_kqueue_dump_event(ev->log, &event_list[i]);
+            }
+
+            if (ev->oneshot) {
+                ev->active = 0;
+            }
+
+            ev->available = event_list[i].data;
+
+            if (event_list[i].flags & EV_EOF) {
+                ev->pending_eof = 1;
+                ev->kq_errno = event_list[i].fflags;
+            }
+
+            ev->ready = 1;
+
+            break;
+
+        case EVFILT_VNODE:
+            ev->kq_vnode = 1;
+
+            break;
+
+        case EVFILT_AIO:
+            ev->complete = 1;
+            ev->ready = 1;
+
+            break;
+
+#ifdef EVFILT_USER
+        case EVFILT_USER:
+            break;
+#endif
+
+        default:
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                          "unexpected kevent() filter %d",
+                          event_list[i].filter);
+            continue;
+        }
+
+        if (flags & NGX_POST_EVENTS) {
+            queue = ev->accept ? &ngx_posted_accept_events
+                               : &ngx_posted_events;
+
+            ngx_post_event(ev, queue);
+
+            continue;
+        }
+
+        ev->handler(ev);
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_inline void
+ngx_kqueue_dump_event(ngx_log_t *log, struct kevent *kev)
+{
+    if (kev->ident > 0x8000000 && kev->ident != (unsigned) -1) {
+        ngx_log_debug6(NGX_LOG_DEBUG_EVENT, log, 0,
+                       "kevent: %p: ft:%d fl:%04Xd ff:%08Xd d:%d ud:%p",
+                       (void *) kev->ident, kev->filter,
+                       kev->flags, kev->fflags,
+                       (int) kev->data, kev->udata);
+
+    } else {
+        ngx_log_debug6(NGX_LOG_DEBUG_EVENT, log, 0,
+                       "kevent: %d: ft:%d fl:%04Xd ff:%08Xd d:%d ud:%p",
+                       (int) kev->ident, kev->filter,
+                       kev->flags, kev->fflags,
+                       (int) kev->data, kev->udata);
+    }
+}
+
+
+static void *
+ngx_kqueue_create_conf(ngx_cycle_t *cycle)
+{
+    ngx_kqueue_conf_t  *kcf;
+
+    kcf = ngx_palloc(cycle->pool, sizeof(ngx_kqueue_conf_t));
+    if (kcf == NULL) {
+        return NULL;
+    }
+
+    kcf->changes = NGX_CONF_UNSET;
+    kcf->events = NGX_CONF_UNSET;
+
+    return kcf;
+}
+
+
+static char *
+ngx_kqueue_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+    ngx_kqueue_conf_t *kcf = conf;
+
+    ngx_conf_init_uint_value(kcf->changes, 512);
+    ngx_conf_init_uint_value(kcf->events, 512);
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/event/modules/ngx_poll_module.c b/nginx/src/event/modules/ngx_poll_module.c
new file mode 100644 (file)
index 0000000..4e03dab
--- /dev/null
@@ -0,0 +1,415 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+static ngx_int_t ngx_poll_init(ngx_cycle_t *cycle, ngx_msec_t timer);
+static void ngx_poll_done(ngx_cycle_t *cycle);
+static ngx_int_t ngx_poll_add_event(ngx_event_t *ev, ngx_int_t event,
+    ngx_uint_t flags);
+static ngx_int_t ngx_poll_del_event(ngx_event_t *ev, ngx_int_t event,
+    ngx_uint_t flags);
+static ngx_int_t ngx_poll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
+    ngx_uint_t flags);
+static char *ngx_poll_init_conf(ngx_cycle_t *cycle, void *conf);
+
+
+static struct pollfd  *event_list;
+static ngx_uint_t      nevents;
+
+
+static ngx_str_t           poll_name = ngx_string("poll");
+
+static ngx_event_module_t  ngx_poll_module_ctx = {
+    &poll_name,
+    NULL,                                  /* create configuration */
+    ngx_poll_init_conf,                    /* init configuration */
+
+    {
+        ngx_poll_add_event,                /* add an event */
+        ngx_poll_del_event,                /* delete an event */
+        ngx_poll_add_event,                /* enable an event */
+        ngx_poll_del_event,                /* disable an event */
+        NULL,                              /* add an connection */
+        NULL,                              /* delete an connection */
+        NULL,                              /* trigger a notify */
+        ngx_poll_process_events,           /* process the events */
+        ngx_poll_init,                     /* init the events */
+        ngx_poll_done                      /* done the events */
+    }
+
+};
+
+ngx_module_t  ngx_poll_module = {
+    NGX_MODULE_V1,
+    &ngx_poll_module_ctx,                  /* module context */
+    NULL,                                  /* module directives */
+    NGX_EVENT_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_poll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
+{
+    struct pollfd   *list;
+
+    if (event_list == NULL) {
+        nevents = 0;
+    }
+
+    if (ngx_process >= NGX_PROCESS_WORKER
+        || cycle->old_cycle == NULL
+        || cycle->old_cycle->connection_n < cycle->connection_n)
+    {
+        list = ngx_alloc(sizeof(struct pollfd) * cycle->connection_n,
+                         cycle->log);
+        if (list == NULL) {
+            return NGX_ERROR;
+        }
+
+        if (event_list) {
+            ngx_memcpy(list, event_list, sizeof(ngx_event_t *) * nevents);
+            ngx_free(event_list);
+        }
+
+        event_list = list;
+    }
+
+    ngx_io = ngx_os_io;
+
+    ngx_event_actions = ngx_poll_module_ctx.actions;
+
+    ngx_event_flags = NGX_USE_LEVEL_EVENT|NGX_USE_FD_EVENT;
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_poll_done(ngx_cycle_t *cycle)
+{
+    ngx_free(event_list);
+
+    event_list = NULL;
+}
+
+
+static ngx_int_t
+ngx_poll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+    ngx_event_t       *e;
+    ngx_connection_t  *c;
+
+    c = ev->data;
+
+    ev->active = 1;
+
+    if (ev->index != NGX_INVALID_INDEX) {
+        ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+                      "poll event fd:%d ev:%i is already set", c->fd, event);
+        return NGX_OK;
+    }
+
+    if (event == NGX_READ_EVENT) {
+        e = c->write;
+#if (NGX_READ_EVENT != POLLIN)
+        event = POLLIN;
+#endif
+
+    } else {
+        e = c->read;
+#if (NGX_WRITE_EVENT != POLLOUT)
+        event = POLLOUT;
+#endif
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "poll add event: fd:%d ev:%i", c->fd, event);
+
+    if (e == NULL || e->index == NGX_INVALID_INDEX) {
+        event_list[nevents].fd = c->fd;
+        event_list[nevents].events = (short) event;
+        event_list[nevents].revents = 0;
+
+        ev->index = nevents;
+        nevents++;
+
+    } else {
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                       "poll add index: %i", e->index);
+
+        event_list[e->index].events |= (short) event;
+        ev->index = e->index;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_poll_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+    ngx_event_t       *e;
+    ngx_connection_t  *c;
+
+    c = ev->data;
+
+    ev->active = 0;
+
+    if (ev->index == NGX_INVALID_INDEX) {
+        ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+                      "poll event fd:%d ev:%i is already deleted",
+                      c->fd, event);
+        return NGX_OK;
+    }
+
+    if (event == NGX_READ_EVENT) {
+        e = c->write;
+#if (NGX_READ_EVENT != POLLIN)
+        event = POLLIN;
+#endif
+
+    } else {
+        e = c->read;
+#if (NGX_WRITE_EVENT != POLLOUT)
+        event = POLLOUT;
+#endif
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "poll del event: fd:%d ev:%i", c->fd, event);
+
+    if (e == NULL || e->index == NGX_INVALID_INDEX) {
+        nevents--;
+
+        if (ev->index < nevents) {
+
+            ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                           "index: copy event %ui to %i", nevents, ev->index);
+
+            event_list[ev->index] = event_list[nevents];
+
+            c = ngx_cycle->files[event_list[nevents].fd];
+
+            if (c->fd == -1) {
+                ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+                              "unexpected last event");
+
+            } else {
+                if (c->read->index == nevents) {
+                    c->read->index = ev->index;
+                }
+
+                if (c->write->index == nevents) {
+                    c->write->index = ev->index;
+                }
+            }
+        }
+
+    } else {
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                       "poll del index: %i", e->index);
+
+        event_list[e->index].events &= (short) ~event;
+    }
+
+    ev->index = NGX_INVALID_INDEX;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_poll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
+{
+    int                 ready, revents;
+    ngx_err_t           err;
+    ngx_uint_t          i, found, level;
+    ngx_event_t        *ev;
+    ngx_queue_t        *queue;
+    ngx_connection_t   *c;
+
+    /* NGX_TIMER_INFINITE == INFTIM */
+
+#if (NGX_DEBUG0)
+    if (cycle->log->log_level & NGX_LOG_DEBUG_ALL) {
+        for (i = 0; i < nevents; i++) {
+            ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                           "poll: %ui: fd:%d ev:%04Xd",
+                           i, event_list[i].fd, event_list[i].events);
+        }
+    }
+#endif
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "poll timer: %M", timer);
+
+    ready = poll(event_list, (u_int) nevents, (int) timer);
+
+    err = (ready == -1) ? ngx_errno : 0;
+
+    if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
+        ngx_time_update();
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "poll ready %d of %ui", ready, nevents);
+
+    if (err) {
+        if (err == NGX_EINTR) {
+
+            if (ngx_event_timer_alarm) {
+                ngx_event_timer_alarm = 0;
+                return NGX_OK;
+            }
+
+            level = NGX_LOG_INFO;
+
+        } else {
+            level = NGX_LOG_ALERT;
+        }
+
+        ngx_log_error(level, cycle->log, err, "poll() failed");
+        return NGX_ERROR;
+    }
+
+    if (ready == 0) {
+        if (timer != NGX_TIMER_INFINITE) {
+            return NGX_OK;
+        }
+
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                      "poll() returned no events without timeout");
+        return NGX_ERROR;
+    }
+
+    for (i = 0; i < nevents && ready; i++) {
+
+        revents = event_list[i].revents;
+
+#if 1
+        ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "poll: %ui: fd:%d ev:%04Xd rev:%04Xd",
+                       i, event_list[i].fd, event_list[i].events, revents);
+#else
+        if (revents) {
+            ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                           "poll: %ui: fd:%d ev:%04Xd rev:%04Xd",
+                           i, event_list[i].fd, event_list[i].events, revents);
+        }
+#endif
+
+        if (revents & POLLNVAL) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                          "poll() error fd:%d ev:%04Xd rev:%04Xd",
+                          event_list[i].fd, event_list[i].events, revents);
+        }
+
+        if (revents & ~(POLLIN|POLLOUT|POLLERR|POLLHUP|POLLNVAL)) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                          "strange poll() events fd:%d ev:%04Xd rev:%04Xd",
+                          event_list[i].fd, event_list[i].events, revents);
+        }
+
+        if (event_list[i].fd == -1) {
+            /*
+             * the disabled event, a workaround for our possible bug,
+             * see the comment below
+             */
+            continue;
+        }
+
+        c = ngx_cycle->files[event_list[i].fd];
+
+        if (c->fd == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "unexpected event");
+
+            /*
+             * it is certainly our fault and it should be investigated,
+             * in the meantime we disable this event to avoid a CPU spinning
+             */
+
+            if (i == nevents - 1) {
+                nevents--;
+            } else {
+                event_list[i].fd = -1;
+            }
+
+            continue;
+        }
+
+        if (revents & (POLLERR|POLLHUP|POLLNVAL)) {
+
+            /*
+             * if the error events were returned, add POLLIN and POLLOUT
+             * to handle the events at least in one active handler
+             */
+
+            revents |= POLLIN|POLLOUT;
+        }
+
+        found = 0;
+
+        if ((revents & POLLIN) && c->read->active) {
+            found = 1;
+
+            ev = c->read;
+            ev->ready = 1;
+
+            queue = ev->accept ? &ngx_posted_accept_events
+                               : &ngx_posted_events;
+
+            ngx_post_event(ev, queue);
+        }
+
+        if ((revents & POLLOUT) && c->write->active) {
+            found = 1;
+
+            ev = c->write;
+            ev->ready = 1;
+
+            ngx_post_event(ev, &ngx_posted_events);
+        }
+
+        if (found) {
+            ready--;
+            continue;
+        }
+    }
+
+    if (ready != 0) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "poll ready != events");
+    }
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_poll_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+    ngx_event_conf_t  *ecf;
+
+    ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
+
+    if (ecf->use != ngx_poll_module.ctx_index) {
+        return NGX_CONF_OK;
+    }
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/event/modules/ngx_select_module.c b/nginx/src/event/modules/ngx_select_module.c
new file mode 100644 (file)
index 0000000..c76c915
--- /dev/null
@@ -0,0 +1,423 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+static ngx_int_t ngx_select_init(ngx_cycle_t *cycle, ngx_msec_t timer);
+static void ngx_select_done(ngx_cycle_t *cycle);
+static ngx_int_t ngx_select_add_event(ngx_event_t *ev, ngx_int_t event,
+    ngx_uint_t flags);
+static ngx_int_t ngx_select_del_event(ngx_event_t *ev, ngx_int_t event,
+    ngx_uint_t flags);
+static ngx_int_t ngx_select_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
+    ngx_uint_t flags);
+static void ngx_select_repair_fd_sets(ngx_cycle_t *cycle);
+static char *ngx_select_init_conf(ngx_cycle_t *cycle, void *conf);
+
+
+static fd_set         master_read_fd_set;
+static fd_set         master_write_fd_set;
+static fd_set         work_read_fd_set;
+static fd_set         work_write_fd_set;
+
+static ngx_int_t      max_fd;
+static ngx_uint_t     nevents;
+
+static ngx_event_t  **event_index;
+
+
+static ngx_str_t           select_name = ngx_string("select");
+
+static ngx_event_module_t  ngx_select_module_ctx = {
+    &select_name,
+    NULL,                                  /* create configuration */
+    ngx_select_init_conf,                  /* init configuration */
+
+    {
+        ngx_select_add_event,              /* add an event */
+        ngx_select_del_event,              /* delete an event */
+        ngx_select_add_event,              /* enable an event */
+        ngx_select_del_event,              /* disable an event */
+        NULL,                              /* add an connection */
+        NULL,                              /* delete an connection */
+        NULL,                              /* trigger a notify */
+        ngx_select_process_events,         /* process the events */
+        ngx_select_init,                   /* init the events */
+        ngx_select_done                    /* done the events */
+    }
+
+};
+
+ngx_module_t  ngx_select_module = {
+    NGX_MODULE_V1,
+    &ngx_select_module_ctx,                /* module context */
+    NULL,                                  /* module directives */
+    NGX_EVENT_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_select_init(ngx_cycle_t *cycle, ngx_msec_t timer)
+{
+    ngx_event_t  **index;
+
+    if (event_index == NULL) {
+        FD_ZERO(&master_read_fd_set);
+        FD_ZERO(&master_write_fd_set);
+        nevents = 0;
+    }
+
+    if (ngx_process >= NGX_PROCESS_WORKER
+        || cycle->old_cycle == NULL
+        || cycle->old_cycle->connection_n < cycle->connection_n)
+    {
+        index = ngx_alloc(sizeof(ngx_event_t *) * 2 * cycle->connection_n,
+                          cycle->log);
+        if (index == NULL) {
+            return NGX_ERROR;
+        }
+
+        if (event_index) {
+            ngx_memcpy(index, event_index, sizeof(ngx_event_t *) * nevents);
+            ngx_free(event_index);
+        }
+
+        event_index = index;
+    }
+
+    ngx_io = ngx_os_io;
+
+    ngx_event_actions = ngx_select_module_ctx.actions;
+
+    ngx_event_flags = NGX_USE_LEVEL_EVENT;
+
+    max_fd = -1;
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_select_done(ngx_cycle_t *cycle)
+{
+    ngx_free(event_index);
+
+    event_index = NULL;
+}
+
+
+static ngx_int_t
+ngx_select_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+    ngx_connection_t  *c;
+
+    c = ev->data;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "select add event fd:%d ev:%i", c->fd, event);
+
+    if (ev->index != NGX_INVALID_INDEX) {
+        ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+                      "select event fd:%d ev:%i is already set", c->fd, event);
+        return NGX_OK;
+    }
+
+    if ((event == NGX_READ_EVENT && ev->write)
+        || (event == NGX_WRITE_EVENT && !ev->write))
+    {
+        ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+                      "invalid select %s event fd:%d ev:%i",
+                      ev->write ? "write" : "read", c->fd, event);
+        return NGX_ERROR;
+    }
+
+    if (event == NGX_READ_EVENT) {
+        FD_SET(c->fd, &master_read_fd_set);
+
+    } else if (event == NGX_WRITE_EVENT) {
+        FD_SET(c->fd, &master_write_fd_set);
+    }
+
+    if (max_fd != -1 && max_fd < c->fd) {
+        max_fd = c->fd;
+    }
+
+    ev->active = 1;
+
+    event_index[nevents] = ev;
+    ev->index = nevents;
+    nevents++;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_select_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+    ngx_event_t       *e;
+    ngx_connection_t  *c;
+
+    c = ev->data;
+
+    ev->active = 0;
+
+    if (ev->index == NGX_INVALID_INDEX) {
+        return NGX_OK;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "select del event fd:%d ev:%i", c->fd, event);
+
+    if (event == NGX_READ_EVENT) {
+        FD_CLR(c->fd, &master_read_fd_set);
+
+    } else if (event == NGX_WRITE_EVENT) {
+        FD_CLR(c->fd, &master_write_fd_set);
+    }
+
+    if (max_fd == c->fd) {
+        max_fd = -1;
+    }
+
+    if (ev->index < --nevents) {
+        e = event_index[nevents];
+        event_index[ev->index] = e;
+        e->index = ev->index;
+    }
+
+    ev->index = NGX_INVALID_INDEX;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_select_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
+    ngx_uint_t flags)
+{
+    int                ready, nready;
+    ngx_err_t          err;
+    ngx_uint_t         i, found;
+    ngx_event_t       *ev;
+    ngx_queue_t       *queue;
+    struct timeval     tv, *tp;
+    ngx_connection_t  *c;
+
+    if (max_fd == -1) {
+        for (i = 0; i < nevents; i++) {
+            c = event_index[i]->data;
+            if (max_fd < c->fd) {
+                max_fd = c->fd;
+            }
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "change max_fd: %i", max_fd);
+    }
+
+#if (NGX_DEBUG)
+    if (cycle->log->log_level & NGX_LOG_DEBUG_ALL) {
+        for (i = 0; i < nevents; i++) {
+            ev = event_index[i];
+            c = ev->data;
+            ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                           "select event: fd:%d wr:%d", c->fd, ev->write);
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "max_fd: %i", max_fd);
+    }
+#endif
+
+    if (timer == NGX_TIMER_INFINITE) {
+        tp = NULL;
+
+    } else {
+        tv.tv_sec = (long) (timer / 1000);
+        tv.tv_usec = (long) ((timer % 1000) * 1000);
+        tp = &tv;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "select timer: %M", timer);
+
+    work_read_fd_set = master_read_fd_set;
+    work_write_fd_set = master_write_fd_set;
+
+    ready = select(max_fd + 1, &work_read_fd_set, &work_write_fd_set, NULL, tp);
+
+    err = (ready == -1) ? ngx_errno : 0;
+
+    if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
+        ngx_time_update();
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "select ready %d", ready);
+
+    if (err) {
+        ngx_uint_t  level;
+
+        if (err == NGX_EINTR) {
+
+            if (ngx_event_timer_alarm) {
+                ngx_event_timer_alarm = 0;
+                return NGX_OK;
+            }
+
+            level = NGX_LOG_INFO;
+
+        } else {
+            level = NGX_LOG_ALERT;
+        }
+
+        ngx_log_error(level, cycle->log, err, "select() failed");
+
+        if (err == NGX_EBADF) {
+            ngx_select_repair_fd_sets(cycle);
+        }
+
+        return NGX_ERROR;
+    }
+
+    if (ready == 0) {
+        if (timer != NGX_TIMER_INFINITE) {
+            return NGX_OK;
+        }
+
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                      "select() returned no events without timeout");
+        return NGX_ERROR;
+    }
+
+    nready = 0;
+
+    for (i = 0; i < nevents; i++) {
+        ev = event_index[i];
+        c = ev->data;
+        found = 0;
+
+        if (ev->write) {
+            if (FD_ISSET(c->fd, &work_write_fd_set)) {
+                found = 1;
+                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                               "select write %d", c->fd);
+            }
+
+        } else {
+            if (FD_ISSET(c->fd, &work_read_fd_set)) {
+                found = 1;
+                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                               "select read %d", c->fd);
+            }
+        }
+
+        if (found) {
+            ev->ready = 1;
+
+            queue = ev->accept ? &ngx_posted_accept_events
+                               : &ngx_posted_events;
+
+            ngx_post_event(ev, queue);
+
+            nready++;
+        }
+    }
+
+    if (ready != nready) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                      "select ready != events: %d:%d", ready, nready);
+
+        ngx_select_repair_fd_sets(cycle);
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_select_repair_fd_sets(ngx_cycle_t *cycle)
+{
+    int           n;
+    socklen_t     len;
+    ngx_err_t     err;
+    ngx_socket_t  s;
+
+    for (s = 0; s <= max_fd; s++) {
+
+        if (FD_ISSET(s, &master_read_fd_set) == 0) {
+            continue;
+        }
+
+        len = sizeof(int);
+
+        if (ngxvcl_kvfd_getsockopt(s, SOL_SOCKET, SO_TYPE, &n, &len) == -1) {
+            err = ngx_socket_errno;
+
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+                          "invalid descriptor #%d in read fd_set", s);
+
+            FD_CLR(s, &master_read_fd_set);
+        }
+    }
+
+    for (s = 0; s <= max_fd; s++) {
+
+        if (FD_ISSET(s, &master_write_fd_set) == 0) {
+            continue;
+        }
+
+        len = sizeof(int);
+
+        if (ngxvcl_kvfd_getsockopt(s, SOL_SOCKET, SO_TYPE, &n, &len) == -1) {
+            err = ngx_socket_errno;
+
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+                          "invalid descriptor #%d in write fd_set", s);
+
+            FD_CLR(s, &master_write_fd_set);
+        }
+    }
+
+    max_fd = -1;
+}
+
+
+static char *
+ngx_select_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+    ngx_event_conf_t  *ecf;
+
+    ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
+
+    if (ecf->use != ngx_select_module.ctx_index) {
+        return NGX_CONF_OK;
+    }
+
+    /* disable warning: the default FD_SETSIZE is 1024U in FreeBSD 5.x */
+
+    if (cycle->connection_n > FD_SETSIZE) {
+        ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+                      "the maximum number of files "
+                      "supported by select() is %ud", FD_SETSIZE);
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/event/modules/ngx_win32_select_module.c b/nginx/src/event/modules/ngx_win32_select_module.c
new file mode 100644 (file)
index 0000000..cb7febf
--- /dev/null
@@ -0,0 +1,398 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+static ngx_int_t ngx_select_init(ngx_cycle_t *cycle, ngx_msec_t timer);
+static void ngx_select_done(ngx_cycle_t *cycle);
+static ngx_int_t ngx_select_add_event(ngx_event_t *ev, ngx_int_t event,
+    ngx_uint_t flags);
+static ngx_int_t ngx_select_del_event(ngx_event_t *ev, ngx_int_t event,
+    ngx_uint_t flags);
+static ngx_int_t ngx_select_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
+    ngx_uint_t flags);
+static void ngx_select_repair_fd_sets(ngx_cycle_t *cycle);
+static char *ngx_select_init_conf(ngx_cycle_t *cycle, void *conf);
+
+
+static fd_set         master_read_fd_set;
+static fd_set         master_write_fd_set;
+static fd_set         work_read_fd_set;
+static fd_set         work_write_fd_set;
+
+static ngx_uint_t     max_read;
+static ngx_uint_t     max_write;
+static ngx_uint_t     nevents;
+
+static ngx_event_t  **event_index;
+
+
+static ngx_str_t           select_name = ngx_string("select");
+
+static ngx_event_module_t  ngx_select_module_ctx = {
+    &select_name,
+    NULL,                                  /* create configuration */
+    ngx_select_init_conf,                  /* init configuration */
+
+    {
+        ngx_select_add_event,              /* add an event */
+        ngx_select_del_event,              /* delete an event */
+        ngx_select_add_event,              /* enable an event */
+        ngx_select_del_event,              /* disable an event */
+        NULL,                              /* add an connection */
+        NULL,                              /* delete an connection */
+        NULL,                              /* trigger a notify */
+        ngx_select_process_events,         /* process the events */
+        ngx_select_init,                   /* init the events */
+        ngx_select_done                    /* done the events */
+    }
+
+};
+
+ngx_module_t  ngx_select_module = {
+    NGX_MODULE_V1,
+    &ngx_select_module_ctx,                /* module context */
+    NULL,                                  /* module directives */
+    NGX_EVENT_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_select_init(ngx_cycle_t *cycle, ngx_msec_t timer)
+{
+    ngx_event_t  **index;
+
+    if (event_index == NULL) {
+        FD_ZERO(&master_read_fd_set);
+        FD_ZERO(&master_write_fd_set);
+        nevents = 0;
+    }
+
+    if (ngx_process >= NGX_PROCESS_WORKER
+        || cycle->old_cycle == NULL
+        || cycle->old_cycle->connection_n < cycle->connection_n)
+    {
+        index = ngx_alloc(sizeof(ngx_event_t *) * 2 * cycle->connection_n,
+                          cycle->log);
+        if (index == NULL) {
+            return NGX_ERROR;
+        }
+
+        if (event_index) {
+            ngx_memcpy(index, event_index, sizeof(ngx_event_t *) * nevents);
+            ngx_free(event_index);
+        }
+
+        event_index = index;
+    }
+
+    ngx_io = ngx_os_io;
+
+    ngx_event_actions = ngx_select_module_ctx.actions;
+
+    ngx_event_flags = NGX_USE_LEVEL_EVENT;
+
+    max_read = 0;
+    max_write = 0;
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_select_done(ngx_cycle_t *cycle)
+{
+    ngx_free(event_index);
+
+    event_index = NULL;
+}
+
+
+static ngx_int_t
+ngx_select_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+    ngx_connection_t  *c;
+
+    c = ev->data;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "select add event fd:%d ev:%i", c->fd, event);
+
+    if (ev->index != NGX_INVALID_INDEX) {
+        ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+                      "select event fd:%d ev:%i is already set", c->fd, event);
+        return NGX_OK;
+    }
+
+    if ((event == NGX_READ_EVENT && ev->write)
+        || (event == NGX_WRITE_EVENT && !ev->write))
+    {
+        ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+                      "invalid select %s event fd:%d ev:%i",
+                      ev->write ? "write" : "read", c->fd, event);
+        return NGX_ERROR;
+    }
+
+    if ((event == NGX_READ_EVENT && max_read >= FD_SETSIZE)
+        || (event == NGX_WRITE_EVENT && max_write >= FD_SETSIZE))
+    {
+        ngx_log_error(NGX_LOG_ERR, ev->log, 0,
+                      "maximum number of descriptors "
+                      "supported by select() is %d", FD_SETSIZE);
+        return NGX_ERROR;
+    }
+
+    if (event == NGX_READ_EVENT) {
+        FD_SET(c->fd, &master_read_fd_set);
+        max_read++;
+
+    } else if (event == NGX_WRITE_EVENT) {
+        FD_SET(c->fd, &master_write_fd_set);
+        max_write++;
+    }
+
+    ev->active = 1;
+
+    event_index[nevents] = ev;
+    ev->index = nevents;
+    nevents++;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_select_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+    ngx_event_t       *e;
+    ngx_connection_t  *c;
+
+    c = ev->data;
+
+    ev->active = 0;
+
+    if (ev->index == NGX_INVALID_INDEX) {
+        return NGX_OK;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "select del event fd:%d ev:%i", c->fd, event);
+
+    if (event == NGX_READ_EVENT) {
+        FD_CLR(c->fd, &master_read_fd_set);
+        max_read--;
+
+    } else if (event == NGX_WRITE_EVENT) {
+        FD_CLR(c->fd, &master_write_fd_set);
+        max_write--;
+    }
+
+    if (ev->index < --nevents) {
+        e = event_index[nevents];
+        event_index[ev->index] = e;
+        e->index = ev->index;
+    }
+
+    ev->index = NGX_INVALID_INDEX;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_select_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
+    ngx_uint_t flags)
+{
+    int                ready, nready;
+    ngx_err_t          err;
+    ngx_uint_t         i, found;
+    ngx_event_t       *ev;
+    ngx_queue_t       *queue;
+    struct timeval     tv, *tp;
+    ngx_connection_t  *c;
+
+#if (NGX_DEBUG)
+    if (cycle->log->log_level & NGX_LOG_DEBUG_ALL) {
+        for (i = 0; i < nevents; i++) {
+            ev = event_index[i];
+            c = ev->data;
+            ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                           "select event: fd:%d wr:%d", c->fd, ev->write);
+        }
+    }
+#endif
+
+    if (timer == NGX_TIMER_INFINITE) {
+        tp = NULL;
+
+    } else {
+        tv.tv_sec = (long) (timer / 1000);
+        tv.tv_usec = (long) ((timer % 1000) * 1000);
+        tp = &tv;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "select timer: %M", timer);
+
+    work_read_fd_set = master_read_fd_set;
+    work_write_fd_set = master_write_fd_set;
+
+    if (max_read || max_write) {
+        ready = select(0, &work_read_fd_set, &work_write_fd_set, NULL, tp);
+
+    } else {
+
+        /*
+         * Winsock select() requires that at least one descriptor set must be
+         * be non-null, and any non-null descriptor set must contain at least
+         * one handle to a socket.  Otherwise select() returns WSAEINVAL.
+         */
+
+        ngx_msleep(timer);
+
+        ready = 0;
+    }
+
+    err = (ready == -1) ? ngx_socket_errno : 0;
+
+    if (flags & NGX_UPDATE_TIME) {
+        ngx_time_update();
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "select ready %d", ready);
+
+    if (err) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, err, "select() failed");
+
+        if (err == WSAENOTSOCK) {
+            ngx_select_repair_fd_sets(cycle);
+        }
+
+        return NGX_ERROR;
+    }
+
+    if (ready == 0) {
+        if (timer != NGX_TIMER_INFINITE) {
+            return NGX_OK;
+        }
+
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                      "select() returned no events without timeout");
+        return NGX_ERROR;
+    }
+
+    nready = 0;
+
+    for (i = 0; i < nevents; i++) {
+        ev = event_index[i];
+        c = ev->data;
+        found = 0;
+
+        if (ev->write) {
+            if (FD_ISSET(c->fd, &work_write_fd_set)) {
+                found = 1;
+                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                               "select write %d", c->fd);
+            }
+
+        } else {
+            if (FD_ISSET(c->fd, &work_read_fd_set)) {
+                found = 1;
+                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                               "select read %d", c->fd);
+            }
+        }
+
+        if (found) {
+            ev->ready = 1;
+
+            queue = ev->accept ? &ngx_posted_accept_events
+                               : &ngx_posted_events;
+
+            ngx_post_event(ev, queue);
+
+            nready++;
+        }
+    }
+
+    if (ready != nready) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                      "select ready != events: %d:%d", ready, nready);
+
+        ngx_select_repair_fd_sets(cycle);
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_select_repair_fd_sets(ngx_cycle_t *cycle)
+{
+    int           n;
+    u_int         i;
+    socklen_t     len;
+    ngx_err_t     err;
+    ngx_socket_t  s;
+
+    for (i = 0; i < master_read_fd_set.fd_count; i++) {
+
+        s = master_read_fd_set.fd_array[i];
+        len = sizeof(int);
+
+        if (ngxvcl_kvfd_getsockopt(s, SOL_SOCKET, SO_TYPE, (char *) &n, &len) == -1) {
+            err = ngx_socket_errno;
+
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+                          "invalid descriptor #%d in read fd_set", s);
+
+            FD_CLR(s, &master_read_fd_set);
+        }
+    }
+
+    for (i = 0; i < master_write_fd_set.fd_count; i++) {
+
+        s = master_write_fd_set.fd_array[i];
+        len = sizeof(int);
+
+        if (ngxvcl_kvfd_getsockopt(s, SOL_SOCKET, SO_TYPE, (char *) &n, &len) == -1) {
+            err = ngx_socket_errno;
+
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+                          "invalid descriptor #%d in write fd_set", s);
+
+            FD_CLR(s, &master_write_fd_set);
+        }
+    }
+}
+
+
+static char *
+ngx_select_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+    ngx_event_conf_t  *ecf;
+
+    ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
+
+    if (ecf->use != ngx_select_module.ctx_index) {
+        return NGX_CONF_OK;
+    }
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/event/ngx_event.c b/nginx/src/event/ngx_event.c
new file mode 100644 (file)
index 0000000..c9d3486
--- /dev/null
@@ -0,0 +1,1290 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#define DEFAULT_CONNECTIONS  512
+
+
+extern ngx_module_t ngx_kqueue_module;
+extern ngx_module_t ngx_eventport_module;
+extern ngx_module_t ngx_devpoll_module;
+extern ngx_module_t ngx_epoll_module;
+extern ngx_module_t ngx_select_module;
+
+
+static char *ngx_event_init_conf(ngx_cycle_t *cycle, void *conf);
+static ngx_int_t ngx_event_module_init(ngx_cycle_t *cycle);
+static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle);
+static char *ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+static char *ngx_event_connections(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_event_use(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_event_debug_connection(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+static void *ngx_event_core_create_conf(ngx_cycle_t *cycle);
+static char *ngx_event_core_init_conf(ngx_cycle_t *cycle, void *conf);
+
+
+static ngx_uint_t     ngx_timer_resolution;
+sig_atomic_t          ngx_event_timer_alarm;
+
+static ngx_uint_t     ngx_event_max_module;
+
+ngx_uint_t            ngx_event_flags;
+ngx_event_actions_t   ngx_event_actions;
+
+
+static ngx_atomic_t   connection_counter = 1;
+ngx_atomic_t         *ngx_connection_counter = &connection_counter;
+
+
+ngx_atomic_t         *ngx_accept_mutex_ptr;
+ngx_shmtx_t           ngx_accept_mutex;
+ngx_uint_t            ngx_use_accept_mutex;
+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;
+
+
+#if (NGX_STAT_STUB)
+
+static ngx_atomic_t   ngx_stat_accepted0;
+ngx_atomic_t         *ngx_stat_accepted = &ngx_stat_accepted0;
+static ngx_atomic_t   ngx_stat_handled0;
+ngx_atomic_t         *ngx_stat_handled = &ngx_stat_handled0;
+static ngx_atomic_t   ngx_stat_requests0;
+ngx_atomic_t         *ngx_stat_requests = &ngx_stat_requests0;
+static ngx_atomic_t   ngx_stat_active0;
+ngx_atomic_t         *ngx_stat_active = &ngx_stat_active0;
+static ngx_atomic_t   ngx_stat_reading0;
+ngx_atomic_t         *ngx_stat_reading = &ngx_stat_reading0;
+static ngx_atomic_t   ngx_stat_writing0;
+ngx_atomic_t         *ngx_stat_writing = &ngx_stat_writing0;
+static ngx_atomic_t   ngx_stat_waiting0;
+ngx_atomic_t         *ngx_stat_waiting = &ngx_stat_waiting0;
+
+#endif
+
+
+
+static ngx_command_t  ngx_events_commands[] = {
+
+    { ngx_string("events"),
+      NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+      ngx_events_block,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_core_module_t  ngx_events_module_ctx = {
+    ngx_string("events"),
+    NULL,
+    ngx_event_init_conf
+};
+
+
+ngx_module_t  ngx_events_module = {
+    NGX_MODULE_V1,
+    &ngx_events_module_ctx,                /* module context */
+    ngx_events_commands,                   /* module directives */
+    NGX_CORE_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_str_t  event_core_name = ngx_string("event_core");
+
+
+static ngx_command_t  ngx_event_core_commands[] = {
+
+    { ngx_string("worker_connections"),
+      NGX_EVENT_CONF|NGX_CONF_TAKE1,
+      ngx_event_connections,
+      0,
+      0,
+      NULL },
+
+    { ngx_string("use"),
+      NGX_EVENT_CONF|NGX_CONF_TAKE1,
+      ngx_event_use,
+      0,
+      0,
+      NULL },
+
+    { ngx_string("multi_accept"),
+      NGX_EVENT_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      0,
+      offsetof(ngx_event_conf_t, multi_accept),
+      NULL },
+
+    { ngx_string("accept_mutex"),
+      NGX_EVENT_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      0,
+      offsetof(ngx_event_conf_t, accept_mutex),
+      NULL },
+
+    { ngx_string("accept_mutex_delay"),
+      NGX_EVENT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      0,
+      offsetof(ngx_event_conf_t, accept_mutex_delay),
+      NULL },
+
+    { ngx_string("debug_connection"),
+      NGX_EVENT_CONF|NGX_CONF_TAKE1,
+      ngx_event_debug_connection,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_event_module_t  ngx_event_core_module_ctx = {
+    &event_core_name,
+    ngx_event_core_create_conf,            /* create configuration */
+    ngx_event_core_init_conf,              /* init configuration */
+
+    { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
+};
+
+
+ngx_module_t  ngx_event_core_module = {
+    NGX_MODULE_V1,
+    &ngx_event_core_module_ctx,            /* module context */
+    ngx_event_core_commands,               /* module directives */
+    NGX_EVENT_MODULE,                      /* module type */
+    NULL,                                  /* init master */
+    ngx_event_module_init,                 /* init module */
+    ngx_event_process_init,                /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+void
+ngx_process_events_and_timers(ngx_cycle_t *cycle)
+{
+    ngx_uint_t  flags;
+    ngx_msec_t  timer, delta;
+
+    if (ngx_timer_resolution) {
+        timer = NGX_TIMER_INFINITE;
+        flags = 0;
+
+    } else {
+        timer = ngx_event_find_timer();
+        flags = NGX_UPDATE_TIME;
+
+#if (NGX_WIN32)
+
+        /* handle signals from master in case of network inactivity */
+
+        if (timer == NGX_TIMER_INFINITE || timer > 500) {
+            timer = 500;
+        }
+
+#endif
+    }
+
+    if (ngx_use_accept_mutex) {
+        if (ngx_accept_disabled > 0) {
+            ngx_accept_disabled--;
+
+        } else {
+            if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
+                return;
+            }
+
+            if (ngx_accept_mutex_held) {
+                flags |= NGX_POST_EVENTS;
+
+            } else {
+                if (timer == NGX_TIMER_INFINITE
+                    || timer > ngx_accept_mutex_delay)
+                {
+                    timer = ngx_accept_mutex_delay;
+                }
+            }
+        }
+    }
+
+    delta = ngx_current_msec;
+
+    (void) ngx_process_events(cycle, timer, flags);
+
+    delta = ngx_current_msec - delta;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "timer delta: %M", delta);
+
+    ngx_event_process_posted(cycle, &ngx_posted_accept_events);
+
+    if (ngx_accept_mutex_held) {
+        ngx_shmtx_unlock(&ngx_accept_mutex);
+    }
+
+    if (delta) {
+        ngx_event_expire_timers();
+    }
+
+    ngx_event_process_posted(cycle, &ngx_posted_events);
+}
+
+
+ngx_int_t
+ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags)
+{
+    if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
+
+        /* kqueue, epoll */
+
+        if (!rev->active && !rev->ready) {
+            if (ngx_add_event(rev, NGX_READ_EVENT, NGX_CLEAR_EVENT)
+                == NGX_ERROR)
+            {
+                return NGX_ERROR;
+            }
+        }
+
+        return NGX_OK;
+
+    } else if (ngx_event_flags & NGX_USE_LEVEL_EVENT) {
+
+        /* select, poll, /dev/poll */
+
+        if (!rev->active && !rev->ready) {
+            if (ngx_add_event(rev, NGX_READ_EVENT, NGX_LEVEL_EVENT)
+                == NGX_ERROR)
+            {
+                return NGX_ERROR;
+            }
+
+            return NGX_OK;
+        }
+
+        if (rev->active && (rev->ready || (flags & NGX_CLOSE_EVENT))) {
+            if (ngx_del_event(rev, NGX_READ_EVENT, NGX_LEVEL_EVENT | flags)
+                == NGX_ERROR)
+            {
+                return NGX_ERROR;
+            }
+
+            return NGX_OK;
+        }
+
+    } else if (ngx_event_flags & NGX_USE_EVENTPORT_EVENT) {
+
+        /* event ports */
+
+        if (!rev->active && !rev->ready) {
+            if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            return NGX_OK;
+        }
+
+        if (rev->oneshot && !rev->ready) {
+            if (ngx_del_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            return NGX_OK;
+        }
+    }
+
+    /* iocp */
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_handle_write_event(ngx_event_t *wev, size_t lowat)
+{
+    ngx_connection_t  *c;
+
+    if (lowat) {
+        c = wev->data;
+
+        if (ngx_send_lowat(c, lowat) == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+    }
+
+    if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
+
+        /* kqueue, epoll */
+
+        if (!wev->active && !wev->ready) {
+            if (ngx_add_event(wev, NGX_WRITE_EVENT,
+                              NGX_CLEAR_EVENT | (lowat ? NGX_LOWAT_EVENT : 0))
+                == NGX_ERROR)
+            {
+                return NGX_ERROR;
+            }
+        }
+
+        return NGX_OK;
+
+    } else if (ngx_event_flags & NGX_USE_LEVEL_EVENT) {
+
+        /* select, poll, /dev/poll */
+
+        if (!wev->active && !wev->ready) {
+            if (ngx_add_event(wev, NGX_WRITE_EVENT, NGX_LEVEL_EVENT)
+                == NGX_ERROR)
+            {
+                return NGX_ERROR;
+            }
+
+            return NGX_OK;
+        }
+
+        if (wev->active && wev->ready) {
+            if (ngx_del_event(wev, NGX_WRITE_EVENT, NGX_LEVEL_EVENT)
+                == NGX_ERROR)
+            {
+                return NGX_ERROR;
+            }
+
+            return NGX_OK;
+        }
+
+    } else if (ngx_event_flags & NGX_USE_EVENTPORT_EVENT) {
+
+        /* event ports */
+
+        if (!wev->active && !wev->ready) {
+            if (ngx_add_event(wev, NGX_WRITE_EVENT, 0) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            return NGX_OK;
+        }
+
+        if (wev->oneshot && wev->ready) {
+            if (ngx_del_event(wev, NGX_WRITE_EVENT, 0) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            return NGX_OK;
+        }
+    }
+
+    /* iocp */
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_event_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+    if (ngx_get_conf(cycle->conf_ctx, ngx_events_module) == NULL) {
+        ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+                      "no \"events\" section in configuration");
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_event_module_init(ngx_cycle_t *cycle)
+{
+    void              ***cf;
+    u_char              *shared;
+    size_t               size, cl;
+    ngx_shm_t            shm;
+    ngx_time_t          *tp;
+    ngx_core_conf_t     *ccf;
+    ngx_event_conf_t    *ecf;
+
+    cf = ngx_get_conf(cycle->conf_ctx, ngx_events_module);
+    ecf = (*cf)[ngx_event_core_module.ctx_index];
+
+    if (!ngx_test_config && ngx_process <= NGX_PROCESS_MASTER) {
+        ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
+                      "using the \"%s\" event method", ecf->name);
+    }
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+    ngx_timer_resolution = ccf->timer_resolution;
+
+#if !(NGX_WIN32)
+    {
+    ngx_int_t      limit;
+    struct rlimit  rlmt;
+
+    if (getrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "getrlimit(RLIMIT_NOFILE) failed, ignored");
+
+    } else {
+        if (ecf->connections > (ngx_uint_t) rlmt.rlim_cur
+            && (ccf->rlimit_nofile == NGX_CONF_UNSET
+                || ecf->connections > (ngx_uint_t) ccf->rlimit_nofile))
+        {
+            limit = (ccf->rlimit_nofile == NGX_CONF_UNSET) ?
+                         (ngx_int_t) rlmt.rlim_cur : ccf->rlimit_nofile;
+
+            ngx_log_error(NGX_LOG_WARN, cycle->log, 0,
+                          "%ui worker_connections exceed "
+                          "open file resource limit: %i",
+                          ecf->connections, limit);
+        }
+    }
+    }
+#endif /* !(NGX_WIN32) */
+
+
+    if (ccf->master == 0) {
+        return NGX_OK;
+    }
+
+    if (ngx_accept_mutex_ptr) {
+        return NGX_OK;
+    }
+
+
+    /* cl should be equal to or greater than cache line size */
+
+    cl = 128;
+
+    size = cl            /* ngx_accept_mutex */
+           + cl          /* ngx_connection_counter */
+           + cl;         /* ngx_temp_number */
+
+#if (NGX_STAT_STUB)
+
+    size += cl           /* ngx_stat_accepted */
+           + cl          /* ngx_stat_handled */
+           + cl          /* ngx_stat_requests */
+           + cl          /* ngx_stat_active */
+           + cl          /* ngx_stat_reading */
+           + cl          /* ngx_stat_writing */
+           + cl;         /* ngx_stat_waiting */
+
+#endif
+
+    shm.size = size;
+    ngx_str_set(&shm.name, "nginx_shared_zone");
+    shm.log = cycle->log;
+
+    if (ngx_shm_alloc(&shm) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    shared = shm.addr;
+
+    ngx_accept_mutex_ptr = (ngx_atomic_t *) shared;
+    ngx_accept_mutex.spin = (ngx_uint_t) -1;
+
+    if (ngx_shmtx_create(&ngx_accept_mutex, (ngx_shmtx_sh_t *) shared,
+                         cycle->lock_file.data)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    ngx_connection_counter = (ngx_atomic_t *) (shared + 1 * cl);
+
+    (void) ngx_atomic_cmp_set(ngx_connection_counter, 0, 1);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "counter: %p, %uA",
+                   ngx_connection_counter, *ngx_connection_counter);
+
+    ngx_temp_number = (ngx_atomic_t *) (shared + 2 * cl);
+
+    tp = ngx_timeofday();
+
+    ngx_random_number = (tp->msec << 16) + ngx_pid;
+
+#if (NGX_STAT_STUB)
+
+    ngx_stat_accepted = (ngx_atomic_t *) (shared + 3 * cl);
+    ngx_stat_handled = (ngx_atomic_t *) (shared + 4 * cl);
+    ngx_stat_requests = (ngx_atomic_t *) (shared + 5 * cl);
+    ngx_stat_active = (ngx_atomic_t *) (shared + 6 * cl);
+    ngx_stat_reading = (ngx_atomic_t *) (shared + 7 * cl);
+    ngx_stat_writing = (ngx_atomic_t *) (shared + 8 * cl);
+    ngx_stat_waiting = (ngx_atomic_t *) (shared + 9 * cl);
+
+#endif
+
+    return NGX_OK;
+}
+
+
+#if !(NGX_WIN32)
+
+static void
+ngx_timer_signal_handler(int signo)
+{
+    ngx_event_timer_alarm = 1;
+
+#if 1
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, "timer signal");
+#endif
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_event_process_init(ngx_cycle_t *cycle)
+{
+    ngx_uint_t           m, i;
+    ngx_event_t         *rev, *wev;
+    ngx_listening_t     *ls;
+    ngx_connection_t    *c, *next, *old;
+    ngx_core_conf_t     *ccf;
+    ngx_event_conf_t    *ecf;
+    ngx_event_module_t  *module;
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+    ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
+
+    if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) {
+        ngx_use_accept_mutex = 1;
+        ngx_accept_mutex_held = 0;
+        ngx_accept_mutex_delay = ecf->accept_mutex_delay;
+
+    } else {
+        ngx_use_accept_mutex = 0;
+    }
+
+#if (NGX_WIN32)
+
+    /*
+     * disable accept mutex on win32 as it may cause deadlock if
+     * grabbed by a process which can't accept connections
+     */
+
+    ngx_use_accept_mutex = 0;
+
+#endif
+
+    ngx_queue_init(&ngx_posted_accept_events);
+    ngx_queue_init(&ngx_posted_events);
+
+    if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    for (m = 0; cycle->modules[m]; m++) {
+        if (cycle->modules[m]->type != NGX_EVENT_MODULE) {
+            continue;
+        }
+
+        if (cycle->modules[m]->ctx_index != ecf->use) {
+            continue;
+        }
+
+        module = cycle->modules[m]->ctx;
+
+        if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) {
+            /* fatal */
+            exit(2);
+        }
+
+        break;
+    }
+
+#if !(NGX_WIN32)
+
+    if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) {
+        struct sigaction  sa;
+        struct itimerval  itv;
+
+        ngx_memzero(&sa, sizeof(struct sigaction));
+        sa.sa_handler = ngx_timer_signal_handler;
+        sigemptyset(&sa.sa_mask);
+
+        if (sigaction(SIGALRM, &sa, NULL) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "sigaction(SIGALRM) failed");
+            return NGX_ERROR;
+        }
+
+        itv.it_interval.tv_sec = ngx_timer_resolution / 1000;
+        itv.it_interval.tv_usec = (ngx_timer_resolution % 1000) * 1000;
+        itv.it_value.tv_sec = ngx_timer_resolution / 1000;
+        itv.it_value.tv_usec = (ngx_timer_resolution % 1000 ) * 1000;
+
+        if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "setitimer() failed");
+        }
+    }
+
+    if (ngx_event_flags & NGX_USE_FD_EVENT) {
+        struct rlimit  rlmt;
+
+        if (getrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "getrlimit(RLIMIT_NOFILE) failed");
+            return NGX_ERROR;
+        }
+
+        cycle->files_n = (ngx_uint_t) rlmt.rlim_cur;
+
+        cycle->files = ngx_calloc(sizeof(ngx_connection_t *) * cycle->files_n,
+                                  cycle->log);
+        if (cycle->files == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+#else
+
+    if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) {
+        ngx_log_error(NGX_LOG_WARN, cycle->log, 0,
+                      "the \"timer_resolution\" directive is not supported "
+                      "with the configured event method, ignored");
+        ngx_timer_resolution = 0;
+    }
+
+#endif
+
+    cycle->connections =
+        ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
+    if (cycle->connections == NULL) {
+        return NGX_ERROR;
+    }
+
+    c = cycle->connections;
+
+    cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
+                                   cycle->log);
+    if (cycle->read_events == NULL) {
+        return NGX_ERROR;
+    }
+
+    rev = cycle->read_events;
+    for (i = 0; i < cycle->connection_n; i++) {
+        rev[i].closed = 1;
+        rev[i].instance = 1;
+    }
+
+    cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
+                                    cycle->log);
+    if (cycle->write_events == NULL) {
+        return NGX_ERROR;
+    }
+
+    wev = cycle->write_events;
+    for (i = 0; i < cycle->connection_n; i++) {
+        wev[i].closed = 1;
+    }
+
+    i = cycle->connection_n;
+    next = NULL;
+
+    do {
+        i--;
+
+        c[i].data = next;
+        c[i].read = &cycle->read_events[i];
+        c[i].write = &cycle->write_events[i];
+        c[i].fd = (ngx_socket_t) -1;
+
+        next = &c[i];
+    } while (i);
+
+    cycle->free_connections = next;
+    cycle->free_connection_n = cycle->connection_n;
+
+    /* for each listening socket */
+
+    ls = cycle->listening.elts;
+    for (i = 0; i < cycle->listening.nelts; i++) {
+
+#if (NGX_HAVE_REUSEPORT)
+        if (ls[i].reuseport && ls[i].worker != ngx_worker) {
+            continue;
+        }
+#endif
+
+        c = ngx_get_connection(ls[i].fd, cycle->log);
+
+        if (c == NULL) {
+            return NGX_ERROR;
+        }
+
+        c->type = ls[i].type;
+        c->log = &ls[i].log;
+
+        c->listening = &ls[i];
+        ls[i].connection = c;
+
+        rev = c->read;
+
+        rev->log = c->log;
+        rev->accept = 1;
+
+#if (NGX_HAVE_DEFERRED_ACCEPT)
+        rev->deferred_accept = ls[i].deferred_accept;
+#endif
+
+        if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) {
+            if (ls[i].previous) {
+
+                /*
+                 * delete the old accept events that were bound to
+                 * the old cycle read events array
+                 */
+
+                old = ls[i].previous->connection;
+
+                if (ngx_del_event(old->read, NGX_READ_EVENT, NGX_CLOSE_EVENT)
+                    == NGX_ERROR)
+                {
+                    return NGX_ERROR;
+                }
+
+                old->fd = (ngx_socket_t) -1;
+            }
+        }
+
+#if (NGX_WIN32)
+
+        if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
+            ngx_iocp_conf_t  *iocpcf;
+
+            rev->handler = ngx_event_acceptex;
+
+            if (ngx_use_accept_mutex) {
+                continue;
+            }
+
+            if (ngx_add_event(rev, 0, NGX_IOCP_ACCEPT) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            ls[i].log.handler = ngx_acceptex_log_error;
+
+            iocpcf = ngx_event_get_conf(cycle->conf_ctx, ngx_iocp_module);
+            if (ngx_event_post_acceptex(&ls[i], iocpcf->post_acceptex)
+                == NGX_ERROR)
+            {
+                return NGX_ERROR;
+            }
+
+        } else {
+            rev->handler = ngx_event_accept;
+
+            if (ngx_use_accept_mutex) {
+                continue;
+            }
+
+            if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+        }
+
+#else
+
+        rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept
+                                                : ngx_event_recvmsg;
+
+#if (NGX_HAVE_REUSEPORT)
+
+        if (ls[i].reuseport) {
+            if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            continue;
+        }
+
+#endif
+
+        if (ngx_use_accept_mutex) {
+            continue;
+        }
+
+#if (NGX_HAVE_EPOLLEXCLUSIVE)
+
+        if ((ngx_event_flags & NGX_USE_EPOLL_EVENT)
+            && ccf->worker_processes > 1)
+        {
+            if (ngx_add_event(rev, NGX_READ_EVENT, NGX_EXCLUSIVE_EVENT)
+                == NGX_ERROR)
+            {
+                return NGX_ERROR;
+            }
+
+            continue;
+        }
+
+#endif
+
+        if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+
+#endif
+
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_send_lowat(ngx_connection_t *c, size_t lowat)
+{
+    int  sndlowat;
+
+#if (NGX_HAVE_LOWAT_EVENT)
+
+    if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+        c->write->available = lowat;
+        return NGX_OK;
+    }
+
+#endif
+
+    if (lowat == 0 || c->sndlowat) {
+        return NGX_OK;
+    }
+
+    sndlowat = (int) lowat;
+
+    if (ngxvcl_kvfd_setsockopt(c->fd, SOL_SOCKET, SO_SNDLOWAT,
+                   (const void *) &sndlowat, sizeof(int))
+        == -1)
+    {
+        ngx_connection_error(c, ngx_socket_errno,
+                             "setsockopt(SO_SNDLOWAT) failed");
+        return NGX_ERROR;
+    }
+
+    c->sndlowat = 1;
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char                 *rv;
+    void               ***ctx;
+    ngx_uint_t            i;
+    ngx_conf_t            pcf;
+    ngx_event_module_t   *m;
+
+    if (*(void **) conf) {
+        return "is duplicate";
+    }
+
+    /* count the number of the event modules and set up their indices */
+
+    ngx_event_max_module = ngx_count_modules(cf->cycle, NGX_EVENT_MODULE);
+
+    ctx = ngx_pcalloc(cf->pool, sizeof(void *));
+    if (ctx == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    *ctx = ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *));
+    if (*ctx == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    *(void **) conf = ctx;
+
+    for (i = 0; cf->cycle->modules[i]; i++) {
+        if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE) {
+            continue;
+        }
+
+        m = cf->cycle->modules[i]->ctx;
+
+        if (m->create_conf) {
+            (*ctx)[cf->cycle->modules[i]->ctx_index] =
+                                                     m->create_conf(cf->cycle);
+            if ((*ctx)[cf->cycle->modules[i]->ctx_index] == NULL) {
+                return NGX_CONF_ERROR;
+            }
+        }
+    }
+
+    pcf = *cf;
+    cf->ctx = ctx;
+    cf->module_type = NGX_EVENT_MODULE;
+    cf->cmd_type = NGX_EVENT_CONF;
+
+    rv = ngx_conf_parse(cf, NULL);
+
+    *cf = pcf;
+
+    if (rv != NGX_CONF_OK) {
+        return rv;
+    }
+
+    for (i = 0; cf->cycle->modules[i]; i++) {
+        if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE) {
+            continue;
+        }
+
+        m = cf->cycle->modules[i]->ctx;
+
+        if (m->init_conf) {
+            rv = m->init_conf(cf->cycle,
+                              (*ctx)[cf->cycle->modules[i]->ctx_index]);
+            if (rv != NGX_CONF_OK) {
+                return rv;
+            }
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_event_connections(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_event_conf_t  *ecf = conf;
+
+    ngx_str_t  *value;
+
+    if (ecf->connections != NGX_CONF_UNSET_UINT) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+    ecf->connections = ngx_atoi(value[1].data, value[1].len);
+    if (ecf->connections == (ngx_uint_t) NGX_ERROR) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid number \"%V\"", &value[1]);
+
+        return NGX_CONF_ERROR;
+    }
+
+    cf->cycle->connection_n = ecf->connections;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_event_use(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_event_conf_t  *ecf = conf;
+
+    ngx_int_t             m;
+    ngx_str_t            *value;
+    ngx_event_conf_t     *old_ecf;
+    ngx_event_module_t   *module;
+
+    if (ecf->use != NGX_CONF_UNSET_UINT) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    if (cf->cycle->old_cycle->conf_ctx) {
+        old_ecf = ngx_event_get_conf(cf->cycle->old_cycle->conf_ctx,
+                                     ngx_event_core_module);
+    } else {
+        old_ecf = NULL;
+    }
+
+
+    for (m = 0; cf->cycle->modules[m]; m++) {
+        if (cf->cycle->modules[m]->type != NGX_EVENT_MODULE) {
+            continue;
+        }
+
+        module = cf->cycle->modules[m]->ctx;
+        if (module->name->len == value[1].len) {
+            if (ngx_strcmp(module->name->data, value[1].data) == 0) {
+                ecf->use = cf->cycle->modules[m]->ctx_index;
+                ecf->name = module->name->data;
+
+                if (ngx_process == NGX_PROCESS_SINGLE
+                    && old_ecf
+                    && old_ecf->use != ecf->use)
+                {
+                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "when the server runs without a master process "
+                               "the \"%V\" event type must be the same as "
+                               "in previous configuration - \"%s\" "
+                               "and it cannot be changed on the fly, "
+                               "to change it you need to stop server "
+                               "and start it again",
+                               &value[1], old_ecf->name);
+
+                    return NGX_CONF_ERROR;
+                }
+
+                return NGX_CONF_OK;
+            }
+        }
+    }
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "invalid event type \"%V\"", &value[1]);
+
+    return NGX_CONF_ERROR;
+}
+
+
+static char *
+ngx_event_debug_connection(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+#if (NGX_DEBUG)
+    ngx_event_conf_t  *ecf = 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 (NGX_HAVE_UNIX_DOMAIN)
+
+    if (ngx_strcmp(value[1].data, "unix:") == 0) {
+        cidr = ngx_array_push(&ecf->debug_connection);
+        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(&ecf->debug_connection);
+        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 debug_connection \"%V\"",
+                               u.err, &u.host);
+        }
+
+        return NGX_CONF_ERROR;
+    }
+
+    cidr = ngx_array_push_n(&ecf->debug_connection, 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;
+        }
+    }
+
+#else
+
+    ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                       "\"debug_connection\" is ignored, you need to rebuild "
+                       "nginx using --with-debug option to enable it");
+
+#endif
+
+    return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_event_core_create_conf(ngx_cycle_t *cycle)
+{
+    ngx_event_conf_t  *ecf;
+
+    ecf = ngx_palloc(cycle->pool, sizeof(ngx_event_conf_t));
+    if (ecf == NULL) {
+        return NULL;
+    }
+
+    ecf->connections = NGX_CONF_UNSET_UINT;
+    ecf->use = NGX_CONF_UNSET_UINT;
+    ecf->multi_accept = NGX_CONF_UNSET;
+    ecf->accept_mutex = NGX_CONF_UNSET;
+    ecf->accept_mutex_delay = NGX_CONF_UNSET_MSEC;
+    ecf->name = (void *) NGX_CONF_UNSET;
+
+#if (NGX_DEBUG)
+
+    if (ngx_array_init(&ecf->debug_connection, cycle->pool, 4,
+                       sizeof(ngx_cidr_t)) == NGX_ERROR)
+    {
+        return NULL;
+    }
+
+#endif
+
+    return ecf;
+}
+
+
+static char *
+ngx_event_core_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+    ngx_event_conf_t  *ecf = conf;
+
+#if (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL)
+    int                  fd;
+#endif
+    ngx_int_t            i;
+    ngx_module_t        *module;
+    ngx_event_module_t  *event_module;
+
+    module = NULL;
+
+#if (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL)
+
+    fd = epoll_create(100);
+
+    if (fd != -1) {
+        (void) close(fd);
+        module = &ngx_epoll_module;
+
+    } else if (ngx_errno != NGX_ENOSYS) {
+        module = &ngx_epoll_module;
+    }
+
+#endif
+
+#if (NGX_HAVE_DEVPOLL) && !(NGX_TEST_BUILD_DEVPOLL)
+
+    module = &ngx_devpoll_module;
+
+#endif
+
+#if (NGX_HAVE_KQUEUE)
+
+    module = &ngx_kqueue_module;
+
+#endif
+
+#if (NGX_HAVE_SELECT)
+
+    if (module == NULL) {
+        module = &ngx_select_module;
+    }
+
+#endif
+
+    if (module == NULL) {
+        for (i = 0; cycle->modules[i]; i++) {
+
+            if (cycle->modules[i]->type != NGX_EVENT_MODULE) {
+                continue;
+            }
+
+            event_module = cycle->modules[i]->ctx;
+
+            if (ngx_strcmp(event_module->name->data, event_core_name.data) == 0)
+            {
+                continue;
+            }
+
+            module = cycle->modules[i];
+            break;
+        }
+    }
+
+    if (module == NULL) {
+        ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, "no events module found");
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_conf_init_uint_value(ecf->connections, DEFAULT_CONNECTIONS);
+    cycle->connection_n = ecf->connections;
+
+    ngx_conf_init_uint_value(ecf->use, module->ctx_index);
+
+    event_module = module->ctx;
+    ngx_conf_init_ptr_value(ecf->name, event_module->name->data);
+
+    ngx_conf_init_value(ecf->multi_accept, 0);
+    ngx_conf_init_value(ecf->accept_mutex, 0);
+    ngx_conf_init_msec_value(ecf->accept_mutex_delay, 500);
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/event/ngx_event.h b/nginx/src/event/ngx_event.h
new file mode 100644 (file)
index 0000000..19fec68
--- /dev/null
@@ -0,0 +1,541 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_EVENT_H_INCLUDED_
+#define _NGX_EVENT_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#define NGX_INVALID_INDEX  0xd0d0d0d0
+
+
+#if (NGX_HAVE_IOCP)
+
+typedef struct {
+    WSAOVERLAPPED    ovlp;
+    ngx_event_t     *event;
+    int              error;
+} ngx_event_ovlp_t;
+
+#endif
+
+
+struct ngx_event_s {
+    void            *data;
+
+    unsigned         write:1;
+
+    unsigned         accept:1;
+
+    /* used to detect the stale events in kqueue and epoll */
+    unsigned         instance:1;
+
+    /*
+     * the event was passed or would be passed to a kernel;
+     * in aio mode - operation was posted.
+     */
+    unsigned         active:1;
+
+    unsigned         disabled:1;
+
+    /* the ready event; in aio mode 0 means that no operation can be posted */
+    unsigned         ready:1;
+
+    unsigned         oneshot:1;
+
+    /* aio operation is complete */
+    unsigned         complete:1;
+
+    unsigned         eof:1;
+    unsigned         error:1;
+
+    unsigned         timedout:1;
+    unsigned         timer_set:1;
+
+    unsigned         delayed:1;
+
+    unsigned         deferred_accept:1;
+
+    /* the pending eof reported by kqueue, epoll or in aio chain operation */
+    unsigned         pending_eof:1;
+
+    unsigned         posted:1;
+
+    unsigned         closed:1;
+
+    /* to test on worker exit */
+    unsigned         channel:1;
+    unsigned         resolver:1;
+
+    unsigned         cancelable:1;
+
+#if (NGX_HAVE_KQUEUE)
+    unsigned         kq_vnode:1;
+
+    /* the pending errno reported by kqueue */
+    int              kq_errno;
+#endif
+
+    /*
+     * kqueue only:
+     *   accept:     number of sockets that wait to be accepted
+     *   read:       bytes to read when event is ready
+     *               or lowat when event is set with NGX_LOWAT_EVENT flag
+     *   write:      available space in buffer when event is ready
+     *               or lowat when event is set with NGX_LOWAT_EVENT flag
+     *
+     * epoll with EPOLLRDHUP:
+     *   accept:     1 if accept many, 0 otherwise
+     *   read:       1 if there can be data to read, 0 otherwise
+     *
+     * iocp: TODO
+     *
+     * otherwise:
+     *   accept:     1 if accept many, 0 otherwise
+     */
+
+#if (NGX_HAVE_KQUEUE) || (NGX_HAVE_IOCP)
+    int              available;
+#else
+    unsigned         available:1;
+#endif
+
+    ngx_event_handler_pt  handler;
+
+
+#if (NGX_HAVE_IOCP)
+    ngx_event_ovlp_t ovlp;
+#endif
+
+    ngx_uint_t       index;
+
+    ngx_log_t       *log;
+
+    ngx_rbtree_node_t   timer;
+
+    /* the posted queue */
+    ngx_queue_t      queue;
+
+#if 0
+
+    /* the threads support */
+
+    /*
+     * the event thread context, we store it here
+     * if $(CC) does not understand __thread declaration
+     * and pthread_getspecific() is too costly
+     */
+
+    void            *thr_ctx;
+
+#if (NGX_EVENT_T_PADDING)
+
+    /* event should not cross cache line in SMP */
+
+    uint32_t         padding[NGX_EVENT_T_PADDING];
+#endif
+#endif
+};
+
+
+#if (NGX_HAVE_FILE_AIO)
+
+struct ngx_event_aio_s {
+    void                      *data;
+    ngx_event_handler_pt       handler;
+    ngx_file_t                *file;
+
+    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
+
+#if !(NGX_HAVE_EVENTFD) || (NGX_TEST_BUILD_EPOLL)
+    ngx_err_t                  err;
+    size_t                     nbytes;
+#endif
+
+    ngx_aiocb_t                aiocb;
+    ngx_event_t                event;
+};
+
+#endif
+
+
+typedef struct {
+    ngx_int_t  (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
+    ngx_int_t  (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
+
+    ngx_int_t  (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
+    ngx_int_t  (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
+
+    ngx_int_t  (*add_conn)(ngx_connection_t *c);
+    ngx_int_t  (*del_conn)(ngx_connection_t *c, ngx_uint_t flags);
+
+    ngx_int_t  (*notify)(ngx_event_handler_pt handler);
+
+    ngx_int_t  (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer,
+                                 ngx_uint_t flags);
+
+    ngx_int_t  (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);
+    void       (*done)(ngx_cycle_t *cycle);
+} ngx_event_actions_t;
+
+
+extern ngx_event_actions_t   ngx_event_actions;
+#if (NGX_HAVE_EPOLLRDHUP)
+extern ngx_uint_t            ngx_use_epoll_rdhup;
+#endif
+
+
+/*
+ * The event filter requires to read/write the whole data:
+ * select, poll, /dev/poll, kqueue, epoll.
+ */
+#define NGX_USE_LEVEL_EVENT      0x00000001
+
+/*
+ * The event filter is deleted after a notification without an additional
+ * syscall: kqueue, epoll.
+ */
+#define NGX_USE_ONESHOT_EVENT    0x00000002
+
+/*
+ * The event filter notifies only the changes and an initial level:
+ * kqueue, epoll.
+ */
+#define NGX_USE_CLEAR_EVENT      0x00000004
+
+/*
+ * The event filter has kqueue features: the eof flag, errno,
+ * available data, etc.
+ */
+#define NGX_USE_KQUEUE_EVENT     0x00000008
+
+/*
+ * The event filter supports low water mark: kqueue's NOTE_LOWAT.
+ * kqueue in FreeBSD 4.1-4.2 has no NOTE_LOWAT so we need a separate flag.
+ */
+#define NGX_USE_LOWAT_EVENT      0x00000010
+
+/*
+ * The event filter requires to do i/o operation until EAGAIN: epoll.
+ */
+#define NGX_USE_GREEDY_EVENT     0x00000020
+
+/*
+ * The event filter is epoll.
+ */
+#define NGX_USE_EPOLL_EVENT      0x00000040
+
+/*
+ * Obsolete.
+ */
+#define NGX_USE_RTSIG_EVENT      0x00000080
+
+/*
+ * Obsolete.
+ */
+#define NGX_USE_AIO_EVENT        0x00000100
+
+/*
+ * Need to add socket or handle only once: i/o completion port.
+ */
+#define NGX_USE_IOCP_EVENT       0x00000200
+
+/*
+ * The event filter has no opaque data and requires file descriptors table:
+ * poll, /dev/poll.
+ */
+#define NGX_USE_FD_EVENT         0x00000400
+
+/*
+ * The event module handles periodic or absolute timer event by itself:
+ * kqueue in FreeBSD 4.4, NetBSD 2.0, and MacOSX 10.4, Solaris 10's event ports.
+ */
+#define NGX_USE_TIMER_EVENT      0x00000800
+
+/*
+ * All event filters on file descriptor are deleted after a notification:
+ * Solaris 10's event ports.
+ */
+#define NGX_USE_EVENTPORT_EVENT  0x00001000
+
+/*
+ * The event filter support vnode notifications: kqueue.
+ */
+#define NGX_USE_VNODE_EVENT      0x00002000
+
+
+/*
+ * The event filter is deleted just before the closing file.
+ * Has no meaning for select and poll.
+ * kqueue, epoll, eventport:         allows to avoid explicit delete,
+ *                                   because filter automatically is deleted
+ *                                   on file close,
+ *
+ * /dev/poll:                        we need to flush POLLREMOVE event
+ *                                   before closing file.
+ */
+#define NGX_CLOSE_EVENT    1
+
+/*
+ * disable temporarily event filter, this may avoid locks
+ * in kernel malloc()/free(): kqueue.
+ */
+#define NGX_DISABLE_EVENT  2
+
+/*
+ * event must be passed to kernel right now, do not wait until batch processing.
+ */
+#define NGX_FLUSH_EVENT    4
+
+
+/* these flags have a meaning only for kqueue */
+#define NGX_LOWAT_EVENT    0
+#define NGX_VNODE_EVENT    0
+
+
+#if (NGX_HAVE_EPOLL) && !(NGX_HAVE_EPOLLRDHUP)
+#define EPOLLRDHUP         0
+#endif
+
+
+#if (NGX_HAVE_KQUEUE)
+
+#define NGX_READ_EVENT     EVFILT_READ
+#define NGX_WRITE_EVENT    EVFILT_WRITE
+
+#undef  NGX_VNODE_EVENT
+#define NGX_VNODE_EVENT    EVFILT_VNODE
+
+/*
+ * NGX_CLOSE_EVENT, NGX_LOWAT_EVENT, and NGX_FLUSH_EVENT are the module flags
+ * and they must not go into a kernel so we need to choose the value
+ * that must not interfere with any existent and future kqueue flags.
+ * kqueue has such values - EV_FLAG1, EV_EOF, and EV_ERROR:
+ * they are reserved and cleared on a kernel entrance.
+ */
+#undef  NGX_CLOSE_EVENT
+#define NGX_CLOSE_EVENT    EV_EOF
+
+#undef  NGX_LOWAT_EVENT
+#define NGX_LOWAT_EVENT    EV_FLAG1
+
+#undef  NGX_FLUSH_EVENT
+#define NGX_FLUSH_EVENT    EV_ERROR
+
+#define NGX_LEVEL_EVENT    0
+#define NGX_ONESHOT_EVENT  EV_ONESHOT
+#define NGX_CLEAR_EVENT    EV_CLEAR
+
+#undef  NGX_DISABLE_EVENT
+#define NGX_DISABLE_EVENT  EV_DISABLE
+
+
+#elif (NGX_HAVE_DEVPOLL && !(NGX_TEST_BUILD_DEVPOLL)) \
+      || (NGX_HAVE_EVENTPORT && !(NGX_TEST_BUILD_EVENTPORT))
+
+#define NGX_READ_EVENT     POLLIN
+#define NGX_WRITE_EVENT    POLLOUT
+
+#define NGX_LEVEL_EVENT    0
+#define NGX_ONESHOT_EVENT  1
+
+
+#elif (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL)
+
+#define NGX_READ_EVENT     (EPOLLIN|EPOLLRDHUP)
+#define NGX_WRITE_EVENT    EPOLLOUT
+
+#define NGX_LEVEL_EVENT    0
+#define NGX_CLEAR_EVENT    EPOLLET
+#define NGX_ONESHOT_EVENT  0x70000000
+#if 0
+#define NGX_ONESHOT_EVENT  EPOLLONESHOT
+#endif
+
+#if (NGX_HAVE_EPOLLEXCLUSIVE)
+#define NGX_EXCLUSIVE_EVENT  EPOLLEXCLUSIVE
+#endif
+
+#elif (NGX_HAVE_POLL)
+
+#define NGX_READ_EVENT     POLLIN
+#define NGX_WRITE_EVENT    POLLOUT
+
+#define NGX_LEVEL_EVENT    0
+#define NGX_ONESHOT_EVENT  1
+
+
+#else /* select */
+
+#define NGX_READ_EVENT     0
+#define NGX_WRITE_EVENT    1
+
+#define NGX_LEVEL_EVENT    0
+#define NGX_ONESHOT_EVENT  1
+
+#endif /* NGX_HAVE_KQUEUE */
+
+
+#if (NGX_HAVE_IOCP)
+#define NGX_IOCP_ACCEPT      0
+#define NGX_IOCP_IO          1
+#define NGX_IOCP_CONNECT     2
+#endif
+
+
+#if (NGX_TEST_BUILD_EPOLL)
+#define NGX_EXCLUSIVE_EVENT  0
+#endif
+
+
+#ifndef NGX_CLEAR_EVENT
+#define NGX_CLEAR_EVENT    0    /* dummy declaration */
+#endif
+
+
+#define ngx_process_events   ngx_event_actions.process_events
+#define ngx_done_events      ngx_event_actions.done
+
+#define ngx_add_event        ngx_event_actions.add
+#define ngx_del_event        ngx_event_actions.del
+#define ngx_add_conn         ngx_event_actions.add_conn
+#define ngx_del_conn         ngx_event_actions.del_conn
+
+#define ngx_notify           ngx_event_actions.notify
+
+#define ngx_add_timer        ngx_event_add_timer
+#define ngx_del_timer        ngx_event_del_timer
+
+
+extern ngx_os_io_t  ngx_io;
+
+#define ngx_recv             ngx_io.recv
+#define ngx_recv_chain       ngx_io.recv_chain
+#define ngx_udp_recv         ngx_io.udp_recv
+#define ngx_send             ngx_io.send
+#define ngx_send_chain       ngx_io.send_chain
+#define ngx_udp_send         ngx_io.udp_send
+#define ngx_udp_send_chain   ngx_io.udp_send_chain
+
+
+#define NGX_EVENT_MODULE      0x544E5645  /* "EVNT" */
+#define NGX_EVENT_CONF        0x02000000
+
+
+typedef struct {
+    ngx_uint_t    connections;
+    ngx_uint_t    use;
+
+    ngx_flag_t    multi_accept;
+    ngx_flag_t    accept_mutex;
+
+    ngx_msec_t    accept_mutex_delay;
+
+    u_char       *name;
+
+#if (NGX_DEBUG)
+    ngx_array_t   debug_connection;
+#endif
+} ngx_event_conf_t;
+
+
+typedef struct {
+    ngx_str_t              *name;
+
+    void                 *(*create_conf)(ngx_cycle_t *cycle);
+    char                 *(*init_conf)(ngx_cycle_t *cycle, void *conf);
+
+    ngx_event_actions_t     actions;
+} ngx_event_module_t;
+
+
+extern ngx_atomic_t          *ngx_connection_counter;
+
+extern ngx_atomic_t          *ngx_accept_mutex_ptr;
+extern ngx_shmtx_t            ngx_accept_mutex;
+extern ngx_uint_t             ngx_use_accept_mutex;
+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;
+
+
+#if (NGX_STAT_STUB)
+
+extern ngx_atomic_t  *ngx_stat_accepted;
+extern ngx_atomic_t  *ngx_stat_handled;
+extern ngx_atomic_t  *ngx_stat_requests;
+extern ngx_atomic_t  *ngx_stat_active;
+extern ngx_atomic_t  *ngx_stat_reading;
+extern ngx_atomic_t  *ngx_stat_writing;
+extern ngx_atomic_t  *ngx_stat_waiting;
+
+#endif
+
+
+#define NGX_UPDATE_TIME         1
+#define NGX_POST_EVENTS         2
+
+
+extern sig_atomic_t           ngx_event_timer_alarm;
+extern ngx_uint_t             ngx_event_flags;
+extern ngx_module_t           ngx_events_module;
+extern ngx_module_t           ngx_event_core_module;
+
+
+#define ngx_event_get_conf(conf_ctx, module)                                  \
+             (*(ngx_get_conf(conf_ctx, ngx_events_module))) [module.ctx_index];
+
+
+
+void ngx_event_accept(ngx_event_t *ev);
+#if !(NGX_WIN32)
+void ngx_event_recvmsg(ngx_event_t *ev);
+#endif
+ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle);
+u_char *ngx_accept_log_error(ngx_log_t *log, u_char *buf, size_t len);
+
+
+void ngx_process_events_and_timers(ngx_cycle_t *cycle);
+ngx_int_t ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags);
+ngx_int_t ngx_handle_write_event(ngx_event_t *wev, size_t lowat);
+
+
+#if (NGX_WIN32)
+void ngx_event_acceptex(ngx_event_t *ev);
+ngx_int_t ngx_event_post_acceptex(ngx_listening_t *ls, ngx_uint_t n);
+u_char *ngx_acceptex_log_error(ngx_log_t *log, u_char *buf, size_t len);
+#endif
+
+
+ngx_int_t ngx_send_lowat(ngx_connection_t *c, size_t lowat);
+
+
+/* used in ngx_log_debugX() */
+#define ngx_event_ident(p)  ((ngx_connection_t *) (p))->fd
+
+
+#include <ngx_event_timer.h>
+#include <ngx_event_posted.h>
+
+#if (NGX_WIN32)
+#include <ngx_iocp_module.h>
+#endif
+
+
+#endif /* _NGX_EVENT_H_INCLUDED_ */
diff --git a/nginx/src/event/ngx_event_accept.c b/nginx/src/event/ngx_event_accept.c
new file mode 100644 (file)
index 0000000..d5cf094
--- /dev/null
@@ -0,0 +1,852 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+static ngx_int_t ngx_enable_accept_events(ngx_cycle_t *cycle);
+static ngx_int_t ngx_disable_accept_events(ngx_cycle_t *cycle, ngx_uint_t all);
+static void ngx_close_accepted_connection(ngx_connection_t *c);
+#if (NGX_DEBUG)
+static void ngx_debug_accepted_connection(ngx_event_conf_t *ecf,
+    ngx_connection_t *c);
+#endif
+
+
+void
+ngx_event_accept(ngx_event_t *ev)
+{
+    socklen_t          socklen;
+    ngx_err_t          err;
+    ngx_log_t         *log;
+    ngx_uint_t         level;
+    ngx_socket_t       s;
+    ngx_event_t       *rev, *wev;
+    ngx_sockaddr_t     sa;
+    ngx_listening_t   *ls;
+    ngx_connection_t  *c, *lc;
+    ngx_event_conf_t  *ecf;
+#if (NGX_HAVE_ACCEPT4)
+    static ngx_uint_t  use_accept4 = 1;
+#endif
+
+    if (ev->timedout) {
+        if (ngx_enable_accept_events((ngx_cycle_t *) ngx_cycle) != NGX_OK) {
+            return;
+        }
+
+        ev->timedout = 0;
+    }
+
+    ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module);
+
+    if (!(ngx_event_flags & NGX_USE_KQUEUE_EVENT)) {
+        ev->available = ecf->multi_accept;
+    }
+
+    lc = ev->data;
+    ls = lc->listening;
+    ev->ready = 0;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "accept on %V, ready: %d", &ls->addr_text, ev->available);
+
+    do {
+        socklen = sizeof(ngx_sockaddr_t);
+
+#if (NGX_HAVE_ACCEPT4)
+        if (use_accept4) {
+            s = ngxvcl_accept4(lc->fd, &sa.sockaddr, &socklen, SOCK_NONBLOCK);
+        } else {
+            s = ngxvcl_accept(lc->fd, &sa.sockaddr, &socklen);
+        }
+#else
+        s = ngxvcl_accept(lc->fd, &sa.sockaddr, &socklen);
+#endif
+
+        if (s == (ngx_socket_t) -1) {
+            err = ngx_socket_errno;
+
+            if (err == NGX_EAGAIN) {
+                ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, err,
+                               "accept() not ready");
+                return;
+            }
+
+            level = NGX_LOG_ALERT;
+
+            if (err == NGX_ECONNABORTED) {
+                level = NGX_LOG_ERR;
+
+            } else if (err == NGX_EMFILE || err == NGX_ENFILE) {
+                level = NGX_LOG_CRIT;
+            }
+
+#if (NGX_HAVE_ACCEPT4)
+            ngx_log_error(level, ev->log, err,
+                          use_accept4 ? "accept4() failed" : "accept() failed");
+
+            if (use_accept4 && err == NGX_ENOSYS) {
+                use_accept4 = 0;
+                ngx_inherited_nonblocking = 0;
+                continue;
+            }
+#else
+            ngx_log_error(level, ev->log, err, "accept() failed");
+#endif
+
+            if (err == NGX_ECONNABORTED) {
+                if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+                    ev->available--;
+                }
+
+                if (ev->available) {
+                    continue;
+                }
+            }
+
+            if (err == NGX_EMFILE || err == NGX_ENFILE) {
+                if (ngx_disable_accept_events((ngx_cycle_t *) ngx_cycle, 1)
+                    != NGX_OK)
+                {
+                    return;
+                }
+
+                if (ngx_use_accept_mutex) {
+                    if (ngx_accept_mutex_held) {
+                        ngx_shmtx_unlock(&ngx_accept_mutex);
+                        ngx_accept_mutex_held = 0;
+                    }
+
+                    ngx_accept_disabled = 1;
+
+                } else {
+                    ngx_add_timer(ev, ecf->accept_mutex_delay);
+                }
+            }
+
+            return;
+        }
+
+#if (NGX_STAT_STUB)
+        (void) ngx_atomic_fetch_add(ngx_stat_accepted, 1);
+#endif
+
+        ngx_accept_disabled = ngx_cycle->connection_n / 8
+                              - ngx_cycle->free_connection_n;
+
+        c = ngx_get_connection(s, ev->log);
+
+        if (c == NULL) {
+            if (ngx_close_socket(s) == -1) {
+                ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
+                              ngx_close_socket_n " failed");
+            }
+
+            return;
+        }
+
+        c->type = SOCK_STREAM;
+
+#if (NGX_STAT_STUB)
+        (void) ngx_atomic_fetch_add(ngx_stat_active, 1);
+#endif
+
+        c->pool = ngx_create_pool(ls->pool_size, ev->log);
+        if (c->pool == NULL) {
+            ngx_close_accepted_connection(c);
+            return;
+        }
+
+        if (socklen > (socklen_t) sizeof(ngx_sockaddr_t)) {
+            socklen = sizeof(ngx_sockaddr_t);
+        }
+
+        c->sockaddr = ngx_palloc(c->pool, socklen);
+        if (c->sockaddr == NULL) {
+            ngx_close_accepted_connection(c);
+            return;
+        }
+
+        ngx_memcpy(c->sockaddr, &sa, socklen);
+
+        log = ngx_palloc(c->pool, sizeof(ngx_log_t));
+        if (log == NULL) {
+            ngx_close_accepted_connection(c);
+            return;
+        }
+
+        /* set a blocking mode for iocp and non-blocking mode for others */
+
+        if (ngx_inherited_nonblocking) {
+            if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
+                if (ngx_blocking(s) == -1) {
+                    ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
+                                  ngx_blocking_n " failed");
+                    ngx_close_accepted_connection(c);
+                    return;
+                }
+            }
+
+        } else {
+            if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) {
+                if (ngx_nonblocking(s) == -1) {
+                    ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
+                                  ngx_nonblocking_n " failed");
+                    ngx_close_accepted_connection(c);
+                    return;
+                }
+            }
+        }
+
+        *log = ls->log;
+
+        c->recv = ngx_recv;
+        c->send = ngx_send;
+        c->recv_chain = ngx_recv_chain;
+        c->send_chain = ngx_send_chain;
+
+        c->log = log;
+        c->pool->log = log;
+
+        c->socklen = socklen;
+        c->listening = ls;
+        c->local_sockaddr = ls->sockaddr;
+        c->local_socklen = ls->socklen;
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+        if (c->sockaddr->sa_family == AF_UNIX) {
+            c->tcp_nopush = NGX_TCP_NOPUSH_DISABLED;
+            c->tcp_nodelay = NGX_TCP_NODELAY_DISABLED;
+#if (NGX_SOLARIS)
+            /* Solaris's sendfilev() supports AF_NCA, AF_INET, and AF_INET6 */
+            c->sendfile = 0;
+#endif
+        }
+#endif
+
+        rev = c->read;
+        wev = c->write;
+
+        wev->ready = 1;
+
+        if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
+            rev->ready = 1;
+        }
+
+        if (ev->deferred_accept) {
+            rev->ready = 1;
+#if (NGX_HAVE_KQUEUE || NGX_HAVE_EPOLLRDHUP)
+            rev->available = 1;
+#endif
+        }
+
+        rev->log = log;
+        wev->log = log;
+
+        /*
+         * TODO: MT: - ngx_atomic_fetch_add()
+         *             or protection by critical section or light mutex
+         *
+         * TODO: MP: - allocated in a shared memory
+         *           - ngx_atomic_fetch_add()
+         *             or protection by critical section or light mutex
+         */
+
+        c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
+
+#if (NGX_STAT_STUB)
+        (void) ngx_atomic_fetch_add(ngx_stat_handled, 1);
+#endif
+
+        if (ls->addr_ntop) {
+            c->addr_text.data = ngx_pnalloc(c->pool, ls->addr_text_max_len);
+            if (c->addr_text.data == NULL) {
+                ngx_close_accepted_connection(c);
+                return;
+            }
+
+            c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen,
+                                             c->addr_text.data,
+                                             ls->addr_text_max_len, 0);
+            if (c->addr_text.len == 0) {
+                ngx_close_accepted_connection(c);
+                return;
+            }
+        }
+
+#if (NGX_DEBUG)
+        {
+        ngx_str_t  addr;
+        u_char     text[NGX_SOCKADDR_STRLEN];
+
+        ngx_debug_accepted_connection(ecf, c);
+
+        if (log->log_level & NGX_LOG_DEBUG_EVENT) {
+            addr.data = text;
+            addr.len = ngx_sock_ntop(c->sockaddr, c->socklen, text,
+                                     NGX_SOCKADDR_STRLEN, 1);
+
+            ngx_log_debug3(NGX_LOG_DEBUG_EVENT, log, 0,
+                           "*%uA accept: %V fd:%d", c->number, &addr, s);
+        }
+
+        }
+#endif
+
+        if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
+            if (ngx_add_conn(c) == NGX_ERROR) {
+                ngx_close_accepted_connection(c);
+                return;
+            }
+        }
+
+        log->data = NULL;
+        log->handler = NULL;
+
+        ls->handler(c);
+
+        if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+            ev->available--;
+        }
+
+    } while (ev->available);
+}
+
+
+#if !(NGX_WIN32)
+
+void
+ngx_event_recvmsg(ngx_event_t *ev)
+{
+    ssize_t            n;
+    ngx_log_t         *log;
+    ngx_err_t          err;
+    ngx_event_t       *rev, *wev;
+    struct iovec       iov[1];
+    struct msghdr      msg;
+    ngx_sockaddr_t     sa;
+    ngx_listening_t   *ls;
+    ngx_event_conf_t  *ecf;
+    ngx_connection_t  *c, *lc;
+    static u_char      buffer[65535];
+
+#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
+
+#if (NGX_HAVE_IP_RECVDSTADDR)
+    u_char             msg_control[CMSG_SPACE(sizeof(struct in_addr))];
+#elif (NGX_HAVE_IP_PKTINFO)
+    u_char             msg_control[CMSG_SPACE(sizeof(struct in_pktinfo))];
+#endif
+
+#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO)
+    u_char             msg_control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+#endif
+
+#endif
+
+    if (ev->timedout) {
+        if (ngx_enable_accept_events((ngx_cycle_t *) ngx_cycle) != NGX_OK) {
+            return;
+        }
+
+        ev->timedout = 0;
+    }
+
+    ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module);
+
+    if (!(ngx_event_flags & NGX_USE_KQUEUE_EVENT)) {
+        ev->available = ecf->multi_accept;
+    }
+
+    lc = ev->data;
+    ls = lc->listening;
+    ev->ready = 0;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "recvmsg on %V, ready: %d", &ls->addr_text, ev->available);
+
+    do {
+        ngx_memzero(&msg, sizeof(struct msghdr));
+
+        iov[0].iov_base = (void *) buffer;
+        iov[0].iov_len = sizeof(buffer);
+
+        msg.msg_name = &sa;
+        msg.msg_namelen = sizeof(ngx_sockaddr_t);
+        msg.msg_iov = iov;
+        msg.msg_iovlen = 1;
+
+#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
+
+        if (ls->wildcard) {
+
+#if (NGX_HAVE_IP_RECVDSTADDR || NGX_HAVE_IP_PKTINFO)
+            if (ls->sockaddr->sa_family == AF_INET) {
+                msg.msg_control = &msg_control;
+                msg.msg_controllen = sizeof(msg_control);
+            }
+#endif
+
+#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO)
+            if (ls->sockaddr->sa_family == AF_INET6) {
+                msg.msg_control = &msg_control6;
+                msg.msg_controllen = sizeof(msg_control6);
+            }
+#endif
+        }
+
+#endif
+
+        n = ngxvcl_recvmsg(lc->fd, &msg, 0);
+
+        if (n == -1) {
+            err = ngx_socket_errno;
+
+            if (err == NGX_EAGAIN) {
+                ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, err,
+                               "recvmsg() not ready");
+                return;
+            }
+
+            ngx_log_error(NGX_LOG_ALERT, ev->log, err, "recvmsg() failed");
+
+            return;
+        }
+
+#if (NGX_STAT_STUB)
+        (void) ngx_atomic_fetch_add(ngx_stat_accepted, 1);
+#endif
+
+#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
+        if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) {
+            ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+                          "recvmsg() truncated data");
+            continue;
+        }
+#endif
+
+        ngx_accept_disabled = ngx_cycle->connection_n / 8
+                              - ngx_cycle->free_connection_n;
+
+        c = ngx_get_connection(lc->fd, ev->log);
+        if (c == NULL) {
+            return;
+        }
+
+        c->shared = 1;
+        c->type = SOCK_DGRAM;
+        c->socklen = msg.msg_namelen;
+
+        if (c->socklen > (socklen_t) sizeof(ngx_sockaddr_t)) {
+            c->socklen = sizeof(ngx_sockaddr_t);
+        }
+
+        if (c->socklen == 0) {
+
+            /*
+             * on Linux recvmsg() returns zero msg_namelen
+             * when receiving packets from unbound AF_UNIX sockets
+             */
+
+            c->socklen = sizeof(struct sockaddr);
+            ngx_memzero(&sa, sizeof(struct sockaddr));
+            sa.sockaddr.sa_family = ls->sockaddr->sa_family;
+        }
+
+#if (NGX_STAT_STUB)
+        (void) ngx_atomic_fetch_add(ngx_stat_active, 1);
+#endif
+
+        c->pool = ngx_create_pool(ls->pool_size, ev->log);
+        if (c->pool == NULL) {
+            ngx_close_accepted_connection(c);
+            return;
+        }
+
+        c->sockaddr = ngx_palloc(c->pool, c->socklen);
+        if (c->sockaddr == NULL) {
+            ngx_close_accepted_connection(c);
+            return;
+        }
+
+        ngx_memcpy(c->sockaddr, msg.msg_name, c->socklen);
+
+        log = ngx_palloc(c->pool, sizeof(ngx_log_t));
+        if (log == NULL) {
+            ngx_close_accepted_connection(c);
+            return;
+        }
+
+        *log = ls->log;
+
+        c->send = ngx_udp_send;
+        c->send_chain = ngx_udp_send_chain;
+
+        c->log = log;
+        c->pool->log = log;
+
+        c->listening = ls;
+        c->local_sockaddr = ls->sockaddr;
+        c->local_socklen = ls->socklen;
+
+#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
+
+        if (ls->wildcard) {
+            struct cmsghdr   *cmsg;
+            struct sockaddr  *sockaddr;
+
+            sockaddr = ngx_palloc(c->pool, c->local_socklen);
+            if (sockaddr == NULL) {
+                ngx_close_accepted_connection(c);
+                return;
+            }
+
+            ngx_memcpy(sockaddr, c->local_sockaddr, c->local_socklen);
+            c->local_sockaddr = sockaddr;
+
+            for (cmsg = CMSG_FIRSTHDR(&msg);
+                 cmsg != NULL;
+                 cmsg = CMSG_NXTHDR(&msg, cmsg))
+            {
+
+#if (NGX_HAVE_IP_RECVDSTADDR)
+
+                if (cmsg->cmsg_level == IPPROTO_IP
+                    && cmsg->cmsg_type == IP_RECVDSTADDR
+                    && sockaddr->sa_family == AF_INET)
+                {
+                    struct in_addr      *addr;
+                    struct sockaddr_in  *sin;
+
+                    addr = (struct in_addr *) CMSG_DATA(cmsg);
+                    sin = (struct sockaddr_in *) sockaddr;
+                    sin->sin_addr = *addr;
+
+                    break;
+                }
+
+#elif (NGX_HAVE_IP_PKTINFO)
+
+                if (cmsg->cmsg_level == IPPROTO_IP
+                    && cmsg->cmsg_type == IP_PKTINFO
+                    && sockaddr->sa_family == AF_INET)
+                {
+                    struct in_pktinfo   *pkt;
+                    struct sockaddr_in  *sin;
+
+                    pkt = (struct in_pktinfo *) CMSG_DATA(cmsg);
+                    sin = (struct sockaddr_in *) sockaddr;
+                    sin->sin_addr = pkt->ipi_addr;
+
+                    break;
+                }
+
+#endif
+
+#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO)
+
+                if (cmsg->cmsg_level == IPPROTO_IPV6
+                    && cmsg->cmsg_type == IPV6_PKTINFO
+                    && sockaddr->sa_family == AF_INET6)
+                {
+                    struct in6_pktinfo   *pkt6;
+                    struct sockaddr_in6  *sin6;
+
+                    pkt6 = (struct in6_pktinfo *) CMSG_DATA(cmsg);
+                    sin6 = (struct sockaddr_in6 *) sockaddr;
+                    sin6->sin6_addr = pkt6->ipi6_addr;
+
+                    break;
+                }
+
+#endif
+
+            }
+        }
+
+#endif
+
+        c->buffer = ngx_create_temp_buf(c->pool, n);
+        if (c->buffer == NULL) {
+            ngx_close_accepted_connection(c);
+            return;
+        }
+
+        c->buffer->last = ngx_cpymem(c->buffer->last, buffer, n);
+
+        rev = c->read;
+        wev = c->write;
+
+        wev->ready = 1;
+
+        rev->log = log;
+        wev->log = log;
+
+        /*
+         * TODO: MT: - ngx_atomic_fetch_add()
+         *             or protection by critical section or light mutex
+         *
+         * TODO: MP: - allocated in a shared memory
+         *           - ngx_atomic_fetch_add()
+         *             or protection by critical section or light mutex
+         */
+
+        c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
+
+#if (NGX_STAT_STUB)
+        (void) ngx_atomic_fetch_add(ngx_stat_handled, 1);
+#endif
+
+        if (ls->addr_ntop) {
+            c->addr_text.data = ngx_pnalloc(c->pool, ls->addr_text_max_len);
+            if (c->addr_text.data == NULL) {
+                ngx_close_accepted_connection(c);
+                return;
+            }
+
+            c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen,
+                                             c->addr_text.data,
+                                             ls->addr_text_max_len, 0);
+            if (c->addr_text.len == 0) {
+                ngx_close_accepted_connection(c);
+                return;
+            }
+        }
+
+#if (NGX_DEBUG)
+        {
+        ngx_str_t  addr;
+        u_char     text[NGX_SOCKADDR_STRLEN];
+
+        ngx_debug_accepted_connection(ecf, c);
+
+        if (log->log_level & NGX_LOG_DEBUG_EVENT) {
+            addr.data = text;
+            addr.len = ngx_sock_ntop(c->sockaddr, c->socklen, text,
+                                     NGX_SOCKADDR_STRLEN, 1);
+
+            ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0,
+                           "*%uA recvmsg: %V fd:%d n:%z",
+                           c->number, &addr, c->fd, n);
+        }
+
+        }
+#endif
+
+        log->data = NULL;
+        log->handler = NULL;
+
+        ls->handler(c);
+
+        if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+            ev->available -= n;
+        }
+
+    } while (ev->available);
+}
+
+#endif
+
+
+ngx_int_t
+ngx_trylock_accept_mutex(ngx_cycle_t *cycle)
+{
+    if (ngx_shmtx_trylock(&ngx_accept_mutex)) {
+
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "accept mutex locked");
+
+        if (ngx_accept_mutex_held && ngx_accept_events == 0) {
+            return NGX_OK;
+        }
+
+        if (ngx_enable_accept_events(cycle) == NGX_ERROR) {
+            ngx_shmtx_unlock(&ngx_accept_mutex);
+            return NGX_ERROR;
+        }
+
+        ngx_accept_events = 0;
+        ngx_accept_mutex_held = 1;
+
+        return NGX_OK;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "accept mutex lock failed: %ui", ngx_accept_mutex_held);
+
+    if (ngx_accept_mutex_held) {
+        if (ngx_disable_accept_events(cycle, 0) == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+
+        ngx_accept_mutex_held = 0;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_enable_accept_events(ngx_cycle_t *cycle)
+{
+    ngx_uint_t         i;
+    ngx_listening_t   *ls;
+    ngx_connection_t  *c;
+
+    ls = cycle->listening.elts;
+    for (i = 0; i < cycle->listening.nelts; i++) {
+
+        c = ls[i].connection;
+
+        if (c == NULL || c->read->active) {
+            continue;
+        }
+
+        if (ngx_add_event(c->read, NGX_READ_EVENT, 0) == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_disable_accept_events(ngx_cycle_t *cycle, ngx_uint_t all)
+{
+    ngx_uint_t         i;
+    ngx_listening_t   *ls;
+    ngx_connection_t  *c;
+
+    ls = cycle->listening.elts;
+    for (i = 0; i < cycle->listening.nelts; i++) {
+
+        c = ls[i].connection;
+
+        if (c == NULL || !c->read->active) {
+            continue;
+        }
+
+#if (NGX_HAVE_REUSEPORT)
+
+        /*
+         * do not disable accept on worker's own sockets
+         * when disabling accept events due to accept mutex
+         */
+
+        if (ls[i].reuseport && !all) {
+            continue;
+        }
+
+#endif
+
+        if (ngx_del_event(c->read, NGX_READ_EVENT, NGX_DISABLE_EVENT)
+            == NGX_ERROR)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_close_accepted_connection(ngx_connection_t *c)
+{
+    ngx_socket_t  fd;
+
+    ngx_free_connection(c);
+
+    fd = c->fd;
+    c->fd = (ngx_socket_t) -1;
+
+    if (!c->shared && ngx_close_socket(fd) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno,
+                      ngx_close_socket_n " failed");
+    }
+
+    if (c->pool) {
+        ngx_destroy_pool(c->pool);
+    }
+
+#if (NGX_STAT_STUB)
+    (void) ngx_atomic_fetch_add(ngx_stat_active, -1);
+#endif
+}
+
+
+u_char *
+ngx_accept_log_error(ngx_log_t *log, u_char *buf, size_t len)
+{
+    return ngx_snprintf(buf, len, " while accepting new connection on %V",
+                        log->data);
+}
+
+
+#if (NGX_DEBUG)
+
+static void
+ngx_debug_accepted_connection(ngx_event_conf_t *ecf, ngx_connection_t *c)
+{
+    struct sockaddr_in   *sin;
+    ngx_cidr_t           *cidr;
+    ngx_uint_t            i;
+#if (NGX_HAVE_INET6)
+    struct sockaddr_in6  *sin6;
+    ngx_uint_t            n;
+#endif
+
+    cidr = ecf->debug_connection.elts;
+    for (i = 0; i < ecf->debug_connection.nelts; i++) {
+        if (cidr[i].family != (ngx_uint_t) c->sockaddr->sa_family) {
+            goto next;
+        }
+
+        switch (cidr[i].family) {
+
+#if (NGX_HAVE_INET6)
+        case AF_INET6:
+            sin6 = (struct sockaddr_in6 *) c->sockaddr;
+            for (n = 0; n < 16; n++) {
+                if ((sin6->sin6_addr.s6_addr[n]
+                    & cidr[i].u.in6.mask.s6_addr[n])
+                    != cidr[i].u.in6.addr.s6_addr[n])
+                {
+                    goto next;
+                }
+            }
+            break;
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+        case AF_UNIX:
+            break;
+#endif
+
+        default: /* AF_INET */
+            sin = (struct sockaddr_in *) c->sockaddr;
+            if ((sin->sin_addr.s_addr & cidr[i].u.in.mask)
+                != cidr[i].u.in.addr)
+            {
+                goto next;
+            }
+            break;
+        }
+
+        c->log->log_level = NGX_LOG_DEBUG_CONNECTION|NGX_LOG_DEBUG_ALL;
+        break;
+
+    next:
+        continue;
+    }
+}
+
+#endif
diff --git a/nginx/src/event/ngx_event_connect.c b/nginx/src/event/ngx_event_connect.c
new file mode 100644 (file)
index 0000000..f7e539f
--- /dev/null
@@ -0,0 +1,419 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_connect.h>
+
+
+#if (NGX_HAVE_TRANSPARENT_PROXY)
+static ngx_int_t ngx_event_connect_set_transparent(ngx_peer_connection_t *pc,
+    ngx_socket_t s);
+#endif
+
+
+ngx_int_t
+ngx_event_connect_peer(ngx_peer_connection_t *pc)
+{
+    int                rc, type;
+#if (NGX_HAVE_IP_BIND_ADDRESS_NO_PORT || NGX_LINUX)
+    in_port_t          port;
+#endif
+    ngx_int_t          event;
+    ngx_err_t          err;
+    ngx_uint_t         level;
+    ngx_socket_t       s;
+    ngx_event_t       *rev, *wev;
+    ngx_connection_t  *c;
+
+    rc = pc->get(pc, pc->data);
+    if (rc != NGX_OK) {
+        return rc;
+    }
+
+    type = (pc->type ? pc->type : SOCK_STREAM);
+
+    s = ngx_socket(pc->sockaddr->sa_family, type, 0);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, "%s socket %d",
+                   (type == SOCK_STREAM) ? "stream" : "dgram", s);
+
+    if (s == (ngx_socket_t) -1) {
+        ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+                      ngx_socket_n " failed");
+        return NGX_ERROR;
+    }
+
+
+    c = ngx_get_connection(s, pc->log);
+
+    if (c == NULL) {
+        if (ngx_close_socket(s) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+                          ngx_close_socket_n "failed");
+        }
+
+        return NGX_ERROR;
+    }
+
+    c->type = type;
+
+    if (pc->rcvbuf) {
+        if (ngxvcl_kvfd_setsockopt(s, SOL_SOCKET, SO_RCVBUF,
+                       (const void *) &pc->rcvbuf, sizeof(int)) == -1)
+        {
+            ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+                          "setsockopt(SO_RCVBUF) failed");
+            goto failed;
+        }
+    }
+
+    if (ngx_nonblocking(s) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+                      ngx_nonblocking_n " failed");
+
+        goto failed;
+    }
+
+    if (pc->local) {
+
+#if (NGX_HAVE_TRANSPARENT_PROXY)
+        if (pc->transparent) {
+            if (ngx_event_connect_set_transparent(pc, s) != NGX_OK) {
+                goto failed;
+            }
+        }
+#endif
+
+#if (NGX_HAVE_IP_BIND_ADDRESS_NO_PORT || NGX_LINUX)
+        port = ngx_inet_get_port(pc->local->sockaddr);
+#endif
+
+#if (NGX_HAVE_IP_BIND_ADDRESS_NO_PORT)
+
+        if (pc->sockaddr->sa_family != AF_UNIX && port == 0) {
+            static int  bind_address_no_port = 1;
+
+            if (bind_address_no_port) {
+                if (ngxvcl_kvfd_setsockopt(s, IPPROTO_IP, IP_BIND_ADDRESS_NO_PORT,
+                               (const void *) &bind_address_no_port,
+                               sizeof(int)) == -1)
+                {
+                    err = ngx_socket_errno;
+
+                    if (err != NGX_EOPNOTSUPP && err != NGX_ENOPROTOOPT) {
+                        ngx_log_error(NGX_LOG_ALERT, pc->log, err,
+                                      "setsockopt(IP_BIND_ADDRESS_NO_PORT) "
+                                      "failed, ignored");
+
+                    } else {
+                        bind_address_no_port = 0;
+                    }
+                }
+            }
+        }
+
+#endif
+
+#if (NGX_LINUX)
+
+        if (pc->type == SOCK_DGRAM && port != 0) {
+            int  reuse_addr = 1;
+
+            if (ngxvcl_kvfd_setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
+                           (const void *) &reuse_addr, sizeof(int))
+                 == -1)
+            {
+                ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+                              "setsockopt(SO_REUSEADDR) failed");
+                goto failed;
+            }
+        }
+
+#endif
+
+        if (ngxvcl_bind(s, pc->local->sockaddr, pc->local->socklen) == -1) {
+            ngx_log_error(NGX_LOG_CRIT, pc->log, ngx_socket_errno,
+                          "bind(%V) failed", &pc->local->name);
+
+            goto failed;
+        }
+    }
+
+    if (type == SOCK_STREAM) {
+        c->recv = ngx_recv;
+        c->send = ngx_send;
+        c->recv_chain = ngx_recv_chain;
+        c->send_chain = ngx_send_chain;
+
+        c->sendfile = 1;
+
+        if (pc->sockaddr->sa_family == AF_UNIX) {
+            c->tcp_nopush = NGX_TCP_NOPUSH_DISABLED;
+            c->tcp_nodelay = NGX_TCP_NODELAY_DISABLED;
+
+#if (NGX_SOLARIS)
+            /* Solaris's sendfilev() supports AF_NCA, AF_INET, and AF_INET6 */
+            c->sendfile = 0;
+#endif
+        }
+
+    } else { /* type == SOCK_DGRAM */
+        c->recv = ngx_udp_recv;
+        c->send = ngx_send;
+        c->send_chain = ngx_udp_send_chain;
+    }
+
+    c->log_error = pc->log_error;
+
+    rev = c->read;
+    wev = c->write;
+
+    rev->log = pc->log;
+    wev->log = pc->log;
+
+    pc->connection = c;
+
+    c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
+
+    if (ngx_add_conn) {
+        if (ngx_add_conn(c) == NGX_ERROR) {
+            goto failed;
+        }
+    }
+
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pc->log, 0,
+                   "connect to %V, fd:%d #%uA", pc->name, s, c->number);
+
+    rc = ngxvcl_connect(s, pc->sockaddr, pc->socklen);
+
+    if (rc == -1) {
+        err = ngx_socket_errno;
+
+
+        if (err != NGX_EINPROGRESS
+#if (NGX_WIN32)
+            /* Winsock returns WSAEWOULDBLOCK (NGX_EAGAIN) */
+            && err != NGX_EAGAIN
+#endif
+            )
+        {
+            if (err == NGX_ECONNREFUSED
+#if (NGX_LINUX)
+                /*
+                 * Linux returns EAGAIN instead of ECONNREFUSED
+                 * for unix sockets if listen queue is full
+                 */
+                || err == NGX_EAGAIN
+#endif
+                || err == NGX_ECONNRESET
+                || err == NGX_ENETDOWN
+                || err == NGX_ENETUNREACH
+                || err == NGX_EHOSTDOWN
+                || err == NGX_EHOSTUNREACH)
+            {
+                level = NGX_LOG_ERR;
+
+            } else {
+                level = NGX_LOG_CRIT;
+            }
+
+            ngx_log_error(level, c->log, err, "connect() to %V failed",
+                          pc->name);
+
+            ngx_close_connection(c);
+            pc->connection = NULL;
+
+            return NGX_DECLINED;
+        }
+    }
+
+    if (ngx_add_conn) {
+        if (rc == -1) {
+
+            /* NGX_EINPROGRESS */
+
+            return NGX_AGAIN;
+        }
+
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pc->log, 0, "connected");
+
+        wev->ready = 1;
+
+        return NGX_OK;
+    }
+
+    if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, ngx_socket_errno,
+                       "connect(): %d", rc);
+
+        if (ngx_blocking(s) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+                          ngx_blocking_n " failed");
+            goto failed;
+        }
+
+        /*
+         * FreeBSD's aio allows to post an operation on non-connected socket.
+         * NT does not support it.
+         *
+         * TODO: check in Win32, etc. As workaround we can use NGX_ONESHOT_EVENT
+         */
+
+        rev->ready = 1;
+        wev->ready = 1;
+
+        return NGX_OK;
+    }
+
+    if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
+
+        /* kqueue */
+
+        event = NGX_CLEAR_EVENT;
+
+    } else {
+
+        /* select, poll, /dev/poll */
+
+        event = NGX_LEVEL_EVENT;
+    }
+
+    if (ngx_add_event(rev, NGX_READ_EVENT, event) != NGX_OK) {
+        goto failed;
+    }
+
+    if (rc == -1) {
+
+        /* NGX_EINPROGRESS */
+
+        if (ngx_add_event(wev, NGX_WRITE_EVENT, event) != NGX_OK) {
+            goto failed;
+        }
+
+        return NGX_AGAIN;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pc->log, 0, "connected");
+
+    wev->ready = 1;
+
+    return NGX_OK;
+
+failed:
+
+    ngx_close_connection(c);
+    pc->connection = NULL;
+
+    return NGX_ERROR;
+}
+
+
+#if (NGX_HAVE_TRANSPARENT_PROXY)
+
+static ngx_int_t
+ngx_event_connect_set_transparent(ngx_peer_connection_t *pc, ngx_socket_t s)
+{
+    int  value;
+
+    value = 1;
+
+#if defined(SO_BINDANY)
+
+    if (ngxvcl_kvfd_setsockopt(s, SOL_SOCKET, SO_BINDANY,
+                   (const void *) &value, sizeof(int)) == -1)
+    {
+        ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+                      "setsockopt(SO_BINDANY) failed");
+        return NGX_ERROR;
+    }
+
+#else
+
+    switch (pc->local->sockaddr->sa_family) {
+
+    case AF_INET:
+
+#if defined(IP_TRANSPARENT)
+
+        if (ngxvcl_kvfd_setsockopt(s, IPPROTO_IP, IP_TRANSPARENT,
+                       (const void *) &value, sizeof(int)) == -1)
+        {
+            ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+                          "setsockopt(IP_TRANSPARENT) failed");
+            return NGX_ERROR;
+        }
+
+#elif defined(IP_BINDANY)
+
+        if (ngxvcl_kvfd_setsockopt(s, IPPROTO_IP, IP_BINDANY,
+                       (const void *) &value, sizeof(int)) == -1)
+        {
+            ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+                          "setsockopt(IP_BINDANY) failed");
+            return NGX_ERROR;
+        }
+
+#endif
+
+        break;
+
+#if (NGX_HAVE_INET6)
+
+    case AF_INET6:
+
+#if defined(IPV6_TRANSPARENT)
+
+        if (ngxvcl_kvfd_setsockopt(s, IPPROTO_IPV6, IPV6_TRANSPARENT,
+                       (const void *) &value, sizeof(int)) == -1)
+        {
+            ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+                          "setsockopt(IPV6_TRANSPARENT) failed");
+            return NGX_ERROR;
+        }
+
+#elif defined(IPV6_BINDANY)
+
+        if (ngxvcl_kvfd_setsockopt(s, IPPROTO_IPV6, IPV6_BINDANY,
+                       (const void *) &value, sizeof(int)) == -1)
+        {
+            ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+                          "setsockopt(IPV6_BINDANY) failed");
+            return NGX_ERROR;
+        }
+
+#else
+
+        ngx_log_error(NGX_LOG_ALERT, pc->log, 0,
+                      "could not enable transparent proxying for IPv6 "
+                      "on this platform");
+
+        return NGX_ERROR;
+
+#endif
+
+        break;
+
+#endif /* NGX_HAVE_INET6 */
+
+    }
+
+#endif /* SO_BINDANY */
+
+    return NGX_OK;
+}
+
+#endif
+
+
+ngx_int_t
+ngx_event_get_peer(ngx_peer_connection_t *pc, void *data)
+{
+    return NGX_OK;
+}
diff --git a/nginx/src/event/ngx_event_connect.h b/nginx/src/event/ngx_event_connect.h
new file mode 100644 (file)
index 0000000..72d21d7
--- /dev/null
@@ -0,0 +1,78 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_EVENT_CONNECT_H_INCLUDED_
+#define _NGX_EVENT_CONNECT_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#define NGX_PEER_KEEPALIVE           1
+#define NGX_PEER_NEXT                2
+#define NGX_PEER_FAILED              4
+
+
+typedef struct ngx_peer_connection_s  ngx_peer_connection_t;
+
+typedef ngx_int_t (*ngx_event_get_peer_pt)(ngx_peer_connection_t *pc,
+    void *data);
+typedef void (*ngx_event_free_peer_pt)(ngx_peer_connection_t *pc, void *data,
+    ngx_uint_t state);
+typedef void (*ngx_event_notify_peer_pt)(ngx_peer_connection_t *pc,
+    void *data, ngx_uint_t type);
+typedef ngx_int_t (*ngx_event_set_peer_session_pt)(ngx_peer_connection_t *pc,
+    void *data);
+typedef void (*ngx_event_save_peer_session_pt)(ngx_peer_connection_t *pc,
+    void *data);
+
+
+struct ngx_peer_connection_s {
+    ngx_connection_t                *connection;
+
+    struct sockaddr                 *sockaddr;
+    socklen_t                        socklen;
+    ngx_str_t                       *name;
+
+    ngx_uint_t                       tries;
+    ngx_msec_t                       start_time;
+
+    ngx_event_get_peer_pt            get;
+    ngx_event_free_peer_pt           free;
+    ngx_event_notify_peer_pt         notify;
+    void                            *data;
+
+#if (NGX_SSL || NGX_COMPAT)
+    ngx_event_set_peer_session_pt    set_session;
+    ngx_event_save_peer_session_pt   save_session;
+#endif
+
+    ngx_addr_t                      *local;
+
+    int                              type;
+    int                              rcvbuf;
+
+    ngx_log_t                       *log;
+
+    unsigned                         cached:1;
+    unsigned                         transparent:1;
+
+                                     /* ngx_connection_log_error_e */
+    unsigned                         log_error:2;
+
+    NGX_COMPAT_BEGIN(2)
+    NGX_COMPAT_END
+};
+
+
+ngx_int_t ngx_event_connect_peer(ngx_peer_connection_t *pc);
+ngx_int_t ngx_event_get_peer(ngx_peer_connection_t *pc, void *data);
+
+
+#endif /* _NGX_EVENT_CONNECT_H_INCLUDED_ */
diff --git a/nginx/src/event/ngx_event_openssl.c b/nginx/src/event/ngx_event_openssl.c
new file mode 100644 (file)
index 0000000..c4b51b5
--- /dev/null
@@ -0,0 +1,4279 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#define NGX_SSL_PASSWORD_BUFFER_SIZE  4096
+
+
+typedef struct {
+    ngx_uint_t  engine;   /* unsigned  engine:1; */
+} ngx_openssl_conf_t;
+
+
+static int ngx_ssl_password_callback(char *buf, int size, int rwflag,
+    void *userdata);
+static int ngx_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store);
+static void ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where,
+    int ret);
+static void ngx_ssl_passwords_cleanup(void *data);
+static void ngx_ssl_handshake_handler(ngx_event_t *ev);
+static ngx_int_t ngx_ssl_handle_recv(ngx_connection_t *c, int n);
+static void ngx_ssl_write_handler(ngx_event_t *wev);
+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,
+    ngx_err_t err, char *text);
+static void ngx_ssl_clear_error(ngx_log_t *log);
+
+static ngx_int_t ngx_ssl_session_id_context(ngx_ssl_t *ssl,
+    ngx_str_t *sess_ctx);
+ngx_int_t ngx_ssl_session_cache_init(ngx_shm_zone_t *shm_zone, void *data);
+static int ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn,
+    ngx_ssl_session_t *sess);
+static ngx_ssl_session_t *ngx_ssl_get_cached_session(ngx_ssl_conn_t *ssl_conn,
+#if OPENSSL_VERSION_NUMBER >= 0x10100003L
+    const
+#endif
+    u_char *id, int len, int *copy);
+static void ngx_ssl_remove_session(SSL_CTX *ssl, ngx_ssl_session_t *sess);
+static void ngx_ssl_expire_sessions(ngx_ssl_session_cache_t *cache,
+    ngx_slab_pool_t *shpool, ngx_uint_t n);
+static void ngx_ssl_session_rbtree_insert_value(ngx_rbtree_node_t *temp,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
+
+#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
+static int ngx_ssl_session_ticket_key_callback(ngx_ssl_conn_t *ssl_conn,
+    unsigned char *name, unsigned char *iv, EVP_CIPHER_CTX *ectx,
+    HMAC_CTX *hctx, int enc);
+#endif
+
+#ifndef X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT
+static ngx_int_t ngx_ssl_check_name(ngx_str_t *name, ASN1_STRING *str);
+#endif
+
+static time_t ngx_ssl_parse_time(
+#if OPENSSL_VERSION_NUMBER > 0x10100000L
+    const
+#endif
+    ASN1_TIME *asn1time);
+
+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);
+static void ngx_openssl_exit(ngx_cycle_t *cycle);
+
+
+static ngx_command_t  ngx_openssl_commands[] = {
+
+    { ngx_string("ssl_engine"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+      ngx_openssl_engine,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_core_module_t  ngx_openssl_module_ctx = {
+    ngx_string("openssl"),
+    ngx_openssl_create_conf,
+    NULL
+};
+
+
+ngx_module_t  ngx_openssl_module = {
+    NGX_MODULE_V1,
+    &ngx_openssl_module_ctx,               /* module context */
+    ngx_openssl_commands,                  /* module directives */
+    NGX_CORE_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    ngx_openssl_exit,                      /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+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_certificate_index;
+int  ngx_ssl_next_certificate_index;
+int  ngx_ssl_certificate_name_index;
+int  ngx_ssl_stapling_index;
+
+
+ngx_int_t
+ngx_ssl_init(ngx_log_t *log)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x10100003L
+
+    if (OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL) == 0) {
+        ngx_ssl_error(NGX_LOG_ALERT, log, 0, "OPENSSL_init_ssl() failed");
+        return NGX_ERROR;
+    }
+
+    /*
+     * OPENSSL_init_ssl() may leave errors in the error queue
+     * while returning success
+     */
+
+    ERR_clear_error();
+
+#else
+
+    OPENSSL_config(NULL);
+
+    SSL_library_init();
+    SSL_load_error_strings();
+
+    OpenSSL_add_all_algorithms();
+
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+#ifndef SSL_OP_NO_COMPRESSION
+    {
+    /*
+     * Disable gzip compression in OpenSSL prior to 1.0.0 version,
+     * this saves about 522K per connection.
+     */
+    int                  n;
+    STACK_OF(SSL_COMP)  *ssl_comp_methods;
+
+    ssl_comp_methods = SSL_COMP_get_compression_methods();
+    n = sk_SSL_COMP_num(ssl_comp_methods);
+
+    while (n--) {
+        (void) sk_SSL_COMP_pop(ssl_comp_methods);
+    }
+    }
+#endif
+#endif
+
+    ngx_ssl_connection_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+
+    if (ngx_ssl_connection_index == -1) {
+        ngx_ssl_error(NGX_LOG_ALERT, log, 0, "SSL_get_ex_new_index() failed");
+        return NGX_ERROR;
+    }
+
+    ngx_ssl_server_conf_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
+                                                         NULL);
+    if (ngx_ssl_server_conf_index == -1) {
+        ngx_ssl_error(NGX_LOG_ALERT, log, 0,
+                      "SSL_CTX_get_ex_new_index() failed");
+        return NGX_ERROR;
+    }
+
+    ngx_ssl_session_cache_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
+                                                           NULL);
+    if (ngx_ssl_session_cache_index == -1) {
+        ngx_ssl_error(NGX_LOG_ALERT, log, 0,
+                      "SSL_CTX_get_ex_new_index() failed");
+        return NGX_ERROR;
+    }
+
+    ngx_ssl_session_ticket_keys_index = SSL_CTX_get_ex_new_index(0, NULL, NULL,
+                                                                 NULL, NULL);
+    if (ngx_ssl_session_ticket_keys_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) {
+        ngx_ssl_error(NGX_LOG_ALERT, log, 0,
+                      "SSL_CTX_get_ex_new_index() failed");
+        return NGX_ERROR;
+    }
+
+    ngx_ssl_next_certificate_index = X509_get_ex_new_index(0, NULL, NULL, NULL,
+                                                           NULL);
+    if (ngx_ssl_next_certificate_index == -1) {
+        ngx_ssl_error(NGX_LOG_ALERT, log, 0, "X509_get_ex_new_index() failed");
+        return NGX_ERROR;
+    }
+
+    ngx_ssl_certificate_name_index = X509_get_ex_new_index(0, NULL, NULL, NULL,
+                                                           NULL);
+
+    if (ngx_ssl_certificate_name_index == -1) {
+        ngx_ssl_error(NGX_LOG_ALERT, log, 0, "X509_get_ex_new_index() failed");
+        return NGX_ERROR;
+    }
+
+    ngx_ssl_stapling_index = X509_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+
+    if (ngx_ssl_stapling_index == -1) {
+        ngx_ssl_error(NGX_LOG_ALERT, log, 0, "X509_get_ex_new_index() failed");
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data)
+{
+    ssl->ctx = SSL_CTX_new(SSLv23_method());
+
+    if (ssl->ctx == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "SSL_CTX_new() failed");
+        return NGX_ERROR;
+    }
+
+    if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_server_conf_index, data) == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_set_ex_data() failed");
+        return NGX_ERROR;
+    }
+
+    if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_certificate_index, NULL) == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_set_ex_data() failed");
+        return NGX_ERROR;
+    }
+
+    ssl->buffer_size = NGX_SSL_BUFSIZE;
+
+    /* client side options */
+
+#ifdef SSL_OP_MICROSOFT_SESS_ID_BUG
+    SSL_CTX_set_options(ssl->ctx, SSL_OP_MICROSOFT_SESS_ID_BUG);
+#endif
+
+#ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG
+    SSL_CTX_set_options(ssl->ctx, SSL_OP_NETSCAPE_CHALLENGE_BUG);
+#endif
+
+    /* server side options */
+
+#ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
+    SSL_CTX_set_options(ssl->ctx, SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG);
+#endif
+
+#ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
+    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
+
+#ifdef SSL_OP_TLS_D5_BUG
+    SSL_CTX_set_options(ssl->ctx, SSL_OP_TLS_D5_BUG);
+#endif
+
+#ifdef SSL_OP_TLS_BLOCK_PADDING_BUG
+    SSL_CTX_set_options(ssl->ctx, SSL_OP_TLS_BLOCK_PADDING_BUG);
+#endif
+
+#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
+    SSL_CTX_set_options(ssl->ctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);
+#endif
+
+    SSL_CTX_set_options(ssl->ctx, SSL_OP_SINGLE_DH_USE);
+
+#if OPENSSL_VERSION_NUMBER >= 0x009080dfL
+    /* only in 0.9.8m+ */
+    SSL_CTX_clear_options(ssl->ctx,
+                          SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1);
+#endif
+
+    if (!(protocols & NGX_SSL_SSLv2)) {
+        SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_SSLv2);
+    }
+    if (!(protocols & NGX_SSL_SSLv3)) {
+        SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_SSLv3);
+    }
+    if (!(protocols & NGX_SSL_TLSv1)) {
+        SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_TLSv1);
+    }
+#ifdef SSL_OP_NO_TLSv1_1
+    SSL_CTX_clear_options(ssl->ctx, SSL_OP_NO_TLSv1_1);
+    if (!(protocols & NGX_SSL_TLSv1_1)) {
+        SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_TLSv1_1);
+    }
+#endif
+#ifdef SSL_OP_NO_TLSv1_2
+    SSL_CTX_clear_options(ssl->ctx, SSL_OP_NO_TLSv1_2);
+    if (!(protocols & NGX_SSL_TLSv1_2)) {
+        SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_TLSv1_2);
+    }
+#endif
+#ifdef SSL_OP_NO_TLSv1_3
+    SSL_CTX_clear_options(ssl->ctx, SSL_OP_NO_TLSv1_3);
+    if (!(protocols & NGX_SSL_TLSv1_3)) {
+        SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_TLSv1_3);
+    }
+#endif
+
+#ifdef SSL_CTX_set_min_proto_version
+    SSL_CTX_set_min_proto_version(ssl->ctx, 0);
+    SSL_CTX_set_max_proto_version(ssl->ctx, TLS1_2_VERSION);
+#endif
+
+#ifdef TLS1_3_VERSION
+    SSL_CTX_set_min_proto_version(ssl->ctx, 0);
+    SSL_CTX_set_max_proto_version(ssl->ctx, TLS1_3_VERSION);
+#endif
+
+#ifdef SSL_OP_NO_COMPRESSION
+    SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_COMPRESSION);
+#endif
+
+#ifdef SSL_MODE_RELEASE_BUFFERS
+    SSL_CTX_set_mode(ssl->ctx, SSL_MODE_RELEASE_BUFFERS);
+#endif
+
+#ifdef SSL_MODE_NO_AUTO_CHAIN
+    SSL_CTX_set_mode(ssl->ctx, SSL_MODE_NO_AUTO_CHAIN);
+#endif
+
+    SSL_CTX_set_read_ahead(ssl->ctx, 1);
+
+    SSL_CTX_set_info_callback(ssl->ctx, ngx_ssl_info_callback);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_certificates(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *certs,
+    ngx_array_t *keys, ngx_array_t *passwords)
+{
+    ngx_str_t   *cert, *key;
+    ngx_uint_t   i;
+
+    cert = certs->elts;
+    key = keys->elts;
+
+    for (i = 0; i < certs->nelts; i++) {
+
+        if (ngx_ssl_certificate(cf, ssl, &cert[i], &key[i], passwords)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert,
+    ngx_str_t *key, ngx_array_t *passwords)
+{
+    BIO         *bio;
+    X509        *x509;
+    u_long       n;
+    ngx_str_t   *pwd;
+    ngx_uint_t   tries;
+
+    if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    /*
+     * we can't use SSL_CTX_use_certificate_chain_file() as it doesn't
+     * allow to access certificate later from SSL_CTX, so we reimplement
+     * it here
+     */
+
+    bio = BIO_new_file((char *) cert->data, "r");
+    if (bio == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "BIO_new_file(\"%s\") failed", cert->data);
+        return NGX_ERROR;
+    }
+
+    x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL);
+    if (x509 == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "PEM_read_bio_X509_AUX(\"%s\") failed", cert->data);
+        BIO_free(bio);
+        return NGX_ERROR;
+    }
+
+    if (SSL_CTX_use_certificate(ssl->ctx, x509) == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_use_certificate(\"%s\") failed", cert->data);
+        X509_free(x509);
+        BIO_free(bio);
+        return NGX_ERROR;
+    }
+
+    if (X509_set_ex_data(x509, ngx_ssl_certificate_name_index, cert->data)
+        == 0)
+    {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_set_ex_data() failed");
+        X509_free(x509);
+        BIO_free(bio);
+        return NGX_ERROR;
+    }
+
+    if (X509_set_ex_data(x509, ngx_ssl_next_certificate_index,
+                      SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index))
+        == 0)
+    {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_set_ex_data() failed");
+        X509_free(x509);
+        BIO_free(bio);
+        return NGX_ERROR;
+    }
+
+    if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_certificate_index, x509)
+        == 0)
+    {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_set_ex_data() failed");
+        X509_free(x509);
+        BIO_free(bio);
+        return NGX_ERROR;
+    }
+
+    /* read rest of the chain */
+
+    for ( ;; ) {
+
+        x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
+        if (x509 == NULL) {
+            n = ERR_peek_last_error();
+
+            if (ERR_GET_LIB(n) == ERR_LIB_PEM
+                && ERR_GET_REASON(n) == PEM_R_NO_START_LINE)
+            {
+                /* end of file */
+                ERR_clear_error();
+                break;
+            }
+
+            /* some real error */
+
+            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                          "PEM_read_bio_X509(\"%s\") failed", cert->data);
+            BIO_free(bio);
+            return NGX_ERROR;
+        }
+
+#ifdef SSL_CTRL_CHAIN_CERT
+
+        /*
+         * SSL_CTX_add0_chain_cert() is needed to add chain to
+         * a particular certificate when multiple certificates are used;
+         * only available in OpenSSL 1.0.2+
+         */
+
+        if (SSL_CTX_add0_chain_cert(ssl->ctx, x509) == 0) {
+            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                          "SSL_CTX_add0_chain_cert(\"%s\") failed",
+                          cert->data);
+            X509_free(x509);
+            BIO_free(bio);
+            return NGX_ERROR;
+        }
+
+#else
+        if (SSL_CTX_add_extra_chain_cert(ssl->ctx, x509) == 0) {
+            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                          "SSL_CTX_add_extra_chain_cert(\"%s\") failed",
+                          cert->data);
+            X509_free(x509);
+            BIO_free(bio);
+            return NGX_ERROR;
+        }
+#endif
+    }
+
+    BIO_free(bio);
+
+    if (ngx_strncmp(key->data, "engine:", sizeof("engine:") - 1) == 0) {
+
+#ifndef OPENSSL_NO_ENGINE
+
+        u_char      *p, *last;
+        ENGINE      *engine;
+        EVP_PKEY    *pkey;
+
+        p = key->data + sizeof("engine:") - 1;
+        last = (u_char *) ngx_strchr(p, ':');
+
+        if (last == NULL) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid syntax in \"%V\"", key);
+            return NGX_ERROR;
+        }
+
+        *last = '\0';
+
+        engine = ENGINE_by_id((char *) p);
+
+        if (engine == NULL) {
+            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                          "ENGINE_by_id(\"%s\") failed", p);
+            return NGX_ERROR;
+        }
+
+        *last++ = ':';
+
+        pkey = ENGINE_load_private_key(engine, (char *) last, 0, 0);
+
+        if (pkey == NULL) {
+            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                          "ENGINE_load_private_key(\"%s\") failed", last);
+            ENGINE_free(engine);
+            return NGX_ERROR;
+        }
+
+        ENGINE_free(engine);
+
+        if (SSL_CTX_use_PrivateKey(ssl->ctx, pkey) == 0) {
+            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                          "SSL_CTX_use_PrivateKey(\"%s\") failed", last);
+            EVP_PKEY_free(pkey);
+            return NGX_ERROR;
+        }
+
+        EVP_PKEY_free(pkey);
+
+        return NGX_OK;
+
+#else
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "loading \"engine:...\" certificate keys "
+                           "is not supported");
+        return NGX_ERROR;
+
+#endif
+    }
+
+    if (ngx_conf_full_name(cf->cycle, key, 1) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (passwords) {
+        tries = passwords->nelts;
+        pwd = passwords->elts;
+
+        SSL_CTX_set_default_passwd_cb(ssl->ctx, ngx_ssl_password_callback);
+        SSL_CTX_set_default_passwd_cb_userdata(ssl->ctx, pwd);
+
+    } else {
+        tries = 1;
+#if (NGX_SUPPRESS_WARN)
+        pwd = NULL;
+#endif
+    }
+
+    for ( ;; ) {
+
+        if (SSL_CTX_use_PrivateKey_file(ssl->ctx, (char *) key->data,
+                                        SSL_FILETYPE_PEM)
+            != 0)
+        {
+            break;
+        }
+
+        if (--tries) {
+            ERR_clear_error();
+            SSL_CTX_set_default_passwd_cb_userdata(ssl->ctx, ++pwd);
+            continue;
+        }
+
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_use_PrivateKey_file(\"%s\") failed", key->data);
+        return NGX_ERROR;
+    }
+
+    SSL_CTX_set_default_passwd_cb(ssl->ctx, NULL);
+
+    return NGX_OK;
+}
+
+
+static int
+ngx_ssl_password_callback(char *buf, int size, int rwflag, void *userdata)
+{
+    ngx_str_t *pwd = userdata;
+
+    if (rwflag) {
+        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+                      "ngx_ssl_password_callback() is called for encryption");
+        return 0;
+    }
+
+    if (pwd->len > (size_t) size) {
+        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
+                      "password is truncated to %d bytes", size);
+    } else {
+        size = pwd->len;
+    }
+
+    ngx_memcpy(buf, pwd->data, size);
+
+    return size;
+}
+
+
+ngx_int_t
+ngx_ssl_ciphers(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *ciphers,
+    ngx_uint_t prefer_server_ciphers)
+{
+    if (SSL_CTX_set_cipher_list(ssl->ctx, (char *) ciphers->data) == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_set_cipher_list(\"%V\") failed",
+                      ciphers);
+        return NGX_ERROR;
+    }
+
+    if (prefer_server_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;
+}
+
+
+ngx_int_t
+ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert,
+    ngx_int_t depth)
+{
+    STACK_OF(X509_NAME)  *list;
+
+    SSL_CTX_set_verify(ssl->ctx, SSL_VERIFY_PEER, ngx_ssl_verify_callback);
+
+    SSL_CTX_set_verify_depth(ssl->ctx, depth);
+
+    if (cert->len == 0) {
+        return NGX_OK;
+    }
+
+    if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (SSL_CTX_load_verify_locations(ssl->ctx, (char *) cert->data, NULL)
+        == 0)
+    {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_load_verify_locations(\"%s\") failed",
+                      cert->data);
+        return NGX_ERROR;
+    }
+
+    /*
+     * SSL_CTX_load_verify_locations() may leave errors in the error queue
+     * while returning success
+     */
+
+    ERR_clear_error();
+
+    list = SSL_load_client_CA_file((char *) cert->data);
+
+    if (list == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_load_client_CA_file(\"%s\") failed", cert->data);
+        return NGX_ERROR;
+    }
+
+    /*
+     * before 0.9.7h and 0.9.8 SSL_load_client_CA_file()
+     * always leaved an error in the error queue
+     */
+
+    ERR_clear_error();
+
+    SSL_CTX_set_client_CA_list(ssl->ctx, list);
+
+    return NGX_OK;
+}
+
+
+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_depth(ssl->ctx, depth);
+
+    if (cert->len == 0) {
+        return NGX_OK;
+    }
+
+    if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (SSL_CTX_load_verify_locations(ssl->ctx, (char *) cert->data, NULL)
+        == 0)
+    {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_load_verify_locations(\"%s\") failed",
+                      cert->data);
+        return NGX_ERROR;
+    }
+
+    /*
+     * SSL_CTX_load_verify_locations() may leave errors in the error queue
+     * while returning success
+     */
+
+    ERR_clear_error();
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_crl(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *crl)
+{
+    X509_STORE   *store;
+    X509_LOOKUP  *lookup;
+
+    if (crl->len == 0) {
+        return NGX_OK;
+    }
+
+    if (ngx_conf_full_name(cf->cycle, crl, 1) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    store = SSL_CTX_get_cert_store(ssl->ctx);
+
+    if (store == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_get_cert_store() failed");
+        return NGX_ERROR;
+    }
+
+    lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
+
+    if (lookup == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "X509_STORE_add_lookup() failed");
+        return NGX_ERROR;
+    }
+
+    if (X509_LOOKUP_load_file(lookup, (char *) crl->data, X509_FILETYPE_PEM)
+        == 0)
+    {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "X509_LOOKUP_load_file(\"%s\") failed", crl->data);
+        return NGX_ERROR;
+    }
+
+    X509_STORE_set_flags(store,
+                         X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
+
+    return NGX_OK;
+}
+
+
+static int
+ngx_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store)
+{
+#if (NGX_DEBUG)
+    char              *subject, *issuer;
+    int                err, depth;
+    X509              *cert;
+    X509_NAME         *sname, *iname;
+    ngx_connection_t  *c;
+    ngx_ssl_conn_t    *ssl_conn;
+
+    ssl_conn = X509_STORE_CTX_get_ex_data(x509_store,
+                                          SSL_get_ex_data_X509_STORE_CTX_idx());
+
+    c = ngx_ssl_get_connection(ssl_conn);
+
+    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)";
+
+    iname = X509_get_issuer_name(cert);
+    issuer = iname ? X509_NAME_oneline(iname, NULL, 0) : "(none)";
+
+    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);
+
+    if (sname) {
+        OPENSSL_free(subject);
+    }
+
+    if (iname) {
+        OPENSSL_free(issuer);
+    }
+#endif
+
+    return 1;
+}
+
+
+static void
+ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where, int ret)
+{
+    BIO               *rbio, *wbio;
+    ngx_connection_t  *c;
+
+    if ((where & SSL_CB_HANDSHAKE_START)
+        && SSL_is_server((ngx_ssl_conn_t *) ssl_conn))
+    {
+        c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
+
+        if (c->ssl->handshaked) {
+            c->ssl->renegotiation = 1;
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL renegotiation");
+        }
+    }
+
+    if ((where & SSL_CB_ACCEPT_LOOP) == SSL_CB_ACCEPT_LOOP) {
+        c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
+
+        if (!c->ssl->handshake_buffer_set) {
+            /*
+             * By default OpenSSL uses 4k buffer during a handshake,
+             * which is too low for long certificate chains and might
+             * result in extra round-trips.
+             *
+             * To adjust a buffer size we detect that buffering was added
+             * to write side of the connection by comparing rbio and wbio.
+             * If they are different, we assume that it's due to buffering
+             * added to wbio, and set buffer size.
+             */
+
+            rbio = SSL_get_rbio((ngx_ssl_conn_t *) ssl_conn);
+            wbio = SSL_get_wbio((ngx_ssl_conn_t *) ssl_conn);
+
+            if (rbio != wbio) {
+                (void) BIO_set_write_buffer_size(wbio, NGX_SSL_BUFSIZE);
+                c->ssl->handshake_buffer_set = 1;
+            }
+        }
+    }
+}
+
+
+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)
+{
+    u_char              *p, *last, *end;
+    size_t               len;
+    ssize_t              n;
+    ngx_fd_t             fd;
+    ngx_str_t           *pwd;
+    ngx_array_t         *passwords;
+    ngx_pool_cleanup_t  *cln;
+    u_char               buf[NGX_SSL_PASSWORD_BUFFER_SIZE];
+
+    if (ngx_conf_full_name(cf->cycle, file, 1) != NGX_OK) {
+        return NULL;
+    }
+
+    cln = ngx_pool_cleanup_add(cf->temp_pool, 0);
+    passwords = ngx_array_create(cf->temp_pool, 4, sizeof(ngx_str_t));
+
+    if (cln == NULL || passwords == NULL) {
+        return NULL;
+    }
+
+    cln->handler = ngx_ssl_passwords_cleanup;
+    cln->data = passwords;
+
+    fd = ngx_open_file(file->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
+
+    if (fd == NGX_INVALID_FILE) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
+                           ngx_open_file_n " \"%s\" failed", file->data);
+        return NULL;
+    }
+
+    len = 0;
+    last = buf;
+
+    do {
+        n = ngx_read_fd(fd, last, NGX_SSL_PASSWORD_BUFFER_SIZE - len);
+
+        if (n == -1) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
+                               ngx_read_fd_n " \"%s\" failed", file->data);
+            passwords = NULL;
+            goto cleanup;
+        }
+
+        end = last + n;
+
+        if (len && n == 0) {
+            *end++ = LF;
+        }
+
+        p = buf;
+
+        for ( ;; ) {
+            last = ngx_strlchr(last, end, LF);
+
+            if (last == NULL) {
+                break;
+            }
+
+            len = last++ - p;
+
+            if (len && p[len - 1] == CR) {
+                len--;
+            }
+
+            if (len) {
+                pwd = ngx_array_push(passwords);
+                if (pwd == NULL) {
+                    passwords = NULL;
+                    goto cleanup;
+                }
+
+                pwd->len = len;
+                pwd->data = ngx_pnalloc(cf->temp_pool, len);
+
+                if (pwd->data == NULL) {
+                    passwords->nelts--;
+                    passwords = NULL;
+                    goto cleanup;
+                }
+
+                ngx_memcpy(pwd->data, p, len);
+            }
+
+            p = last;
+        }
+
+        len = end - p;
+
+        if (len == NGX_SSL_PASSWORD_BUFFER_SIZE) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "too long line in \"%s\"", file->data);
+            passwords = NULL;
+            goto cleanup;
+        }
+
+        ngx_memmove(buf, p, len);
+        last = buf + len;
+
+    } while (n != 0);
+
+    if (passwords->nelts == 0) {
+        pwd = ngx_array_push(passwords);
+        if (pwd == NULL) {
+            passwords = NULL;
+            goto cleanup;
+        }
+
+        ngx_memzero(pwd, sizeof(ngx_str_t));
+    }
+
+cleanup:
+
+    if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+        ngx_conf_log_error(NGX_LOG_ALERT, cf, ngx_errno,
+                           ngx_close_file_n " \"%s\" failed", file->data);
+    }
+
+    ngx_memzero(buf, NGX_SSL_PASSWORD_BUFFER_SIZE);
+
+    return passwords;
+}
+
+
+static void
+ngx_ssl_passwords_cleanup(void *data)
+{
+    ngx_array_t *passwords = data;
+
+    ngx_str_t   *pwd;
+    ngx_uint_t   i;
+
+    pwd = passwords->elts;
+
+    for (i = 0; i < passwords->nelts; i++) {
+        ngx_memzero(pwd[i].data, pwd[i].len);
+    }
+}
+
+
+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) {
+        return NGX_OK;
+    }
+
+    if (ngx_conf_full_name(cf->cycle, file, 1) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    bio = BIO_new_file((char *) file->data, "r");
+    if (bio == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "BIO_new_file(\"%s\") failed", file->data);
+        return NGX_ERROR;
+    }
+
+    dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
+    if (dh == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "PEM_read_bio_DHparams(\"%s\") failed", file->data);
+        BIO_free(bio);
+        return NGX_ERROR;
+    }
+
+    SSL_CTX_set_tmp_dh(ssl->ctx, dh);
+
+    DH_free(dh);
+    BIO_free(bio);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+#ifndef OPENSSL_NO_ECDH
+
+    /*
+     * Elliptic-Curve Diffie-Hellman parameters are either "named curves"
+     * from RFC 4492 section 5.1.1, or explicitly described curves over
+     * binary fields.  OpenSSL only supports the "named curves", which provide
+     * maximum interoperability.
+     */
+
+#if (defined SSL_CTX_set1_curves_list || defined SSL_CTRL_SET_CURVES_LIST)
+
+    /*
+     * OpenSSL 1.0.2+ allows configuring a curve list instead of a single
+     * curve previously supported.  By default an internal list is used,
+     * with prime256v1 being preferred by server in OpenSSL 1.0.2b+
+     * and X25519 in OpenSSL 1.1.0+.
+     *
+     * By default a curve preferred by the client will be used for
+     * key exchange.  The SSL_OP_CIPHER_SERVER_PREFERENCE option can
+     * be used to prefer server curves instead, similar to what it
+     * does for ciphers.
+     */
+
+    SSL_CTX_set_options(ssl->ctx, SSL_OP_SINGLE_ECDH_USE);
+
+#if SSL_CTRL_SET_ECDH_AUTO
+    /* not needed in OpenSSL 1.1.0+ */
+    SSL_CTX_set_ecdh_auto(ssl->ctx, 1);
+#endif
+
+    if (ngx_strcmp(name->data, "auto") == 0) {
+        return NGX_OK;
+    }
+
+    if (SSL_CTX_set1_curves_list(ssl->ctx, (char *) name->data) == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_set1_curves_list(\"%s\") failed", name->data);
+        return NGX_ERROR;
+    }
+
+#else
+
+    int      nid;
+    char    *curve;
+    EC_KEY  *ecdh;
+
+    if (ngx_strcmp(name->data, "auto") == 0) {
+        curve = "prime256v1";
+
+    } else {
+        curve = (char *) name->data;
+    }
+
+    nid = OBJ_sn2nid(curve);
+    if (nid == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "OBJ_sn2nid(\"%s\") failed: unknown curve", curve);
+        return NGX_ERROR;
+    }
+
+    ecdh = EC_KEY_new_by_curve_name(nid);
+    if (ecdh == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "EC_KEY_new_by_curve_name(\"%s\") failed", curve);
+        return NGX_ERROR;
+    }
+
+    SSL_CTX_set_options(ssl->ctx, SSL_OP_SINGLE_ECDH_USE);
+
+    SSL_CTX_set_tmp_ecdh(ssl->ctx, ecdh);
+
+    EC_KEY_free(ecdh);
+#endif
+#endif
+#endif
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c, ngx_uint_t flags)
+{
+    ngx_ssl_connection_t  *sc;
+
+    sc = ngx_pcalloc(c->pool, sizeof(ngx_ssl_connection_t));
+    if (sc == NULL) {
+        return NGX_ERROR;
+    }
+
+    sc->buffer = ((flags & NGX_SSL_BUFFER) != 0);
+    sc->buffer_size = ssl->buffer_size;
+
+    sc->session_ctx = ssl->ctx;
+
+    sc->connection = SSL_new(ssl->ctx);
+
+    if (sc->connection == NULL) {
+        ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_new() failed");
+        return NGX_ERROR;
+    }
+
+    if (SSL_set_fd(sc->connection, c->fd) == 0) {
+        ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_set_fd() failed");
+        return NGX_ERROR;
+    }
+
+    if (flags & NGX_SSL_CLIENT) {
+        SSL_set_connect_state(sc->connection);
+
+    } else {
+        SSL_set_accept_state(sc->connection);
+    }
+
+    if (SSL_set_ex_data(sc->connection, ngx_ssl_connection_index, c) == 0) {
+        ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_set_ex_data() failed");
+        return NGX_ERROR;
+    }
+
+    c->ssl = sc;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_set_session(ngx_connection_t *c, ngx_ssl_session_t *session)
+{
+    if (session) {
+        if (SSL_set_session(c->ssl->connection, session) == 0) {
+            ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_set_session() failed");
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_handshake(ngx_connection_t *c)
+{
+    int        n, sslerr;
+    ngx_err_t  err;
+
+    ngx_ssl_clear_error(c->log);
+
+    n = SSL_do_handshake(c->ssl->connection);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n);
+
+    if (n == 1) {
+
+        if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+#if (NGX_DEBUG)
+        {
+        char         buf[129], *s, *d;
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+        const
+#endif
+        SSL_CIPHER  *cipher;
+
+        cipher = SSL_get_current_cipher(c->ssl->connection);
+
+        if (cipher) {
+            SSL_CIPHER_description(cipher, &buf[1], 128);
+
+            for (s = &buf[1], d = buf; *s; s++) {
+                if (*s == ' ' && *d == ' ') {
+                    continue;
+                }
+
+                if (*s == LF || *s == CR) {
+                    continue;
+                }
+
+                *++d = *s;
+            }
+
+            if (*d != ' ') {
+                d++;
+            }
+
+            *d = '\0';
+
+            ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                           "SSL: %s, cipher: \"%s\"",
+                           SSL_get_version(c->ssl->connection), &buf[1]);
+
+            if (SSL_session_reused(c->ssl->connection)) {
+                ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                               "SSL reused session");
+            }
+
+        } else {
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                           "SSL no shared ciphers");
+        }
+        }
+#endif
+
+        c->ssl->handshaked = 1;
+
+        c->recv = ngx_ssl_recv;
+        c->send = ngx_ssl_write;
+        c->recv_chain = ngx_ssl_recv_chain;
+        c->send_chain = ngx_ssl_send_chain;
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+#ifdef SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS
+
+        /* initial handshake done, disable renegotiation (CVE-2009-3555) */
+        if (c->ssl->connection->s3 && SSL_is_server(c->ssl->connection)) {
+            c->ssl->connection->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS;
+        }
+
+#endif
+#endif
+
+        return NGX_OK;
+    }
+
+    sslerr = SSL_get_error(c->ssl->connection, n);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr);
+
+    if (sslerr == SSL_ERROR_WANT_READ) {
+        c->read->ready = 0;
+        c->read->handler = ngx_ssl_handshake_handler;
+        c->write->handler = ngx_ssl_handshake_handler;
+
+        if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        return NGX_AGAIN;
+    }
+
+    if (sslerr == SSL_ERROR_WANT_WRITE) {
+        c->write->ready = 0;
+        c->read->handler = ngx_ssl_handshake_handler;
+        c->write->handler = ngx_ssl_handshake_handler;
+
+        if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        return NGX_AGAIN;
+    }
+
+    err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0;
+
+    c->ssl->no_wait_shutdown = 1;
+    c->ssl->no_send_shutdown = 1;
+    c->read->eof = 1;
+
+    if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) {
+        ngx_connection_error(c, err,
+                             "peer closed connection in SSL handshake");
+
+        return NGX_ERROR;
+    }
+
+    c->read->error = 1;
+
+    ngx_ssl_connection_error(c, sslerr, err, "SSL_do_handshake() failed");
+
+    return NGX_ERROR;
+}
+
+
+static void
+ngx_ssl_handshake_handler(ngx_event_t *ev)
+{
+    ngx_connection_t  *c;
+
+    c = ev->data;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "SSL handshake handler: %d", ev->write);
+
+    if (ev->timedout) {
+        c->ssl->handler(c);
+        return;
+    }
+
+    if (ngx_ssl_handshake(c) == NGX_AGAIN) {
+        return;
+    }
+
+    c->ssl->handler(c);
+}
+
+
+ssize_t
+ngx_ssl_recv_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t limit)
+{
+    u_char     *last;
+    ssize_t     n, bytes, size;
+    ngx_buf_t  *b;
+
+    bytes = 0;
+
+    b = cl->buf;
+    last = b->last;
+
+    for ( ;; ) {
+        size = b->end - last;
+
+        if (limit) {
+            if (bytes >= limit) {
+                return bytes;
+            }
+
+            if (bytes + size > limit) {
+                size = (ssize_t) (limit - bytes);
+            }
+        }
+
+        n = ngx_ssl_recv(c, last, size);
+
+        if (n > 0) {
+            last += n;
+            bytes += n;
+
+            if (last == b->end) {
+                cl = cl->next;
+
+                if (cl == NULL) {
+                    return bytes;
+                }
+
+                b = cl->buf;
+                last = b->last;
+            }
+
+            continue;
+        }
+
+        if (bytes) {
+
+            if (n == 0 || n == NGX_ERROR) {
+                c->read->ready = 1;
+            }
+
+            return bytes;
+        }
+
+        return n;
+    }
+}
+
+
+ssize_t
+ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size)
+{
+    int  n, bytes;
+
+    if (c->ssl->last == NGX_ERROR) {
+        c->read->error = 1;
+        return NGX_ERROR;
+    }
+
+    if (c->ssl->last == NGX_DONE) {
+        c->read->ready = 0;
+        c->read->eof = 1;
+        return 0;
+    }
+
+    bytes = 0;
+
+    ngx_ssl_clear_error(c->log);
+
+    /*
+     * SSL_read() may return data in parts, so try to read
+     * until SSL_read() would return no data
+     */
+
+    for ( ;; ) {
+
+        n = SSL_read(c->ssl->connection, buf, size);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_read: %d", n);
+
+        if (n > 0) {
+            bytes += n;
+        }
+
+        c->ssl->last = ngx_ssl_handle_recv(c, n);
+
+        if (c->ssl->last == NGX_OK) {
+
+            size -= n;
+
+            if (size == 0) {
+                c->read->ready = 1;
+                return bytes;
+            }
+
+            buf += n;
+
+            continue;
+        }
+
+        if (bytes) {
+            if (c->ssl->last != NGX_AGAIN) {
+                c->read->ready = 1;
+            }
+
+            return bytes;
+        }
+
+        switch (c->ssl->last) {
+
+        case NGX_DONE:
+            c->read->ready = 0;
+            c->read->eof = 1;
+            return 0;
+
+        case NGX_ERROR:
+            c->read->error = 1;
+
+            /* fall through */
+
+        case NGX_AGAIN:
+            return c->ssl->last;
+        }
+    }
+}
+
+
+static ngx_int_t
+ngx_ssl_handle_recv(ngx_connection_t *c, int n)
+{
+    int        sslerr;
+    ngx_err_t  err;
+
+    if (c->ssl->renegotiation) {
+        /*
+         * disable renegotiation (CVE-2009-3555):
+         * OpenSSL (at least up to 0.9.8l) does not handle disabled
+         * renegotiation gracefully, so drop connection here
+         */
+
+        ngx_log_error(NGX_LOG_NOTICE, c->log, 0, "SSL renegotiation disabled");
+
+        while (ERR_peek_error()) {
+            ngx_ssl_error(NGX_LOG_DEBUG, c->log, 0,
+                          "ignoring stale global SSL error");
+        }
+
+        ERR_clear_error();
+
+        c->ssl->no_wait_shutdown = 1;
+        c->ssl->no_send_shutdown = 1;
+
+        return NGX_ERROR;
+    }
+
+    if (n > 0) {
+
+        if (c->ssl->saved_write_handler) {
+
+            c->write->handler = c->ssl->saved_write_handler;
+            c->ssl->saved_write_handler = NULL;
+            c->write->ready = 1;
+
+            if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+                return NGX_ERROR;
+            }
+
+            ngx_post_event(c->write, &ngx_posted_events);
+        }
+
+        return NGX_OK;
+    }
+
+    sslerr = SSL_get_error(c->ssl->connection, n);
+
+    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_READ) {
+        c->read->ready = 0;
+        return NGX_AGAIN;
+    }
+
+    if (sslerr == SSL_ERROR_WANT_WRITE) {
+
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "peer started SSL renegotiation");
+
+        c->write->ready = 0;
+
+        if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        /*
+         * we do not set the timer because there is already the read event timer
+         */
+
+        if (c->ssl->saved_write_handler == NULL) {
+            c->ssl->saved_write_handler = c->write->handler;
+            c->write->handler = ngx_ssl_write_handler;
+        }
+
+        return NGX_AGAIN;
+    }
+
+    c->ssl->no_wait_shutdown = 1;
+    c->ssl->no_send_shutdown = 1;
+
+    if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) {
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "peer shutdown SSL cleanly");
+        return NGX_DONE;
+    }
+
+    ngx_ssl_connection_error(c, sslerr, err, "SSL_read() failed");
+
+    return NGX_ERROR;
+}
+
+
+static void
+ngx_ssl_write_handler(ngx_event_t *wev)
+{
+    ngx_connection_t  *c;
+
+    c = wev->data;
+
+    c->read->handler(c->read);
+}
+
+
+/*
+ * OpenSSL has no SSL_writev() so we copy several bufs into our 16K buffer
+ * before the SSL_write() call to decrease a SSL overhead.
+ *
+ * Besides for protocols such as HTTP it is possible to always buffer
+ * the output to decrease a SSL overhead some more.
+ */
+
+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;
+
+    if (!c->ssl->buffer) {
+
+        while (in) {
+            if (ngx_buf_special(in->buf)) {
+                in = in->next;
+                continue;
+            }
+
+            n = ngx_ssl_write(c, in->buf->pos, in->buf->last - in->buf->pos);
+
+            if (n == NGX_ERROR) {
+                return NGX_CHAIN_ERROR;
+            }
+
+            if (n == NGX_AGAIN) {
+                return in;
+            }
+
+            in->buf->pos += n;
+
+            if (in->buf->pos == in->buf->last) {
+                in = in->next;
+            }
+        }
+
+        return in;
+    }
+
+
+    /* the maximum limit size is the maximum int32_t value - the page size */
+
+    if (limit == 0 || limit > (off_t) (NGX_MAX_INT32_VALUE - ngx_pagesize)) {
+        limit = NGX_MAX_INT32_VALUE - ngx_pagesize;
+    }
+
+    buf = c->ssl->buf;
+
+    if (buf == NULL) {
+        buf = ngx_create_temp_buf(c->pool, c->ssl->buffer_size);
+        if (buf == NULL) {
+            return NGX_CHAIN_ERROR;
+        }
+
+        c->ssl->buf = buf;
+    }
+
+    if (buf->start == NULL) {
+        buf->start = ngx_palloc(c->pool, c->ssl->buffer_size);
+        if (buf->start == NULL) {
+            return NGX_CHAIN_ERROR;
+        }
+
+        buf->pos = buf->start;
+        buf->last = buf->start;
+        buf->end = buf->start + c->ssl->buffer_size;
+    }
+
+    send = buf->last - buf->pos;
+    flush = (in == NULL) ? 1 : buf->flush;
+
+    for ( ;; ) {
+
+        while (in && buf->last < buf->end && send < limit) {
+            if (in->buf->last_buf || in->buf->flush) {
+                flush = 1;
+            }
+
+            if (ngx_buf_special(in->buf)) {
+                in = in->next;
+                continue;
+            }
+
+            size = in->buf->last - in->buf->pos;
+
+            if (size > buf->end - buf->last) {
+                size = buf->end - buf->last;
+            }
+
+            if (send + size > limit) {
+                size = (ssize_t) (limit - send);
+            }
+
+            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                           "SSL buf copy: %z", size);
+
+            ngx_memcpy(buf->last, in->buf->pos, size);
+
+            buf->last += size;
+            in->buf->pos += size;
+            send += size;
+
+            if (in->buf->pos == in->buf->last) {
+                in = in->next;
+            }
+        }
+
+        if (!flush && send < limit && buf->last < buf->end) {
+            break;
+        }
+
+        size = buf->last - buf->pos;
+
+        if (size == 0) {
+            buf->flush = 0;
+            c->buffered &= ~NGX_SSL_BUFFERED;
+            return in;
+        }
+
+        n = ngx_ssl_write(c, buf->pos, size);
+
+        if (n == NGX_ERROR) {
+            return NGX_CHAIN_ERROR;
+        }
+
+        if (n == NGX_AGAIN) {
+            break;
+        }
+
+        buf->pos += n;
+
+        if (n < size) {
+            break;
+        }
+
+        flush = 0;
+
+        buf->pos = buf->start;
+        buf->last = buf->start;
+
+        if (in == NULL || send == limit) {
+            break;
+        }
+    }
+
+    buf->flush = flush;
+
+    if (buf->pos < buf->last) {
+        c->buffered |= NGX_SSL_BUFFERED;
+
+    } else {
+        c->buffered &= ~NGX_SSL_BUFFERED;
+    }
+
+    return in;
+}
+
+
+ssize_t
+ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size)
+{
+    int        n, sslerr;
+    ngx_err_t  err;
+
+    ngx_ssl_clear_error(c->log);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL to write: %uz", size);
+
+    n = SSL_write(c->ssl->connection, data, size);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_write: %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);
+        }
+
+        c->sent += n;
+
+        return n;
+    }
+
+    sslerr = SSL_get_error(c->ssl->connection, n);
+
+    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) {
+        c->write->ready = 0;
+        return NGX_AGAIN;
+    }
+
+    if (sslerr == SSL_ERROR_WANT_READ) {
+
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "peer started SSL renegotiation");
+
+        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_write() failed");
+
+    return NGX_ERROR;
+}
+
+
+static void
+ngx_ssl_read_handler(ngx_event_t *rev)
+{
+    ngx_connection_t  *c;
+
+    c = rev->data;
+
+    c->write->handler(c->write);
+}
+
+
+void
+ngx_ssl_free_buffer(ngx_connection_t *c)
+{
+    if (c->ssl->buf && c->ssl->buf->start) {
+        if (ngx_pfree(c->pool, c->ssl->buf->start) == NGX_OK) {
+            c->ssl->buf->start = NULL;
+        }
+    }
+}
+
+
+ngx_int_t
+ngx_ssl_shutdown(ngx_connection_t *c)
+{
+    int        n, sslerr, mode;
+    ngx_err_t  err;
+
+    if (SSL_in_init(c->ssl->connection)) {
+        /*
+         * OpenSSL 1.0.2f complains if SSL_shutdown() is called during
+         * an SSL handshake, while previous versions always return 0.
+         * Avoid calling SSL_shutdown() if handshake wasn't completed.
+         */
+
+        SSL_free(c->ssl->connection);
+        c->ssl = NULL;
+
+        return NGX_OK;
+    }
+
+    if (c->timedout) {
+        mode = SSL_RECEIVED_SHUTDOWN|SSL_SENT_SHUTDOWN;
+        SSL_set_quiet_shutdown(c->ssl->connection, 1);
+
+    } else {
+        mode = SSL_get_shutdown(c->ssl->connection);
+
+        if (c->ssl->no_wait_shutdown) {
+            mode |= SSL_RECEIVED_SHUTDOWN;
+        }
+
+        if (c->ssl->no_send_shutdown) {
+            mode |= SSL_SENT_SHUTDOWN;
+        }
+
+        if (c->ssl->no_wait_shutdown && c->ssl->no_send_shutdown) {
+            SSL_set_quiet_shutdown(c->ssl->connection, 1);
+        }
+    }
+
+    SSL_set_shutdown(c->ssl->connection, mode);
+
+    ngx_ssl_clear_error(c->log);
+
+    n = SSL_shutdown(c->ssl->connection);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_shutdown: %d", n);
+
+    sslerr = 0;
+
+    /* before 0.9.8m SSL_shutdown() returned 0 instead of -1 on errors */
+
+    if (n != 1 && ERR_peek_error()) {
+        sslerr = SSL_get_error(c->ssl->connection, n);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "SSL_get_error: %d", sslerr);
+    }
+
+    if (n == 1 || sslerr == 0 || sslerr == SSL_ERROR_ZERO_RETURN) {
+        SSL_free(c->ssl->connection);
+        c->ssl = NULL;
+
+        return NGX_OK;
+    }
+
+    if (sslerr == SSL_ERROR_WANT_READ || sslerr == SSL_ERROR_WANT_WRITE) {
+        c->read->handler = ngx_ssl_shutdown_handler;
+        c->write->handler = ngx_ssl_shutdown_handler;
+
+        if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        if (sslerr == SSL_ERROR_WANT_READ) {
+            ngx_add_timer(c->read, 30000);
+        }
+
+        return NGX_AGAIN;
+    }
+
+    err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0;
+
+    ngx_ssl_connection_error(c, sslerr, err, "SSL_shutdown() failed");
+
+    SSL_free(c->ssl->connection);
+    c->ssl = NULL;
+
+    return NGX_ERROR;
+}
+
+
+static void
+ngx_ssl_shutdown_handler(ngx_event_t *ev)
+{
+    ngx_connection_t           *c;
+    ngx_connection_handler_pt   handler;
+
+    c = ev->data;
+    handler = c->ssl->handler;
+
+    if (ev->timedout) {
+        c->timedout = 1;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "SSL shutdown handler");
+
+    if (ngx_ssl_shutdown(c) == NGX_AGAIN) {
+        return;
+    }
+
+    handler(c);
+}
+
+
+static void
+ngx_ssl_connection_error(ngx_connection_t *c, int sslerr, ngx_err_t err,
+    char *text)
+{
+    int         n;
+    ngx_uint_t  level;
+
+    level = NGX_LOG_CRIT;
+
+    if (sslerr == SSL_ERROR_SYSCALL) {
+
+        if (err == NGX_ECONNRESET
+            || err == NGX_EPIPE
+            || err == NGX_ENOTCONN
+            || err == NGX_ETIMEDOUT
+            || err == NGX_ECONNREFUSED
+            || err == NGX_ENETDOWN
+            || err == NGX_ENETUNREACH
+            || err == NGX_EHOSTDOWN
+            || err == NGX_EHOSTUNREACH)
+        {
+            switch (c->log_error) {
+
+            case NGX_ERROR_IGNORE_ECONNRESET:
+            case NGX_ERROR_INFO:
+                level = NGX_LOG_INFO;
+                break;
+
+            case NGX_ERROR_ERR:
+                level = NGX_LOG_ERR;
+                break;
+
+            default:
+                break;
+            }
+        }
+
+    } else if (sslerr == SSL_ERROR_SSL) {
+
+        n = ERR_GET_REASON(ERR_peek_error());
+
+            /* handshake failures */
+        if (n == SSL_R_BAD_CHANGE_CIPHER_SPEC                        /*  103 */
+#ifdef SSL_R_NO_SUITABLE_KEY_SHARE
+            || n == SSL_R_NO_SUITABLE_KEY_SHARE                      /*  101 */
+#endif
+#ifdef SSL_R_NO_SUITABLE_SIGNATURE_ALGORITHM
+            || n == SSL_R_NO_SUITABLE_SIGNATURE_ALGORITHM            /*  118 */
+#endif
+            || n == SSL_R_BLOCK_CIPHER_PAD_IS_WRONG                  /*  129 */
+            || n == SSL_R_DIGEST_CHECK_FAILED                        /*  149 */
+            || n == SSL_R_ERROR_IN_RECEIVED_CIPHER_LIST              /*  151 */
+            || n == SSL_R_EXCESSIVE_MESSAGE_SIZE                     /*  152 */
+            || n == SSL_R_HTTPS_PROXY_REQUEST                        /*  155 */
+            || n == SSL_R_HTTP_REQUEST                               /*  156 */
+            || n == SSL_R_LENGTH_MISMATCH                            /*  159 */
+#ifdef SSL_R_NO_CIPHERS_PASSED
+            || n == SSL_R_NO_CIPHERS_PASSED                          /*  182 */
+#endif
+            || n == SSL_R_NO_CIPHERS_SPECIFIED                       /*  183 */
+            || n == SSL_R_NO_COMPRESSION_SPECIFIED                   /*  187 */
+            || n == SSL_R_NO_SHARED_CIPHER                           /*  193 */
+            || n == SSL_R_RECORD_LENGTH_MISMATCH                     /*  213 */
+#ifdef SSL_R_PARSE_TLSEXT
+            || n == SSL_R_PARSE_TLSEXT                               /*  227 */
+#endif
+            || n == SSL_R_UNEXPECTED_MESSAGE                         /*  244 */
+            || n == SSL_R_UNEXPECTED_RECORD                          /*  245 */
+            || n == SSL_R_UNKNOWN_ALERT_TYPE                         /*  246 */
+            || n == SSL_R_UNKNOWN_PROTOCOL                           /*  252 */
+#ifdef SSL_R_NO_COMMON_SIGNATURE_ALGORITHMS
+            || n == SSL_R_NO_COMMON_SIGNATURE_ALGORITHMS             /*  253 */
+#endif
+            || n == SSL_R_UNSUPPORTED_PROTOCOL                       /*  258 */
+#ifdef SSL_R_NO_SHARED_GROUP
+            || n == SSL_R_NO_SHARED_GROUP                            /*  266 */
+#endif
+            || n == SSL_R_WRONG_VERSION_NUMBER                       /*  267 */
+            || n == SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC        /*  281 */
+#ifdef SSL_R_RENEGOTIATE_EXT_TOO_LONG
+            || n == SSL_R_RENEGOTIATE_EXT_TOO_LONG                   /*  335 */
+            || n == SSL_R_RENEGOTIATION_ENCODING_ERR                 /*  336 */
+            || n == SSL_R_RENEGOTIATION_MISMATCH                     /*  337 */
+#endif
+#ifdef SSL_R_UNSAFE_LEGACY_RENEGOTIATION_DISABLED
+            || n == SSL_R_UNSAFE_LEGACY_RENEGOTIATION_DISABLED       /*  338 */
+#endif
+#ifdef SSL_R_SCSV_RECEIVED_WHEN_RENEGOTIATING
+            || n == SSL_R_SCSV_RECEIVED_WHEN_RENEGOTIATING           /*  345 */
+#endif
+#ifdef SSL_R_INAPPROPRIATE_FALLBACK
+            || n == SSL_R_INAPPROPRIATE_FALLBACK                     /*  373 */
+#endif
+#ifdef SSL_R_VERSION_TOO_LOW
+            || n == SSL_R_VERSION_TOO_LOW                            /*  396 */
+#endif
+            || n == 1000 /* SSL_R_SSLV3_ALERT_CLOSE_NOTIFY */
+#ifdef SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE
+            || n == SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE             /* 1010 */
+            || n == SSL_R_SSLV3_ALERT_BAD_RECORD_MAC                 /* 1020 */
+            || n == SSL_R_TLSV1_ALERT_DECRYPTION_FAILED              /* 1021 */
+            || n == SSL_R_TLSV1_ALERT_RECORD_OVERFLOW                /* 1022 */
+            || n == SSL_R_SSLV3_ALERT_DECOMPRESSION_FAILURE          /* 1030 */
+            || n == SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE              /* 1040 */
+            || n == SSL_R_SSLV3_ALERT_NO_CERTIFICATE                 /* 1041 */
+            || n == SSL_R_SSLV3_ALERT_BAD_CERTIFICATE                /* 1042 */
+            || n == SSL_R_SSLV3_ALERT_UNSUPPORTED_CERTIFICATE        /* 1043 */
+            || n == SSL_R_SSLV3_ALERT_CERTIFICATE_REVOKED            /* 1044 */
+            || n == SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED            /* 1045 */
+            || n == SSL_R_SSLV3_ALERT_CERTIFICATE_UNKNOWN            /* 1046 */
+            || n == SSL_R_SSLV3_ALERT_ILLEGAL_PARAMETER              /* 1047 */
+            || n == SSL_R_TLSV1_ALERT_UNKNOWN_CA                     /* 1048 */
+            || n == SSL_R_TLSV1_ALERT_ACCESS_DENIED                  /* 1049 */
+            || n == SSL_R_TLSV1_ALERT_DECODE_ERROR                   /* 1050 */
+            || n == SSL_R_TLSV1_ALERT_DECRYPT_ERROR                  /* 1051 */
+            || n == SSL_R_TLSV1_ALERT_EXPORT_RESTRICTION             /* 1060 */
+            || n == SSL_R_TLSV1_ALERT_PROTOCOL_VERSION               /* 1070 */
+            || n == SSL_R_TLSV1_ALERT_INSUFFICIENT_SECURITY          /* 1071 */
+            || n == SSL_R_TLSV1_ALERT_INTERNAL_ERROR                 /* 1080 */
+            || n == SSL_R_TLSV1_ALERT_USER_CANCELLED                 /* 1090 */
+            || n == SSL_R_TLSV1_ALERT_NO_RENEGOTIATION               /* 1100 */
+#endif
+            )
+        {
+            switch (c->log_error) {
+
+            case NGX_ERROR_IGNORE_ECONNRESET:
+            case NGX_ERROR_INFO:
+                level = NGX_LOG_INFO;
+                break;
+
+            case NGX_ERROR_ERR:
+                level = NGX_LOG_ERR;
+                break;
+
+            default:
+                break;
+            }
+        }
+    }
+
+    ngx_ssl_error(level, c->log, err, text);
+}
+
+
+static void
+ngx_ssl_clear_error(ngx_log_t *log)
+{
+    while (ERR_peek_error()) {
+        ngx_ssl_error(NGX_LOG_ALERT, log, 0, "ignoring stale global SSL error");
+    }
+
+    ERR_clear_error();
+}
+
+
+void ngx_cdecl
+ngx_ssl_error(ngx_uint_t level, ngx_log_t *log, ngx_err_t err, char *fmt, ...)
+{
+    int          flags;
+    u_long       n;
+    va_list      args;
+    u_char      *p, *last;
+    u_char       errstr[NGX_MAX_CONF_ERRSTR];
+    const char  *data;
+
+    last = errstr + NGX_MAX_CONF_ERRSTR;
+
+    va_start(args, fmt);
+    p = ngx_vslprintf(errstr, last - 1, fmt, args);
+    va_end(args);
+
+    p = ngx_cpystrn(p, (u_char *) " (SSL:", last - p);
+
+    for ( ;; ) {
+
+        n = ERR_peek_error_line_data(NULL, NULL, &data, &flags);
+
+        if (n == 0) {
+            break;
+        }
+
+        /* ERR_error_string_n() requires at least one byte */
+
+        if (p >= last - 1) {
+            goto next;
+        }
+
+        *p++ = ' ';
+
+        ERR_error_string_n(n, (char *) p, last - p);
+
+        while (p < last && *p) {
+            p++;
+        }
+
+        if (p < last && *data && (flags & ERR_TXT_STRING)) {
+            *p++ = ':';
+            p = ngx_cpystrn(p, (u_char *) data, last - p);
+        }
+
+    next:
+
+        (void) ERR_get_error();
+    }
+
+    ngx_log_error(level, log, err, "%*s)", p - errstr, errstr);
+}
+
+
+ngx_int_t
+ngx_ssl_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx,
+    ssize_t builtin_session_cache, ngx_shm_zone_t *shm_zone, time_t timeout)
+{
+    long  cache_mode;
+
+    SSL_CTX_set_timeout(ssl->ctx, (long) timeout);
+
+    if (ngx_ssl_session_id_context(ssl, sess_ctx) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (builtin_session_cache == NGX_SSL_NO_SCACHE) {
+        SSL_CTX_set_session_cache_mode(ssl->ctx, SSL_SESS_CACHE_OFF);
+        return NGX_OK;
+    }
+
+    if (builtin_session_cache == NGX_SSL_NONE_SCACHE) {
+
+        /*
+         * If the server explicitly says that it does not support
+         * session reuse (see SSL_SESS_CACHE_OFF above), then
+         * Outlook Express fails to upload a sent email to
+         * the Sent Items folder on the IMAP server via a separate IMAP
+         * connection in the background.  Therefore we have a special
+         * mode (SSL_SESS_CACHE_SERVER|SSL_SESS_CACHE_NO_INTERNAL_STORE)
+         * where the server pretends that it supports session reuse,
+         * but it does not actually store any session.
+         */
+
+        SSL_CTX_set_session_cache_mode(ssl->ctx,
+                                       SSL_SESS_CACHE_SERVER
+                                       |SSL_SESS_CACHE_NO_AUTO_CLEAR
+                                       |SSL_SESS_CACHE_NO_INTERNAL_STORE);
+
+        SSL_CTX_sess_set_cache_size(ssl->ctx, 1);
+
+        return NGX_OK;
+    }
+
+    cache_mode = SSL_SESS_CACHE_SERVER;
+
+    if (shm_zone && builtin_session_cache == NGX_SSL_NO_BUILTIN_SCACHE) {
+        cache_mode |= SSL_SESS_CACHE_NO_INTERNAL;
+    }
+
+    SSL_CTX_set_session_cache_mode(ssl->ctx, cache_mode);
+
+    if (builtin_session_cache != NGX_SSL_NO_BUILTIN_SCACHE) {
+
+        if (builtin_session_cache != NGX_SSL_DFLT_BUILTIN_SCACHE) {
+            SSL_CTX_sess_set_cache_size(ssl->ctx, builtin_session_cache);
+        }
+    }
+
+    if (shm_zone) {
+        SSL_CTX_sess_set_new_cb(ssl->ctx, ngx_ssl_new_session);
+        SSL_CTX_sess_set_get_cb(ssl->ctx, ngx_ssl_get_cached_session);
+        SSL_CTX_sess_set_remove_cb(ssl->ctx, ngx_ssl_remove_session);
+
+        if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_session_cache_index, shm_zone)
+            == 0)
+        {
+            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                          "SSL_CTX_set_ex_data() failed");
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_ssl_session_id_context(ngx_ssl_t *ssl, ngx_str_t *sess_ctx)
+{
+    int                   n, i;
+    X509                 *cert;
+    X509_NAME            *name;
+    EVP_MD_CTX           *md;
+    unsigned int          len;
+    STACK_OF(X509_NAME)  *list;
+    u_char                buf[EVP_MAX_MD_SIZE];
+
+    /*
+     * Session ID context is set based on the string provided,
+     * the server certificates, and the client CA list.
+     */
+
+    md = EVP_MD_CTX_create();
+    if (md == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (EVP_DigestInit_ex(md, EVP_sha1(), NULL) == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "EVP_DigestInit_ex() failed");
+        goto failed;
+    }
+
+    if (EVP_DigestUpdate(md, sess_ctx->data, sess_ctx->len) == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "EVP_DigestUpdate() failed");
+        goto failed;
+    }
+
+    for (cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index);
+         cert;
+         cert = X509_get_ex_data(cert, ngx_ssl_next_certificate_index))
+    {
+        if (X509_digest(cert, EVP_sha1(), buf, &len) == 0) {
+            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                          "X509_digest() failed");
+            goto failed;
+        }
+
+        if (EVP_DigestUpdate(md, buf, len) == 0) {
+            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                          "EVP_DigestUpdate() failed");
+            goto failed;
+        }
+    }
+
+    list = SSL_CTX_get_client_CA_list(ssl->ctx);
+
+    if (list != NULL) {
+        n = sk_X509_NAME_num(list);
+
+        for (i = 0; i < n; i++) {
+            name = sk_X509_NAME_value(list, i);
+
+            if (X509_NAME_digest(name, EVP_sha1(), buf, &len) == 0) {
+                ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                              "X509_NAME_digest() failed");
+                goto failed;
+            }
+
+            if (EVP_DigestUpdate(md, buf, len) == 0) {
+                ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                              "EVP_DigestUpdate() failed");
+                goto failed;
+            }
+        }
+    }
+
+    if (EVP_DigestFinal_ex(md, buf, &len) == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "EVP_DigestUpdate() failed");
+        goto failed;
+    }
+
+    EVP_MD_CTX_destroy(md);
+
+    if (SSL_CTX_set_session_id_context(ssl->ctx, buf, len) == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_set_session_id_context() failed");
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+
+failed:
+
+    EVP_MD_CTX_destroy(md);
+
+    return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_ssl_session_cache_init(ngx_shm_zone_t *shm_zone, void *data)
+{
+    size_t                    len;
+    ngx_slab_pool_t          *shpool;
+    ngx_ssl_session_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_session_cache_t));
+    if (cache == NULL) {
+        return NGX_ERROR;
+    }
+
+    shpool->data = cache;
+    shm_zone->data = cache;
+
+    ngx_rbtree_init(&cache->session_rbtree, &cache->sentinel,
+                    ngx_ssl_session_rbtree_insert_value);
+
+    ngx_queue_init(&cache->expire_queue);
+
+    len = sizeof(" in SSL session shared cache \"\"") + shm_zone->shm.name.len;
+
+    shpool->log_ctx = ngx_slab_alloc(shpool, len);
+    if (shpool->log_ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_sprintf(shpool->log_ctx, " in SSL session shared cache \"%V\"%Z",
+                &shm_zone->shm.name);
+
+    shpool->log_nomem = 0;
+
+    return NGX_OK;
+}
+
+
+/*
+ * The length of the session id is 16 bytes for SSLv2 sessions and
+ * between 1 and 32 bytes for SSLv3/TLSv1, typically 32 bytes.
+ * It seems that the typical length of the external ASN1 representation
+ * of a session is 118 or 119 bytes for SSLv3/TSLv1.
+ *
+ * Thus on 32-bit platforms we allocate separately an rbtree node,
+ * a session id, and an ASN1 representation, they take accordingly
+ * 64, 32, and 128 bytes.
+ *
+ * On 64-bit platforms we allocate separately an rbtree node + session_id,
+ * and an ASN1 representation, they take accordingly 128 and 128 bytes.
+ *
+ * OpenSSL's i2d_SSL_SESSION() and d2i_SSL_SESSION are slow,
+ * so they are outside the code locked by shared pool mutex
+ */
+
+static int
+ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess)
+{
+    int                       len;
+    u_char                   *p, *id, *cached_sess, *session_id;
+    uint32_t                  hash;
+    SSL_CTX                  *ssl_ctx;
+    unsigned int              session_id_length;
+    ngx_shm_zone_t           *shm_zone;
+    ngx_connection_t         *c;
+    ngx_slab_pool_t          *shpool;
+    ngx_ssl_sess_id_t        *sess_id;
+    ngx_ssl_session_cache_t  *cache;
+    u_char                    buf[NGX_SSL_MAX_SESSION_SIZE];
+
+    len = i2d_SSL_SESSION(sess, NULL);
+
+    /* do not cache too big session */
+
+    if (len > (int) NGX_SSL_MAX_SESSION_SIZE) {
+        return 0;
+    }
+
+    p = buf;
+    i2d_SSL_SESSION(sess, &p);
+
+    c = ngx_ssl_get_connection(ssl_conn);
+
+    ssl_ctx = c->ssl->session_ctx;
+    shm_zone = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_session_cache_index);
+
+    cache = shm_zone->data;
+    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+    ngx_shmtx_lock(&shpool->mutex);
+
+    /* drop one or two expired sessions */
+    ngx_ssl_expire_sessions(cache, shpool, 1);
+
+    cached_sess = ngx_slab_alloc_locked(shpool, len);
+
+    if (cached_sess == NULL) {
+
+        /* drop the oldest non-expired session and try once more */
+
+        ngx_ssl_expire_sessions(cache, shpool, 0);
+
+        cached_sess = ngx_slab_alloc_locked(shpool, len);
+
+        if (cached_sess == NULL) {
+            sess_id = NULL;
+            goto failed;
+        }
+    }
+
+    sess_id = ngx_slab_alloc_locked(shpool, sizeof(ngx_ssl_sess_id_t));
+
+    if (sess_id == NULL) {
+
+        /* drop the oldest non-expired session and try once more */
+
+        ngx_ssl_expire_sessions(cache, shpool, 0);
+
+        sess_id = ngx_slab_alloc_locked(shpool, sizeof(ngx_ssl_sess_id_t));
+
+        if (sess_id == NULL) {
+            goto failed;
+        }
+    }
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+
+    session_id = (u_char *) SSL_SESSION_get_id(sess, &session_id_length);
+
+#else
+
+    session_id = sess->session_id;
+    session_id_length = sess->session_id_length;
+
+#endif
+
+#if (NGX_PTR_SIZE == 8)
+
+    id = sess_id->sess_id;
+
+#else
+
+    id = ngx_slab_alloc_locked(shpool, session_id_length);
+
+    if (id == NULL) {
+
+        /* drop the oldest non-expired session and try once more */
+
+        ngx_ssl_expire_sessions(cache, shpool, 0);
+
+        id = ngx_slab_alloc_locked(shpool, session_id_length);
+
+        if (id == NULL) {
+            goto failed;
+        }
+    }
+
+#endif
+
+    ngx_memcpy(cached_sess, buf, len);
+
+    ngx_memcpy(id, session_id, session_id_length);
+
+    hash = ngx_crc32_short(session_id, session_id_length);
+
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "ssl new session: %08XD:%ud:%d",
+                   hash, session_id_length, len);
+
+    sess_id->node.key = hash;
+    sess_id->node.data = (u_char) session_id_length;
+    sess_id->id = id;
+    sess_id->len = len;
+    sess_id->session = cached_sess;
+
+    sess_id->expire = ngx_time() + SSL_CTX_get_timeout(ssl_ctx);
+
+    ngx_queue_insert_head(&cache->expire_queue, &sess_id->queue);
+
+    ngx_rbtree_insert(&cache->session_rbtree, &sess_id->node);
+
+    ngx_shmtx_unlock(&shpool->mutex);
+
+    return 0;
+
+failed:
+
+    if (cached_sess) {
+        ngx_slab_free_locked(shpool, cached_sess);
+    }
+
+    if (sess_id) {
+        ngx_slab_free_locked(shpool, sess_id);
+    }
+
+    ngx_shmtx_unlock(&shpool->mutex);
+
+    ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                  "could not allocate new session%s", shpool->log_ctx);
+
+    return 0;
+}
+
+
+static ngx_ssl_session_t *
+ngx_ssl_get_cached_session(ngx_ssl_conn_t *ssl_conn,
+#if OPENSSL_VERSION_NUMBER >= 0x10100003L
+    const
+#endif
+    u_char *id, int len, int *copy)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x0090707fL
+    const
+#endif
+    u_char                   *p;
+    uint32_t                  hash;
+    ngx_int_t                 rc;
+    ngx_shm_zone_t           *shm_zone;
+    ngx_slab_pool_t          *shpool;
+    ngx_rbtree_node_t        *node, *sentinel;
+    ngx_ssl_session_t        *sess;
+    ngx_ssl_sess_id_t        *sess_id;
+    ngx_ssl_session_cache_t  *cache;
+    u_char                    buf[NGX_SSL_MAX_SESSION_SIZE];
+    ngx_connection_t         *c;
+
+    hash = ngx_crc32_short((u_char *) (uintptr_t) id, (size_t) len);
+    *copy = 0;
+
+    c = ngx_ssl_get_connection(ssl_conn);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "ssl get session: %08XD:%d", hash, len);
+
+    shm_zone = SSL_CTX_get_ex_data(c->ssl->session_ctx,
+                                   ngx_ssl_session_cache_index);
+
+    cache = shm_zone->data;
+
+    sess = NULL;
+
+    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+    ngx_shmtx_lock(&shpool->mutex);
+
+    node = cache->session_rbtree.root;
+    sentinel = cache->session_rbtree.sentinel;
+
+    while (node != sentinel) {
+
+        if (hash < node->key) {
+            node = node->left;
+            continue;
+        }
+
+        if (hash > node->key) {
+            node = node->right;
+            continue;
+        }
+
+        /* hash == node->key */
+
+        sess_id = (ngx_ssl_sess_id_t *) node;
+
+        rc = ngx_memn2cmp((u_char *) (uintptr_t) id, sess_id->id,
+                          (size_t) len, (size_t) node->data);
+
+        if (rc == 0) {
+
+            if (sess_id->expire > ngx_time()) {
+                ngx_memcpy(buf, sess_id->session, sess_id->len);
+
+                ngx_shmtx_unlock(&shpool->mutex);
+
+                p = buf;
+                sess = d2i_SSL_SESSION(NULL, &p, sess_id->len);
+
+                return sess;
+            }
+
+            ngx_queue_remove(&sess_id->queue);
+
+            ngx_rbtree_delete(&cache->session_rbtree, node);
+
+            ngx_slab_free_locked(shpool, sess_id->session);
+#if (NGX_PTR_SIZE == 4)
+            ngx_slab_free_locked(shpool, sess_id->id);
+#endif
+            ngx_slab_free_locked(shpool, sess_id);
+
+            sess = NULL;
+
+            goto done;
+        }
+
+        node = (rc < 0) ? node->left : node->right;
+    }
+
+done:
+
+    ngx_shmtx_unlock(&shpool->mutex);
+
+    return sess;
+}
+
+
+void
+ngx_ssl_remove_cached_session(SSL_CTX *ssl, ngx_ssl_session_t *sess)
+{
+    SSL_CTX_remove_session(ssl, sess);
+
+    ngx_ssl_remove_session(ssl, sess);
+}
+
+
+static void
+ngx_ssl_remove_session(SSL_CTX *ssl, ngx_ssl_session_t *sess)
+{
+    u_char                   *id;
+    uint32_t                  hash;
+    ngx_int_t                 rc;
+    unsigned int              len;
+    ngx_shm_zone_t           *shm_zone;
+    ngx_slab_pool_t          *shpool;
+    ngx_rbtree_node_t        *node, *sentinel;
+    ngx_ssl_sess_id_t        *sess_id;
+    ngx_ssl_session_cache_t  *cache;
+
+    shm_zone = SSL_CTX_get_ex_data(ssl, ngx_ssl_session_cache_index);
+
+    if (shm_zone == NULL) {
+        return;
+    }
+
+    cache = shm_zone->data;
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+
+    id = (u_char *) SSL_SESSION_get_id(sess, &len);
+
+#else
+
+    id = sess->session_id;
+    len = sess->session_id_length;
+
+#endif
+
+    hash = ngx_crc32_short(id, len);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0,
+                   "ssl remove session: %08XD:%ud", hash, len);
+
+    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+    ngx_shmtx_lock(&shpool->mutex);
+
+    node = cache->session_rbtree.root;
+    sentinel = cache->session_rbtree.sentinel;
+
+    while (node != sentinel) {
+
+        if (hash < node->key) {
+            node = node->left;
+            continue;
+        }
+
+        if (hash > node->key) {
+            node = node->right;
+            continue;
+        }
+
+        /* hash == node->key */
+
+        sess_id = (ngx_ssl_sess_id_t *) node;
+
+        rc = ngx_memn2cmp(id, sess_id->id, len, (size_t) node->data);
+
+        if (rc == 0) {
+
+            ngx_queue_remove(&sess_id->queue);
+
+            ngx_rbtree_delete(&cache->session_rbtree, node);
+
+            ngx_slab_free_locked(shpool, sess_id->session);
+#if (NGX_PTR_SIZE == 4)
+            ngx_slab_free_locked(shpool, sess_id->id);
+#endif
+            ngx_slab_free_locked(shpool, sess_id);
+
+            goto done;
+        }
+
+        node = (rc < 0) ? node->left : node->right;
+    }
+
+done:
+
+    ngx_shmtx_unlock(&shpool->mutex);
+}
+
+
+static void
+ngx_ssl_expire_sessions(ngx_ssl_session_cache_t *cache,
+    ngx_slab_pool_t *shpool, ngx_uint_t n)
+{
+    time_t              now;
+    ngx_queue_t        *q;
+    ngx_ssl_sess_id_t  *sess_id;
+
+    now = ngx_time();
+
+    while (n < 3) {
+
+        if (ngx_queue_empty(&cache->expire_queue)) {
+            return;
+        }
+
+        q = ngx_queue_last(&cache->expire_queue);
+
+        sess_id = ngx_queue_data(q, ngx_ssl_sess_id_t, queue);
+
+        if (n++ != 0 && sess_id->expire > now) {
+            return;
+        }
+
+        ngx_queue_remove(q);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0,
+                       "expire session: %08Xi", sess_id->node.key);
+
+        ngx_rbtree_delete(&cache->session_rbtree, &sess_id->node);
+
+        ngx_slab_free_locked(shpool, sess_id->session);
+#if (NGX_PTR_SIZE == 4)
+        ngx_slab_free_locked(shpool, sess_id->id);
+#endif
+        ngx_slab_free_locked(shpool, sess_id);
+    }
+}
+
+
+static void
+ngx_ssl_session_rbtree_insert_value(ngx_rbtree_node_t *temp,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
+{
+    ngx_rbtree_node_t  **p;
+    ngx_ssl_sess_id_t   *sess_id, *sess_id_temp;
+
+    for ( ;; ) {
+
+        if (node->key < temp->key) {
+
+            p = &temp->left;
+
+        } else if (node->key > temp->key) {
+
+            p = &temp->right;
+
+        } else { /* node->key == temp->key */
+
+            sess_id = (ngx_ssl_sess_id_t *) node;
+            sess_id_temp = (ngx_ssl_sess_id_t *) temp;
+
+            p = (ngx_memn2cmp(sess_id->id, sess_id_temp->id,
+                              (size_t) node->data, (size_t) temp->data)
+                 < 0) ? &temp->left : &temp->right;
+        }
+
+        if (*p == sentinel) {
+            break;
+        }
+
+        temp = *p;
+    }
+
+    *p = node;
+    node->parent = temp;
+    node->left = sentinel;
+    node->right = sentinel;
+    ngx_rbt_red(node);
+}
+
+
+#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
+
+ngx_int_t
+ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *paths)
+{
+    u_char                         buf[80];
+    size_t                         size;
+    ssize_t                        n;
+    ngx_str_t                     *path;
+    ngx_file_t                     file;
+    ngx_uint_t                     i;
+    ngx_array_t                   *keys;
+    ngx_file_info_t                fi;
+    ngx_ssl_session_ticket_key_t  *key;
+
+    if (paths == NULL) {
+        return NGX_OK;
+    }
+
+    keys = ngx_array_create(cf->pool, paths->nelts,
+                            sizeof(ngx_ssl_session_ticket_key_t));
+    if (keys == NULL) {
+        return NGX_ERROR;
+    }
+
+    path = paths->elts;
+    for (i = 0; i < paths->nelts; i++) {
+
+        if (ngx_conf_full_name(cf->cycle, &path[i], 1) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        ngx_memzero(&file, sizeof(ngx_file_t));
+        file.name = path[i];
+        file.log = cf->log;
+
+        file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY,
+                                NGX_FILE_OPEN, 0);
+
+        if (file.fd == NGX_INVALID_FILE) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
+                               ngx_open_file_n " \"%V\" failed", &file.name);
+            return NGX_ERROR;
+        }
+
+        if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) {
+            ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
+                               ngx_fd_info_n " \"%V\" failed", &file.name);
+            goto failed;
+        }
+
+        size = ngx_file_size(&fi);
+
+        if (size != 48 && size != 80) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "\"%V\" must be 48 or 80 bytes", &file.name);
+            goto failed;
+        }
+
+        n = ngx_read_file(&file, buf, size, 0);
+
+        if (n == NGX_ERROR) {
+            ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
+                               ngx_read_file_n " \"%V\" failed", &file.name);
+            goto failed;
+        }
+
+        if ((size_t) n != size) {
+            ngx_conf_log_error(NGX_LOG_CRIT, cf, 0,
+                               ngx_read_file_n " \"%V\" returned only "
+                               "%z bytes instead of %uz", &file.name, n, size);
+            goto failed;
+        }
+
+        key = ngx_array_push(keys);
+        if (key == NULL) {
+            goto failed;
+        }
+
+        if (size == 48) {
+            key->size = 48;
+            ngx_memcpy(key->name, buf, 16);
+            ngx_memcpy(key->aes_key, buf + 16, 16);
+            ngx_memcpy(key->hmac_key, buf + 32, 16);
+
+        } else {
+            key->size = 80;
+            ngx_memcpy(key->name, buf, 16);
+            ngx_memcpy(key->hmac_key, buf + 16, 32);
+            ngx_memcpy(key->aes_key, buf + 48, 32);
+        }
+
+        if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
+                          ngx_close_file_n " \"%V\" failed", &file.name);
+        }
+    }
+
+    if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_session_ticket_keys_index, keys)
+        == 0)
+    {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_set_ex_data() failed");
+        return NGX_ERROR;
+    }
+
+    if (SSL_CTX_set_tlsext_ticket_key_cb(ssl->ctx,
+                                         ngx_ssl_session_ticket_key_callback)
+        == 0)
+    {
+        ngx_log_error(NGX_LOG_WARN, cf->log, 0,
+                      "nginx was built with Session Tickets support, however, "
+                      "now it is linked dynamically to an OpenSSL library "
+                      "which has no tlsext support, therefore Session Tickets "
+                      "are not available");
+    }
+
+    return NGX_OK;
+
+failed:
+
+    if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
+                      ngx_close_file_n " \"%V\" failed", &file.name);
+    }
+
+    return NGX_ERROR;
+}
+
+
+static int
+ngx_ssl_session_ticket_key_callback(ngx_ssl_conn_t *ssl_conn,
+    unsigned char *name, unsigned char *iv, EVP_CIPHER_CTX *ectx,
+    HMAC_CTX *hctx, int enc)
+{
+    size_t                         size;
+    SSL_CTX                       *ssl_ctx;
+    ngx_uint_t                     i;
+    ngx_array_t                   *keys;
+    ngx_connection_t              *c;
+    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;
+
+#ifdef OPENSSL_NO_SHA256
+    digest = EVP_sha1();
+#else
+    digest = EVP_sha256();
+#endif
+
+    keys = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_session_ticket_keys_index);
+    if (keys == NULL) {
+        return -1;
+    }
+
+    key = keys->elts;
+
+    if (enc == 1) {
+        /* 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_reused(ssl_conn) ? "reused" : "new");
+
+        if (key[0].size == 48) {
+            cipher = EVP_aes_128_cbc();
+            size = 16;
+
+        } else {
+            cipher = EVP_aes_256_cbc();
+            size = 32;
+        }
+
+        if (RAND_bytes(iv, EVP_CIPHER_iv_length(cipher)) != 1) {
+            ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "RAND_bytes() failed");
+            return -1;
+        }
+
+        if (EVP_EncryptInit_ex(ectx, cipher, NULL, key[0].aes_key, iv) != 1) {
+            ngx_ssl_error(NGX_LOG_ALERT, c->log, 0,
+                          "EVP_EncryptInit_ex() failed");
+            return -1;
+        }
+
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+        if (HMAC_Init_ex(hctx, key[0].hmac_key, size, digest, NULL) != 1) {
+            ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "HMAC_Init_ex() failed");
+            return -1;
+        }
+#else
+        HMAC_Init_ex(hctx, key[0].hmac_key, size, digest, NULL);
+#endif
+
+        ngx_memcpy(name, key[0].name, 16);
+
+        return 1;
+
+    } else {
+        /* decrypt session ticket */
+
+        for (i = 0; i < keys->nelts; i++) {
+            if (ngx_memcmp(name, key[i].name, 16) == 0) {
+                goto found;
+            }
+        }
+
+        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);
+
+        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)" : "");
+
+        if (key[i].size == 48) {
+            cipher = EVP_aes_128_cbc();
+            size = 16;
+
+        } else {
+            cipher = EVP_aes_256_cbc();
+            size = 32;
+        }
+
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+        if (HMAC_Init_ex(hctx, key[i].hmac_key, size, digest, NULL) != 1) {
+            ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "HMAC_Init_ex() failed");
+            return -1;
+        }
+#else
+        HMAC_Init_ex(hctx, key[i].hmac_key, size, digest, NULL);
+#endif
+
+        if (EVP_DecryptInit_ex(ectx, cipher, NULL, key[i].aes_key, iv) != 1) {
+            ngx_ssl_error(NGX_LOG_ALERT, c->log, 0,
+                          "EVP_DecryptInit_ex() failed");
+            return -1;
+        }
+
+        return (i == 0) ? 1 : 2 /* renew */;
+    }
+}
+
+#else
+
+ngx_int_t
+ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *paths)
+{
+    if (paths) {
+        ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
+                      "\"ssl_session_ticket_key\" ignored, not supported");
+    }
+
+    return NGX_OK;
+}
+
+#endif
+
+
+void
+ngx_ssl_cleanup_ctx(void *data)
+{
+    ngx_ssl_t  *ssl = data;
+
+    X509  *cert, *next;
+
+    cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index);
+
+    while (cert) {
+        next = X509_get_ex_data(cert, ngx_ssl_next_certificate_index);
+        X509_free(cert);
+        cert = next;
+    }
+
+    SSL_CTX_free(ssl->ctx);
+}
+
+
+ngx_int_t
+ngx_ssl_check_host(ngx_connection_t *c, ngx_str_t *name)
+{
+    X509   *cert;
+
+    cert = SSL_get_peer_certificate(c->ssl->connection);
+    if (cert == NULL) {
+        return NGX_ERROR;
+    }
+
+#ifdef X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT
+
+    /* X509_check_host() is only available in OpenSSL 1.0.2+ */
+
+    if (name->len == 0) {
+        goto failed;
+    }
+
+    if (X509_check_host(cert, (char *) name->data, name->len, 0, NULL) != 1) {
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "X509_check_host(): no match");
+        goto failed;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "X509_check_host(): match");
+
+    goto found;
+
+#else
+    {
+    int                      n, i;
+    X509_NAME               *sname;
+    ASN1_STRING             *str;
+    X509_NAME_ENTRY         *entry;
+    GENERAL_NAME            *altname;
+    STACK_OF(GENERAL_NAME)  *altnames;
+
+    /*
+     * As per RFC6125 and RFC2818, we check subjectAltName extension,
+     * and if it's not present - commonName in Subject is checked.
+     */
+
+    altnames = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
+
+    if (altnames) {
+        n = sk_GENERAL_NAME_num(altnames);
+
+        for (i = 0; i < n; i++) {
+            altname = sk_GENERAL_NAME_value(altnames, i);
+
+            if (altname->type != GEN_DNS) {
+                continue;
+            }
+
+            str = altname->d.dNSName;
+
+            ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                           "SSL subjectAltName: \"%*s\"",
+                           ASN1_STRING_length(str), ASN1_STRING_data(str));
+
+            if (ngx_ssl_check_name(name, str) == NGX_OK) {
+                ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                               "SSL subjectAltName: match");
+                GENERAL_NAMES_free(altnames);
+                goto found;
+            }
+        }
+
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "SSL subjectAltName: no match");
+
+        GENERAL_NAMES_free(altnames);
+        goto failed;
+    }
+
+    /*
+     * If there is no subjectAltName extension, check commonName
+     * in Subject.  While RFC2818 requires to only check "most specific"
+     * CN, both Apache and OpenSSL check all CNs, and so do we.
+     */
+
+    sname = X509_get_subject_name(cert);
+
+    if (sname == NULL) {
+        goto failed;
+    }
+
+    i = -1;
+    for ( ;; ) {
+        i = X509_NAME_get_index_by_NID(sname, NID_commonName, i);
+
+        if (i < 0) {
+            break;
+        }
+
+        entry = X509_NAME_get_entry(sname, i);
+        str = X509_NAME_ENTRY_get_data(entry);
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "SSL commonName: \"%*s\"",
+                       ASN1_STRING_length(str), ASN1_STRING_data(str));
+
+        if (ngx_ssl_check_name(name, str) == NGX_OK) {
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                           "SSL commonName: match");
+            goto found;
+        }
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "SSL commonName: no match");
+    }
+#endif
+
+failed:
+
+    X509_free(cert);
+    return NGX_ERROR;
+
+found:
+
+    X509_free(cert);
+    return NGX_OK;
+}
+
+
+#ifndef X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT
+
+static ngx_int_t
+ngx_ssl_check_name(ngx_str_t *name, ASN1_STRING *pattern)
+{
+    u_char  *s, *p, *end;
+    size_t   slen, plen;
+
+    s = name->data;
+    slen = name->len;
+
+    p = ASN1_STRING_data(pattern);
+    plen = ASN1_STRING_length(pattern);
+
+    if (slen == plen && ngx_strncasecmp(s, p, plen) == 0) {
+        return NGX_OK;
+    }
+
+    if (plen > 2 && p[0] == '*' && p[1] == '.') {
+        plen -= 1;
+        p += 1;
+
+        end = s + slen;
+        s = ngx_strlchr(s, end, '.');
+
+        if (s == NULL) {
+            return NGX_ERROR;
+        }
+
+        slen = end - s;
+
+        if (plen == slen && ngx_strncasecmp(s, p, plen) == 0) {
+            return NGX_OK;
+        }
+    }
+
+    return NGX_ERROR;
+}
+
+#endif
+
+
+ngx_int_t
+ngx_ssl_get_protocol(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+    s->data = (u_char *) SSL_get_version(c->ssl->connection);
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_cipher_name(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+    s->data = (u_char *) SSL_get_cipher_name(c->ssl->connection);
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_ciphers(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+#ifdef SSL_CTRL_GET_RAW_CIPHERLIST
+
+    int                n, i, bytes;
+    size_t             len;
+    u_char            *ciphers, *p;
+    const SSL_CIPHER  *cipher;
+
+    bytes = SSL_get0_raw_cipherlist(c->ssl->connection, NULL);
+    n = SSL_get0_raw_cipherlist(c->ssl->connection, &ciphers);
+
+    if (n <= 0) {
+        s->len = 0;
+        return NGX_OK;
+    }
+
+    len = 0;
+    n /= bytes;
+
+    for (i = 0; i < n; i++) {
+        cipher = SSL_CIPHER_find(c->ssl->connection, ciphers + i * bytes);
+
+        if (cipher) {
+            len += ngx_strlen(SSL_CIPHER_get_name(cipher));
+
+        } else {
+            len += sizeof("0x") - 1 + bytes * (sizeof("00") - 1);
+        }
+
+        len += sizeof(":") - 1;
+    }
+
+    s->data = ngx_pnalloc(pool, len);
+    if (s->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    p = s->data;
+
+    for (i = 0; i < n; i++) {
+        cipher = SSL_CIPHER_find(c->ssl->connection, ciphers + i * bytes);
+
+        if (cipher) {
+            p = ngx_sprintf(p, "%s", SSL_CIPHER_get_name(cipher));
+
+        } else {
+            p = ngx_sprintf(p, "0x");
+            p = ngx_hex_dump(p, ciphers + i * bytes, bytes);
+        }
+
+        *p++ = ':';
+    }
+
+    p--;
+
+    s->len = p - s->data;
+
+#else
+
+    u_char  buf[4096];
+
+    if (SSL_get_shared_ciphers(c->ssl->connection, (char *) buf, 4096)
+        == NULL)
+    {
+        s->len = 0;
+        return NGX_OK;
+    }
+
+    s->len = ngx_strlen(buf);
+    s->data = ngx_pnalloc(pool, s->len);
+    if (s->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(s->data, buf, s->len);
+
+#endif
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_curves(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+#ifdef SSL_CTRL_GET_CURVES
+
+    int         *curves, n, i, nid;
+    u_char      *p;
+    size_t       len;
+
+    n = SSL_get1_curves(c->ssl->connection, NULL);
+
+    if (n <= 0) {
+        s->len = 0;
+        return NGX_OK;
+    }
+
+    curves = ngx_palloc(pool, n * sizeof(int));
+
+    n = SSL_get1_curves(c->ssl->connection, curves);
+    len = 0;
+
+    for (i = 0; i < n; i++) {
+        nid = curves[i];
+
+        if (nid & TLSEXT_nid_unknown) {
+            len += sizeof("0x0000") - 1;
+
+        } else {
+            len += ngx_strlen(OBJ_nid2sn(nid));
+        }
+
+        len += sizeof(":") - 1;
+    }
+
+    s->data = ngx_pnalloc(pool, len);
+    if (s->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    p = s->data;
+
+    for (i = 0; i < n; i++) {
+        nid = curves[i];
+
+        if (nid & TLSEXT_nid_unknown) {
+            p = ngx_sprintf(p, "0x%04xd", nid & 0xffff);
+
+        } else {
+            p = ngx_sprintf(p, "%s", OBJ_nid2sn(nid));
+        }
+
+        *p++ = ':';
+    }
+
+    p--;
+
+    s->len = p - s->data;
+
+#else
+
+    s->len = 0;
+
+#endif
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_session_id(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+    u_char        *buf;
+    SSL_SESSION   *sess;
+    unsigned int   len;
+
+    sess = SSL_get0_session(c->ssl->connection);
+    if (sess == NULL) {
+        s->len = 0;
+        return NGX_OK;
+    }
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+
+    buf = (u_char *) SSL_SESSION_get_id(sess, &len);
+
+#else
+
+    buf = sess->session_id;
+    len = sess->session_id_length;
+
+#endif
+
+    s->len = 2 * len;
+    s->data = ngx_pnalloc(pool, 2 * len);
+    if (s->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_hex_dump(s->data, buf, len);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_session_reused(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+    if (SSL_session_reused(c->ssl->connection)) {
+        ngx_str_set(s, "r");
+
+    } else {
+        ngx_str_set(s, ".");
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_server_name(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+
+    size_t       len;
+    const char  *name;
+
+    name = SSL_get_servername(c->ssl->connection, TLSEXT_NAMETYPE_host_name);
+
+    if (name) {
+        len = ngx_strlen(name);
+
+        s->len = len;
+        s->data = ngx_pnalloc(pool, len);
+        if (s->data == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_memcpy(s->data, name, 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)
+{
+    size_t   len;
+    BIO     *bio;
+    X509    *cert;
+
+    s->len = 0;
+
+    cert = SSL_get_peer_certificate(c->ssl->connection);
+    if (cert == NULL) {
+        return NGX_OK;
+    }
+
+    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 (PEM_write_bio_X509(bio, cert) == 0) {
+        ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "PEM_write_bio_X509() failed");
+        goto failed;
+    }
+
+    len = BIO_pending(bio);
+    s->len = len;
+
+    s->data = ngx_pnalloc(pool, len);
+    if (s->data == NULL) {
+        goto failed;
+    }
+
+    BIO_read(bio, s->data, len);
+
+    BIO_free(bio);
+    X509_free(cert);
+
+    return NGX_OK;
+
+failed:
+
+    BIO_free(bio);
+    X509_free(cert);
+
+    return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_ssl_get_certificate(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+    u_char      *p;
+    size_t       len;
+    ngx_uint_t   i;
+    ngx_str_t    cert;
+
+    if (ngx_ssl_get_raw_certificate(c, pool, &cert) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (cert.len == 0) {
+        s->len = 0;
+        return NGX_OK;
+    }
+
+    len = cert.len - 1;
+
+    for (i = 0; i < cert.len - 1; i++) {
+        if (cert.data[i] == LF) {
+            len++;
+        }
+    }
+
+    s->len = len;
+    s->data = ngx_pnalloc(pool, len);
+    if (s->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    p = s->data;
+
+    for (i = 0; i < cert.len - 1; i++) {
+        *p++ = cert.data[i];
+        if (cert.data[i] == LF) {
+            *p++ = '\t';
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_escaped_certificate(ngx_connection_t *c, ngx_pool_t *pool,
+    ngx_str_t *s)
+{
+    ngx_str_t  cert;
+    uintptr_t  n;
+
+    if (ngx_ssl_get_raw_certificate(c, pool, &cert) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (cert.len == 0) {
+        s->len = 0;
+        return NGX_OK;
+    }
+
+    n = ngx_escape_uri(NULL, cert.data, cert.len, NGX_ESCAPE_URI_COMPONENT);
+
+    s->len = cert.len + n * 2;
+    s->data = ngx_pnalloc(pool, s->len);
+    if (s->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_escape_uri(s->data, cert.data, cert.len, NGX_ESCAPE_URI_COMPONENT);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_subject_dn(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+    BIO        *bio;
+    X509       *cert;
+    X509_NAME  *name;
+
+    s->len = 0;
+
+    cert = SSL_get_peer_certificate(c->ssl->connection);
+    if (cert == NULL) {
+        return NGX_OK;
+    }
+
+    name = X509_get_subject_name(cert);
+    if (name == NULL) {
+        return NGX_ERROR;
+    }
+
+    bio = BIO_new(BIO_s_mem());
+    if (bio == NULL) {
+        X509_free(cert);
+        return NGX_ERROR;
+    }
+
+    if (X509_NAME_print_ex(bio, name, 0, XN_FLAG_RFC2253) < 0) {
+        goto failed;
+    }
+
+    s->len = BIO_pending(bio);
+    s->data = ngx_pnalloc(pool, s->len);
+    if (s->data == NULL) {
+        goto failed;
+    }
+
+    BIO_read(bio, s->data, s->len);
+
+    BIO_free(bio);
+    X509_free(cert);
+
+    return NGX_OK;
+
+failed:
+
+    BIO_free(bio);
+    X509_free(cert);
+
+    return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_ssl_get_issuer_dn(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+    BIO        *bio;
+    X509       *cert;
+    X509_NAME  *name;
+
+    s->len = 0;
+
+    cert = SSL_get_peer_certificate(c->ssl->connection);
+    if (cert == NULL) {
+        return NGX_OK;
+    }
+
+    name = X509_get_issuer_name(cert);
+    if (name == NULL) {
+        return NGX_ERROR;
+    }
+
+    bio = BIO_new(BIO_s_mem());
+    if (bio == NULL) {
+        X509_free(cert);
+        return NGX_ERROR;
+    }
+
+    if (X509_NAME_print_ex(bio, name, 0, XN_FLAG_RFC2253) < 0) {
+        goto failed;
+    }
+
+    s->len = BIO_pending(bio);
+    s->data = ngx_pnalloc(pool, s->len);
+    if (s->data == NULL) {
+        goto failed;
+    }
+
+    BIO_read(bio, s->data, s->len);
+
+    BIO_free(bio);
+    X509_free(cert);
+
+    return NGX_OK;
+
+failed:
+
+    BIO_free(bio);
+    X509_free(cert);
+
+    return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_ssl_get_subject_dn_legacy(ngx_connection_t *c, ngx_pool_t *pool,
+    ngx_str_t *s)
+{
+    char       *p;
+    size_t      len;
+    X509       *cert;
+    X509_NAME  *name;
+
+    s->len = 0;
+
+    cert = SSL_get_peer_certificate(c->ssl->connection);
+    if (cert == NULL) {
+        return NGX_OK;
+    }
+
+    name = X509_get_subject_name(cert);
+    if (name == NULL) {
+        X509_free(cert);
+        return NGX_ERROR;
+    }
+
+    p = X509_NAME_oneline(name, NULL, 0);
+
+    for (len = 0; p[len]; len++) { /* void */ }
+
+    s->len = len;
+    s->data = ngx_pnalloc(pool, len);
+    if (s->data == NULL) {
+        OPENSSL_free(p);
+        X509_free(cert);
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(s->data, p, len);
+
+    OPENSSL_free(p);
+    X509_free(cert);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_issuer_dn_legacy(ngx_connection_t *c, ngx_pool_t *pool,
+    ngx_str_t *s)
+{
+    char       *p;
+    size_t      len;
+    X509       *cert;
+    X509_NAME  *name;
+
+    s->len = 0;
+
+    cert = SSL_get_peer_certificate(c->ssl->connection);
+    if (cert == NULL) {
+        return NGX_OK;
+    }
+
+    name = X509_get_issuer_name(cert);
+    if (name == NULL) {
+        X509_free(cert);
+        return NGX_ERROR;
+    }
+
+    p = X509_NAME_oneline(name, NULL, 0);
+
+    for (len = 0; p[len]; len++) { /* void */ }
+
+    s->len = len;
+    s->data = ngx_pnalloc(pool, len);
+    if (s->data == NULL) {
+        OPENSSL_free(p);
+        X509_free(cert);
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(s->data, p, len);
+
+    OPENSSL_free(p);
+    X509_free(cert);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_serial_number(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+    size_t   len;
+    X509    *cert;
+    BIO     *bio;
+
+    s->len = 0;
+
+    cert = SSL_get_peer_certificate(c->ssl->connection);
+    if (cert == NULL) {
+        return NGX_OK;
+    }
+
+    bio = BIO_new(BIO_s_mem());
+    if (bio == NULL) {
+        X509_free(cert);
+        return NGX_ERROR;
+    }
+
+    i2a_ASN1_INTEGER(bio, X509_get_serialNumber(cert));
+    len = BIO_pending(bio);
+
+    s->len = len;
+    s->data = ngx_pnalloc(pool, len);
+    if (s->data == NULL) {
+        BIO_free(bio);
+        X509_free(cert);
+        return NGX_ERROR;
+    }
+
+    BIO_read(bio, s->data, len);
+    BIO_free(bio);
+    X509_free(cert);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_fingerprint(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+    X509          *cert;
+    unsigned int   len;
+    u_char         buf[EVP_MAX_MD_SIZE];
+
+    s->len = 0;
+
+    cert = SSL_get_peer_certificate(c->ssl->connection);
+    if (cert == NULL) {
+        return NGX_OK;
+    }
+
+    if (!X509_digest(cert, EVP_sha1(), buf, &len)) {
+        X509_free(cert);
+        return NGX_ERROR;
+    }
+
+    s->len = 2 * len;
+    s->data = ngx_pnalloc(pool, 2 * len);
+    if (s->data == NULL) {
+        X509_free(cert);
+        return NGX_ERROR;
+    }
+
+    ngx_hex_dump(s->data, buf, len);
+
+    X509_free(cert);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_client_verify(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+    X509        *cert;
+    long         rc;
+    const char  *str;
+
+    cert = SSL_get_peer_certificate(c->ssl->connection);
+    if (cert == NULL) {
+        ngx_str_set(s, "NONE");
+        return NGX_OK;
+    }
+
+    X509_free(cert);
+
+    rc = SSL_get_verify_result(c->ssl->connection);
+
+    if (rc == X509_V_OK) {
+        ngx_str_set(s, "SUCCESS");
+        return NGX_OK;
+    }
+
+    str = X509_verify_cert_error_string(rc);
+
+    s->data = ngx_pnalloc(pool, sizeof("FAILED:") - 1 + ngx_strlen(str));
+    if (s->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    s->len = ngx_sprintf(s->data, "FAILED:%s", str) - s->data;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_client_v_start(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+    BIO     *bio;
+    X509    *cert;
+    size_t   len;
+
+    s->len = 0;
+
+    cert = SSL_get_peer_certificate(c->ssl->connection);
+    if (cert == NULL) {
+        return NGX_OK;
+    }
+
+    bio = BIO_new(BIO_s_mem());
+    if (bio == NULL) {
+        X509_free(cert);
+        return NGX_ERROR;
+    }
+
+#if OPENSSL_VERSION_NUMBER > 0x10100000L
+    ASN1_TIME_print(bio, X509_get0_notBefore(cert));
+#else
+    ASN1_TIME_print(bio, X509_get_notBefore(cert));
+#endif
+
+    len = BIO_pending(bio);
+
+    s->len = len;
+    s->data = ngx_pnalloc(pool, len);
+    if (s->data == NULL) {
+        BIO_free(bio);
+        X509_free(cert);
+        return NGX_ERROR;
+    }
+
+    BIO_read(bio, s->data, len);
+    BIO_free(bio);
+    X509_free(cert);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_client_v_end(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+    BIO     *bio;
+    X509    *cert;
+    size_t   len;
+
+    s->len = 0;
+
+    cert = SSL_get_peer_certificate(c->ssl->connection);
+    if (cert == NULL) {
+        return NGX_OK;
+    }
+
+    bio = BIO_new(BIO_s_mem());
+    if (bio == NULL) {
+        X509_free(cert);
+        return NGX_ERROR;
+    }
+
+#if OPENSSL_VERSION_NUMBER > 0x10100000L
+    ASN1_TIME_print(bio, X509_get0_notAfter(cert));
+#else
+    ASN1_TIME_print(bio, X509_get_notAfter(cert));
+#endif
+
+    len = BIO_pending(bio);
+
+    s->len = len;
+    s->data = ngx_pnalloc(pool, len);
+    if (s->data == NULL) {
+        BIO_free(bio);
+        X509_free(cert);
+        return NGX_ERROR;
+    }
+
+    BIO_read(bio, s->data, len);
+    BIO_free(bio);
+    X509_free(cert);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_client_v_remain(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+    X509    *cert;
+    time_t   now, end;
+
+    s->len = 0;
+
+    cert = SSL_get_peer_certificate(c->ssl->connection);
+    if (cert == NULL) {
+        return NGX_OK;
+    }
+
+#if OPENSSL_VERSION_NUMBER > 0x10100000L
+    end = ngx_ssl_parse_time(X509_get0_notAfter(cert));
+#else
+    end = ngx_ssl_parse_time(X509_get_notAfter(cert));
+#endif
+
+    if (end == (time_t) NGX_ERROR) {
+        X509_free(cert);
+        return NGX_OK;
+    }
+
+    now = ngx_time();
+
+    if (end < now + 86400) {
+        ngx_str_set(s, "0");
+        X509_free(cert);
+        return NGX_OK;
+    }
+
+    s->data = ngx_pnalloc(pool, NGX_TIME_T_LEN);
+    if (s->data == NULL) {
+        X509_free(cert);
+        return NGX_ERROR;
+    }
+
+    s->len = ngx_sprintf(s->data, "%T", (end - now) / 86400) - s->data;
+
+    X509_free(cert);
+
+    return NGX_OK;
+}
+
+
+static time_t
+ngx_ssl_parse_time(
+#if OPENSSL_VERSION_NUMBER > 0x10100000L
+    const
+#endif
+    ASN1_TIME *asn1time)
+{
+    BIO     *bio;
+    char    *value;
+    size_t   len;
+    time_t   time;
+
+    /*
+     * OpenSSL doesn't provide a way to convert ASN1_TIME
+     * into time_t.  To do this, we use ASN1_TIME_print(),
+     * which uses the "MMM DD HH:MM:SS YYYY [GMT]" format (e.g.,
+     * "Feb  3 00:55:52 2015 GMT"), and parse the result.
+     */
+
+    bio = BIO_new(BIO_s_mem());
+    if (bio == NULL) {
+        return NGX_ERROR;
+    }
+
+    /* fake weekday prepended to match C asctime() format */
+
+    BIO_write(bio, "Tue ", sizeof("Tue ") - 1);
+    ASN1_TIME_print(bio, asn1time);
+    len = BIO_get_mem_data(bio, &value);
+
+    time = ngx_parse_http_time((u_char *) value, len);
+
+    BIO_free(bio);
+
+    return time;
+}
+
+
+static void *
+ngx_openssl_create_conf(ngx_cycle_t *cycle)
+{
+    ngx_openssl_conf_t  *oscf;
+
+    oscf = ngx_pcalloc(cycle->pool, sizeof(ngx_openssl_conf_t));
+    if (oscf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     oscf->engine = 0;
+     */
+
+    return oscf;
+}
+
+
+static char *
+ngx_openssl_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+#ifndef OPENSSL_NO_ENGINE
+
+    ngx_openssl_conf_t *oscf = conf;
+
+    ENGINE     *engine;
+    ngx_str_t  *value;
+
+    if (oscf->engine) {
+        return "is duplicate";
+    }
+
+    oscf->engine = 1;
+
+    value = cf->args->elts;
+
+    engine = ENGINE_by_id((char *) value[1].data);
+
+    if (engine == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+                      "ENGINE_by_id(\"%V\") failed", &value[1]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (ENGINE_set_default(engine, ENGINE_METHOD_ALL) == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+                      "ENGINE_set_default(\"%V\", ENGINE_METHOD_ALL) failed",
+                      &value[1]);
+
+        ENGINE_free(engine);
+
+        return NGX_CONF_ERROR;
+    }
+
+    ENGINE_free(engine);
+
+    return NGX_CONF_OK;
+
+#else
+
+    return "is not supported";
+
+#endif
+}
+
+
+static void
+ngx_openssl_exit(ngx_cycle_t *cycle)
+{
+#if OPENSSL_VERSION_NUMBER < 0x10100003L
+
+    EVP_cleanup();
+#ifndef OPENSSL_NO_ENGINE
+    ENGINE_cleanup();
+#endif
+
+#endif
+}
diff --git a/nginx/src/event/ngx_event_openssl.h b/nginx/src/event/ngx_event_openssl.h
new file mode 100644 (file)
index 0000000..05d3291
--- /dev/null
@@ -0,0 +1,267 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_EVENT_OPENSSL_H_INCLUDED_
+#define _NGX_EVENT_OPENSSL_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/bn.h>
+#include <openssl/conf.h>
+#include <openssl/crypto.h>
+#include <openssl/dh.h>
+#ifndef OPENSSL_NO_ENGINE
+#include <openssl/engine.h>
+#endif
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#ifndef OPENSSL_NO_OCSP
+#include <openssl/ocsp.h>
+#endif
+#include <openssl/rand.h>
+#include <openssl/rsa.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+
+#define NGX_SSL_NAME     "OpenSSL"
+
+
+#if (defined LIBRESSL_VERSION_NUMBER && OPENSSL_VERSION_NUMBER == 0x20000000L)
+#undef OPENSSL_VERSION_NUMBER
+#if (LIBRESSL_VERSION_NUMBER >= 0x2080000fL)
+#define OPENSSL_VERSION_NUMBER  0x1010000fL
+#else
+#define OPENSSL_VERSION_NUMBER  0x1000107fL
+#endif
+#endif
+
+
+#if (OPENSSL_VERSION_NUMBER >= 0x10100001L)
+
+#define ngx_ssl_version()       OpenSSL_version(OPENSSL_VERSION)
+
+#else
+
+#define ngx_ssl_version()       SSLeay_version(SSLEAY_VERSION)
+
+#endif
+
+
+#define ngx_ssl_session_t       SSL_SESSION
+#define ngx_ssl_conn_t          SSL
+
+
+#if (OPENSSL_VERSION_NUMBER < 0x10002000L)
+#define SSL_is_server(s)        (s)->server
+#endif
+
+
+struct ngx_ssl_s {
+    SSL_CTX                    *ctx;
+    ngx_log_t                  *log;
+    size_t                      buffer_size;
+};
+
+
+struct ngx_ssl_connection_s {
+    ngx_ssl_conn_t             *connection;
+    SSL_CTX                    *session_ctx;
+
+    ngx_int_t                   last;
+    ngx_buf_t                  *buf;
+    size_t                      buffer_size;
+
+    ngx_connection_handler_pt   handler;
+
+    ngx_event_handler_pt        saved_read_handler;
+    ngx_event_handler_pt        saved_write_handler;
+
+    unsigned                    handshaked:1;
+    unsigned                    renegotiation:1;
+    unsigned                    buffer:1;
+    unsigned                    no_wait_shutdown:1;
+    unsigned                    no_send_shutdown:1;
+    unsigned                    handshake_buffer_set:1;
+};
+
+
+#define NGX_SSL_NO_SCACHE            -2
+#define NGX_SSL_NONE_SCACHE          -3
+#define NGX_SSL_NO_BUILTIN_SCACHE    -4
+#define NGX_SSL_DFLT_BUILTIN_SCACHE  -5
+
+
+#define NGX_SSL_MAX_SESSION_SIZE  4096
+
+typedef struct ngx_ssl_sess_id_s  ngx_ssl_sess_id_t;
+
+struct ngx_ssl_sess_id_s {
+    ngx_rbtree_node_t           node;
+    u_char                     *id;
+    size_t                      len;
+    u_char                     *session;
+    ngx_queue_t                 queue;
+    time_t                      expire;
+#if (NGX_PTR_SIZE == 8)
+    void                       *stub;
+    u_char                      sess_id[32];
+#endif
+};
+
+
+typedef struct {
+    ngx_rbtree_t                session_rbtree;
+    ngx_rbtree_node_t           sentinel;
+    ngx_queue_t                 expire_queue;
+} ngx_ssl_session_cache_t;
+
+
+#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
+
+typedef struct {
+    size_t                      size;
+    u_char                      name[16];
+    u_char                      hmac_key[32];
+    u_char                      aes_key[32];
+} ngx_ssl_session_ticket_key_t;
+
+#endif
+
+
+#define NGX_SSL_SSLv2    0x0002
+#define NGX_SSL_SSLv3    0x0004
+#define NGX_SSL_TLSv1    0x0008
+#define NGX_SSL_TLSv1_1  0x0010
+#define NGX_SSL_TLSv1_2  0x0020
+#define NGX_SSL_TLSv1_3  0x0040
+
+
+#define NGX_SSL_BUFFER   1
+#define NGX_SSL_CLIENT   2
+
+#define NGX_SSL_BUFSIZE  16384
+
+
+ngx_int_t ngx_ssl_init(ngx_log_t *log);
+ngx_int_t ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data);
+ngx_int_t ngx_ssl_certificates(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_array_t *certs, ngx_array_t *keys, ngx_array_t *passwords);
+ngx_int_t ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_str_t *cert, ngx_str_t *key, ngx_array_t *passwords);
+ngx_int_t ngx_ssl_ciphers(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *ciphers,
+    ngx_uint_t prefer_server_ciphers);
+ngx_int_t ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_str_t *cert, ngx_int_t depth);
+ngx_int_t ngx_ssl_trusted_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_str_t *cert, ngx_int_t depth);
+ngx_int_t ngx_ssl_crl(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *crl);
+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_array_t *ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file);
+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_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx,
+    ssize_t builtin_session_cache, ngx_shm_zone_t *shm_zone, time_t timeout);
+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);
+
+void ngx_ssl_remove_cached_session(SSL_CTX *ssl, ngx_ssl_session_t *sess);
+ngx_int_t ngx_ssl_set_session(ngx_connection_t *c, ngx_ssl_session_t *session);
+#define ngx_ssl_get_session(c)      SSL_get1_session(c->ssl->connection)
+#define ngx_ssl_free_session        SSL_SESSION_free
+#define ngx_ssl_get_connection(ssl_conn)                                      \
+    SSL_get_ex_data(ssl_conn, ngx_ssl_connection_index)
+#define ngx_ssl_get_server_conf(ssl_ctx)                                      \
+    SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_server_conf_index)
+
+#define ngx_ssl_verify_error_optional(n)                                      \
+    (n == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT                              \
+     || n == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN                             \
+     || n == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY                     \
+     || n == X509_V_ERR_CERT_UNTRUSTED                                        \
+     || n == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE)
+
+ngx_int_t ngx_ssl_check_host(ngx_connection_t *c, ngx_str_t *name);
+
+
+ngx_int_t ngx_ssl_get_protocol(ngx_connection_t *c, ngx_pool_t *pool,
+    ngx_str_t *s);
+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_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,
+    ngx_str_t *s);
+ngx_int_t ngx_ssl_get_session_reused(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_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,
+    ngx_str_t *s);
+ngx_int_t ngx_ssl_get_escaped_certificate(ngx_connection_t *c, ngx_pool_t *pool,
+    ngx_str_t *s);
+ngx_int_t ngx_ssl_get_subject_dn(ngx_connection_t *c, ngx_pool_t *pool,
+    ngx_str_t *s);
+ngx_int_t ngx_ssl_get_issuer_dn(ngx_connection_t *c, ngx_pool_t *pool,
+    ngx_str_t *s);
+ngx_int_t ngx_ssl_get_subject_dn_legacy(ngx_connection_t *c, ngx_pool_t *pool,
+    ngx_str_t *s);
+ngx_int_t ngx_ssl_get_issuer_dn_legacy(ngx_connection_t *c, ngx_pool_t *pool,
+    ngx_str_t *s);
+ngx_int_t ngx_ssl_get_serial_number(ngx_connection_t *c, ngx_pool_t *pool,
+    ngx_str_t *s);
+ngx_int_t ngx_ssl_get_fingerprint(ngx_connection_t *c, ngx_pool_t *pool,
+    ngx_str_t *s);
+ngx_int_t ngx_ssl_get_client_verify(ngx_connection_t *c, ngx_pool_t *pool,
+    ngx_str_t *s);
+ngx_int_t ngx_ssl_get_client_v_start(ngx_connection_t *c, ngx_pool_t *pool,
+    ngx_str_t *s);
+ngx_int_t ngx_ssl_get_client_v_end(ngx_connection_t *c, ngx_pool_t *pool,
+    ngx_str_t *s);
+ngx_int_t ngx_ssl_get_client_v_remain(ngx_connection_t *c, ngx_pool_t *pool,
+    ngx_str_t *s);
+
+
+ngx_int_t ngx_ssl_handshake(ngx_connection_t *c);
+ssize_t ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size);
+ssize_t ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size);
+ssize_t ngx_ssl_recv_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t limit);
+ngx_chain_t *ngx_ssl_send_chain(ngx_connection_t *c, ngx_chain_t *in,
+    off_t limit);
+void ngx_ssl_free_buffer(ngx_connection_t *c);
+ngx_int_t ngx_ssl_shutdown(ngx_connection_t *c);
+void ngx_cdecl ngx_ssl_error(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
+    char *fmt, ...);
+void ngx_ssl_cleanup_ctx(void *data);
+
+
+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_certificate_index;
+extern int  ngx_ssl_next_certificate_index;
+extern int  ngx_ssl_certificate_name_index;
+extern int  ngx_ssl_stapling_index;
+
+
+#endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */
diff --git a/nginx/src/event/ngx_event_openssl_stapling.c b/nginx/src/event/ngx_event_openssl_stapling.c
new file mode 100644 (file)
index 0000000..0bea5e7
--- /dev/null
@@ -0,0 +1,1892 @@
+
+/*
+ * Copyright (C) Maxim Dounin
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_connect.h>
+
+
+#if (!defined OPENSSL_NO_OCSP && defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB)
+
+
+typedef struct {
+    ngx_str_t                    staple;
+    ngx_msec_t                   timeout;
+
+    ngx_resolver_t              *resolver;
+    ngx_msec_t                   resolver_timeout;
+
+    ngx_addr_t                  *addrs;
+    ngx_str_t                    host;
+    ngx_str_t                    uri;
+    in_port_t                    port;
+
+    SSL_CTX                     *ssl_ctx;
+
+    X509                        *cert;
+    X509                        *issuer;
+
+    u_char                      *name;
+
+    time_t                       valid;
+    time_t                       refresh;
+
+    unsigned                     verify:1;
+    unsigned                     loading:1;
+} ngx_ssl_stapling_t;
+
+
+typedef struct ngx_ssl_ocsp_ctx_s  ngx_ssl_ocsp_ctx_t;
+
+struct ngx_ssl_ocsp_ctx_s {
+    X509                        *cert;
+    X509                        *issuer;
+
+    u_char                      *name;
+
+    ngx_uint_t                   naddrs;
+
+    ngx_addr_t                  *addrs;
+    ngx_str_t                    host;
+    ngx_str_t                    uri;
+    in_port_t                    port;
+
+    ngx_resolver_t              *resolver;
+    ngx_msec_t                   resolver_timeout;
+
+    ngx_msec_t                   timeout;
+
+    void                       (*handler)(ngx_ssl_ocsp_ctx_t *ctx);
+    void                        *data;
+
+    ngx_buf_t                   *request;
+    ngx_buf_t                   *response;
+    ngx_peer_connection_t        peer;
+
+    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                   done;
+
+    u_char                      *header_name_start;
+    u_char                      *header_name_end;
+    u_char                      *header_start;
+    u_char                      *header_end;
+
+    ngx_pool_t                  *pool;
+    ngx_log_t                   *log;
+};
+
+
+static ngx_int_t ngx_ssl_stapling_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    X509 *cert, ngx_str_t *file, ngx_str_t *responder, ngx_uint_t verify);
+static ngx_int_t ngx_ssl_stapling_file(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_ssl_stapling_t *staple, ngx_str_t *file);
+static ngx_int_t ngx_ssl_stapling_issuer(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_ssl_stapling_t *staple);
+static ngx_int_t ngx_ssl_stapling_responder(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_ssl_stapling_t *staple, ngx_str_t *responder);
+
+static int ngx_ssl_certificate_status_callback(ngx_ssl_conn_t *ssl_conn,
+    void *data);
+static void ngx_ssl_stapling_update(ngx_ssl_stapling_t *staple);
+static void ngx_ssl_stapling_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx);
+
+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_done(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);
+static void ngx_ssl_ocsp_write_handler(ngx_event_t *wev);
+static void ngx_ssl_ocsp_read_handler(ngx_event_t *rev);
+static void ngx_ssl_ocsp_dummy_handler(ngx_event_t *ev);
+
+static ngx_int_t ngx_ssl_ocsp_create_request(ngx_ssl_ocsp_ctx_t *ctx);
+static ngx_int_t ngx_ssl_ocsp_process_status_line(ngx_ssl_ocsp_ctx_t *ctx);
+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 u_char *ngx_ssl_ocsp_log_error(ngx_log_t *log, u_char *buf, size_t len);
+
+
+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)
+{
+    X509  *cert;
+
+    for (cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index);
+         cert;
+         cert = X509_get_ex_data(cert, ngx_ssl_next_certificate_index))
+    {
+        if (ngx_ssl_stapling_certificate(cf, ssl, cert, file, responder, verify)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    SSL_CTX_set_tlsext_status_cb(ssl->ctx, ngx_ssl_certificate_status_callback);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_ssl_stapling_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, X509 *cert,
+    ngx_str_t *file, ngx_str_t *responder, ngx_uint_t verify)
+{
+    ngx_int_t            rc;
+    ngx_pool_cleanup_t  *cln;
+    ngx_ssl_stapling_t  *staple;
+
+    staple = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_stapling_t));
+    if (staple == NULL) {
+        return NGX_ERROR;
+    }
+
+    cln = ngx_pool_cleanup_add(cf->pool, 0);
+    if (cln == NULL) {
+        return NGX_ERROR;
+    }
+
+    cln->handler = ngx_ssl_stapling_cleanup;
+    cln->data = staple;
+
+    if (X509_set_ex_data(cert, ngx_ssl_stapling_index, staple) == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_set_ex_data() failed");
+        return NGX_ERROR;
+    }
+
+    staple->ssl_ctx = ssl->ctx;
+    staple->timeout = 60000;
+    staple->verify = verify;
+    staple->cert = cert;
+    staple->name = X509_get_ex_data(staple->cert,
+                                    ngx_ssl_certificate_name_index);
+
+    if (file->len) {
+        /* use OCSP response from the file */
+
+        if (ngx_ssl_stapling_file(cf, ssl, staple, file) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        return NGX_OK;
+    }
+
+    rc = ngx_ssl_stapling_issuer(cf, ssl, staple);
+
+    if (rc == NGX_DECLINED) {
+        return NGX_OK;
+    }
+
+    if (rc != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    rc = ngx_ssl_stapling_responder(cf, ssl, staple, responder);
+
+    if (rc == NGX_DECLINED) {
+        return NGX_OK;
+    }
+
+    if (rc != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_ssl_stapling_file(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_ssl_stapling_t *staple, ngx_str_t *file)
+{
+    BIO            *bio;
+    int             len;
+    u_char         *p, *buf;
+    OCSP_RESPONSE  *response;
+
+    if (ngx_conf_full_name(cf->cycle, file, 1) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    bio = BIO_new_file((char *) file->data, "r");
+    if (bio == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "BIO_new_file(\"%s\") failed", file->data);
+        return NGX_ERROR;
+    }
+
+    response = d2i_OCSP_RESPONSE_bio(bio, NULL);
+    if (response == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "d2i_OCSP_RESPONSE_bio(\"%s\") failed", file->data);
+        BIO_free(bio);
+        return NGX_ERROR;
+    }
+
+    len = i2d_OCSP_RESPONSE(response, NULL);
+    if (len <= 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "i2d_OCSP_RESPONSE(\"%s\") failed", file->data);
+        goto failed;
+    }
+
+    buf = ngx_alloc(len, ssl->log);
+    if (buf == NULL) {
+        goto failed;
+    }
+
+    p = buf;
+    len = i2d_OCSP_RESPONSE(response, &p);
+    if (len <= 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "i2d_OCSP_RESPONSE(\"%s\") failed", file->data);
+        ngx_free(buf);
+        goto failed;
+    }
+
+    OCSP_RESPONSE_free(response);
+    BIO_free(bio);
+
+    staple->staple.data = buf;
+    staple->staple.len = len;
+    staple->valid = NGX_MAX_TIME_T_VALUE;
+
+    return NGX_OK;
+
+failed:
+
+    OCSP_RESPONSE_free(response);
+    BIO_free(bio);
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_ssl_stapling_issuer(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_ssl_stapling_t *staple)
+{
+    int              i, n, rc;
+    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);
+
+    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);
+        if (X509_check_issued(issuer, cert) == X509_V_OK) {
+#if OPENSSL_VERSION_NUMBER >= 0x10100001L
+            X509_up_ref(issuer);
+#else
+            CRYPTO_add(&issuer->references, 1, CRYPTO_LOCK_X509);
+#endif
+
+            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ssl->log, 0,
+                           "SSL get issuer: found %p in extra certs", issuer);
+
+            staple->issuer = issuer;
+
+            return NGX_OK;
+        }
+    }
+
+    store = SSL_CTX_get_cert_store(ssl->ctx);
+    if (store == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_get_cert_store() failed");
+        return NGX_ERROR;
+    }
+
+    store_ctx = X509_STORE_CTX_new();
+    if (store_ctx == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "X509_STORE_CTX_new() failed");
+        return NGX_ERROR;
+    }
+
+    if (X509_STORE_CTX_init(store_ctx, store, NULL, NULL) == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "X509_STORE_CTX_init() failed");
+        X509_STORE_CTX_free(store_ctx);
+        return NGX_ERROR;
+    }
+
+    rc = X509_STORE_CTX_get1_issuer(&issuer, store_ctx, cert);
+
+    if (rc == -1) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "X509_STORE_CTX_get1_issuer() failed");
+        X509_STORE_CTX_free(store_ctx);
+        return NGX_ERROR;
+    }
+
+    if (rc == 0) {
+        ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
+                      "\"ssl_stapling\" ignored, "
+                      "issuer certificate not found for certificate \"%s\"",
+                      staple->name);
+        X509_STORE_CTX_free(store_ctx);
+        return NGX_DECLINED;
+    }
+
+    X509_STORE_CTX_free(store_ctx);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ssl->log, 0,
+                   "SSL get issuer: found %p in cert store", issuer);
+
+    staple->issuer = issuer;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_ssl_stapling_responder(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_ssl_stapling_t *staple, ngx_str_t *responder)
+{
+    char                      *s;
+    ngx_str_t                  rsp;
+    ngx_url_t                  u;
+    STACK_OF(OPENSSL_STRING)  *aia;
+
+    if (responder->len == 0) {
+
+        /* extract OCSP responder URL from certificate */
+
+        aia = X509_get1_ocsp(staple->cert);
+        if (aia == NULL) {
+            ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
+                          "\"ssl_stapling\" ignored, "
+                          "no OCSP responder URL in the certificate \"%s\"",
+                          staple->name);
+            return NGX_DECLINED;
+        }
+
+#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_WARN, ssl->log, 0,
+                          "\"ssl_stapling\" ignored, "
+                          "no OCSP responder URL in the certificate \"%s\"",
+                          staple->name);
+            X509_email_free(aia);
+            return NGX_DECLINED;
+        }
+
+        responder = &rsp;
+
+        responder->len = ngx_strlen(s);
+        responder->data = ngx_palloc(cf->pool, responder->len);
+        if (responder->data == NULL) {
+            X509_email_free(aia);
+            return NGX_ERROR;
+        }
+
+        ngx_memcpy(responder->data, s, responder->len);
+        X509_email_free(aia);
+    }
+
+    ngx_memzero(&u, sizeof(ngx_url_t));
+
+    u.url = *responder;
+    u.default_port = 80;
+    u.uri_part = 1;
+
+    if (u.url.len > 7
+        && ngx_strncasecmp(u.url.data, (u_char *) "http://", 7) == 0)
+    {
+        u.url.len -= 7;
+        u.url.data += 7;
+
+    } else {
+        ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
+                      "\"ssl_stapling\" ignored, "
+                      "invalid URL prefix in OCSP responder \"%V\" "
+                      "in the certificate \"%s\"",
+                      &u.url, staple->name);
+        return NGX_DECLINED;
+    }
+
+    if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
+        if (u.err) {
+            ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
+                          "\"ssl_stapling\" ignored, "
+                          "%s in OCSP responder \"%V\" "
+                          "in the certificate \"%s\"",
+                          u.err, &u.url, staple->name);
+            return NGX_DECLINED;
+        }
+
+        return NGX_ERROR;
+    }
+
+    staple->addrs = u.addrs;
+    staple->host = u.host;
+    staple->uri = u.uri;
+    staple->port = u.port;
+
+    if (staple->uri.len == 0) {
+        ngx_str_set(&staple->uri, "/");
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_stapling_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_resolver_t *resolver, ngx_msec_t resolver_timeout)
+{
+    X509                *cert;
+    ngx_ssl_stapling_t  *staple;
+
+    for (cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index);
+         cert;
+         cert = X509_get_ex_data(cert, ngx_ssl_next_certificate_index))
+    {
+        staple = X509_get_ex_data(cert, ngx_ssl_stapling_index);
+        staple->resolver = resolver;
+        staple->resolver_timeout = resolver_timeout;
+    }
+
+    return NGX_OK;
+}
+
+
+static int
+ngx_ssl_certificate_status_callback(ngx_ssl_conn_t *ssl_conn, void *data)
+{
+    int                  rc;
+    X509                *cert;
+    u_char              *p;
+    ngx_connection_t    *c;
+    ngx_ssl_stapling_t  *staple;
+
+    c = ngx_ssl_get_connection(ssl_conn);
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "SSL certificate status callback");
+
+    rc = SSL_TLSEXT_ERR_NOACK;
+
+    cert = SSL_get_certificate(ssl_conn);
+    staple = X509_get_ex_data(cert, ngx_ssl_stapling_index);
+
+    if (staple == NULL) {
+        return rc;
+    }
+
+    if (staple->staple.len
+        && staple->valid >= ngx_time())
+    {
+        /* we have to copy ocsp response as OpenSSL will free it by itself */
+
+        p = OPENSSL_malloc(staple->staple.len);
+        if (p == NULL) {
+            ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "OPENSSL_malloc() failed");
+            return SSL_TLSEXT_ERR_NOACK;
+        }
+
+        ngx_memcpy(p, staple->staple.data, staple->staple.len);
+
+        SSL_set_tlsext_status_ocsp_resp(ssl_conn, p, staple->staple.len);
+
+        rc = SSL_TLSEXT_ERR_OK;
+    }
+
+    ngx_ssl_stapling_update(staple);
+
+    return rc;
+}
+
+
+static void
+ngx_ssl_stapling_update(ngx_ssl_stapling_t *staple)
+{
+    ngx_ssl_ocsp_ctx_t  *ctx;
+
+    if (staple->host.len == 0
+        || staple->loading || staple->refresh >= ngx_time())
+    {
+        return;
+    }
+
+    staple->loading = 1;
+
+    ctx = ngx_ssl_ocsp_start();
+    if (ctx == NULL) {
+        return;
+    }
+
+    ctx->cert = staple->cert;
+    ctx->issuer = staple->issuer;
+    ctx->name = staple->name;
+
+    ctx->addrs = staple->addrs;
+    ctx->host = staple->host;
+    ctx->uri = staple->uri;
+    ctx->port = staple->port;
+    ctx->timeout = staple->timeout;
+
+    ctx->resolver = staple->resolver;
+    ctx->resolver_timeout = staple->resolver_timeout;
+
+    ctx->handler = ngx_ssl_stapling_ocsp_handler;
+    ctx->data = staple;
+
+    ngx_ssl_ocsp_request(ctx);
+
+    return;
+}
+
+
+static void
+ngx_ssl_stapling_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x0090707fL
+    const
+#endif
+    u_char                *p;
+    int                    n;
+    size_t                 len;
+    time_t                 now, valid;
+    ngx_str_t              response;
+    X509_STORE            *store;
+    STACK_OF(X509)        *chain;
+    OCSP_CERTID           *id;
+    OCSP_RESPONSE         *ocsp;
+    OCSP_BASICRESP        *basic;
+    ngx_ssl_stapling_t    *staple;
+    ASN1_GENERALIZEDTIME  *thisupdate, *nextupdate;
+
+    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");
+        goto error;
+    }
+
+    if (n != 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");
+        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.data = ngx_alloc(response.len, ctx->log);
+
+    if (response.data == NULL) {
+        goto error;
+    }
+
+    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;
+
+    /*
+     * refresh before the response expires,
+     * but not earlier than in 5 minutes, and at least in an hour
+     */
+
+    staple->loading = 0;
+    staple->refresh = ngx_max(ngx_min(valid - 300, now + 3600), now + 300);
+
+    ngx_ssl_ocsp_done(ctx);
+    return;
+
+error:
+
+    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);
+}
+
+
+static time_t
+ngx_ssl_stapling_time(ASN1_GENERALIZEDTIME *asn1time)
+{
+    BIO     *bio;
+    char    *value;
+    size_t   len;
+    time_t   time;
+
+    /*
+     * OpenSSL doesn't provide a way to convert ASN1_GENERALIZEDTIME
+     * into time_t.  To do this, we use ASN1_GENERALIZEDTIME_print(),
+     * which uses the "MMM DD HH:MM:SS YYYY [GMT]" format (e.g.,
+     * "Feb  3 00:55:52 2015 GMT"), and parse the result.
+     */
+
+    bio = BIO_new(BIO_s_mem());
+    if (bio == NULL) {
+        return NGX_ERROR;
+    }
+
+    /* fake weekday prepended to match C asctime() format */
+
+    BIO_write(bio, "Tue ", sizeof("Tue ") - 1);
+    ASN1_GENERALIZEDTIME_print(bio, asn1time);
+    len = BIO_get_mem_data(bio, &value);
+
+    time = ngx_parse_http_time((u_char *) value, len);
+
+    BIO_free(bio);
+
+    return time;
+}
+
+
+static void
+ngx_ssl_stapling_cleanup(void *data)
+{
+    ngx_ssl_stapling_t  *staple = data;
+
+    if (staple->issuer) {
+        X509_free(staple->issuer);
+    }
+
+    if (staple->staple.data) {
+        ngx_free(staple->staple.data);
+    }
+}
+
+
+static ngx_ssl_ocsp_ctx_t *
+ngx_ssl_ocsp_start(void)
+{
+    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;
+    }
+
+    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_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) {
+            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_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp connect");
+
+    /* TODO: use all ip addresses */
+
+    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;
+
+    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 || rc == NGX_BUSY || rc == NGX_DECLINED) {
+        ngx_ssl_ocsp_error(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;
+
+    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_error(ctx);
+        return;
+    }
+
+    size = ctx->request->last - ctx->request->pos;
+
+    n = ngx_send(c, ctx->request->pos, size);
+
+    if (n == NGX_ERROR) {
+        ngx_ssl_ocsp_error(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) {
+        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_error(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_error(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 */
+        return;
+    }
+
+    ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+                  "OCSP responder prematurely closed connection");
+
+    ngx_ssl_ocsp_error(ctx);
+}
+
+
+static void
+ngx_ssl_ocsp_dummy_handler(ngx_event_t *ev)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "ssl ocsp dummy handler");
+}
+
+
+static ngx_int_t
+ngx_ssl_ocsp_create_request(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    int            len;
+    u_char        *p;
+    uintptr_t      escape;
+    ngx_str_t      binary, base64;
+    ngx_buf_t     *b;
+    OCSP_CERTID   *id;
+    OCSP_REQUEST  *ocsp;
+
+    ocsp = OCSP_REQUEST_new();
+    if (ocsp == NULL) {
+        ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
+                      "OCSP_REQUEST_new() failed");
+        return NGX_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 failed;
+    }
+
+    if (OCSP_request_add0_id(ocsp, id) == NULL) {
+        ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
+                      "OCSP_request_add0_id() failed");
+        OCSP_CERTID_free(id);
+        goto failed;
+    }
+
+    len = i2d_OCSP_REQUEST(ocsp, NULL);
+    if (len <= 0) {
+        ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
+                      "i2d_OCSP_REQUEST() failed");
+        goto failed;
+    }
+
+    binary.len = len;
+    binary.data = ngx_palloc(ctx->pool, len);
+    if (binary.data == NULL) {
+        goto failed;
+    }
+
+    p = binary.data;
+    len = i2d_OCSP_REQUEST(ocsp, &p);
+    if (len <= 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ctx->log, 0,
+                      "i2d_OCSP_REQUEST() failed");
+        goto failed;
+    }
+
+    base64.len = ngx_base64_encoded_length(binary.len);
+    base64.data = ngx_palloc(ctx->pool, base64.len);
+    if (base64.data == NULL) {
+        goto failed;
+    }
+
+    ngx_encode_base64(&base64, &binary);
+
+    escape = ngx_escape_uri(NULL, base64.data, base64.len,
+                            NGX_ESCAPE_URI_COMPONENT);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp request length %z, escape %d",
+                   base64.len, (int) escape);
+
+    len = sizeof("GET ") - 1 + ctx->uri.len + sizeof("/") - 1
+          + base64.len + 2 * escape + sizeof(" HTTP/1.0" CRLF) - 1
+          + sizeof("Host: ") - 1 + ctx->host.len + sizeof(CRLF) - 1
+          + sizeof(CRLF) - 1;
+
+    b = ngx_create_temp_buf(ctx->pool, len);
+    if (b == NULL) {
+        goto failed;
+    }
+
+    p = b->last;
+
+    p = ngx_cpymem(p, "GET ", sizeof("GET ") - 1);
+    p = ngx_cpymem(p, ctx->uri.data, ctx->uri.len);
+
+    if (ctx->uri.data[ctx->uri.len - 1] != '/') {
+        *p++ = '/';
+    }
+
+    if (escape == 0) {
+        p = ngx_cpymem(p, base64.data, base64.len);
+
+    } else {
+        p = (u_char *) ngx_escape_uri(p, base64.data, base64.len,
+                                      NGX_ESCAPE_URI_COMPONENT);
+    }
+
+    p = ngx_cpymem(p, " HTTP/1.0" CRLF, sizeof(" HTTP/1.0" CRLF) - 1);
+    p = ngx_cpymem(p, "Host: ", sizeof("Host: ") - 1);
+    p = ngx_cpymem(p, ctx->host.data, ctx->host.len);
+    *p++ = CR; *p++ = LF;
+
+    /* add "\r\n" at the header end */
+    *p++ = CR; *p++ = LF;
+
+    b->last = p;
+    ctx->request = b;
+
+    OCSP_REQUEST_free(ocsp);
+
+    return NGX_OK;
+
+failed:
+
+    OCSP_REQUEST_free(ocsp);
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_ssl_ocsp_process_status_line(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    ngx_int_t  rc;
+
+    rc = ngx_ssl_ocsp_parse_status_line(ctx);
+
+    if (rc == NGX_OK) {
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                       "ssl ocsp status %ui \"%*s\"",
+                       ctx->code,
+                       ctx->header_end - ctx->header_start,
+                       ctx->header_start);
+
+        ctx->process = ngx_ssl_ocsp_process_headers;
+        return ctx->process(ctx);
+    }
+
+    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;
+}
+
+
+static ngx_int_t
+ngx_ssl_ocsp_parse_status_line(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    u_char      ch;
+    u_char     *p;
+    ngx_buf_t  *b;
+    enum {
+        sw_start = 0,
+        sw_H,
+        sw_HT,
+        sw_HTT,
+        sw_HTTP,
+        sw_first_major_digit,
+        sw_major_digit,
+        sw_first_minor_digit,
+        sw_minor_digit,
+        sw_status,
+        sw_space_after_status,
+        sw_status_text,
+        sw_almost_done
+    } state;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp process status line");
+
+    state = ctx->state;
+    b = ctx->response;
+
+    for (p = b->pos; p < b->last; p++) {
+        ch = *p;
+
+        switch (state) {
+
+        /* "HTTP/" */
+        case sw_start:
+            switch (ch) {
+            case 'H':
+                state = sw_H;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        case sw_H:
+            switch (ch) {
+            case 'T':
+                state = sw_HT;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        case sw_HT:
+            switch (ch) {
+            case 'T':
+                state = sw_HTT;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        case sw_HTT:
+            switch (ch) {
+            case 'P':
+                state = sw_HTTP;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        case sw_HTTP:
+            switch (ch) {
+            case '/':
+                state = sw_first_major_digit;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        /* the first digit of major HTTP version */
+        case sw_first_major_digit:
+            if (ch < '1' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            state = sw_major_digit;
+            break;
+
+        /* the major HTTP version or dot */
+        case sw_major_digit:
+            if (ch == '.') {
+                state = sw_first_minor_digit;
+                break;
+            }
+
+            if (ch < '0' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            break;
+
+        /* the first digit of minor HTTP version */
+        case sw_first_minor_digit:
+            if (ch < '0' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            state = sw_minor_digit;
+            break;
+
+        /* the minor HTTP version or the end of the request line */
+        case sw_minor_digit:
+            if (ch == ' ') {
+                state = sw_status;
+                break;
+            }
+
+            if (ch < '0' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            break;
+
+        /* HTTP status code */
+        case sw_status:
+            if (ch == ' ') {
+                break;
+            }
+
+            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;
+            }
+
+            if (ch == CR) {
+                ctx->header_name_end = p;
+                ctx->header_start = p;
+                ctx->header_end = p;
+                state = sw_almost_done;
+                break;
+            }
+
+            if (ch == LF) {
+                ctx->header_name_end = p;
+                ctx->header_start = p;
+                ctx->header_end = p;
+                goto done;
+            }
+
+            return NGX_ERROR;
+
+        /* 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;
+
+        /* 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;
+
+        /* end of header line */
+        case sw_almost_done:
+            switch (ch) {
+            case LF:
+                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;
+            }
+        }
+    }
+
+    ctx->response->pos = p;
+    ctx->state = state;
+
+    return NGX_AGAIN;
+
+done:
+
+    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_body(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp process body");
+
+    if (ctx->done) {
+        ctx->handler(ctx);
+        return NGX_DONE;
+    }
+
+    return NGX_AGAIN;
+}
+
+
+static u_char *
+ngx_ssl_ocsp_log_error(ngx_log_t *log, u_char *buf, size_t len)
+{
+    u_char              *p;
+    ngx_ssl_ocsp_ctx_t  *ctx;
+
+    p = buf;
+
+    if (log->action) {
+        p = ngx_snprintf(buf, len, " while %s", log->action);
+        len -= p - buf;
+        buf = p;
+    }
+
+    ctx = log->data;
+
+    if (ctx) {
+        p = ngx_snprintf(buf, len, ", responder: %V", &ctx->host);
+        len -= p - buf;
+        buf = p;
+    }
+
+    if (ctx && ctx->peer.name) {
+        p = ngx_snprintf(buf, len, ", peer: %V", ctx->peer.name);
+        len -= p - buf;
+        buf = p;
+    }
+
+    if (ctx && ctx->name) {
+        p = ngx_snprintf(buf, len, ", certificate: \"%s\"", ctx->name);
+        len -= p - buf;
+        buf = p;
+    }
+
+    return p;
+}
+
+
+#else
+
+
+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_log_error(NGX_LOG_WARN, ssl->log, 0,
+                  "\"ssl_stapling\" ignored, not supported");
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_stapling_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_resolver_t *resolver, ngx_msec_t resolver_timeout)
+{
+    return NGX_OK;
+}
+
+
+#endif
diff --git a/nginx/src/event/ngx_event_pipe.c b/nginx/src/event/ngx_event_pipe.c
new file mode 100644 (file)
index 0000000..da7c4ee
--- /dev/null
@@ -0,0 +1,1110 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_pipe.h>
+
+
+static ngx_int_t ngx_event_pipe_read_upstream(ngx_event_pipe_t *p);
+static ngx_int_t ngx_event_pipe_write_to_downstream(ngx_event_pipe_t *p);
+
+static ngx_int_t ngx_event_pipe_write_chain_to_temp_file(ngx_event_pipe_t *p);
+static ngx_inline void ngx_event_pipe_remove_shadow_links(ngx_buf_t *buf);
+static ngx_int_t ngx_event_pipe_drain_chains(ngx_event_pipe_t *p);
+
+
+ngx_int_t
+ngx_event_pipe(ngx_event_pipe_t *p, ngx_int_t do_write)
+{
+    ngx_int_t     rc;
+    ngx_uint_t    flags;
+    ngx_event_t  *rev, *wev;
+
+    for ( ;; ) {
+        if (do_write) {
+            p->log->action = "sending to client";
+
+            rc = ngx_event_pipe_write_to_downstream(p);
+
+            if (rc == NGX_ABORT) {
+                return NGX_ABORT;
+            }
+
+            if (rc == NGX_BUSY) {
+                return NGX_OK;
+            }
+        }
+
+        p->read = 0;
+        p->upstream_blocked = 0;
+
+        p->log->action = "reading upstream";
+
+        if (ngx_event_pipe_read_upstream(p) == NGX_ABORT) {
+            return NGX_ABORT;
+        }
+
+        if (!p->read && !p->upstream_blocked) {
+            break;
+        }
+
+        do_write = 1;
+    }
+
+    if (p->upstream->fd != (ngx_socket_t) -1) {
+        rev = p->upstream->read;
+
+        flags = (rev->eof || rev->error) ? NGX_CLOSE_EVENT : 0;
+
+        if (ngx_handle_read_event(rev, flags) != NGX_OK) {
+            return NGX_ABORT;
+        }
+
+        if (!rev->delayed) {
+            if (rev->active && !rev->ready) {
+                ngx_add_timer(rev, p->read_timeout);
+
+            } else if (rev->timer_set) {
+                ngx_del_timer(rev);
+            }
+        }
+    }
+
+    if (p->downstream->fd != (ngx_socket_t) -1
+        && p->downstream->data == p->output_ctx)
+    {
+        wev = p->downstream->write;
+        if (ngx_handle_write_event(wev, p->send_lowat) != NGX_OK) {
+            return NGX_ABORT;
+        }
+
+        if (!wev->delayed) {
+            if (wev->active && !wev->ready) {
+                ngx_add_timer(wev, p->send_timeout);
+
+            } else if (wev->timer_set) {
+                ngx_del_timer(wev);
+            }
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_event_pipe_read_upstream(ngx_event_pipe_t *p)
+{
+    off_t         limit;
+    ssize_t       n, size;
+    ngx_int_t     rc;
+    ngx_buf_t    *b;
+    ngx_msec_t    delay;
+    ngx_chain_t  *chain, *cl, *ln;
+
+    if (p->upstream_eof || p->upstream_error || p->upstream_done) {
+        return NGX_OK;
+    }
+
+#if (NGX_THREADS)
+
+    if (p->aio) {
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                       "pipe read upstream: aio");
+        return NGX_AGAIN;
+    }
+
+    if (p->writing) {
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                       "pipe read upstream: writing");
+
+        rc = ngx_event_pipe_write_chain_to_temp_file(p);
+
+        if (rc != NGX_OK) {
+            return rc;
+        }
+    }
+
+#endif
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                   "pipe read upstream: %d", p->upstream->read->ready);
+
+    for ( ;; ) {
+
+        if (p->upstream_eof || p->upstream_error || p->upstream_done) {
+            break;
+        }
+
+        if (p->preread_bufs == NULL && !p->upstream->read->ready) {
+            break;
+        }
+
+        if (p->preread_bufs) {
+
+            /* use the pre-read bufs if they exist */
+
+            chain = p->preread_bufs;
+            p->preread_bufs = NULL;
+            n = p->preread_size;
+
+            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                           "pipe preread: %z", n);
+
+            if (n) {
+                p->read = 1;
+            }
+
+        } else {
+
+#if (NGX_HAVE_KQUEUE)
+
+            /*
+             * kqueue notifies about the end of file or a pending error.
+             * This test allows not to allocate a buf on these conditions
+             * and not to call c->recv_chain().
+             */
+
+            if (p->upstream->read->available == 0
+                && p->upstream->read->pending_eof)
+            {
+                p->upstream->read->ready = 0;
+                p->upstream->read->eof = 1;
+                p->upstream_eof = 1;
+                p->read = 1;
+
+                if (p->upstream->read->kq_errno) {
+                    p->upstream->read->error = 1;
+                    p->upstream_error = 1;
+                    p->upstream_eof = 0;
+
+                    ngx_log_error(NGX_LOG_ERR, p->log,
+                                  p->upstream->read->kq_errno,
+                                  "kevent() reported that upstream "
+                                  "closed connection");
+                }
+
+                break;
+            }
+#endif
+
+            if (p->limit_rate) {
+                if (p->upstream->read->delayed) {
+                    break;
+                }
+
+                limit = (off_t) p->limit_rate * (ngx_time() - p->start_sec + 1)
+                        - p->read_length;
+
+                if (limit <= 0) {
+                    p->upstream->read->delayed = 1;
+                    delay = (ngx_msec_t) (- limit * 1000 / p->limit_rate + 1);
+                    ngx_add_timer(p->upstream->read, delay);
+                    break;
+                }
+
+            } else {
+                limit = 0;
+            }
+
+            if (p->free_raw_bufs) {
+
+                /* use the free bufs if they exist */
+
+                chain = p->free_raw_bufs;
+                if (p->single_buf) {
+                    p->free_raw_bufs = p->free_raw_bufs->next;
+                    chain->next = NULL;
+                } else {
+                    p->free_raw_bufs = NULL;
+                }
+
+            } else if (p->allocated < p->bufs.num) {
+
+                /* allocate a new buf if it's still allowed */
+
+                b = ngx_create_temp_buf(p->pool, p->bufs.size);
+                if (b == NULL) {
+                    return NGX_ABORT;
+                }
+
+                p->allocated++;
+
+                chain = ngx_alloc_chain_link(p->pool);
+                if (chain == NULL) {
+                    return NGX_ABORT;
+                }
+
+                chain->buf = b;
+                chain->next = NULL;
+
+            } else if (!p->cacheable
+                       && p->downstream->data == p->output_ctx
+                       && p->downstream->write->ready
+                       && !p->downstream->write->delayed)
+            {
+                /*
+                 * if the bufs are not needed to be saved in a cache and
+                 * a downstream is ready then write the bufs to a downstream
+                 */
+
+                p->upstream_blocked = 1;
+
+                ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                               "pipe downstream ready");
+
+                break;
+
+            } else if (p->cacheable
+                       || p->temp_file->offset < p->max_temp_file_size)
+            {
+
+                /*
+                 * if it is allowed, then save some bufs from p->in
+                 * to a temporary file, and add them to a p->out chain
+                 */
+
+                rc = ngx_event_pipe_write_chain_to_temp_file(p);
+
+                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                               "pipe temp offset: %O", p->temp_file->offset);
+
+                if (rc == NGX_BUSY) {
+                    break;
+                }
+
+                if (rc != NGX_OK) {
+                    return rc;
+                }
+
+                chain = p->free_raw_bufs;
+                if (p->single_buf) {
+                    p->free_raw_bufs = p->free_raw_bufs->next;
+                    chain->next = NULL;
+                } else {
+                    p->free_raw_bufs = NULL;
+                }
+
+            } else {
+
+                /* there are no bufs to read in */
+
+                ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                               "no pipe bufs to read in");
+
+                break;
+            }
+
+            n = p->upstream->recv_chain(p->upstream, chain, limit);
+
+            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                           "pipe recv chain: %z", n);
+
+            if (p->free_raw_bufs) {
+                chain->next = p->free_raw_bufs;
+            }
+            p->free_raw_bufs = chain;
+
+            if (n == NGX_ERROR) {
+                p->upstream_error = 1;
+                break;
+            }
+
+            if (n == NGX_AGAIN) {
+                if (p->single_buf) {
+                    ngx_event_pipe_remove_shadow_links(chain->buf);
+                }
+
+                break;
+            }
+
+            p->read = 1;
+
+            if (n == 0) {
+                p->upstream_eof = 1;
+                break;
+            }
+        }
+
+        delay = p->limit_rate ? (ngx_msec_t) n * 1000 / p->limit_rate : 0;
+
+        p->read_length += n;
+        cl = chain;
+        p->free_raw_bufs = NULL;
+
+        while (cl && n > 0) {
+
+            ngx_event_pipe_remove_shadow_links(cl->buf);
+
+            size = cl->buf->end - cl->buf->last;
+
+            if (n >= size) {
+                cl->buf->last = cl->buf->end;
+
+                /* STUB */ cl->buf->num = p->num++;
+
+                if (p->input_filter(p, cl->buf) == NGX_ERROR) {
+                    return NGX_ABORT;
+                }
+
+                n -= size;
+                ln = cl;
+                cl = cl->next;
+                ngx_free_chain(p->pool, ln);
+
+            } else {
+                cl->buf->last += n;
+                n = 0;
+            }
+        }
+
+        if (cl) {
+            for (ln = cl; ln->next; ln = ln->next) { /* void */ }
+
+            ln->next = p->free_raw_bufs;
+            p->free_raw_bufs = cl;
+        }
+
+        if (delay > 0) {
+            p->upstream->read->delayed = 1;
+            ngx_add_timer(p->upstream->read, delay);
+            break;
+        }
+    }
+
+#if (NGX_DEBUG)
+
+    for (cl = p->busy; cl; cl = cl->next) {
+        ngx_log_debug8(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                       "pipe buf busy s:%d t:%d f:%d "
+                       "%p, pos %p, size: %z "
+                       "file: %O, size: %O",
+                       (cl->buf->shadow ? 1 : 0),
+                       cl->buf->temporary, cl->buf->in_file,
+                       cl->buf->start, cl->buf->pos,
+                       cl->buf->last - cl->buf->pos,
+                       cl->buf->file_pos,
+                       cl->buf->file_last - cl->buf->file_pos);
+    }
+
+    for (cl = p->out; cl; cl = cl->next) {
+        ngx_log_debug8(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                       "pipe buf out  s:%d t:%d f:%d "
+                       "%p, pos %p, size: %z "
+                       "file: %O, size: %O",
+                       (cl->buf->shadow ? 1 : 0),
+                       cl->buf->temporary, cl->buf->in_file,
+                       cl->buf->start, cl->buf->pos,
+                       cl->buf->last - cl->buf->pos,
+                       cl->buf->file_pos,
+                       cl->buf->file_last - cl->buf->file_pos);
+    }
+
+    for (cl = p->in; cl; cl = cl->next) {
+        ngx_log_debug8(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                       "pipe buf in   s:%d t:%d f:%d "
+                       "%p, pos %p, size: %z "
+                       "file: %O, size: %O",
+                       (cl->buf->shadow ? 1 : 0),
+                       cl->buf->temporary, cl->buf->in_file,
+                       cl->buf->start, cl->buf->pos,
+                       cl->buf->last - cl->buf->pos,
+                       cl->buf->file_pos,
+                       cl->buf->file_last - cl->buf->file_pos);
+    }
+
+    for (cl = p->free_raw_bufs; cl; cl = cl->next) {
+        ngx_log_debug8(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                       "pipe buf free s:%d t:%d f:%d "
+                       "%p, pos %p, size: %z "
+                       "file: %O, size: %O",
+                       (cl->buf->shadow ? 1 : 0),
+                       cl->buf->temporary, cl->buf->in_file,
+                       cl->buf->start, cl->buf->pos,
+                       cl->buf->last - cl->buf->pos,
+                       cl->buf->file_pos,
+                       cl->buf->file_last - cl->buf->file_pos);
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                   "pipe length: %O", p->length);
+
+#endif
+
+    if (p->free_raw_bufs && p->length != -1) {
+        cl = p->free_raw_bufs;
+
+        if (cl->buf->last - cl->buf->pos >= p->length) {
+
+            p->free_raw_bufs = cl->next;
+
+            /* STUB */ cl->buf->num = p->num++;
+
+            if (p->input_filter(p, cl->buf) == NGX_ERROR) {
+                return NGX_ABORT;
+            }
+
+            ngx_free_chain(p->pool, cl);
+        }
+    }
+
+    if (p->length == 0) {
+        p->upstream_done = 1;
+        p->read = 1;
+    }
+
+    if ((p->upstream_eof || p->upstream_error) && p->free_raw_bufs) {
+
+        /* STUB */ p->free_raw_bufs->buf->num = p->num++;
+
+        if (p->input_filter(p, p->free_raw_bufs->buf) == NGX_ERROR) {
+            return NGX_ABORT;
+        }
+
+        p->free_raw_bufs = p->free_raw_bufs->next;
+
+        if (p->free_bufs && p->buf_to_file == NULL) {
+            for (cl = p->free_raw_bufs; cl; cl = cl->next) {
+                if (cl->buf->shadow == NULL) {
+                    ngx_pfree(p->pool, cl->buf->start);
+                }
+            }
+        }
+    }
+
+    if (p->cacheable && (p->in || p->buf_to_file)) {
+
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                       "pipe write chain");
+
+        rc = ngx_event_pipe_write_chain_to_temp_file(p);
+
+        if (rc != NGX_OK) {
+            return rc;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_event_pipe_write_to_downstream(ngx_event_pipe_t *p)
+{
+    u_char            *prev;
+    size_t             bsize;
+    ngx_int_t          rc;
+    ngx_uint_t         flush, flushed, prev_last_shadow;
+    ngx_chain_t       *out, **ll, *cl;
+    ngx_connection_t  *downstream;
+
+    downstream = p->downstream;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                   "pipe write downstream: %d", downstream->write->ready);
+
+#if (NGX_THREADS)
+
+    if (p->writing) {
+        rc = ngx_event_pipe_write_chain_to_temp_file(p);
+
+        if (rc == NGX_ABORT) {
+            return NGX_ABORT;
+        }
+    }
+
+#endif
+
+    flushed = 0;
+
+    for ( ;; ) {
+        if (p->downstream_error) {
+            return ngx_event_pipe_drain_chains(p);
+        }
+
+        if (p->upstream_eof || p->upstream_error || p->upstream_done) {
+
+            /* pass the p->out and p->in chains to the output filter */
+
+            for (cl = p->busy; cl; cl = cl->next) {
+                cl->buf->recycled = 0;
+            }
+
+            if (p->out) {
+                ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                               "pipe write downstream flush out");
+
+                for (cl = p->out; cl; cl = cl->next) {
+                    cl->buf->recycled = 0;
+                }
+
+                rc = p->output_filter(p->output_ctx, p->out);
+
+                if (rc == NGX_ERROR) {
+                    p->downstream_error = 1;
+                    return ngx_event_pipe_drain_chains(p);
+                }
+
+                p->out = NULL;
+            }
+
+            if (p->writing) {
+                break;
+            }
+
+            if (p->in) {
+                ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                               "pipe write downstream flush in");
+
+                for (cl = p->in; cl; cl = cl->next) {
+                    cl->buf->recycled = 0;
+                }
+
+                rc = p->output_filter(p->output_ctx, p->in);
+
+                if (rc == NGX_ERROR) {
+                    p->downstream_error = 1;
+                    return ngx_event_pipe_drain_chains(p);
+                }
+
+                p->in = NULL;
+            }
+
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                           "pipe write downstream done");
+
+            /* TODO: free unused bufs */
+
+            p->downstream_done = 1;
+            break;
+        }
+
+        if (downstream->data != p->output_ctx
+            || !downstream->write->ready
+            || downstream->write->delayed)
+        {
+            break;
+        }
+
+        /* bsize is the size of the busy recycled bufs */
+
+        prev = NULL;
+        bsize = 0;
+
+        for (cl = p->busy; cl; cl = cl->next) {
+
+            if (cl->buf->recycled) {
+                if (prev == cl->buf->start) {
+                    continue;
+                }
+
+                bsize += cl->buf->end - cl->buf->start;
+                prev = cl->buf->start;
+            }
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                       "pipe write busy: %uz", bsize);
+
+        out = NULL;
+
+        if (bsize >= (size_t) p->busy_size) {
+            flush = 1;
+            goto flush;
+        }
+
+        flush = 0;
+        ll = NULL;
+        prev_last_shadow = 1;
+
+        for ( ;; ) {
+            if (p->out) {
+                cl = p->out;
+
+                if (cl->buf->recycled) {
+                    ngx_log_error(NGX_LOG_ALERT, p->log, 0,
+                                  "recycled buffer in pipe out chain");
+                }
+
+                p->out = p->out->next;
+
+            } else if (!p->cacheable && !p->writing && p->in) {
+                cl = p->in;
+
+                ngx_log_debug3(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                               "pipe write buf ls:%d %p %z",
+                               cl->buf->last_shadow,
+                               cl->buf->pos,
+                               cl->buf->last - cl->buf->pos);
+
+                if (cl->buf->recycled && prev_last_shadow) {
+                    if (bsize + cl->buf->end - cl->buf->start > p->busy_size) {
+                        flush = 1;
+                        break;
+                    }
+
+                    bsize += cl->buf->end - cl->buf->start;
+                }
+
+                prev_last_shadow = cl->buf->last_shadow;
+
+                p->in = p->in->next;
+
+            } else {
+                break;
+            }
+
+            cl->next = NULL;
+
+            if (out) {
+                *ll = cl;
+            } else {
+                out = cl;
+            }
+            ll = &cl->next;
+        }
+
+    flush:
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                       "pipe write: out:%p, f:%ui", out, flush);
+
+        if (out == NULL) {
+
+            if (!flush) {
+                break;
+            }
+
+            /* a workaround for AIO */
+            if (flushed++ > 10) {
+                return NGX_BUSY;
+            }
+        }
+
+        rc = p->output_filter(p->output_ctx, out);
+
+        ngx_chain_update_chains(p->pool, &p->free, &p->busy, &out, p->tag);
+
+        if (rc == NGX_ERROR) {
+            p->downstream_error = 1;
+            return ngx_event_pipe_drain_chains(p);
+        }
+
+        for (cl = p->free; cl; cl = cl->next) {
+
+            if (cl->buf->temp_file) {
+                if (p->cacheable || !p->cyclic_temp_file) {
+                    continue;
+                }
+
+                /* reset p->temp_offset if all bufs had been sent */
+
+                if (cl->buf->file_last == p->temp_file->offset) {
+                    p->temp_file->offset = 0;
+                }
+            }
+
+            /* TODO: free buf if p->free_bufs && upstream done */
+
+            /* add the free shadow raw buf to p->free_raw_bufs */
+
+            if (cl->buf->last_shadow) {
+                if (ngx_event_pipe_add_free_buf(p, cl->buf->shadow) != NGX_OK) {
+                    return NGX_ABORT;
+                }
+
+                cl->buf->last_shadow = 0;
+            }
+
+            cl->buf->shadow = NULL;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_event_pipe_write_chain_to_temp_file(ngx_event_pipe_t *p)
+{
+    ssize_t       size, bsize, n;
+    ngx_buf_t    *b;
+    ngx_uint_t    prev_last_shadow;
+    ngx_chain_t  *cl, *tl, *next, *out, **ll, **last_out, **last_free;
+
+#if (NGX_THREADS)
+
+    if (p->writing) {
+
+        if (p->aio) {
+            return NGX_AGAIN;
+        }
+
+        out = p->writing;
+        p->writing = NULL;
+
+        n = ngx_write_chain_to_temp_file(p->temp_file, NULL);
+
+        if (n == NGX_ERROR) {
+            return NGX_ABORT;
+        }
+
+        goto done;
+    }
+
+#endif
+
+    if (p->buf_to_file) {
+        out = ngx_alloc_chain_link(p->pool);
+        if (out == NULL) {
+            return NGX_ABORT;
+        }
+
+        out->buf = p->buf_to_file;
+        out->next = p->in;
+
+    } else {
+        out = p->in;
+    }
+
+    if (!p->cacheable) {
+
+        size = 0;
+        cl = out;
+        ll = NULL;
+        prev_last_shadow = 1;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                       "pipe offset: %O", p->temp_file->offset);
+
+        do {
+            bsize = cl->buf->last - cl->buf->pos;
+
+            ngx_log_debug4(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                           "pipe buf ls:%d %p, pos %p, size: %z",
+                           cl->buf->last_shadow, cl->buf->start,
+                           cl->buf->pos, bsize);
+
+            if (prev_last_shadow
+                && ((size + bsize > p->temp_file_write_size)
+                    || (p->temp_file->offset + size + bsize
+                        > p->max_temp_file_size)))
+            {
+                break;
+            }
+
+            prev_last_shadow = cl->buf->last_shadow;
+
+            size += bsize;
+            ll = &cl->next;
+            cl = cl->next;
+
+        } while (cl);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0, "size: %z", size);
+
+        if (ll == NULL) {
+            return NGX_BUSY;
+        }
+
+        if (cl) {
+            p->in = cl;
+            *ll = NULL;
+
+        } else {
+            p->in = NULL;
+            p->last_in = &p->in;
+        }
+
+    } else {
+        p->in = NULL;
+        p->last_in = &p->in;
+    }
+
+#if (NGX_THREADS)
+    if (p->thread_handler) {
+        p->temp_file->thread_write = 1;
+        p->temp_file->file.thread_task = p->thread_task;
+        p->temp_file->file.thread_handler = p->thread_handler;
+        p->temp_file->file.thread_ctx = p->thread_ctx;
+    }
+#endif
+
+    n = ngx_write_chain_to_temp_file(p->temp_file, out);
+
+    if (n == NGX_ERROR) {
+        return NGX_ABORT;
+    }
+
+#if (NGX_THREADS)
+
+    if (n == NGX_AGAIN) {
+        p->writing = out;
+        p->thread_task = p->temp_file->file.thread_task;
+        return NGX_AGAIN;
+    }
+
+done:
+
+#endif
+
+    if (p->buf_to_file) {
+        p->temp_file->offset = p->buf_to_file->last - p->buf_to_file->pos;
+        n -= p->buf_to_file->last - p->buf_to_file->pos;
+        p->buf_to_file = NULL;
+        out = out->next;
+    }
+
+    if (n > 0) {
+        /* update previous buffer or add new buffer */
+
+        if (p->out) {
+            for (cl = p->out; cl->next; cl = cl->next) { /* void */ }
+
+            b = cl->buf;
+
+            if (b->file_last == p->temp_file->offset) {
+                p->temp_file->offset += n;
+                b->file_last = p->temp_file->offset;
+                goto free;
+            }
+
+            last_out = &cl->next;
+
+        } else {
+            last_out = &p->out;
+        }
+
+        cl = ngx_chain_get_free_buf(p->pool, &p->free);
+        if (cl == NULL) {
+            return NGX_ABORT;
+        }
+
+        b = cl->buf;
+
+        ngx_memzero(b, sizeof(ngx_buf_t));
+
+        b->tag = p->tag;
+
+        b->file = &p->temp_file->file;
+        b->file_pos = p->temp_file->offset;
+        p->temp_file->offset += n;
+        b->file_last = p->temp_file->offset;
+
+        b->in_file = 1;
+        b->temp_file = 1;
+
+        *last_out = cl;
+    }
+
+free:
+
+    for (last_free = &p->free_raw_bufs;
+         *last_free != NULL;
+         last_free = &(*last_free)->next)
+    {
+        /* void */
+    }
+
+    for (cl = out; cl; cl = next) {
+        next = cl->next;
+
+        cl->next = p->free;
+        p->free = cl;
+
+        b = cl->buf;
+
+        if (b->last_shadow) {
+
+            tl = ngx_alloc_chain_link(p->pool);
+            if (tl == NULL) {
+                return NGX_ABORT;
+            }
+
+            tl->buf = b->shadow;
+            tl->next = NULL;
+
+            *last_free = tl;
+            last_free = &tl->next;
+
+            b->shadow->pos = b->shadow->start;
+            b->shadow->last = b->shadow->start;
+
+            ngx_event_pipe_remove_shadow_links(b->shadow);
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+/* the copy input filter */
+
+ngx_int_t
+ngx_event_pipe_copy_input_filter(ngx_event_pipe_t *p, ngx_buf_t *buf)
+{
+    ngx_buf_t    *b;
+    ngx_chain_t  *cl;
+
+    if (buf->pos == buf->last) {
+        return NGX_OK;
+    }
+
+    cl = ngx_chain_get_free_buf(p->pool, &p->free);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    b = cl->buf;
+
+    ngx_memcpy(b, buf, sizeof(ngx_buf_t));
+    b->shadow = buf;
+    b->tag = p->tag;
+    b->last_shadow = 1;
+    b->recycled = 1;
+    buf->shadow = b;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0, "input buf #%d", b->num);
+
+    if (p->in) {
+        *p->last_in = cl;
+    } else {
+        p->in = cl;
+    }
+    p->last_in = &cl->next;
+
+    if (p->length == -1) {
+        return NGX_OK;
+    }
+
+    p->length -= b->last - b->pos;
+
+    return NGX_OK;
+}
+
+
+static ngx_inline void
+ngx_event_pipe_remove_shadow_links(ngx_buf_t *buf)
+{
+    ngx_buf_t  *b, *next;
+
+    b = buf->shadow;
+
+    if (b == NULL) {
+        return;
+    }
+
+    while (!b->last_shadow) {
+        next = b->shadow;
+
+        b->temporary = 0;
+        b->recycled = 0;
+
+        b->shadow = NULL;
+        b = next;
+    }
+
+    b->temporary = 0;
+    b->recycled = 0;
+    b->last_shadow = 0;
+
+    b->shadow = NULL;
+
+    buf->shadow = NULL;
+}
+
+
+ngx_int_t
+ngx_event_pipe_add_free_buf(ngx_event_pipe_t *p, ngx_buf_t *b)
+{
+    ngx_chain_t  *cl;
+
+    cl = ngx_alloc_chain_link(p->pool);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (p->buf_to_file && b->start == p->buf_to_file->start) {
+        b->pos = p->buf_to_file->last;
+        b->last = p->buf_to_file->last;
+
+    } else {
+        b->pos = b->start;
+        b->last = b->start;
+    }
+
+    b->shadow = NULL;
+
+    cl->buf = b;
+
+    if (p->free_raw_bufs == NULL) {
+        p->free_raw_bufs = cl;
+        cl->next = NULL;
+
+        return NGX_OK;
+    }
+
+    if (p->free_raw_bufs->buf->pos == p->free_raw_bufs->buf->last) {
+
+        /* add the free buf to the list start */
+
+        cl->next = p->free_raw_bufs;
+        p->free_raw_bufs = cl;
+
+        return NGX_OK;
+    }
+
+    /* the first free buf is partially filled, thus add the free buf after it */
+
+    cl->next = p->free_raw_bufs->next;
+    p->free_raw_bufs->next = cl;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_event_pipe_drain_chains(ngx_event_pipe_t *p)
+{
+    ngx_chain_t  *cl, *tl;
+
+    for ( ;; ) {
+        if (p->busy) {
+            cl = p->busy;
+            p->busy = NULL;
+
+        } else if (p->out) {
+            cl = p->out;
+            p->out = NULL;
+
+        } else if (p->in) {
+            cl = p->in;
+            p->in = NULL;
+
+        } else {
+            return NGX_OK;
+        }
+
+        while (cl) {
+            if (cl->buf->last_shadow) {
+                if (ngx_event_pipe_add_free_buf(p, cl->buf->shadow) != NGX_OK) {
+                    return NGX_ABORT;
+                }
+
+                cl->buf->last_shadow = 0;
+            }
+
+            cl->buf->shadow = NULL;
+            tl = cl->next;
+            cl->next = p->free;
+            p->free = cl;
+            cl = tl;
+        }
+    }
+}
diff --git a/nginx/src/event/ngx_event_pipe.h b/nginx/src/event/ngx_event_pipe.h
new file mode 100644 (file)
index 0000000..10a3340
--- /dev/null
@@ -0,0 +1,107 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_EVENT_PIPE_H_INCLUDED_
+#define _NGX_EVENT_PIPE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+typedef struct ngx_event_pipe_s  ngx_event_pipe_t;
+
+typedef ngx_int_t (*ngx_event_pipe_input_filter_pt)(ngx_event_pipe_t *p,
+                                                    ngx_buf_t *buf);
+typedef ngx_int_t (*ngx_event_pipe_output_filter_pt)(void *data,
+                                                     ngx_chain_t *chain);
+
+
+struct ngx_event_pipe_s {
+    ngx_connection_t  *upstream;
+    ngx_connection_t  *downstream;
+
+    ngx_chain_t       *free_raw_bufs;
+    ngx_chain_t       *in;
+    ngx_chain_t      **last_in;
+
+    ngx_chain_t       *writing;
+
+    ngx_chain_t       *out;
+    ngx_chain_t       *free;
+    ngx_chain_t       *busy;
+
+    /*
+     * the input filter i.e. that moves HTTP/1.1 chunks
+     * from the raw bufs to an incoming chain
+     */
+
+    ngx_event_pipe_input_filter_pt    input_filter;
+    void                             *input_ctx;
+
+    ngx_event_pipe_output_filter_pt   output_filter;
+    void                             *output_ctx;
+
+#if (NGX_THREADS || NGX_COMPAT)
+    ngx_int_t                       (*thread_handler)(ngx_thread_task_t *task,
+                                                      ngx_file_t *file);
+    void                             *thread_ctx;
+    ngx_thread_task_t                *thread_task;
+#endif
+
+    unsigned           read:1;
+    unsigned           cacheable:1;
+    unsigned           single_buf:1;
+    unsigned           free_bufs:1;
+    unsigned           upstream_done:1;
+    unsigned           upstream_error:1;
+    unsigned           upstream_eof:1;
+    unsigned           upstream_blocked:1;
+    unsigned           downstream_done:1;
+    unsigned           downstream_error:1;
+    unsigned           cyclic_temp_file:1;
+    unsigned           aio:1;
+
+    ngx_int_t          allocated;
+    ngx_bufs_t         bufs;
+    ngx_buf_tag_t      tag;
+
+    ssize_t            busy_size;
+
+    off_t              read_length;
+    off_t              length;
+
+    off_t              max_temp_file_size;
+    ssize_t            temp_file_write_size;
+
+    ngx_msec_t         read_timeout;
+    ngx_msec_t         send_timeout;
+    ssize_t            send_lowat;
+
+    ngx_pool_t        *pool;
+    ngx_log_t         *log;
+
+    ngx_chain_t       *preread_bufs;
+    size_t             preread_size;
+    ngx_buf_t         *buf_to_file;
+
+    size_t             limit_rate;
+    time_t             start_sec;
+
+    ngx_temp_file_t   *temp_file;
+
+    /* STUB */ int     num;
+};
+
+
+ngx_int_t ngx_event_pipe(ngx_event_pipe_t *p, ngx_int_t do_write);
+ngx_int_t ngx_event_pipe_copy_input_filter(ngx_event_pipe_t *p, ngx_buf_t *buf);
+ngx_int_t ngx_event_pipe_add_free_buf(ngx_event_pipe_t *p, ngx_buf_t *b);
+
+
+#endif /* _NGX_EVENT_PIPE_H_INCLUDED_ */
diff --git a/nginx/src/event/ngx_event_posted.c b/nginx/src/event/ngx_event_posted.c
new file mode 100644 (file)
index 0000000..d851f3d
--- /dev/null
@@ -0,0 +1,35 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+ngx_queue_t  ngx_posted_accept_events;
+ngx_queue_t  ngx_posted_events;
+
+
+void
+ngx_event_process_posted(ngx_cycle_t *cycle, ngx_queue_t *posted)
+{
+    ngx_queue_t  *q;
+    ngx_event_t  *ev;
+
+    while (!ngx_queue_empty(posted)) {
+
+        q = ngx_queue_head(posted);
+        ev = ngx_queue_data(q, ngx_event_t, queue);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                      "posted event %p", ev);
+
+        ngx_delete_posted_event(ev);
+
+        ev->handler(ev);
+    }
+}
diff --git a/nginx/src/event/ngx_event_posted.h b/nginx/src/event/ngx_event_posted.h
new file mode 100644 (file)
index 0000000..145d30f
--- /dev/null
@@ -0,0 +1,48 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_EVENT_POSTED_H_INCLUDED_
+#define _NGX_EVENT_POSTED_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#define ngx_post_event(ev, q)                                                 \
+                                                                              \
+    if (!(ev)->posted) {                                                      \
+        (ev)->posted = 1;                                                     \
+        ngx_queue_insert_tail(q, &(ev)->queue);                               \
+                                                                              \
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, (ev)->log, 0, "post event %p", ev);\
+                                                                              \
+    } else  {                                                                 \
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, (ev)->log, 0,                      \
+                       "update posted event %p", ev);                         \
+    }
+
+
+#define ngx_delete_posted_event(ev)                                           \
+                                                                              \
+    (ev)->posted = 0;                                                         \
+    ngx_queue_remove(&(ev)->queue);                                           \
+                                                                              \
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, (ev)->log, 0,                          \
+                   "delete posted event %p", ev);
+
+
+
+void ngx_event_process_posted(ngx_cycle_t *cycle, ngx_queue_t *posted);
+
+
+extern ngx_queue_t  ngx_posted_accept_events;
+extern ngx_queue_t  ngx_posted_events;
+
+
+#endif /* _NGX_EVENT_POSTED_H_INCLUDED_ */
diff --git a/nginx/src/event/ngx_event_timer.c b/nginx/src/event/ngx_event_timer.c
new file mode 100644 (file)
index 0000000..698b88f
--- /dev/null
@@ -0,0 +1,126 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+ngx_rbtree_t              ngx_event_timer_rbtree;
+static ngx_rbtree_node_t  ngx_event_timer_sentinel;
+
+/*
+ * the event timer rbtree may contain the duplicate keys, however,
+ * it should not be a problem, because we use the rbtree to find
+ * a minimum timer value only
+ */
+
+ngx_int_t
+ngx_event_timer_init(ngx_log_t *log)
+{
+    ngx_rbtree_init(&ngx_event_timer_rbtree, &ngx_event_timer_sentinel,
+                    ngx_rbtree_insert_timer_value);
+
+    return NGX_OK;
+}
+
+
+ngx_msec_t
+ngx_event_find_timer(void)
+{
+    ngx_msec_int_t      timer;
+    ngx_rbtree_node_t  *node, *root, *sentinel;
+
+    if (ngx_event_timer_rbtree.root == &ngx_event_timer_sentinel) {
+        return NGX_TIMER_INFINITE;
+    }
+
+    root = ngx_event_timer_rbtree.root;
+    sentinel = ngx_event_timer_rbtree.sentinel;
+
+    node = ngx_rbtree_min(root, sentinel);
+
+    timer = (ngx_msec_int_t) (node->key - ngx_current_msec);
+
+    return (ngx_msec_t) (timer > 0 ? timer : 0);
+}
+
+
+void
+ngx_event_expire_timers(void)
+{
+    ngx_event_t        *ev;
+    ngx_rbtree_node_t  *node, *root, *sentinel;
+
+    sentinel = ngx_event_timer_rbtree.sentinel;
+
+    for ( ;; ) {
+        root = ngx_event_timer_rbtree.root;
+
+        if (root == sentinel) {
+            return;
+        }
+
+        node = ngx_rbtree_min(root, sentinel);
+
+        /* node->key > ngx_current_msec */
+
+        if ((ngx_msec_int_t) (node->key - ngx_current_msec) > 0) {
+            return;
+        }
+
+        ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer));
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                       "event timer del: %d: %M",
+                       ngx_event_ident(ev->data), ev->timer.key);
+
+        ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);
+
+#if (NGX_DEBUG)
+        ev->timer.left = NULL;
+        ev->timer.right = NULL;
+        ev->timer.parent = NULL;
+#endif
+
+        ev->timer_set = 0;
+
+        ev->timedout = 1;
+
+        ev->handler(ev);
+    }
+}
+
+
+ngx_int_t
+ngx_event_no_timers_left(void)
+{
+    ngx_event_t        *ev;
+    ngx_rbtree_node_t  *node, *root, *sentinel;
+
+    sentinel = ngx_event_timer_rbtree.sentinel;
+    root = ngx_event_timer_rbtree.root;
+
+    if (root == sentinel) {
+        return NGX_OK;
+    }
+
+    for (node = ngx_rbtree_min(root, sentinel);
+         node;
+         node = ngx_rbtree_next(&ngx_event_timer_rbtree, node))
+    {
+        ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer));
+
+        if (!ev->cancelable) {
+            return NGX_AGAIN;
+        }
+    }
+
+    /* only cancelable timers left */
+
+    return NGX_OK;
+}
diff --git a/nginx/src/event/ngx_event_timer.h b/nginx/src/event/ngx_event_timer.h
new file mode 100644 (file)
index 0000000..be81b15
--- /dev/null
@@ -0,0 +1,90 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_EVENT_TIMER_H_INCLUDED_
+#define _NGX_EVENT_TIMER_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#define NGX_TIMER_INFINITE  (ngx_msec_t) -1
+
+#define NGX_TIMER_LAZY_DELAY  300
+
+
+ngx_int_t ngx_event_timer_init(ngx_log_t *log);
+ngx_msec_t ngx_event_find_timer(void);
+void ngx_event_expire_timers(void);
+ngx_int_t ngx_event_no_timers_left(void);
+
+
+extern ngx_rbtree_t  ngx_event_timer_rbtree;
+
+
+static ngx_inline void
+ngx_event_del_timer(ngx_event_t *ev)
+{
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "event timer del: %d: %M",
+                    ngx_event_ident(ev->data), ev->timer.key);
+
+    ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);
+
+#if (NGX_DEBUG)
+    ev->timer.left = NULL;
+    ev->timer.right = NULL;
+    ev->timer.parent = NULL;
+#endif
+
+    ev->timer_set = 0;
+}
+
+
+static ngx_inline void
+ngx_event_add_timer(ngx_event_t *ev, ngx_msec_t timer)
+{
+    ngx_msec_t      key;
+    ngx_msec_int_t  diff;
+
+    key = ngx_current_msec + timer;
+
+    if (ev->timer_set) {
+
+        /*
+         * Use a previous timer value if difference between it and a new
+         * value is less than NGX_TIMER_LAZY_DELAY milliseconds: this allows
+         * to minimize the rbtree operations for fast connections.
+         */
+
+        diff = (ngx_msec_int_t) (key - ev->timer.key);
+
+        if (ngx_abs(diff) < NGX_TIMER_LAZY_DELAY) {
+            ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                           "event timer: %d, old: %M, new: %M",
+                            ngx_event_ident(ev->data), ev->timer.key, key);
+            return;
+        }
+
+        ngx_del_timer(ev);
+    }
+
+    ev->timer.key = key;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "event timer add: %d: %M:%M",
+                    ngx_event_ident(ev->data), timer, ev->timer.key);
+
+    ngx_rbtree_insert(&ngx_event_timer_rbtree, &ev->timer);
+
+    ev->timer_set = 1;
+}
+
+
+#endif /* _NGX_EVENT_TIMER_H_INCLUDED_ */
diff --git a/nginx/src/http/modules/ngx_http_access_module.c b/nginx/src/http/modules/ngx_http_access_module.c
new file mode 100644 (file)
index 0000000..7355de9
--- /dev/null
@@ -0,0 +1,463 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    in_addr_t         mask;
+    in_addr_t         addr;
+    ngx_uint_t        deny;      /* unsigned  deny:1; */
+} ngx_http_access_rule_t;
+
+#if (NGX_HAVE_INET6)
+
+typedef struct {
+    struct in6_addr   addr;
+    struct in6_addr   mask;
+    ngx_uint_t        deny;      /* unsigned  deny:1; */
+} ngx_http_access_rule6_t;
+
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+
+typedef struct {
+    ngx_uint_t        deny;      /* unsigned  deny:1; */
+} ngx_http_access_rule_un_t;
+
+#endif
+
+typedef struct {
+    ngx_array_t      *rules;     /* array of ngx_http_access_rule_t */
+#if (NGX_HAVE_INET6)
+    ngx_array_t      *rules6;    /* array of ngx_http_access_rule6_t */
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+    ngx_array_t      *rules_un;  /* array of ngx_http_access_rule_un_t */
+#endif
+} ngx_http_access_loc_conf_t;
+
+
+static ngx_int_t ngx_http_access_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_access_inet(ngx_http_request_t *r,
+    ngx_http_access_loc_conf_t *alcf, in_addr_t addr);
+#if (NGX_HAVE_INET6)
+static ngx_int_t ngx_http_access_inet6(ngx_http_request_t *r,
+    ngx_http_access_loc_conf_t *alcf, u_char *p);
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+static ngx_int_t ngx_http_access_unix(ngx_http_request_t *r,
+    ngx_http_access_loc_conf_t *alcf);
+#endif
+static ngx_int_t ngx_http_access_found(ngx_http_request_t *r, ngx_uint_t deny);
+static char *ngx_http_access_rule(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static void *ngx_http_access_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_access_merge_loc_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+static ngx_int_t ngx_http_access_init(ngx_conf_t *cf);
+
+
+static ngx_command_t  ngx_http_access_commands[] = {
+
+    { ngx_string("allow"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF
+                        |NGX_CONF_TAKE1,
+      ngx_http_access_rule,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("deny"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF
+                        |NGX_CONF_TAKE1,
+      ngx_http_access_rule,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+
+static ngx_http_module_t  ngx_http_access_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_access_init,                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_access_create_loc_conf,       /* create location configuration */
+    ngx_http_access_merge_loc_conf         /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_access_module = {
+    NGX_MODULE_V1,
+    &ngx_http_access_module_ctx,           /* module context */
+    ngx_http_access_commands,              /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_access_handler(ngx_http_request_t *r)
+{
+    struct sockaddr_in          *sin;
+    ngx_http_access_loc_conf_t  *alcf;
+#if (NGX_HAVE_INET6)
+    u_char                      *p;
+    in_addr_t                    addr;
+    struct sockaddr_in6         *sin6;
+#endif
+
+    alcf = ngx_http_get_module_loc_conf(r, ngx_http_access_module);
+
+    switch (r->connection->sockaddr->sa_family) {
+
+    case AF_INET:
+        if (alcf->rules) {
+            sin = (struct sockaddr_in *) r->connection->sockaddr;
+            return ngx_http_access_inet(r, alcf, sin->sin_addr.s_addr);
+        }
+        break;
+
+#if (NGX_HAVE_INET6)
+
+    case AF_INET6:
+        sin6 = (struct sockaddr_in6 *) r->connection->sockaddr;
+        p = sin6->sin6_addr.s6_addr;
+
+        if (alcf->rules && IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+            addr = p[12] << 24;
+            addr += p[13] << 16;
+            addr += p[14] << 8;
+            addr += p[15];
+            return ngx_http_access_inet(r, alcf, htonl(addr));
+        }
+
+        if (alcf->rules6) {
+            return ngx_http_access_inet6(r, alcf, p);
+        }
+
+        break;
+
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+
+    case AF_UNIX:
+        if (alcf->rules_un) {
+            return ngx_http_access_unix(r, alcf);
+        }
+
+        break;
+
+#endif
+    }
+
+    return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_http_access_inet(ngx_http_request_t *r, ngx_http_access_loc_conf_t *alcf,
+    in_addr_t addr)
+{
+    ngx_uint_t               i;
+    ngx_http_access_rule_t  *rule;
+
+    rule = alcf->rules->elts;
+    for (i = 0; i < alcf->rules->nelts; i++) {
+
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "access: %08XD %08XD %08XD",
+                       addr, rule[i].mask, rule[i].addr);
+
+        if ((addr & rule[i].mask) == rule[i].addr) {
+            return ngx_http_access_found(r, rule[i].deny);
+        }
+    }
+
+    return NGX_DECLINED;
+}
+
+
+#if (NGX_HAVE_INET6)
+
+static ngx_int_t
+ngx_http_access_inet6(ngx_http_request_t *r, ngx_http_access_loc_conf_t *alcf,
+    u_char *p)
+{
+    ngx_uint_t                n;
+    ngx_uint_t                i;
+    ngx_http_access_rule6_t  *rule6;
+
+    rule6 = alcf->rules6->elts;
+    for (i = 0; i < alcf->rules6->nelts; i++) {
+
+#if (NGX_DEBUG)
+        {
+        size_t  cl, ml, al;
+        u_char  ct[NGX_INET6_ADDRSTRLEN];
+        u_char  mt[NGX_INET6_ADDRSTRLEN];
+        u_char  at[NGX_INET6_ADDRSTRLEN];
+
+        cl = ngx_inet6_ntop(p, ct, NGX_INET6_ADDRSTRLEN);
+        ml = ngx_inet6_ntop(rule6[i].mask.s6_addr, mt, NGX_INET6_ADDRSTRLEN);
+        al = ngx_inet6_ntop(rule6[i].addr.s6_addr, at, NGX_INET6_ADDRSTRLEN);
+
+        ngx_log_debug6(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "access: %*s %*s %*s", cl, ct, ml, mt, al, at);
+        }
+#endif
+
+        for (n = 0; n < 16; n++) {
+            if ((p[n] & rule6[i].mask.s6_addr[n]) != rule6[i].addr.s6_addr[n]) {
+                goto next;
+            }
+        }
+
+        return ngx_http_access_found(r, rule6[i].deny);
+
+    next:
+        continue;
+    }
+
+    return NGX_DECLINED;
+}
+
+#endif
+
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+
+static ngx_int_t
+ngx_http_access_unix(ngx_http_request_t *r, ngx_http_access_loc_conf_t *alcf)
+{
+    ngx_uint_t                  i;
+    ngx_http_access_rule_un_t  *rule_un;
+
+    rule_un = alcf->rules_un->elts;
+    for (i = 0; i < alcf->rules_un->nelts; i++) {
+
+        /* TODO: check path */
+        if (1) {
+            return ngx_http_access_found(r, rule_un[i].deny);
+        }
+    }
+
+    return NGX_DECLINED;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_access_found(ngx_http_request_t *r, ngx_uint_t deny)
+{
+    ngx_http_core_loc_conf_t  *clcf;
+
+    if (deny) {
+        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+        if (clcf->satisfy == NGX_HTTP_SATISFY_ALL) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "access forbidden by rule");
+        }
+
+        return NGX_HTTP_FORBIDDEN;
+    }
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_http_access_rule(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_access_loc_conf_t *alcf = conf;
+
+    ngx_int_t                   rc;
+    ngx_uint_t                  all;
+    ngx_str_t                  *value;
+    ngx_cidr_t                  cidr;
+    ngx_http_access_rule_t     *rule;
+#if (NGX_HAVE_INET6)
+    ngx_http_access_rule6_t    *rule6;
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+    ngx_http_access_rule_un_t  *rule_un;
+#endif
+
+    all = 0;
+    ngx_memzero(&cidr, sizeof(ngx_cidr_t));
+
+    value = cf->args->elts;
+
+    if (value[1].len == 3 && ngx_strcmp(value[1].data, "all") == 0) {
+        all = 1;
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+    } else if (value[1].len == 5 && ngx_strcmp(value[1].data, "unix:") == 0) {
+        cidr.family = AF_UNIX;
+#endif
+
+    } else {
+        rc = ngx_ptocidr(&value[1], &cidr);
+
+        if (rc == NGX_ERROR) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                         "invalid parameter \"%V\"", &value[1]);
+            return NGX_CONF_ERROR;
+        }
+
+        if (rc == NGX_DONE) {
+            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                         "low address bits of %V are meaningless", &value[1]);
+        }
+    }
+
+    if (cidr.family == AF_INET || all) {
+
+        if (alcf->rules == NULL) {
+            alcf->rules = ngx_array_create(cf->pool, 4,
+                                           sizeof(ngx_http_access_rule_t));
+            if (alcf->rules == NULL) {
+                return NGX_CONF_ERROR;
+            }
+        }
+
+        rule = ngx_array_push(alcf->rules);
+        if (rule == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        rule->mask = cidr.u.in.mask;
+        rule->addr = cidr.u.in.addr;
+        rule->deny = (value[0].data[0] == 'd') ? 1 : 0;
+    }
+
+#if (NGX_HAVE_INET6)
+    if (cidr.family == AF_INET6 || all) {
+
+        if (alcf->rules6 == NULL) {
+            alcf->rules6 = ngx_array_create(cf->pool, 4,
+                                            sizeof(ngx_http_access_rule6_t));
+            if (alcf->rules6 == NULL) {
+                return NGX_CONF_ERROR;
+            }
+        }
+
+        rule6 = ngx_array_push(alcf->rules6);
+        if (rule6 == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        rule6->mask = cidr.u.in6.mask;
+        rule6->addr = cidr.u.in6.addr;
+        rule6->deny = (value[0].data[0] == 'd') ? 1 : 0;
+    }
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+    if (cidr.family == AF_UNIX || all) {
+
+        if (alcf->rules_un == NULL) {
+            alcf->rules_un = ngx_array_create(cf->pool, 1,
+                                            sizeof(ngx_http_access_rule_un_t));
+            if (alcf->rules_un == NULL) {
+                return NGX_CONF_ERROR;
+            }
+        }
+
+        rule_un = ngx_array_push(alcf->rules_un);
+        if (rule_un == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        rule_un->deny = (value[0].data[0] == 'd') ? 1 : 0;
+    }
+#endif
+
+    return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_http_access_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_access_loc_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_access_loc_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    return conf;
+}
+
+
+static char *
+ngx_http_access_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_access_loc_conf_t  *prev = parent;
+    ngx_http_access_loc_conf_t  *conf = child;
+
+    if (conf->rules == NULL
+#if (NGX_HAVE_INET6)
+        && conf->rules6 == NULL
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+        && conf->rules_un == NULL
+#endif
+    ) {
+        conf->rules = prev->rules;
+#if (NGX_HAVE_INET6)
+        conf->rules6 = prev->rules6;
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+        conf->rules_un = prev->rules_un;
+#endif
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_access_init(ngx_conf_t *cf)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_access_handler;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_addition_filter_module.c b/nginx/src/http/modules/ngx_http_addition_filter_module.c
new file mode 100644 (file)
index 0000000..e546f0d
--- /dev/null
@@ -0,0 +1,254 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_str_t     before_body;
+    ngx_str_t     after_body;
+
+    ngx_hash_t    types;
+    ngx_array_t  *types_keys;
+} ngx_http_addition_conf_t;
+
+
+typedef struct {
+    ngx_uint_t    before_body_sent;
+} ngx_http_addition_ctx_t;
+
+
+static void *ngx_http_addition_create_conf(ngx_conf_t *cf);
+static char *ngx_http_addition_merge_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static ngx_int_t ngx_http_addition_filter_init(ngx_conf_t *cf);
+
+
+static ngx_command_t  ngx_http_addition_commands[] = {
+
+    { ngx_string("add_before_body"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_addition_conf_t, before_body),
+      NULL },
+
+    { ngx_string("add_after_body"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_addition_conf_t, after_body),
+      NULL },
+
+    { ngx_string("addition_types"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_types_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_addition_conf_t, types_keys),
+      &ngx_http_html_default_types[0] },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_addition_filter_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_addition_filter_init,         /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_addition_create_conf,         /* create location configuration */
+    ngx_http_addition_merge_conf           /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_addition_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_addition_filter_module_ctx,  /* module context */
+    ngx_http_addition_commands,            /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_addition_header_filter(ngx_http_request_t *r)
+{
+    ngx_http_addition_ctx_t   *ctx;
+    ngx_http_addition_conf_t  *conf;
+
+    if (r->headers_out.status != NGX_HTTP_OK || r != r->main) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_addition_filter_module);
+
+    if (conf->before_body.len == 0 && conf->after_body.len == 0) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    if (ngx_http_test_content_type(r, &conf->types) == NULL) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_addition_ctx_t));
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_http_set_ctx(r, ctx, ngx_http_addition_filter_module);
+
+    ngx_http_clear_content_length(r);
+    ngx_http_clear_accept_ranges(r);
+    ngx_http_weak_etag(r);
+
+    r->preserve_body = 1;
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_addition_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    ngx_int_t                  rc;
+    ngx_uint_t                 last;
+    ngx_chain_t               *cl;
+    ngx_http_request_t        *sr;
+    ngx_http_addition_ctx_t   *ctx;
+    ngx_http_addition_conf_t  *conf;
+
+    if (in == NULL || r->header_only) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_addition_filter_module);
+
+    if (ctx == NULL) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_addition_filter_module);
+
+    if (!ctx->before_body_sent) {
+        ctx->before_body_sent = 1;
+
+        if (conf->before_body.len) {
+            if (ngx_http_subrequest(r, &conf->before_body, NULL, &sr, NULL, 0)
+                != NGX_OK)
+            {
+                return NGX_ERROR;
+            }
+        }
+    }
+
+    if (conf->after_body.len == 0) {
+        ngx_http_set_ctx(r, NULL, ngx_http_addition_filter_module);
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    last = 0;
+
+    for (cl = in; cl; cl = cl->next) {
+        if (cl->buf->last_buf) {
+            cl->buf->last_buf = 0;
+            cl->buf->last_in_chain = 1;
+            cl->buf->sync = 1;
+            last = 1;
+        }
+    }
+
+    rc = ngx_http_next_body_filter(r, in);
+
+    if (rc == NGX_ERROR || !last || conf->after_body.len == 0) {
+        return rc;
+    }
+
+    if (ngx_http_subrequest(r, &conf->after_body, NULL, &sr, NULL, 0)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    ngx_http_set_ctx(r, NULL, ngx_http_addition_filter_module);
+
+    return ngx_http_send_special(r, NGX_HTTP_LAST);
+}
+
+
+static ngx_int_t
+ngx_http_addition_filter_init(ngx_conf_t *cf)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_addition_header_filter;
+
+    ngx_http_next_body_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_addition_body_filter;
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_addition_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_addition_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_addition_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->before_body = { 0, NULL };
+     *     conf->after_body = { 0, NULL };
+     *     conf->types = { NULL };
+     *     conf->types_keys = NULL;
+     */
+
+    return conf;
+}
+
+
+static char *
+ngx_http_addition_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_addition_conf_t *prev = parent;
+    ngx_http_addition_conf_t *conf = child;
+
+    ngx_conf_merge_str_value(conf->before_body, prev->before_body, "");
+    ngx_conf_merge_str_value(conf->after_body, prev->after_body, "");
+
+    if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types,
+                             &prev->types_keys, &prev->types,
+                             ngx_http_html_default_types)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_auth_basic_module.c b/nginx/src/http/modules/ngx_http_auth_basic_module.c
new file mode 100644 (file)
index 0000000..a6f9ec4
--- /dev/null
@@ -0,0 +1,429 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_crypt.h>
+
+
+#define NGX_HTTP_AUTH_BUF_SIZE  2048
+
+
+typedef struct {
+    ngx_http_complex_value_t  *realm;
+    ngx_http_complex_value_t   user_file;
+} ngx_http_auth_basic_loc_conf_t;
+
+
+static ngx_int_t ngx_http_auth_basic_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_auth_basic_crypt_handler(ngx_http_request_t *r,
+    ngx_str_t *passwd, ngx_str_t *realm);
+static ngx_int_t ngx_http_auth_basic_set_realm(ngx_http_request_t *r,
+    ngx_str_t *realm);
+static void ngx_http_auth_basic_close(ngx_file_t *file);
+static void *ngx_http_auth_basic_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_auth_basic_merge_loc_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+static ngx_int_t ngx_http_auth_basic_init(ngx_conf_t *cf);
+static char *ngx_http_auth_basic_user_file(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+
+static ngx_command_t  ngx_http_auth_basic_commands[] = {
+
+    { ngx_string("auth_basic"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF
+                        |NGX_CONF_TAKE1,
+      ngx_http_set_complex_value_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_auth_basic_loc_conf_t, realm),
+      NULL },
+
+    { ngx_string("auth_basic_user_file"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF
+                        |NGX_CONF_TAKE1,
+      ngx_http_auth_basic_user_file,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_auth_basic_loc_conf_t, user_file),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_auth_basic_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_auth_basic_init,              /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_auth_basic_create_loc_conf,   /* create location configuration */
+    ngx_http_auth_basic_merge_loc_conf     /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_auth_basic_module = {
+    NGX_MODULE_V1,
+    &ngx_http_auth_basic_module_ctx,       /* module context */
+    ngx_http_auth_basic_commands,          /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_auth_basic_handler(ngx_http_request_t *r)
+{
+    off_t                            offset;
+    ssize_t                          n;
+    ngx_fd_t                         fd;
+    ngx_int_t                        rc;
+    ngx_err_t                        err;
+    ngx_str_t                        pwd, realm, user_file;
+    ngx_uint_t                       i, level, login, left, passwd;
+    ngx_file_t                       file;
+    ngx_http_auth_basic_loc_conf_t  *alcf;
+    u_char                           buf[NGX_HTTP_AUTH_BUF_SIZE];
+    enum {
+        sw_login,
+        sw_passwd,
+        sw_skip
+    } state;
+
+    alcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_basic_module);
+
+    if (alcf->realm == NULL || alcf->user_file.value.data == NULL) {
+        return NGX_DECLINED;
+    }
+
+    if (ngx_http_complex_value(r, alcf->realm, &realm) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (realm.len == 3 && ngx_strncmp(realm.data, "off", 3) == 0) {
+        return NGX_DECLINED;
+    }
+
+    rc = ngx_http_auth_basic_user(r);
+
+    if (rc == NGX_DECLINED) {
+
+        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                      "no user/password was provided for basic authentication");
+
+        return ngx_http_auth_basic_set_realm(r, &realm);
+    }
+
+    if (rc == NGX_ERROR) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (ngx_http_complex_value(r, &alcf->user_file, &user_file) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    fd = ngx_open_file(user_file.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
+
+    if (fd == NGX_INVALID_FILE) {
+        err = ngx_errno;
+
+        if (err == NGX_ENOENT) {
+            level = NGX_LOG_ERR;
+            rc = NGX_HTTP_FORBIDDEN;
+
+        } else {
+            level = NGX_LOG_CRIT;
+            rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        ngx_log_error(level, r->connection->log, err,
+                      ngx_open_file_n " \"%s\" failed", user_file.data);
+
+        return rc;
+    }
+
+    ngx_memzero(&file, sizeof(ngx_file_t));
+
+    file.fd = fd;
+    file.name = user_file;
+    file.log = r->connection->log;
+
+    state = sw_login;
+    passwd = 0;
+    login = 0;
+    left = 0;
+    offset = 0;
+
+    for ( ;; ) {
+        i = left;
+
+        n = ngx_read_file(&file, buf + left, NGX_HTTP_AUTH_BUF_SIZE - left,
+                          offset);
+
+        if (n == NGX_ERROR) {
+            ngx_http_auth_basic_close(&file);
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        if (n == 0) {
+            break;
+        }
+
+        for (i = left; i < left + n; i++) {
+            switch (state) {
+
+            case sw_login:
+                if (login == 0) {
+
+                    if (buf[i] == '#' || buf[i] == CR) {
+                        state = sw_skip;
+                        break;
+                    }
+
+                    if (buf[i] == LF) {
+                        break;
+                    }
+                }
+
+                if (buf[i] != r->headers_in.user.data[login]) {
+                    state = sw_skip;
+                    break;
+                }
+
+                if (login == r->headers_in.user.len) {
+                    state = sw_passwd;
+                    passwd = i + 1;
+                }
+
+                login++;
+
+                break;
+
+            case sw_passwd:
+                if (buf[i] == LF || buf[i] == CR || buf[i] == ':') {
+                    buf[i] = '\0';
+
+                    ngx_http_auth_basic_close(&file);
+
+                    pwd.len = i - passwd;
+                    pwd.data = &buf[passwd];
+
+                    return ngx_http_auth_basic_crypt_handler(r, &pwd, &realm);
+                }
+
+                break;
+
+            case sw_skip:
+                if (buf[i] == LF) {
+                    state = sw_login;
+                    login = 0;
+                }
+
+                break;
+            }
+        }
+
+        if (state == sw_passwd) {
+            left = left + n - passwd;
+            ngx_memmove(buf, &buf[passwd], left);
+            passwd = 0;
+
+        } else {
+            left = 0;
+        }
+
+        offset += n;
+    }
+
+    ngx_http_auth_basic_close(&file);
+
+    if (state == sw_passwd) {
+        pwd.len = i - passwd;
+        pwd.data = ngx_pnalloc(r->pool, pwd.len + 1);
+        if (pwd.data == NULL) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        ngx_cpystrn(pwd.data, &buf[passwd], pwd.len + 1);
+
+        return ngx_http_auth_basic_crypt_handler(r, &pwd, &realm);
+    }
+
+    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                  "user \"%V\" was not found in \"%s\"",
+                  &r->headers_in.user, user_file.data);
+
+    return ngx_http_auth_basic_set_realm(r, &realm);
+}
+
+
+static ngx_int_t
+ngx_http_auth_basic_crypt_handler(ngx_http_request_t *r, ngx_str_t *passwd,
+    ngx_str_t *realm)
+{
+    ngx_int_t   rc;
+    u_char     *encrypted;
+
+    rc = ngx_crypt(r->pool, r->headers_in.passwd.data, passwd->data,
+                   &encrypted);
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "rc: %i user: \"%V\" salt: \"%s\"",
+                   rc, &r->headers_in.user, passwd->data);
+
+    if (rc != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (ngx_strcmp(encrypted, passwd->data) == 0) {
+        return NGX_OK;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "encrypted: \"%s\"", encrypted);
+
+    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                  "user \"%V\": password mismatch",
+                  &r->headers_in.user);
+
+    return ngx_http_auth_basic_set_realm(r, realm);
+}
+
+
+static ngx_int_t
+ngx_http_auth_basic_set_realm(ngx_http_request_t *r, ngx_str_t *realm)
+{
+    size_t   len;
+    u_char  *basic, *p;
+
+    r->headers_out.www_authenticate = ngx_list_push(&r->headers_out.headers);
+    if (r->headers_out.www_authenticate == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    len = sizeof("Basic realm=\"\"") - 1 + realm->len;
+
+    basic = ngx_pnalloc(r->pool, len);
+    if (basic == NULL) {
+        r->headers_out.www_authenticate->hash = 0;
+        r->headers_out.www_authenticate = NULL;
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    p = ngx_cpymem(basic, "Basic realm=\"", sizeof("Basic realm=\"") - 1);
+    p = ngx_cpymem(p, realm->data, realm->len);
+    *p = '"';
+
+    r->headers_out.www_authenticate->hash = 1;
+    ngx_str_set(&r->headers_out.www_authenticate->key, "WWW-Authenticate");
+    r->headers_out.www_authenticate->value.data = basic;
+    r->headers_out.www_authenticate->value.len = len;
+
+    return NGX_HTTP_UNAUTHORIZED;
+}
+
+static void
+ngx_http_auth_basic_close(ngx_file_t *file)
+{
+    if (ngx_close_file(file->fd) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, file->log, ngx_errno,
+                      ngx_close_file_n " \"%s\" failed", file->name.data);
+    }
+}
+
+
+static void *
+ngx_http_auth_basic_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_auth_basic_loc_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_auth_basic_loc_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    return conf;
+}
+
+
+static char *
+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;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_auth_basic_init(ngx_conf_t *cf)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_auth_basic_handler;
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_http_auth_basic_user_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_auth_basic_loc_conf_t *alcf = conf;
+
+    ngx_str_t                         *value;
+    ngx_http_compile_complex_value_t   ccv;
+
+    if (alcf->user_file.value.data) {
+        return "is duplicate";
+    }
+
+    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.zero = 1;
+    ccv.conf_prefix = 1;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_auth_request_module.c b/nginx/src/http/modules/ngx_http_auth_request_module.c
new file mode 100644 (file)
index 0000000..bab79e4
--- /dev/null
@@ -0,0 +1,444 @@
+
+/*
+ * Copyright (C) Maxim Dounin
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_str_t                 uri;
+    ngx_array_t              *vars;
+} ngx_http_auth_request_conf_t;
+
+
+typedef struct {
+    ngx_uint_t                done;
+    ngx_uint_t                status;
+    ngx_http_request_t       *subrequest;
+} ngx_http_auth_request_ctx_t;
+
+
+typedef struct {
+    ngx_int_t                 index;
+    ngx_http_complex_value_t  value;
+    ngx_http_set_variable_pt  set_handler;
+} ngx_http_auth_request_variable_t;
+
+
+static ngx_int_t ngx_http_auth_request_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_auth_request_done(ngx_http_request_t *r,
+    void *data, ngx_int_t rc);
+static ngx_int_t ngx_http_auth_request_set_variables(ngx_http_request_t *r,
+    ngx_http_auth_request_conf_t *arcf, ngx_http_auth_request_ctx_t *ctx);
+static ngx_int_t ngx_http_auth_request_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static void *ngx_http_auth_request_create_conf(ngx_conf_t *cf);
+static char *ngx_http_auth_request_merge_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+static ngx_int_t ngx_http_auth_request_init(ngx_conf_t *cf);
+static char *ngx_http_auth_request(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_auth_request_set(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+
+static ngx_command_t  ngx_http_auth_request_commands[] = {
+
+    { ngx_string("auth_request"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_auth_request,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("auth_request_set"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+      ngx_http_auth_request_set,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_auth_request_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_auth_request_init,            /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_auth_request_create_conf,     /* create location configuration */
+    ngx_http_auth_request_merge_conf       /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_auth_request_module = {
+    NGX_MODULE_V1,
+    &ngx_http_auth_request_module_ctx,     /* module context */
+    ngx_http_auth_request_commands,        /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_auth_request_handler(ngx_http_request_t *r)
+{
+    ngx_table_elt_t               *h, *ho;
+    ngx_http_request_t            *sr;
+    ngx_http_post_subrequest_t    *ps;
+    ngx_http_auth_request_ctx_t   *ctx;
+    ngx_http_auth_request_conf_t  *arcf;
+
+    arcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_request_module);
+
+    if (arcf->uri.len == 0) {
+        return NGX_DECLINED;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "auth request handler");
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_auth_request_module);
+
+    if (ctx != NULL) {
+        if (!ctx->done) {
+            return NGX_AGAIN;
+        }
+
+        /*
+         * as soon as we are done - explicitly set variables to make
+         * sure they will be available after internal redirects
+         */
+
+        if (ngx_http_auth_request_set_variables(r, arcf, ctx) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        /* return appropriate status */
+
+        if (ctx->status == NGX_HTTP_FORBIDDEN) {
+            return ctx->status;
+        }
+
+        if (ctx->status == NGX_HTTP_UNAUTHORIZED) {
+            sr = ctx->subrequest;
+
+            h = sr->headers_out.www_authenticate;
+
+            if (!h && sr->upstream) {
+                h = sr->upstream->headers_in.www_authenticate;
+            }
+
+            if (h) {
+                ho = ngx_list_push(&r->headers_out.headers);
+                if (ho == NULL) {
+                    return NGX_ERROR;
+                }
+
+                *ho = *h;
+
+                r->headers_out.www_authenticate = ho;
+            }
+
+            return ctx->status;
+        }
+
+        if (ctx->status >= NGX_HTTP_OK
+            && ctx->status < NGX_HTTP_SPECIAL_RESPONSE)
+        {
+            return NGX_OK;
+        }
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "auth request unexpected status: %ui", ctx->status);
+
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_auth_request_ctx_t));
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
+    if (ps == NULL) {
+        return NGX_ERROR;
+    }
+
+    ps->handler = ngx_http_auth_request_done;
+    ps->data = ctx;
+
+    if (ngx_http_subrequest(r, &arcf->uri, NULL, &sr, ps,
+                            NGX_HTTP_SUBREQUEST_WAITED)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    /*
+     * allocate fake request body to avoid attempts to read it and to make
+     * sure real body file (if already read) won't be closed by upstream
+     */
+
+    sr->request_body = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t));
+    if (sr->request_body == NULL) {
+        return NGX_ERROR;
+    }
+
+    sr->header_only = 1;
+
+    ctx->subrequest = sr;
+
+    ngx_http_set_ctx(r, ctx, ngx_http_auth_request_module);
+
+    return NGX_AGAIN;
+}
+
+
+static ngx_int_t
+ngx_http_auth_request_done(ngx_http_request_t *r, void *data, ngx_int_t rc)
+{
+    ngx_http_auth_request_ctx_t   *ctx = data;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "auth request done s:%ui", r->headers_out.status);
+
+    ctx->done = 1;
+    ctx->status = r->headers_out.status;
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_http_auth_request_set_variables(ngx_http_request_t *r,
+    ngx_http_auth_request_conf_t *arcf, ngx_http_auth_request_ctx_t *ctx)
+{
+    ngx_str_t                          val;
+    ngx_http_variable_t               *v;
+    ngx_http_variable_value_t         *vv;
+    ngx_http_auth_request_variable_t  *av, *last;
+    ngx_http_core_main_conf_t         *cmcf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "auth request set variables");
+
+    if (arcf->vars == NULL) {
+        return NGX_OK;
+    }
+
+    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+    v = cmcf->variables.elts;
+
+    av = arcf->vars->elts;
+    last = av + arcf->vars->nelts;
+
+    while (av < last) {
+        /*
+         * explicitly set new value to make sure it will be available after
+         * internal redirects
+         */
+
+        vv = &r->variables[av->index];
+
+        if (ngx_http_complex_value(ctx->subrequest, &av->value, &val)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+
+        vv->valid = 1;
+        vv->not_found = 0;
+        vv->data = val.data;
+        vv->len = val.len;
+
+        if (av->set_handler) {
+            /*
+             * set_handler only available in cmcf->variables_keys, so we store
+             * it explicitly
+             */
+
+            av->set_handler(r, vv, v[av->index].data);
+        }
+
+        av++;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_auth_request_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "auth request variable");
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_auth_request_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_auth_request_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_auth_request_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->uri = { 0, NULL };
+     */
+
+    conf->vars = NGX_CONF_UNSET_PTR;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_auth_request_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_auth_request_conf_t *prev = parent;
+    ngx_http_auth_request_conf_t *conf = child;
+
+    ngx_conf_merge_str_value(conf->uri, prev->uri, "");
+    ngx_conf_merge_ptr_value(conf->vars, prev->vars, NULL);
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_auth_request_init(ngx_conf_t *cf)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_auth_request_handler;
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_http_auth_request(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_auth_request_conf_t *arcf = conf;
+
+    ngx_str_t        *value;
+
+    if (arcf->uri.data != NULL) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    if (ngx_strcmp(value[1].data, "off") == 0) {
+        arcf->uri.len = 0;
+        arcf->uri.data = (u_char *) "";
+
+        return NGX_CONF_OK;
+    }
+
+    arcf->uri = value[1];
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_auth_request_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_auth_request_conf_t *arcf = conf;
+
+    ngx_str_t                         *value;
+    ngx_http_variable_t               *v;
+    ngx_http_auth_request_variable_t  *av;
+    ngx_http_compile_complex_value_t   ccv;
+
+    value = cf->args->elts;
+
+    if (value[1].data[0] != '$') {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid variable name \"%V\"", &value[1]);
+        return NGX_CONF_ERROR;
+    }
+
+    value[1].len--;
+    value[1].data++;
+
+    if (arcf->vars == NGX_CONF_UNSET_PTR) {
+        arcf->vars = ngx_array_create(cf->pool, 1,
+                                      sizeof(ngx_http_auth_request_variable_t));
+        if (arcf->vars == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    av = ngx_array_push(arcf->vars);
+    if (av == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    v = ngx_http_add_variable(cf, &value[1], NGX_HTTP_VAR_CHANGEABLE);
+    if (v == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    av->index = ngx_http_get_variable_index(cf, &value[1]);
+    if (av->index == NGX_ERROR) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (v->get_handler == NULL) {
+        v->get_handler = ngx_http_auth_request_variable;
+        v->data = (uintptr_t) av;
+    }
+
+    av->set_handler = v->set_handler;
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[2];
+    ccv.complex_value = &av->value;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_autoindex_module.c b/nginx/src/http/modules/ngx_http_autoindex_module.c
new file mode 100644 (file)
index 0000000..94b91db
--- /dev/null
@@ -0,0 +1,1056 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#if 0
+
+typedef struct {
+    ngx_buf_t     *buf;
+    size_t         size;
+    ngx_pool_t    *pool;
+    size_t         alloc_size;
+    ngx_chain_t  **last_out;
+} ngx_http_autoindex_ctx_t;
+
+#endif
+
+
+typedef struct {
+    ngx_str_t      name;
+    size_t         utf_len;
+    size_t         escape;
+    size_t         escape_html;
+
+    unsigned       dir:1;
+    unsigned       file:1;
+
+    time_t         mtime;
+    off_t          size;
+} ngx_http_autoindex_entry_t;
+
+
+typedef struct {
+    ngx_flag_t     enable;
+    ngx_uint_t     format;
+    ngx_flag_t     localtime;
+    ngx_flag_t     exact_size;
+} ngx_http_autoindex_loc_conf_t;
+
+
+#define NGX_HTTP_AUTOINDEX_HTML         0
+#define NGX_HTTP_AUTOINDEX_JSON         1
+#define NGX_HTTP_AUTOINDEX_JSONP        2
+#define NGX_HTTP_AUTOINDEX_XML          3
+
+#define NGX_HTTP_AUTOINDEX_PREALLOCATE  50
+
+#define NGX_HTTP_AUTOINDEX_NAME_LEN     50
+
+
+static ngx_buf_t *ngx_http_autoindex_html(ngx_http_request_t *r,
+    ngx_array_t *entries);
+static ngx_buf_t *ngx_http_autoindex_json(ngx_http_request_t *r,
+    ngx_array_t *entries, ngx_str_t *callback);
+static ngx_int_t ngx_http_autoindex_jsonp_callback(ngx_http_request_t *r,
+    ngx_str_t *callback);
+static ngx_buf_t *ngx_http_autoindex_xml(ngx_http_request_t *r,
+    ngx_array_t *entries);
+
+static int ngx_libc_cdecl ngx_http_autoindex_cmp_entries(const void *one,
+    const void *two);
+static ngx_int_t ngx_http_autoindex_error(ngx_http_request_t *r,
+    ngx_dir_t *dir, ngx_str_t *name);
+
+static ngx_int_t ngx_http_autoindex_init(ngx_conf_t *cf);
+static void *ngx_http_autoindex_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_autoindex_merge_loc_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+
+
+static ngx_conf_enum_t  ngx_http_autoindex_format[] = {
+    { ngx_string("html"), NGX_HTTP_AUTOINDEX_HTML },
+    { ngx_string("json"), NGX_HTTP_AUTOINDEX_JSON },
+    { ngx_string("jsonp"), NGX_HTTP_AUTOINDEX_JSONP },
+    { ngx_string("xml"), NGX_HTTP_AUTOINDEX_XML },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t  ngx_http_autoindex_commands[] = {
+
+    { ngx_string("autoindex"),
+      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_autoindex_loc_conf_t, enable),
+      NULL },
+
+    { ngx_string("autoindex_format"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_autoindex_loc_conf_t, format),
+      &ngx_http_autoindex_format },
+
+    { ngx_string("autoindex_localtime"),
+      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_autoindex_loc_conf_t, localtime),
+      NULL },
+
+    { ngx_string("autoindex_exact_size"),
+      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_autoindex_loc_conf_t, exact_size),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_autoindex_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_autoindex_init,               /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_autoindex_create_loc_conf,    /* create location configuration */
+    ngx_http_autoindex_merge_loc_conf      /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_autoindex_module = {
+    NGX_MODULE_V1,
+    &ngx_http_autoindex_module_ctx,        /* module context */
+    ngx_http_autoindex_commands,           /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_autoindex_handler(ngx_http_request_t *r)
+{
+    u_char                         *last, *filename;
+    size_t                          len, allocated, root;
+    ngx_err_t                       err;
+    ngx_buf_t                      *b;
+    ngx_int_t                       rc;
+    ngx_str_t                       path, callback;
+    ngx_dir_t                       dir;
+    ngx_uint_t                      level, format;
+    ngx_pool_t                     *pool;
+    ngx_chain_t                     out;
+    ngx_array_t                     entries;
+    ngx_http_autoindex_entry_t     *entry;
+    ngx_http_autoindex_loc_conf_t  *alcf;
+
+    if (r->uri.data[r->uri.len - 1] != '/') {
+        return NGX_DECLINED;
+    }
+
+    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
+        return NGX_DECLINED;
+    }
+
+    alcf = ngx_http_get_module_loc_conf(r, ngx_http_autoindex_module);
+
+    if (!alcf->enable) {
+        return NGX_DECLINED;
+    }
+
+    rc = ngx_http_discard_request_body(r);
+
+    if (rc != NGX_OK) {
+        return rc;
+    }
+
+    /* NGX_DIR_MASK_LEN is lesser than NGX_HTTP_AUTOINDEX_PREALLOCATE */
+
+    last = ngx_http_map_uri_to_path(r, &path, &root,
+                                    NGX_HTTP_AUTOINDEX_PREALLOCATE);
+    if (last == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    allocated = path.len;
+    path.len = last - path.data;
+    if (path.len > 1) {
+        path.len--;
+    }
+    path.data[path.len] = '\0';
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http autoindex: \"%s\"", path.data);
+
+    format = alcf->format;
+
+    if (format == NGX_HTTP_AUTOINDEX_JSONP) {
+        if (ngx_http_autoindex_jsonp_callback(r, &callback) != NGX_OK) {
+            return NGX_HTTP_BAD_REQUEST;
+        }
+
+        if (callback.len == 0) {
+            format = NGX_HTTP_AUTOINDEX_JSON;
+        }
+    }
+
+    if (ngx_open_dir(&path, &dir) == NGX_ERROR) {
+        err = ngx_errno;
+
+        if (err == NGX_ENOENT
+            || err == NGX_ENOTDIR
+            || err == NGX_ENAMETOOLONG)
+        {
+            level = NGX_LOG_ERR;
+            rc = NGX_HTTP_NOT_FOUND;
+
+        } else if (err == NGX_EACCES) {
+            level = NGX_LOG_ERR;
+            rc = NGX_HTTP_FORBIDDEN;
+
+        } else {
+            level = NGX_LOG_CRIT;
+            rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        ngx_log_error(level, r->connection->log, err,
+                      ngx_open_dir_n " \"%s\" failed", path.data);
+
+        return rc;
+    }
+
+#if (NGX_SUPPRESS_WARN)
+
+    /* MSVC thinks 'entries' may be used without having been initialized */
+    ngx_memzero(&entries, sizeof(ngx_array_t));
+
+#endif
+
+    /* TODO: pool should be temporary pool */
+    pool = r->pool;
+
+    if (ngx_array_init(&entries, pool, 40, sizeof(ngx_http_autoindex_entry_t))
+        != NGX_OK)
+    {
+        return ngx_http_autoindex_error(r, &dir, &path);
+    }
+
+    r->headers_out.status = NGX_HTTP_OK;
+
+    switch (format) {
+
+    case NGX_HTTP_AUTOINDEX_JSON:
+        ngx_str_set(&r->headers_out.content_type, "application/json");
+        break;
+
+    case NGX_HTTP_AUTOINDEX_JSONP:
+        ngx_str_set(&r->headers_out.content_type, "application/javascript");
+        break;
+
+    case NGX_HTTP_AUTOINDEX_XML:
+        ngx_str_set(&r->headers_out.content_type, "text/xml");
+        ngx_str_set(&r->headers_out.charset, "utf-8");
+        break;
+
+    default: /* NGX_HTTP_AUTOINDEX_HTML */
+        ngx_str_set(&r->headers_out.content_type, "text/html");
+        break;
+    }
+
+    r->headers_out.content_type_len = r->headers_out.content_type.len;
+    r->headers_out.content_type_lowcase = NULL;
+
+    rc = ngx_http_send_header(r);
+
+    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+        if (ngx_close_dir(&dir) == NGX_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
+                          ngx_close_dir_n " \"%V\" failed", &path);
+        }
+
+        return rc;
+    }
+
+    filename = path.data;
+    filename[path.len] = '/';
+
+    for ( ;; ) {
+        ngx_set_errno(0);
+
+        if (ngx_read_dir(&dir) == NGX_ERROR) {
+            err = ngx_errno;
+
+            if (err != NGX_ENOMOREFILES) {
+                ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
+                              ngx_read_dir_n " \"%V\" failed", &path);
+                return ngx_http_autoindex_error(r, &dir, &path);
+            }
+
+            break;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http autoindex file: \"%s\"", ngx_de_name(&dir));
+
+        len = ngx_de_namelen(&dir);
+
+        if (ngx_de_name(&dir)[0] == '.') {
+            continue;
+        }
+
+        if (!dir.valid_info) {
+
+            /* 1 byte for '/' and 1 byte for terminating '\0' */
+
+            if (path.len + 1 + len + 1 > allocated) {
+                allocated = path.len + 1 + len + 1
+                                     + NGX_HTTP_AUTOINDEX_PREALLOCATE;
+
+                filename = ngx_pnalloc(pool, allocated);
+                if (filename == NULL) {
+                    return ngx_http_autoindex_error(r, &dir, &path);
+                }
+
+                last = ngx_cpystrn(filename, path.data, path.len + 1);
+                *last++ = '/';
+            }
+
+            ngx_cpystrn(last, ngx_de_name(&dir), len + 1);
+
+            if (ngx_de_info(filename, &dir) == NGX_FILE_ERROR) {
+                err = ngx_errno;
+
+                if (err != NGX_ENOENT && err != NGX_ELOOP) {
+                    ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
+                                  ngx_de_info_n " \"%s\" failed", filename);
+
+                    if (err == NGX_EACCES) {
+                        continue;
+                    }
+
+                    return ngx_http_autoindex_error(r, &dir, &path);
+                }
+
+                if (ngx_de_link_info(filename, &dir) == NGX_FILE_ERROR) {
+                    ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+                                  ngx_de_link_info_n " \"%s\" failed",
+                                  filename);
+                    return ngx_http_autoindex_error(r, &dir, &path);
+                }
+            }
+        }
+
+        entry = ngx_array_push(&entries);
+        if (entry == NULL) {
+            return ngx_http_autoindex_error(r, &dir, &path);
+        }
+
+        entry->name.len = len;
+
+        entry->name.data = ngx_pnalloc(pool, len + 1);
+        if (entry->name.data == NULL) {
+            return ngx_http_autoindex_error(r, &dir, &path);
+        }
+
+        ngx_cpystrn(entry->name.data, ngx_de_name(&dir), len + 1);
+
+        entry->dir = ngx_de_is_dir(&dir);
+        entry->file = ngx_de_is_file(&dir);
+        entry->mtime = ngx_de_mtime(&dir);
+        entry->size = ngx_de_size(&dir);
+    }
+
+    if (ngx_close_dir(&dir) == NGX_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
+                      ngx_close_dir_n " \"%V\" failed", &path);
+    }
+
+    if (entries.nelts > 1) {
+        ngx_qsort(entries.elts, (size_t) entries.nelts,
+                  sizeof(ngx_http_autoindex_entry_t),
+                  ngx_http_autoindex_cmp_entries);
+    }
+
+    switch (format) {
+
+    case NGX_HTTP_AUTOINDEX_JSON:
+        b = ngx_http_autoindex_json(r, &entries, NULL);
+        break;
+
+    case NGX_HTTP_AUTOINDEX_JSONP:
+        b = ngx_http_autoindex_json(r, &entries, &callback);
+        break;
+
+    case NGX_HTTP_AUTOINDEX_XML:
+        b = ngx_http_autoindex_xml(r, &entries);
+        break;
+
+    default: /* NGX_HTTP_AUTOINDEX_HTML */
+        b = ngx_http_autoindex_html(r, &entries);
+        break;
+    }
+
+    if (b == NULL) {
+        return NGX_ERROR;
+    }
+
+    /* TODO: free temporary pool */
+
+    if (r == r->main) {
+        b->last_buf = 1;
+    }
+
+    b->last_in_chain = 1;
+
+    out.buf = b;
+    out.next = NULL;
+
+    return ngx_http_output_filter(r, &out);
+}
+
+
+static ngx_buf_t *
+ngx_http_autoindex_html(ngx_http_request_t *r, ngx_array_t *entries)
+{
+    u_char                         *last, scale;
+    off_t                           length;
+    size_t                          len, char_len, escape_html;
+    ngx_tm_t                        tm;
+    ngx_buf_t                      *b;
+    ngx_int_t                       size;
+    ngx_uint_t                      i, utf8;
+    ngx_time_t                     *tp;
+    ngx_http_autoindex_entry_t     *entry;
+    ngx_http_autoindex_loc_conf_t  *alcf;
+
+    static u_char  title[] =
+        "<html>" CRLF
+        "<head><title>Index of "
+    ;
+
+    static u_char  header[] =
+        "</title></head>" CRLF
+        "<body bgcolor=\"white\">" CRLF
+        "<h1>Index of "
+    ;
+
+    static u_char  tail[] =
+        "</body>" CRLF
+        "</html>" CRLF
+    ;
+
+    static char  *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+                               "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+    if (r->headers_out.charset.len == 5
+        && ngx_strncasecmp(r->headers_out.charset.data, (u_char *) "utf-8", 5)
+           == 0)
+    {
+        utf8 = 1;
+
+    } else {
+        utf8 = 0;
+    }
+
+    escape_html = ngx_escape_html(NULL, r->uri.data, r->uri.len);
+
+    len = sizeof(title) - 1
+          + r->uri.len + escape_html
+          + sizeof(header) - 1
+          + r->uri.len + escape_html
+          + sizeof("</h1>") - 1
+          + sizeof("<hr><pre><a href=\"../\">../</a>" CRLF) - 1
+          + sizeof("</pre><hr>") - 1
+          + sizeof(tail) - 1;
+
+    entry = entries->elts;
+    for (i = 0; i < entries->nelts; i++) {
+        entry[i].escape = 2 * ngx_escape_uri(NULL, entry[i].name.data,
+                                             entry[i].name.len,
+                                             NGX_ESCAPE_URI_COMPONENT);
+
+        entry[i].escape_html = ngx_escape_html(NULL, entry[i].name.data,
+                                               entry[i].name.len);
+
+        if (utf8) {
+            entry[i].utf_len = ngx_utf8_length(entry[i].name.data,
+                                               entry[i].name.len);
+        } else {
+            entry[i].utf_len = entry[i].name.len;
+        }
+
+        len += sizeof("<a href=\"") - 1
+            + entry[i].name.len + entry[i].escape
+            + 1                                          /* 1 is for "/" */
+            + sizeof("\">") - 1
+            + entry[i].name.len - entry[i].utf_len
+            + entry[i].escape_html
+            + NGX_HTTP_AUTOINDEX_NAME_LEN + sizeof("&gt;") - 2
+            + sizeof("</a>") - 1
+            + sizeof(" 28-Sep-1970 12:00 ") - 1
+            + 20                                         /* the file size */
+            + 2;
+    }
+
+    b = ngx_create_temp_buf(r->pool, len);
+    if (b == NULL) {
+        return NULL;
+    }
+
+    b->last = ngx_cpymem(b->last, title, sizeof(title) - 1);
+
+    if (escape_html) {
+        b->last = (u_char *) ngx_escape_html(b->last, r->uri.data, r->uri.len);
+        b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
+        b->last = (u_char *) ngx_escape_html(b->last, r->uri.data, r->uri.len);
+
+    } else {
+        b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
+        b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
+        b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
+    }
+
+    b->last = ngx_cpymem(b->last, "</h1>", sizeof("</h1>") - 1);
+
+    b->last = ngx_cpymem(b->last, "<hr><pre><a href=\"../\">../</a>" CRLF,
+                         sizeof("<hr><pre><a href=\"../\">../</a>" CRLF) - 1);
+
+    alcf = ngx_http_get_module_loc_conf(r, ngx_http_autoindex_module);
+    tp = ngx_timeofday();
+
+    for (i = 0; i < entries->nelts; i++) {
+        b->last = ngx_cpymem(b->last, "<a href=\"", sizeof("<a href=\"") - 1);
+
+        if (entry[i].escape) {
+            ngx_escape_uri(b->last, entry[i].name.data, entry[i].name.len,
+                           NGX_ESCAPE_URI_COMPONENT);
+
+            b->last += entry[i].name.len + entry[i].escape;
+
+        } else {
+            b->last = ngx_cpymem(b->last, entry[i].name.data,
+                                 entry[i].name.len);
+        }
+
+        if (entry[i].dir) {
+            *b->last++ = '/';
+        }
+
+        *b->last++ = '"';
+        *b->last++ = '>';
+
+        len = entry[i].utf_len;
+
+        if (entry[i].name.len != len) {
+            if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
+                char_len = NGX_HTTP_AUTOINDEX_NAME_LEN - 3 + 1;
+
+            } else {
+                char_len = NGX_HTTP_AUTOINDEX_NAME_LEN + 1;
+            }
+
+            last = b->last;
+            b->last = ngx_utf8_cpystrn(b->last, entry[i].name.data,
+                                       char_len, entry[i].name.len + 1);
+
+            if (entry[i].escape_html) {
+                b->last = (u_char *) ngx_escape_html(last, entry[i].name.data,
+                                                     b->last - last);
+            }
+
+            last = b->last;
+
+        } else {
+            if (entry[i].escape_html) {
+                if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
+                    char_len = NGX_HTTP_AUTOINDEX_NAME_LEN - 3;
+
+                } else {
+                    char_len = len;
+                }
+
+                b->last = (u_char *) ngx_escape_html(b->last,
+                                                  entry[i].name.data, char_len);
+                last = b->last;
+
+            } else {
+                b->last = ngx_cpystrn(b->last, entry[i].name.data,
+                                      NGX_HTTP_AUTOINDEX_NAME_LEN + 1);
+                last = b->last - 3;
+            }
+        }
+
+        if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
+            b->last = ngx_cpymem(last, "..&gt;</a>", sizeof("..&gt;</a>") - 1);
+
+        } else {
+            if (entry[i].dir && NGX_HTTP_AUTOINDEX_NAME_LEN - len > 0) {
+                *b->last++ = '/';
+                len++;
+            }
+
+            b->last = ngx_cpymem(b->last, "</a>", sizeof("</a>") - 1);
+
+            if (NGX_HTTP_AUTOINDEX_NAME_LEN - len > 0) {
+                ngx_memset(b->last, ' ', NGX_HTTP_AUTOINDEX_NAME_LEN - len);
+                b->last += NGX_HTTP_AUTOINDEX_NAME_LEN - len;
+            }
+        }
+
+        *b->last++ = ' ';
+
+        ngx_gmtime(entry[i].mtime + tp->gmtoff * 60 * alcf->localtime, &tm);
+
+        b->last = ngx_sprintf(b->last, "%02d-%s-%d %02d:%02d ",
+                              tm.ngx_tm_mday,
+                              months[tm.ngx_tm_mon - 1],
+                              tm.ngx_tm_year,
+                              tm.ngx_tm_hour,
+                              tm.ngx_tm_min);
+
+        if (alcf->exact_size) {
+            if (entry[i].dir) {
+                b->last = ngx_cpymem(b->last,  "                  -",
+                                     sizeof("                  -") - 1);
+            } else {
+                b->last = ngx_sprintf(b->last, "%19O", entry[i].size);
+            }
+
+        } else {
+            if (entry[i].dir) {
+                b->last = ngx_cpymem(b->last,  "      -",
+                                     sizeof("      -") - 1);
+
+            } else {
+                length = entry[i].size;
+
+                if (length > 1024 * 1024 * 1024 - 1) {
+                    size = (ngx_int_t) (length / (1024 * 1024 * 1024));
+                    if ((length % (1024 * 1024 * 1024))
+                                                > (1024 * 1024 * 1024 / 2 - 1))
+                    {
+                        size++;
+                    }
+                    scale = 'G';
+
+                } else if (length > 1024 * 1024 - 1) {
+                    size = (ngx_int_t) (length / (1024 * 1024));
+                    if ((length % (1024 * 1024)) > (1024 * 1024 / 2 - 1)) {
+                        size++;
+                    }
+                    scale = 'M';
+
+                } else if (length > 9999) {
+                    size = (ngx_int_t) (length / 1024);
+                    if (length % 1024 > 511) {
+                        size++;
+                    }
+                    scale = 'K';
+
+                } else {
+                    size = (ngx_int_t) length;
+                    scale = '\0';
+                }
+
+                if (scale) {
+                    b->last = ngx_sprintf(b->last, "%6i%c", size, scale);
+
+                } else {
+                    b->last = ngx_sprintf(b->last, " %6i", size);
+                }
+            }
+        }
+
+        *b->last++ = CR;
+        *b->last++ = LF;
+    }
+
+    b->last = ngx_cpymem(b->last, "</pre><hr>", sizeof("</pre><hr>") - 1);
+
+    b->last = ngx_cpymem(b->last, tail, sizeof(tail) - 1);
+
+    return b;
+}
+
+
+static ngx_buf_t *
+ngx_http_autoindex_json(ngx_http_request_t *r, ngx_array_t *entries,
+    ngx_str_t *callback)
+{
+    size_t                       len;
+    ngx_buf_t                   *b;
+    ngx_uint_t                   i;
+    ngx_http_autoindex_entry_t  *entry;
+
+    len = sizeof("[" CRLF CRLF "]") - 1;
+
+    if (callback) {
+        len += sizeof("/* callback */" CRLF "();") - 1 + callback->len;
+    }
+
+    entry = entries->elts;
+
+    for (i = 0; i < entries->nelts; i++) {
+        entry[i].escape = ngx_escape_json(NULL, entry[i].name.data,
+                                          entry[i].name.len);
+
+        len += sizeof("{  }," CRLF) - 1
+            + sizeof("\"name\":\"\"") - 1
+            + entry[i].name.len + entry[i].escape
+            + sizeof(", \"type\":\"directory\"") - 1
+            + sizeof(", \"mtime\":\"Wed, 31 Dec 1986 10:00:00 GMT\"") - 1;
+
+        if (entry[i].file) {
+            len += sizeof(", \"size\":") - 1 + NGX_OFF_T_LEN;
+        }
+    }
+
+    b = ngx_create_temp_buf(r->pool, len);
+    if (b == NULL) {
+        return NULL;
+    }
+
+    if (callback) {
+        b->last = ngx_cpymem(b->last, "/* callback */" CRLF,
+                             sizeof("/* callback */" CRLF) - 1);
+
+        b->last = ngx_cpymem(b->last, callback->data, callback->len);
+
+        *b->last++ = '(';
+    }
+
+    *b->last++ = '[';
+
+    for (i = 0; i < entries->nelts; i++) {
+        b->last = ngx_cpymem(b->last, CRLF "{ \"name\":\"",
+                             sizeof(CRLF "{ \"name\":\"") - 1);
+
+        if (entry[i].escape) {
+            b->last = (u_char *) ngx_escape_json(b->last, entry[i].name.data,
+                                                 entry[i].name.len);
+        } else {
+            b->last = ngx_cpymem(b->last, entry[i].name.data,
+                                 entry[i].name.len);
+        }
+
+        b->last = ngx_cpymem(b->last, "\", \"type\":\"",
+                             sizeof("\", \"type\":\"") - 1);
+
+        if (entry[i].dir) {
+            b->last = ngx_cpymem(b->last, "directory", sizeof("directory") - 1);
+
+        } else if (entry[i].file) {
+            b->last = ngx_cpymem(b->last, "file", sizeof("file") - 1);
+
+        } else {
+            b->last = ngx_cpymem(b->last, "other", sizeof("other") - 1);
+        }
+
+        b->last = ngx_cpymem(b->last, "\", \"mtime\":\"",
+                             sizeof("\", \"mtime\":\"") - 1);
+
+        b->last = ngx_http_time(b->last, entry[i].mtime);
+
+        if (entry[i].file) {
+            b->last = ngx_cpymem(b->last, "\", \"size\":",
+                                 sizeof("\", \"size\":") - 1);
+            b->last = ngx_sprintf(b->last, "%O", entry[i].size);
+
+        } else {
+            *b->last++ = '"';
+        }
+
+        b->last = ngx_cpymem(b->last, " },", sizeof(" },") - 1);
+    }
+
+    if (i > 0) {
+        b->last--;  /* strip last comma */
+    }
+
+    b->last = ngx_cpymem(b->last, CRLF "]", sizeof(CRLF "]") - 1);
+
+    if (callback) {
+        *b->last++ = ')'; *b->last++ = ';';
+    }
+
+    return b;
+}
+
+
+static ngx_int_t
+ngx_http_autoindex_jsonp_callback(ngx_http_request_t *r, ngx_str_t *callback)
+{
+    u_char      *p, c, ch;
+    ngx_uint_t   i;
+
+    if (ngx_http_arg(r, (u_char *) "callback", 8, callback) != NGX_OK) {
+        callback->len = 0;
+        return NGX_OK;
+    }
+
+    if (callback->len > 128) {
+        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                      "client sent too long callback name: \"%V\"", callback);
+        return NGX_DECLINED;
+    }
+
+    p = callback->data;
+
+    for (i = 0; i < callback->len; i++) {
+        ch = p[i];
+
+        c = (u_char) (ch | 0x20);
+        if (c >= 'a' && c <= 'z') {
+            continue;
+        }
+
+        if ((ch >= '0' && ch <= '9') || ch == '_' || ch == '.') {
+            continue;
+        }
+
+        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                      "client sent invalid callback name: \"%V\"", callback);
+
+        return NGX_DECLINED;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_buf_t *
+ngx_http_autoindex_xml(ngx_http_request_t *r, ngx_array_t *entries)
+{
+    size_t                          len;
+    ngx_tm_t                        tm;
+    ngx_buf_t                      *b;
+    ngx_str_t                       type;
+    ngx_uint_t                      i;
+    ngx_http_autoindex_entry_t     *entry;
+
+    static u_char  head[] = "<?xml version=\"1.0\"?>" CRLF "<list>" CRLF;
+    static u_char  tail[] = "</list>" CRLF;
+
+    len = sizeof(head) - 1 + sizeof(tail) - 1;
+
+    entry = entries->elts;
+
+    for (i = 0; i < entries->nelts; i++) {
+        entry[i].escape = ngx_escape_html(NULL, entry[i].name.data,
+                                          entry[i].name.len);
+
+        len += sizeof("<directory></directory>" CRLF) - 1
+            + entry[i].name.len + entry[i].escape
+            + sizeof(" mtime=\"1986-12-31T10:00:00Z\"") - 1;
+
+        if (entry[i].file) {
+            len += sizeof(" size=\"\"") - 1 + NGX_OFF_T_LEN;
+        }
+    }
+
+    b = ngx_create_temp_buf(r->pool, len);
+    if (b == NULL) {
+        return NULL;
+    }
+
+    b->last = ngx_cpymem(b->last, head, sizeof(head) - 1);
+
+    for (i = 0; i < entries->nelts; i++) {
+        *b->last++ = '<';
+
+        if (entry[i].dir) {
+            ngx_str_set(&type, "directory");
+
+        } else if (entry[i].file) {
+            ngx_str_set(&type, "file");
+
+        } else {
+            ngx_str_set(&type, "other");
+        }
+
+        b->last = ngx_cpymem(b->last, type.data, type.len);
+
+        b->last = ngx_cpymem(b->last, " mtime=\"", sizeof(" mtime=\"") - 1);
+
+        ngx_gmtime(entry[i].mtime, &tm);
+
+        b->last = ngx_sprintf(b->last, "%4d-%02d-%02dT%02d:%02d:%02dZ",
+                              tm.ngx_tm_year, tm.ngx_tm_mon,
+                              tm.ngx_tm_mday, tm.ngx_tm_hour,
+                              tm.ngx_tm_min, tm.ngx_tm_sec);
+
+        if (entry[i].file) {
+            b->last = ngx_cpymem(b->last, "\" size=\"",
+                                 sizeof("\" size=\"") - 1);
+            b->last = ngx_sprintf(b->last, "%O", entry[i].size);
+        }
+
+        *b->last++ = '"'; *b->last++ = '>';
+
+        if (entry[i].escape) {
+            b->last = (u_char *) ngx_escape_html(b->last, entry[i].name.data,
+                                                 entry[i].name.len);
+        } else {
+            b->last = ngx_cpymem(b->last, entry[i].name.data,
+                                 entry[i].name.len);
+        }
+
+        *b->last++ = '<'; *b->last++ = '/';
+
+        b->last = ngx_cpymem(b->last, type.data, type.len);
+
+        *b->last++ = '>';
+
+        *b->last++ = CR; *b->last++ = LF;
+    }
+
+    b->last = ngx_cpymem(b->last, tail, sizeof(tail) - 1);
+
+    return b;
+}
+
+
+static int ngx_libc_cdecl
+ngx_http_autoindex_cmp_entries(const void *one, const void *two)
+{
+    ngx_http_autoindex_entry_t *first = (ngx_http_autoindex_entry_t *) one;
+    ngx_http_autoindex_entry_t *second = (ngx_http_autoindex_entry_t *) two;
+
+    if (first->dir && !second->dir) {
+        /* move the directories to the start */
+        return -1;
+    }
+
+    if (!first->dir && second->dir) {
+        /* move the directories to the start */
+        return 1;
+    }
+
+    return (int) ngx_strcmp(first->name.data, second->name.data);
+}
+
+
+#if 0
+
+static ngx_buf_t *
+ngx_http_autoindex_alloc(ngx_http_autoindex_ctx_t *ctx, size_t size)
+{
+    ngx_chain_t  *cl;
+
+    if (ctx->buf) {
+
+        if ((size_t) (ctx->buf->end - ctx->buf->last) >= size) {
+            return ctx->buf;
+        }
+
+        ctx->size += ctx->buf->last - ctx->buf->pos;
+    }
+
+    ctx->buf = ngx_create_temp_buf(ctx->pool, ctx->alloc_size);
+    if (ctx->buf == NULL) {
+        return NULL;
+    }
+
+    cl = ngx_alloc_chain_link(ctx->pool);
+    if (cl == NULL) {
+        return NULL;
+    }
+
+    cl->buf = ctx->buf;
+    cl->next = NULL;
+
+    *ctx->last_out = cl;
+    ctx->last_out = &cl->next;
+
+    return ctx->buf;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_autoindex_error(ngx_http_request_t *r, ngx_dir_t *dir, ngx_str_t *name)
+{
+    if (ngx_close_dir(dir) == NGX_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
+                      ngx_close_dir_n " \"%V\" failed", name);
+    }
+
+    return r->header_sent ? NGX_ERROR : NGX_HTTP_INTERNAL_SERVER_ERROR;
+}
+
+
+static void *
+ngx_http_autoindex_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_autoindex_loc_conf_t  *conf;
+
+    conf = ngx_palloc(cf->pool, sizeof(ngx_http_autoindex_loc_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    conf->enable = NGX_CONF_UNSET;
+    conf->format = NGX_CONF_UNSET_UINT;
+    conf->localtime = NGX_CONF_UNSET;
+    conf->exact_size = NGX_CONF_UNSET;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_autoindex_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_autoindex_loc_conf_t *prev = parent;
+    ngx_http_autoindex_loc_conf_t *conf = child;
+
+    ngx_conf_merge_value(conf->enable, prev->enable, 0);
+    ngx_conf_merge_uint_value(conf->format, prev->format,
+                              NGX_HTTP_AUTOINDEX_HTML);
+    ngx_conf_merge_value(conf->localtime, prev->localtime, 0);
+    ngx_conf_merge_value(conf->exact_size, prev->exact_size, 1);
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_autoindex_init(ngx_conf_t *cf)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_autoindex_handler;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_browser_module.c b/nginx/src/http/modules/ngx_http_browser_module.c
new file mode 100644 (file)
index 0000000..f774254
--- /dev/null
@@ -0,0 +1,712 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+/*
+ * The module can check browser versions conforming to the following formats:
+ * X, X.X, X.X.X, and X.X.X.X.  The maximum values of each format may be
+ * 4000, 4000.99, 4000.99.99, and 4000.99.99.99.
+ */
+
+
+#define  NGX_HTTP_MODERN_BROWSER   0
+#define  NGX_HTTP_ANCIENT_BROWSER  1
+
+
+typedef struct {
+    u_char                      browser[12];
+    size_t                      skip;
+    size_t                      add;
+    u_char                      name[12];
+} ngx_http_modern_browser_mask_t;
+
+
+typedef struct {
+    ngx_uint_t                  version;
+    size_t                      skip;
+    size_t                      add;
+    u_char                      name[12];
+} ngx_http_modern_browser_t;
+
+
+typedef struct {
+    ngx_array_t                *modern_browsers;
+    ngx_array_t                *ancient_browsers;
+    ngx_http_variable_value_t  *modern_browser_value;
+    ngx_http_variable_value_t  *ancient_browser_value;
+
+    unsigned                    modern_unlisted_browsers:1;
+    unsigned                    netscape4:1;
+} ngx_http_browser_conf_t;
+
+
+static ngx_int_t ngx_http_msie_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_browser_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+
+static ngx_uint_t ngx_http_browser(ngx_http_request_t *r,
+    ngx_http_browser_conf_t *cf);
+
+static ngx_int_t ngx_http_browser_add_variables(ngx_conf_t *cf);
+static void *ngx_http_browser_create_conf(ngx_conf_t *cf);
+static char *ngx_http_browser_merge_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static int ngx_libc_cdecl ngx_http_modern_browser_sort(const void *one,
+    const void *two);
+static char *ngx_http_modern_browser(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_ancient_browser(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_modern_browser_value(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_ancient_browser_value(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+
+static ngx_command_t  ngx_http_browser_commands[] = {
+
+    { ngx_string("modern_browser"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+      ngx_http_modern_browser,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("ancient_browser"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_ancient_browser,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("modern_browser_value"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_modern_browser_value,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("ancient_browser_value"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_ancient_browser_value,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_browser_module_ctx = {
+    ngx_http_browser_add_variables,        /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_browser_create_conf,          /* create location configuration */
+    ngx_http_browser_merge_conf            /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_browser_module = {
+    NGX_MODULE_V1,
+    &ngx_http_browser_module_ctx,          /* module context */
+    ngx_http_browser_commands,             /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_modern_browser_mask_t  ngx_http_modern_browser_masks[] = {
+
+    /* Opera must be the first browser to check */
+
+    /*
+     * "Opera/7.50 (X11; FreeBSD i386; U)  [en]"
+     * "Mozilla/5.0 (X11; FreeBSD i386; U) Opera 7.50  [en]"
+     * "Mozilla/4.0 (compatible; MSIE 6.0; X11; FreeBSD i386) Opera 7.50  [en]"
+     * "Opera/8.0 (Windows NT 5.1; U; ru)"
+     * "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; en) Opera 8.0"
+     * "Opera/9.01 (X11; FreeBSD 6 i386; U; en)"
+     */
+
+    { "opera",
+      0,
+      sizeof("Opera ") - 1,
+      "Opera"},
+
+    /* "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)" */
+
+    { "msie",
+      sizeof("Mozilla/4.0 (compatible; ") - 1,
+      sizeof("MSIE ") - 1,
+      "MSIE "},
+
+    /*
+     * "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.0.0) Gecko/20020610"
+     * "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU; rv:1.5) Gecko/20031006"
+     * "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU; rv:1.6) Gecko/20040206
+     *              Firefox/0.8"
+     * "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU; rv:1.7.8)
+     *              Gecko/20050511 Firefox/1.0.4"
+     * "Mozilla/5.0 (X11; U; FreeBSD i386; en-US; rv:1.8.0.5) Gecko/20060729
+     *              Firefox/1.5.0.5"
+     */
+
+    { "gecko",
+      sizeof("Mozilla/5.0 (") - 1,
+      sizeof("rv:") - 1,
+      "rv:"},
+
+    /*
+     * "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ru-ru) AppleWebKit/125.2
+     *              (KHTML, like Gecko) Safari/125.7"
+     * "Mozilla/5.0 (SymbianOS/9.1; U; en-us) AppleWebKit/413
+     *              (KHTML, like Gecko) Safari/413"
+     * "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418
+     *              (KHTML, like Gecko) Safari/417.9.3"
+     * "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ru-ru) AppleWebKit/418.8
+     *              (KHTML, like Gecko) Safari/419.3"
+     */
+
+    { "safari",
+      sizeof("Mozilla/5.0 (") - 1,
+      sizeof("Safari/") - 1,
+      "Safari/"},
+
+    /*
+     * "Mozilla/5.0 (compatible; Konqueror/3.1; Linux)"
+     * "Mozilla/5.0 (compatible; Konqueror/3.4; Linux) KHTML/3.4.2 (like Gecko)"
+     * "Mozilla/5.0 (compatible; Konqueror/3.5; FreeBSD) KHTML/3.5.1
+     *              (like Gecko)"
+     */
+
+    { "konqueror",
+      sizeof("Mozilla/5.0 (compatible; ") - 1,
+      sizeof("Konqueror/") - 1,
+      "Konqueror/"},
+
+    { "", 0, 0, "" }
+
+};
+
+
+static ngx_http_variable_t  ngx_http_browser_vars[] = {
+
+    { ngx_string("msie"), NULL, ngx_http_msie_variable,
+      0, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("modern_browser"), NULL, ngx_http_browser_variable,
+      NGX_HTTP_MODERN_BROWSER, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ancient_browser"), NULL, ngx_http_browser_variable,
+      NGX_HTTP_ANCIENT_BROWSER, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+      ngx_http_null_variable
+};
+
+
+static ngx_int_t
+ngx_http_browser_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+    uintptr_t data)
+{
+    ngx_uint_t                rc;
+    ngx_http_browser_conf_t  *cf;
+
+    cf = ngx_http_get_module_loc_conf(r, ngx_http_browser_module);
+
+    rc = ngx_http_browser(r, cf);
+
+    if (data == NGX_HTTP_MODERN_BROWSER && rc == NGX_HTTP_MODERN_BROWSER) {
+        *v = *cf->modern_browser_value;
+        return NGX_OK;
+    }
+
+    if (data == NGX_HTTP_ANCIENT_BROWSER && rc == NGX_HTTP_ANCIENT_BROWSER) {
+        *v = *cf->ancient_browser_value;
+        return NGX_OK;
+    }
+
+    *v = ngx_http_variable_null_value;
+    return NGX_OK;
+}
+
+
+static ngx_uint_t
+ngx_http_browser(ngx_http_request_t *r, ngx_http_browser_conf_t *cf)
+{
+    size_t                      len;
+    u_char                     *name, *ua, *last, c;
+    ngx_str_t                  *ancient;
+    ngx_uint_t                  i, version, ver, scale;
+    ngx_http_modern_browser_t  *modern;
+
+    if (r->headers_in.user_agent == NULL) {
+        if (cf->modern_unlisted_browsers) {
+            return NGX_HTTP_MODERN_BROWSER;
+        }
+
+        return NGX_HTTP_ANCIENT_BROWSER;
+    }
+
+    ua = r->headers_in.user_agent->value.data;
+    len = r->headers_in.user_agent->value.len;
+    last = ua + len;
+
+    if (cf->modern_browsers) {
+        modern = cf->modern_browsers->elts;
+
+        for (i = 0; i < cf->modern_browsers->nelts; i++) {
+            name = ua + modern[i].skip;
+
+            if (name >= last) {
+                continue;
+            }
+
+            name = (u_char *) ngx_strstr(name, modern[i].name);
+
+            if (name == NULL) {
+                continue;
+            }
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "browser: \"%s\"", name);
+
+            name += modern[i].add;
+
+            if (name >= last) {
+                continue;
+            }
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "version: \"%ui\" \"%s\"", modern[i].version, name);
+
+            version = 0;
+            ver = 0;
+            scale = 1000000;
+
+            while (name < last) {
+
+                c = *name++;
+
+                if (c >= '0' && c <= '9') {
+                    ver = ver * 10 + (c - '0');
+                    continue;
+                }
+
+                if (c == '.') {
+                    version += ver * scale;
+
+                    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                                   "version: \"%ui\" \"%ui\"",
+                                   modern[i].version, version);
+
+                    if (version > modern[i].version) {
+                        return NGX_HTTP_MODERN_BROWSER;
+                    }
+
+                    ver = 0;
+                    scale /= 100;
+                    continue;
+                }
+
+                break;
+            }
+
+            version += ver * scale;
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "version: \"%ui\" \"%ui\"",
+                           modern[i].version, version);
+
+            if (version >= modern[i].version) {
+                return NGX_HTTP_MODERN_BROWSER;
+            }
+
+            return NGX_HTTP_ANCIENT_BROWSER;
+        }
+
+        if (!cf->modern_unlisted_browsers) {
+            return NGX_HTTP_ANCIENT_BROWSER;
+        }
+    }
+
+    if (cf->netscape4) {
+        if (len > sizeof("Mozilla/4.72 ") - 1
+            && ngx_strncmp(ua, "Mozilla/", sizeof("Mozilla/") - 1) == 0
+            && ua[8] > '0' && ua[8] < '5')
+        {
+            return NGX_HTTP_ANCIENT_BROWSER;
+        }
+    }
+
+    if (cf->ancient_browsers) {
+        ancient = cf->ancient_browsers->elts;
+
+        for (i = 0; i < cf->ancient_browsers->nelts; i++) {
+            if (len >= ancient[i].len
+                && ngx_strstr(ua, ancient[i].data) != NULL)
+            {
+                return NGX_HTTP_ANCIENT_BROWSER;
+            }
+        }
+    }
+
+    if (cf->modern_unlisted_browsers) {
+        return NGX_HTTP_MODERN_BROWSER;
+    }
+
+    return NGX_HTTP_ANCIENT_BROWSER;
+}
+
+
+static ngx_int_t
+ngx_http_msie_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+    uintptr_t data)
+{
+    if (r->headers_in.msie) {
+        *v = ngx_http_variable_true_value;
+        return NGX_OK;
+    }
+
+    *v = ngx_http_variable_null_value;
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_browser_add_variables(ngx_conf_t *cf)
+{
+    ngx_http_variable_t  *var, *v;
+
+    for (v = ngx_http_browser_vars; v->name.len; v++) {
+
+        var = ngx_http_add_variable(cf, &v->name, v->flags);
+        if (var == NULL) {
+            return NGX_ERROR;
+        }
+
+        var->get_handler = v->get_handler;
+        var->data = v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_browser_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_browser_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_browser_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->modern_browsers = NULL;
+     *     conf->ancient_browsers = NULL;
+     *     conf->modern_browser_value = NULL;
+     *     conf->ancient_browser_value = NULL;
+     *
+     *     conf->modern_unlisted_browsers = 0;
+     *     conf->netscape4 = 0;
+     */
+
+    return conf;
+}
+
+
+static char *
+ngx_http_browser_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_browser_conf_t *prev = parent;
+    ngx_http_browser_conf_t *conf = child;
+
+    ngx_uint_t                  i, n;
+    ngx_http_modern_browser_t  *browsers, *opera;
+
+    /*
+     * At the merge the skip field is used to store the browser slot,
+     * it will be used in sorting and then will overwritten
+     * with a real skip value.  The zero value means Opera.
+     */
+
+    if (conf->modern_browsers == NULL && conf->modern_unlisted_browsers == 0) {
+        conf->modern_browsers = prev->modern_browsers;
+        conf->modern_unlisted_browsers = prev->modern_unlisted_browsers;
+
+    } else if (conf->modern_browsers != NULL) {
+        browsers = conf->modern_browsers->elts;
+
+        for (i = 0; i < conf->modern_browsers->nelts; i++) {
+            if (browsers[i].skip == 0) {
+                goto found;
+            }
+        }
+
+        /*
+         * Opera may contain MSIE string, so if Opera was not enumerated
+         * as modern browsers, then add it and set a unreachable version
+         */
+
+        opera = ngx_array_push(conf->modern_browsers);
+        if (opera == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        opera->skip = 0;
+        opera->version = 4001000000U;
+
+        browsers = conf->modern_browsers->elts;
+
+found:
+
+        ngx_qsort(browsers, (size_t) conf->modern_browsers->nelts,
+                  sizeof(ngx_http_modern_browser_t),
+                  ngx_http_modern_browser_sort);
+
+        for (i = 0; i < conf->modern_browsers->nelts; i++) {
+             n = browsers[i].skip;
+
+             browsers[i].skip = ngx_http_modern_browser_masks[n].skip;
+             browsers[i].add = ngx_http_modern_browser_masks[n].add;
+             (void) ngx_cpystrn(browsers[i].name,
+                                ngx_http_modern_browser_masks[n].name, 12);
+        }
+    }
+
+    if (conf->ancient_browsers == NULL && conf->netscape4 == 0) {
+        conf->ancient_browsers = prev->ancient_browsers;
+        conf->netscape4 = prev->netscape4;
+    }
+
+    if (conf->modern_browser_value == NULL) {
+        conf->modern_browser_value = prev->modern_browser_value;
+    }
+
+    if (conf->modern_browser_value == NULL) {
+        conf->modern_browser_value = &ngx_http_variable_true_value;
+    }
+
+    if (conf->ancient_browser_value == NULL) {
+        conf->ancient_browser_value = prev->ancient_browser_value;
+    }
+
+    if (conf->ancient_browser_value == NULL) {
+        conf->ancient_browser_value = &ngx_http_variable_true_value;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static int ngx_libc_cdecl
+ngx_http_modern_browser_sort(const void *one, const void *two)
+{
+    ngx_http_modern_browser_t *first = (ngx_http_modern_browser_t *) one;
+    ngx_http_modern_browser_t *second = (ngx_http_modern_browser_t *) two;
+
+    return (first->skip - second->skip);
+}
+
+
+static char *
+ngx_http_modern_browser(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_browser_conf_t *bcf = conf;
+
+    u_char                           c;
+    ngx_str_t                       *value;
+    ngx_uint_t                       i, n, version, ver, scale;
+    ngx_http_modern_browser_t       *browser;
+    ngx_http_modern_browser_mask_t  *mask;
+
+    value = cf->args->elts;
+
+    if (cf->args->nelts == 2) {
+        if (ngx_strcmp(value[1].data, "unlisted") == 0) {
+            bcf->modern_unlisted_browsers = 1;
+            return NGX_CONF_OK;
+        }
+
+        return NGX_CONF_ERROR;
+    }
+
+    if (bcf->modern_browsers == NULL) {
+        bcf->modern_browsers = ngx_array_create(cf->pool, 5,
+                                            sizeof(ngx_http_modern_browser_t));
+        if (bcf->modern_browsers == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    browser = ngx_array_push(bcf->modern_browsers);
+    if (browser == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    mask = ngx_http_modern_browser_masks;
+
+    for (n = 0; mask[n].browser[0] != '\0'; n++) {
+        if (ngx_strcasecmp(mask[n].browser, value[1].data) == 0) {
+            goto found;
+        }
+    }
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "unknown browser name \"%V\"", &value[1]);
+
+    return NGX_CONF_ERROR;
+
+found:
+
+    /*
+     * at this stage the skip field is used to store the browser slot,
+     * it will be used in sorting in merge stage and then will overwritten
+     * with a real value
+     */
+
+    browser->skip = n;
+
+    version = 0;
+    ver = 0;
+    scale = 1000000;
+
+    for (i = 0; i < value[2].len; i++) {
+
+        c = value[2].data[i];
+
+        if (c >= '0' && c <= '9') {
+            ver = ver * 10 + (c - '0');
+            continue;
+        }
+
+        if (c == '.') {
+            version += ver * scale;
+            ver = 0;
+            scale /= 100;
+            continue;
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid browser version \"%V\"", &value[2]);
+
+        return NGX_CONF_ERROR;
+    }
+
+    version += ver * scale;
+
+    browser->version = version;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_ancient_browser(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_browser_conf_t *bcf = conf;
+
+    ngx_str_t   *value, *browser;
+    ngx_uint_t   i;
+
+    value = cf->args->elts;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+        if (ngx_strcmp(value[i].data, "netscape4") == 0) {
+            bcf->netscape4 = 1;
+            continue;
+        }
+
+        if (bcf->ancient_browsers == NULL) {
+            bcf->ancient_browsers = ngx_array_create(cf->pool, 4,
+                                                     sizeof(ngx_str_t));
+            if (bcf->ancient_browsers == NULL) {
+                return NGX_CONF_ERROR;
+            }
+        }
+
+        browser = ngx_array_push(bcf->ancient_browsers);
+        if (browser == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        *browser = value[i];
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_modern_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_browser_conf_t *bcf = conf;
+
+    ngx_str_t  *value;
+
+    bcf->modern_browser_value = ngx_palloc(cf->pool,
+                                           sizeof(ngx_http_variable_value_t));
+    if (bcf->modern_browser_value == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    value = cf->args->elts;
+
+    bcf->modern_browser_value->len = value[1].len;
+    bcf->modern_browser_value->valid = 1;
+    bcf->modern_browser_value->no_cacheable = 0;
+    bcf->modern_browser_value->not_found = 0;
+    bcf->modern_browser_value->data = value[1].data;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_ancient_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_browser_conf_t *bcf = conf;
+
+    ngx_str_t  *value;
+
+    bcf->ancient_browser_value = ngx_palloc(cf->pool,
+                                            sizeof(ngx_http_variable_value_t));
+    if (bcf->ancient_browser_value == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    value = cf->args->elts;
+
+    bcf->ancient_browser_value->len = value[1].len;
+    bcf->ancient_browser_value->valid = 1;
+    bcf->ancient_browser_value->no_cacheable = 0;
+    bcf->ancient_browser_value->not_found = 0;
+    bcf->ancient_browser_value->data = value[1].data;
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_charset_filter_module.c b/nginx/src/http/modules/ngx_http_charset_filter_module.c
new file mode 100644 (file)
index 0000000..e52b96e
--- /dev/null
@@ -0,0 +1,1685 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_CHARSET_OFF    -2
+#define NGX_HTTP_NO_CHARSET     -3
+#define NGX_HTTP_CHARSET_VAR    0x10000
+
+/* 1 byte length and up to 3 bytes for the UTF-8 encoding of the UCS-2 */
+#define NGX_UTF_LEN             4
+
+#define NGX_HTML_ENTITY_LEN     (sizeof("&#1114111;") - 1)
+
+
+typedef struct {
+    u_char                    **tables;
+    ngx_str_t                   name;
+
+    unsigned                    length:16;
+    unsigned                    utf8:1;
+} ngx_http_charset_t;
+
+
+typedef struct {
+    ngx_int_t                   src;
+    ngx_int_t                   dst;
+} ngx_http_charset_recode_t;
+
+
+typedef struct {
+    ngx_int_t                   src;
+    ngx_int_t                   dst;
+    u_char                     *src2dst;
+    u_char                     *dst2src;
+} ngx_http_charset_tables_t;
+
+
+typedef struct {
+    ngx_array_t                 charsets;       /* ngx_http_charset_t */
+    ngx_array_t                 tables;         /* ngx_http_charset_tables_t */
+    ngx_array_t                 recodes;        /* ngx_http_charset_recode_t */
+} ngx_http_charset_main_conf_t;
+
+
+typedef struct {
+    ngx_int_t                   charset;
+    ngx_int_t                   source_charset;
+    ngx_flag_t                  override_charset;
+
+    ngx_hash_t                  types;
+    ngx_array_t                *types_keys;
+} ngx_http_charset_loc_conf_t;
+
+
+typedef struct {
+    u_char                     *table;
+    ngx_int_t                   charset;
+    ngx_str_t                   charset_name;
+
+    ngx_chain_t                *busy;
+    ngx_chain_t                *free_bufs;
+    ngx_chain_t                *free_buffers;
+
+    size_t                      saved_len;
+    u_char                      saved[NGX_UTF_LEN];
+
+    unsigned                    length:16;
+    unsigned                    from_utf8:1;
+    unsigned                    to_utf8:1;
+} ngx_http_charset_ctx_t;
+
+
+typedef struct {
+    ngx_http_charset_tables_t  *table;
+    ngx_http_charset_t         *charset;
+    ngx_uint_t                  characters;
+} ngx_http_charset_conf_ctx_t;
+
+
+static ngx_int_t ngx_http_destination_charset(ngx_http_request_t *r,
+    ngx_str_t *name);
+static ngx_int_t ngx_http_main_request_charset(ngx_http_request_t *r,
+    ngx_str_t *name);
+static ngx_int_t ngx_http_source_charset(ngx_http_request_t *r,
+    ngx_str_t *name);
+static ngx_int_t ngx_http_get_charset(ngx_http_request_t *r, ngx_str_t *name);
+static ngx_inline void ngx_http_set_charset(ngx_http_request_t *r,
+    ngx_str_t *charset);
+static ngx_int_t ngx_http_charset_ctx(ngx_http_request_t *r,
+    ngx_http_charset_t *charsets, ngx_int_t charset, ngx_int_t source_charset);
+static ngx_uint_t ngx_http_charset_recode(ngx_buf_t *b, u_char *table);
+static ngx_chain_t *ngx_http_charset_recode_from_utf8(ngx_pool_t *pool,
+    ngx_buf_t *buf, ngx_http_charset_ctx_t *ctx);
+static ngx_chain_t *ngx_http_charset_recode_to_utf8(ngx_pool_t *pool,
+    ngx_buf_t *buf, ngx_http_charset_ctx_t *ctx);
+
+static ngx_chain_t *ngx_http_charset_get_buf(ngx_pool_t *pool,
+    ngx_http_charset_ctx_t *ctx);
+static ngx_chain_t *ngx_http_charset_get_buffer(ngx_pool_t *pool,
+    ngx_http_charset_ctx_t *ctx, size_t size);
+
+static char *ngx_http_charset_map_block(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_charset_map(ngx_conf_t *cf, ngx_command_t *dummy,
+    void *conf);
+
+static char *ngx_http_set_charset_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static ngx_int_t ngx_http_add_charset(ngx_array_t *charsets, ngx_str_t *name);
+
+static void *ngx_http_charset_create_main_conf(ngx_conf_t *cf);
+static void *ngx_http_charset_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_charset_merge_loc_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+static ngx_int_t ngx_http_charset_postconfiguration(ngx_conf_t *cf);
+
+
+static ngx_str_t  ngx_http_charset_default_types[] = {
+    ngx_string("text/html"),
+    ngx_string("text/xml"),
+    ngx_string("text/plain"),
+    ngx_string("text/vnd.wap.wml"),
+    ngx_string("application/javascript"),
+    ngx_string("application/rss+xml"),
+    ngx_null_string
+};
+
+
+static ngx_command_t  ngx_http_charset_filter_commands[] = {
+
+    { ngx_string("charset"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF
+                        |NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
+      ngx_http_set_charset_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_charset_loc_conf_t, charset),
+      NULL },
+
+    { ngx_string("source_charset"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF
+                        |NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
+      ngx_http_set_charset_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_charset_loc_conf_t, source_charset),
+      NULL },
+
+    { ngx_string("override_charset"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF
+                        |NGX_HTTP_LIF_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_charset_loc_conf_t, override_charset),
+      NULL },
+
+    { ngx_string("charset_types"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_types_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_charset_loc_conf_t, types_keys),
+      &ngx_http_charset_default_types[0] },
+
+    { ngx_string("charset_map"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE2,
+      ngx_http_charset_map_block,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_charset_filter_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_charset_postconfiguration,    /* postconfiguration */
+
+    ngx_http_charset_create_main_conf,     /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_charset_create_loc_conf,      /* create location configuration */
+    ngx_http_charset_merge_loc_conf        /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_charset_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_charset_filter_module_ctx,   /* module context */
+    ngx_http_charset_filter_commands,      /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_charset_header_filter(ngx_http_request_t *r)
+{
+    ngx_int_t                      charset, source_charset;
+    ngx_str_t                      dst, src;
+    ngx_http_charset_t            *charsets;
+    ngx_http_charset_main_conf_t  *mcf;
+
+    if (r == r->main) {
+        charset = ngx_http_destination_charset(r, &dst);
+
+    } else {
+        charset = ngx_http_main_request_charset(r, &dst);
+    }
+
+    if (charset == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if (charset == NGX_DECLINED) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    /* charset: charset index or NGX_HTTP_NO_CHARSET */
+
+    source_charset = ngx_http_source_charset(r, &src);
+
+    if (source_charset == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    /*
+     * source_charset: charset index, NGX_HTTP_NO_CHARSET,
+     *                 or NGX_HTTP_CHARSET_OFF
+     */
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "charset: \"%V\" > \"%V\"", &src, &dst);
+
+    if (source_charset == NGX_HTTP_CHARSET_OFF) {
+        ngx_http_set_charset(r, &dst);
+
+        return ngx_http_next_header_filter(r);
+    }
+
+    if (charset == NGX_HTTP_NO_CHARSET
+        || source_charset == NGX_HTTP_NO_CHARSET)
+    {
+        if (source_charset != charset
+            || ngx_strncasecmp(dst.data, src.data, dst.len) != 0)
+        {
+            goto no_charset_map;
+        }
+
+        ngx_http_set_charset(r, &dst);
+
+        return ngx_http_next_header_filter(r);
+    }
+
+    if (source_charset == charset) {
+        r->headers_out.content_type.len = r->headers_out.content_type_len;
+
+        ngx_http_set_charset(r, &dst);
+
+        return ngx_http_next_header_filter(r);
+    }
+
+    /* source_charset != charset */
+
+    if (r->headers_out.content_encoding
+        && r->headers_out.content_encoding->value.len)
+    {
+        return ngx_http_next_header_filter(r);
+    }
+
+    mcf = ngx_http_get_module_main_conf(r, ngx_http_charset_filter_module);
+    charsets = mcf->charsets.elts;
+
+    if (charsets[source_charset].tables == NULL
+        || charsets[source_charset].tables[charset] == NULL)
+    {
+        goto no_charset_map;
+    }
+
+    r->headers_out.content_type.len = r->headers_out.content_type_len;
+
+    ngx_http_set_charset(r, &dst);
+
+    return ngx_http_charset_ctx(r, charsets, charset, source_charset);
+
+no_charset_map:
+
+    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                  "no \"charset_map\" between the charsets \"%V\" and \"%V\"",
+                  &src, &dst);
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_destination_charset(ngx_http_request_t *r, ngx_str_t *name)
+{
+    ngx_int_t                      charset;
+    ngx_http_charset_t            *charsets;
+    ngx_http_variable_value_t     *vv;
+    ngx_http_charset_loc_conf_t   *mlcf;
+    ngx_http_charset_main_conf_t  *mcf;
+
+    if (r->headers_out.content_type.len == 0) {
+        return NGX_DECLINED;
+    }
+
+    if (r->headers_out.override_charset
+        && r->headers_out.override_charset->len)
+    {
+        *name = *r->headers_out.override_charset;
+
+        charset = ngx_http_get_charset(r, name);
+
+        if (charset != NGX_HTTP_NO_CHARSET) {
+            return charset;
+        }
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "unknown charset \"%V\" to override", name);
+
+        return NGX_DECLINED;
+    }
+
+    mlcf = ngx_http_get_module_loc_conf(r, ngx_http_charset_filter_module);
+    charset = mlcf->charset;
+
+    if (charset == NGX_HTTP_CHARSET_OFF) {
+        return NGX_DECLINED;
+    }
+
+    if (r->headers_out.charset.len) {
+        if (mlcf->override_charset == 0) {
+            return NGX_DECLINED;
+        }
+
+    } else {
+        if (ngx_http_test_content_type(r, &mlcf->types) == NULL) {
+            return NGX_DECLINED;
+        }
+    }
+
+    if (charset < NGX_HTTP_CHARSET_VAR) {
+        mcf = ngx_http_get_module_main_conf(r, ngx_http_charset_filter_module);
+        charsets = mcf->charsets.elts;
+        *name = charsets[charset].name;
+        return charset;
+    }
+
+    vv = ngx_http_get_indexed_variable(r, charset - NGX_HTTP_CHARSET_VAR);
+
+    if (vv == NULL || vv->not_found) {
+        return NGX_ERROR;
+    }
+
+    name->len = vv->len;
+    name->data = vv->data;
+
+    return ngx_http_get_charset(r, name);
+}
+
+
+static ngx_int_t
+ngx_http_main_request_charset(ngx_http_request_t *r, ngx_str_t *src)
+{
+    ngx_int_t                charset;
+    ngx_str_t               *main_charset;
+    ngx_http_charset_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r->main, ngx_http_charset_filter_module);
+
+    if (ctx) {
+        *src = ctx->charset_name;
+        return ctx->charset;
+    }
+
+    main_charset = &r->main->headers_out.charset;
+
+    if (main_charset->len == 0) {
+        return NGX_DECLINED;
+    }
+
+    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_charset_ctx_t));
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_http_set_ctx(r->main, ctx, ngx_http_charset_filter_module);
+
+    charset = ngx_http_get_charset(r, main_charset);
+
+    ctx->charset = charset;
+    ctx->charset_name = *main_charset;
+    *src = *main_charset;
+
+    return charset;
+}
+
+
+static ngx_int_t
+ngx_http_source_charset(ngx_http_request_t *r, ngx_str_t *name)
+{
+    ngx_int_t                      charset;
+    ngx_http_charset_t            *charsets;
+    ngx_http_variable_value_t     *vv;
+    ngx_http_charset_loc_conf_t   *lcf;
+    ngx_http_charset_main_conf_t  *mcf;
+
+    if (r->headers_out.charset.len) {
+        *name = r->headers_out.charset;
+        return ngx_http_get_charset(r, name);
+    }
+
+    lcf = ngx_http_get_module_loc_conf(r, ngx_http_charset_filter_module);
+
+    charset = lcf->source_charset;
+
+    if (charset == NGX_HTTP_CHARSET_OFF) {
+        name->len = 0;
+        return charset;
+    }
+
+    if (charset < NGX_HTTP_CHARSET_VAR) {
+        mcf = ngx_http_get_module_main_conf(r, ngx_http_charset_filter_module);
+        charsets = mcf->charsets.elts;
+        *name = charsets[charset].name;
+        return charset;
+    }
+
+    vv = ngx_http_get_indexed_variable(r, charset - NGX_HTTP_CHARSET_VAR);
+
+    if (vv == NULL || vv->not_found) {
+        return NGX_ERROR;
+    }
+
+    name->len = vv->len;
+    name->data = vv->data;
+
+    return ngx_http_get_charset(r, name);
+}
+
+
+static ngx_int_t
+ngx_http_get_charset(ngx_http_request_t *r, ngx_str_t *name)
+{
+    ngx_uint_t                     i, n;
+    ngx_http_charset_t            *charset;
+    ngx_http_charset_main_conf_t  *mcf;
+
+    mcf = ngx_http_get_module_main_conf(r, ngx_http_charset_filter_module);
+
+    charset = mcf->charsets.elts;
+    n = mcf->charsets.nelts;
+
+    for (i = 0; i < n; i++) {
+        if (charset[i].name.len != name->len) {
+            continue;
+        }
+
+        if (ngx_strncasecmp(charset[i].name.data, name->data, name->len) == 0) {
+            return i;
+        }
+    }
+
+    return NGX_HTTP_NO_CHARSET;
+}
+
+
+static ngx_inline void
+ngx_http_set_charset(ngx_http_request_t *r, ngx_str_t *charset)
+{
+    if (r != r->main) {
+        return;
+    }
+
+    if (r->headers_out.status == NGX_HTTP_MOVED_PERMANENTLY
+        || r->headers_out.status == NGX_HTTP_MOVED_TEMPORARILY)
+    {
+        /*
+         * do not set charset for the redirect because NN 4.x
+         * use this charset instead of the next page charset
+         */
+
+        r->headers_out.charset.len = 0;
+        return;
+    }
+
+    r->headers_out.charset = *charset;
+}
+
+
+static ngx_int_t
+ngx_http_charset_ctx(ngx_http_request_t *r, ngx_http_charset_t *charsets,
+    ngx_int_t charset, ngx_int_t source_charset)
+{
+    ngx_http_charset_ctx_t  *ctx;
+
+    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_charset_ctx_t));
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_http_set_ctx(r, ctx, ngx_http_charset_filter_module);
+
+    ctx->table = charsets[source_charset].tables[charset];
+    ctx->charset = charset;
+    ctx->charset_name = charsets[charset].name;
+    ctx->length = charsets[charset].length;
+    ctx->from_utf8 = charsets[source_charset].utf8;
+    ctx->to_utf8 = charsets[charset].utf8;
+
+    r->filter_need_in_memory = 1;
+
+    if ((ctx->to_utf8 || ctx->from_utf8) && r == r->main) {
+        ngx_http_clear_content_length(r);
+
+    } else {
+        r->filter_need_temporary = 1;
+    }
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_charset_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    ngx_int_t                rc;
+    ngx_buf_t               *b;
+    ngx_chain_t             *cl, *out, **ll;
+    ngx_http_charset_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_charset_filter_module);
+
+    if (ctx == NULL || ctx->table == NULL) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    if ((ctx->to_utf8 || ctx->from_utf8) || ctx->busy) {
+
+        out = NULL;
+        ll = &out;
+
+        for (cl = in; cl; cl = cl->next) {
+            b = cl->buf;
+
+            if (ngx_buf_size(b) == 0) {
+
+                *ll = ngx_alloc_chain_link(r->pool);
+                if (*ll == NULL) {
+                    return NGX_ERROR;
+                }
+
+                (*ll)->buf = b;
+                (*ll)->next = NULL;
+
+                ll = &(*ll)->next;
+
+                continue;
+            }
+
+            if (ctx->to_utf8) {
+                *ll = ngx_http_charset_recode_to_utf8(r->pool, b, ctx);
+
+            } else {
+                *ll = ngx_http_charset_recode_from_utf8(r->pool, b, ctx);
+            }
+
+            if (*ll == NULL) {
+                return NGX_ERROR;
+            }
+
+            while (*ll) {
+                ll = &(*ll)->next;
+            }
+        }
+
+        rc = ngx_http_next_body_filter(r, out);
+
+        if (out) {
+            if (ctx->busy == NULL) {
+                ctx->busy = out;
+
+            } else {
+                for (cl = ctx->busy; cl->next; cl = cl->next) { /* void */ }
+                cl->next = out;
+            }
+        }
+
+        while (ctx->busy) {
+
+            cl = ctx->busy;
+            b = cl->buf;
+
+            if (ngx_buf_size(b) != 0) {
+                break;
+            }
+
+            ctx->busy = cl->next;
+
+            if (b->tag != (ngx_buf_tag_t) &ngx_http_charset_filter_module) {
+                continue;
+            }
+
+            if (b->shadow) {
+                b->shadow->pos = b->shadow->last;
+            }
+
+            if (b->pos) {
+                cl->next = ctx->free_buffers;
+                ctx->free_buffers = cl;
+                continue;
+            }
+
+            cl->next = ctx->free_bufs;
+            ctx->free_bufs = cl;
+        }
+
+        return rc;
+    }
+
+    for (cl = in; cl; cl = cl->next) {
+        (void) ngx_http_charset_recode(cl->buf, ctx->table);
+    }
+
+    return ngx_http_next_body_filter(r, in);
+}
+
+
+static ngx_uint_t
+ngx_http_charset_recode(ngx_buf_t *b, u_char *table)
+{
+    u_char  *p, *last;
+
+    last = b->last;
+
+    for (p = b->pos; p < last; p++) {
+
+        if (*p != table[*p]) {
+            goto recode;
+        }
+    }
+
+    return 0;
+
+recode:
+
+    do {
+        if (*p != table[*p]) {
+            *p = table[*p];
+        }
+
+        p++;
+
+    } while (p < last);
+
+    b->in_file = 0;
+
+    return 1;
+}
+
+
+static ngx_chain_t *
+ngx_http_charset_recode_from_utf8(ngx_pool_t *pool, ngx_buf_t *buf,
+    ngx_http_charset_ctx_t *ctx)
+{
+    size_t        len, size;
+    u_char        c, *p, *src, *dst, *saved, **table;
+    uint32_t      n;
+    ngx_buf_t    *b;
+    ngx_uint_t    i;
+    ngx_chain_t  *out, *cl, **ll;
+
+    src = buf->pos;
+
+    if (ctx->saved_len == 0) {
+
+        for ( /* void */ ; src < buf->last; src++) {
+
+            if (*src < 0x80) {
+                continue;
+            }
+
+            len = src - buf->pos;
+
+            if (len > 512) {
+                out = ngx_http_charset_get_buf(pool, ctx);
+                if (out == NULL) {
+                    return NULL;
+                }
+
+                b = out->buf;
+
+                b->temporary = buf->temporary;
+                b->memory = buf->memory;
+                b->mmap = buf->mmap;
+                b->flush = buf->flush;
+
+                b->pos = buf->pos;
+                b->last = src;
+
+                out->buf = b;
+                out->next = NULL;
+
+                size = buf->last - src;
+
+                saved = src;
+                n = ngx_utf8_decode(&saved, size);
+
+                if (n == 0xfffffffe) {
+                    /* incomplete UTF-8 symbol */
+
+                    ngx_memcpy(ctx->saved, src, size);
+                    ctx->saved_len = size;
+
+                    b->shadow = buf;
+
+                    return out;
+                }
+
+            } else {
+                out = NULL;
+                size = len + buf->last - src;
+                src = buf->pos;
+            }
+
+            if (size < NGX_HTML_ENTITY_LEN) {
+                size += NGX_HTML_ENTITY_LEN;
+            }
+
+            cl = ngx_http_charset_get_buffer(pool, ctx, size);
+            if (cl == NULL) {
+                return NULL;
+            }
+
+            if (out) {
+                out->next = cl;
+
+            } else {
+                out = cl;
+            }
+
+            b = cl->buf;
+            dst = b->pos;
+
+            goto recode;
+        }
+
+        out = ngx_alloc_chain_link(pool);
+        if (out == NULL) {
+            return NULL;
+        }
+
+        out->buf = buf;
+        out->next = NULL;
+
+        return out;
+    }
+
+    /* process incomplete UTF sequence from previous buffer */
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pool->log, 0,
+                   "http charset utf saved: %z", ctx->saved_len);
+
+    p = src;
+
+    for (i = ctx->saved_len; i < NGX_UTF_LEN; i++) {
+        ctx->saved[i] = *p++;
+
+        if (p == buf->last) {
+            break;
+        }
+    }
+
+    saved = ctx->saved;
+    n = ngx_utf8_decode(&saved, i);
+
+    c = '\0';
+
+    if (n < 0x10000) {
+        table = (u_char **) ctx->table;
+        p = table[n >> 8];
+
+        if (p) {
+            c = p[n & 0xff];
+        }
+
+    } else if (n == 0xfffffffe) {
+
+        /* incomplete UTF-8 symbol */
+
+        if (i < NGX_UTF_LEN) {
+            out = ngx_http_charset_get_buf(pool, ctx);
+            if (out == NULL) {
+                return NULL;
+            }
+
+            b = out->buf;
+
+            b->pos = buf->pos;
+            b->last = buf->last;
+            b->sync = 1;
+            b->shadow = buf;
+
+            ngx_memcpy(&ctx->saved[ctx->saved_len], src, i);
+            ctx->saved_len += i;
+
+            return out;
+        }
+    }
+
+    size = buf->last - buf->pos;
+
+    if (size < NGX_HTML_ENTITY_LEN) {
+        size += NGX_HTML_ENTITY_LEN;
+    }
+
+    cl = ngx_http_charset_get_buffer(pool, ctx, size);
+    if (cl == NULL) {
+        return NULL;
+    }
+
+    out = cl;
+
+    b = cl->buf;
+    dst = b->pos;
+
+    if (c) {
+        *dst++ = c;
+
+    } else if (n == 0xfffffffe) {
+        *dst++ = '?';
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pool->log, 0,
+                       "http charset invalid utf 0");
+
+        saved = &ctx->saved[NGX_UTF_LEN];
+
+    } else if (n > 0x10ffff) {
+        *dst++ = '?';
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pool->log, 0,
+                       "http charset invalid utf 1");
+
+    } else {
+        dst = ngx_sprintf(dst, "&#%uD;", n);
+    }
+
+    src += (saved - ctx->saved) - ctx->saved_len;
+    ctx->saved_len = 0;
+
+recode:
+
+    ll = &cl->next;
+
+    table = (u_char **) ctx->table;
+
+    while (src < buf->last) {
+
+        if ((size_t) (b->end - dst) < NGX_HTML_ENTITY_LEN) {
+            b->last = dst;
+
+            size = buf->last - src + NGX_HTML_ENTITY_LEN;
+
+            cl = ngx_http_charset_get_buffer(pool, ctx, size);
+            if (cl == NULL) {
+                return NULL;
+            }
+
+            *ll = cl;
+            ll = &cl->next;
+
+            b = cl->buf;
+            dst = b->pos;
+        }
+
+        if (*src < 0x80) {
+            *dst++ = *src++;
+            continue;
+        }
+
+        len = buf->last - src;
+
+        n = ngx_utf8_decode(&src, len);
+
+        if (n < 0x10000) {
+
+            p = table[n >> 8];
+
+            if (p) {
+                c = p[n & 0xff];
+
+                if (c) {
+                    *dst++ = c;
+                    continue;
+                }
+            }
+
+            dst = ngx_sprintf(dst, "&#%uD;", n);
+
+            continue;
+        }
+
+        if (n == 0xfffffffe) {
+            /* incomplete UTF-8 symbol */
+
+            ngx_memcpy(ctx->saved, src, len);
+            ctx->saved_len = len;
+
+            if (b->pos == dst) {
+                b->sync = 1;
+                b->temporary = 0;
+            }
+
+            break;
+        }
+
+        if (n > 0x10ffff) {
+            *dst++ = '?';
+
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pool->log, 0,
+                           "http charset invalid utf 2");
+
+            continue;
+        }
+
+        /* n > 0xffff */
+
+        dst = ngx_sprintf(dst, "&#%uD;", n);
+    }
+
+    b->last = dst;
+
+    b->last_buf = buf->last_buf;
+    b->last_in_chain = buf->last_in_chain;
+    b->flush = buf->flush;
+
+    b->shadow = buf;
+
+    return out;
+}
+
+
+static ngx_chain_t *
+ngx_http_charset_recode_to_utf8(ngx_pool_t *pool, ngx_buf_t *buf,
+    ngx_http_charset_ctx_t *ctx)
+{
+    size_t        len, size;
+    u_char       *p, *src, *dst, *table;
+    ngx_buf_t    *b;
+    ngx_chain_t  *out, *cl, **ll;
+
+    table = ctx->table;
+
+    for (src = buf->pos; src < buf->last; src++) {
+        if (table[*src * NGX_UTF_LEN] == '\1') {
+            continue;
+        }
+
+        goto recode;
+    }
+
+    out = ngx_alloc_chain_link(pool);
+    if (out == NULL) {
+        return NULL;
+    }
+
+    out->buf = buf;
+    out->next = NULL;
+
+    return out;
+
+recode:
+
+    /*
+     * we assume that there are about half of characters to be recoded,
+     * so we preallocate "size / 2 + size / 2 * ctx->length"
+     */
+
+    len = src - buf->pos;
+
+    if (len > 512) {
+        out = ngx_http_charset_get_buf(pool, ctx);
+        if (out == NULL) {
+            return NULL;
+        }
+
+        b = out->buf;
+
+        b->temporary = buf->temporary;
+        b->memory = buf->memory;
+        b->mmap = buf->mmap;
+        b->flush = buf->flush;
+
+        b->pos = buf->pos;
+        b->last = src;
+
+        out->buf = b;
+        out->next = NULL;
+
+        size = buf->last - src;
+        size = size / 2 + size / 2 * ctx->length;
+
+    } else {
+        out = NULL;
+
+        size = buf->last - src;
+        size = len + size / 2 + size / 2 * ctx->length;
+
+        src = buf->pos;
+    }
+
+    cl = ngx_http_charset_get_buffer(pool, ctx, size);
+    if (cl == NULL) {
+        return NULL;
+    }
+
+    if (out) {
+        out->next = cl;
+
+    } else {
+        out = cl;
+    }
+
+    ll = &cl->next;
+
+    b = cl->buf;
+    dst = b->pos;
+
+    while (src < buf->last) {
+
+        p = &table[*src++ * NGX_UTF_LEN];
+        len = *p++;
+
+        if ((size_t) (b->end - dst) < len) {
+            b->last = dst;
+
+            size = buf->last - src;
+            size = len + size / 2 + size / 2 * ctx->length;
+
+            cl = ngx_http_charset_get_buffer(pool, ctx, size);
+            if (cl == NULL) {
+                return NULL;
+            }
+
+            *ll = cl;
+            ll = &cl->next;
+
+            b = cl->buf;
+            dst = b->pos;
+        }
+
+        while (len) {
+            *dst++ = *p++;
+            len--;
+        }
+    }
+
+    b->last = dst;
+
+    b->last_buf = buf->last_buf;
+    b->last_in_chain = buf->last_in_chain;
+    b->flush = buf->flush;
+
+    b->shadow = buf;
+
+    return out;
+}
+
+
+static ngx_chain_t *
+ngx_http_charset_get_buf(ngx_pool_t *pool, ngx_http_charset_ctx_t *ctx)
+{
+    ngx_chain_t  *cl;
+
+    cl = ctx->free_bufs;
+
+    if (cl) {
+        ctx->free_bufs = cl->next;
+
+        cl->buf->shadow = NULL;
+        cl->next = NULL;
+
+        return cl;
+    }
+
+    cl = ngx_alloc_chain_link(pool);
+    if (cl == NULL) {
+        return NULL;
+    }
+
+    cl->buf = ngx_calloc_buf(pool);
+    if (cl->buf == NULL) {
+        return NULL;
+    }
+
+    cl->next = NULL;
+
+    cl->buf->tag = (ngx_buf_tag_t) &ngx_http_charset_filter_module;
+
+    return cl;
+}
+
+
+static ngx_chain_t *
+ngx_http_charset_get_buffer(ngx_pool_t *pool, ngx_http_charset_ctx_t *ctx,
+    size_t size)
+{
+    ngx_buf_t    *b;
+    ngx_chain_t  *cl, **ll;
+
+    for (ll = &ctx->free_buffers, cl = ctx->free_buffers;
+         cl;
+         ll = &cl->next, cl = cl->next)
+    {
+        b = cl->buf;
+
+        if ((size_t) (b->end - b->start) >= size) {
+            *ll = cl->next;
+            cl->next = NULL;
+
+            b->pos = b->start;
+            b->temporary = 1;
+            b->shadow = NULL;
+
+            return cl;
+        }
+    }
+
+    cl = ngx_alloc_chain_link(pool);
+    if (cl == NULL) {
+        return NULL;
+    }
+
+    cl->buf = ngx_create_temp_buf(pool, size);
+    if (cl->buf == NULL) {
+        return NULL;
+    }
+
+    cl->next = NULL;
+
+    cl->buf->temporary = 1;
+    cl->buf->tag = (ngx_buf_tag_t) &ngx_http_charset_filter_module;
+
+    return cl;
+}
+
+
+static char *
+ngx_http_charset_map_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_charset_main_conf_t  *mcf = conf;
+
+    char                         *rv;
+    u_char                       *p, *dst2src, **pp;
+    ngx_int_t                     src, dst;
+    ngx_uint_t                    i, n;
+    ngx_str_t                    *value;
+    ngx_conf_t                    pvcf;
+    ngx_http_charset_t           *charset;
+    ngx_http_charset_tables_t    *table;
+    ngx_http_charset_conf_ctx_t   ctx;
+
+    value = cf->args->elts;
+
+    src = ngx_http_add_charset(&mcf->charsets, &value[1]);
+    if (src == NGX_ERROR) {
+        return NGX_CONF_ERROR;
+    }
+
+    dst = ngx_http_add_charset(&mcf->charsets, &value[2]);
+    if (dst == NGX_ERROR) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (src == dst) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"charset_map\" between the same charsets "
+                           "\"%V\" and \"%V\"", &value[1], &value[2]);
+        return NGX_CONF_ERROR;
+    }
+
+    table = mcf->tables.elts;
+    for (i = 0; i < mcf->tables.nelts; i++) {
+        if ((src == table->src && dst == table->dst)
+             || (src == table->dst && dst == table->src))
+        {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "duplicate \"charset_map\" between "
+                               "\"%V\" and \"%V\"", &value[1], &value[2]);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    table = ngx_array_push(&mcf->tables);
+    if (table == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    table->src = src;
+    table->dst = dst;
+
+    if (ngx_strcasecmp(value[2].data, (u_char *) "utf-8") == 0) {
+        table->src2dst = ngx_pcalloc(cf->pool, 256 * NGX_UTF_LEN);
+        if (table->src2dst == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        table->dst2src = ngx_pcalloc(cf->pool, 256 * sizeof(void *));
+        if (table->dst2src == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        dst2src = ngx_pcalloc(cf->pool, 256);
+        if (dst2src == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        pp = (u_char **) &table->dst2src[0];
+        pp[0] = dst2src;
+
+        for (i = 0; i < 128; i++) {
+            p = &table->src2dst[i * NGX_UTF_LEN];
+            p[0] = '\1';
+            p[1] = (u_char) i;
+            dst2src[i] = (u_char) i;
+        }
+
+        for (/* void */; i < 256; i++) {
+            p = &table->src2dst[i * NGX_UTF_LEN];
+            p[0] = '\1';
+            p[1] = '?';
+        }
+
+    } else {
+        table->src2dst = ngx_palloc(cf->pool, 256);
+        if (table->src2dst == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        table->dst2src = ngx_palloc(cf->pool, 256);
+        if (table->dst2src == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        for (i = 0; i < 128; i++) {
+            table->src2dst[i] = (u_char) i;
+            table->dst2src[i] = (u_char) i;
+        }
+
+        for (/* void */; i < 256; i++) {
+            table->src2dst[i] = '?';
+            table->dst2src[i] = '?';
+        }
+    }
+
+    charset = mcf->charsets.elts;
+
+    ctx.table = table;
+    ctx.charset = &charset[dst];
+    ctx.characters = 0;
+
+    pvcf = *cf;
+    cf->ctx = &ctx;
+    cf->handler = ngx_http_charset_map;
+    cf->handler_conf = conf;
+
+    rv = ngx_conf_parse(cf, NULL);
+
+    *cf = pvcf;
+
+    if (ctx.characters) {
+        n = ctx.charset->length;
+        ctx.charset->length /= ctx.characters;
+
+        if (((n * 10) / ctx.characters) % 10 > 4) {
+            ctx.charset->length++;
+        }
+    }
+
+    return rv;
+}
+
+
+static char *
+ngx_http_charset_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
+{
+    u_char                       *p, *dst2src, **pp;
+    uint32_t                      n;
+    ngx_int_t                     src, dst;
+    ngx_str_t                    *value;
+    ngx_uint_t                    i;
+    ngx_http_charset_tables_t    *table;
+    ngx_http_charset_conf_ctx_t  *ctx;
+
+    if (cf->args->nelts != 2) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameters number");
+        return NGX_CONF_ERROR;
+    }
+
+    value = cf->args->elts;
+
+    src = ngx_hextoi(value[0].data, value[0].len);
+    if (src == NGX_ERROR || src > 255) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid value \"%V\"", &value[0]);
+        return NGX_CONF_ERROR;
+    }
+
+    ctx = cf->ctx;
+    table = ctx->table;
+
+    if (ctx->charset->utf8) {
+        p = &table->src2dst[src * NGX_UTF_LEN];
+
+        *p++ = (u_char) (value[1].len / 2);
+
+        for (i = 0; i < value[1].len; i += 2) {
+            dst = ngx_hextoi(&value[1].data[i], 2);
+            if (dst == NGX_ERROR || dst > 255) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid value \"%V\"", &value[1]);
+                return NGX_CONF_ERROR;
+            }
+
+            *p++ = (u_char) dst;
+        }
+
+        i /= 2;
+
+        ctx->charset->length += i;
+        ctx->characters++;
+
+        p = &table->src2dst[src * NGX_UTF_LEN] + 1;
+
+        n = ngx_utf8_decode(&p, i);
+
+        if (n > 0xffff) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid value \"%V\"", &value[1]);
+            return NGX_CONF_ERROR;
+        }
+
+        pp = (u_char **) &table->dst2src[0];
+
+        dst2src = pp[n >> 8];
+
+        if (dst2src == NULL) {
+            dst2src = ngx_pcalloc(cf->pool, 256);
+            if (dst2src == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            pp[n >> 8] = dst2src;
+        }
+
+        dst2src[n & 0xff] = (u_char) src;
+
+    } else {
+        dst = ngx_hextoi(value[1].data, value[1].len);
+        if (dst == NGX_ERROR || dst > 255) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid value \"%V\"", &value[1]);
+            return NGX_CONF_ERROR;
+        }
+
+        table->src2dst[src] = (u_char) dst;
+        table->dst2src[dst] = (u_char) src;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_set_charset_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ngx_int_t                     *cp;
+    ngx_str_t                     *value, var;
+    ngx_http_charset_main_conf_t  *mcf;
+
+    cp = (ngx_int_t *) (p + cmd->offset);
+
+    if (*cp != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    if (cmd->offset == offsetof(ngx_http_charset_loc_conf_t, charset)
+        && ngx_strcmp(value[1].data, "off") == 0)
+    {
+        *cp = NGX_HTTP_CHARSET_OFF;
+        return NGX_CONF_OK;
+    }
+
+
+    if (value[1].data[0] == '$') {
+        var.len = value[1].len - 1;
+        var.data = value[1].data + 1;
+
+        *cp = ngx_http_get_variable_index(cf, &var);
+
+        if (*cp == NGX_ERROR) {
+            return NGX_CONF_ERROR;
+        }
+
+        *cp += NGX_HTTP_CHARSET_VAR;
+
+        return NGX_CONF_OK;
+    }
+
+    mcf = ngx_http_conf_get_module_main_conf(cf,
+                                             ngx_http_charset_filter_module);
+
+    *cp = ngx_http_add_charset(&mcf->charsets, &value[1]);
+    if (*cp == NGX_ERROR) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_add_charset(ngx_array_t *charsets, ngx_str_t *name)
+{
+    ngx_uint_t           i;
+    ngx_http_charset_t  *c;
+
+    c = charsets->elts;
+    for (i = 0; i < charsets->nelts; i++) {
+        if (name->len != c[i].name.len) {
+            continue;
+        }
+
+        if (ngx_strcasecmp(name->data, c[i].name.data) == 0) {
+            break;
+        }
+    }
+
+    if (i < charsets->nelts) {
+        return i;
+    }
+
+    c = ngx_array_push(charsets);
+    if (c == NULL) {
+        return NGX_ERROR;
+    }
+
+    c->tables = NULL;
+    c->name = *name;
+    c->length = 0;
+
+    if (ngx_strcasecmp(name->data, (u_char *) "utf-8") == 0) {
+        c->utf8 = 1;
+
+    } else {
+        c->utf8 = 0;
+    }
+
+    return i;
+}
+
+
+static void *
+ngx_http_charset_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_http_charset_main_conf_t  *mcf;
+
+    mcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_charset_main_conf_t));
+    if (mcf == NULL) {
+        return NULL;
+    }
+
+    if (ngx_array_init(&mcf->charsets, cf->pool, 2, sizeof(ngx_http_charset_t))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    if (ngx_array_init(&mcf->tables, cf->pool, 1,
+                       sizeof(ngx_http_charset_tables_t))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    if (ngx_array_init(&mcf->recodes, cf->pool, 2,
+                       sizeof(ngx_http_charset_recode_t))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    return mcf;
+}
+
+
+static void *
+ngx_http_charset_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_charset_loc_conf_t  *lcf;
+
+    lcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_charset_loc_conf_t));
+    if (lcf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     lcf->types = { NULL };
+     *     lcf->types_keys = NULL;
+     */
+
+    lcf->charset = NGX_CONF_UNSET;
+    lcf->source_charset = NGX_CONF_UNSET;
+    lcf->override_charset = NGX_CONF_UNSET;
+
+    return lcf;
+}
+
+
+static char *
+ngx_http_charset_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_charset_loc_conf_t *prev = parent;
+    ngx_http_charset_loc_conf_t *conf = child;
+
+    ngx_uint_t                     i;
+    ngx_http_charset_recode_t     *recode;
+    ngx_http_charset_main_conf_t  *mcf;
+
+    if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types,
+                             &prev->types_keys, &prev->types,
+                             ngx_http_charset_default_types)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_conf_merge_value(conf->override_charset, prev->override_charset, 0);
+    ngx_conf_merge_value(conf->charset, prev->charset, NGX_HTTP_CHARSET_OFF);
+    ngx_conf_merge_value(conf->source_charset, prev->source_charset,
+                         NGX_HTTP_CHARSET_OFF);
+
+    if (conf->charset == NGX_HTTP_CHARSET_OFF
+        || conf->source_charset == NGX_HTTP_CHARSET_OFF
+        || conf->charset == conf->source_charset)
+    {
+        return NGX_CONF_OK;
+    }
+
+    if (conf->source_charset >= NGX_HTTP_CHARSET_VAR
+        || conf->charset >= NGX_HTTP_CHARSET_VAR)
+    {
+        return NGX_CONF_OK;
+    }
+
+    mcf = ngx_http_conf_get_module_main_conf(cf,
+                                             ngx_http_charset_filter_module);
+    recode = mcf->recodes.elts;
+    for (i = 0; i < mcf->recodes.nelts; i++) {
+        if (conf->source_charset == recode[i].src
+            && conf->charset == recode[i].dst)
+        {
+            return NGX_CONF_OK;
+        }
+    }
+
+    recode = ngx_array_push(&mcf->recodes);
+    if (recode == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    recode->src = conf->source_charset;
+    recode->dst = conf->charset;
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_charset_postconfiguration(ngx_conf_t *cf)
+{
+    u_char                       **src, **dst;
+    ngx_int_t                      c;
+    ngx_uint_t                     i, t;
+    ngx_http_charset_t            *charset;
+    ngx_http_charset_recode_t     *recode;
+    ngx_http_charset_tables_t     *tables;
+    ngx_http_charset_main_conf_t  *mcf;
+
+    mcf = ngx_http_conf_get_module_main_conf(cf,
+                                             ngx_http_charset_filter_module);
+
+    recode = mcf->recodes.elts;
+    tables = mcf->tables.elts;
+    charset = mcf->charsets.elts;
+
+    for (i = 0; i < mcf->recodes.nelts; i++) {
+
+        c = recode[i].src;
+
+        for (t = 0; t < mcf->tables.nelts; t++) {
+
+            if (c == tables[t].src && recode[i].dst == tables[t].dst) {
+                goto next;
+            }
+
+            if (c == tables[t].dst && recode[i].dst == tables[t].src) {
+                goto next;
+            }
+        }
+
+        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                   "no \"charset_map\" between the charsets \"%V\" and \"%V\"",
+                   &charset[c].name, &charset[recode[i].dst].name);
+        return NGX_ERROR;
+
+    next:
+        continue;
+    }
+
+
+    for (t = 0; t < mcf->tables.nelts; t++) {
+
+        src = charset[tables[t].src].tables;
+
+        if (src == NULL) {
+            src = ngx_pcalloc(cf->pool, sizeof(u_char *) * mcf->charsets.nelts);
+            if (src == NULL) {
+                return NGX_ERROR;
+            }
+
+            charset[tables[t].src].tables = src;
+        }
+
+        dst = charset[tables[t].dst].tables;
+
+        if (dst == NULL) {
+            dst = ngx_pcalloc(cf->pool, sizeof(u_char *) * mcf->charsets.nelts);
+            if (dst == NULL) {
+                return NGX_ERROR;
+            }
+
+            charset[tables[t].dst].tables = dst;
+        }
+
+        src[tables[t].dst] = tables[t].src2dst;
+        dst[tables[t].src] = tables[t].dst2src;
+    }
+
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_charset_header_filter;
+
+    ngx_http_next_body_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_charset_body_filter;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_chunked_filter_module.c b/nginx/src/http/modules/ngx_http_chunked_filter_module.c
new file mode 100644 (file)
index 0000000..4d6fd3e
--- /dev/null
@@ -0,0 +1,341 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_chain_t         *free;
+    ngx_chain_t         *busy;
+} ngx_http_chunked_filter_ctx_t;
+
+
+static ngx_int_t ngx_http_chunked_filter_init(ngx_conf_t *cf);
+static ngx_chain_t *ngx_http_chunked_create_trailers(ngx_http_request_t *r,
+    ngx_http_chunked_filter_ctx_t *ctx);
+
+
+static ngx_http_module_t  ngx_http_chunked_filter_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_chunked_filter_init,          /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_chunked_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_chunked_filter_module_ctx,   /* module context */
+    NULL,                                  /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_chunked_header_filter(ngx_http_request_t *r)
+{
+    ngx_http_core_loc_conf_t       *clcf;
+    ngx_http_chunked_filter_ctx_t  *ctx;
+
+    if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED
+        || r->headers_out.status == NGX_HTTP_NO_CONTENT
+        || r->headers_out.status < NGX_HTTP_OK
+        || r != r->main
+        || r->method == NGX_HTTP_HEAD)
+    {
+        return ngx_http_next_header_filter(r);
+    }
+
+    if (r->headers_out.content_length_n == -1
+        || r->expect_trailers)
+    {
+        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+        if (r->http_version >= NGX_HTTP_VERSION_11
+            && clcf->chunked_transfer_encoding)
+        {
+            if (r->expect_trailers) {
+                ngx_http_clear_content_length(r);
+            }
+
+            r->chunked = 1;
+
+            ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_chunked_filter_ctx_t));
+            if (ctx == NULL) {
+                return NGX_ERROR;
+            }
+
+            ngx_http_set_ctx(r, ctx, ngx_http_chunked_filter_module);
+
+        } else if (r->headers_out.content_length_n == -1) {
+            r->keepalive = 0;
+        }
+    }
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    u_char                         *chunk;
+    off_t                           size;
+    ngx_int_t                       rc;
+    ngx_buf_t                      *b;
+    ngx_chain_t                    *out, *cl, *tl, **ll;
+    ngx_http_chunked_filter_ctx_t  *ctx;
+
+    if (in == NULL || !r->chunked || r->header_only) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_chunked_filter_module);
+
+    out = NULL;
+    ll = &out;
+
+    size = 0;
+    cl = in;
+
+    for ( ;; ) {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http chunk: %O", ngx_buf_size(cl->buf));
+
+        size += ngx_buf_size(cl->buf);
+
+        if (cl->buf->flush
+            || cl->buf->sync
+            || ngx_buf_in_memory(cl->buf)
+            || cl->buf->in_file)
+        {
+            tl = ngx_alloc_chain_link(r->pool);
+            if (tl == NULL) {
+                return NGX_ERROR;
+            }
+
+            tl->buf = cl->buf;
+            *ll = tl;
+            ll = &tl->next;
+        }
+
+        if (cl->next == NULL) {
+            break;
+        }
+
+        cl = cl->next;
+    }
+
+    if (size) {
+        tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+        if (tl == NULL) {
+            return NGX_ERROR;
+        }
+
+        b = tl->buf;
+        chunk = b->start;
+
+        if (chunk == NULL) {
+            /* the "0000000000000000" is 64-bit hexadecimal string */
+
+            chunk = ngx_palloc(r->pool, sizeof("0000000000000000" CRLF) - 1);
+            if (chunk == NULL) {
+                return NGX_ERROR;
+            }
+
+            b->start = chunk;
+            b->end = chunk + sizeof("0000000000000000" CRLF) - 1;
+        }
+
+        b->tag = (ngx_buf_tag_t) &ngx_http_chunked_filter_module;
+        b->memory = 0;
+        b->temporary = 1;
+        b->pos = chunk;
+        b->last = ngx_sprintf(chunk, "%xO" CRLF, size);
+
+        tl->next = out;
+        out = tl;
+    }
+
+    if (cl->buf->last_buf) {
+        tl = ngx_http_chunked_create_trailers(r, ctx);
+        if (tl == NULL) {
+            return NGX_ERROR;
+        }
+
+        cl->buf->last_buf = 0;
+
+        *ll = tl;
+
+        if (size == 0) {
+            tl->buf->pos += 2;
+        }
+
+    } else if (size > 0) {
+        tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+        if (tl == NULL) {
+            return NGX_ERROR;
+        }
+
+        b = tl->buf;
+
+        b->tag = (ngx_buf_tag_t) &ngx_http_chunked_filter_module;
+        b->temporary = 0;
+        b->memory = 1;
+        b->pos = (u_char *) CRLF;
+        b->last = b->pos + 2;
+
+        *ll = tl;
+
+    } else {
+        *ll = NULL;
+    }
+
+    rc = ngx_http_next_body_filter(r, out);
+
+    ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out,
+                            (ngx_buf_tag_t) &ngx_http_chunked_filter_module);
+
+    return rc;
+}
+
+
+static ngx_chain_t *
+ngx_http_chunked_create_trailers(ngx_http_request_t *r,
+    ngx_http_chunked_filter_ctx_t *ctx)
+{
+    size_t            len;
+    ngx_buf_t        *b;
+    ngx_uint_t        i;
+    ngx_chain_t      *cl;
+    ngx_list_part_t  *part;
+    ngx_table_elt_t  *header;
+
+    len = 0;
+
+    part = &r->headers_out.trailers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (header[i].hash == 0) {
+            continue;
+        }
+
+        len += header[i].key.len + sizeof(": ") - 1
+               + header[i].value.len + sizeof(CRLF) - 1;
+    }
+
+    cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+    if (cl == NULL) {
+        return NULL;
+    }
+
+    b = cl->buf;
+
+    b->tag = (ngx_buf_tag_t) &ngx_http_chunked_filter_module;
+    b->temporary = 0;
+    b->memory = 1;
+    b->last_buf = 1;
+
+    if (len == 0) {
+        b->pos = (u_char *) CRLF "0" CRLF CRLF;
+        b->last = b->pos + sizeof(CRLF "0" CRLF CRLF) - 1;
+        return cl;
+    }
+
+    len += sizeof(CRLF "0" CRLF CRLF) - 1;
+
+    b->pos = ngx_palloc(r->pool, len);
+    if (b->pos == NULL) {
+        return NULL;
+    }
+
+    b->last = b->pos;
+
+    *b->last++ = CR; *b->last++ = LF;
+    *b->last++ = '0';
+    *b->last++ = CR; *b->last++ = LF;
+
+    part = &r->headers_out.trailers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (header[i].hash == 0) {
+            continue;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http trailer: \"%V: %V\"",
+                       &header[i].key, &header[i].value);
+
+        b->last = ngx_copy(b->last, header[i].key.data, header[i].key.len);
+        *b->last++ = ':'; *b->last++ = ' ';
+
+        b->last = ngx_copy(b->last, header[i].value.data, header[i].value.len);
+        *b->last++ = CR; *b->last++ = LF;
+    }
+
+    *b->last++ = CR; *b->last++ = LF;
+
+    return cl;
+}
+
+
+static ngx_int_t
+ngx_http_chunked_filter_init(ngx_conf_t *cf)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_chunked_header_filter;
+
+    ngx_http_next_body_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_chunked_body_filter;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_dav_module.c b/nginx/src/http/modules/ngx_http_dav_module.c
new file mode 100644 (file)
index 0000000..55ad9eb
--- /dev/null
@@ -0,0 +1,1169 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_DAV_OFF             2
+
+
+#define NGX_HTTP_DAV_NO_DEPTH        -3
+#define NGX_HTTP_DAV_INVALID_DEPTH   -2
+#define NGX_HTTP_DAV_INFINITY_DEPTH  -1
+
+
+typedef struct {
+    ngx_uint_t  methods;
+    ngx_uint_t  access;
+    ngx_uint_t  min_delete_depth;
+    ngx_flag_t  create_full_put_path;
+} ngx_http_dav_loc_conf_t;
+
+
+typedef struct {
+    ngx_str_t   path;
+    size_t      len;
+} ngx_http_dav_copy_ctx_t;
+
+
+static ngx_int_t ngx_http_dav_handler(ngx_http_request_t *r);
+
+static void ngx_http_dav_put_handler(ngx_http_request_t *r);
+
+static ngx_int_t ngx_http_dav_delete_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_dav_delete_path(ngx_http_request_t *r,
+    ngx_str_t *path, ngx_uint_t dir);
+static ngx_int_t ngx_http_dav_delete_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path);
+static ngx_int_t ngx_http_dav_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path);
+static ngx_int_t ngx_http_dav_noop(ngx_tree_ctx_t *ctx, ngx_str_t *path);
+
+static ngx_int_t ngx_http_dav_mkcol_handler(ngx_http_request_t *r,
+    ngx_http_dav_loc_conf_t *dlcf);
+
+static ngx_int_t ngx_http_dav_copy_move_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_dav_copy_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path);
+static ngx_int_t ngx_http_dav_copy_dir_time(ngx_tree_ctx_t *ctx,
+    ngx_str_t *path);
+static ngx_int_t ngx_http_dav_copy_tree_file(ngx_tree_ctx_t *ctx,
+    ngx_str_t *path);
+
+static ngx_int_t ngx_http_dav_depth(ngx_http_request_t *r, ngx_int_t dflt);
+static ngx_int_t ngx_http_dav_error(ngx_log_t *log, ngx_err_t err,
+    ngx_int_t not_found, char *failed, u_char *path);
+static ngx_int_t ngx_http_dav_location(ngx_http_request_t *r, u_char *path);
+static void *ngx_http_dav_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_dav_merge_loc_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+static ngx_int_t ngx_http_dav_init(ngx_conf_t *cf);
+
+
+static ngx_conf_bitmask_t  ngx_http_dav_methods_mask[] = {
+    { ngx_string("off"), NGX_HTTP_DAV_OFF },
+    { ngx_string("put"), NGX_HTTP_PUT },
+    { ngx_string("delete"), NGX_HTTP_DELETE },
+    { ngx_string("mkcol"), NGX_HTTP_MKCOL },
+    { ngx_string("copy"), NGX_HTTP_COPY },
+    { ngx_string("move"), NGX_HTTP_MOVE },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t  ngx_http_dav_commands[] = {
+
+    { ngx_string("dav_methods"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_dav_loc_conf_t, methods),
+      &ngx_http_dav_methods_mask },
+
+    { ngx_string("create_full_put_path"),
+      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_dav_loc_conf_t, create_full_put_path),
+      NULL },
+
+    { ngx_string("min_delete_depth"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_dav_loc_conf_t, min_delete_depth),
+      NULL },
+
+    { ngx_string("dav_access"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
+      ngx_conf_set_access_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_dav_loc_conf_t, access),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_dav_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_dav_init,                     /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_dav_create_loc_conf,          /* create location configuration */
+    ngx_http_dav_merge_loc_conf            /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_dav_module = {
+    NGX_MODULE_V1,
+    &ngx_http_dav_module_ctx,              /* module context */
+    ngx_http_dav_commands,                 /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_dav_handler(ngx_http_request_t *r)
+{
+    ngx_int_t                 rc;
+    ngx_http_dav_loc_conf_t  *dlcf;
+
+    dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module);
+
+    if (!(r->method & dlcf->methods)) {
+        return NGX_DECLINED;
+    }
+
+    switch (r->method) {
+
+    case NGX_HTTP_PUT:
+
+        if (r->uri.data[r->uri.len - 1] == '/') {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "cannot PUT to a collection");
+            return NGX_HTTP_CONFLICT;
+        }
+
+        if (r->headers_in.content_range) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "PUT with range is unsupported");
+            return NGX_HTTP_NOT_IMPLEMENTED;
+        }
+
+        r->request_body_in_file_only = 1;
+        r->request_body_in_persistent_file = 1;
+        r->request_body_in_clean_file = 1;
+        r->request_body_file_group_access = 1;
+        r->request_body_file_log_level = 0;
+
+        rc = ngx_http_read_client_request_body(r, ngx_http_dav_put_handler);
+
+        if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+            return rc;
+        }
+
+        return NGX_DONE;
+
+    case NGX_HTTP_DELETE:
+
+        return ngx_http_dav_delete_handler(r);
+
+    case NGX_HTTP_MKCOL:
+
+        return ngx_http_dav_mkcol_handler(r, dlcf);
+
+    case NGX_HTTP_COPY:
+
+        return ngx_http_dav_copy_move_handler(r);
+
+    case NGX_HTTP_MOVE:
+
+        return ngx_http_dav_copy_move_handler(r);
+    }
+
+    return NGX_DECLINED;
+}
+
+
+static void
+ngx_http_dav_put_handler(ngx_http_request_t *r)
+{
+    size_t                    root;
+    time_t                    date;
+    ngx_str_t                *temp, path;
+    ngx_uint_t                status;
+    ngx_file_info_t           fi;
+    ngx_ext_rename_file_t     ext;
+    ngx_http_dav_loc_conf_t  *dlcf;
+
+    if (r->request_body == NULL) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "PUT request body is unavailable");
+        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    if (r->request_body->temp_file == NULL) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "PUT request body must be in a file");
+        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) {
+        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    path.len--;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http put filename: \"%s\"", path.data);
+
+    temp = &r->request_body->temp_file->file.name;
+
+    if (ngx_file_info(path.data, &fi) == NGX_FILE_ERROR) {
+        status = NGX_HTTP_CREATED;
+
+    } else {
+        status = NGX_HTTP_NO_CONTENT;
+
+        if (ngx_is_dir(&fi)) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_EISDIR,
+                          "\"%s\" could not be created", path.data);
+
+            if (ngx_delete_file(temp->data) == NGX_FILE_ERROR) {
+                ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+                              ngx_delete_file_n " \"%s\" failed",
+                              temp->data);
+            }
+
+            ngx_http_finalize_request(r, NGX_HTTP_CONFLICT);
+            return;
+        }
+    }
+
+    dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module);
+
+    ext.access = dlcf->access;
+    ext.path_access = dlcf->access;
+    ext.time = -1;
+    ext.create_path = dlcf->create_full_put_path;
+    ext.delete_file = 1;
+    ext.log = r->connection->log;
+
+    if (r->headers_in.date) {
+        date = ngx_parse_http_time(r->headers_in.date->value.data,
+                                   r->headers_in.date->value.len);
+
+        if (date != NGX_ERROR) {
+            ext.time = date;
+            ext.fd = r->request_body->temp_file->file.fd;
+        }
+    }
+
+    if (ngx_ext_rename_file(temp, &path, &ext) != NGX_OK) {
+        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    if (status == NGX_HTTP_CREATED) {
+        if (ngx_http_dav_location(r, path.data) != NGX_OK) {
+            ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        r->headers_out.content_length_n = 0;
+    }
+
+    r->headers_out.status = status;
+    r->header_only = 1;
+
+    ngx_http_finalize_request(r, ngx_http_send_header(r));
+    return;
+}
+
+
+static ngx_int_t
+ngx_http_dav_delete_handler(ngx_http_request_t *r)
+{
+    size_t                    root;
+    ngx_err_t                 err;
+    ngx_int_t                 rc, depth;
+    ngx_uint_t                i, d, dir;
+    ngx_str_t                 path;
+    ngx_file_info_t           fi;
+    ngx_http_dav_loc_conf_t  *dlcf;
+
+    if (r->headers_in.content_length_n > 0) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "DELETE with body is unsupported");
+        return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE;
+    }
+
+    dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module);
+
+    if (dlcf->min_delete_depth) {
+        d = 0;
+
+        for (i = 0; i < r->uri.len; /* void */) {
+            if (r->uri.data[i++] == '/') {
+                if (++d >= dlcf->min_delete_depth && i < r->uri.len) {
+                    goto ok;
+                }
+            }
+        }
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "insufficient URI depth:%i to DELETE", d);
+        return NGX_HTTP_CONFLICT;
+    }
+
+ok:
+
+    if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http delete filename: \"%s\"", path.data);
+
+    if (ngx_link_info(path.data, &fi) == NGX_FILE_ERROR) {
+        err = ngx_errno;
+
+        rc = (err == NGX_ENOTDIR) ? NGX_HTTP_CONFLICT : NGX_HTTP_NOT_FOUND;
+
+        return ngx_http_dav_error(r->connection->log, err,
+                                  rc, ngx_link_info_n, path.data);
+    }
+
+    if (ngx_is_dir(&fi)) {
+
+        if (r->uri.data[r->uri.len - 1] != '/') {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_EISDIR,
+                          "DELETE \"%s\" failed", path.data);
+            return NGX_HTTP_CONFLICT;
+        }
+
+        depth = ngx_http_dav_depth(r, NGX_HTTP_DAV_INFINITY_DEPTH);
+
+        if (depth != NGX_HTTP_DAV_INFINITY_DEPTH) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "\"Depth\" header must be infinity");
+            return NGX_HTTP_BAD_REQUEST;
+        }
+
+        path.len -= 2;  /* omit "/\0" */
+
+        dir = 1;
+
+    } else {
+
+        /*
+         * we do not need to test (r->uri.data[r->uri.len - 1] == '/')
+         * because ngx_link_info("/file/") returned NGX_ENOTDIR above
+         */
+
+        depth = ngx_http_dav_depth(r, 0);
+
+        if (depth != 0 && depth != NGX_HTTP_DAV_INFINITY_DEPTH) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "\"Depth\" header must be 0 or infinity");
+            return NGX_HTTP_BAD_REQUEST;
+        }
+
+        dir = 0;
+    }
+
+    rc = ngx_http_dav_delete_path(r, &path, dir);
+
+    if (rc == NGX_OK) {
+        return NGX_HTTP_NO_CONTENT;
+    }
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_http_dav_delete_path(ngx_http_request_t *r, ngx_str_t *path, ngx_uint_t dir)
+{
+    char            *failed;
+    ngx_tree_ctx_t   tree;
+
+    if (dir) {
+
+        tree.init_handler = NULL;
+        tree.file_handler = ngx_http_dav_delete_file;
+        tree.pre_tree_handler = ngx_http_dav_noop;
+        tree.post_tree_handler = ngx_http_dav_delete_dir;
+        tree.spec_handler = ngx_http_dav_delete_file;
+        tree.data = NULL;
+        tree.alloc = 0;
+        tree.log = r->connection->log;
+
+        /* TODO: 207 */
+
+        if (ngx_walk_tree(&tree, path) != NGX_OK) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        if (ngx_delete_dir(path->data) != NGX_FILE_ERROR) {
+            return NGX_OK;
+        }
+
+        failed = ngx_delete_dir_n;
+
+    } else {
+
+        if (ngx_delete_file(path->data) != NGX_FILE_ERROR) {
+            return NGX_OK;
+        }
+
+        failed = ngx_delete_file_n;
+    }
+
+    return ngx_http_dav_error(r->connection->log, ngx_errno,
+                              NGX_HTTP_NOT_FOUND, failed, path->data);
+}
+
+
+static ngx_int_t
+ngx_http_dav_delete_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+                   "http delete dir: \"%s\"", path->data);
+
+    if (ngx_delete_dir(path->data) == NGX_FILE_ERROR) {
+
+        /* TODO: add to 207 */
+
+        (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_delete_dir_n,
+                                  path->data);
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_dav_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+                   "http delete file: \"%s\"", path->data);
+
+    if (ngx_delete_file(path->data) == NGX_FILE_ERROR) {
+
+        /* TODO: add to 207 */
+
+        (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_delete_file_n,
+                                  path->data);
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_dav_noop(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_dav_mkcol_handler(ngx_http_request_t *r, ngx_http_dav_loc_conf_t *dlcf)
+{
+    u_char    *p;
+    size_t     root;
+    ngx_str_t  path;
+
+    if (r->headers_in.content_length_n > 0) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "MKCOL with body is unsupported");
+        return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE;
+    }
+
+    if (r->uri.data[r->uri.len - 1] != '/') {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "MKCOL can create a collection only");
+        return NGX_HTTP_CONFLICT;
+    }
+
+    p = ngx_http_map_uri_to_path(r, &path, &root, 0);
+    if (p == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    *(p - 1) = '\0';
+    r->uri.len--;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http mkcol path: \"%s\"", path.data);
+
+    if (ngx_create_dir(path.data, ngx_dir_access(dlcf->access))
+        != NGX_FILE_ERROR)
+    {
+        if (ngx_http_dav_location(r, path.data) != NGX_OK) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        return NGX_HTTP_CREATED;
+    }
+
+    return ngx_http_dav_error(r->connection->log, ngx_errno,
+                              NGX_HTTP_CONFLICT, ngx_create_dir_n, path.data);
+}
+
+
+static ngx_int_t
+ngx_http_dav_copy_move_handler(ngx_http_request_t *r)
+{
+    u_char                   *p, *host, *last, ch;
+    size_t                    len, root;
+    ngx_err_t                 err;
+    ngx_int_t                 rc, depth;
+    ngx_uint_t                overwrite, slash, dir, flags;
+    ngx_str_t                 path, uri, duri, args;
+    ngx_tree_ctx_t            tree;
+    ngx_copy_file_t           cf;
+    ngx_file_info_t           fi;
+    ngx_table_elt_t          *dest, *over;
+    ngx_ext_rename_file_t     ext;
+    ngx_http_dav_copy_ctx_t   copy;
+    ngx_http_dav_loc_conf_t  *dlcf;
+
+    if (r->headers_in.content_length_n > 0) {
+        return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE;
+    }
+
+    dest = r->headers_in.destination;
+
+    if (dest == NULL) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "client sent no \"Destination\" header");
+        return NGX_HTTP_BAD_REQUEST;
+    }
+
+    p = dest->value.data;
+    /* there is always '\0' even after empty header value */
+    if (p[0] == '/') {
+        last = p + dest->value.len;
+        goto destination_done;
+    }
+
+    len = r->headers_in.server.len;
+
+    if (len == 0) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "client sent no \"Host\" header");
+        return NGX_HTTP_BAD_REQUEST;
+    }
+
+#if (NGX_HTTP_SSL)
+
+    if (r->connection->ssl) {
+        if (ngx_strncmp(dest->value.data, "https://", sizeof("https://") - 1)
+            != 0)
+        {
+            goto invalid_destination;
+        }
+
+        host = dest->value.data + sizeof("https://") - 1;
+
+    } else
+#endif
+    {
+        if (ngx_strncmp(dest->value.data, "http://", sizeof("http://") - 1)
+            != 0)
+        {
+            goto invalid_destination;
+        }
+
+        host = dest->value.data + sizeof("http://") - 1;
+    }
+
+    if (ngx_strncmp(host, r->headers_in.server.data, len) != 0) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "\"Destination\" URI \"%V\" is handled by "
+                      "different repository than the source URI",
+                      &dest->value);
+        return NGX_HTTP_BAD_REQUEST;
+    }
+
+    last = dest->value.data + dest->value.len;
+
+    for (p = host + len; p < last; p++) {
+        if (*p == '/') {
+            goto destination_done;
+        }
+    }
+
+invalid_destination:
+
+    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                  "client sent invalid \"Destination\" header: \"%V\"",
+                  &dest->value);
+    return NGX_HTTP_BAD_REQUEST;
+
+destination_done:
+
+    duri.len = last - p;
+    duri.data = p;
+    flags = NGX_HTTP_LOG_UNSAFE;
+
+    if (ngx_http_parse_unsafe_uri(r, &duri, &args, &flags) != NGX_OK) {
+        goto invalid_destination;
+    }
+
+    if ((r->uri.data[r->uri.len - 1] == '/' && *(last - 1) != '/')
+        || (r->uri.data[r->uri.len - 1] != '/' && *(last - 1) == '/'))
+    {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "both URI \"%V\" and \"Destination\" URI \"%V\" "
+                      "should be either collections or non-collections",
+                      &r->uri, &dest->value);
+        return NGX_HTTP_CONFLICT;
+    }
+
+    depth = ngx_http_dav_depth(r, NGX_HTTP_DAV_INFINITY_DEPTH);
+
+    if (depth != NGX_HTTP_DAV_INFINITY_DEPTH) {
+
+        if (r->method == NGX_HTTP_COPY) {
+            if (depth != 0) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "\"Depth\" header must be 0 or infinity");
+                return NGX_HTTP_BAD_REQUEST;
+            }
+
+        } else {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "\"Depth\" header must be infinity");
+            return NGX_HTTP_BAD_REQUEST;
+        }
+    }
+
+    over = r->headers_in.overwrite;
+
+    if (over) {
+        if (over->value.len == 1) {
+            ch = over->value.data[0];
+
+            if (ch == 'T' || ch == 't') {
+                overwrite = 1;
+                goto overwrite_done;
+            }
+
+            if (ch == 'F' || ch == 'f') {
+                overwrite = 0;
+                goto overwrite_done;
+            }
+
+        }
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "client sent invalid \"Overwrite\" header: \"%V\"",
+                      &over->value);
+        return NGX_HTTP_BAD_REQUEST;
+    }
+
+    overwrite = 1;
+
+overwrite_done:
+
+    if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http copy from: \"%s\"", path.data);
+
+    uri = r->uri;
+    r->uri = duri;
+
+    if (ngx_http_map_uri_to_path(r, &copy.path, &root, 0) == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    r->uri = uri;
+
+    copy.path.len--;  /* omit "\0" */
+
+    if (copy.path.data[copy.path.len - 1] == '/') {
+        slash = 1;
+        copy.path.len--;
+        copy.path.data[copy.path.len] = '\0';
+
+    } else {
+        slash = 0;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http copy to: \"%s\"", copy.path.data);
+
+    if (ngx_link_info(copy.path.data, &fi) == NGX_FILE_ERROR) {
+        err = ngx_errno;
+
+        if (err != NGX_ENOENT) {
+            return ngx_http_dav_error(r->connection->log, err,
+                                      NGX_HTTP_NOT_FOUND, ngx_link_info_n,
+                                      copy.path.data);
+        }
+
+        /* destination does not exist */
+
+        overwrite = 0;
+        dir = 0;
+
+    } else {
+
+        /* destination exists */
+
+        if (ngx_is_dir(&fi) && !slash) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "\"%V\" could not be %Ved to collection \"%V\"",
+                          &r->uri, &r->method_name, &dest->value);
+            return NGX_HTTP_CONFLICT;
+        }
+
+        if (!overwrite) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_EEXIST,
+                          "\"%s\" could not be created", copy.path.data);
+            return NGX_HTTP_PRECONDITION_FAILED;
+        }
+
+        dir = ngx_is_dir(&fi);
+    }
+
+    if (ngx_link_info(path.data, &fi) == NGX_FILE_ERROR) {
+        return ngx_http_dav_error(r->connection->log, ngx_errno,
+                                  NGX_HTTP_NOT_FOUND, ngx_link_info_n,
+                                  path.data);
+    }
+
+    if (ngx_is_dir(&fi)) {
+
+        if (r->uri.data[r->uri.len - 1] != '/') {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "\"%V\" is collection", &r->uri);
+            return NGX_HTTP_BAD_REQUEST;
+        }
+
+        if (overwrite) {
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http delete: \"%s\"", copy.path.data);
+
+            rc = ngx_http_dav_delete_path(r, &copy.path, dir);
+
+            if (rc != NGX_OK) {
+                return rc;
+            }
+        }
+    }
+
+    if (ngx_is_dir(&fi)) {
+
+        path.len -= 2;  /* omit "/\0" */
+
+        if (r->method == NGX_HTTP_MOVE) {
+            if (ngx_rename_file(path.data, copy.path.data) != NGX_FILE_ERROR) {
+                return NGX_HTTP_CREATED;
+            }
+        }
+
+        if (ngx_create_dir(copy.path.data, ngx_file_access(&fi))
+            == NGX_FILE_ERROR)
+        {
+            return ngx_http_dav_error(r->connection->log, ngx_errno,
+                                      NGX_HTTP_NOT_FOUND,
+                                      ngx_create_dir_n, copy.path.data);
+        }
+
+        copy.len = path.len;
+
+        tree.init_handler = NULL;
+        tree.file_handler = ngx_http_dav_copy_tree_file;
+        tree.pre_tree_handler = ngx_http_dav_copy_dir;
+        tree.post_tree_handler = ngx_http_dav_copy_dir_time;
+        tree.spec_handler = ngx_http_dav_noop;
+        tree.data = &copy;
+        tree.alloc = 0;
+        tree.log = r->connection->log;
+
+        if (ngx_walk_tree(&tree, &path) == NGX_OK) {
+
+            if (r->method == NGX_HTTP_MOVE) {
+                rc = ngx_http_dav_delete_path(r, &path, 1);
+
+                if (rc != NGX_OK) {
+                    return rc;
+                }
+            }
+
+            return NGX_HTTP_CREATED;
+        }
+
+    } else {
+
+        if (r->method == NGX_HTTP_MOVE) {
+
+            dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module);
+
+            ext.access = 0;
+            ext.path_access = dlcf->access;
+            ext.time = -1;
+            ext.create_path = 1;
+            ext.delete_file = 0;
+            ext.log = r->connection->log;
+
+            if (ngx_ext_rename_file(&path, &copy.path, &ext) == NGX_OK) {
+                return NGX_HTTP_NO_CONTENT;
+            }
+
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module);
+
+        cf.size = ngx_file_size(&fi);
+        cf.buf_size = 0;
+        cf.access = dlcf->access;
+        cf.time = ngx_file_mtime(&fi);
+        cf.log = r->connection->log;
+
+        if (ngx_copy_file(path.data, copy.path.data, &cf) == NGX_OK) {
+            return NGX_HTTP_NO_CONTENT;
+        }
+    }
+
+    return NGX_HTTP_INTERNAL_SERVER_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_dav_copy_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+    u_char                   *p, *dir;
+    size_t                    len;
+    ngx_http_dav_copy_ctx_t  *copy;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+                   "http copy dir: \"%s\"", path->data);
+
+    copy = ctx->data;
+
+    len = copy->path.len + path->len;
+
+    dir = ngx_alloc(len + 1, ctx->log);
+    if (dir == NULL) {
+        return NGX_ABORT;
+    }
+
+    p = ngx_cpymem(dir, copy->path.data, copy->path.len);
+    (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+                   "http copy dir to: \"%s\"", dir);
+
+    if (ngx_create_dir(dir, ngx_dir_access(ctx->access)) == NGX_FILE_ERROR) {
+        (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_create_dir_n,
+                                  dir);
+    }
+
+    ngx_free(dir);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_dav_copy_dir_time(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+    u_char                   *p, *dir;
+    size_t                    len;
+    ngx_http_dav_copy_ctx_t  *copy;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+                   "http copy dir time: \"%s\"", path->data);
+
+    copy = ctx->data;
+
+    len = copy->path.len + path->len;
+
+    dir = ngx_alloc(len + 1, ctx->log);
+    if (dir == NULL) {
+        return NGX_ABORT;
+    }
+
+    p = ngx_cpymem(dir, copy->path.data, copy->path.len);
+    (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+                   "http copy dir time to: \"%s\"", dir);
+
+#if (NGX_WIN32)
+    {
+    ngx_fd_t  fd;
+
+    fd = ngx_open_file(dir, NGX_FILE_RDWR, NGX_FILE_OPEN, 0);
+
+    if (fd == NGX_INVALID_FILE) {
+        (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_open_file_n, dir);
+        goto failed;
+    }
+
+    if (ngx_set_file_time(NULL, fd, ctx->mtime) != NGX_OK) {
+        ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno,
+                      ngx_set_file_time_n " \"%s\" failed", dir);
+    }
+
+    if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno,
+                      ngx_close_file_n " \"%s\" failed", dir);
+    }
+    }
+
+failed:
+
+#else
+
+    if (ngx_set_file_time(dir, 0, ctx->mtime) != NGX_OK) {
+        ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno,
+                      ngx_set_file_time_n " \"%s\" failed", dir);
+    }
+
+#endif
+
+    ngx_free(dir);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_dav_copy_tree_file(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+    u_char                   *p, *file;
+    size_t                    len;
+    ngx_copy_file_t           cf;
+    ngx_http_dav_copy_ctx_t  *copy;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+                   "http copy file: \"%s\"", path->data);
+
+    copy = ctx->data;
+
+    len = copy->path.len + path->len;
+
+    file = ngx_alloc(len + 1, ctx->log);
+    if (file == NULL) {
+        return NGX_ABORT;
+    }
+
+    p = ngx_cpymem(file, copy->path.data, copy->path.len);
+    (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+                   "http copy file to: \"%s\"", file);
+
+    cf.size = ctx->size;
+    cf.buf_size = 0;
+    cf.access = ctx->access;
+    cf.time = ctx->mtime;
+    cf.log = ctx->log;
+
+    (void) ngx_copy_file(path->data, file, &cf);
+
+    ngx_free(file);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_dav_depth(ngx_http_request_t *r, ngx_int_t dflt)
+{
+    ngx_table_elt_t  *depth;
+
+    depth = r->headers_in.depth;
+
+    if (depth == NULL) {
+        return dflt;
+    }
+
+    if (depth->value.len == 1) {
+
+        if (depth->value.data[0] == '0') {
+            return 0;
+        }
+
+        if (depth->value.data[0] == '1') {
+            return 1;
+        }
+
+    } else {
+
+        if (depth->value.len == sizeof("infinity") - 1
+            && ngx_strcmp(depth->value.data, "infinity") == 0)
+        {
+            return NGX_HTTP_DAV_INFINITY_DEPTH;
+        }
+    }
+
+    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                  "client sent invalid \"Depth\" header: \"%V\"",
+                  &depth->value);
+
+    return NGX_HTTP_DAV_INVALID_DEPTH;
+}
+
+
+static ngx_int_t
+ngx_http_dav_error(ngx_log_t *log, ngx_err_t err, ngx_int_t not_found,
+    char *failed, u_char *path)
+{
+    ngx_int_t   rc;
+    ngx_uint_t  level;
+
+    if (err == NGX_ENOENT || err == NGX_ENOTDIR || err == NGX_ENAMETOOLONG) {
+        level = NGX_LOG_ERR;
+        rc = not_found;
+
+    } else if (err == NGX_EACCES || err == NGX_EPERM) {
+        level = NGX_LOG_ERR;
+        rc = NGX_HTTP_FORBIDDEN;
+
+    } else if (err == NGX_EEXIST) {
+        level = NGX_LOG_ERR;
+        rc = NGX_HTTP_NOT_ALLOWED;
+
+    } else if (err == NGX_ENOSPC) {
+        level = NGX_LOG_CRIT;
+        rc = NGX_HTTP_INSUFFICIENT_STORAGE;
+
+    } else {
+        level = NGX_LOG_CRIT;
+        rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    ngx_log_error(level, log, err, "%s \"%s\" failed", failed, path);
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_http_dav_location(ngx_http_request_t *r, u_char *path)
+{
+    u_char                    *location;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    r->headers_out.location = ngx_list_push(&r->headers_out.headers);
+    if (r->headers_out.location == NULL) {
+        return NGX_ERROR;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (!clcf->alias && clcf->root_lengths == NULL) {
+        location = path + clcf->root.len;
+
+    } else {
+        location = ngx_pnalloc(r->pool, r->uri.len);
+        if (location == NULL) {
+            ngx_http_clear_location(r);
+            return NGX_ERROR;
+        }
+
+        ngx_memcpy(location, r->uri.data, r->uri.len);
+    }
+
+    r->headers_out.location->hash = 1;
+    ngx_str_set(&r->headers_out.location->key, "Location");
+    r->headers_out.location->value.len = r->uri.len;
+    r->headers_out.location->value.data = location;
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_dav_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_dav_loc_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_dav_loc_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->methods = 0;
+     */
+
+    conf->min_delete_depth = NGX_CONF_UNSET_UINT;
+    conf->access = NGX_CONF_UNSET_UINT;
+    conf->create_full_put_path = NGX_CONF_UNSET;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_dav_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_dav_loc_conf_t  *prev = parent;
+    ngx_http_dav_loc_conf_t  *conf = child;
+
+    ngx_conf_merge_bitmask_value(conf->methods, prev->methods,
+                         (NGX_CONF_BITMASK_SET|NGX_HTTP_DAV_OFF));
+
+    ngx_conf_merge_uint_value(conf->min_delete_depth,
+                         prev->min_delete_depth, 0);
+
+    ngx_conf_merge_uint_value(conf->access, prev->access, 0600);
+
+    ngx_conf_merge_value(conf->create_full_put_path,
+                         prev->create_full_put_path, 0);
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_dav_init(ngx_conf_t *cf)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_dav_handler;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_degradation_module.c b/nginx/src/http/modules/ngx_http_degradation_module.c
new file mode 100644 (file)
index 0000000..b9c65cd
--- /dev/null
@@ -0,0 +1,243 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    size_t      sbrk_size;
+} ngx_http_degradation_main_conf_t;
+
+
+typedef struct {
+    ngx_uint_t  degrade;
+} ngx_http_degradation_loc_conf_t;
+
+
+static ngx_conf_enum_t  ngx_http_degrade[] = {
+    { ngx_string("204"), 204 },
+    { ngx_string("444"), 444 },
+    { ngx_null_string, 0 }
+};
+
+
+static void *ngx_http_degradation_create_main_conf(ngx_conf_t *cf);
+static void *ngx_http_degradation_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_degradation_merge_loc_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static char *ngx_http_degradation(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static ngx_int_t ngx_http_degradation_init(ngx_conf_t *cf);
+
+
+static ngx_command_t  ngx_http_degradation_commands[] = {
+
+    { ngx_string("degradation"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+      ngx_http_degradation,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("degrade"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_degradation_loc_conf_t, degrade),
+      &ngx_http_degrade },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_degradation_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_degradation_init,             /* postconfiguration */
+
+    ngx_http_degradation_create_main_conf, /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_degradation_create_loc_conf,  /* create location configuration */
+    ngx_http_degradation_merge_loc_conf    /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_degradation_module = {
+    NGX_MODULE_V1,
+    &ngx_http_degradation_module_ctx,      /* module context */
+    ngx_http_degradation_commands,         /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_degradation_handler(ngx_http_request_t *r)
+{
+    ngx_http_degradation_loc_conf_t  *dlcf;
+
+    dlcf = ngx_http_get_module_loc_conf(r, ngx_http_degradation_module);
+
+    if (dlcf->degrade && ngx_http_degraded(r)) {
+        return dlcf->degrade;
+    }
+
+    return NGX_DECLINED;
+}
+
+
+ngx_uint_t
+ngx_http_degraded(ngx_http_request_t *r)
+{
+    time_t                             now;
+    ngx_uint_t                         log;
+    static size_t                      sbrk_size;
+    static time_t                      sbrk_time;
+    ngx_http_degradation_main_conf_t  *dmcf;
+
+    dmcf = ngx_http_get_module_main_conf(r, ngx_http_degradation_module);
+
+    if (dmcf->sbrk_size) {
+
+        log = 0;
+        now = ngx_time();
+
+        /* lock mutex */
+
+        if (now != sbrk_time) {
+
+            /*
+             * ELF/i386 is loaded at 0x08000000, 128M
+             * ELF/amd64 is loaded at 0x00400000, 4M
+             *
+             * use a function address to subtract the loading address
+             */
+
+            sbrk_size = (size_t) sbrk(0) - ((uintptr_t) ngx_palloc & ~0x3FFFFF);
+            sbrk_time = now;
+            log = 1;
+        }
+
+        /* unlock mutex */
+
+        if (sbrk_size >= dmcf->sbrk_size) {
+            if (log) {
+                ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
+                              "degradation sbrk:%uzM",
+                              sbrk_size / (1024 * 1024));
+            }
+
+            return 1;
+        }
+    }
+
+    return 0;
+}
+
+
+static void *
+ngx_http_degradation_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_http_degradation_main_conf_t  *dmcf;
+
+    dmcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_degradation_main_conf_t));
+    if (dmcf == NULL) {
+        return NULL;
+    }
+
+    return dmcf;
+}
+
+
+static void *
+ngx_http_degradation_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_degradation_loc_conf_t  *conf;
+
+    conf = ngx_palloc(cf->pool, sizeof(ngx_http_degradation_loc_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    conf->degrade = NGX_CONF_UNSET_UINT;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_degradation_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_degradation_loc_conf_t  *prev = parent;
+    ngx_http_degradation_loc_conf_t  *conf = child;
+
+    ngx_conf_merge_uint_value(conf->degrade, prev->degrade, 0);
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_degradation(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_degradation_main_conf_t  *dmcf = conf;
+
+    ngx_str_t  *value, s;
+
+    value = cf->args->elts;
+
+    if (ngx_strncmp(value[1].data, "sbrk=", 5) == 0) {
+
+        s.len = value[1].len - 5;
+        s.data = value[1].data + 5;
+
+        dmcf->sbrk_size = ngx_parse_size(&s);
+        if (dmcf->sbrk_size == (size_t) NGX_ERROR) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid sbrk size \"%V\"", &value[1]);
+            return NGX_CONF_ERROR;
+        }
+
+        return NGX_CONF_OK;
+    }
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "invalid parameter \"%V\"", &value[1]);
+
+    return NGX_CONF_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_degradation_init(ngx_conf_t *cf)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_degradation_handler;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_empty_gif_module.c b/nginx/src/http/modules/ngx_http_empty_gif_module.c
new file mode 100644 (file)
index 0000000..04114dc
--- /dev/null
@@ -0,0 +1,140 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static char *ngx_http_empty_gif(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+static ngx_command_t  ngx_http_empty_gif_commands[] = {
+
+    { ngx_string("empty_gif"),
+      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
+      ngx_http_empty_gif,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+/* the minimal single pixel transparent GIF, 43 bytes */
+
+static u_char  ngx_empty_gif[] = {
+
+    'G', 'I', 'F', '8', '9', 'a',  /* header                                 */
+
+                                   /* logical screen descriptor              */
+    0x01, 0x00,                    /* logical screen width                   */
+    0x01, 0x00,                    /* logical screen height                  */
+    0x80,                          /* global 1-bit color table               */
+    0x01,                          /* background color #1                    */
+    0x00,                          /* no aspect ratio                        */
+
+                                   /* global color table                     */
+    0x00, 0x00, 0x00,              /* #0: black                              */
+    0xff, 0xff, 0xff,              /* #1: white                              */
+
+                                   /* graphic control extension              */
+    0x21,                          /* extension introducer                   */
+    0xf9,                          /* graphic control label                  */
+    0x04,                          /* block size                             */
+    0x01,                          /* transparent color is given,            */
+                                   /*     no disposal specified,             */
+                                   /*     user input is not expected         */
+    0x00, 0x00,                    /* delay time                             */
+    0x01,                          /* transparent color #1                   */
+    0x00,                          /* block terminator                       */
+
+                                   /* image descriptor                       */
+    0x2c,                          /* image separator                        */
+    0x00, 0x00,                    /* image left position                    */
+    0x00, 0x00,                    /* image top position                     */
+    0x01, 0x00,                    /* image width                            */
+    0x01, 0x00,                    /* image height                           */
+    0x00,                          /* no local color table, no interlaced    */
+
+                                   /* table based image data                 */
+    0x02,                          /* LZW minimum code size,                 */
+                                   /*     must be at least 2-bit             */
+    0x02,                          /* block size                             */
+    0x4c, 0x01,                    /* compressed bytes 01_001_100, 0000000_1 */
+                                   /* 100: clear code                        */
+                                   /* 001: 1                                 */
+                                   /* 101: end of information code           */
+    0x00,                          /* block terminator                       */
+
+    0x3B                           /* trailer                                */
+};
+
+
+static ngx_http_module_t  ngx_http_empty_gif_module_ctx = {
+    NULL,                          /* preconfiguration */
+    NULL,                          /* postconfiguration */
+
+    NULL,                          /* create main configuration */
+    NULL,                          /* init main configuration */
+
+    NULL,                          /* create server configuration */
+    NULL,                          /* merge server configuration */
+
+    NULL,                          /* create location configuration */
+    NULL                           /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_empty_gif_module = {
+    NGX_MODULE_V1,
+    &ngx_http_empty_gif_module_ctx, /* module context */
+    ngx_http_empty_gif_commands,   /* module directives */
+    NGX_HTTP_MODULE,               /* module type */
+    NULL,                          /* init master */
+    NULL,                          /* init module */
+    NULL,                          /* init process */
+    NULL,                          /* init thread */
+    NULL,                          /* exit thread */
+    NULL,                          /* exit process */
+    NULL,                          /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t  ngx_http_gif_type = ngx_string("image/gif");
+
+
+static ngx_int_t
+ngx_http_empty_gif_handler(ngx_http_request_t *r)
+{
+    ngx_http_complex_value_t  cv;
+
+    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
+        return NGX_HTTP_NOT_ALLOWED;
+    }
+
+    ngx_memzero(&cv, sizeof(ngx_http_complex_value_t));
+
+    cv.value.len = sizeof(ngx_empty_gif);
+    cv.value.data = ngx_empty_gif;
+    r->headers_out.last_modified_time = 23349600;
+
+    return ngx_http_send_response(r, NGX_HTTP_OK, &ngx_http_gif_type, &cv);
+}
+
+
+static char *
+ngx_http_empty_gif(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_loc_conf_t  *clcf;
+
+    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+    clcf->handler = ngx_http_empty_gif_handler;
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_fastcgi_module.c b/nginx/src/http/modules/ngx_http_fastcgi_module.c
new file mode 100644 (file)
index 0000000..3eec1b7
--- /dev/null
@@ -0,0 +1,3762 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_array_t                    caches;  /* ngx_http_file_cache_t * */
+} ngx_http_fastcgi_main_conf_t;
+
+
+typedef struct {
+    ngx_array_t                   *flushes;
+    ngx_array_t                   *lengths;
+    ngx_array_t                   *values;
+    ngx_uint_t                     number;
+    ngx_hash_t                     hash;
+} ngx_http_fastcgi_params_t;
+
+
+typedef struct {
+    ngx_http_upstream_conf_t       upstream;
+
+    ngx_str_t                      index;
+
+    ngx_http_fastcgi_params_t      params;
+#if (NGX_HTTP_CACHE)
+    ngx_http_fastcgi_params_t      params_cache;
+#endif
+
+    ngx_array_t                   *params_source;
+    ngx_array_t                   *catch_stderr;
+
+    ngx_array_t                   *fastcgi_lengths;
+    ngx_array_t                   *fastcgi_values;
+
+    ngx_flag_t                     keep_conn;
+
+#if (NGX_HTTP_CACHE)
+    ngx_http_complex_value_t       cache_key;
+#endif
+
+#if (NGX_PCRE)
+    ngx_regex_t                   *split_regex;
+    ngx_str_t                      split_name;
+#endif
+} ngx_http_fastcgi_loc_conf_t;
+
+
+typedef enum {
+    ngx_http_fastcgi_st_version = 0,
+    ngx_http_fastcgi_st_type,
+    ngx_http_fastcgi_st_request_id_hi,
+    ngx_http_fastcgi_st_request_id_lo,
+    ngx_http_fastcgi_st_content_length_hi,
+    ngx_http_fastcgi_st_content_length_lo,
+    ngx_http_fastcgi_st_padding_length,
+    ngx_http_fastcgi_st_reserved,
+    ngx_http_fastcgi_st_data,
+    ngx_http_fastcgi_st_padding
+} ngx_http_fastcgi_state_e;
+
+
+typedef struct {
+    u_char                        *start;
+    u_char                        *end;
+} ngx_http_fastcgi_split_part_t;
+
+
+typedef struct {
+    ngx_http_fastcgi_state_e       state;
+    u_char                        *pos;
+    u_char                        *last;
+    ngx_uint_t                     type;
+    size_t                         length;
+    size_t                         padding;
+
+    ngx_chain_t                   *free;
+    ngx_chain_t                   *busy;
+
+    unsigned                       fastcgi_stdout:1;
+    unsigned                       large_stderr:1;
+    unsigned                       header_sent:1;
+
+    ngx_array_t                   *split_parts;
+
+    ngx_str_t                      script_name;
+    ngx_str_t                      path_info;
+} ngx_http_fastcgi_ctx_t;
+
+
+#define NGX_HTTP_FASTCGI_RESPONDER      1
+
+#define NGX_HTTP_FASTCGI_KEEP_CONN      1
+
+#define NGX_HTTP_FASTCGI_BEGIN_REQUEST  1
+#define NGX_HTTP_FASTCGI_ABORT_REQUEST  2
+#define NGX_HTTP_FASTCGI_END_REQUEST    3
+#define NGX_HTTP_FASTCGI_PARAMS         4
+#define NGX_HTTP_FASTCGI_STDIN          5
+#define NGX_HTTP_FASTCGI_STDOUT         6
+#define NGX_HTTP_FASTCGI_STDERR         7
+#define NGX_HTTP_FASTCGI_DATA           8
+
+
+typedef struct {
+    u_char  version;
+    u_char  type;
+    u_char  request_id_hi;
+    u_char  request_id_lo;
+    u_char  content_length_hi;
+    u_char  content_length_lo;
+    u_char  padding_length;
+    u_char  reserved;
+} ngx_http_fastcgi_header_t;
+
+
+typedef struct {
+    u_char  role_hi;
+    u_char  role_lo;
+    u_char  flags;
+    u_char  reserved[5];
+} ngx_http_fastcgi_begin_request_t;
+
+
+typedef struct {
+    u_char  version;
+    u_char  type;
+    u_char  request_id_hi;
+    u_char  request_id_lo;
+} ngx_http_fastcgi_header_small_t;
+
+
+typedef struct {
+    ngx_http_fastcgi_header_t         h0;
+    ngx_http_fastcgi_begin_request_t  br;
+    ngx_http_fastcgi_header_small_t   h1;
+} ngx_http_fastcgi_request_start_t;
+
+
+static ngx_int_t ngx_http_fastcgi_eval(ngx_http_request_t *r,
+    ngx_http_fastcgi_loc_conf_t *flcf);
+#if (NGX_HTTP_CACHE)
+static ngx_int_t ngx_http_fastcgi_create_key(ngx_http_request_t *r);
+#endif
+static ngx_int_t ngx_http_fastcgi_create_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_fastcgi_reinit_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_fastcgi_body_output_filter(void *data,
+    ngx_chain_t *in);
+static ngx_int_t ngx_http_fastcgi_process_header(ngx_http_request_t *r);
+static ngx_int_t ngx_http_fastcgi_input_filter_init(void *data);
+static ngx_int_t ngx_http_fastcgi_input_filter(ngx_event_pipe_t *p,
+    ngx_buf_t *buf);
+static ngx_int_t ngx_http_fastcgi_non_buffered_filter(void *data,
+    ssize_t bytes);
+static ngx_int_t ngx_http_fastcgi_process_record(ngx_http_request_t *r,
+    ngx_http_fastcgi_ctx_t *f);
+static void ngx_http_fastcgi_abort_request(ngx_http_request_t *r);
+static void ngx_http_fastcgi_finalize_request(ngx_http_request_t *r,
+    ngx_int_t rc);
+
+static ngx_int_t ngx_http_fastcgi_add_variables(ngx_conf_t *cf);
+static void *ngx_http_fastcgi_create_main_conf(ngx_conf_t *cf);
+static void *ngx_http_fastcgi_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_fastcgi_merge_loc_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+static ngx_int_t ngx_http_fastcgi_init_params(ngx_conf_t *cf,
+    ngx_http_fastcgi_loc_conf_t *conf, ngx_http_fastcgi_params_t *params,
+    ngx_keyval_t *default_params);
+
+static ngx_int_t ngx_http_fastcgi_script_name_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_fastcgi_path_info_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_http_fastcgi_ctx_t *ngx_http_fastcgi_split(ngx_http_request_t *r,
+    ngx_http_fastcgi_loc_conf_t *flcf);
+
+static char *ngx_http_fastcgi_pass(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_fastcgi_split_path_info(ngx_conf_t *cf,
+    ngx_command_t *cmd, void *conf);
+static char *ngx_http_fastcgi_store(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+#if (NGX_HTTP_CACHE)
+static char *ngx_http_fastcgi_cache(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_fastcgi_cache_key(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+#endif
+
+static char *ngx_http_fastcgi_lowat_check(ngx_conf_t *cf, void *post,
+    void *data);
+
+
+static ngx_conf_post_t  ngx_http_fastcgi_lowat_post =
+    { ngx_http_fastcgi_lowat_check };
+
+
+static ngx_conf_bitmask_t  ngx_http_fastcgi_next_upstream_masks[] = {
+    { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR },
+    { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT },
+    { ngx_string("invalid_header"), NGX_HTTP_UPSTREAM_FT_INVALID_HEADER },
+    { ngx_string("non_idempotent"), NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT },
+    { ngx_string("http_500"), NGX_HTTP_UPSTREAM_FT_HTTP_500 },
+    { ngx_string("http_503"), NGX_HTTP_UPSTREAM_FT_HTTP_503 },
+    { ngx_string("http_403"), NGX_HTTP_UPSTREAM_FT_HTTP_403 },
+    { ngx_string("http_404"), NGX_HTTP_UPSTREAM_FT_HTTP_404 },
+    { ngx_string("http_429"), NGX_HTTP_UPSTREAM_FT_HTTP_429 },
+    { ngx_string("updating"), NGX_HTTP_UPSTREAM_FT_UPDATING },
+    { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF },
+    { ngx_null_string, 0 }
+};
+
+
+ngx_module_t  ngx_http_fastcgi_module;
+
+
+static ngx_command_t  ngx_http_fastcgi_commands[] = {
+
+    { ngx_string("fastcgi_pass"),
+      NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
+      ngx_http_fastcgi_pass,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("fastcgi_index"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, index),
+      NULL },
+
+    { ngx_string("fastcgi_split_path_info"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_fastcgi_split_path_info,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("fastcgi_store"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_fastcgi_store,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("fastcgi_store_access"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
+      ngx_conf_set_access_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.store_access),
+      NULL },
+
+    { ngx_string("fastcgi_buffering"),
+      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_fastcgi_loc_conf_t, upstream.buffering),
+      NULL },
+
+    { ngx_string("fastcgi_request_buffering"),
+      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_fastcgi_loc_conf_t, upstream.request_buffering),
+      NULL },
+
+    { ngx_string("fastcgi_ignore_client_abort"),
+      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_fastcgi_loc_conf_t, upstream.ignore_client_abort),
+      NULL },
+
+    { ngx_string("fastcgi_bind"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+      ngx_http_upstream_bind_set_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.local),
+      NULL },
+
+    { ngx_string("fastcgi_connect_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.connect_timeout),
+      NULL },
+
+    { ngx_string("fastcgi_send_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.send_timeout),
+      NULL },
+
+    { ngx_string("fastcgi_send_lowat"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.send_lowat),
+      &ngx_http_fastcgi_lowat_post },
+
+    { ngx_string("fastcgi_buffer_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.buffer_size),
+      NULL },
+
+    { ngx_string("fastcgi_pass_request_headers"),
+      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_fastcgi_loc_conf_t, upstream.pass_request_headers),
+      NULL },
+
+    { ngx_string("fastcgi_pass_request_body"),
+      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_fastcgi_loc_conf_t, upstream.pass_request_body),
+      NULL },
+
+    { ngx_string("fastcgi_intercept_errors"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.intercept_errors),
+      NULL },
+
+    { ngx_string("fastcgi_read_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.read_timeout),
+      NULL },
+
+    { ngx_string("fastcgi_buffers"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+      ngx_conf_set_bufs_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.bufs),
+      NULL },
+
+    { ngx_string("fastcgi_busy_buffers_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.busy_buffers_size_conf),
+      NULL },
+
+    { ngx_string("fastcgi_force_ranges"),
+      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_fastcgi_loc_conf_t, upstream.force_ranges),
+      NULL },
+
+    { ngx_string("fastcgi_limit_rate"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.limit_rate),
+      NULL },
+
+#if (NGX_HTTP_CACHE)
+
+    { ngx_string("fastcgi_cache"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_fastcgi_cache,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("fastcgi_cache_key"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_fastcgi_cache_key,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("fastcgi_cache_path"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_2MORE,
+      ngx_http_file_cache_set_slot,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_main_conf_t, caches),
+      &ngx_http_fastcgi_module },
+
+    { ngx_string("fastcgi_cache_bypass"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_set_predicate_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_bypass),
+      NULL },
+
+    { ngx_string("fastcgi_no_cache"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_set_predicate_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.no_cache),
+      NULL },
+
+    { ngx_string("fastcgi_cache_valid"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_file_cache_valid_set_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_valid),
+      NULL },
+
+    { ngx_string("fastcgi_cache_min_uses"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_min_uses),
+      NULL },
+
+    { ngx_string("fastcgi_cache_max_range_offset"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_off_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_max_range_offset),
+      NULL },
+
+    { ngx_string("fastcgi_cache_use_stale"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_use_stale),
+      &ngx_http_fastcgi_next_upstream_masks },
+
+    { ngx_string("fastcgi_cache_methods"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_methods),
+      &ngx_http_upstream_cache_method_mask },
+
+    { ngx_string("fastcgi_cache_lock"),
+      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_fastcgi_loc_conf_t, upstream.cache_lock),
+      NULL },
+
+    { ngx_string("fastcgi_cache_lock_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_lock_timeout),
+      NULL },
+
+    { ngx_string("fastcgi_cache_lock_age"),
+      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_fastcgi_loc_conf_t, upstream.cache_lock_age),
+      NULL },
+
+    { ngx_string("fastcgi_cache_revalidate"),
+      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_fastcgi_loc_conf_t, upstream.cache_revalidate),
+      NULL },
+
+    { ngx_string("fastcgi_cache_background_update"),
+      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_fastcgi_loc_conf_t, upstream.cache_background_update),
+      NULL },
+
+#endif
+
+    { ngx_string("fastcgi_temp_path"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234,
+      ngx_conf_set_path_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.temp_path),
+      NULL },
+
+    { ngx_string("fastcgi_max_temp_file_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.max_temp_file_size_conf),
+      NULL },
+
+    { ngx_string("fastcgi_temp_file_write_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.temp_file_write_size_conf),
+      NULL },
+
+    { ngx_string("fastcgi_next_upstream"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.next_upstream),
+      &ngx_http_fastcgi_next_upstream_masks },
+
+    { ngx_string("fastcgi_next_upstream_tries"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.next_upstream_tries),
+      NULL },
+
+    { ngx_string("fastcgi_next_upstream_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.next_upstream_timeout),
+      NULL },
+
+    { ngx_string("fastcgi_param"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE23,
+      ngx_http_upstream_param_set_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, params_source),
+      NULL },
+
+    { ngx_string("fastcgi_pass_header"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.pass_headers),
+      NULL },
+
+    { ngx_string("fastcgi_hide_header"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.hide_headers),
+      NULL },
+
+    { ngx_string("fastcgi_ignore_headers"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.ignore_headers),
+      &ngx_http_upstream_ignore_headers_masks },
+
+    { ngx_string("fastcgi_catch_stderr"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, catch_stderr),
+      NULL },
+
+    { ngx_string("fastcgi_keep_conn"),
+      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_fastcgi_loc_conf_t, keep_conn),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_fastcgi_module_ctx = {
+    ngx_http_fastcgi_add_variables,        /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    ngx_http_fastcgi_create_main_conf,     /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_fastcgi_create_loc_conf,      /* create location configuration */
+    ngx_http_fastcgi_merge_loc_conf        /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_fastcgi_module = {
+    NGX_MODULE_V1,
+    &ngx_http_fastcgi_module_ctx,          /* module context */
+    ngx_http_fastcgi_commands,             /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_fastcgi_request_start_t  ngx_http_fastcgi_request_start = {
+    { 1,                                               /* version */
+      NGX_HTTP_FASTCGI_BEGIN_REQUEST,                  /* type */
+      0,                                               /* request_id_hi */
+      1,                                               /* request_id_lo */
+      0,                                               /* content_length_hi */
+      sizeof(ngx_http_fastcgi_begin_request_t),        /* content_length_lo */
+      0,                                               /* padding_length */
+      0 },                                             /* reserved */
+
+    { 0,                                               /* role_hi */
+      NGX_HTTP_FASTCGI_RESPONDER,                      /* role_lo */
+      0, /* NGX_HTTP_FASTCGI_KEEP_CONN */              /* flags */
+      { 0, 0, 0, 0, 0 } },                             /* reserved[5] */
+
+    { 1,                                               /* version */
+      NGX_HTTP_FASTCGI_PARAMS,                         /* type */
+      0,                                               /* request_id_hi */
+      1 },                                             /* request_id_lo */
+
+};
+
+
+static ngx_http_variable_t  ngx_http_fastcgi_vars[] = {
+
+    { ngx_string("fastcgi_script_name"), NULL,
+      ngx_http_fastcgi_script_name_variable, 0,
+      NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
+
+    { ngx_string("fastcgi_path_info"), NULL,
+      ngx_http_fastcgi_path_info_variable, 0,
+      NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
+
+      ngx_http_null_variable
+};
+
+
+static ngx_str_t  ngx_http_fastcgi_hide_headers[] = {
+    ngx_string("Status"),
+    ngx_string("X-Accel-Expires"),
+    ngx_string("X-Accel-Redirect"),
+    ngx_string("X-Accel-Limit-Rate"),
+    ngx_string("X-Accel-Buffering"),
+    ngx_string("X-Accel-Charset"),
+    ngx_null_string
+};
+
+
+#if (NGX_HTTP_CACHE)
+
+static ngx_keyval_t  ngx_http_fastcgi_cache_headers[] = {
+    { ngx_string("HTTP_IF_MODIFIED_SINCE"),
+      ngx_string("$upstream_cache_last_modified") },
+    { ngx_string("HTTP_IF_UNMODIFIED_SINCE"), ngx_string("") },
+    { ngx_string("HTTP_IF_NONE_MATCH"), ngx_string("$upstream_cache_etag") },
+    { ngx_string("HTTP_IF_MATCH"), ngx_string("") },
+    { ngx_string("HTTP_RANGE"), ngx_string("") },
+    { ngx_string("HTTP_IF_RANGE"), ngx_string("") },
+    { ngx_null_string, ngx_null_string }
+};
+
+#endif
+
+
+static ngx_path_init_t  ngx_http_fastcgi_temp_path = {
+    ngx_string(NGX_HTTP_FASTCGI_TEMP_PATH), { 1, 2, 0 }
+};
+
+
+static ngx_int_t
+ngx_http_fastcgi_handler(ngx_http_request_t *r)
+{
+    ngx_int_t                      rc;
+    ngx_http_upstream_t           *u;
+    ngx_http_fastcgi_ctx_t        *f;
+    ngx_http_fastcgi_loc_conf_t   *flcf;
+#if (NGX_HTTP_CACHE)
+    ngx_http_fastcgi_main_conf_t  *fmcf;
+#endif
+
+    if (ngx_http_upstream_create(r) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    f = ngx_pcalloc(r->pool, sizeof(ngx_http_fastcgi_ctx_t));
+    if (f == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    ngx_http_set_ctx(r, f, ngx_http_fastcgi_module);
+
+    flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module);
+
+    if (flcf->fastcgi_lengths) {
+        if (ngx_http_fastcgi_eval(r, flcf) != NGX_OK) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+    }
+
+    u = r->upstream;
+
+    ngx_str_set(&u->schema, "fastcgi://");
+    u->output.tag = (ngx_buf_tag_t) &ngx_http_fastcgi_module;
+
+    u->conf = &flcf->upstream;
+
+#if (NGX_HTTP_CACHE)
+    fmcf = ngx_http_get_module_main_conf(r, ngx_http_fastcgi_module);
+
+    u->caches = &fmcf->caches;
+    u->create_key = ngx_http_fastcgi_create_key;
+#endif
+
+    u->create_request = ngx_http_fastcgi_create_request;
+    u->reinit_request = ngx_http_fastcgi_reinit_request;
+    u->process_header = ngx_http_fastcgi_process_header;
+    u->abort_request = ngx_http_fastcgi_abort_request;
+    u->finalize_request = ngx_http_fastcgi_finalize_request;
+    r->state = 0;
+
+    u->buffering = flcf->upstream.buffering;
+
+    u->pipe = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t));
+    if (u->pipe == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    u->pipe->input_filter = ngx_http_fastcgi_input_filter;
+    u->pipe->input_ctx = r;
+
+    u->input_filter_init = ngx_http_fastcgi_input_filter_init;
+    u->input_filter = ngx_http_fastcgi_non_buffered_filter;
+    u->input_filter_ctx = r;
+
+    if (!flcf->upstream.request_buffering
+        && flcf->upstream.pass_request_body)
+    {
+        r->request_body_no_buffering = 1;
+    }
+
+    rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);
+
+    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+        return rc;
+    }
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_eval(ngx_http_request_t *r, ngx_http_fastcgi_loc_conf_t *flcf)
+{
+    ngx_url_t             url;
+    ngx_http_upstream_t  *u;
+
+    ngx_memzero(&url, sizeof(ngx_url_t));
+
+    if (ngx_http_script_run(r, &url.url, flcf->fastcgi_lengths->elts, 0,
+                            flcf->fastcgi_values->elts)
+        == NULL)
+    {
+        return NGX_ERROR;
+    }
+
+    url.no_resolve = 1;
+
+    if (ngx_parse_url(r->pool, &url) != NGX_OK) {
+        if (url.err) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "%s in upstream \"%V\"", url.err, &url.url);
+        }
+
+        return NGX_ERROR;
+    }
+
+    u = r->upstream;
+
+    u->resolved = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t));
+    if (u->resolved == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (url.addrs) {
+        u->resolved->sockaddr = url.addrs[0].sockaddr;
+        u->resolved->socklen = url.addrs[0].socklen;
+        u->resolved->name = url.addrs[0].name;
+        u->resolved->naddrs = 1;
+    }
+
+    u->resolved->host = url.host;
+    u->resolved->port = url.port;
+    u->resolved->no_port = url.no_port;
+
+    return NGX_OK;
+}
+
+
+#if (NGX_HTTP_CACHE)
+
+static ngx_int_t
+ngx_http_fastcgi_create_key(ngx_http_request_t *r)
+{
+    ngx_str_t                    *key;
+    ngx_http_fastcgi_loc_conf_t  *flcf;
+
+    key = ngx_array_push(&r->cache->keys);
+    if (key == NULL) {
+        return NGX_ERROR;
+    }
+
+    flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module);
+
+    if (ngx_http_complex_value(r, &flcf->cache_key, key) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_fastcgi_create_request(ngx_http_request_t *r)
+{
+    off_t                         file_pos;
+    u_char                        ch, *pos, *lowcase_key;
+    size_t                        size, len, key_len, val_len, padding,
+                                  allocated;
+    ngx_uint_t                    i, n, next, hash, skip_empty, header_params;
+    ngx_buf_t                    *b;
+    ngx_chain_t                  *cl, *body;
+    ngx_list_part_t              *part;
+    ngx_table_elt_t              *header, **ignored;
+    ngx_http_upstream_t          *u;
+    ngx_http_script_code_pt       code;
+    ngx_http_script_engine_t      e, le;
+    ngx_http_fastcgi_header_t    *h;
+    ngx_http_fastcgi_params_t    *params;
+    ngx_http_fastcgi_loc_conf_t  *flcf;
+    ngx_http_script_len_code_pt   lcode;
+
+    len = 0;
+    header_params = 0;
+    ignored = NULL;
+
+    u = r->upstream;
+
+    flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module);
+
+#if (NGX_HTTP_CACHE)
+    params = u->cacheable ? &flcf->params_cache : &flcf->params;
+#else
+    params = &flcf->params;
+#endif
+
+    if (params->lengths) {
+        ngx_memzero(&le, sizeof(ngx_http_script_engine_t));
+
+        ngx_http_script_flush_no_cacheable_variables(r, params->flushes);
+        le.flushed = 1;
+
+        le.ip = params->lengths->elts;
+        le.request = r;
+
+        while (*(uintptr_t *) le.ip) {
+
+            lcode = *(ngx_http_script_len_code_pt *) le.ip;
+            key_len = lcode(&le);
+
+            lcode = *(ngx_http_script_len_code_pt *) le.ip;
+            skip_empty = lcode(&le);
+
+            for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) {
+                lcode = *(ngx_http_script_len_code_pt *) le.ip;
+            }
+            le.ip += sizeof(uintptr_t);
+
+            if (skip_empty && val_len == 0) {
+                continue;
+            }
+
+            len += 1 + key_len + ((val_len > 127) ? 4 : 1) + val_len;
+        }
+    }
+
+    if (flcf->upstream.pass_request_headers) {
+
+        allocated = 0;
+        lowcase_key = NULL;
+
+        if (params->number) {
+            n = 0;
+            part = &r->headers_in.headers.part;
+
+            while (part) {
+                n += part->nelts;
+                part = part->next;
+            }
+
+            ignored = ngx_palloc(r->pool, n * sizeof(void *));
+            if (ignored == NULL) {
+                return NGX_ERROR;
+            }
+        }
+
+        part = &r->headers_in.headers.part;
+        header = part->elts;
+
+        for (i = 0; /* void */; i++) {
+
+            if (i >= part->nelts) {
+                if (part->next == NULL) {
+                    break;
+                }
+
+                part = part->next;
+                header = part->elts;
+                i = 0;
+            }
+
+            if (params->number) {
+                if (allocated < header[i].key.len) {
+                    allocated = header[i].key.len + 16;
+                    lowcase_key = ngx_pnalloc(r->pool, allocated);
+                    if (lowcase_key == NULL) {
+                        return NGX_ERROR;
+                    }
+                }
+
+                hash = 0;
+
+                for (n = 0; n < header[i].key.len; n++) {
+                    ch = header[i].key.data[n];
+
+                    if (ch >= 'A' && ch <= 'Z') {
+                        ch |= 0x20;
+
+                    } else if (ch == '-') {
+                        ch = '_';
+                    }
+
+                    hash = ngx_hash(hash, ch);
+                    lowcase_key[n] = ch;
+                }
+
+                if (ngx_hash_find(&params->hash, hash, lowcase_key, n)) {
+                    ignored[header_params++] = &header[i];
+                    continue;
+                }
+
+                n += sizeof("HTTP_") - 1;
+
+            } else {
+                n = sizeof("HTTP_") - 1 + header[i].key.len;
+            }
+
+            len += ((n > 127) ? 4 : 1) + ((header[i].value.len > 127) ? 4 : 1)
+                + n + header[i].value.len;
+        }
+    }
+
+
+    if (len > 65535) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                      "fastcgi request record is too big: %uz", len);
+        return NGX_ERROR;
+    }
+
+
+    padding = 8 - len % 8;
+    padding = (padding == 8) ? 0 : padding;
+
+
+    size = sizeof(ngx_http_fastcgi_header_t)
+           + sizeof(ngx_http_fastcgi_begin_request_t)
+
+           + sizeof(ngx_http_fastcgi_header_t)  /* NGX_HTTP_FASTCGI_PARAMS */
+           + len + padding
+           + sizeof(ngx_http_fastcgi_header_t)  /* NGX_HTTP_FASTCGI_PARAMS */
+
+           + sizeof(ngx_http_fastcgi_header_t); /* NGX_HTTP_FASTCGI_STDIN */
+
+
+    b = ngx_create_temp_buf(r->pool, size);
+    if (b == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl = ngx_alloc_chain_link(r->pool);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl->buf = b;
+
+    ngx_http_fastcgi_request_start.br.flags =
+        flcf->keep_conn ? NGX_HTTP_FASTCGI_KEEP_CONN : 0;
+
+    ngx_memcpy(b->pos, &ngx_http_fastcgi_request_start,
+               sizeof(ngx_http_fastcgi_request_start_t));
+
+    h = (ngx_http_fastcgi_header_t *)
+             (b->pos + sizeof(ngx_http_fastcgi_header_t)
+                     + sizeof(ngx_http_fastcgi_begin_request_t));
+
+    h->content_length_hi = (u_char) ((len >> 8) & 0xff);
+    h->content_length_lo = (u_char) (len & 0xff);
+    h->padding_length = (u_char) padding;
+    h->reserved = 0;
+
+    b->last = b->pos + sizeof(ngx_http_fastcgi_header_t)
+                     + sizeof(ngx_http_fastcgi_begin_request_t)
+                     + sizeof(ngx_http_fastcgi_header_t);
+
+
+    if (params->lengths) {
+        ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
+
+        e.ip = params->values->elts;
+        e.pos = b->last;
+        e.request = r;
+        e.flushed = 1;
+
+        le.ip = params->lengths->elts;
+
+        while (*(uintptr_t *) le.ip) {
+
+            lcode = *(ngx_http_script_len_code_pt *) le.ip;
+            key_len = (u_char) lcode(&le);
+
+            lcode = *(ngx_http_script_len_code_pt *) le.ip;
+            skip_empty = lcode(&le);
+
+            for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) {
+                lcode = *(ngx_http_script_len_code_pt *) le.ip;
+            }
+            le.ip += sizeof(uintptr_t);
+
+            if (skip_empty && val_len == 0) {
+                e.skip = 1;
+
+                while (*(uintptr_t *) e.ip) {
+                    code = *(ngx_http_script_code_pt *) e.ip;
+                    code((ngx_http_script_engine_t *) &e);
+                }
+                e.ip += sizeof(uintptr_t);
+
+                e.skip = 0;
+
+                continue;
+            }
+
+            *e.pos++ = (u_char) key_len;
+
+            if (val_len > 127) {
+                *e.pos++ = (u_char) (((val_len >> 24) & 0x7f) | 0x80);
+                *e.pos++ = (u_char) ((val_len >> 16) & 0xff);
+                *e.pos++ = (u_char) ((val_len >> 8) & 0xff);
+                *e.pos++ = (u_char) (val_len & 0xff);
+
+            } else {
+                *e.pos++ = (u_char) val_len;
+            }
+
+            while (*(uintptr_t *) e.ip) {
+                code = *(ngx_http_script_code_pt *) e.ip;
+                code((ngx_http_script_engine_t *) &e);
+            }
+            e.ip += sizeof(uintptr_t);
+
+            ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "fastcgi param: \"%*s: %*s\"",
+                           key_len, e.pos - (key_len + val_len),
+                           val_len, e.pos - val_len);
+        }
+
+        b->last = e.pos;
+    }
+
+
+    if (flcf->upstream.pass_request_headers) {
+
+        part = &r->headers_in.headers.part;
+        header = part->elts;
+
+        for (i = 0; /* void */; i++) {
+
+            if (i >= part->nelts) {
+                if (part->next == NULL) {
+                    break;
+                }
+
+                part = part->next;
+                header = part->elts;
+                i = 0;
+            }
+
+            for (n = 0; n < header_params; n++) {
+                if (&header[i] == ignored[n]) {
+                    goto next;
+                }
+            }
+
+            key_len = sizeof("HTTP_") - 1 + header[i].key.len;
+            if (key_len > 127) {
+                *b->last++ = (u_char) (((key_len >> 24) & 0x7f) | 0x80);
+                *b->last++ = (u_char) ((key_len >> 16) & 0xff);
+                *b->last++ = (u_char) ((key_len >> 8) & 0xff);
+                *b->last++ = (u_char) (key_len & 0xff);
+
+            } else {
+                *b->last++ = (u_char) key_len;
+            }
+
+            val_len = header[i].value.len;
+            if (val_len > 127) {
+                *b->last++ = (u_char) (((val_len >> 24) & 0x7f) | 0x80);
+                *b->last++ = (u_char) ((val_len >> 16) & 0xff);
+                *b->last++ = (u_char) ((val_len >> 8) & 0xff);
+                *b->last++ = (u_char) (val_len & 0xff);
+
+            } else {
+                *b->last++ = (u_char) val_len;
+            }
+
+            b->last = ngx_cpymem(b->last, "HTTP_", sizeof("HTTP_") - 1);
+
+            for (n = 0; n < header[i].key.len; n++) {
+                ch = header[i].key.data[n];
+
+                if (ch >= 'a' && ch <= 'z') {
+                    ch &= ~0x20;
+
+                } else if (ch == '-') {
+                    ch = '_';
+                }
+
+                *b->last++ = ch;
+            }
+
+            b->last = ngx_copy(b->last, header[i].value.data, val_len);
+
+            ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "fastcgi param: \"%*s: %*s\"",
+                           key_len, b->last - (key_len + val_len),
+                           val_len, b->last - val_len);
+        next:
+
+            continue;
+        }
+    }
+
+
+    if (padding) {
+        ngx_memzero(b->last, padding);
+        b->last += padding;
+    }
+
+
+    h = (ngx_http_fastcgi_header_t *) b->last;
+    b->last += sizeof(ngx_http_fastcgi_header_t);
+
+    h->version = 1;
+    h->type = NGX_HTTP_FASTCGI_PARAMS;
+    h->request_id_hi = 0;
+    h->request_id_lo = 1;
+    h->content_length_hi = 0;
+    h->content_length_lo = 0;
+    h->padding_length = 0;
+    h->reserved = 0;
+
+    if (r->request_body_no_buffering) {
+
+        u->request_bufs = cl;
+
+        u->output.output_filter = ngx_http_fastcgi_body_output_filter;
+        u->output.filter_ctx = r;
+
+    } else if (flcf->upstream.pass_request_body) {
+
+        body = u->request_bufs;
+        u->request_bufs = cl;
+
+#if (NGX_SUPPRESS_WARN)
+        file_pos = 0;
+        pos = NULL;
+#endif
+
+        while (body) {
+
+            if (ngx_buf_special(body->buf)) {
+                body = body->next;
+                continue;
+            }
+
+            if (body->buf->in_file) {
+                file_pos = body->buf->file_pos;
+
+            } else {
+                pos = body->buf->pos;
+            }
+
+            next = 0;
+
+            do {
+                b = ngx_alloc_buf(r->pool);
+                if (b == NULL) {
+                    return NGX_ERROR;
+                }
+
+                ngx_memcpy(b, body->buf, sizeof(ngx_buf_t));
+
+                if (body->buf->in_file) {
+                    b->file_pos = file_pos;
+                    file_pos += 32 * 1024;
+
+                    if (file_pos >= body->buf->file_last) {
+                        file_pos = body->buf->file_last;
+                        next = 1;
+                    }
+
+                    b->file_last = file_pos;
+                    len = (ngx_uint_t) (file_pos - b->file_pos);
+
+                } else {
+                    b->pos = pos;
+                    b->start = pos;
+                    pos += 32 * 1024;
+
+                    if (pos >= body->buf->last) {
+                        pos = body->buf->last;
+                        next = 1;
+                    }
+
+                    b->last = pos;
+                    len = (ngx_uint_t) (pos - b->pos);
+                }
+
+                padding = 8 - len % 8;
+                padding = (padding == 8) ? 0 : padding;
+
+                h = (ngx_http_fastcgi_header_t *) cl->buf->last;
+                cl->buf->last += sizeof(ngx_http_fastcgi_header_t);
+
+                h->version = 1;
+                h->type = NGX_HTTP_FASTCGI_STDIN;
+                h->request_id_hi = 0;
+                h->request_id_lo = 1;
+                h->content_length_hi = (u_char) ((len >> 8) & 0xff);
+                h->content_length_lo = (u_char) (len & 0xff);
+                h->padding_length = (u_char) padding;
+                h->reserved = 0;
+
+                cl->next = ngx_alloc_chain_link(r->pool);
+                if (cl->next == NULL) {
+                    return NGX_ERROR;
+                }
+
+                cl = cl->next;
+                cl->buf = b;
+
+                b = ngx_create_temp_buf(r->pool,
+                                        sizeof(ngx_http_fastcgi_header_t)
+                                        + padding);
+                if (b == NULL) {
+                    return NGX_ERROR;
+                }
+
+                if (padding) {
+                    ngx_memzero(b->last, padding);
+                    b->last += padding;
+                }
+
+                cl->next = ngx_alloc_chain_link(r->pool);
+                if (cl->next == NULL) {
+                    return NGX_ERROR;
+                }
+
+                cl = cl->next;
+                cl->buf = b;
+
+            } while (!next);
+
+            body = body->next;
+        }
+
+    } else {
+        u->request_bufs = cl;
+    }
+
+    if (!r->request_body_no_buffering) {
+        h = (ngx_http_fastcgi_header_t *) cl->buf->last;
+        cl->buf->last += sizeof(ngx_http_fastcgi_header_t);
+
+        h->version = 1;
+        h->type = NGX_HTTP_FASTCGI_STDIN;
+        h->request_id_hi = 0;
+        h->request_id_lo = 1;
+        h->content_length_hi = 0;
+        h->content_length_lo = 0;
+        h->padding_length = 0;
+        h->reserved = 0;
+    }
+
+    cl->next = NULL;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_reinit_request(ngx_http_request_t *r)
+{
+    ngx_http_fastcgi_ctx_t  *f;
+
+    f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module);
+
+    if (f == NULL) {
+        return NGX_OK;
+    }
+
+    f->state = ngx_http_fastcgi_st_version;
+    f->fastcgi_stdout = 0;
+    f->large_stderr = 0;
+
+    if (f->split_parts) {
+        f->split_parts->nelts = 0;
+    }
+
+    r->state = 0;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_body_output_filter(void *data, ngx_chain_t *in)
+{
+    ngx_http_request_t  *r = data;
+
+    off_t                       file_pos;
+    u_char                     *pos, *start;
+    size_t                      len, padding;
+    ngx_buf_t                  *b;
+    ngx_int_t                   rc;
+    ngx_uint_t                  next, last;
+    ngx_chain_t                *cl, *tl, *out, **ll;
+    ngx_http_fastcgi_ctx_t     *f;
+    ngx_http_fastcgi_header_t  *h;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "fastcgi output filter");
+
+    f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module);
+
+    if (in == NULL) {
+        out = in;
+        goto out;
+    }
+
+    out = NULL;
+    ll = &out;
+
+    if (!f->header_sent) {
+        /* first buffer contains headers, pass it unmodified */
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "fastcgi output header");
+
+        f->header_sent = 1;
+
+        tl = ngx_alloc_chain_link(r->pool);
+        if (tl == NULL) {
+            return NGX_ERROR;
+        }
+
+        tl->buf = in->buf;
+        *ll = tl;
+        ll = &tl->next;
+
+        in = in->next;
+
+        if (in == NULL) {
+            tl->next = NULL;
+            goto out;
+        }
+    }
+
+    cl = ngx_chain_get_free_buf(r->pool, &f->free);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    b = cl->buf;
+
+    b->tag = (ngx_buf_tag_t) &ngx_http_fastcgi_body_output_filter;
+    b->temporary = 1;
+
+    if (b->start == NULL) {
+        /* reserve space for maximum possible padding, 7 bytes */
+
+        b->start = ngx_palloc(r->pool,
+                              sizeof(ngx_http_fastcgi_header_t) + 7);
+        if (b->start == NULL) {
+            return NGX_ERROR;
+        }
+
+        b->pos = b->start;
+        b->last = b->start;
+
+        b->end = b->start + sizeof(ngx_http_fastcgi_header_t) + 7;
+    }
+
+    *ll = cl;
+
+    last = 0;
+    padding = 0;
+
+#if (NGX_SUPPRESS_WARN)
+    file_pos = 0;
+    pos = NULL;
+#endif
+
+    while (in) {
+
+        ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0,
+                       "fastcgi output in  l:%d f:%d %p, pos %p, size: %z "
+                       "file: %O, size: %O",
+                       in->buf->last_buf,
+                       in->buf->in_file,
+                       in->buf->start, in->buf->pos,
+                       in->buf->last - in->buf->pos,
+                       in->buf->file_pos,
+                       in->buf->file_last - in->buf->file_pos);
+
+        if (in->buf->last_buf) {
+            last = 1;
+        }
+
+        if (ngx_buf_special(in->buf)) {
+            in = in->next;
+            continue;
+        }
+
+        if (in->buf->in_file) {
+            file_pos = in->buf->file_pos;
+
+        } else {
+            pos = in->buf->pos;
+        }
+
+        next = 0;
+
+        do {
+            tl = ngx_chain_get_free_buf(r->pool, &f->free);
+            if (tl == NULL) {
+                return NGX_ERROR;
+            }
+
+            b = tl->buf;
+            start = b->start;
+
+            ngx_memcpy(b, in->buf, sizeof(ngx_buf_t));
+
+            /*
+             * restore b->start to preserve memory allocated in the buffer,
+             * to reuse it later for headers and padding
+             */
+
+            b->start = start;
+
+            if (in->buf->in_file) {
+                b->file_pos = file_pos;
+                file_pos += 32 * 1024;
+
+                if (file_pos >= in->buf->file_last) {
+                    file_pos = in->buf->file_last;
+                    next = 1;
+                }
+
+                b->file_last = file_pos;
+                len = (ngx_uint_t) (file_pos - b->file_pos);
+
+            } else {
+                b->pos = pos;
+                pos += 32 * 1024;
+
+                if (pos >= in->buf->last) {
+                    pos = in->buf->last;
+                    next = 1;
+                }
+
+                b->last = pos;
+                len = (ngx_uint_t) (pos - b->pos);
+            }
+
+            b->tag = (ngx_buf_tag_t) &ngx_http_fastcgi_body_output_filter;
+            b->shadow = in->buf;
+            b->last_shadow = next;
+
+            b->last_buf = 0;
+            b->last_in_chain = 0;
+
+            padding = 8 - len % 8;
+            padding = (padding == 8) ? 0 : padding;
+
+            h = (ngx_http_fastcgi_header_t *) cl->buf->last;
+            cl->buf->last += sizeof(ngx_http_fastcgi_header_t);
+
+            h->version = 1;
+            h->type = NGX_HTTP_FASTCGI_STDIN;
+            h->request_id_hi = 0;
+            h->request_id_lo = 1;
+            h->content_length_hi = (u_char) ((len >> 8) & 0xff);
+            h->content_length_lo = (u_char) (len & 0xff);
+            h->padding_length = (u_char) padding;
+            h->reserved = 0;
+
+            cl->next = tl;
+            cl = tl;
+
+            tl = ngx_chain_get_free_buf(r->pool, &f->free);
+            if (tl == NULL) {
+                return NGX_ERROR;
+            }
+
+            b = tl->buf;
+
+            b->tag = (ngx_buf_tag_t) &ngx_http_fastcgi_body_output_filter;
+            b->temporary = 1;
+
+            if (b->start == NULL) {
+                /* reserve space for maximum possible padding, 7 bytes */
+
+                b->start = ngx_palloc(r->pool,
+                                      sizeof(ngx_http_fastcgi_header_t) + 7);
+                if (b->start == NULL) {
+                    return NGX_ERROR;
+                }
+
+                b->pos = b->start;
+                b->last = b->start;
+
+                b->end = b->start + sizeof(ngx_http_fastcgi_header_t) + 7;
+            }
+
+            if (padding) {
+                ngx_memzero(b->last, padding);
+                b->last += padding;
+            }
+
+            cl->next = tl;
+            cl = tl;
+
+        } while (!next);
+
+        in = in->next;
+    }
+
+    if (last) {
+        h = (ngx_http_fastcgi_header_t *) cl->buf->last;
+        cl->buf->last += sizeof(ngx_http_fastcgi_header_t);
+
+        h->version = 1;
+        h->type = NGX_HTTP_FASTCGI_STDIN;
+        h->request_id_hi = 0;
+        h->request_id_lo = 1;
+        h->content_length_hi = 0;
+        h->content_length_lo = 0;
+        h->padding_length = 0;
+        h->reserved = 0;
+
+        cl->buf->last_buf = 1;
+
+    } else if (padding == 0) {
+        /* TODO: do not allocate buffers instead */
+        cl->buf->temporary = 0;
+        cl->buf->sync = 1;
+    }
+
+    cl->next = NULL;
+
+out:
+
+#if (NGX_DEBUG)
+
+    for (cl = out; cl; cl = cl->next) {
+        ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0,
+                       "fastcgi output out l:%d f:%d %p, pos %p, size: %z "
+                       "file: %O, size: %O",
+                       cl->buf->last_buf,
+                       cl->buf->in_file,
+                       cl->buf->start, cl->buf->pos,
+                       cl->buf->last - cl->buf->pos,
+                       cl->buf->file_pos,
+                       cl->buf->file_last - cl->buf->file_pos);
+    }
+
+#endif
+
+    rc = ngx_chain_writer(&r->upstream->writer, out);
+
+    ngx_chain_update_chains(r->pool, &f->free, &f->busy, &out,
+                         (ngx_buf_tag_t) &ngx_http_fastcgi_body_output_filter);
+
+    for (cl = f->free; cl; cl = cl->next) {
+
+        /* mark original buffers as sent */
+
+        if (cl->buf->shadow) {
+            if (cl->buf->last_shadow) {
+                b = cl->buf->shadow;
+                b->pos = b->last;
+            }
+
+            cl->buf->shadow = NULL;
+        }
+    }
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_process_header(ngx_http_request_t *r)
+{
+    u_char                         *p, *msg, *start, *last,
+                                   *part_start, *part_end;
+    size_t                          size;
+    ngx_str_t                      *status_line, *pattern;
+    ngx_int_t                       rc, status;
+    ngx_buf_t                       buf;
+    ngx_uint_t                      i;
+    ngx_table_elt_t                *h;
+    ngx_http_upstream_t            *u;
+    ngx_http_fastcgi_ctx_t         *f;
+    ngx_http_upstream_header_t     *hh;
+    ngx_http_fastcgi_loc_conf_t    *flcf;
+    ngx_http_fastcgi_split_part_t  *part;
+    ngx_http_upstream_main_conf_t  *umcf;
+
+    f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module);
+
+    umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);
+
+    u = r->upstream;
+
+    for ( ;; ) {
+
+        if (f->state < ngx_http_fastcgi_st_data) {
+
+            f->pos = u->buffer.pos;
+            f->last = u->buffer.last;
+
+            rc = ngx_http_fastcgi_process_record(r, f);
+
+            u->buffer.pos = f->pos;
+            u->buffer.last = f->last;
+
+            if (rc == NGX_AGAIN) {
+                return NGX_AGAIN;
+            }
+
+            if (rc == NGX_ERROR) {
+                return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+            }
+
+            if (f->type != NGX_HTTP_FASTCGI_STDOUT
+                && f->type != NGX_HTTP_FASTCGI_STDERR)
+            {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent unexpected FastCGI record: %ui",
+                              f->type);
+
+                return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+            }
+
+            if (f->type == NGX_HTTP_FASTCGI_STDOUT && f->length == 0) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream prematurely closed FastCGI stdout");
+
+                return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+            }
+        }
+
+        if (f->state == ngx_http_fastcgi_st_padding) {
+
+            if (u->buffer.pos + f->padding < u->buffer.last) {
+                f->state = ngx_http_fastcgi_st_version;
+                u->buffer.pos += f->padding;
+
+                continue;
+            }
+
+            if (u->buffer.pos + f->padding == u->buffer.last) {
+                f->state = ngx_http_fastcgi_st_version;
+                u->buffer.pos = u->buffer.last;
+
+                return NGX_AGAIN;
+            }
+
+            f->padding -= u->buffer.last - u->buffer.pos;
+            u->buffer.pos = u->buffer.last;
+
+            return NGX_AGAIN;
+        }
+
+
+        /* f->state == ngx_http_fastcgi_st_data */
+
+        if (f->type == NGX_HTTP_FASTCGI_STDERR) {
+
+            if (f->length) {
+                msg = u->buffer.pos;
+
+                if (u->buffer.pos + f->length <= u->buffer.last) {
+                    u->buffer.pos += f->length;
+                    f->length = 0;
+                    f->state = ngx_http_fastcgi_st_padding;
+
+                } else {
+                    f->length -= u->buffer.last - u->buffer.pos;
+                    u->buffer.pos = u->buffer.last;
+                }
+
+                for (p = u->buffer.pos - 1; msg < p; p--) {
+                    if (*p != LF && *p != CR && *p != '.' && *p != ' ') {
+                        break;
+                    }
+                }
+
+                p++;
+
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "FastCGI sent in stderr: \"%*s\"", p - msg, msg);
+
+                flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module);
+
+                if (flcf->catch_stderr) {
+                    pattern = flcf->catch_stderr->elts;
+
+                    for (i = 0; i < flcf->catch_stderr->nelts; i++) {
+                        if (ngx_strnstr(msg, (char *) pattern[i].data,
+                                        p - msg)
+                            != NULL)
+                        {
+                            return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+                        }
+                    }
+                }
+
+                if (u->buffer.pos == u->buffer.last) {
+
+                    if (!f->fastcgi_stdout) {
+
+                        /*
+                         * the special handling the large number
+                         * of the PHP warnings to not allocate memory
+                         */
+
+#if (NGX_HTTP_CACHE)
+                        if (r->cache) {
+                            u->buffer.pos = u->buffer.start
+                                                     + r->cache->header_start;
+                        } else {
+                            u->buffer.pos = u->buffer.start;
+                        }
+#else
+                        u->buffer.pos = u->buffer.start;
+#endif
+                        u->buffer.last = u->buffer.pos;
+                        f->large_stderr = 1;
+                    }
+
+                    return NGX_AGAIN;
+                }
+
+            } else {
+                f->state = ngx_http_fastcgi_st_padding;
+            }
+
+            continue;
+        }
+
+
+        /* f->type == NGX_HTTP_FASTCGI_STDOUT */
+
+#if (NGX_HTTP_CACHE)
+
+        if (f->large_stderr && r->cache) {
+            ssize_t                     len;
+            ngx_http_fastcgi_header_t  *fh;
+
+            start = u->buffer.start + r->cache->header_start;
+
+            len = u->buffer.pos - start - 2 * sizeof(ngx_http_fastcgi_header_t);
+
+            /*
+             * A tail of large stderr output before HTTP header is placed
+             * in a cache file without a FastCGI record header.
+             * To workaround it we put a dummy FastCGI record header at the
+             * start of the stderr output or update r->cache_header_start,
+             * if there is no enough place for the record header.
+             */
+
+            if (len >= 0) {
+                fh = (ngx_http_fastcgi_header_t *) start;
+                fh->version = 1;
+                fh->type = NGX_HTTP_FASTCGI_STDERR;
+                fh->request_id_hi = 0;
+                fh->request_id_lo = 1;
+                fh->content_length_hi = (u_char) ((len >> 8) & 0xff);
+                fh->content_length_lo = (u_char) (len & 0xff);
+                fh->padding_length = 0;
+                fh->reserved = 0;
+
+            } else {
+                r->cache->header_start += u->buffer.pos - start
+                                          - sizeof(ngx_http_fastcgi_header_t);
+            }
+
+            f->large_stderr = 0;
+        }
+
+#endif
+
+        f->fastcgi_stdout = 1;
+
+        start = u->buffer.pos;
+
+        if (u->buffer.pos + f->length < u->buffer.last) {
+
+            /*
+             * set u->buffer.last to the end of the FastCGI record data
+             * for ngx_http_parse_header_line()
+             */
+
+            last = u->buffer.last;
+            u->buffer.last = u->buffer.pos + f->length;
+
+        } else {
+            last = NULL;
+        }
+
+        for ( ;; ) {
+
+            part_start = u->buffer.pos;
+            part_end = u->buffer.last;
+
+            rc = ngx_http_parse_header_line(r, &u->buffer, 1);
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http fastcgi parser: %i", rc);
+
+            if (rc == NGX_AGAIN) {
+                break;
+            }
+
+            if (rc == NGX_OK) {
+
+                /* a header line has been parsed successfully */
+
+                h = ngx_list_push(&u->headers_in.headers);
+                if (h == NULL) {
+                    return NGX_ERROR;
+                }
+
+                if (f->split_parts && f->split_parts->nelts) {
+
+                    part = f->split_parts->elts;
+                    size = u->buffer.pos - part_start;
+
+                    for (i = 0; i < f->split_parts->nelts; i++) {
+                        size += part[i].end - part[i].start;
+                    }
+
+                    p = ngx_pnalloc(r->pool, size);
+                    if (p == NULL) {
+                        h->hash = 0;
+                        return NGX_ERROR;
+                    }
+
+                    buf.pos = p;
+
+                    for (i = 0; i < f->split_parts->nelts; i++) {
+                        p = ngx_cpymem(p, part[i].start,
+                                       part[i].end - part[i].start);
+                    }
+
+                    p = ngx_cpymem(p, part_start, u->buffer.pos - part_start);
+
+                    buf.last = p;
+
+                    f->split_parts->nelts = 0;
+
+                    rc = ngx_http_parse_header_line(r, &buf, 1);
+
+                    if (rc != NGX_OK) {
+                        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                                      "invalid header after joining "
+                                      "FastCGI records");
+                        h->hash = 0;
+                        return NGX_ERROR;
+                    }
+
+                    h->key.len = r->header_name_end - r->header_name_start;
+                    h->key.data = r->header_name_start;
+                    h->key.data[h->key.len] = '\0';
+
+                    h->value.len = r->header_end - r->header_start;
+                    h->value.data = r->header_start;
+                    h->value.data[h->value.len] = '\0';
+
+                    h->lowcase_key = ngx_pnalloc(r->pool, h->key.len);
+                    if (h->lowcase_key == NULL) {
+                        return NGX_ERROR;
+                    }
+
+                } else {
+
+                    h->key.len = r->header_name_end - r->header_name_start;
+                    h->value.len = r->header_end - r->header_start;
+
+                    h->key.data = ngx_pnalloc(r->pool,
+                                              h->key.len + 1 + h->value.len + 1
+                                              + h->key.len);
+                    if (h->key.data == NULL) {
+                        h->hash = 0;
+                        return NGX_ERROR;
+                    }
+
+                    h->value.data = h->key.data + h->key.len + 1;
+                    h->lowcase_key = h->key.data + h->key.len + 1
+                                     + h->value.len + 1;
+
+                    ngx_memcpy(h->key.data, r->header_name_start, h->key.len);
+                    h->key.data[h->key.len] = '\0';
+                    ngx_memcpy(h->value.data, r->header_start, h->value.len);
+                    h->value.data[h->value.len] = '\0';
+                }
+
+                h->hash = r->header_hash;
+
+                if (h->key.len == r->lowcase_index) {
+                    ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);
+
+                } else {
+                    ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
+                }
+
+                hh = ngx_hash_find(&umcf->headers_in_hash, h->hash,
+                                   h->lowcase_key, h->key.len);
+
+                if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
+                    return NGX_ERROR;
+                }
+
+                ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                               "http fastcgi header: \"%V: %V\"",
+                               &h->key, &h->value);
+
+                if (u->buffer.pos < u->buffer.last) {
+                    continue;
+                }
+
+                /* the end of the FastCGI record */
+
+                break;
+            }
+
+            if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
+
+                /* a whole header has been parsed successfully */
+
+                ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                               "http fastcgi header done");
+
+                if (u->headers_in.status) {
+                    status_line = &u->headers_in.status->value;
+
+                    status = ngx_atoi(status_line->data, 3);
+
+                    if (status == NGX_ERROR) {
+                        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                      "upstream sent invalid status \"%V\"",
+                                      status_line);
+                        return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+                    }
+
+                    u->headers_in.status_n = status;
+                    u->headers_in.status_line = *status_line;
+
+                } else if (u->headers_in.location) {
+                    u->headers_in.status_n = 302;
+                    ngx_str_set(&u->headers_in.status_line,
+                                "302 Moved Temporarily");
+
+                } else {
+                    u->headers_in.status_n = 200;
+                    ngx_str_set(&u->headers_in.status_line, "200 OK");
+                }
+
+                if (u->state && u->state->status == 0) {
+                    u->state->status = u->headers_in.status_n;
+                }
+
+                break;
+            }
+
+            /* there was error while a header line parsing */
+
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "upstream sent invalid header");
+
+            return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+        }
+
+        if (last) {
+            u->buffer.last = last;
+        }
+
+        f->length -= u->buffer.pos - start;
+
+        if (f->length == 0) {
+            f->state = ngx_http_fastcgi_st_padding;
+        }
+
+        if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
+            return NGX_OK;
+        }
+
+        if (rc == NGX_OK) {
+            continue;
+        }
+
+        /* rc == NGX_AGAIN */
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "upstream split a header line in FastCGI records");
+
+        if (f->split_parts == NULL) {
+            f->split_parts = ngx_array_create(r->pool, 1,
+                                        sizeof(ngx_http_fastcgi_split_part_t));
+            if (f->split_parts == NULL) {
+                return NGX_ERROR;
+            }
+        }
+
+        part = ngx_array_push(f->split_parts);
+        if (part == NULL) {
+            return NGX_ERROR;
+        }
+
+        part->start = part_start;
+        part->end = part_end;
+
+        if (u->buffer.pos < u->buffer.last) {
+            continue;
+        }
+
+        return NGX_AGAIN;
+    }
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_input_filter_init(void *data)
+{
+    ngx_http_request_t           *r = data;
+    ngx_http_fastcgi_loc_conf_t  *flcf;
+
+    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;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_input_filter(ngx_event_pipe_t *p, ngx_buf_t *buf)
+{
+    u_char                       *m, *msg;
+    ngx_int_t                     rc;
+    ngx_buf_t                    *b, **prev;
+    ngx_chain_t                  *cl;
+    ngx_http_request_t           *r;
+    ngx_http_fastcgi_ctx_t       *f;
+    ngx_http_fastcgi_loc_conf_t  *flcf;
+
+    if (buf->pos == buf->last) {
+        return NGX_OK;
+    }
+
+    r = p->input_ctx;
+    f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module);
+    flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module);
+
+    b = NULL;
+    prev = &buf->shadow;
+
+    f->pos = buf->pos;
+    f->last = buf->last;
+
+    for ( ;; ) {
+        if (f->state < ngx_http_fastcgi_st_data) {
+
+            rc = ngx_http_fastcgi_process_record(r, f);
+
+            if (rc == NGX_AGAIN) {
+                break;
+            }
+
+            if (rc == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            if (f->type == NGX_HTTP_FASTCGI_STDOUT && f->length == 0) {
+                f->state = ngx_http_fastcgi_st_padding;
+
+                if (!flcf->keep_conn) {
+                    p->upstream_done = 1;
+                }
+
+                ngx_log_debug0(NGX_LOG_DEBUG_HTTP, p->log, 0,
+                               "http fastcgi closed stdout");
+
+                continue;
+            }
+
+            if (f->type == NGX_HTTP_FASTCGI_END_REQUEST) {
+
+                ngx_log_debug0(NGX_LOG_DEBUG_HTTP, p->log, 0,
+                               "http fastcgi sent end request");
+
+                if (!flcf->keep_conn) {
+                    p->upstream_done = 1;
+                    break;
+                }
+
+                continue;
+            }
+        }
+
+
+        if (f->state == ngx_http_fastcgi_st_padding) {
+
+            if (f->type == NGX_HTTP_FASTCGI_END_REQUEST) {
+
+                if (f->pos + f->padding < f->last) {
+                    p->upstream_done = 1;
+                    break;
+                }
+
+                if (f->pos + f->padding == f->last) {
+                    p->upstream_done = 1;
+                    r->upstream->keepalive = 1;
+                    break;
+                }
+
+                f->padding -= f->last - f->pos;
+
+                break;
+            }
+
+            if (f->pos + f->padding < f->last) {
+                f->state = ngx_http_fastcgi_st_version;
+                f->pos += f->padding;
+
+                continue;
+            }
+
+            if (f->pos + f->padding == f->last) {
+                f->state = ngx_http_fastcgi_st_version;
+
+                break;
+            }
+
+            f->padding -= f->last - f->pos;
+
+            break;
+        }
+
+
+        /* f->state == ngx_http_fastcgi_st_data */
+
+        if (f->type == NGX_HTTP_FASTCGI_STDERR) {
+
+            if (f->length) {
+
+                if (f->pos == f->last) {
+                    break;
+                }
+
+                msg = f->pos;
+
+                if (f->pos + f->length <= f->last) {
+                    f->pos += f->length;
+                    f->length = 0;
+                    f->state = ngx_http_fastcgi_st_padding;
+
+                } else {
+                    f->length -= f->last - f->pos;
+                    f->pos = f->last;
+                }
+
+                for (m = f->pos - 1; msg < m; m--) {
+                    if (*m != LF && *m != CR && *m != '.' && *m != ' ') {
+                        break;
+                    }
+                }
+
+                ngx_log_error(NGX_LOG_ERR, p->log, 0,
+                              "FastCGI sent in stderr: \"%*s\"",
+                              m + 1 - msg, msg);
+
+            } else {
+                f->state = ngx_http_fastcgi_st_padding;
+            }
+
+            continue;
+        }
+
+        if (f->type == NGX_HTTP_FASTCGI_END_REQUEST) {
+
+            if (f->pos + f->length <= f->last) {
+                f->state = ngx_http_fastcgi_st_padding;
+                f->pos += f->length;
+
+                continue;
+            }
+
+            f->length -= f->last - f->pos;
+
+            break;
+        }
+
+
+        /* f->type == NGX_HTTP_FASTCGI_STDOUT */
+
+        if (f->pos == f->last) {
+            break;
+        }
+
+        cl = ngx_chain_get_free_buf(p->pool, &p->free);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+        b = cl->buf;
+
+        ngx_memzero(b, sizeof(ngx_buf_t));
+
+        b->pos = f->pos;
+        b->start = buf->start;
+        b->end = buf->end;
+        b->tag = p->tag;
+        b->temporary = 1;
+        b->recycled = 1;
+
+        *prev = b;
+        prev = &b->shadow;
+
+        if (p->in) {
+            *p->last_in = cl;
+        } else {
+            p->in = cl;
+        }
+        p->last_in = &cl->next;
+
+
+        /* STUB */ b->num = buf->num;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                       "input buf #%d %p", b->num, b->pos);
+
+        if (f->pos + f->length <= f->last) {
+            f->state = ngx_http_fastcgi_st_padding;
+            f->pos += f->length;
+            b->last = f->pos;
+
+            continue;
+        }
+
+        f->length -= f->last - f->pos;
+
+        b->last = f->last;
+
+        break;
+
+    }
+
+    if (flcf->keep_conn) {
+
+        /* set p->length, minimal amount of data we want to see */
+
+        if (f->state < ngx_http_fastcgi_st_data) {
+            p->length = 1;
+
+        } else if (f->state == ngx_http_fastcgi_st_padding) {
+            p->length = f->padding;
+
+        } else {
+            /* ngx_http_fastcgi_st_data */
+
+            p->length = f->length;
+        }
+    }
+
+    if (b) {
+        b->shadow = buf;
+        b->last_shadow = 1;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                       "input buf %p %z", b->pos, b->last - b->pos);
+
+        return NGX_OK;
+    }
+
+    /* there is no data record in the buf, add it to free chain */
+
+    if (ngx_event_pipe_add_free_buf(p, buf) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_non_buffered_filter(void *data, ssize_t bytes)
+{
+    u_char                  *m, *msg;
+    ngx_int_t                rc;
+    ngx_buf_t               *b, *buf;
+    ngx_chain_t             *cl, **ll;
+    ngx_http_request_t      *r;
+    ngx_http_upstream_t     *u;
+    ngx_http_fastcgi_ctx_t  *f;
+
+    r = data;
+    f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module);
+
+    u = r->upstream;
+    buf = &u->buffer;
+
+    buf->pos = buf->last;
+    buf->last += bytes;
+
+    for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) {
+        ll = &cl->next;
+    }
+
+    f->pos = buf->pos;
+    f->last = buf->last;
+
+    for ( ;; ) {
+        if (f->state < ngx_http_fastcgi_st_data) {
+
+            rc = ngx_http_fastcgi_process_record(r, f);
+
+            if (rc == NGX_AGAIN) {
+                break;
+            }
+
+            if (rc == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            if (f->type == NGX_HTTP_FASTCGI_STDOUT && f->length == 0) {
+                f->state = ngx_http_fastcgi_st_padding;
+
+                ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                               "http fastcgi closed stdout");
+
+                continue;
+            }
+        }
+
+        if (f->state == ngx_http_fastcgi_st_padding) {
+
+            if (f->type == NGX_HTTP_FASTCGI_END_REQUEST) {
+
+                if (f->pos + f->padding < f->last) {
+                    u->length = 0;
+                    break;
+                }
+
+                if (f->pos + f->padding == f->last) {
+                    u->length = 0;
+                    u->keepalive = 1;
+                    break;
+                }
+
+                f->padding -= f->last - f->pos;
+
+                break;
+            }
+
+            if (f->pos + f->padding < f->last) {
+                f->state = ngx_http_fastcgi_st_version;
+                f->pos += f->padding;
+
+                continue;
+            }
+
+            if (f->pos + f->padding == f->last) {
+                f->state = ngx_http_fastcgi_st_version;
+
+                break;
+            }
+
+            f->padding -= f->last - f->pos;
+
+            break;
+        }
+
+
+        /* f->state == ngx_http_fastcgi_st_data */
+
+        if (f->type == NGX_HTTP_FASTCGI_STDERR) {
+
+            if (f->length) {
+
+                if (f->pos == f->last) {
+                    break;
+                }
+
+                msg = f->pos;
+
+                if (f->pos + f->length <= f->last) {
+                    f->pos += f->length;
+                    f->length = 0;
+                    f->state = ngx_http_fastcgi_st_padding;
+
+                } else {
+                    f->length -= f->last - f->pos;
+                    f->pos = f->last;
+                }
+
+                for (m = f->pos - 1; msg < m; m--) {
+                    if (*m != LF && *m != CR && *m != '.' && *m != ' ') {
+                        break;
+                    }
+                }
+
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "FastCGI sent in stderr: \"%*s\"",
+                              m + 1 - msg, msg);
+
+            } else {
+                f->state = ngx_http_fastcgi_st_padding;
+            }
+
+            continue;
+        }
+
+        if (f->type == NGX_HTTP_FASTCGI_END_REQUEST) {
+
+            if (f->pos + f->length <= f->last) {
+                f->state = ngx_http_fastcgi_st_padding;
+                f->pos += f->length;
+
+                continue;
+            }
+
+            f->length -= f->last - f->pos;
+
+            break;
+        }
+
+
+        /* f->type == NGX_HTTP_FASTCGI_STDOUT */
+
+        if (f->pos == f->last) {
+            break;
+        }
+
+        cl = ngx_chain_get_free_buf(r->pool, &u->free_bufs);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+        *ll = cl;
+        ll = &cl->next;
+
+        b = cl->buf;
+
+        b->flush = 1;
+        b->memory = 1;
+
+        b->pos = f->pos;
+        b->tag = u->output.tag;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http fastcgi output buf %p", b->pos);
+
+        if (f->pos + f->length <= f->last) {
+            f->state = ngx_http_fastcgi_st_padding;
+            f->pos += f->length;
+            b->last = f->pos;
+
+            continue;
+        }
+
+        f->length -= f->last - f->pos;
+        b->last = f->last;
+
+        break;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_process_record(ngx_http_request_t *r,
+    ngx_http_fastcgi_ctx_t *f)
+{
+    u_char                     ch, *p;
+    ngx_http_fastcgi_state_e   state;
+
+    state = f->state;
+
+    for (p = f->pos; p < f->last; p++) {
+
+        ch = *p;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http fastcgi record byte: %02Xd", ch);
+
+        switch (state) {
+
+        case ngx_http_fastcgi_st_version:
+            if (ch != 1) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent unsupported FastCGI "
+                              "protocol version: %d", ch);
+                return NGX_ERROR;
+            }
+            state = ngx_http_fastcgi_st_type;
+            break;
+
+        case ngx_http_fastcgi_st_type:
+            switch (ch) {
+            case NGX_HTTP_FASTCGI_STDOUT:
+            case NGX_HTTP_FASTCGI_STDERR:
+            case NGX_HTTP_FASTCGI_END_REQUEST:
+                f->type = (ngx_uint_t) ch;
+                break;
+            default:
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent invalid FastCGI "
+                              "record type: %d", ch);
+                return NGX_ERROR;
+
+            }
+            state = ngx_http_fastcgi_st_request_id_hi;
+            break;
+
+        /* we support the single request per connection */
+
+        case ngx_http_fastcgi_st_request_id_hi:
+            if (ch != 0) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent unexpected FastCGI "
+                              "request id high byte: %d", ch);
+                return NGX_ERROR;
+            }
+            state = ngx_http_fastcgi_st_request_id_lo;
+            break;
+
+        case ngx_http_fastcgi_st_request_id_lo:
+            if (ch != 1) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent unexpected FastCGI "
+                              "request id low byte: %d", ch);
+                return NGX_ERROR;
+            }
+            state = ngx_http_fastcgi_st_content_length_hi;
+            break;
+
+        case ngx_http_fastcgi_st_content_length_hi:
+            f->length = ch << 8;
+            state = ngx_http_fastcgi_st_content_length_lo;
+            break;
+
+        case ngx_http_fastcgi_st_content_length_lo:
+            f->length |= (size_t) ch;
+            state = ngx_http_fastcgi_st_padding_length;
+            break;
+
+        case ngx_http_fastcgi_st_padding_length:
+            f->padding = (size_t) ch;
+            state = ngx_http_fastcgi_st_reserved;
+            break;
+
+        case ngx_http_fastcgi_st_reserved:
+            state = ngx_http_fastcgi_st_data;
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http fastcgi record length: %z", f->length);
+
+            f->pos = p + 1;
+            f->state = state;
+
+            return NGX_OK;
+
+        /* suppress warning */
+        case ngx_http_fastcgi_st_data:
+        case ngx_http_fastcgi_st_padding:
+            break;
+        }
+    }
+
+    f->pos = p;
+    f->state = state;
+
+    return NGX_AGAIN;
+}
+
+
+static void
+ngx_http_fastcgi_abort_request(ngx_http_request_t *r)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "abort http fastcgi request");
+
+    return;
+}
+
+
+static void
+ngx_http_fastcgi_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "finalize http fastcgi request");
+
+    return;
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_add_variables(ngx_conf_t *cf)
+{
+    ngx_http_variable_t  *var, *v;
+
+    for (v = ngx_http_fastcgi_vars; v->name.len; v++) {
+        var = ngx_http_add_variable(cf, &v->name, v->flags);
+        if (var == NULL) {
+            return NGX_ERROR;
+        }
+
+        var->get_handler = v->get_handler;
+        var->data = v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_fastcgi_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_http_fastcgi_main_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_fastcgi_main_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+#if (NGX_HTTP_CACHE)
+    if (ngx_array_init(&conf->caches, cf->pool, 4,
+                       sizeof(ngx_http_file_cache_t *))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+#endif
+
+    return conf;
+}
+
+
+static void *
+ngx_http_fastcgi_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_fastcgi_loc_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_fastcgi_loc_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->upstream.bufs.num = 0;
+     *     conf->upstream.ignore_headers = 0;
+     *     conf->upstream.next_upstream = 0;
+     *     conf->upstream.cache_zone = NULL;
+     *     conf->upstream.cache_use_stale = 0;
+     *     conf->upstream.cache_methods = 0;
+     *     conf->upstream.temp_path = NULL;
+     *     conf->upstream.hide_headers_hash = { NULL, 0 };
+     *     conf->upstream.store_lengths = NULL;
+     *     conf->upstream.store_values = NULL;
+     *
+     *     conf->index.len = { 0, NULL };
+     */
+
+    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;
+    conf->upstream.force_ranges = NGX_CONF_UNSET;
+
+    conf->upstream.local = NGX_CONF_UNSET_PTR;
+
+    conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.next_upstream_timeout = NGX_CONF_UNSET_MSEC;
+
+    conf->upstream.send_lowat = NGX_CONF_UNSET_SIZE;
+    conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE;
+    conf->upstream.limit_rate = NGX_CONF_UNSET_SIZE;
+
+    conf->upstream.busy_buffers_size_conf = NGX_CONF_UNSET_SIZE;
+    conf->upstream.max_temp_file_size_conf = NGX_CONF_UNSET_SIZE;
+    conf->upstream.temp_file_write_size_conf = NGX_CONF_UNSET_SIZE;
+
+    conf->upstream.pass_request_headers = NGX_CONF_UNSET;
+    conf->upstream.pass_request_body = NGX_CONF_UNSET;
+
+#if (NGX_HTTP_CACHE)
+    conf->upstream.cache = NGX_CONF_UNSET;
+    conf->upstream.cache_min_uses = NGX_CONF_UNSET_UINT;
+    conf->upstream.cache_max_range_offset = NGX_CONF_UNSET;
+    conf->upstream.cache_bypass = NGX_CONF_UNSET_PTR;
+    conf->upstream.no_cache = NGX_CONF_UNSET_PTR;
+    conf->upstream.cache_valid = NGX_CONF_UNSET_PTR;
+    conf->upstream.cache_lock = NGX_CONF_UNSET;
+    conf->upstream.cache_lock_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.cache_lock_age = NGX_CONF_UNSET_MSEC;
+    conf->upstream.cache_revalidate = NGX_CONF_UNSET;
+    conf->upstream.cache_background_update = NGX_CONF_UNSET;
+#endif
+
+    conf->upstream.hide_headers = NGX_CONF_UNSET_PTR;
+    conf->upstream.pass_headers = NGX_CONF_UNSET_PTR;
+
+    conf->upstream.intercept_errors = NGX_CONF_UNSET;
+
+    /* "fastcgi_cyclic_temp_file" is disabled */
+    conf->upstream.cyclic_temp_file = 0;
+
+    conf->upstream.change_buffering = 1;
+
+    conf->catch_stderr = NGX_CONF_UNSET_PTR;
+
+    conf->keep_conn = NGX_CONF_UNSET;
+
+    ngx_str_set(&conf->upstream.module, "fastcgi");
+
+    return conf;
+}
+
+
+static char *
+ngx_http_fastcgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_fastcgi_loc_conf_t *prev = parent;
+    ngx_http_fastcgi_loc_conf_t *conf = child;
+
+    size_t                        size;
+    ngx_int_t                     rc;
+    ngx_hash_init_t               hash;
+    ngx_http_core_loc_conf_t     *clcf;
+
+#if (NGX_HTTP_CACHE)
+
+    if (conf->upstream.store > 0) {
+        conf->upstream.cache = 0;
+    }
+
+    if (conf->upstream.cache > 0) {
+        conf->upstream.store = 0;
+    }
+
+#endif
+
+    if (conf->upstream.store == NGX_CONF_UNSET) {
+        ngx_conf_merge_value(conf->upstream.store,
+                              prev->upstream.store, 0);
+
+        conf->upstream.store_lengths = prev->upstream.store_lengths;
+        conf->upstream.store_values = prev->upstream.store_values;
+    }
+
+    ngx_conf_merge_uint_value(conf->upstream.store_access,
+                              prev->upstream.store_access, 0600);
+
+    ngx_conf_merge_uint_value(conf->upstream.next_upstream_tries,
+                              prev->upstream.next_upstream_tries, 0);
+
+    ngx_conf_merge_value(conf->upstream.buffering,
+                              prev->upstream.buffering, 1);
+
+    ngx_conf_merge_value(conf->upstream.request_buffering,
+                              prev->upstream.request_buffering, 1);
+
+    ngx_conf_merge_value(conf->upstream.ignore_client_abort,
+                              prev->upstream.ignore_client_abort, 0);
+
+    ngx_conf_merge_value(conf->upstream.force_ranges,
+                              prev->upstream.force_ranges, 0);
+
+    ngx_conf_merge_ptr_value(conf->upstream.local,
+                              prev->upstream.local, NULL);
+
+    ngx_conf_merge_msec_value(conf->upstream.connect_timeout,
+                              prev->upstream.connect_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->upstream.send_timeout,
+                              prev->upstream.send_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->upstream.read_timeout,
+                              prev->upstream.read_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->upstream.next_upstream_timeout,
+                              prev->upstream.next_upstream_timeout, 0);
+
+    ngx_conf_merge_size_value(conf->upstream.send_lowat,
+                              prev->upstream.send_lowat, 0);
+
+    ngx_conf_merge_size_value(conf->upstream.buffer_size,
+                              prev->upstream.buffer_size,
+                              (size_t) ngx_pagesize);
+
+    ngx_conf_merge_size_value(conf->upstream.limit_rate,
+                              prev->upstream.limit_rate, 0);
+
+
+    ngx_conf_merge_bufs_value(conf->upstream.bufs, prev->upstream.bufs,
+                              8, ngx_pagesize);
+
+    if (conf->upstream.bufs.num < 2) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "there must be at least 2 \"fastcgi_buffers\"");
+        return NGX_CONF_ERROR;
+    }
+
+
+    size = conf->upstream.buffer_size;
+    if (size < conf->upstream.bufs.size) {
+        size = conf->upstream.bufs.size;
+    }
+
+
+    ngx_conf_merge_size_value(conf->upstream.busy_buffers_size_conf,
+                              prev->upstream.busy_buffers_size_conf,
+                              NGX_CONF_UNSET_SIZE);
+
+    if (conf->upstream.busy_buffers_size_conf == NGX_CONF_UNSET_SIZE) {
+        conf->upstream.busy_buffers_size = 2 * size;
+    } else {
+        conf->upstream.busy_buffers_size =
+                                         conf->upstream.busy_buffers_size_conf;
+    }
+
+    if (conf->upstream.busy_buffers_size < size) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+             "\"fastcgi_busy_buffers_size\" must be equal to or greater than "
+             "the maximum of the value of \"fastcgi_buffer_size\" and "
+             "one of the \"fastcgi_buffers\"");
+
+        return NGX_CONF_ERROR;
+    }
+
+    if (conf->upstream.busy_buffers_size
+        > (conf->upstream.bufs.num - 1) * conf->upstream.bufs.size)
+    {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+             "\"fastcgi_busy_buffers_size\" must be less than "
+             "the size of all \"fastcgi_buffers\" minus one buffer");
+
+        return NGX_CONF_ERROR;
+    }
+
+
+    ngx_conf_merge_size_value(conf->upstream.temp_file_write_size_conf,
+                              prev->upstream.temp_file_write_size_conf,
+                              NGX_CONF_UNSET_SIZE);
+
+    if (conf->upstream.temp_file_write_size_conf == NGX_CONF_UNSET_SIZE) {
+        conf->upstream.temp_file_write_size = 2 * size;
+    } else {
+        conf->upstream.temp_file_write_size =
+                                      conf->upstream.temp_file_write_size_conf;
+    }
+
+    if (conf->upstream.temp_file_write_size < size) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+             "\"fastcgi_temp_file_write_size\" must be equal to or greater "
+             "than the maximum of the value of \"fastcgi_buffer_size\" and "
+             "one of the \"fastcgi_buffers\"");
+
+        return NGX_CONF_ERROR;
+    }
+
+
+    ngx_conf_merge_size_value(conf->upstream.max_temp_file_size_conf,
+                              prev->upstream.max_temp_file_size_conf,
+                              NGX_CONF_UNSET_SIZE);
+
+    if (conf->upstream.max_temp_file_size_conf == NGX_CONF_UNSET_SIZE) {
+        conf->upstream.max_temp_file_size = 1024 * 1024 * 1024;
+    } else {
+        conf->upstream.max_temp_file_size =
+                                        conf->upstream.max_temp_file_size_conf;
+    }
+
+    if (conf->upstream.max_temp_file_size != 0
+        && conf->upstream.max_temp_file_size < size)
+    {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+             "\"fastcgi_max_temp_file_size\" must be equal to zero to disable "
+             "temporary files usage or must be equal to or greater than "
+             "the maximum of the value of \"fastcgi_buffer_size\" and "
+             "one of the \"fastcgi_buffers\"");
+
+        return NGX_CONF_ERROR;
+    }
+
+
+    ngx_conf_merge_bitmask_value(conf->upstream.ignore_headers,
+                              prev->upstream.ignore_headers,
+                              NGX_CONF_BITMASK_SET);
+
+
+    ngx_conf_merge_bitmask_value(conf->upstream.next_upstream,
+                              prev->upstream.next_upstream,
+                              (NGX_CONF_BITMASK_SET
+                               |NGX_HTTP_UPSTREAM_FT_ERROR
+                               |NGX_HTTP_UPSTREAM_FT_TIMEOUT));
+
+    if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) {
+        conf->upstream.next_upstream = NGX_CONF_BITMASK_SET
+                                       |NGX_HTTP_UPSTREAM_FT_OFF;
+    }
+
+    if (ngx_conf_merge_path_value(cf, &conf->upstream.temp_path,
+                              prev->upstream.temp_path,
+                              &ngx_http_fastcgi_temp_path)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+#if (NGX_HTTP_CACHE)
+
+    if (conf->upstream.cache == NGX_CONF_UNSET) {
+        ngx_conf_merge_value(conf->upstream.cache,
+                              prev->upstream.cache, 0);
+
+        conf->upstream.cache_zone = prev->upstream.cache_zone;
+        conf->upstream.cache_value = prev->upstream.cache_value;
+    }
+
+    if (conf->upstream.cache_zone && conf->upstream.cache_zone->data == NULL) {
+        ngx_shm_zone_t  *shm_zone;
+
+        shm_zone = conf->upstream.cache_zone;
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"fastcgi_cache\" zone \"%V\" is unknown",
+                           &shm_zone->shm.name);
+
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_conf_merge_uint_value(conf->upstream.cache_min_uses,
+                              prev->upstream.cache_min_uses, 1);
+
+    ngx_conf_merge_off_value(conf->upstream.cache_max_range_offset,
+                              prev->upstream.cache_max_range_offset,
+                              NGX_MAX_OFF_T_VALUE);
+
+    ngx_conf_merge_bitmask_value(conf->upstream.cache_use_stale,
+                              prev->upstream.cache_use_stale,
+                              (NGX_CONF_BITMASK_SET
+                               |NGX_HTTP_UPSTREAM_FT_OFF));
+
+    if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_OFF) {
+        conf->upstream.cache_use_stale = NGX_CONF_BITMASK_SET
+                                         |NGX_HTTP_UPSTREAM_FT_OFF;
+    }
+
+    if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_ERROR) {
+        conf->upstream.cache_use_stale |= NGX_HTTP_UPSTREAM_FT_NOLIVE;
+    }
+
+    if (conf->upstream.cache_methods == 0) {
+        conf->upstream.cache_methods = prev->upstream.cache_methods;
+    }
+
+    conf->upstream.cache_methods |= NGX_HTTP_GET|NGX_HTTP_HEAD;
+
+    ngx_conf_merge_ptr_value(conf->upstream.cache_bypass,
+                             prev->upstream.cache_bypass, NULL);
+
+    ngx_conf_merge_ptr_value(conf->upstream.no_cache,
+                             prev->upstream.no_cache, NULL);
+
+    ngx_conf_merge_ptr_value(conf->upstream.cache_valid,
+                             prev->upstream.cache_valid, NULL);
+
+    if (conf->cache_key.value.data == NULL) {
+        conf->cache_key = prev->cache_key;
+    }
+
+    if (conf->upstream.cache && conf->cache_key.value.data == NULL) {
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                           "no \"fastcgi_cache_key\" for \"fastcgi_cache\"");
+    }
+
+    ngx_conf_merge_value(conf->upstream.cache_lock,
+                              prev->upstream.cache_lock, 0);
+
+    ngx_conf_merge_msec_value(conf->upstream.cache_lock_timeout,
+                              prev->upstream.cache_lock_timeout, 5000);
+
+    ngx_conf_merge_msec_value(conf->upstream.cache_lock_age,
+                              prev->upstream.cache_lock_age, 5000);
+
+    ngx_conf_merge_value(conf->upstream.cache_revalidate,
+                              prev->upstream.cache_revalidate, 0);
+
+    ngx_conf_merge_value(conf->upstream.cache_background_update,
+                              prev->upstream.cache_background_update, 0);
+
+#endif
+
+    ngx_conf_merge_value(conf->upstream.pass_request_headers,
+                              prev->upstream.pass_request_headers, 1);
+    ngx_conf_merge_value(conf->upstream.pass_request_body,
+                              prev->upstream.pass_request_body, 1);
+
+    ngx_conf_merge_value(conf->upstream.intercept_errors,
+                              prev->upstream.intercept_errors, 0);
+
+    ngx_conf_merge_ptr_value(conf->catch_stderr, prev->catch_stderr, NULL);
+
+    ngx_conf_merge_value(conf->keep_conn, prev->keep_conn, 0);
+
+
+    ngx_conf_merge_str_value(conf->index, prev->index, "");
+
+    hash.max_size = 512;
+    hash.bucket_size = ngx_align(64, ngx_cacheline_size);
+    hash.name = "fastcgi_hide_headers_hash";
+
+    if (ngx_http_upstream_hide_headers_hash(cf, &conf->upstream,
+             &prev->upstream, ngx_http_fastcgi_hide_headers, &hash)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+    if (clcf->noname
+        && conf->upstream.upstream == NULL && conf->fastcgi_lengths == NULL)
+    {
+        conf->upstream.upstream = prev->upstream.upstream;
+        conf->fastcgi_lengths = prev->fastcgi_lengths;
+        conf->fastcgi_values = prev->fastcgi_values;
+    }
+
+    if (clcf->lmt_excpt && clcf->handler == NULL
+        && (conf->upstream.upstream || conf->fastcgi_lengths))
+    {
+        clcf->handler = ngx_http_fastcgi_handler;
+    }
+
+#if (NGX_PCRE)
+    if (conf->split_regex == NULL) {
+        conf->split_regex = prev->split_regex;
+        conf->split_name = prev->split_name;
+    }
+#endif
+
+    if (conf->params_source == NULL) {
+        conf->params = prev->params;
+#if (NGX_HTTP_CACHE)
+        conf->params_cache = prev->params_cache;
+#endif
+        conf->params_source = prev->params_source;
+    }
+
+    rc = ngx_http_fastcgi_init_params(cf, conf, &conf->params, NULL);
+    if (rc != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+#if (NGX_HTTP_CACHE)
+
+    if (conf->upstream.cache) {
+        rc = ngx_http_fastcgi_init_params(cf, conf, &conf->params_cache,
+                                          ngx_http_fastcgi_cache_headers);
+        if (rc != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+#endif
+
+    /*
+     * special handling to preserve conf->params in the "http" section
+     * to inherit it to all servers
+     */
+
+    if (prev->params.hash.buckets == NULL
+        && conf->params_source == prev->params_source)
+    {
+        prev->params = conf->params;
+#if (NGX_HTTP_CACHE)
+        prev->params_cache = conf->params_cache;
+#endif
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_init_params(ngx_conf_t *cf, ngx_http_fastcgi_loc_conf_t *conf,
+    ngx_http_fastcgi_params_t *params, ngx_keyval_t *default_params)
+{
+    u_char                       *p;
+    size_t                        size;
+    uintptr_t                    *code;
+    ngx_uint_t                    i, nsrc;
+    ngx_array_t                   headers_names, params_merged;
+    ngx_keyval_t                 *h;
+    ngx_hash_key_t               *hk;
+    ngx_hash_init_t               hash;
+    ngx_http_upstream_param_t    *src, *s;
+    ngx_http_script_compile_t     sc;
+    ngx_http_script_copy_code_t  *copy;
+
+    if (params->hash.buckets) {
+        return NGX_OK;
+    }
+
+    if (conf->params_source == NULL && default_params == NULL) {
+        params->hash.buckets = (void *) 1;
+        return NGX_OK;
+    }
+
+    params->lengths = ngx_array_create(cf->pool, 64, 1);
+    if (params->lengths == NULL) {
+        return NGX_ERROR;
+    }
+
+    params->values = ngx_array_create(cf->pool, 512, 1);
+    if (params->values == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (conf->params_source) {
+        src = conf->params_source->elts;
+        nsrc = conf->params_source->nelts;
+
+    } else {
+        src = NULL;
+        nsrc = 0;
+    }
+
+    if (default_params) {
+        if (ngx_array_init(&params_merged, cf->temp_pool, 4,
+                           sizeof(ngx_http_upstream_param_t))
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+
+        for (i = 0; i < nsrc; i++) {
+
+            s = ngx_array_push(&params_merged);
+            if (s == NULL) {
+                return NGX_ERROR;
+            }
+
+            *s = src[i];
+        }
+
+        h = default_params;
+
+        while (h->key.len) {
+
+            src = params_merged.elts;
+            nsrc = params_merged.nelts;
+
+            for (i = 0; i < nsrc; i++) {
+                if (ngx_strcasecmp(h->key.data, src[i].key.data) == 0) {
+                    goto next;
+                }
+            }
+
+            s = ngx_array_push(&params_merged);
+            if (s == NULL) {
+                return NGX_ERROR;
+            }
+
+            s->key = h->key;
+            s->value = h->value;
+            s->skip_empty = 1;
+
+        next:
+
+            h++;
+        }
+
+        src = params_merged.elts;
+        nsrc = params_merged.nelts;
+    }
+
+    for (i = 0; i < nsrc; i++) {
+
+        if (src[i].key.len > sizeof("HTTP_") - 1
+            && ngx_strncmp(src[i].key.data, "HTTP_", sizeof("HTTP_") - 1) == 0)
+        {
+            hk = ngx_array_push(&headers_names);
+            if (hk == NULL) {
+                return NGX_ERROR;
+            }
+
+            hk->key.len = src[i].key.len - 5;
+            hk->key.data = src[i].key.data + 5;
+            hk->key_hash = ngx_hash_key_lc(hk->key.data, hk->key.len);
+            hk->value = (void *) 1;
+
+            if (src[i].value.len == 0) {
+                continue;
+            }
+        }
+
+        copy = ngx_array_push_n(params->lengths,
+                                sizeof(ngx_http_script_copy_code_t));
+        if (copy == NULL) {
+            return NGX_ERROR;
+        }
+
+        copy->code = (ngx_http_script_code_pt) (void *)
+                                                 ngx_http_script_copy_len_code;
+        copy->len = src[i].key.len;
+
+        copy = ngx_array_push_n(params->lengths,
+                                sizeof(ngx_http_script_copy_code_t));
+        if (copy == NULL) {
+            return NGX_ERROR;
+        }
+
+        copy->code = (ngx_http_script_code_pt) (void *)
+                                                 ngx_http_script_copy_len_code;
+        copy->len = src[i].skip_empty;
+
+
+        size = (sizeof(ngx_http_script_copy_code_t)
+                + src[i].key.len + sizeof(uintptr_t) - 1)
+               & ~(sizeof(uintptr_t) - 1);
+
+        copy = ngx_array_push_n(params->values, size);
+        if (copy == NULL) {
+            return NGX_ERROR;
+        }
+
+        copy->code = ngx_http_script_copy_code;
+        copy->len = src[i].key.len;
+
+        p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t);
+        ngx_memcpy(p, src[i].key.data, src[i].key.len);
+
+
+        ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+        sc.cf = cf;
+        sc.source = &src[i].value;
+        sc.flushes = &params->flushes;
+        sc.lengths = &params->lengths;
+        sc.values = &params->values;
+
+        if (ngx_http_script_compile(&sc) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        code = ngx_array_push_n(params->lengths, sizeof(uintptr_t));
+        if (code == NULL) {
+            return NGX_ERROR;
+        }
+
+        *code = (uintptr_t) NULL;
+
+
+        code = ngx_array_push_n(params->values, sizeof(uintptr_t));
+        if (code == NULL) {
+            return NGX_ERROR;
+        }
+
+        *code = (uintptr_t) NULL;
+    }
+
+    code = ngx_array_push_n(params->lengths, sizeof(uintptr_t));
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    *code = (uintptr_t) NULL;
+
+    params->number = headers_names.nelts;
+
+    hash.hash = &params->hash;
+    hash.key = ngx_hash_key_lc;
+    hash.max_size = 512;
+    hash.bucket_size = 64;
+    hash.name = "fastcgi_params_hash";
+    hash.pool = cf->pool;
+    hash.temp_pool = NULL;
+
+    return ngx_hash_init(&hash, headers_names.elts, headers_names.nelts);
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_script_name_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char                       *p;
+    ngx_http_fastcgi_ctx_t       *f;
+    ngx_http_fastcgi_loc_conf_t  *flcf;
+
+    flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module);
+
+    f = ngx_http_fastcgi_split(r, flcf);
+
+    if (f == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (f->script_name.len == 0
+        || f->script_name.data[f->script_name.len - 1] != '/')
+    {
+        v->len = f->script_name.len;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = f->script_name.data;
+
+        return NGX_OK;
+    }
+
+    v->len = f->script_name.len + flcf->index.len;
+
+    v->data = ngx_pnalloc(r->pool, v->len);
+    if (v->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    p = ngx_copy(v->data, f->script_name.data, f->script_name.len);
+    ngx_memcpy(p, flcf->index.data, flcf->index.len);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_path_info_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_http_fastcgi_ctx_t       *f;
+    ngx_http_fastcgi_loc_conf_t  *flcf;
+
+    flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module);
+
+    f = ngx_http_fastcgi_split(r, flcf);
+
+    if (f == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->len = f->path_info.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = f->path_info.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_http_fastcgi_ctx_t *
+ngx_http_fastcgi_split(ngx_http_request_t *r, ngx_http_fastcgi_loc_conf_t *flcf)
+{
+    ngx_http_fastcgi_ctx_t       *f;
+#if (NGX_PCRE)
+    ngx_int_t                     n;
+    int                           captures[(1 + 2) * 3];
+
+    f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module);
+
+    if (f == NULL) {
+        f = ngx_pcalloc(r->pool, sizeof(ngx_http_fastcgi_ctx_t));
+        if (f == NULL) {
+            return NULL;
+        }
+
+        ngx_http_set_ctx(r, f, ngx_http_fastcgi_module);
+    }
+
+    if (f->script_name.len) {
+        return f;
+    }
+
+    if (flcf->split_regex == NULL) {
+        f->script_name = r->uri;
+        return f;
+    }
+
+    n = ngx_regex_exec(flcf->split_regex, &r->uri, captures, (1 + 2) * 3);
+
+    if (n >= 0) { /* match */
+        f->script_name.len = captures[3] - captures[2];
+        f->script_name.data = r->uri.data + captures[2];
+
+        f->path_info.len = captures[5] - captures[4];
+        f->path_info.data = r->uri.data + captures[4];
+
+        return f;
+    }
+
+    if (n == NGX_REGEX_NO_MATCHED) {
+        f->script_name = r->uri;
+        return f;
+    }
+
+    ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                  ngx_regex_exec_n " failed: %i on \"%V\" using \"%V\"",
+                  n, &r->uri, &flcf->split_name);
+    return NULL;
+
+#else
+
+    f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module);
+
+    if (f == NULL) {
+        f = ngx_pcalloc(r->pool, sizeof(ngx_http_fastcgi_ctx_t));
+        if (f == NULL) {
+            return NULL;
+        }
+
+        ngx_http_set_ctx(r, f, ngx_http_fastcgi_module);
+    }
+
+    f->script_name = r->uri;
+
+    return f;
+
+#endif
+}
+
+
+static char *
+ngx_http_fastcgi_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_fastcgi_loc_conf_t *flcf = conf;
+
+    ngx_url_t                   u;
+    ngx_str_t                  *value, *url;
+    ngx_uint_t                  n;
+    ngx_http_core_loc_conf_t   *clcf;
+    ngx_http_script_compile_t   sc;
+
+    if (flcf->upstream.upstream || flcf->fastcgi_lengths) {
+        return "is duplicate";
+    }
+
+    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+    clcf->handler = ngx_http_fastcgi_handler;
+
+    if (clcf->name.data[clcf->name.len - 1] == '/') {
+        clcf->auto_redirect = 1;
+    }
+
+    value = cf->args->elts;
+
+    url = &value[1];
+
+    n = ngx_http_script_variables_count(url);
+
+    if (n) {
+
+        ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+        sc.cf = cf;
+        sc.source = url;
+        sc.lengths = &flcf->fastcgi_lengths;
+        sc.values = &flcf->fastcgi_values;
+        sc.variables = n;
+        sc.complete_lengths = 1;
+        sc.complete_values = 1;
+
+        if (ngx_http_script_compile(&sc) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+        return NGX_CONF_OK;
+    }
+
+    ngx_memzero(&u, sizeof(ngx_url_t));
+
+    u.url = value[1];
+    u.no_resolve = 1;
+
+    flcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);
+    if (flcf->upstream.upstream == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_fastcgi_split_path_info(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+#if (NGX_PCRE)
+    ngx_http_fastcgi_loc_conf_t *flcf = conf;
+
+    ngx_str_t            *value;
+    ngx_regex_compile_t   rc;
+    u_char                errstr[NGX_MAX_CONF_ERRSTR];
+
+    value = cf->args->elts;
+
+    flcf->split_name = value[1];
+
+    ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
+
+    rc.pattern = value[1];
+    rc.pool = cf->pool;
+    rc.err.len = NGX_MAX_CONF_ERRSTR;
+    rc.err.data = errstr;
+
+    if (ngx_regex_compile(&rc) != NGX_OK) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err);
+        return NGX_CONF_ERROR;
+    }
+
+    if (rc.captures != 2) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "pattern \"%V\" must have 2 captures", &value[1]);
+        return NGX_CONF_ERROR;
+    }
+
+    flcf->split_regex = rc.regex;
+
+    return NGX_CONF_OK;
+
+#else
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "\"%V\" requires PCRE library", &cmd->name);
+    return NGX_CONF_ERROR;
+
+#endif
+}
+
+
+static char *
+ngx_http_fastcgi_store(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_fastcgi_loc_conf_t *flcf = conf;
+
+    ngx_str_t                  *value;
+    ngx_http_script_compile_t   sc;
+
+    if (flcf->upstream.store != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    if (ngx_strcmp(value[1].data, "off") == 0) {
+        flcf->upstream.store = 0;
+        return NGX_CONF_OK;
+    }
+
+#if (NGX_HTTP_CACHE)
+    if (flcf->upstream.cache > 0) {
+        return "is incompatible with \"fastcgi_cache\"";
+    }
+#endif
+
+    flcf->upstream.store = 1;
+
+    if (ngx_strcmp(value[1].data, "on") == 0) {
+        return NGX_CONF_OK;
+    }
+
+    /* include the terminating '\0' into script */
+    value[1].len++;
+
+    ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+    sc.cf = cf;
+    sc.source = &value[1];
+    sc.lengths = &flcf->upstream.store_lengths;
+    sc.values = &flcf->upstream.store_values;
+    sc.variables = ngx_http_script_variables_count(&value[1]);
+    sc.complete_lengths = 1;
+    sc.complete_values = 1;
+
+    if (ngx_http_script_compile(&sc) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+#if (NGX_HTTP_CACHE)
+
+static char *
+ngx_http_fastcgi_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_fastcgi_loc_conf_t *flcf = conf;
+
+    ngx_str_t                         *value;
+    ngx_http_complex_value_t           cv;
+    ngx_http_compile_complex_value_t   ccv;
+
+    value = cf->args->elts;
+
+    if (flcf->upstream.cache != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    if (ngx_strcmp(value[1].data, "off") == 0) {
+        flcf->upstream.cache = 0;
+        return NGX_CONF_OK;
+    }
+
+    if (flcf->upstream.store > 0) {
+        return "is incompatible with \"fastcgi_store\"";
+    }
+
+    flcf->upstream.cache = 1;
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[1];
+    ccv.complex_value = &cv;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (cv.lengths != NULL) {
+
+        flcf->upstream.cache_value = ngx_palloc(cf->pool,
+                                             sizeof(ngx_http_complex_value_t));
+        if (flcf->upstream.cache_value == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        *flcf->upstream.cache_value = cv;
+
+        return NGX_CONF_OK;
+    }
+
+    flcf->upstream.cache_zone = ngx_shared_memory_add(cf, &value[1], 0,
+                                                      &ngx_http_fastcgi_module);
+    if (flcf->upstream.cache_zone == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_fastcgi_cache_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_fastcgi_loc_conf_t *flcf = conf;
+
+    ngx_str_t                         *value;
+    ngx_http_compile_complex_value_t   ccv;
+
+    value = cf->args->elts;
+
+    if (flcf->cache_key.value.data) {
+        return "is duplicate";
+    }
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[1];
+    ccv.complex_value = &flcf->cache_key;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+#endif
+
+
+static char *
+ngx_http_fastcgi_lowat_check(ngx_conf_t *cf, void *post, void *data)
+{
+#if (NGX_FREEBSD)
+    ssize_t *np = data;
+
+    if ((u_long) *np >= ngx_freebsd_net_inet_tcp_sendspace) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"fastcgi_send_lowat\" must be less than %d "
+                           "(sysctl net.inet.tcp.sendspace)",
+                           ngx_freebsd_net_inet_tcp_sendspace);
+
+        return NGX_CONF_ERROR;
+    }
+
+#elif !(NGX_HAVE_SO_SNDLOWAT)
+    ssize_t *np = data;
+
+    ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                       "\"fastcgi_send_lowat\" is not supported, ignored");
+
+    *np = 0;
+
+#endif
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_flv_module.c b/nginx/src/http/modules/ngx_http_flv_module.c
new file mode 100644 (file)
index 0000000..7b72fae
--- /dev/null
@@ -0,0 +1,266 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static char *ngx_http_flv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+static ngx_command_t  ngx_http_flv_commands[] = {
+
+    { ngx_string("flv"),
+      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
+      ngx_http_flv,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static u_char  ngx_flv_header[] = "FLV\x1\x5\0\0\0\x9\0\0\0\0";
+
+
+static ngx_http_module_t  ngx_http_flv_module_ctx = {
+    NULL,                          /* preconfiguration */
+    NULL,                          /* postconfiguration */
+
+    NULL,                          /* create main configuration */
+    NULL,                          /* init main configuration */
+
+    NULL,                          /* create server configuration */
+    NULL,                          /* merge server configuration */
+
+    NULL,                          /* create location configuration */
+    NULL                           /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_flv_module = {
+    NGX_MODULE_V1,
+    &ngx_http_flv_module_ctx,      /* module context */
+    ngx_http_flv_commands,         /* module directives */
+    NGX_HTTP_MODULE,               /* module type */
+    NULL,                          /* init master */
+    NULL,                          /* init module */
+    NULL,                          /* init process */
+    NULL,                          /* init thread */
+    NULL,                          /* exit thread */
+    NULL,                          /* exit process */
+    NULL,                          /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_flv_handler(ngx_http_request_t *r)
+{
+    u_char                    *last;
+    off_t                      start, len;
+    size_t                     root;
+    ngx_int_t                  rc;
+    ngx_uint_t                 level, i;
+    ngx_str_t                  path, value;
+    ngx_log_t                 *log;
+    ngx_buf_t                 *b;
+    ngx_chain_t                out[2];
+    ngx_open_file_info_t       of;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
+        return NGX_HTTP_NOT_ALLOWED;
+    }
+
+    if (r->uri.data[r->uri.len - 1] == '/') {
+        return NGX_DECLINED;
+    }
+
+    rc = ngx_http_discard_request_body(r);
+
+    if (rc != NGX_OK) {
+        return rc;
+    }
+
+    last = ngx_http_map_uri_to_path(r, &path, &root, 0);
+    if (last == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    log = r->connection->log;
+
+    path.len = last - path.data;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+                   "http flv filename: \"%V\"", &path);
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+    of.read_ahead = clcf->read_ahead;
+    of.directio = clcf->directio;
+    of.valid = clcf->open_file_cache_valid;
+    of.min_uses = clcf->open_file_cache_min_uses;
+    of.errors = clcf->open_file_cache_errors;
+    of.events = clcf->open_file_cache_events;
+
+    if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
+        != NGX_OK)
+    {
+        switch (of.err) {
+
+        case 0:
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+
+        case NGX_ENOENT:
+        case NGX_ENOTDIR:
+        case NGX_ENAMETOOLONG:
+
+            level = NGX_LOG_ERR;
+            rc = NGX_HTTP_NOT_FOUND;
+            break;
+
+        case NGX_EACCES:
+#if (NGX_HAVE_OPENAT)
+        case NGX_EMLINK:
+        case NGX_ELOOP:
+#endif
+
+            level = NGX_LOG_ERR;
+            rc = NGX_HTTP_FORBIDDEN;
+            break;
+
+        default:
+
+            level = NGX_LOG_CRIT;
+            rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+            break;
+        }
+
+        if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) {
+            ngx_log_error(level, log, of.err,
+                          "%s \"%s\" failed", of.failed, path.data);
+        }
+
+        return rc;
+    }
+
+    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;
+    }
+
+    r->root_tested = !r->error_page;
+
+    start = 0;
+    len = of.size;
+    i = 1;
+
+    if (r->args.len) {
+
+        if (ngx_http_arg(r, (u_char *) "start", 5, &value) == NGX_OK) {
+
+            start = ngx_atoof(value.data, value.len);
+
+            if (start == NGX_ERROR || start >= len) {
+                start = 0;
+            }
+
+            if (start) {
+                len = sizeof(ngx_flv_header) - 1 + len - start;
+                i = 0;
+            }
+        }
+    }
+
+    log->action = "sending flv to client";
+
+    r->headers_out.status = NGX_HTTP_OK;
+    r->headers_out.content_length_n = len;
+    r->headers_out.last_modified_time = of.mtime;
+
+    if (ngx_http_set_etag(r) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (ngx_http_set_content_type(r) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (i == 0) {
+        b = ngx_calloc_buf(r->pool);
+        if (b == NULL) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        b->pos = ngx_flv_header;
+        b->last = ngx_flv_header + sizeof(ngx_flv_header) - 1;
+        b->memory = 1;
+
+        out[0].buf = b;
+        out[0].next = &out[1];
+    }
+
+
+    b = ngx_calloc_buf(r->pool);
+    if (b == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
+    if (b->file == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    r->allow_ranges = 1;
+
+    rc = ngx_http_send_header(r);
+
+    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+        return rc;
+    }
+
+    b->file_pos = start;
+    b->file_last = of.size;
+
+    b->in_file = b->file_last ? 1: 0;
+    b->last_buf = (r == r->main) ? 1 : 0;
+    b->last_in_chain = 1;
+
+    b->file->fd = of.fd;
+    b->file->name = path;
+    b->file->log = log;
+    b->file->directio = of.is_directio;
+
+    out[1].buf = b;
+    out[1].next = NULL;
+
+    return ngx_http_output_filter(r, &out[i]);
+}
+
+
+static char *
+ngx_http_flv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_loc_conf_t  *clcf;
+
+    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+    clcf->handler = ngx_http_flv_handler;
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_geo_module.c b/nginx/src/http/modules/ngx_http_geo_module.c
new file mode 100644 (file)
index 0000000..c11bafa
--- /dev/null
@@ -0,0 +1,1668 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_http_variable_value_t       *value;
+    u_short                          start;
+    u_short                          end;
+} ngx_http_geo_range_t;
+
+
+typedef struct {
+    ngx_radix_tree_t                *tree;
+#if (NGX_HAVE_INET6)
+    ngx_radix_tree_t                *tree6;
+#endif
+} ngx_http_geo_trees_t;
+
+
+typedef struct {
+    ngx_http_geo_range_t           **low;
+    ngx_http_variable_value_t       *default_value;
+} ngx_http_geo_high_ranges_t;
+
+
+typedef struct {
+    ngx_str_node_t                   sn;
+    ngx_http_variable_value_t       *value;
+    size_t                           offset;
+} ngx_http_geo_variable_value_node_t;
+
+
+typedef struct {
+    ngx_http_variable_value_t       *value;
+    ngx_str_t                       *net;
+    ngx_http_geo_high_ranges_t       high;
+    ngx_radix_tree_t                *tree;
+#if (NGX_HAVE_INET6)
+    ngx_radix_tree_t                *tree6;
+#endif
+    ngx_rbtree_t                     rbtree;
+    ngx_rbtree_node_t                sentinel;
+    ngx_array_t                     *proxies;
+    ngx_pool_t                      *pool;
+    ngx_pool_t                      *temp_pool;
+
+    size_t                           data_size;
+
+    ngx_str_t                        include_name;
+    ngx_uint_t                       includes;
+    ngx_uint_t                       entries;
+
+    unsigned                         ranges:1;
+    unsigned                         outside_entries:1;
+    unsigned                         allow_binary_include:1;
+    unsigned                         binary_include:1;
+    unsigned                         proxy_recursive:1;
+} ngx_http_geo_conf_ctx_t;
+
+
+typedef struct {
+    union {
+        ngx_http_geo_trees_t         trees;
+        ngx_http_geo_high_ranges_t   high;
+    } u;
+
+    ngx_array_t                     *proxies;
+    unsigned                         proxy_recursive:1;
+
+    ngx_int_t                        index;
+} ngx_http_geo_ctx_t;
+
+
+static ngx_int_t ngx_http_geo_addr(ngx_http_request_t *r,
+    ngx_http_geo_ctx_t *ctx, ngx_addr_t *addr);
+static ngx_int_t ngx_http_geo_real_addr(ngx_http_request_t *r,
+    ngx_http_geo_ctx_t *ctx, ngx_addr_t *addr);
+static char *ngx_http_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_http_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf);
+static char *ngx_http_geo_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+    ngx_str_t *value);
+static char *ngx_http_geo_add_range(ngx_conf_t *cf,
+    ngx_http_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end);
+static ngx_uint_t ngx_http_geo_delete_range(ngx_conf_t *cf,
+    ngx_http_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end);
+static char *ngx_http_geo_cidr(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+    ngx_str_t *value);
+static char *ngx_http_geo_cidr_add(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+    ngx_cidr_t *cidr, ngx_str_t *value, ngx_str_t *net);
+static ngx_http_variable_value_t *ngx_http_geo_value(ngx_conf_t *cf,
+    ngx_http_geo_conf_ctx_t *ctx, ngx_str_t *value);
+static char *ngx_http_geo_add_proxy(ngx_conf_t *cf,
+    ngx_http_geo_conf_ctx_t *ctx, ngx_cidr_t *cidr);
+static ngx_int_t ngx_http_geo_cidr_value(ngx_conf_t *cf, ngx_str_t *net,
+    ngx_cidr_t *cidr);
+static char *ngx_http_geo_include(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+    ngx_str_t *name);
+static ngx_int_t ngx_http_geo_include_binary_base(ngx_conf_t *cf,
+    ngx_http_geo_conf_ctx_t *ctx, ngx_str_t *name);
+static void ngx_http_geo_create_binary_base(ngx_http_geo_conf_ctx_t *ctx);
+static u_char *ngx_http_geo_copy_values(u_char *base, u_char *p,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
+
+
+static ngx_command_t  ngx_http_geo_commands[] = {
+
+    { ngx_string("geo"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12,
+      ngx_http_geo_block,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_geo_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_geo_module = {
+    NGX_MODULE_V1,
+    &ngx_http_geo_module_ctx,              /* module context */
+    ngx_http_geo_commands,                 /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+typedef struct {
+    u_char    GEORNG[6];
+    u_char    version;
+    u_char    ptr_size;
+    uint32_t  endianness;
+    uint32_t  crc32;
+} ngx_http_geo_header_t;
+
+
+static ngx_http_geo_header_t  ngx_http_geo_header = {
+    { 'G', 'E', 'O', 'R', 'N', 'G' }, 0, sizeof(void *), 0x12345678, 0
+};
+
+
+/* geo range is AF_INET only */
+
+static ngx_int_t
+ngx_http_geo_cidr_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+    uintptr_t data)
+{
+    ngx_http_geo_ctx_t *ctx = (ngx_http_geo_ctx_t *) data;
+
+    in_addr_t                   inaddr;
+    ngx_addr_t                  addr;
+    struct sockaddr_in         *sin;
+    ngx_http_variable_value_t  *vv;
+#if (NGX_HAVE_INET6)
+    u_char                     *p;
+    struct in6_addr            *inaddr6;
+#endif
+
+    if (ngx_http_geo_addr(r, ctx, &addr) != NGX_OK) {
+        vv = (ngx_http_variable_value_t *)
+                  ngx_radix32tree_find(ctx->u.trees.tree, INADDR_NONE);
+        goto done;
+    }
+
+    switch (addr.sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+    case AF_INET6:
+        inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr;
+        p = inaddr6->s6_addr;
+
+        if (IN6_IS_ADDR_V4MAPPED(inaddr6)) {
+            inaddr = p[12] << 24;
+            inaddr += p[13] << 16;
+            inaddr += p[14] << 8;
+            inaddr += p[15];
+
+            vv = (ngx_http_variable_value_t *)
+                      ngx_radix32tree_find(ctx->u.trees.tree, inaddr);
+
+        } else {
+            vv = (ngx_http_variable_value_t *)
+                      ngx_radix128tree_find(ctx->u.trees.tree6, p);
+        }
+
+        break;
+#endif
+
+    default: /* AF_INET */
+        sin = (struct sockaddr_in *) addr.sockaddr;
+        inaddr = ntohl(sin->sin_addr.s_addr);
+
+        vv = (ngx_http_variable_value_t *)
+                  ngx_radix32tree_find(ctx->u.trees.tree, inaddr);
+
+        break;
+    }
+
+done:
+
+    *v = *vv;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http geo: %v", v);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_geo_range_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+    uintptr_t data)
+{
+    ngx_http_geo_ctx_t *ctx = (ngx_http_geo_ctx_t *) data;
+
+    in_addr_t              inaddr;
+    ngx_addr_t             addr;
+    ngx_uint_t             n;
+    struct sockaddr_in    *sin;
+    ngx_http_geo_range_t  *range;
+#if (NGX_HAVE_INET6)
+    u_char                *p;
+    struct in6_addr       *inaddr6;
+#endif
+
+    *v = *ctx->u.high.default_value;
+
+    if (ngx_http_geo_addr(r, ctx, &addr) == NGX_OK) {
+
+        switch (addr.sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+        case AF_INET6:
+            inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr;
+
+            if (IN6_IS_ADDR_V4MAPPED(inaddr6)) {
+                p = inaddr6->s6_addr;
+
+                inaddr = p[12] << 24;
+                inaddr += p[13] << 16;
+                inaddr += p[14] << 8;
+                inaddr += p[15];
+
+            } else {
+                inaddr = INADDR_NONE;
+            }
+
+            break;
+#endif
+
+        default: /* AF_INET */
+            sin = (struct sockaddr_in *) addr.sockaddr;
+            inaddr = ntohl(sin->sin_addr.s_addr);
+            break;
+        }
+
+    } else {
+        inaddr = INADDR_NONE;
+    }
+
+    if (ctx->u.high.low) {
+        range = ctx->u.high.low[inaddr >> 16];
+
+        if (range) {
+            n = inaddr & 0xffff;
+            do {
+                if (n >= (ngx_uint_t) range->start
+                    && n <= (ngx_uint_t) range->end)
+                {
+                    *v = *range->value;
+                    break;
+                }
+            } while ((++range)->value);
+        }
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http geo: %v", v);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_geo_addr(ngx_http_request_t *r, ngx_http_geo_ctx_t *ctx,
+    ngx_addr_t *addr)
+{
+    ngx_array_t  *xfwd;
+
+    if (ngx_http_geo_real_addr(r, ctx, addr) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    xfwd = &r->headers_in.x_forwarded_for;
+
+    if (xfwd->nelts > 0 && ctx->proxies != NULL) {
+        (void) ngx_http_get_forwarded_addr(r, addr, xfwd, NULL,
+                                           ctx->proxies, ctx->proxy_recursive);
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_geo_real_addr(ngx_http_request_t *r, ngx_http_geo_ctx_t *ctx,
+    ngx_addr_t *addr)
+{
+    ngx_http_variable_value_t  *v;
+
+    if (ctx->index == -1) {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http geo started: %V", &r->connection->addr_text);
+
+        addr->sockaddr = r->connection->sockaddr;
+        addr->socklen = r->connection->socklen;
+        /* addr->name = r->connection->addr_text; */
+
+        return NGX_OK;
+    }
+
+    v = ngx_http_get_flushed_variable(r, ctx->index);
+
+    if (v == NULL || v->not_found) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http geo not found");
+
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http geo started: %v", v);
+
+    if (ngx_parse_addr(r->pool, addr, v->data, v->len) == NGX_OK) {
+        return NGX_OK;
+    }
+
+    return NGX_ERROR;
+}
+
+
+static char *
+ngx_http_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char                     *rv;
+    size_t                    len;
+    ngx_str_t                *value, name;
+    ngx_uint_t                i;
+    ngx_conf_t                save;
+    ngx_pool_t               *pool;
+    ngx_array_t              *a;
+    ngx_http_variable_t      *var;
+    ngx_http_geo_ctx_t       *geo;
+    ngx_http_geo_conf_ctx_t   ctx;
+#if (NGX_HAVE_INET6)
+    static struct in6_addr    zero;
+#endif
+
+    value = cf->args->elts;
+
+    geo = ngx_palloc(cf->pool, sizeof(ngx_http_geo_ctx_t));
+    if (geo == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    name = value[1];
+
+    if (name.data[0] != '$') {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid variable name \"%V\"", &name);
+        return NGX_CONF_ERROR;
+    }
+
+    name.len--;
+    name.data++;
+
+    if (cf->args->nelts == 3) {
+
+        geo->index = ngx_http_get_variable_index(cf, &name);
+        if (geo->index == NGX_ERROR) {
+            return NGX_CONF_ERROR;
+        }
+
+        name = value[2];
+
+        if (name.data[0] != '$') {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid variable name \"%V\"", &name);
+            return NGX_CONF_ERROR;
+        }
+
+        name.len--;
+        name.data++;
+
+    } else {
+        geo->index = -1;
+    }
+
+    var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
+    if (var == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
+    if (pool == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memzero(&ctx, sizeof(ngx_http_geo_conf_ctx_t));
+
+    ctx.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
+    if (ctx.temp_pool == NULL) {
+        ngx_destroy_pool(pool);
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_rbtree_init(&ctx.rbtree, &ctx.sentinel, ngx_str_rbtree_insert_value);
+
+    ctx.pool = cf->pool;
+    ctx.data_size = sizeof(ngx_http_geo_header_t)
+                  + sizeof(ngx_http_variable_value_t)
+                  + 0x10000 * sizeof(ngx_http_geo_range_t *);
+    ctx.allow_binary_include = 1;
+
+    save = *cf;
+    cf->pool = pool;
+    cf->ctx = &ctx;
+    cf->handler = ngx_http_geo;
+    cf->handler_conf = conf;
+
+    rv = ngx_conf_parse(cf, NULL);
+
+    *cf = save;
+
+    if (rv != NGX_CONF_OK) {
+        goto failed;
+    }
+
+    geo->proxies = ctx.proxies;
+    geo->proxy_recursive = ctx.proxy_recursive;
+
+    if (ctx.ranges) {
+
+        if (ctx.high.low && !ctx.binary_include) {
+            for (i = 0; i < 0x10000; i++) {
+                a = (ngx_array_t *) ctx.high.low[i];
+
+                if (a == NULL) {
+                    continue;
+                }
+
+                if (a->nelts == 0) {
+                    ctx.high.low[i] = NULL;
+                    continue;
+                }
+
+                len = a->nelts * sizeof(ngx_http_geo_range_t);
+
+                ctx.high.low[i] = ngx_palloc(cf->pool, len + sizeof(void *));
+                if (ctx.high.low[i] == NULL) {
+                    goto failed;
+                }
+
+                ngx_memcpy(ctx.high.low[i], a->elts, len);
+                ctx.high.low[i][a->nelts].value = NULL;
+                ctx.data_size += len + sizeof(void *);
+            }
+
+            if (ctx.allow_binary_include
+                && !ctx.outside_entries
+                && ctx.entries > 100000
+                && ctx.includes == 1)
+            {
+                ngx_http_geo_create_binary_base(&ctx);
+            }
+        }
+
+        if (ctx.high.default_value == NULL) {
+            ctx.high.default_value = &ngx_http_variable_null_value;
+        }
+
+        geo->u.high = ctx.high;
+
+        var->get_handler = ngx_http_geo_range_variable;
+        var->data = (uintptr_t) geo;
+
+    } else {
+        if (ctx.tree == NULL) {
+            ctx.tree = ngx_radix_tree_create(cf->pool, -1);
+            if (ctx.tree == NULL) {
+                goto failed;
+            }
+        }
+
+        geo->u.trees.tree = ctx.tree;
+
+#if (NGX_HAVE_INET6)
+        if (ctx.tree6 == NULL) {
+            ctx.tree6 = ngx_radix_tree_create(cf->pool, -1);
+            if (ctx.tree6 == NULL) {
+                goto failed;
+            }
+        }
+
+        geo->u.trees.tree6 = ctx.tree6;
+#endif
+
+        var->get_handler = ngx_http_geo_cidr_variable;
+        var->data = (uintptr_t) geo;
+
+        if (ngx_radix32tree_insert(ctx.tree, 0, 0,
+                                   (uintptr_t) &ngx_http_variable_null_value)
+            == NGX_ERROR)
+        {
+            goto failed;
+        }
+
+        /* NGX_BUSY is okay (default was set explicitly) */
+
+#if (NGX_HAVE_INET6)
+        if (ngx_radix128tree_insert(ctx.tree6, zero.s6_addr, zero.s6_addr,
+                                    (uintptr_t) &ngx_http_variable_null_value)
+            == NGX_ERROR)
+        {
+            goto failed;
+        }
+#endif
+    }
+
+    ngx_destroy_pool(ctx.temp_pool);
+    ngx_destroy_pool(pool);
+
+    return NGX_CONF_OK;
+
+failed:
+
+    ngx_destroy_pool(ctx.temp_pool);
+    ngx_destroy_pool(pool);
+
+    return NGX_CONF_ERROR;
+}
+
+
+static char *
+ngx_http_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
+{
+    char                     *rv;
+    ngx_str_t                *value;
+    ngx_cidr_t                cidr;
+    ngx_http_geo_conf_ctx_t  *ctx;
+
+    ctx = cf->ctx;
+
+    value = cf->args->elts;
+
+    if (cf->args->nelts == 1) {
+
+        if (ngx_strcmp(value[0].data, "ranges") == 0) {
+
+            if (ctx->tree
+#if (NGX_HAVE_INET6)
+                || ctx->tree6
+#endif
+               )
+            {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "the \"ranges\" directive must be "
+                                   "the first directive inside \"geo\" block");
+                goto failed;
+            }
+
+            ctx->ranges = 1;
+
+            rv = NGX_CONF_OK;
+
+            goto done;
+        }
+
+        else if (ngx_strcmp(value[0].data, "proxy_recursive") == 0) {
+            ctx->proxy_recursive = 1;
+            rv = NGX_CONF_OK;
+            goto done;
+        }
+    }
+
+    if (cf->args->nelts != 2) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid number of the geo parameters");
+        goto failed;
+    }
+
+    if (ngx_strcmp(value[0].data, "include") == 0) {
+
+        rv = ngx_http_geo_include(cf, ctx, &value[1]);
+
+        goto done;
+
+    } else if (ngx_strcmp(value[0].data, "proxy") == 0) {
+
+        if (ngx_http_geo_cidr_value(cf, &value[1], &cidr) != NGX_OK) {
+            goto failed;
+        }
+
+        rv = ngx_http_geo_add_proxy(cf, ctx, &cidr);
+
+        goto done;
+    }
+
+    if (ctx->ranges) {
+        rv = ngx_http_geo_range(cf, ctx, value);
+
+    } else {
+        rv = ngx_http_geo_cidr(cf, ctx, value);
+    }
+
+done:
+
+    ngx_reset_pool(cf->pool);
+
+    return rv;
+
+failed:
+
+    ngx_reset_pool(cf->pool);
+
+    return NGX_CONF_ERROR;
+}
+
+
+static char *
+ngx_http_geo_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+    ngx_str_t *value)
+{
+    u_char      *p, *last;
+    in_addr_t    start, end;
+    ngx_str_t   *net;
+    ngx_uint_t   del;
+
+    if (ngx_strcmp(value[0].data, "default") == 0) {
+
+        if (ctx->high.default_value) {
+            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                "duplicate default geo range value: \"%V\", old value: \"%v\"",
+                &value[1], ctx->high.default_value);
+        }
+
+        ctx->high.default_value = ngx_http_geo_value(cf, ctx, &value[1]);
+        if (ctx->high.default_value == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        return NGX_CONF_OK;
+    }
+
+    if (ctx->binary_include) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+            "binary geo range base \"%s\" cannot be mixed with usual entries",
+            ctx->include_name.data);
+        return NGX_CONF_ERROR;
+    }
+
+    if (ctx->high.low == NULL) {
+        ctx->high.low = ngx_pcalloc(ctx->pool,
+                                    0x10000 * sizeof(ngx_http_geo_range_t *));
+        if (ctx->high.low == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    ctx->entries++;
+    ctx->outside_entries = 1;
+
+    if (ngx_strcmp(value[0].data, "delete") == 0) {
+        net = &value[1];
+        del = 1;
+
+    } else {
+        net = &value[0];
+        del = 0;
+    }
+
+    last = net->data + net->len;
+
+    p = ngx_strlchr(net->data, last, '-');
+
+    if (p == NULL) {
+        goto invalid;
+    }
+
+    start = ngx_inet_addr(net->data, p - net->data);
+
+    if (start == INADDR_NONE) {
+        goto invalid;
+    }
+
+    start = ntohl(start);
+
+    p++;
+
+    end = ngx_inet_addr(p, last - p);
+
+    if (end == INADDR_NONE) {
+        goto invalid;
+    }
+
+    end = ntohl(end);
+
+    if (start > end) {
+        goto invalid;
+    }
+
+    if (del) {
+        if (ngx_http_geo_delete_range(cf, ctx, start, end)) {
+            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                               "no address range \"%V\" to delete", net);
+        }
+
+        return NGX_CONF_OK;
+    }
+
+    ctx->value = ngx_http_geo_value(cf, ctx, &value[1]);
+
+    if (ctx->value == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ctx->net = net;
+
+    return ngx_http_geo_add_range(cf, ctx, start, end);
+
+invalid:
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid range \"%V\"", net);
+
+    return NGX_CONF_ERROR;
+}
+
+
+/* the add procedure is optimized to add a growing up sequence */
+
+static char *
+ngx_http_geo_add_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+    in_addr_t start, in_addr_t end)
+{
+    in_addr_t              n;
+    ngx_uint_t             h, i, s, e;
+    ngx_array_t           *a;
+    ngx_http_geo_range_t  *range;
+
+    for (n = start; n <= end; n = (n + 0x10000) & 0xffff0000) {
+
+        h = n >> 16;
+
+        if (n == start) {
+            s = n & 0xffff;
+        } else {
+            s = 0;
+        }
+
+        if ((n | 0xffff) > end) {
+            e = end & 0xffff;
+
+        } else {
+            e = 0xffff;
+        }
+
+        a = (ngx_array_t *) ctx->high.low[h];
+
+        if (a == NULL) {
+            a = ngx_array_create(ctx->temp_pool, 64,
+                                 sizeof(ngx_http_geo_range_t));
+            if (a == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            ctx->high.low[h] = (ngx_http_geo_range_t *) a;
+        }
+
+        i = a->nelts;
+        range = a->elts;
+
+        while (i) {
+
+            i--;
+
+            if (e < (ngx_uint_t) range[i].start) {
+                continue;
+            }
+
+            if (s > (ngx_uint_t) range[i].end) {
+
+                /* add after the range */
+
+                range = ngx_array_push(a);
+                if (range == NULL) {
+                    return NGX_CONF_ERROR;
+                }
+
+                range = a->elts;
+
+                ngx_memmove(&range[i + 2], &range[i + 1],
+                            (a->nelts - 2 - i) * sizeof(ngx_http_geo_range_t));
+
+                range[i + 1].start = (u_short) s;
+                range[i + 1].end = (u_short) e;
+                range[i + 1].value = ctx->value;
+
+                goto next;
+            }
+
+            if (s == (ngx_uint_t) range[i].start
+                && e == (ngx_uint_t) range[i].end)
+            {
+                ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                    "duplicate range \"%V\", value: \"%v\", old value: \"%v\"",
+                    ctx->net, ctx->value, range[i].value);
+
+                range[i].value = ctx->value;
+
+                goto next;
+            }
+
+            if (s > (ngx_uint_t) range[i].start
+                && e < (ngx_uint_t) range[i].end)
+            {
+                /* split the range and insert the new one */
+
+                range = ngx_array_push(a);
+                if (range == NULL) {
+                    return NGX_CONF_ERROR;
+                }
+
+                range = ngx_array_push(a);
+                if (range == NULL) {
+                    return NGX_CONF_ERROR;
+                }
+
+                range = a->elts;
+
+                ngx_memmove(&range[i + 3], &range[i + 1],
+                            (a->nelts - 3 - i) * sizeof(ngx_http_geo_range_t));
+
+                range[i + 2].start = (u_short) (e + 1);
+                range[i + 2].end = range[i].end;
+                range[i + 2].value = range[i].value;
+
+                range[i + 1].start = (u_short) s;
+                range[i + 1].end = (u_short) e;
+                range[i + 1].value = ctx->value;
+
+                range[i].end = (u_short) (s - 1);
+
+                goto next;
+            }
+
+            if (s == (ngx_uint_t) range[i].start
+                && e < (ngx_uint_t) range[i].end)
+            {
+                /* shift the range start and insert the new range */
+
+                range = ngx_array_push(a);
+                if (range == NULL) {
+                    return NGX_CONF_ERROR;
+                }
+
+                range = a->elts;
+
+                ngx_memmove(&range[i + 1], &range[i],
+                            (a->nelts - 1 - i) * sizeof(ngx_http_geo_range_t));
+
+                range[i + 1].start = (u_short) (e + 1);
+
+                range[i].start = (u_short) s;
+                range[i].end = (u_short) e;
+                range[i].value = ctx->value;
+
+                goto next;
+            }
+
+            if (s > (ngx_uint_t) range[i].start
+                && e == (ngx_uint_t) range[i].end)
+            {
+                /* shift the range end and insert the new range */
+
+                range = ngx_array_push(a);
+                if (range == NULL) {
+                    return NGX_CONF_ERROR;
+                }
+
+                range = a->elts;
+
+                ngx_memmove(&range[i + 2], &range[i + 1],
+                            (a->nelts - 2 - i) * sizeof(ngx_http_geo_range_t));
+
+                range[i + 1].start = (u_short) s;
+                range[i + 1].end = (u_short) e;
+                range[i + 1].value = ctx->value;
+
+                range[i].end = (u_short) (s - 1);
+
+                goto next;
+            }
+
+            s = (ngx_uint_t) range[i].start;
+            e = (ngx_uint_t) range[i].end;
+
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                         "range \"%V\" overlaps \"%d.%d.%d.%d-%d.%d.%d.%d\"",
+                         ctx->net,
+                         h >> 8, h & 0xff, s >> 8, s & 0xff,
+                         h >> 8, h & 0xff, e >> 8, e & 0xff);
+
+            return NGX_CONF_ERROR;
+        }
+
+        /* add the first range */
+
+        range = ngx_array_push(a);
+        if (range == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        range = a->elts;
+
+        ngx_memmove(&range[1], &range[0],
+                    (a->nelts - 1) * sizeof(ngx_http_geo_range_t));
+
+        range[0].start = (u_short) s;
+        range[0].end = (u_short) e;
+        range[0].value = ctx->value;
+
+    next:
+
+        if (h == 0xffff) {
+            break;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_uint_t
+ngx_http_geo_delete_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+    in_addr_t start, in_addr_t end)
+{
+    in_addr_t              n;
+    ngx_uint_t             h, i, s, e, warn;
+    ngx_array_t           *a;
+    ngx_http_geo_range_t  *range;
+
+    warn = 0;
+
+    for (n = start; n <= end; n = (n + 0x10000) & 0xffff0000) {
+
+        h = n >> 16;
+
+        if (n == start) {
+            s = n & 0xffff;
+        } else {
+            s = 0;
+        }
+
+        if ((n | 0xffff) > end) {
+            e = end & 0xffff;
+
+        } else {
+            e = 0xffff;
+        }
+
+        a = (ngx_array_t *) ctx->high.low[h];
+
+        if (a == NULL || a->nelts == 0) {
+            warn = 1;
+            goto next;
+        }
+
+        range = a->elts;
+        for (i = 0; i < a->nelts; i++) {
+
+            if (s == (ngx_uint_t) range[i].start
+                && e == (ngx_uint_t) range[i].end)
+            {
+                ngx_memmove(&range[i], &range[i + 1],
+                            (a->nelts - 1 - i) * sizeof(ngx_http_geo_range_t));
+
+                a->nelts--;
+
+                break;
+            }
+
+            if (i == a->nelts - 1) {
+                warn = 1;
+            }
+        }
+
+    next:
+
+        if (h == 0xffff) {
+            break;
+        }
+    }
+
+    return warn;
+}
+
+
+static char *
+ngx_http_geo_cidr(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+    ngx_str_t *value)
+{
+    char        *rv;
+    ngx_int_t    rc, del;
+    ngx_str_t   *net;
+    ngx_cidr_t   cidr;
+
+    if (ctx->tree == NULL) {
+        ctx->tree = ngx_radix_tree_create(ctx->pool, -1);
+        if (ctx->tree == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+#if (NGX_HAVE_INET6)
+    if (ctx->tree6 == NULL) {
+        ctx->tree6 = ngx_radix_tree_create(ctx->pool, -1);
+        if (ctx->tree6 == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+#endif
+
+    if (ngx_strcmp(value[0].data, "default") == 0) {
+        cidr.family = AF_INET;
+        cidr.u.in.addr = 0;
+        cidr.u.in.mask = 0;
+
+        rv = ngx_http_geo_cidr_add(cf, ctx, &cidr, &value[1], &value[0]);
+
+        if (rv != NGX_CONF_OK) {
+            return rv;
+        }
+
+#if (NGX_HAVE_INET6)
+        cidr.family = AF_INET6;
+        ngx_memzero(&cidr.u.in6, sizeof(ngx_in6_cidr_t));
+
+        rv = ngx_http_geo_cidr_add(cf, ctx, &cidr, &value[1], &value[0]);
+
+        if (rv != NGX_CONF_OK) {
+            return rv;
+        }
+#endif
+
+        return NGX_CONF_OK;
+    }
+
+    if (ngx_strcmp(value[0].data, "delete") == 0) {
+        net = &value[1];
+        del = 1;
+
+    } else {
+        net = &value[0];
+        del = 0;
+    }
+
+    if (ngx_http_geo_cidr_value(cf, net, &cidr) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (cidr.family == AF_INET) {
+        cidr.u.in.addr = ntohl(cidr.u.in.addr);
+        cidr.u.in.mask = ntohl(cidr.u.in.mask);
+    }
+
+    if (del) {
+        switch (cidr.family) {
+
+#if (NGX_HAVE_INET6)
+        case AF_INET6:
+            rc = ngx_radix128tree_delete(ctx->tree6,
+                                         cidr.u.in6.addr.s6_addr,
+                                         cidr.u.in6.mask.s6_addr);
+            break;
+#endif
+
+        default: /* AF_INET */
+            rc = ngx_radix32tree_delete(ctx->tree, cidr.u.in.addr,
+                                        cidr.u.in.mask);
+            break;
+        }
+
+        if (rc != NGX_OK) {
+            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                               "no network \"%V\" to delete", net);
+        }
+
+        return NGX_CONF_OK;
+    }
+
+    return ngx_http_geo_cidr_add(cf, ctx, &cidr, &value[1], net);
+}
+
+
+static char *
+ngx_http_geo_cidr_add(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+    ngx_cidr_t *cidr, ngx_str_t *value, ngx_str_t *net)
+{
+    ngx_int_t                   rc;
+    ngx_http_variable_value_t  *val, *old;
+
+    val = ngx_http_geo_value(cf, ctx, value);
+
+    if (val == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    switch (cidr->family) {
+
+#if (NGX_HAVE_INET6)
+    case AF_INET6:
+        rc = ngx_radix128tree_insert(ctx->tree6, cidr->u.in6.addr.s6_addr,
+                                     cidr->u.in6.mask.s6_addr,
+                                     (uintptr_t) val);
+
+        if (rc == NGX_OK) {
+            return NGX_CONF_OK;
+        }
+
+        if (rc == NGX_ERROR) {
+            return NGX_CONF_ERROR;
+        }
+
+        /* rc == NGX_BUSY */
+
+        old = (ngx_http_variable_value_t *)
+                   ngx_radix128tree_find(ctx->tree6,
+                                         cidr->u.in6.addr.s6_addr);
+
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+              "duplicate network \"%V\", value: \"%v\", old value: \"%v\"",
+              net, val, old);
+
+        rc = ngx_radix128tree_delete(ctx->tree6,
+                                     cidr->u.in6.addr.s6_addr,
+                                     cidr->u.in6.mask.s6_addr);
+
+        if (rc == NGX_ERROR) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree");
+            return NGX_CONF_ERROR;
+        }
+
+        rc = ngx_radix128tree_insert(ctx->tree6, cidr->u.in6.addr.s6_addr,
+                                     cidr->u.in6.mask.s6_addr,
+                                     (uintptr_t) val);
+
+        break;
+#endif
+
+    default: /* AF_INET */
+        rc = ngx_radix32tree_insert(ctx->tree, cidr->u.in.addr,
+                                    cidr->u.in.mask, (uintptr_t) val);
+
+        if (rc == NGX_OK) {
+            return NGX_CONF_OK;
+        }
+
+        if (rc == NGX_ERROR) {
+            return NGX_CONF_ERROR;
+        }
+
+        /* rc == NGX_BUSY */
+
+        old = (ngx_http_variable_value_t *)
+                   ngx_radix32tree_find(ctx->tree, cidr->u.in.addr);
+
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+              "duplicate network \"%V\", value: \"%v\", old value: \"%v\"",
+              net, val, old);
+
+        rc = ngx_radix32tree_delete(ctx->tree,
+                                    cidr->u.in.addr, cidr->u.in.mask);
+
+        if (rc == NGX_ERROR) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree");
+            return NGX_CONF_ERROR;
+        }
+
+        rc = ngx_radix32tree_insert(ctx->tree, cidr->u.in.addr,
+                                    cidr->u.in.mask, (uintptr_t) val);
+
+        break;
+    }
+
+    if (rc == NGX_OK) {
+        return NGX_CONF_OK;
+    }
+
+    return NGX_CONF_ERROR;
+}
+
+
+static ngx_http_variable_value_t *
+ngx_http_geo_value(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+    ngx_str_t *value)
+{
+    uint32_t                             hash;
+    ngx_http_variable_value_t           *val;
+    ngx_http_geo_variable_value_node_t  *gvvn;
+
+    hash = ngx_crc32_long(value->data, value->len);
+
+    gvvn = (ngx_http_geo_variable_value_node_t *)
+               ngx_str_rbtree_lookup(&ctx->rbtree, value, hash);
+
+    if (gvvn) {
+        return gvvn->value;
+    }
+
+    val = ngx_palloc(ctx->pool, sizeof(ngx_http_variable_value_t));
+    if (val == NULL) {
+        return NULL;
+    }
+
+    val->len = value->len;
+    val->data = ngx_pstrdup(ctx->pool, value);
+    if (val->data == NULL) {
+        return NULL;
+    }
+
+    val->valid = 1;
+    val->no_cacheable = 0;
+    val->not_found = 0;
+
+    gvvn = ngx_palloc(ctx->temp_pool,
+                      sizeof(ngx_http_geo_variable_value_node_t));
+    if (gvvn == NULL) {
+        return NULL;
+    }
+
+    gvvn->sn.node.key = hash;
+    gvvn->sn.str.len = val->len;
+    gvvn->sn.str.data = val->data;
+    gvvn->value = val;
+    gvvn->offset = 0;
+
+    ngx_rbtree_insert(&ctx->rbtree, &gvvn->sn.node);
+
+    ctx->data_size += ngx_align(sizeof(ngx_http_variable_value_t) + value->len,
+                                sizeof(void *));
+
+    return val;
+}
+
+
+static char *
+ngx_http_geo_add_proxy(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+    ngx_cidr_t *cidr)
+{
+    ngx_cidr_t  *c;
+
+    if (ctx->proxies == NULL) {
+        ctx->proxies = ngx_array_create(ctx->pool, 4, sizeof(ngx_cidr_t));
+        if (ctx->proxies == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    c = ngx_array_push(ctx->proxies);
+    if (c == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    *c = *cidr;
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_geo_cidr_value(ngx_conf_t *cf, ngx_str_t *net, ngx_cidr_t *cidr)
+{
+    ngx_int_t  rc;
+
+    if (ngx_strcmp(net->data, "255.255.255.255") == 0) {
+        cidr->family = AF_INET;
+        cidr->u.in.addr = 0xffffffff;
+        cidr->u.in.mask = 0xffffffff;
+
+        return NGX_OK;
+    }
+
+    rc = ngx_ptocidr(net, cidr);
+
+    if (rc == NGX_ERROR) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid network \"%V\"", net);
+        return NGX_ERROR;
+    }
+
+    if (rc == NGX_DONE) {
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                           "low address bits of %V are meaningless", net);
+    }
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_http_geo_include(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+    ngx_str_t *name)
+{
+    char       *rv;
+    ngx_str_t   file;
+
+    file.len = name->len + 4;
+    file.data = ngx_pnalloc(ctx->temp_pool, name->len + 5);
+    if (file.data == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_sprintf(file.data, "%V.bin%Z", name);
+
+    if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (ctx->ranges) {
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data);
+
+        switch (ngx_http_geo_include_binary_base(cf, ctx, &file)) {
+        case NGX_OK:
+            return NGX_CONF_OK;
+        case NGX_ERROR:
+            return NGX_CONF_ERROR;
+        default:
+            break;
+        }
+    }
+
+    file.len -= 4;
+    file.data[file.len] = '\0';
+
+    ctx->include_name = file;
+
+    if (ctx->outside_entries) {
+        ctx->allow_binary_include = 0;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data);
+
+    rv = ngx_conf_parse(cf, &file);
+
+    ctx->includes++;
+    ctx->outside_entries = 0;
+
+    return rv;
+}
+
+
+static ngx_int_t
+ngx_http_geo_include_binary_base(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+    ngx_str_t *name)
+{
+    u_char                     *base, ch;
+    time_t                      mtime;
+    size_t                      size, len;
+    ssize_t                     n;
+    uint32_t                    crc32;
+    ngx_err_t                   err;
+    ngx_int_t                   rc;
+    ngx_uint_t                  i;
+    ngx_file_t                  file;
+    ngx_file_info_t             fi;
+    ngx_http_geo_range_t       *range, **ranges;
+    ngx_http_geo_header_t      *header;
+    ngx_http_variable_value_t  *vv;
+
+    ngx_memzero(&file, sizeof(ngx_file_t));
+    file.name = *name;
+    file.log = cf->log;
+
+    file.fd = ngx_open_file(name->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
+
+    if (file.fd == NGX_INVALID_FILE) {
+        err = ngx_errno;
+        if (err != NGX_ENOENT) {
+            ngx_conf_log_error(NGX_LOG_CRIT, cf, err,
+                               ngx_open_file_n " \"%s\" failed", name->data);
+        }
+        return NGX_DECLINED;
+    }
+
+    if (ctx->outside_entries) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+            "binary geo range base \"%s\" cannot be mixed with usual entries",
+            name->data);
+        rc = NGX_ERROR;
+        goto done;
+    }
+
+    if (ctx->binary_include) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+            "second binary geo range base \"%s\" cannot be mixed with \"%s\"",
+            name->data, ctx->include_name.data);
+        rc = NGX_ERROR;
+        goto done;
+    }
+
+    if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) {
+        ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
+                           ngx_fd_info_n " \"%s\" failed", name->data);
+        goto failed;
+    }
+
+    size = (size_t) ngx_file_size(&fi);
+    mtime = ngx_file_mtime(&fi);
+
+    ch = name->data[name->len - 4];
+    name->data[name->len - 4] = '\0';
+
+    if (ngx_file_info(name->data, &fi) == NGX_FILE_ERROR) {
+        ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
+                           ngx_file_info_n " \"%s\" failed", name->data);
+        goto failed;
+    }
+
+    name->data[name->len - 4] = ch;
+
+    if (mtime < ngx_file_mtime(&fi)) {
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                           "stale binary geo range base \"%s\"", name->data);
+        goto failed;
+    }
+
+    base = ngx_palloc(ctx->pool, size);
+    if (base == NULL) {
+        goto failed;
+    }
+
+    n = ngx_read_file(&file, base, size, 0);
+
+    if (n == NGX_ERROR) {
+        ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
+                           ngx_read_file_n " \"%s\" failed", name->data);
+        goto failed;
+    }
+
+    if ((size_t) n != size) {
+        ngx_conf_log_error(NGX_LOG_CRIT, cf, 0,
+            ngx_read_file_n " \"%s\" returned only %z bytes instead of %z",
+            name->data, n, size);
+        goto failed;
+    }
+
+    header = (ngx_http_geo_header_t *) base;
+
+    if (size < 16 || ngx_memcmp(&ngx_http_geo_header, header, 12) != 0) {
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+             "incompatible binary geo range base \"%s\"", name->data);
+        goto failed;
+    }
+
+    ngx_crc32_init(crc32);
+
+    vv = (ngx_http_variable_value_t *) (base + sizeof(ngx_http_geo_header_t));
+
+    while (vv->data) {
+        len = ngx_align(sizeof(ngx_http_variable_value_t) + vv->len,
+                        sizeof(void *));
+        ngx_crc32_update(&crc32, (u_char *) vv, len);
+        vv->data += (size_t) base;
+        vv = (ngx_http_variable_value_t *) ((u_char *) vv + len);
+    }
+    ngx_crc32_update(&crc32, (u_char *) vv, sizeof(ngx_http_variable_value_t));
+    vv++;
+
+    ranges = (ngx_http_geo_range_t **) vv;
+
+    for (i = 0; i < 0x10000; i++) {
+        ngx_crc32_update(&crc32, (u_char *) &ranges[i], sizeof(void *));
+        if (ranges[i]) {
+            ranges[i] = (ngx_http_geo_range_t *)
+                            ((u_char *) ranges[i] + (size_t) base);
+        }
+    }
+
+    range = (ngx_http_geo_range_t *) &ranges[0x10000];
+
+    while ((u_char *) range < base + size) {
+        while (range->value) {
+            ngx_crc32_update(&crc32, (u_char *) range,
+                             sizeof(ngx_http_geo_range_t));
+            range->value = (ngx_http_variable_value_t *)
+                               ((u_char *) range->value + (size_t) base);
+            range++;
+        }
+        ngx_crc32_update(&crc32, (u_char *) range, sizeof(void *));
+        range = (ngx_http_geo_range_t *) ((u_char *) range + sizeof(void *));
+    }
+
+    ngx_crc32_final(crc32);
+
+    if (crc32 != header->crc32) {
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                  "CRC32 mismatch in binary geo range base \"%s\"", name->data);
+        goto failed;
+    }
+
+    ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0,
+                       "using binary geo range base \"%s\"", name->data);
+
+    ctx->include_name = *name;
+    ctx->binary_include = 1;
+    ctx->high.low = ranges;
+    rc = NGX_OK;
+
+    goto done;
+
+failed:
+
+    rc = NGX_DECLINED;
+
+done:
+
+    if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
+                      ngx_close_file_n " \"%s\" failed", name->data);
+    }
+
+    return rc;
+}
+
+
+static void
+ngx_http_geo_create_binary_base(ngx_http_geo_conf_ctx_t *ctx)
+{
+    u_char                              *p;
+    uint32_t                             hash;
+    ngx_str_t                            s;
+    ngx_uint_t                           i;
+    ngx_file_mapping_t                   fm;
+    ngx_http_geo_range_t                *r, *range, **ranges;
+    ngx_http_geo_header_t               *header;
+    ngx_http_geo_variable_value_node_t  *gvvn;
+
+    fm.name = ngx_pnalloc(ctx->temp_pool, ctx->include_name.len + 5);
+    if (fm.name == NULL) {
+        return;
+    }
+
+    ngx_sprintf(fm.name, "%V.bin%Z", &ctx->include_name);
+
+    fm.size = ctx->data_size;
+    fm.log = ctx->pool->log;
+
+    ngx_log_error(NGX_LOG_NOTICE, fm.log, 0,
+                  "creating binary geo range base \"%s\"", fm.name);
+
+    if (ngx_create_file_mapping(&fm) != NGX_OK) {
+        return;
+    }
+
+    p = ngx_cpymem(fm.addr, &ngx_http_geo_header,
+                   sizeof(ngx_http_geo_header_t));
+
+    p = ngx_http_geo_copy_values(fm.addr, p, ctx->rbtree.root,
+                                 ctx->rbtree.sentinel);
+
+    p += sizeof(ngx_http_variable_value_t);
+
+    ranges = (ngx_http_geo_range_t **) p;
+
+    p += 0x10000 * sizeof(ngx_http_geo_range_t *);
+
+    for (i = 0; i < 0x10000; i++) {
+        r = ctx->high.low[i];
+        if (r == NULL) {
+            continue;
+        }
+
+        range = (ngx_http_geo_range_t *) p;
+        ranges[i] = (ngx_http_geo_range_t *) (p - (u_char *) fm.addr);
+
+        do {
+            s.len = r->value->len;
+            s.data = r->value->data;
+            hash = ngx_crc32_long(s.data, s.len);
+            gvvn = (ngx_http_geo_variable_value_node_t *)
+                        ngx_str_rbtree_lookup(&ctx->rbtree, &s, hash);
+
+            range->value = (ngx_http_variable_value_t *) gvvn->offset;
+            range->start = r->start;
+            range->end = r->end;
+            range++;
+
+        } while ((++r)->value);
+
+        range->value = NULL;
+
+        p = (u_char *) range + sizeof(void *);
+    }
+
+    header = fm.addr;
+    header->crc32 = ngx_crc32_long((u_char *) fm.addr
+                                       + sizeof(ngx_http_geo_header_t),
+                                   fm.size - sizeof(ngx_http_geo_header_t));
+
+    ngx_close_file_mapping(&fm);
+}
+
+
+static u_char *
+ngx_http_geo_copy_values(u_char *base, u_char *p, ngx_rbtree_node_t *node,
+    ngx_rbtree_node_t *sentinel)
+{
+    ngx_http_variable_value_t           *vv;
+    ngx_http_geo_variable_value_node_t  *gvvn;
+
+    if (node == sentinel) {
+        return p;
+    }
+
+    gvvn = (ngx_http_geo_variable_value_node_t *) node;
+    gvvn->offset = p - base;
+
+    vv = (ngx_http_variable_value_t *) p;
+    *vv = *gvvn->value;
+    p += sizeof(ngx_http_variable_value_t);
+    vv->data = (u_char *) (p - base);
+
+    p = ngx_cpymem(p, gvvn->sn.str.data, gvvn->sn.str.len);
+
+    p = ngx_align_ptr(p, sizeof(void *));
+
+    p = ngx_http_geo_copy_values(base, p, node->left, sentinel);
+
+    return ngx_http_geo_copy_values(base, p, node->right, sentinel);
+}
diff --git a/nginx/src/http/modules/ngx_http_geoip_module.c b/nginx/src/http/modules/ngx_http_geoip_module.c
new file mode 100644 (file)
index 0000000..5ea4f5f
--- /dev/null
@@ -0,0 +1,925 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#include <GeoIP.h>
+#include <GeoIPCity.h>
+
+
+#define NGX_GEOIP_COUNTRY_CODE   0
+#define NGX_GEOIP_COUNTRY_CODE3  1
+#define NGX_GEOIP_COUNTRY_NAME   2
+
+
+typedef struct {
+    GeoIP        *country;
+    GeoIP        *org;
+    GeoIP        *city;
+    ngx_array_t  *proxies;    /* array of ngx_cidr_t */
+    ngx_flag_t    proxy_recursive;
+#if (NGX_HAVE_GEOIP_V6)
+    unsigned      country_v6:1;
+    unsigned      org_v6:1;
+    unsigned      city_v6:1;
+#endif
+} ngx_http_geoip_conf_t;
+
+
+typedef struct {
+    ngx_str_t    *name;
+    uintptr_t     data;
+} ngx_http_geoip_var_t;
+
+
+typedef const char *(*ngx_http_geoip_variable_handler_pt)(GeoIP *,
+    u_long addr);
+
+
+ngx_http_geoip_variable_handler_pt ngx_http_geoip_country_functions[] = {
+    GeoIP_country_code_by_ipnum,
+    GeoIP_country_code3_by_ipnum,
+    GeoIP_country_name_by_ipnum,
+};
+
+
+#if (NGX_HAVE_GEOIP_V6)
+
+typedef const char *(*ngx_http_geoip_variable_handler_v6_pt)(GeoIP *,
+    geoipv6_t addr);
+
+
+ngx_http_geoip_variable_handler_v6_pt ngx_http_geoip_country_v6_functions[] = {
+    GeoIP_country_code_by_ipnum_v6,
+    GeoIP_country_code3_by_ipnum_v6,
+    GeoIP_country_name_by_ipnum_v6,
+};
+
+#endif
+
+
+static ngx_int_t ngx_http_geoip_country_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_geoip_org_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_geoip_city_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_geoip_region_name_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_geoip_city_float_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_geoip_city_int_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static GeoIPRecord *ngx_http_geoip_get_city_record(ngx_http_request_t *r);
+
+static ngx_int_t ngx_http_geoip_add_variables(ngx_conf_t *cf);
+static void *ngx_http_geoip_create_conf(ngx_conf_t *cf);
+static char *ngx_http_geoip_init_conf(ngx_conf_t *cf, void *conf);
+static char *ngx_http_geoip_country(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_geoip_org(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_geoip_city(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_geoip_proxy(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static ngx_int_t ngx_http_geoip_cidr_value(ngx_conf_t *cf, ngx_str_t *net,
+    ngx_cidr_t *cidr);
+static void ngx_http_geoip_cleanup(void *data);
+
+
+static ngx_command_t  ngx_http_geoip_commands[] = {
+
+    { ngx_string("geoip_country"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE12,
+      ngx_http_geoip_country,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("geoip_org"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE12,
+      ngx_http_geoip_org,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("geoip_city"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE12,
+      ngx_http_geoip_city,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("geoip_proxy"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+      ngx_http_geoip_proxy,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("geoip_proxy_recursive"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      offsetof(ngx_http_geoip_conf_t, proxy_recursive),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_geoip_module_ctx = {
+    ngx_http_geoip_add_variables,          /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    ngx_http_geoip_create_conf,            /* create main configuration */
+    ngx_http_geoip_init_conf,              /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_geoip_module = {
+    NGX_MODULE_V1,
+    &ngx_http_geoip_module_ctx,            /* module context */
+    ngx_http_geoip_commands,               /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_variable_t  ngx_http_geoip_vars[] = {
+
+    { ngx_string("geoip_country_code"), NULL,
+      ngx_http_geoip_country_variable,
+      NGX_GEOIP_COUNTRY_CODE, 0, 0 },
+
+    { ngx_string("geoip_country_code3"), NULL,
+      ngx_http_geoip_country_variable,
+      NGX_GEOIP_COUNTRY_CODE3, 0, 0 },
+
+    { ngx_string("geoip_country_name"), NULL,
+      ngx_http_geoip_country_variable,
+      NGX_GEOIP_COUNTRY_NAME, 0, 0 },
+
+    { ngx_string("geoip_org"), NULL,
+      ngx_http_geoip_org_variable,
+      0, 0, 0 },
+
+    { ngx_string("geoip_city_continent_code"), NULL,
+      ngx_http_geoip_city_variable,
+      offsetof(GeoIPRecord, continent_code), 0, 0 },
+
+    { ngx_string("geoip_city_country_code"), NULL,
+      ngx_http_geoip_city_variable,
+      offsetof(GeoIPRecord, country_code), 0, 0 },
+
+    { ngx_string("geoip_city_country_code3"), NULL,
+      ngx_http_geoip_city_variable,
+      offsetof(GeoIPRecord, country_code3), 0, 0 },
+
+    { ngx_string("geoip_city_country_name"), NULL,
+      ngx_http_geoip_city_variable,
+      offsetof(GeoIPRecord, country_name), 0, 0 },
+
+    { ngx_string("geoip_region"), NULL,
+      ngx_http_geoip_city_variable,
+      offsetof(GeoIPRecord, region), 0, 0 },
+
+    { ngx_string("geoip_region_name"), NULL,
+      ngx_http_geoip_region_name_variable,
+      0, 0, 0 },
+
+    { ngx_string("geoip_city"), NULL,
+      ngx_http_geoip_city_variable,
+      offsetof(GeoIPRecord, city), 0, 0 },
+
+    { ngx_string("geoip_postal_code"), NULL,
+      ngx_http_geoip_city_variable,
+      offsetof(GeoIPRecord, postal_code), 0, 0 },
+
+    { ngx_string("geoip_latitude"), NULL,
+      ngx_http_geoip_city_float_variable,
+      offsetof(GeoIPRecord, latitude), 0, 0 },
+
+    { ngx_string("geoip_longitude"), NULL,
+      ngx_http_geoip_city_float_variable,
+      offsetof(GeoIPRecord, longitude), 0, 0 },
+
+    { ngx_string("geoip_dma_code"), NULL,
+      ngx_http_geoip_city_int_variable,
+      offsetof(GeoIPRecord, dma_code), 0, 0 },
+
+    { ngx_string("geoip_area_code"), NULL,
+      ngx_http_geoip_city_int_variable,
+      offsetof(GeoIPRecord, area_code), 0, 0 },
+
+      ngx_http_null_variable
+};
+
+
+static u_long
+ngx_http_geoip_addr(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf)
+{
+    ngx_addr_t           addr;
+    ngx_array_t         *xfwd;
+    struct sockaddr_in  *sin;
+
+    addr.sockaddr = r->connection->sockaddr;
+    addr.socklen = r->connection->socklen;
+    /* addr.name = r->connection->addr_text; */
+
+    xfwd = &r->headers_in.x_forwarded_for;
+
+    if (xfwd->nelts > 0 && gcf->proxies != NULL) {
+        (void) ngx_http_get_forwarded_addr(r, &addr, xfwd, NULL,
+                                           gcf->proxies, gcf->proxy_recursive);
+    }
+
+#if (NGX_HAVE_INET6)
+
+    if (addr.sockaddr->sa_family == AF_INET6) {
+        u_char           *p;
+        in_addr_t         inaddr;
+        struct in6_addr  *inaddr6;
+
+        inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr;
+
+        if (IN6_IS_ADDR_V4MAPPED(inaddr6)) {
+            p = inaddr6->s6_addr;
+
+            inaddr = p[12] << 24;
+            inaddr += p[13] << 16;
+            inaddr += p[14] << 8;
+            inaddr += p[15];
+
+            return inaddr;
+        }
+    }
+
+#endif
+
+    if (addr.sockaddr->sa_family != AF_INET) {
+        return INADDR_NONE;
+    }
+
+    sin = (struct sockaddr_in *) addr.sockaddr;
+    return ntohl(sin->sin_addr.s_addr);
+}
+
+
+#if (NGX_HAVE_GEOIP_V6)
+
+static geoipv6_t
+ngx_http_geoip_addr_v6(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf)
+{
+    ngx_addr_t            addr;
+    ngx_array_t          *xfwd;
+    in_addr_t             addr4;
+    struct in6_addr       addr6;
+    struct sockaddr_in   *sin;
+    struct sockaddr_in6  *sin6;
+
+    addr.sockaddr = r->connection->sockaddr;
+    addr.socklen = r->connection->socklen;
+    /* addr.name = r->connection->addr_text; */
+
+    xfwd = &r->headers_in.x_forwarded_for;
+
+    if (xfwd->nelts > 0 && gcf->proxies != NULL) {
+        (void) ngx_http_get_forwarded_addr(r, &addr, xfwd, NULL,
+                                           gcf->proxies, gcf->proxy_recursive);
+    }
+
+    switch (addr.sockaddr->sa_family) {
+
+    case AF_INET:
+        /* Produce IPv4-mapped IPv6 address. */
+        sin = (struct sockaddr_in *) addr.sockaddr;
+        addr4 = ntohl(sin->sin_addr.s_addr);
+
+        ngx_memzero(&addr6, sizeof(struct in6_addr));
+        addr6.s6_addr[10] = 0xff;
+        addr6.s6_addr[11] = 0xff;
+        addr6.s6_addr[12] = addr4 >> 24;
+        addr6.s6_addr[13] = addr4 >> 16;
+        addr6.s6_addr[14] = addr4 >> 8;
+        addr6.s6_addr[15] = addr4;
+        return addr6;
+
+    case AF_INET6:
+        sin6 = (struct sockaddr_in6 *) addr.sockaddr;
+        return sin6->sin6_addr;
+
+    default:
+        return in6addr_any;
+    }
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_geoip_country_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_http_geoip_variable_handler_pt     handler =
+        ngx_http_geoip_country_functions[data];
+#if (NGX_HAVE_GEOIP_V6)
+    ngx_http_geoip_variable_handler_v6_pt  handler_v6 =
+        ngx_http_geoip_country_v6_functions[data];
+#endif
+
+    const char             *val;
+    ngx_http_geoip_conf_t  *gcf;
+
+    gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module);
+
+    if (gcf->country == NULL) {
+        goto not_found;
+    }
+
+#if (NGX_HAVE_GEOIP_V6)
+    val = gcf->country_v6
+              ? handler_v6(gcf->country, ngx_http_geoip_addr_v6(r, gcf))
+              : handler(gcf->country, ngx_http_geoip_addr(r, gcf));
+#else
+    val = handler(gcf->country, ngx_http_geoip_addr(r, gcf));
+#endif
+
+    if (val == NULL) {
+        goto not_found;
+    }
+
+    v->len = ngx_strlen(val);
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = (u_char *) val;
+
+    return NGX_OK;
+
+not_found:
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_geoip_org_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    size_t                  len;
+    char                   *val;
+    ngx_http_geoip_conf_t  *gcf;
+
+    gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module);
+
+    if (gcf->org == NULL) {
+        goto not_found;
+    }
+
+#if (NGX_HAVE_GEOIP_V6)
+    val = gcf->org_v6
+              ? GeoIP_name_by_ipnum_v6(gcf->org,
+                                       ngx_http_geoip_addr_v6(r, gcf))
+              : GeoIP_name_by_ipnum(gcf->org,
+                                    ngx_http_geoip_addr(r, gcf));
+#else
+    val = GeoIP_name_by_ipnum(gcf->org, ngx_http_geoip_addr(r, gcf));
+#endif
+
+    if (val == NULL) {
+        goto not_found;
+    }
+
+    len = ngx_strlen(val);
+    v->data = ngx_pnalloc(r->pool, len);
+    if (v->data == NULL) {
+        ngx_free(val);
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(v->data, val, len);
+
+    v->len = len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    ngx_free(val);
+
+    return NGX_OK;
+
+not_found:
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_geoip_city_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    char         *val;
+    size_t        len;
+    GeoIPRecord  *gr;
+
+    gr = ngx_http_geoip_get_city_record(r);
+    if (gr == NULL) {
+        goto not_found;
+    }
+
+    val = *(char **) ((char *) gr + data);
+    if (val == NULL) {
+        goto no_value;
+    }
+
+    len = ngx_strlen(val);
+    v->data = ngx_pnalloc(r->pool, len);
+    if (v->data == NULL) {
+        GeoIPRecord_delete(gr);
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(v->data, val, len);
+
+    v->len = len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    GeoIPRecord_delete(gr);
+
+    return NGX_OK;
+
+no_value:
+
+    GeoIPRecord_delete(gr);
+
+not_found:
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_geoip_region_name_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    size_t        len;
+    const char   *val;
+    GeoIPRecord  *gr;
+
+    gr = ngx_http_geoip_get_city_record(r);
+    if (gr == NULL) {
+        goto not_found;
+    }
+
+    val = GeoIP_region_name_by_code(gr->country_code, gr->region);
+
+    GeoIPRecord_delete(gr);
+
+    if (val == NULL) {
+        goto not_found;
+    }
+
+    len = ngx_strlen(val);
+    v->data = ngx_pnalloc(r->pool, len);
+    if (v->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(v->data, val, len);
+
+    v->len = len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    return NGX_OK;
+
+not_found:
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_geoip_city_float_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    float         val;
+    GeoIPRecord  *gr;
+
+    gr = ngx_http_geoip_get_city_record(r);
+    if (gr == NULL) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    v->data = ngx_pnalloc(r->pool, NGX_INT64_LEN + 5);
+    if (v->data == NULL) {
+        GeoIPRecord_delete(gr);
+        return NGX_ERROR;
+    }
+
+    val = *(float *) ((char *) gr + data);
+
+    v->len = ngx_sprintf(v->data, "%.4f", val) - v->data;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    GeoIPRecord_delete(gr);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_geoip_city_int_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    int           val;
+    GeoIPRecord  *gr;
+
+    gr = ngx_http_geoip_get_city_record(r);
+    if (gr == NULL) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    v->data = ngx_pnalloc(r->pool, NGX_INT64_LEN);
+    if (v->data == NULL) {
+        GeoIPRecord_delete(gr);
+        return NGX_ERROR;
+    }
+
+    val = *(int *) ((char *) gr + data);
+
+    v->len = ngx_sprintf(v->data, "%d", val) - v->data;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    GeoIPRecord_delete(gr);
+
+    return NGX_OK;
+}
+
+
+static GeoIPRecord *
+ngx_http_geoip_get_city_record(ngx_http_request_t *r)
+{
+    ngx_http_geoip_conf_t  *gcf;
+
+    gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module);
+
+    if (gcf->city) {
+#if (NGX_HAVE_GEOIP_V6)
+        return gcf->city_v6
+                   ? GeoIP_record_by_ipnum_v6(gcf->city,
+                                              ngx_http_geoip_addr_v6(r, gcf))
+                   : GeoIP_record_by_ipnum(gcf->city,
+                                           ngx_http_geoip_addr(r, gcf));
+#else
+        return GeoIP_record_by_ipnum(gcf->city, ngx_http_geoip_addr(r, gcf));
+#endif
+    }
+
+    return NULL;
+}
+
+
+static ngx_int_t
+ngx_http_geoip_add_variables(ngx_conf_t *cf)
+{
+    ngx_http_variable_t  *var, *v;
+
+    for (v = ngx_http_geoip_vars; v->name.len; v++) {
+        var = ngx_http_add_variable(cf, &v->name, v->flags);
+        if (var == NULL) {
+            return NGX_ERROR;
+        }
+
+        var->get_handler = v->get_handler;
+        var->data = v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_geoip_create_conf(ngx_conf_t *cf)
+{
+    ngx_pool_cleanup_t     *cln;
+    ngx_http_geoip_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_geoip_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    conf->proxy_recursive = NGX_CONF_UNSET;
+
+    cln = ngx_pool_cleanup_add(cf->pool, 0);
+    if (cln == NULL) {
+        return NULL;
+    }
+
+    cln->handler = ngx_http_geoip_cleanup;
+    cln->data = conf;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_geoip_init_conf(ngx_conf_t *cf, void *conf)
+{
+    ngx_http_geoip_conf_t  *gcf = conf;
+
+    ngx_conf_init_value(gcf->proxy_recursive, 0);
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_geoip_country(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_geoip_conf_t  *gcf = conf;
+
+    ngx_str_t  *value;
+
+    if (gcf->country) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    gcf->country = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE);
+
+    if (gcf->country == NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "GeoIP_open(\"%V\") failed", &value[1]);
+
+        return NGX_CONF_ERROR;
+    }
+
+    if (cf->args->nelts == 3) {
+        if (ngx_strcmp(value[2].data, "utf8") == 0) {
+            GeoIP_set_charset(gcf->country, GEOIP_CHARSET_UTF8);
+
+        } else {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid parameter \"%V\"", &value[2]);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    switch (gcf->country->databaseType) {
+
+    case GEOIP_COUNTRY_EDITION:
+
+        return NGX_CONF_OK;
+
+#if (NGX_HAVE_GEOIP_V6)
+    case GEOIP_COUNTRY_EDITION_V6:
+
+        gcf->country_v6 = 1;
+        return NGX_CONF_OK;
+#endif
+
+    default:
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid GeoIP database \"%V\" type:%d",
+                           &value[1], gcf->country->databaseType);
+        return NGX_CONF_ERROR;
+    }
+}
+
+
+static char *
+ngx_http_geoip_org(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_geoip_conf_t  *gcf = conf;
+
+    ngx_str_t  *value;
+
+    if (gcf->org) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    gcf->org = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE);
+
+    if (gcf->org == NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "GeoIP_open(\"%V\") failed", &value[1]);
+
+        return NGX_CONF_ERROR;
+    }
+
+    if (cf->args->nelts == 3) {
+        if (ngx_strcmp(value[2].data, "utf8") == 0) {
+            GeoIP_set_charset(gcf->org, GEOIP_CHARSET_UTF8);
+
+        } else {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid parameter \"%V\"", &value[2]);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    switch (gcf->org->databaseType) {
+
+    case GEOIP_ISP_EDITION:
+    case GEOIP_ORG_EDITION:
+    case GEOIP_DOMAIN_EDITION:
+    case GEOIP_ASNUM_EDITION:
+
+        return NGX_CONF_OK;
+
+#if (NGX_HAVE_GEOIP_V6)
+    case GEOIP_ISP_EDITION_V6:
+    case GEOIP_ORG_EDITION_V6:
+    case GEOIP_DOMAIN_EDITION_V6:
+    case GEOIP_ASNUM_EDITION_V6:
+
+        gcf->org_v6 = 1;
+        return NGX_CONF_OK;
+#endif
+
+    default:
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid GeoIP database \"%V\" type:%d",
+                           &value[1], gcf->org->databaseType);
+        return NGX_CONF_ERROR;
+    }
+}
+
+
+static char *
+ngx_http_geoip_city(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_geoip_conf_t  *gcf = conf;
+
+    ngx_str_t  *value;
+
+    if (gcf->city) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    gcf->city = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE);
+
+    if (gcf->city == NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "GeoIP_open(\"%V\") failed", &value[1]);
+
+        return NGX_CONF_ERROR;
+    }
+
+    if (cf->args->nelts == 3) {
+        if (ngx_strcmp(value[2].data, "utf8") == 0) {
+            GeoIP_set_charset(gcf->city, GEOIP_CHARSET_UTF8);
+
+        } else {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid parameter \"%V\"", &value[2]);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    switch (gcf->city->databaseType) {
+
+    case GEOIP_CITY_EDITION_REV0:
+    case GEOIP_CITY_EDITION_REV1:
+
+        return NGX_CONF_OK;
+
+#if (NGX_HAVE_GEOIP_V6)
+    case GEOIP_CITY_EDITION_REV0_V6:
+    case GEOIP_CITY_EDITION_REV1_V6:
+
+        gcf->city_v6 = 1;
+        return NGX_CONF_OK;
+#endif
+
+    default:
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid GeoIP City database \"%V\" type:%d",
+                           &value[1], gcf->city->databaseType);
+        return NGX_CONF_ERROR;
+    }
+}
+
+
+static char *
+ngx_http_geoip_proxy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_geoip_conf_t  *gcf = conf;
+
+    ngx_str_t   *value;
+    ngx_cidr_t  cidr, *c;
+
+    value = cf->args->elts;
+
+    if (ngx_http_geoip_cidr_value(cf, &value[1], &cidr) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (gcf->proxies == NULL) {
+        gcf->proxies = ngx_array_create(cf->pool, 4, sizeof(ngx_cidr_t));
+        if (gcf->proxies == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    c = ngx_array_push(gcf->proxies);
+    if (c == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    *c = cidr;
+
+    return NGX_CONF_OK;
+}
+
+static ngx_int_t
+ngx_http_geoip_cidr_value(ngx_conf_t *cf, ngx_str_t *net, ngx_cidr_t *cidr)
+{
+    ngx_int_t  rc;
+
+    if (ngx_strcmp(net->data, "255.255.255.255") == 0) {
+        cidr->family = AF_INET;
+        cidr->u.in.addr = 0xffffffff;
+        cidr->u.in.mask = 0xffffffff;
+
+        return NGX_OK;
+    }
+
+    rc = ngx_ptocidr(net, cidr);
+
+    if (rc == NGX_ERROR) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid network \"%V\"", net);
+        return NGX_ERROR;
+    }
+
+    if (rc == NGX_DONE) {
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                           "low address bits of %V are meaningless", net);
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_geoip_cleanup(void *data)
+{
+    ngx_http_geoip_conf_t  *gcf = data;
+
+    if (gcf->country) {
+        GeoIP_delete(gcf->country);
+    }
+
+    if (gcf->org) {
+        GeoIP_delete(gcf->org);
+    }
+
+    if (gcf->city) {
+        GeoIP_delete(gcf->city);
+    }
+}
diff --git a/nginx/src/http/modules/ngx_http_grpc_module.c b/nginx/src/http/modules/ngx_http_grpc_module.c
new file mode 100644 (file)
index 0000000..62b33fd
--- /dev/null
@@ -0,0 +1,4707 @@
+
+/*
+ * Copyright (C) Maxim Dounin
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_array_t               *flushes;
+    ngx_array_t               *lengths;
+    ngx_array_t               *values;
+    ngx_hash_t                 hash;
+} ngx_http_grpc_headers_t;
+
+
+typedef struct {
+    ngx_http_upstream_conf_t   upstream;
+
+    ngx_http_grpc_headers_t    headers;
+    ngx_array_t               *headers_source;
+
+    ngx_str_t                  host;
+    ngx_uint_t                 host_set;
+
+#if (NGX_HTTP_SSL)
+    ngx_uint_t                 ssl;
+    ngx_uint_t                 ssl_protocols;
+    ngx_str_t                  ssl_ciphers;
+    ngx_uint_t                 ssl_verify_depth;
+    ngx_str_t                  ssl_trusted_certificate;
+    ngx_str_t                  ssl_crl;
+    ngx_str_t                  ssl_certificate;
+    ngx_str_t                  ssl_certificate_key;
+    ngx_array_t               *ssl_passwords;
+#endif
+} ngx_http_grpc_loc_conf_t;
+
+
+typedef enum {
+    ngx_http_grpc_st_start = 0,
+    ngx_http_grpc_st_length_2,
+    ngx_http_grpc_st_length_3,
+    ngx_http_grpc_st_type,
+    ngx_http_grpc_st_flags,
+    ngx_http_grpc_st_stream_id,
+    ngx_http_grpc_st_stream_id_2,
+    ngx_http_grpc_st_stream_id_3,
+    ngx_http_grpc_st_stream_id_4,
+    ngx_http_grpc_st_payload,
+    ngx_http_grpc_st_padding
+} ngx_http_grpc_state_e;
+
+
+typedef struct {
+    size_t                     init_window;
+    size_t                     send_window;
+    size_t                     recv_window;
+    ngx_uint_t                 last_stream_id;
+} ngx_http_grpc_conn_t;
+
+
+typedef struct {
+    ngx_http_grpc_state_e      state;
+    ngx_uint_t                 frame_state;
+    ngx_uint_t                 fragment_state;
+
+    ngx_chain_t               *in;
+    ngx_chain_t               *out;
+    ngx_chain_t               *free;
+    ngx_chain_t               *busy;
+
+    ngx_http_grpc_conn_t      *connection;
+
+    ngx_uint_t                 id;
+
+    ngx_uint_t                 pings;
+    ngx_uint_t                 settings;
+
+    ssize_t                    send_window;
+    size_t                     recv_window;
+
+    size_t                     rest;
+    ngx_uint_t                 stream_id;
+    u_char                     type;
+    u_char                     flags;
+    u_char                     padding;
+
+    ngx_uint_t                 error;
+    ngx_uint_t                 window_update;
+
+    ngx_uint_t                 setting_id;
+    ngx_uint_t                 setting_value;
+
+    u_char                     ping_data[8];
+
+    ngx_uint_t                 index;
+    ngx_str_t                  name;
+    ngx_str_t                  value;
+
+    u_char                    *field_end;
+    size_t                     field_length;
+    size_t                     field_rest;
+    u_char                     field_state;
+
+    unsigned                   literal:1;
+    unsigned                   field_huffman:1;
+
+    unsigned                   header_sent:1;
+    unsigned                   output_closed:1;
+    unsigned                   output_blocked:1;
+    unsigned                   parsing_headers:1;
+    unsigned                   end_stream:1;
+    unsigned                   done:1;
+    unsigned                   status:1;
+
+    ngx_http_request_t        *request;
+} ngx_http_grpc_ctx_t;
+
+
+typedef struct {
+    u_char                     length_0;
+    u_char                     length_1;
+    u_char                     length_2;
+    u_char                     type;
+    u_char                     flags;
+    u_char                     stream_id_0;
+    u_char                     stream_id_1;
+    u_char                     stream_id_2;
+    u_char                     stream_id_3;
+} ngx_http_grpc_frame_t;
+
+
+static ngx_int_t ngx_http_grpc_create_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_grpc_reinit_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_grpc_body_output_filter(void *data, ngx_chain_t *in);
+static ngx_int_t ngx_http_grpc_process_header(ngx_http_request_t *r);
+static ngx_int_t ngx_http_grpc_filter_init(void *data);
+static ngx_int_t ngx_http_grpc_filter(void *data, ssize_t bytes);
+
+static ngx_int_t ngx_http_grpc_parse_frame(ngx_http_request_t *r,
+    ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b);
+static ngx_int_t ngx_http_grpc_parse_header(ngx_http_request_t *r,
+    ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b);
+static ngx_int_t ngx_http_grpc_parse_fragment(ngx_http_request_t *r,
+    ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b);
+static ngx_int_t ngx_http_grpc_validate_header_name(ngx_http_request_t *r,
+    ngx_str_t *s);
+static ngx_int_t ngx_http_grpc_validate_header_value(ngx_http_request_t *r,
+    ngx_str_t *s);
+static ngx_int_t ngx_http_grpc_parse_rst_stream(ngx_http_request_t *r,
+    ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b);
+static ngx_int_t ngx_http_grpc_parse_goaway(ngx_http_request_t *r,
+    ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b);
+static ngx_int_t ngx_http_grpc_parse_window_update(ngx_http_request_t *r,
+    ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b);
+static ngx_int_t ngx_http_grpc_parse_settings(ngx_http_request_t *r,
+    ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b);
+static ngx_int_t ngx_http_grpc_parse_ping(ngx_http_request_t *r,
+    ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b);
+
+static ngx_int_t ngx_http_grpc_send_settings_ack(ngx_http_request_t *r,
+    ngx_http_grpc_ctx_t *ctx);
+static ngx_int_t ngx_http_grpc_send_ping_ack(ngx_http_request_t *r,
+    ngx_http_grpc_ctx_t *ctx);
+static ngx_int_t ngx_http_grpc_send_window_update(ngx_http_request_t *r,
+    ngx_http_grpc_ctx_t *ctx);
+
+static ngx_chain_t *ngx_http_grpc_get_buf(ngx_http_request_t *r,
+    ngx_http_grpc_ctx_t *ctx);
+static ngx_http_grpc_ctx_t *ngx_http_grpc_get_ctx(ngx_http_request_t *r);
+static ngx_int_t ngx_http_grpc_get_connection_data(ngx_http_request_t *r,
+    ngx_http_grpc_ctx_t *ctx, ngx_peer_connection_t *pc);
+static void ngx_http_grpc_cleanup(void *data);
+
+static void ngx_http_grpc_abort_request(ngx_http_request_t *r);
+static void ngx_http_grpc_finalize_request(ngx_http_request_t *r,
+    ngx_int_t rc);
+
+static ngx_int_t ngx_http_grpc_internal_trailers_variable(
+    ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data);
+
+static ngx_int_t ngx_http_grpc_add_variables(ngx_conf_t *cf);
+static void *ngx_http_grpc_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_grpc_merge_loc_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+static ngx_int_t ngx_http_grpc_init_headers(ngx_conf_t *cf,
+    ngx_http_grpc_loc_conf_t *conf, ngx_http_grpc_headers_t *headers,
+    ngx_keyval_t *default_headers);
+
+static char *ngx_http_grpc_pass(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+#if (NGX_HTTP_SSL)
+static char *ngx_http_grpc_ssl_password_file(ngx_conf_t *cf,
+    ngx_command_t *cmd, void *conf);
+static ngx_int_t ngx_http_grpc_set_ssl(ngx_conf_t *cf,
+    ngx_http_grpc_loc_conf_t *glcf);
+#endif
+
+
+static ngx_conf_bitmask_t  ngx_http_grpc_next_upstream_masks[] = {
+    { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR },
+    { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT },
+    { ngx_string("invalid_header"), NGX_HTTP_UPSTREAM_FT_INVALID_HEADER },
+    { ngx_string("non_idempotent"), NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT },
+    { ngx_string("http_500"), NGX_HTTP_UPSTREAM_FT_HTTP_500 },
+    { ngx_string("http_502"), NGX_HTTP_UPSTREAM_FT_HTTP_502 },
+    { ngx_string("http_503"), NGX_HTTP_UPSTREAM_FT_HTTP_503 },
+    { ngx_string("http_504"), NGX_HTTP_UPSTREAM_FT_HTTP_504 },
+    { ngx_string("http_403"), NGX_HTTP_UPSTREAM_FT_HTTP_403 },
+    { ngx_string("http_404"), NGX_HTTP_UPSTREAM_FT_HTTP_404 },
+    { ngx_string("http_429"), NGX_HTTP_UPSTREAM_FT_HTTP_429 },
+    { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF },
+    { ngx_null_string, 0 }
+};
+
+
+#if (NGX_HTTP_SSL)
+
+static ngx_conf_bitmask_t  ngx_http_grpc_ssl_protocols[] = {
+    { ngx_string("SSLv2"), NGX_SSL_SSLv2 },
+    { ngx_string("SSLv3"), NGX_SSL_SSLv3 },
+    { ngx_string("TLSv1"), NGX_SSL_TLSv1 },
+    { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 },
+    { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 },
+    { ngx_string("TLSv1.3"), NGX_SSL_TLSv1_3 },
+    { ngx_null_string, 0 }
+};
+
+#endif
+
+
+static ngx_command_t  ngx_http_grpc_commands[] = {
+
+    { ngx_string("grpc_pass"),
+      NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
+      ngx_http_grpc_pass,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("grpc_bind"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+      ngx_http_upstream_bind_set_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, upstream.local),
+      NULL },
+
+    { ngx_string("grpc_connect_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, upstream.connect_timeout),
+      NULL },
+
+    { ngx_string("grpc_send_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, upstream.send_timeout),
+      NULL },
+
+    { ngx_string("grpc_intercept_errors"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, upstream.intercept_errors),
+      NULL },
+
+    { ngx_string("grpc_buffer_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, upstream.buffer_size),
+      NULL },
+
+    { ngx_string("grpc_read_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, upstream.read_timeout),
+      NULL },
+
+    { ngx_string("grpc_next_upstream"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, upstream.next_upstream),
+      &ngx_http_grpc_next_upstream_masks },
+
+    { ngx_string("grpc_next_upstream_tries"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, upstream.next_upstream_tries),
+      NULL },
+
+    { ngx_string("grpc_next_upstream_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, upstream.next_upstream_timeout),
+      NULL },
+
+    { ngx_string("grpc_set_header"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+      ngx_conf_set_keyval_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, headers_source),
+      NULL },
+
+    { ngx_string("grpc_pass_header"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, upstream.pass_headers),
+      NULL },
+
+    { ngx_string("grpc_hide_header"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, upstream.hide_headers),
+      NULL },
+
+    { ngx_string("grpc_ignore_headers"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, upstream.ignore_headers),
+      &ngx_http_upstream_ignore_headers_masks },
+
+#if (NGX_HTTP_SSL)
+
+    { ngx_string("grpc_ssl_session_reuse"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, upstream.ssl_session_reuse),
+      NULL },
+
+    { ngx_string("grpc_ssl_protocols"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, ssl_protocols),
+      &ngx_http_grpc_ssl_protocols },
+
+    { ngx_string("grpc_ssl_ciphers"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, ssl_ciphers),
+      NULL },
+
+    { ngx_string("grpc_ssl_name"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_set_complex_value_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, upstream.ssl_name),
+      NULL },
+
+    { ngx_string("grpc_ssl_server_name"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, upstream.ssl_server_name),
+      NULL },
+
+    { ngx_string("grpc_ssl_verify"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, upstream.ssl_verify),
+      NULL },
+
+    { ngx_string("grpc_ssl_verify_depth"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, ssl_verify_depth),
+      NULL },
+
+    { ngx_string("grpc_ssl_trusted_certificate"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, ssl_trusted_certificate),
+      NULL },
+
+    { ngx_string("grpc_ssl_crl"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, ssl_crl),
+      NULL },
+
+    { ngx_string("grpc_ssl_certificate"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, ssl_certificate),
+      NULL },
+
+    { ngx_string("grpc_ssl_certificate_key"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_grpc_loc_conf_t, ssl_certificate_key),
+      NULL },
+
+    { ngx_string("grpc_ssl_password_file"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_grpc_ssl_password_file,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+#endif
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_grpc_module_ctx = {
+    ngx_http_grpc_add_variables,           /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_grpc_create_loc_conf,         /* create location configuration */
+    ngx_http_grpc_merge_loc_conf           /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_grpc_module = {
+    NGX_MODULE_V1,
+    &ngx_http_grpc_module_ctx,             /* module context */
+    ngx_http_grpc_commands,                /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static u_char  ngx_http_grpc_connection_start[] =
+    "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"         /* connection preface */
+
+    "\x00\x00\x12\x04\x00\x00\x00\x00\x00"     /* settings frame */
+    "\x00\x01\x00\x00\x00\x00"                 /* header table size */
+    "\x00\x02\x00\x00\x00\x00"                 /* disable push */
+    "\x00\x04\x7f\xff\xff\xff"                 /* initial window */
+
+    "\x00\x00\x04\x08\x00\x00\x00\x00\x00"     /* window update frame */
+    "\x7f\xff\x00\x00";
+
+
+static ngx_keyval_t  ngx_http_grpc_headers[] = {
+    { ngx_string("Content-Length"), ngx_string("$content_length") },
+    { ngx_string("TE"), ngx_string("$grpc_internal_trailers") },
+    { ngx_string("Host"), ngx_string("") },
+    { ngx_string("Connection"), ngx_string("") },
+    { ngx_string("Transfer-Encoding"), ngx_string("") },
+    { ngx_string("Keep-Alive"), ngx_string("") },
+    { ngx_string("Expect"), ngx_string("") },
+    { ngx_string("Upgrade"), ngx_string("") },
+    { ngx_null_string, ngx_null_string }
+};
+
+
+static ngx_str_t  ngx_http_grpc_hide_headers[] = {
+    ngx_string("Date"),
+    ngx_string("Server"),
+    ngx_string("X-Accel-Expires"),
+    ngx_string("X-Accel-Redirect"),
+    ngx_string("X-Accel-Limit-Rate"),
+    ngx_string("X-Accel-Buffering"),
+    ngx_string("X-Accel-Charset"),
+    ngx_null_string
+};
+
+
+static ngx_http_variable_t  ngx_http_grpc_vars[] = {
+
+    { ngx_string("grpc_internal_trailers"), NULL,
+      ngx_http_grpc_internal_trailers_variable, 0,
+      NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
+
+      ngx_http_null_variable
+};
+
+
+static ngx_int_t
+ngx_http_grpc_handler(ngx_http_request_t *r)
+{
+    ngx_int_t                  rc;
+    ngx_http_upstream_t       *u;
+    ngx_http_grpc_ctx_t       *ctx;
+    ngx_http_grpc_loc_conf_t  *glcf;
+
+    if (ngx_http_upstream_create(r) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    glcf = ngx_http_get_module_loc_conf(r, ngx_http_grpc_module);
+
+    u = r->upstream;
+
+#if (NGX_HTTP_SSL)
+    u->ssl = (glcf->upstream.ssl != NULL);
+
+    if (u->ssl) {
+        ngx_str_set(&u->schema, "grpcs://");
+
+    } else {
+        ngx_str_set(&u->schema, "grpc://");
+    }
+#else
+    ngx_str_set(&u->schema, "grpc://");
+#endif
+
+    u->output.tag = (ngx_buf_tag_t) &ngx_http_grpc_module;
+
+    u->conf = &glcf->upstream;
+
+    u->create_request = ngx_http_grpc_create_request;
+    u->reinit_request = ngx_http_grpc_reinit_request;
+    u->process_header = ngx_http_grpc_process_header;
+    u->abort_request = ngx_http_grpc_abort_request;
+    u->finalize_request = ngx_http_grpc_finalize_request;
+
+    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_grpc_ctx_t));
+    if (ctx == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    ctx->request = r;
+
+    ngx_http_set_ctx(r, ctx, ngx_http_grpc_module);
+
+    u->input_filter_init = ngx_http_grpc_filter_init;
+    u->input_filter = ngx_http_grpc_filter;
+    u->input_filter_ctx = ctx;
+
+    r->request_body_no_buffering = 1;
+
+    rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);
+
+    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+        return rc;
+    }
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_grpc_create_request(ngx_http_request_t *r)
+{
+    u_char                       *p, *tmp, *key_tmp, *val_tmp, *headers_frame;
+    size_t                        len, tmp_len, key_len, val_len, uri_len;
+    uintptr_t                     escape;
+    ngx_buf_t                    *b;
+    ngx_uint_t                    i, next;
+    ngx_chain_t                  *cl, *body;
+    ngx_list_part_t              *part;
+    ngx_table_elt_t              *header;
+    ngx_http_upstream_t          *u;
+    ngx_http_grpc_frame_t        *f;
+    ngx_http_script_code_pt       code;
+    ngx_http_grpc_loc_conf_t     *glcf;
+    ngx_http_script_engine_t      e, le;
+    ngx_http_script_len_code_pt   lcode;
+
+    u = r->upstream;
+
+    glcf = ngx_http_get_module_loc_conf(r, ngx_http_grpc_module);
+
+    len = sizeof(ngx_http_grpc_connection_start) - 1
+          + sizeof(ngx_http_grpc_frame_t);             /* headers frame */
+
+    /* :method header */
+
+    if (r->method == NGX_HTTP_GET || r->method == NGX_HTTP_POST) {
+        len += 1;
+        tmp_len = 0;
+
+    } else {
+        len += 1 + NGX_HTTP_V2_INT_OCTETS + r->method_name.len;
+        tmp_len = r->method_name.len;
+    }
+
+    /* :scheme header */
+
+    len += 1;
+
+    /* :path header */
+
+    if (r->valid_unparsed_uri) {
+        escape = 0;
+        uri_len = r->unparsed_uri.len;
+
+    } else {
+        escape = 2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len,
+                                    NGX_ESCAPE_URI);
+        uri_len = r->uri.len + escape + sizeof("?") - 1 + r->args.len;
+    }
+
+    len += 1 + NGX_HTTP_V2_INT_OCTETS + uri_len;
+
+    if (tmp_len < uri_len) {
+        tmp_len = uri_len;
+    }
+
+    /* :authority header */
+
+    if (!glcf->host_set) {
+        len += 1 + NGX_HTTP_V2_INT_OCTETS + glcf->host.len;
+
+        if (tmp_len < glcf->host.len) {
+            tmp_len = glcf->host.len;
+        }
+    }
+
+    /* other headers */
+
+    ngx_http_script_flush_no_cacheable_variables(r, glcf->headers.flushes);
+    ngx_memzero(&le, sizeof(ngx_http_script_engine_t));
+
+    le.ip = glcf->headers.lengths->elts;
+    le.request = r;
+    le.flushed = 1;
+
+    while (*(uintptr_t *) le.ip) {
+
+        lcode = *(ngx_http_script_len_code_pt *) le.ip;
+        key_len = lcode(&le);
+
+        for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) {
+            lcode = *(ngx_http_script_len_code_pt *) le.ip;
+        }
+        le.ip += sizeof(uintptr_t);
+
+        if (val_len == 0) {
+            continue;
+        }
+
+        len += 1 + NGX_HTTP_V2_INT_OCTETS + key_len
+                 + NGX_HTTP_V2_INT_OCTETS + val_len;
+
+        if (tmp_len < key_len) {
+            tmp_len = key_len;
+        }
+
+        if (tmp_len < val_len) {
+            tmp_len = val_len;
+        }
+    }
+
+    if (glcf->upstream.pass_request_headers) {
+        part = &r->headers_in.headers.part;
+        header = part->elts;
+
+        for (i = 0; /* void */; i++) {
+
+            if (i >= part->nelts) {
+                if (part->next == NULL) {
+                    break;
+                }
+
+                part = part->next;
+                header = part->elts;
+                i = 0;
+            }
+
+            if (ngx_hash_find(&glcf->headers.hash, header[i].hash,
+                              header[i].lowcase_key, header[i].key.len))
+            {
+                continue;
+            }
+
+            len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len
+                     + NGX_HTTP_V2_INT_OCTETS + header[i].value.len;
+
+            if (tmp_len < header[i].key.len) {
+                tmp_len = header[i].key.len;
+            }
+
+            if (tmp_len < header[i].value.len) {
+                tmp_len = header[i].value.len;
+            }
+        }
+    }
+
+    /* continuation frames */
+
+    len += sizeof(ngx_http_grpc_frame_t)
+           * (len / NGX_HTTP_V2_DEFAULT_FRAME_SIZE);
+
+
+    b = ngx_create_temp_buf(r->pool, len);
+    if (b == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl = ngx_alloc_chain_link(r->pool);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl->buf = b;
+    cl->next = NULL;
+
+    tmp = ngx_palloc(r->pool, tmp_len * 3);
+    if (tmp == NULL) {
+        return NGX_ERROR;
+    }
+
+    key_tmp = tmp + tmp_len;
+    val_tmp = tmp + 2 * tmp_len;
+
+    /* connection preface */
+
+    b->last = ngx_copy(b->last, ngx_http_grpc_connection_start,
+                       sizeof(ngx_http_grpc_connection_start) - 1);
+
+    /* headers frame */
+
+    headers_frame = b->last;
+
+    f = (ngx_http_grpc_frame_t *) b->last;
+    b->last += sizeof(ngx_http_grpc_frame_t);
+
+    f->length_0 = 0;
+    f->length_1 = 0;
+    f->length_2 = 0;
+    f->type = NGX_HTTP_V2_HEADERS_FRAME;
+    f->flags = 0;
+    f->stream_id_0 = 0;
+    f->stream_id_1 = 0;
+    f->stream_id_2 = 0;
+    f->stream_id_3 = 1;
+
+    if (r->method == NGX_HTTP_GET) {
+        *b->last++ = ngx_http_v2_indexed(NGX_HTTP_V2_METHOD_GET_INDEX);
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc header: \":method: GET\"");
+
+    } else if (r->method == NGX_HTTP_POST) {
+        *b->last++ = ngx_http_v2_indexed(NGX_HTTP_V2_METHOD_POST_INDEX);
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc header: \":method: POST\"");
+
+    } else {
+        *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_METHOD_INDEX);
+        b->last = ngx_http_v2_write_value(b->last, r->method_name.data,
+                                          r->method_name.len, tmp);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc header: \":method: %V\"", &r->method_name);
+    }
+
+#if (NGX_HTTP_SSL)
+    if (glcf->ssl) {
+        *b->last++ = ngx_http_v2_indexed(NGX_HTTP_V2_SCHEME_HTTPS_INDEX);
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc header: \":scheme: https\"");
+    } else
+#endif
+    {
+        *b->last++ = ngx_http_v2_indexed(NGX_HTTP_V2_SCHEME_HTTP_INDEX);
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc header: \":scheme: http\"");
+    }
+
+    if (r->valid_unparsed_uri) {
+
+        if (r->unparsed_uri.len == 1 && r->unparsed_uri.data[0] == '/') {
+            *b->last++ = ngx_http_v2_indexed(NGX_HTTP_V2_PATH_ROOT_INDEX);
+
+        } else {
+            *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX);
+            b->last = ngx_http_v2_write_value(b->last, r->unparsed_uri.data,
+                                              r->unparsed_uri.len, tmp);
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc header: \":path: %V\"", &r->unparsed_uri);
+
+    } else if (escape || r->args.len > 0) {
+        p = val_tmp;
+
+        if (escape) {
+            p = (u_char *) ngx_escape_uri(p, r->uri.data, r->uri.len,
+                                          NGX_ESCAPE_URI);
+
+        } else {
+            p = ngx_copy(p, r->uri.data, r->uri.len);
+        }
+
+        if (r->args.len > 0) {
+            *p++ = '?';
+            p = ngx_copy(p, r->args.data, r->args.len);
+        }
+
+        *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX);
+        b->last = ngx_http_v2_write_value(b->last, val_tmp, p - val_tmp, tmp);
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc header: \":path: %*s\"", p - val_tmp, val_tmp);
+
+    } else {
+        *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX);
+        b->last = ngx_http_v2_write_value(b->last, r->uri.data,
+                                          r->uri.len, tmp);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc header: \":path: %V\"", &r->uri);
+    }
+
+    if (!glcf->host_set) {
+        *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_AUTHORITY_INDEX);
+        b->last = ngx_http_v2_write_value(b->last, glcf->host.data,
+                                          glcf->host.len, tmp);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc header: \":authority: %V\"", &glcf->host);
+    }
+
+    ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
+
+    e.ip = glcf->headers.values->elts;
+    e.request = r;
+    e.flushed = 1;
+
+    le.ip = glcf->headers.lengths->elts;
+
+    while (*(uintptr_t *) le.ip) {
+
+        lcode = *(ngx_http_script_len_code_pt *) le.ip;
+        key_len = lcode(&le);
+
+        for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) {
+            lcode = *(ngx_http_script_len_code_pt *) le.ip;
+        }
+        le.ip += sizeof(uintptr_t);
+
+        if (val_len == 0) {
+            e.skip = 1;
+
+            while (*(uintptr_t *) e.ip) {
+                code = *(ngx_http_script_code_pt *) e.ip;
+                code((ngx_http_script_engine_t *) &e);
+            }
+            e.ip += sizeof(uintptr_t);
+
+            e.skip = 0;
+
+            continue;
+        }
+
+        *b->last++ = 0;
+
+        e.pos = key_tmp;
+
+        code = *(ngx_http_script_code_pt *) e.ip;
+        code((ngx_http_script_engine_t *) &e);
+
+        b->last = ngx_http_v2_write_name(b->last, key_tmp, key_len, tmp);
+
+        e.pos = val_tmp;
+
+        while (*(uintptr_t *) e.ip) {
+            code = *(ngx_http_script_code_pt *) e.ip;
+            code((ngx_http_script_engine_t *) &e);
+        }
+        e.ip += sizeof(uintptr_t);
+
+        b->last = ngx_http_v2_write_value(b->last, val_tmp, val_len, tmp);
+
+#if (NGX_DEBUG)
+        if (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP) {
+            ngx_strlow(key_tmp, key_tmp, key_len);
+
+            ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "grpc header: \"%*s: %*s\"",
+                           key_len, key_tmp, val_len, val_tmp);
+        }
+#endif
+    }
+
+    if (glcf->upstream.pass_request_headers) {
+        part = &r->headers_in.headers.part;
+        header = part->elts;
+
+        for (i = 0; /* void */; i++) {
+
+            if (i >= part->nelts) {
+                if (part->next == NULL) {
+                    break;
+                }
+
+                part = part->next;
+                header = part->elts;
+                i = 0;
+            }
+
+            if (ngx_hash_find(&glcf->headers.hash, header[i].hash,
+                              header[i].lowcase_key, header[i].key.len))
+            {
+                continue;
+            }
+
+            *b->last++ = 0;
+
+            b->last = ngx_http_v2_write_name(b->last, header[i].key.data,
+                                             header[i].key.len, tmp);
+
+            b->last = ngx_http_v2_write_value(b->last, header[i].value.data,
+                                              header[i].value.len, tmp);
+
+#if (NGX_DEBUG)
+            if (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP) {
+                ngx_strlow(tmp, header[i].key.data, header[i].key.len);
+
+                ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                               "grpc header: \"%*s: %V\"",
+                               header[i].key.len, tmp, &header[i].value);
+            }
+#endif
+        }
+    }
+
+    /* update headers frame length */
+
+    len = b->last - headers_frame - sizeof(ngx_http_grpc_frame_t);
+
+    if (len > NGX_HTTP_V2_DEFAULT_FRAME_SIZE) {
+        len = NGX_HTTP_V2_DEFAULT_FRAME_SIZE;
+        next = 1;
+
+    } else {
+        next = 0;
+    }
+
+    f = (ngx_http_grpc_frame_t *) headers_frame;
+
+    f->length_0 = (u_char) ((len >> 16) & 0xff);
+    f->length_1 = (u_char) ((len >> 8) & 0xff);
+    f->length_2 = (u_char) (len & 0xff);
+
+    /* create additional continuation frames */
+
+    p = headers_frame;
+
+    while (next) {
+        p += sizeof(ngx_http_grpc_frame_t) + NGX_HTTP_V2_DEFAULT_FRAME_SIZE;
+        len = b->last - p;
+
+        ngx_memmove(p + sizeof(ngx_http_grpc_frame_t), p, len);
+        b->last += sizeof(ngx_http_grpc_frame_t);
+
+        if (len > NGX_HTTP_V2_DEFAULT_FRAME_SIZE) {
+            len = NGX_HTTP_V2_DEFAULT_FRAME_SIZE;
+            next = 1;
+
+        } else {
+            next = 0;
+        }
+
+        f = (ngx_http_grpc_frame_t *) p;
+
+        f->length_0 = (u_char) ((len >> 16) & 0xff);
+        f->length_1 = (u_char) ((len >> 8) & 0xff);
+        f->length_2 = (u_char) (len & 0xff);
+        f->type = NGX_HTTP_V2_CONTINUATION_FRAME;
+        f->flags = 0;
+        f->stream_id_0 = 0;
+        f->stream_id_1 = 0;
+        f->stream_id_2 = 0;
+        f->stream_id_3 = 1;
+    }
+
+    f->flags |= NGX_HTTP_V2_END_HEADERS_FLAG;
+
+#if (NGX_DEBUG)
+    if (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP) {
+        u_char  buf[512];
+        size_t  n, m;
+
+        n = ngx_min(b->last - b->pos, 256);
+        m = ngx_hex_dump(buf, b->pos, n) - buf;
+
+        ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc header: %*s%s, len: %uz",
+                       m, buf, b->last - b->pos > 256 ? "..." : "",
+                       b->last - b->pos);
+    }
+#endif
+
+    if (r->request_body_no_buffering) {
+
+        u->request_bufs = cl;
+
+    } else {
+
+        body = u->request_bufs;
+        u->request_bufs = cl;
+
+        if (body == NULL) {
+            f = (ngx_http_grpc_frame_t *) headers_frame;
+            f->flags |= NGX_HTTP_V2_END_STREAM_FLAG;
+        }
+
+        while (body) {
+            b = ngx_alloc_buf(r->pool);
+            if (b == NULL) {
+                return NGX_ERROR;
+            }
+
+            ngx_memcpy(b, body->buf, sizeof(ngx_buf_t));
+
+            cl->next = ngx_alloc_chain_link(r->pool);
+            if (cl->next == NULL) {
+                return NGX_ERROR;
+            }
+
+            cl = cl->next;
+            cl->buf = b;
+
+            body = body->next;
+        }
+
+        b->last_buf = 1;
+    }
+
+    u->output.output_filter = ngx_http_grpc_body_output_filter;
+    u->output.filter_ctx = r;
+
+    b->flush = 1;
+    cl->next = NULL;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_grpc_reinit_request(ngx_http_request_t *r)
+{
+    ngx_http_grpc_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_grpc_module);
+
+    if (ctx == NULL) {
+        return NGX_OK;
+    }
+
+    ctx->state = 0;
+    ctx->header_sent = 0;
+    ctx->output_closed = 0;
+    ctx->output_blocked = 0;
+    ctx->parsing_headers = 0;
+    ctx->end_stream = 0;
+    ctx->done = 0;
+    ctx->status = 0;
+    ctx->connection = NULL;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_grpc_body_output_filter(void *data, ngx_chain_t *in)
+{
+    ngx_http_request_t  *r = data;
+
+    off_t                   file_pos;
+    u_char                 *p, *pos, *start;
+    size_t                  len, limit;
+    ngx_buf_t              *b;
+    ngx_int_t               rc;
+    ngx_uint_t              next, last;
+    ngx_chain_t            *cl, *out, **ll;
+    ngx_http_upstream_t    *u;
+    ngx_http_grpc_ctx_t    *ctx;
+    ngx_http_grpc_frame_t  *f;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "grpc output filter");
+
+    ctx = ngx_http_grpc_get_ctx(r);
+
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (in) {
+        if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    out = NULL;
+    ll = &out;
+
+    if (!ctx->header_sent) {
+        /* first buffer contains headers */
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc output header");
+
+        ctx->header_sent = 1;
+
+        if (ctx->id != 1) {
+            /*
+             * keepalive connection: skip connection preface,
+             * update stream identifiers
+             */
+
+            b = ctx->in->buf;
+            b->pos += sizeof(ngx_http_grpc_connection_start) - 1;
+
+            p = b->pos;
+
+            while (p < b->last) {
+                f = (ngx_http_grpc_frame_t *) p;
+                p += sizeof(ngx_http_grpc_frame_t);
+
+                f->stream_id_0 = (u_char) ((ctx->id >> 24) & 0xff);
+                f->stream_id_1 = (u_char) ((ctx->id >> 16) & 0xff);
+                f->stream_id_2 = (u_char) ((ctx->id >> 8) & 0xff);
+                f->stream_id_3 = (u_char) (ctx->id & 0xff);
+
+                p += (f->length_0 << 16) + (f->length_1 << 8) + f->length_2;
+            }
+        }
+
+        if (ctx->in->buf->last_buf) {
+            ctx->output_closed = 1;
+        }
+
+        *ll = ctx->in;
+        ll = &ctx->in->next;
+
+        ctx->in = ctx->in->next;
+    }
+
+    if (ctx->out) {
+        /* queued control frames */
+
+        *ll = ctx->out;
+
+        for (cl = ctx->out, ll = &cl->next; cl; cl = cl->next) {
+            ll = &cl->next;
+        }
+
+        ctx->out = NULL;
+    }
+
+    f = NULL;
+    last = 0;
+
+    limit = ngx_max(0, ctx->send_window);
+
+    if (limit > ctx->connection->send_window) {
+        limit = ctx->connection->send_window;
+    }
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "grpc output limit: %uz w:%z:%uz",
+                   limit, ctx->send_window, ctx->connection->send_window);
+
+#if (NGX_SUPPRESS_WARN)
+    file_pos = 0;
+    pos = NULL;
+    cl = NULL;
+#endif
+
+    in = ctx->in;
+
+    while (in && limit > 0) {
+
+        ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0,
+                       "grpc output in  l:%d f:%d %p, pos %p, size: %z "
+                       "file: %O, size: %O",
+                       in->buf->last_buf,
+                       in->buf->in_file,
+                       in->buf->start, in->buf->pos,
+                       in->buf->last - in->buf->pos,
+                       in->buf->file_pos,
+                       in->buf->file_last - in->buf->file_pos);
+
+        if (ngx_buf_special(in->buf)) {
+            goto next;
+        }
+
+        if (in->buf->in_file) {
+            file_pos = in->buf->file_pos;
+
+        } else {
+            pos = in->buf->pos;
+        }
+
+        next = 0;
+
+        do {
+
+            cl = ngx_http_grpc_get_buf(r, ctx);
+            if (cl == NULL) {
+                return NGX_ERROR;
+            }
+
+            b = cl->buf;
+
+            f = (ngx_http_grpc_frame_t *) b->last;
+            b->last += sizeof(ngx_http_grpc_frame_t);
+
+            *ll = cl;
+            ll = &cl->next;
+
+            cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+            if (cl == NULL) {
+                return NGX_ERROR;
+            }
+
+            b = cl->buf;
+            start = b->start;
+
+            ngx_memcpy(b, in->buf, sizeof(ngx_buf_t));
+
+            /*
+             * restore b->start to preserve memory allocated in the buffer,
+             * to reuse it later for headers and control frames
+             */
+
+            b->start = start;
+
+            if (in->buf->in_file) {
+                b->file_pos = file_pos;
+                file_pos += ngx_min(NGX_HTTP_V2_DEFAULT_FRAME_SIZE, limit);
+
+                if (file_pos >= in->buf->file_last) {
+                    file_pos = in->buf->file_last;
+                    next = 1;
+                }
+
+                b->file_last = file_pos;
+                len = (ngx_uint_t) (file_pos - b->file_pos);
+
+            } else {
+                b->pos = pos;
+                pos += ngx_min(NGX_HTTP_V2_DEFAULT_FRAME_SIZE, limit);
+
+                if (pos >= in->buf->last) {
+                    pos = in->buf->last;
+                    next = 1;
+                }
+
+                b->last = pos;
+                len = (ngx_uint_t) (pos - b->pos);
+            }
+
+            b->tag = (ngx_buf_tag_t) &ngx_http_grpc_body_output_filter;
+            b->shadow = in->buf;
+            b->last_shadow = next;
+
+            b->last_buf = 0;
+            b->last_in_chain = 0;
+
+            *ll = cl;
+            ll = &cl->next;
+
+            f->length_0 = (u_char) ((len >> 16) & 0xff);
+            f->length_1 = (u_char) ((len >> 8) & 0xff);
+            f->length_2 = (u_char) (len & 0xff);
+            f->type = NGX_HTTP_V2_DATA_FRAME;
+            f->flags = 0;
+            f->stream_id_0 = (u_char) ((ctx->id >> 24) & 0xff);
+            f->stream_id_1 = (u_char) ((ctx->id >> 16) & 0xff);
+            f->stream_id_2 = (u_char) ((ctx->id >> 8) & 0xff);
+            f->stream_id_3 = (u_char) (ctx->id & 0xff);
+
+            limit -= len;
+            ctx->send_window -= len;
+            ctx->connection->send_window -= len;
+
+        } while (!next && limit > 0);
+
+        if (!next) {
+            /*
+             * if the buffer wasn't fully sent due to flow control limits,
+             * preserve position for future use
+             */
+
+            if (in->buf->in_file) {
+                in->buf->file_pos = file_pos;
+
+            } else {
+                in->buf->pos = pos;
+            }
+
+            break;
+        }
+
+    next:
+
+        if (in->buf->last_buf) {
+            last = 1;
+        }
+
+        in = in->next;
+    }
+
+    ctx->in = in;
+
+    if (last) {
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc output last");
+
+        ctx->output_closed = 1;
+
+        if (f) {
+            f->flags |= NGX_HTTP_V2_END_STREAM_FLAG;
+
+        } else {
+            cl = ngx_http_grpc_get_buf(r, ctx);
+            if (cl == NULL) {
+                return NGX_ERROR;
+            }
+
+            b = cl->buf;
+
+            f = (ngx_http_grpc_frame_t *) b->last;
+            b->last += sizeof(ngx_http_grpc_frame_t);
+
+            f->length_0 = 0;
+            f->length_1 = 0;
+            f->length_2 = 0;
+            f->type = NGX_HTTP_V2_DATA_FRAME;
+            f->flags = NGX_HTTP_V2_END_STREAM_FLAG;
+            f->stream_id_0 = (u_char) ((ctx->id >> 24) & 0xff);
+            f->stream_id_1 = (u_char) ((ctx->id >> 16) & 0xff);
+            f->stream_id_2 = (u_char) ((ctx->id >> 8) & 0xff);
+            f->stream_id_3 = (u_char) (ctx->id & 0xff);
+
+            *ll = cl;
+            ll = &cl->next;
+        }
+
+        cl->buf->last_buf = 1;
+    }
+
+    *ll = NULL;
+
+#if (NGX_DEBUG)
+
+    for (cl = out; cl; cl = cl->next) {
+        ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0,
+                       "grpc output out l:%d f:%d %p, pos %p, size: %z "
+                       "file: %O, size: %O",
+                       cl->buf->last_buf,
+                       cl->buf->in_file,
+                       cl->buf->start, cl->buf->pos,
+                       cl->buf->last - cl->buf->pos,
+                       cl->buf->file_pos,
+                       cl->buf->file_last - cl->buf->file_pos);
+    }
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "grpc output limit: %uz w:%z:%uz",
+                   limit, ctx->send_window, ctx->connection->send_window);
+
+#endif
+
+    rc = ngx_chain_writer(&r->upstream->writer, out);
+
+    ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out,
+                            (ngx_buf_tag_t) &ngx_http_grpc_body_output_filter);
+
+    for (cl = ctx->free; cl; cl = cl->next) {
+
+        /* mark original buffers as sent */
+
+        if (cl->buf->shadow) {
+            if (cl->buf->last_shadow) {
+                b = cl->buf->shadow;
+                b->pos = b->last;
+            }
+
+            cl->buf->shadow = NULL;
+        }
+    }
+
+    if (rc == NGX_OK && ctx->in) {
+        rc = NGX_AGAIN;
+    }
+
+    if (rc == NGX_AGAIN) {
+        ctx->output_blocked = 1;
+
+    } else {
+        ctx->output_blocked = 0;
+    }
+
+    if (ctx->done) {
+
+        /*
+         * We have already got the response and were sending some additional
+         * control frames.  Even if there is still something unsent, stop
+         * here anyway.
+         */
+
+        u = r->upstream;
+        u->length = 0;
+
+        if (ctx->in == NULL
+            && ctx->out == NULL
+            && ctx->output_closed
+            && !ctx->output_blocked
+            && ctx->state == ngx_http_grpc_st_start)
+        {
+            u->keepalive = 1;
+        }
+
+        ngx_post_event(u->peer.connection->read, &ngx_posted_events);
+    }
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_http_grpc_process_header(ngx_http_request_t *r)
+{
+    ngx_str_t                      *status_line;
+    ngx_int_t                       rc, status;
+    ngx_buf_t                      *b;
+    ngx_table_elt_t                *h;
+    ngx_http_upstream_t            *u;
+    ngx_http_grpc_ctx_t            *ctx;
+    ngx_http_upstream_header_t     *hh;
+    ngx_http_upstream_main_conf_t  *umcf;
+
+    u = r->upstream;
+    b = &u->buffer;
+
+#if (NGX_DEBUG)
+    if (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP) {
+        u_char  buf[512];
+        size_t  n, m;
+
+        n = ngx_min(b->last - b->pos, 256);
+        m = ngx_hex_dump(buf, b->pos, n) - buf;
+
+        ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc response: %*s%s, len: %uz",
+                       m, buf, b->last - b->pos > 256 ? "..." : "",
+                       b->last - b->pos);
+    }
+#endif
+
+    ctx = ngx_http_grpc_get_ctx(r);
+
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);
+
+    for ( ;; ) {
+
+        if (ctx->state < ngx_http_grpc_st_payload) {
+
+            rc = ngx_http_grpc_parse_frame(r, ctx, b);
+
+            if (rc == NGX_AGAIN) {
+
+                /*
+                 * there can be a lot of window update frames,
+                 * so we reset buffer if it is empty and we haven't
+                 * started parsing headers yet
+                 */
+
+                if (!ctx->parsing_headers) {
+                    b->pos = b->start;
+                    b->last = b->pos;
+                }
+
+                return NGX_AGAIN;
+            }
+
+            if (rc == NGX_ERROR) {
+                return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+            }
+
+            /*
+             * RFC 7540 says that implementations MUST discard frames
+             * that have unknown or unsupported types.  However, extension
+             * frames that appear in the middle of a header block are
+             * not permitted.  Also, for obvious reasons CONTINUATION frames
+             * cannot appear before headers, and DATA frames are not expected
+             * to appear before all headers are parsed.
+             */
+
+            if (ctx->type == NGX_HTTP_V2_DATA_FRAME
+                || (ctx->type == NGX_HTTP_V2_CONTINUATION_FRAME
+                    && !ctx->parsing_headers)
+                || (ctx->type != NGX_HTTP_V2_CONTINUATION_FRAME
+                    && ctx->parsing_headers))
+            {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent unexpected http2 frame: %d",
+                              ctx->type);
+                return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+            }
+
+            if (ctx->stream_id && ctx->stream_id != ctx->id) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent frame for unknown stream %ui",
+                              ctx->stream_id);
+                return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+            }
+        }
+
+        /* frame payload */
+
+        if (ctx->type == NGX_HTTP_V2_RST_STREAM_FRAME) {
+
+            rc = ngx_http_grpc_parse_rst_stream(r, ctx, b);
+
+            if (rc == NGX_AGAIN) {
+                return NGX_AGAIN;
+            }
+
+            if (rc == NGX_ERROR) {
+                return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+            }
+
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "upstream rejected request with error %ui",
+                          ctx->error);
+
+            return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+        }
+
+        if (ctx->type == NGX_HTTP_V2_GOAWAY_FRAME) {
+
+            rc = ngx_http_grpc_parse_goaway(r, ctx, b);
+
+            if (rc == NGX_AGAIN) {
+                return NGX_AGAIN;
+            }
+
+            if (rc == NGX_ERROR) {
+                return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+            }
+
+            /*
+             * If stream_id is lower than one we use, our
+             * request won't be processed and needs to be retried.
+             * If stream_id is greater or equal to the one we use,
+             * we can continue normally (except we can't use this
+             * connection for additional requests).  If there is
+             * a real error, the connection will be closed.
+             */
+
+            if (ctx->stream_id < ctx->id) {
+
+                /* TODO: we can retry non-idempotent requests */
+
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent goaway with error %ui",
+                              ctx->error);
+
+                return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+            }
+
+            continue;
+        }
+
+        if (ctx->type == NGX_HTTP_V2_WINDOW_UPDATE_FRAME) {
+
+            rc = ngx_http_grpc_parse_window_update(r, ctx, b);
+
+            if (rc == NGX_AGAIN) {
+                return NGX_AGAIN;
+            }
+
+            if (rc == NGX_ERROR) {
+                return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+            }
+
+            if (ctx->in) {
+                ngx_post_event(u->peer.connection->write, &ngx_posted_events);
+            }
+
+            continue;
+        }
+
+        if (ctx->type == NGX_HTTP_V2_SETTINGS_FRAME) {
+
+            rc = ngx_http_grpc_parse_settings(r, ctx, b);
+
+            if (rc == NGX_AGAIN) {
+                return NGX_AGAIN;
+            }
+
+            if (rc == NGX_ERROR) {
+                return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+            }
+
+            if (ctx->in) {
+                ngx_post_event(u->peer.connection->write, &ngx_posted_events);
+            }
+
+            continue;
+        }
+
+        if (ctx->type == NGX_HTTP_V2_PING_FRAME) {
+
+            rc = ngx_http_grpc_parse_ping(r, ctx, b);
+
+            if (rc == NGX_AGAIN) {
+                return NGX_AGAIN;
+            }
+
+            if (rc == NGX_ERROR) {
+                return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+            }
+
+            ngx_post_event(u->peer.connection->write, &ngx_posted_events);
+            continue;
+        }
+
+        if (ctx->type == NGX_HTTP_V2_PUSH_PROMISE_FRAME) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "upstream sent unexpected push promise frame");
+            return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+        }
+
+        if (ctx->type != NGX_HTTP_V2_HEADERS_FRAME
+            && ctx->type != NGX_HTTP_V2_CONTINUATION_FRAME)
+        {
+            /* priority, unknown frames */
+
+            if (b->last - b->pos < (ssize_t) ctx->rest) {
+                ctx->rest -= b->last - b->pos;
+                b->pos = b->last;
+                return NGX_AGAIN;
+            }
+
+            b->pos += ctx->rest;
+            ctx->rest = 0;
+            ctx->state = ngx_http_grpc_st_start;
+
+            continue;
+        }
+
+        /* headers */
+
+        for ( ;; ) {
+
+            rc = ngx_http_grpc_parse_header(r, ctx, b);
+
+            if (rc == NGX_AGAIN) {
+                break;
+            }
+
+            if (rc == NGX_OK) {
+
+                /* a header line has been parsed successfully */
+
+                ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                               "grpc header: \"%V: %V\"",
+                               &ctx->name, &ctx->value);
+
+                if (ctx->name.len && ctx->name.data[0] == ':') {
+
+                    if (ctx->name.len != sizeof(":status") - 1
+                        || ngx_strncmp(ctx->name.data, ":status",
+                                       sizeof(":status") - 1)
+                           != 0)
+                    {
+                        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                      "upstream sent invalid header \"%V: %V\"",
+                                      &ctx->name, &ctx->value);
+                        return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+                    }
+
+                    if (ctx->status) {
+                        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                      "upstream sent duplicate :status header");
+                        return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+                    }
+
+                    status_line = &ctx->value;
+
+                    if (status_line->len != 3) {
+                        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                      "upstream sent invalid :status \"%V\"",
+                                      status_line);
+                        return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+                    }
+
+                    status = ngx_atoi(status_line->data, 3);
+
+                    if (status == NGX_ERROR) {
+                        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                      "upstream sent invalid :status \"%V\"",
+                                      status_line);
+                        return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+                    }
+
+                    if (status < NGX_HTTP_OK) {
+                        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                      "upstream sent unexpected :status \"%V\"",
+                                      status_line);
+                        return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+                    }
+
+                    u->headers_in.status_n = status;
+
+                    if (u->state && u->state->status == 0) {
+                        u->state->status = status;
+                    }
+
+                    ctx->status = 1;
+
+                    continue;
+
+                } else if (!ctx->status) {
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                  "upstream sent no :status header");
+                    return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+                }
+
+                h = ngx_list_push(&u->headers_in.headers);
+                if (h == NULL) {
+                    return NGX_ERROR;
+                }
+
+                h->key = ctx->name;
+                h->value = ctx->value;
+                h->lowcase_key = h->key.data;
+                h->hash = ngx_hash_key(h->key.data, h->key.len);
+
+                hh = ngx_hash_find(&umcf->headers_in_hash, h->hash,
+                                   h->lowcase_key, h->key.len);
+
+                if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
+                    return NGX_ERROR;
+                }
+
+                continue;
+            }
+
+            if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
+
+                /* a whole header has been parsed successfully */
+
+                ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                               "grpc header done");
+
+                if (ctx->end_stream) {
+                    u->headers_in.content_length_n = 0;
+
+                    if (ctx->in == NULL
+                        && ctx->out == NULL
+                        && ctx->output_closed
+                        && !ctx->output_blocked
+                        && b->last == b->pos)
+                    {
+                        u->keepalive = 1;
+                    }
+                }
+
+                return NGX_OK;
+            }
+
+            /* there was error while a header line parsing */
+
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "upstream sent invalid header");
+
+            return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+        }
+
+        /* rc == NGX_AGAIN */
+
+        if (ctx->rest == 0) {
+            ctx->state = ngx_http_grpc_st_start;
+            continue;
+        }
+
+        return NGX_AGAIN;
+    }
+}
+
+
+static ngx_int_t
+ngx_http_grpc_filter_init(void *data)
+{
+    ngx_http_grpc_ctx_t  *ctx = data;
+
+    ngx_http_request_t   *r;
+    ngx_http_upstream_t  *u;
+
+    r = ctx->request;
+    u = r->upstream;
+
+    u->length = 1;
+
+    if (ctx->end_stream) {
+        u->length = 0;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_grpc_filter(void *data, ssize_t bytes)
+{
+    ngx_http_grpc_ctx_t  *ctx = data;
+
+    ngx_int_t             rc;
+    ngx_buf_t            *b, *buf;
+    ngx_chain_t          *cl, **ll;
+    ngx_table_elt_t      *h;
+    ngx_http_request_t   *r;
+    ngx_http_upstream_t  *u;
+
+    r = ctx->request;
+    u = r->upstream;
+    b = &u->buffer;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "grpc filter bytes:%z", bytes);
+
+    b->pos = b->last;
+    b->last += bytes;
+
+    for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) {
+        ll = &cl->next;
+    }
+
+    for ( ;; ) {
+
+        if (ctx->state < ngx_http_grpc_st_payload) {
+
+            rc = ngx_http_grpc_parse_frame(r, ctx, b);
+
+            if (rc == NGX_AGAIN) {
+
+                if (ctx->done) {
+
+                    /*
+                     * We have finished parsing the response and the
+                     * remaining control frames.  If there are unsent
+                     * control frames, post a write event to send them.
+                     */
+
+                    if (ctx->out) {
+                        ngx_post_event(u->peer.connection->write,
+                                       &ngx_posted_events);
+                        return NGX_AGAIN;
+                    }
+
+                    u->length = 0;
+
+                    if (ctx->in == NULL
+                        && ctx->output_closed
+                        && !ctx->output_blocked
+                        && ctx->state == ngx_http_grpc_st_start)
+                    {
+                        u->keepalive = 1;
+                    }
+
+                    break;
+                }
+
+                return NGX_AGAIN;
+            }
+
+            if (rc == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            if ((ctx->type == NGX_HTTP_V2_CONTINUATION_FRAME
+                 && !ctx->parsing_headers)
+                || (ctx->type != NGX_HTTP_V2_CONTINUATION_FRAME
+                    && ctx->parsing_headers))
+            {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent unexpected http2 frame: %d",
+                              ctx->type);
+                return NGX_ERROR;
+            }
+
+            if (ctx->type == NGX_HTTP_V2_DATA_FRAME) {
+
+                if (ctx->stream_id != ctx->id) {
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                  "upstream sent data frame "
+                                  "for unknown stream %ui",
+                                  ctx->stream_id);
+                    return NGX_ERROR;
+                }
+
+                if (ctx->rest > ctx->recv_window) {
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                  "upstream violated stream flow control, "
+                                  "received %uz data frame with window %uz",
+                                  ctx->rest, ctx->recv_window);
+                    return NGX_ERROR;
+                }
+
+                if (ctx->rest > ctx->connection->recv_window) {
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                  "upstream violated connection flow control, "
+                                  "received %uz data frame with window %uz",
+                                  ctx->rest, ctx->connection->recv_window);
+                    return NGX_ERROR;
+                }
+
+                ctx->recv_window -= ctx->rest;
+                ctx->connection->recv_window -= ctx->rest;
+
+                if (ctx->connection->recv_window < NGX_HTTP_V2_MAX_WINDOW / 4
+                    || ctx->recv_window < NGX_HTTP_V2_MAX_WINDOW / 4)
+                {
+                    if (ngx_http_grpc_send_window_update(r, ctx) != NGX_OK) {
+                        return NGX_ERROR;
+                    }
+
+                    ngx_post_event(u->peer.connection->write,
+                                   &ngx_posted_events);
+                }
+            }
+
+            if (ctx->stream_id && ctx->stream_id != ctx->id) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent frame for unknown stream %ui",
+                              ctx->stream_id);
+                return NGX_ERROR;
+            }
+
+            if (ctx->stream_id && ctx->done) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent frame for closed stream %ui",
+                              ctx->stream_id);
+                return NGX_ERROR;
+            }
+
+            ctx->padding = 0;
+        }
+
+        if (ctx->state == ngx_http_grpc_st_padding) {
+
+            if (b->last - b->pos < (ssize_t) ctx->rest) {
+                ctx->rest -= b->last - b->pos;
+                b->pos = b->last;
+                return NGX_AGAIN;
+            }
+
+            b->pos += ctx->rest;
+            ctx->rest = 0;
+            ctx->state = ngx_http_grpc_st_start;
+
+            if (ctx->flags & NGX_HTTP_V2_END_STREAM_FLAG) {
+                ctx->done = 1;
+            }
+
+            continue;
+        }
+
+        /* frame payload */
+
+        if (ctx->type == NGX_HTTP_V2_RST_STREAM_FRAME) {
+
+            rc = ngx_http_grpc_parse_rst_stream(r, ctx, b);
+
+            if (rc == NGX_AGAIN) {
+                return NGX_AGAIN;
+            }
+
+            if (rc == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "upstream rejected request with error %ui",
+                          ctx->error);
+
+            return NGX_ERROR;
+        }
+
+        if (ctx->type == NGX_HTTP_V2_GOAWAY_FRAME) {
+
+            rc = ngx_http_grpc_parse_goaway(r, ctx, b);
+
+            if (rc == NGX_AGAIN) {
+                return NGX_AGAIN;
+            }
+
+            if (rc == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            /*
+             * If stream_id is lower than one we use, our
+             * request won't be processed and needs to be retried.
+             * If stream_id is greater or equal to the one we use,
+             * we can continue normally (except we can't use this
+             * connection for additional requests).  If there is
+             * a real error, the connection will be closed.
+             */
+
+            if (ctx->stream_id < ctx->id) {
+
+                /* TODO: we can retry non-idempotent requests */
+
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent goaway with error %ui",
+                              ctx->error);
+
+                return NGX_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ctx->type == NGX_HTTP_V2_WINDOW_UPDATE_FRAME) {
+
+            rc = ngx_http_grpc_parse_window_update(r, ctx, b);
+
+            if (rc == NGX_AGAIN) {
+                return NGX_AGAIN;
+            }
+
+            if (rc == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            if (ctx->in) {
+                ngx_post_event(u->peer.connection->write, &ngx_posted_events);
+            }
+
+            continue;
+        }
+
+        if (ctx->type == NGX_HTTP_V2_SETTINGS_FRAME) {
+
+            rc = ngx_http_grpc_parse_settings(r, ctx, b);
+
+            if (rc == NGX_AGAIN) {
+                return NGX_AGAIN;
+            }
+
+            if (rc == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            if (ctx->in) {
+                ngx_post_event(u->peer.connection->write, &ngx_posted_events);
+            }
+
+            continue;
+        }
+
+        if (ctx->type == NGX_HTTP_V2_PING_FRAME) {
+
+            rc = ngx_http_grpc_parse_ping(r, ctx, b);
+
+            if (rc == NGX_AGAIN) {
+                return NGX_AGAIN;
+            }
+
+            if (rc == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            ngx_post_event(u->peer.connection->write, &ngx_posted_events);
+            continue;
+        }
+
+        if (ctx->type == NGX_HTTP_V2_PUSH_PROMISE_FRAME) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "upstream sent unexpected push promise frame");
+            return NGX_ERROR;
+        }
+
+        if (ctx->type == NGX_HTTP_V2_HEADERS_FRAME
+            || ctx->type == NGX_HTTP_V2_CONTINUATION_FRAME)
+        {
+            for ( ;; ) {
+
+                rc = ngx_http_grpc_parse_header(r, ctx, b);
+
+                if (rc == NGX_AGAIN) {
+                    break;
+                }
+
+                if (rc == NGX_OK) {
+
+                    /* a header line has been parsed successfully */
+
+                    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                                   "grpc trailer: \"%V: %V\"",
+                                   &ctx->name, &ctx->value);
+
+                    if (ctx->name.len && ctx->name.data[0] == ':') {
+                        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                      "upstream sent invalid "
+                                      "trailer \"%V: %V\"",
+                                      &ctx->name, &ctx->value);
+                        return NGX_ERROR;
+                    }
+
+                    h = ngx_list_push(&u->headers_in.trailers);
+                    if (h == NULL) {
+                        return NGX_ERROR;
+                    }
+
+                    h->key = ctx->name;
+                    h->value = ctx->value;
+                    h->lowcase_key = h->key.data;
+                    h->hash = ngx_hash_key(h->key.data, h->key.len);
+
+                    continue;
+                }
+
+                if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
+
+                    /* a whole header has been parsed successfully */
+
+                    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                                   "grpc trailer done");
+
+                    if (ctx->end_stream) {
+                        ctx->done = 1;
+                        break;
+                    }
+
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                  "upstream sent trailer without "
+                                  "end stream flag");
+                    return NGX_ERROR;
+                }
+
+                /* there was error while a header line parsing */
+
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent invalid trailer");
+
+                return NGX_ERROR;
+            }
+
+            if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
+                continue;
+            }
+
+            /* rc == NGX_AGAIN */
+
+            if (ctx->rest == 0) {
+                ctx->state = ngx_http_grpc_st_start;
+                continue;
+            }
+
+            return NGX_AGAIN;
+        }
+
+        if (ctx->type != NGX_HTTP_V2_DATA_FRAME) {
+
+            /* priority, unknown frames */
+
+            if (b->last - b->pos < (ssize_t) ctx->rest) {
+                ctx->rest -= b->last - b->pos;
+                b->pos = b->last;
+                return NGX_AGAIN;
+            }
+
+            b->pos += ctx->rest;
+            ctx->rest = 0;
+            ctx->state = ngx_http_grpc_st_start;
+
+            continue;
+        }
+
+        /*
+         * data frame:
+         *
+         * +---------------+
+         * |Pad Length? (8)|
+         * +---------------+-----------------------------------------------+
+         * |                            Data (*)                         ...
+         * +---------------------------------------------------------------+
+         * |                           Padding (*)                       ...
+         * +---------------------------------------------------------------+
+         */
+
+        if (ctx->flags & NGX_HTTP_V2_PADDED_FLAG) {
+
+            if (ctx->rest == 0) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent too short http2 frame");
+                return NGX_ERROR;
+            }
+
+            if (b->pos == b->last) {
+                return NGX_AGAIN;
+            }
+
+            ctx->flags &= ~NGX_HTTP_V2_PADDED_FLAG;
+            ctx->padding = *b->pos++;
+            ctx->rest -= 1;
+
+            if (ctx->padding > ctx->rest) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent http2 frame with too long "
+                              "padding: %d in frame %uz",
+                              ctx->padding, ctx->rest);
+                return NGX_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ctx->rest == ctx->padding) {
+            goto done;
+        }
+
+        if (b->pos == b->last) {
+            return NGX_AGAIN;
+        }
+
+        cl = ngx_chain_get_free_buf(r->pool, &u->free_bufs);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+        *ll = cl;
+        ll = &cl->next;
+
+        buf = cl->buf;
+
+        buf->flush = 1;
+        buf->memory = 1;
+
+        buf->pos = b->pos;
+        buf->tag = u->output.tag;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc output buf %p", buf->pos);
+
+        if (b->last - b->pos < (ssize_t) ctx->rest - ctx->padding) {
+
+            ctx->rest -= b->last - b->pos;
+            b->pos = b->last;
+            buf->last = b->pos;
+
+            return NGX_AGAIN;
+        }
+
+        b->pos += ctx->rest - ctx->padding;
+        buf->last = b->pos;
+        ctx->rest = ctx->padding;
+
+    done:
+
+        if (ctx->padding) {
+            ctx->state = ngx_http_grpc_st_padding;
+            continue;
+        }
+
+        ctx->state = ngx_http_grpc_st_start;
+
+        if (ctx->flags & NGX_HTTP_V2_END_STREAM_FLAG) {
+            ctx->done = 1;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_grpc_parse_frame(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx,
+    ngx_buf_t *b)
+{
+    u_char                 ch, *p;
+    ngx_http_grpc_state_e  state;
+
+    state = ctx->state;
+
+    for (p = b->pos; p < b->last; p++) {
+        ch = *p;
+
+#if 0
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc frame byte: %02Xd, s:%d", ch, state);
+#endif
+
+        switch (state) {
+
+        case ngx_http_grpc_st_start:
+            ctx->rest = ch << 16;
+            state = ngx_http_grpc_st_length_2;
+            break;
+
+        case ngx_http_grpc_st_length_2:
+            ctx->rest |= ch << 8;
+            state = ngx_http_grpc_st_length_3;
+            break;
+
+        case ngx_http_grpc_st_length_3:
+            ctx->rest |= ch;
+
+            if (ctx->rest > NGX_HTTP_V2_DEFAULT_FRAME_SIZE) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent too large http2 frame: %uz",
+                              ctx->rest);
+                return NGX_ERROR;
+            }
+
+            state = ngx_http_grpc_st_type;
+            break;
+
+        case ngx_http_grpc_st_type:
+            ctx->type = ch;
+            state = ngx_http_grpc_st_flags;
+            break;
+
+        case ngx_http_grpc_st_flags:
+            ctx->flags = ch;
+            state = ngx_http_grpc_st_stream_id;
+            break;
+
+        case ngx_http_grpc_st_stream_id:
+            ctx->stream_id = (ch & 0x7f) << 24;
+            state = ngx_http_grpc_st_stream_id_2;
+            break;
+
+        case ngx_http_grpc_st_stream_id_2:
+            ctx->stream_id |= ch << 16;
+            state = ngx_http_grpc_st_stream_id_3;
+            break;
+
+        case ngx_http_grpc_st_stream_id_3:
+            ctx->stream_id |= ch << 8;
+            state = ngx_http_grpc_st_stream_id_4;
+            break;
+
+        case ngx_http_grpc_st_stream_id_4:
+            ctx->stream_id |= ch;
+
+            ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "grpc frame: %d, len: %uz, f:%d, i:%ui",
+                           ctx->type, ctx->rest, ctx->flags, ctx->stream_id);
+
+            b->pos = p + 1;
+
+            ctx->state = ngx_http_grpc_st_payload;
+            ctx->frame_state = 0;
+
+            return NGX_OK;
+
+        /* suppress warning */
+        case ngx_http_grpc_st_payload:
+        case ngx_http_grpc_st_padding:
+            break;
+        }
+    }
+
+    b->pos = p;
+    ctx->state = state;
+
+    return NGX_AGAIN;
+}
+
+
+static ngx_int_t
+ngx_http_grpc_parse_header(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx,
+    ngx_buf_t *b)
+{
+    u_char     ch, *p, *last;
+    size_t     min;
+    ngx_int_t  rc;
+    enum {
+        sw_start = 0,
+        sw_padding_length,
+        sw_dependency,
+        sw_dependency_2,
+        sw_dependency_3,
+        sw_dependency_4,
+        sw_weight,
+        sw_fragment,
+        sw_padding
+    } state;
+
+    state = ctx->frame_state;
+
+    if (state == sw_start) {
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc parse header: start");
+
+        if (ctx->type == NGX_HTTP_V2_HEADERS_FRAME) {
+            ctx->parsing_headers = 1;
+            ctx->fragment_state = 0;
+
+            min = (ctx->flags & NGX_HTTP_V2_PADDED_FLAG ? 1 : 0)
+                  + (ctx->flags & NGX_HTTP_V2_PRIORITY_FLAG ? 5 : 0);
+
+            if (ctx->rest < min) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent headers frame "
+                              "with invalid length: %uz",
+                              ctx->rest);
+                return NGX_ERROR;
+            }
+
+            if (ctx->flags & NGX_HTTP_V2_END_STREAM_FLAG) {
+                ctx->end_stream = 1;
+            }
+
+            if (ctx->flags & NGX_HTTP_V2_PADDED_FLAG) {
+                state = sw_padding_length;
+
+            } else if (ctx->flags & NGX_HTTP_V2_PRIORITY_FLAG) {
+                state = sw_dependency;
+
+            } else {
+                state = sw_fragment;
+            }
+
+        } else if (ctx->type == NGX_HTTP_V2_CONTINUATION_FRAME) {
+            state = sw_fragment;
+        }
+
+        ctx->padding = 0;
+        ctx->frame_state = state;
+    }
+
+    if (state < sw_fragment) {
+
+        if (b->last - b->pos < (ssize_t) ctx->rest) {
+            last = b->last;
+
+        } else {
+            last = b->pos + ctx->rest;
+        }
+
+        for (p = b->pos; p < last; p++) {
+            ch = *p;
+
+#if 0
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "grpc header byte: %02Xd s:%d", ch, state);
+#endif
+
+            /*
+             * headers frame:
+             *
+             * +---------------+
+             * |Pad Length? (8)|
+             * +-+-------------+----------------------------------------------+
+             * |E|                 Stream Dependency? (31)                    |
+             * +-+-------------+----------------------------------------------+
+             * |  Weight? (8)  |
+             * +-+-------------+----------------------------------------------+
+             * |                   Header Block Fragment (*)                ...
+             * +--------------------------------------------------------------+
+             * |                           Padding (*)                      ...
+             * +--------------------------------------------------------------+
+             */
+
+            switch (state) {
+
+            case sw_padding_length:
+
+                ctx->padding = ch;
+
+                if (ctx->flags & NGX_HTTP_V2_PRIORITY_FLAG) {
+                    state = sw_dependency;
+                    break;
+                }
+
+                goto fragment;
+
+            case sw_dependency:
+                state = sw_dependency_2;
+                break;
+
+            case sw_dependency_2:
+                state = sw_dependency_3;
+                break;
+
+            case sw_dependency_3:
+                state = sw_dependency_4;
+                break;
+
+            case sw_dependency_4:
+                state = sw_weight;
+                break;
+
+            case sw_weight:
+                goto fragment;
+
+            /* suppress warning */
+            case sw_start:
+            case sw_fragment:
+            case sw_padding:
+                break;
+            }
+        }
+
+        ctx->rest -= p - b->pos;
+        b->pos = p;
+
+        ctx->frame_state = state;
+        return NGX_AGAIN;
+
+    fragment:
+
+        p++;
+        ctx->rest -= p - b->pos;
+        b->pos = p;
+
+        if (ctx->padding > ctx->rest) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "upstream sent http2 frame with too long "
+                          "padding: %d in frame %uz",
+                          ctx->padding, ctx->rest);
+            return NGX_ERROR;
+        }
+
+        state = sw_fragment;
+        ctx->frame_state = state;
+    }
+
+    if (state == sw_fragment) {
+
+        rc = ngx_http_grpc_parse_fragment(r, ctx, b);
+
+        if (rc == NGX_AGAIN) {
+            return NGX_AGAIN;
+        }
+
+        if (rc == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+
+        if (rc == NGX_OK) {
+            return NGX_OK;
+        }
+
+        /* rc == NGX_DONE */
+
+        state = sw_padding;
+        ctx->frame_state = state;
+    }
+
+    if (state == sw_padding) {
+
+        if (b->last - b->pos < (ssize_t) ctx->rest) {
+
+            ctx->rest -= b->last - b->pos;
+            b->pos = b->last;
+
+            return NGX_AGAIN;
+        }
+
+        b->pos += ctx->rest;
+        ctx->rest = 0;
+
+        ctx->state = ngx_http_grpc_st_start;
+
+        if (ctx->flags & NGX_HTTP_V2_END_HEADERS_FLAG) {
+
+            if (ctx->fragment_state) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent truncated http2 header");
+                return NGX_ERROR;
+            }
+
+            ctx->parsing_headers = 0;
+
+            return NGX_HTTP_PARSE_HEADER_DONE;
+        }
+
+        return NGX_AGAIN;
+    }
+
+    /* unreachable */
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_grpc_parse_fragment(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx,
+    ngx_buf_t *b)
+{
+    u_char      ch, *p, *last;
+    size_t      size;
+    ngx_uint_t  index, size_update;
+    enum {
+        sw_start = 0,
+        sw_index,
+        sw_name_length,
+        sw_name_length_2,
+        sw_name_length_3,
+        sw_name_length_4,
+        sw_name,
+        sw_name_bytes,
+        sw_value_length,
+        sw_value_length_2,
+        sw_value_length_3,
+        sw_value_length_4,
+        sw_value,
+        sw_value_bytes
+    } state;
+
+    /* header block fragment */
+
+#if 0
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "grpc header fragment %p:%p rest:%uz",
+                   b->pos, b->last, ctx->rest);
+#endif
+
+    if (b->last - b->pos < (ssize_t) ctx->rest - ctx->padding) {
+        last = b->last;
+
+    } else {
+        last = b->pos + ctx->rest - ctx->padding;
+    }
+
+    state = ctx->fragment_state;
+
+    for (p = b->pos; p < last; p++) {
+        ch = *p;
+
+#if 0
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc header byte: %02Xd s:%d", ch, state);
+#endif
+
+        switch (state) {
+
+        case sw_start:
+            ctx->index = 0;
+
+            if ((ch & 0x80) == 0x80) {
+                /*
+                 * indexed header:
+                 *
+                 *   0   1   2   3   4   5   6   7
+                 * +---+---+---+---+---+---+---+---+
+                 * | 1 |        Index (7+)         |
+                 * +---+---------------------------+
+                 */
+
+                index = ch & ~0x80;
+
+                if (index == 0 || index > 61) {
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                  "upstream sent invalid http2 "
+                                  "table index: %ui", index);
+                    return NGX_ERROR;
+                }
+
+                ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                               "grpc indexed header: %ui", index);
+
+                ctx->index = index;
+                ctx->literal = 0;
+
+                goto done;
+
+            } else if ((ch & 0xc0) == 0x40) {
+                /*
+                 * literal header with incremental indexing:
+                 *
+                 *   0   1   2   3   4   5   6   7
+                 * +---+---+---+---+---+---+---+---+
+                 * | 0 | 1 |      Index (6+)       |
+                 * +---+---+-----------------------+
+                 * | H |     Value Length (7+)     |
+                 * +---+---------------------------+
+                 * | Value String (Length octets)  |
+                 * +-------------------------------+
+                 *
+                 *   0   1   2   3   4   5   6   7
+                 * +---+---+---+---+---+---+---+---+
+                 * | 0 | 1 |           0           |
+                 * +---+---+-----------------------+
+                 * | H |     Name Length (7+)      |
+                 * +---+---------------------------+
+                 * |  Name String (Length octets)  |
+                 * +---+---------------------------+
+                 * | H |     Value Length (7+)     |
+                 * +---+---------------------------+
+                 * | Value String (Length octets)  |
+                 * +-------------------------------+
+                 */
+
+                index = ch & ~0xc0;
+
+                if (index > 61) {
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                  "upstream sent invalid http2 "
+                                  "table index: %ui", index);
+                    return NGX_ERROR;
+                }
+
+                ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                               "grpc literal header: %ui", index);
+
+                if (index == 0) {
+                    state = sw_name_length;
+                    break;
+                }
+
+                ctx->index = index;
+                ctx->literal = 1;
+
+                state = sw_value_length;
+                break;
+
+            } else if ((ch & 0xe0) == 0x20) {
+                /*
+                 * dynamic table size update:
+                 *
+                 *   0   1   2   3   4   5   6   7
+                 * +---+---+---+---+---+---+---+---+
+                 * | 0 | 0 | 1 |   Max size (5+)   |
+                 * +---+---------------------------+
+                 */
+
+                size_update = ch & ~0xe0;
+
+                if (size_update > 0) {
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                  "upstream sent invalid http2 "
+                                  "dynamic table size update: %ui",
+                                  size_update);
+                    return NGX_ERROR;
+                }
+
+                ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                               "grpc table size update: %ui", size_update);
+
+                break;
+
+            } else if ((ch & 0xf0) == 0x10) {
+                /*
+                 *  literal header field never indexed:
+                 *
+                 *   0   1   2   3   4   5   6   7
+                 * +---+---+---+---+---+---+---+---+
+                 * | 0 | 0 | 0 | 1 |  Index (4+)   |
+                 * +---+---+-----------------------+
+                 * | H |     Value Length (7+)     |
+                 * +---+---------------------------+
+                 * | Value String (Length octets)  |
+                 * +-------------------------------+
+                 *
+                 *   0   1   2   3   4   5   6   7
+                 * +---+---+---+---+---+---+---+---+
+                 * | 0 | 0 | 0 | 1 |       0       |
+                 * +---+---+-----------------------+
+                 * | H |     Name Length (7+)      |
+                 * +---+---------------------------+
+                 * |  Name String (Length octets)  |
+                 * +---+---------------------------+
+                 * | H |     Value Length (7+)     |
+                 * +---+---------------------------+
+                 * | Value String (Length octets)  |
+                 * +-------------------------------+
+                 */
+
+                index = ch & ~0xf0;
+
+                if (index == 0x0f) {
+                    ctx->index = index;
+                    ctx->literal = 1;
+                    state = sw_index;
+                    break;
+                }
+
+                if (index == 0) {
+                    state = sw_name_length;
+                    break;
+                }
+
+                ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                               "grpc literal header never indexed: %ui",
+                               index);
+
+                ctx->index = index;
+                ctx->literal = 1;
+
+                state = sw_value_length;
+                break;
+
+            } else if ((ch & 0xf0) == 0x00) {
+                /*
+                 * literal header field without indexing:
+                 *
+                 *   0   1   2   3   4   5   6   7
+                 * +---+---+---+---+---+---+---+---+
+                 * | 0 | 0 | 0 | 0 |  Index (4+)   |
+                 * +---+---+-----------------------+
+                 * | H |     Value Length (7+)     |
+                 * +---+---------------------------+
+                 * | Value String (Length octets)  |
+                 * +-------------------------------+
+                 *
+                 *   0   1   2   3   4   5   6   7
+                 * +---+---+---+---+---+---+---+---+
+                 * | 0 | 0 | 0 | 0 |       0       |
+                 * +---+---+-----------------------+
+                 * | H |     Name Length (7+)      |
+                 * +---+---------------------------+
+                 * |  Name String (Length octets)  |
+                 * +---+---------------------------+
+                 * | H |     Value Length (7+)     |
+                 * +---+---------------------------+
+                 * | Value String (Length octets)  |
+                 * +-------------------------------+
+                 */
+
+                index = ch & ~0xf0;
+
+                if (index == 0x0f) {
+                    ctx->index = index;
+                    ctx->literal = 1;
+                    state = sw_index;
+                    break;
+                }
+
+                if (index == 0) {
+                    state = sw_name_length;
+                    break;
+                }
+
+                ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                               "grpc literal header without indexing: %ui",
+                               index);
+
+                ctx->index = index;
+                ctx->literal = 1;
+
+                state = sw_value_length;
+                break;
+            }
+
+            /* not reached */
+
+            return NGX_ERROR;
+
+        case sw_index:
+            ctx->index = ctx->index + (ch & ~0x80);
+
+            if (ch & 0x80) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent http2 table index "
+                              "with continuation flag");
+                return NGX_ERROR;
+            }
+
+            if (ctx->index > 61) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent invalid http2 "
+                              "table index: %ui", ctx->index);
+                return NGX_ERROR;
+            }
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "grpc header index: %ui", ctx->index);
+
+            state = sw_value_length;
+            break;
+
+        case sw_name_length:
+            ctx->field_huffman = ch & 0x80 ? 1 : 0;
+            ctx->field_length = ch & ~0x80;
+
+            if (ctx->field_length == 0x7f) {
+                state = sw_name_length_2;
+                break;
+            }
+
+            if (ctx->field_length == 0) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent zero http2 "
+                              "header name length");
+                return NGX_ERROR;
+            }
+
+            state = sw_name;
+            break;
+
+        case sw_name_length_2:
+            ctx->field_length += ch & ~0x80;
+
+            if (ch & 0x80) {
+                state = sw_name_length_3;
+                break;
+            }
+
+            state = sw_name;
+            break;
+
+        case sw_name_length_3:
+            ctx->field_length += (ch & ~0x80) << 7;
+
+            if (ch & 0x80) {
+                state = sw_name_length_4;
+                break;
+            }
+
+            state = sw_name;
+            break;
+
+        case sw_name_length_4:
+            ctx->field_length += (ch & ~0x80) << 14;
+
+            if (ch & 0x80) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent too large http2 "
+                              "header name length");
+                return NGX_ERROR;
+            }
+
+            state = sw_name;
+            break;
+
+        case sw_name:
+            ctx->name.len = ctx->field_huffman ?
+                            ctx->field_length * 8 / 5 : ctx->field_length;
+
+            ctx->name.data = ngx_pnalloc(r->pool, ctx->name.len + 1);
+            if (ctx->name.data == NULL) {
+                return NGX_ERROR;
+            }
+
+            ctx->field_end = ctx->name.data;
+            ctx->field_rest = ctx->field_length;
+            ctx->field_state = 0;
+
+            state = sw_name_bytes;
+
+            /* fall through */
+
+        case sw_name_bytes:
+
+            ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "grpc name: len:%uz h:%d last:%uz, rest:%uz",
+                           ctx->field_length,
+                           ctx->field_huffman,
+                           last - p,
+                           ctx->rest - (p - b->pos));
+
+            size = ngx_min(last - p, (ssize_t) ctx->field_rest);
+            ctx->field_rest -= size;
+
+            if (ctx->field_huffman) {
+                if (ngx_http_v2_huff_decode(&ctx->field_state, p, size,
+                                            &ctx->field_end,
+                                            ctx->field_rest == 0,
+                                            r->connection->log)
+                    != NGX_OK)
+                {
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                  "upstream sent invalid encoded header");
+                    return NGX_ERROR;
+                }
+
+                ctx->name.len = ctx->field_end - ctx->name.data;
+                ctx->name.data[ctx->name.len] = '\0';
+
+            } else {
+                ctx->field_end = ngx_cpymem(ctx->field_end, p, size);
+                ctx->name.data[ctx->name.len] = '\0';
+            }
+
+            p += size - 1;
+
+            if (ctx->field_rest == 0) {
+                state = sw_value_length;
+            }
+
+            break;
+
+        case sw_value_length:
+            ctx->field_huffman = ch & 0x80 ? 1 : 0;
+            ctx->field_length = ch & ~0x80;
+
+            if (ctx->field_length == 0x7f) {
+                state = sw_value_length_2;
+                break;
+            }
+
+            if (ctx->field_length == 0) {
+                ngx_str_set(&ctx->value, "");
+                goto done;
+            }
+
+            state = sw_value;
+            break;
+
+        case sw_value_length_2:
+            ctx->field_length += ch & ~0x80;
+
+            if (ch & 0x80) {
+                state = sw_value_length_3;
+                break;
+            }
+
+            state = sw_value;
+            break;
+
+        case sw_value_length_3:
+            ctx->field_length += (ch & ~0x80) << 7;
+
+            if (ch & 0x80) {
+                state = sw_value_length_4;
+                break;
+            }
+
+            state = sw_value;
+            break;
+
+        case sw_value_length_4:
+            ctx->field_length += (ch & ~0x80) << 14;
+
+            if (ch & 0x80) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent too large http2 "
+                              "header value length");
+                return NGX_ERROR;
+            }
+
+            state = sw_value;
+            break;
+
+        case sw_value:
+            ctx->value.len = ctx->field_huffman ?
+                             ctx->field_length * 8 / 5 : ctx->field_length;
+
+            ctx->value.data = ngx_pnalloc(r->pool, ctx->value.len + 1);
+            if (ctx->value.data == NULL) {
+                return NGX_ERROR;
+            }
+
+            ctx->field_end = ctx->value.data;
+            ctx->field_rest = ctx->field_length;
+            ctx->field_state = 0;
+
+            state = sw_value_bytes;
+
+            /* fall through */
+
+        case sw_value_bytes:
+
+            ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "grpc value: len:%uz h:%d last:%uz, rest:%uz",
+                           ctx->field_length,
+                           ctx->field_huffman,
+                           last - p,
+                           ctx->rest - (p - b->pos));
+
+            size = ngx_min(last - p, (ssize_t) ctx->field_rest);
+            ctx->field_rest -= size;
+
+            if (ctx->field_huffman) {
+                if (ngx_http_v2_huff_decode(&ctx->field_state, p, size,
+                                            &ctx->field_end,
+                                            ctx->field_rest == 0,
+                                            r->connection->log)
+                    != NGX_OK)
+                {
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                  "upstream sent invalid encoded header");
+                    return NGX_ERROR;
+                }
+
+                ctx->value.len = ctx->field_end - ctx->value.data;
+                ctx->value.data[ctx->value.len] = '\0';
+
+            } else {
+                ctx->field_end = ngx_cpymem(ctx->field_end, p, size);
+                ctx->value.data[ctx->value.len] = '\0';
+            }
+
+            p += size - 1;
+
+            if (ctx->field_rest == 0) {
+                goto done;
+            }
+
+            break;
+        }
+
+        continue;
+
+    done:
+
+        p++;
+        ctx->rest -= p - b->pos;
+        ctx->fragment_state = sw_start;
+        b->pos = p;
+
+        if (ctx->index) {
+            ctx->name = *ngx_http_v2_get_static_name(ctx->index);
+        }
+
+        if (ctx->index && !ctx->literal) {
+            ctx->value = *ngx_http_v2_get_static_value(ctx->index);
+        }
+
+        if (!ctx->index) {
+            if (ngx_http_grpc_validate_header_name(r, &ctx->name) != NGX_OK) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent invalid header: \"%V: %V\"",
+                              &ctx->name, &ctx->value);
+                return NGX_ERROR;
+            }
+        }
+
+        if (!ctx->index || ctx->literal) {
+            if (ngx_http_grpc_validate_header_value(r, &ctx->value) != NGX_OK) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent invalid header: \"%V: %V\"",
+                              &ctx->name, &ctx->value);
+                return NGX_ERROR;
+            }
+        }
+
+        return NGX_OK;
+    }
+
+    ctx->rest -= p - b->pos;
+    ctx->fragment_state = state;
+    b->pos = p;
+
+    if (ctx->rest > ctx->padding) {
+        return NGX_AGAIN;
+    }
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_grpc_validate_header_name(ngx_http_request_t *r, ngx_str_t *s)
+{
+    u_char      ch;
+    ngx_uint_t  i;
+
+    for (i = 0; i < s->len; i++) {
+        ch = s->data[i];
+
+        if (ch == ':' && i > 0) {
+            return NGX_ERROR;
+        }
+
+        if (ch >= 'A' && ch <= 'Z') {
+            return NGX_ERROR;
+        }
+
+        if (ch == '\0' || ch == CR || ch == LF) {
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_grpc_validate_header_value(ngx_http_request_t *r, ngx_str_t *s)
+{
+    u_char      ch;
+    ngx_uint_t  i;
+
+    for (i = 0; i < s->len; i++) {
+        ch = s->data[i];
+
+        if (ch == '\0' || ch == CR || ch == LF) {
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_grpc_parse_rst_stream(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx,
+    ngx_buf_t *b)
+{
+    u_char  ch, *p, *last;
+    enum {
+        sw_start = 0,
+        sw_error_2,
+        sw_error_3,
+        sw_error_4
+    } state;
+
+    if (b->last - b->pos < (ssize_t) ctx->rest) {
+        last = b->last;
+
+    } else {
+        last = b->pos + ctx->rest;
+    }
+
+    state = ctx->frame_state;
+
+    if (state == sw_start) {
+        if (ctx->rest != 4) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "upstream sent rst stream frame "
+                          "with invalid length: %uz",
+                          ctx->rest);
+            return NGX_ERROR;
+        }
+    }
+
+    for (p = b->pos; p < last; p++) {
+        ch = *p;
+
+#if 0
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc rst byte: %02Xd s:%d", ch, state);
+#endif
+
+        switch (state) {
+
+        case sw_start:
+            ctx->error = (ngx_uint_t) ch << 24;
+            state = sw_error_2;
+            break;
+
+        case sw_error_2:
+            ctx->error |= ch << 16;
+            state = sw_error_3;
+            break;
+
+        case sw_error_3:
+            ctx->error |= ch << 8;
+            state = sw_error_4;
+            break;
+
+        case sw_error_4:
+            ctx->error |= ch;
+            state = sw_start;
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "grpc error: %ui", ctx->error);
+
+            break;
+        }
+    }
+
+    ctx->rest -= p - b->pos;
+    ctx->frame_state = state;
+    b->pos = p;
+
+    if (ctx->rest > 0) {
+        return NGX_AGAIN;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_grpc_parse_goaway(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx,
+    ngx_buf_t *b)
+{
+    u_char  ch, *p, *last;
+    enum {
+        sw_start = 0,
+        sw_last_stream_id_2,
+        sw_last_stream_id_3,
+        sw_last_stream_id_4,
+        sw_error,
+        sw_error_2,
+        sw_error_3,
+        sw_error_4,
+        sw_debug
+    } state;
+
+    if (b->last - b->pos < (ssize_t) ctx->rest) {
+        last = b->last;
+
+    } else {
+        last = b->pos + ctx->rest;
+    }
+
+    state = ctx->frame_state;
+
+    if (state == sw_start) {
+
+        if (ctx->stream_id) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "upstream sent goaway frame "
+                          "with non-zero stream id: %ui",
+                          ctx->stream_id);
+            return NGX_ERROR;
+        }
+
+        if (ctx->rest < 8) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "upstream sent goaway frame "
+                          "with invalid length: %uz",
+                          ctx->rest);
+            return NGX_ERROR;
+        }
+    }
+
+    for (p = b->pos; p < last; p++) {
+        ch = *p;
+
+#if 0
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc goaway byte: %02Xd s:%d", ch, state);
+#endif
+
+        switch (state) {
+
+        case sw_start:
+            ctx->stream_id = (ch & 0x7f) << 24;
+            state = sw_last_stream_id_2;
+            break;
+
+        case sw_last_stream_id_2:
+            ctx->stream_id |= ch << 16;
+            state = sw_last_stream_id_3;
+            break;
+
+        case sw_last_stream_id_3:
+            ctx->stream_id |= ch << 8;
+            state = sw_last_stream_id_4;
+            break;
+
+        case sw_last_stream_id_4:
+            ctx->stream_id |= ch;
+            state = sw_error;
+            break;
+
+        case sw_error:
+            ctx->error = (ngx_uint_t) ch << 24;
+            state = sw_error_2;
+            break;
+
+        case sw_error_2:
+            ctx->error |= ch << 16;
+            state = sw_error_3;
+            break;
+
+        case sw_error_3:
+            ctx->error |= ch << 8;
+            state = sw_error_4;
+            break;
+
+        case sw_error_4:
+            ctx->error |= ch;
+            state = sw_debug;
+            break;
+
+        case sw_debug:
+            break;
+        }
+    }
+
+    ctx->rest -= p - b->pos;
+    ctx->frame_state = state;
+    b->pos = p;
+
+    if (ctx->rest > 0) {
+        return NGX_AGAIN;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "grpc goaway: %ui, stream %ui",
+                   ctx->error, ctx->stream_id);
+
+    ctx->state = ngx_http_grpc_st_start;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_grpc_parse_window_update(ngx_http_request_t *r,
+    ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b)
+{
+    u_char  ch, *p, *last;
+    enum {
+        sw_start = 0,
+        sw_size_2,
+        sw_size_3,
+        sw_size_4
+    } state;
+
+    if (b->last - b->pos < (ssize_t) ctx->rest) {
+        last = b->last;
+
+    } else {
+        last = b->pos + ctx->rest;
+    }
+
+    state = ctx->frame_state;
+
+    if (state == sw_start) {
+        if (ctx->rest != 4) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "upstream sent window update frame "
+                          "with invalid length: %uz",
+                          ctx->rest);
+            return NGX_ERROR;
+        }
+    }
+
+    for (p = b->pos; p < last; p++) {
+        ch = *p;
+
+#if 0
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc window update byte: %02Xd s:%d", ch, state);
+#endif
+
+        switch (state) {
+
+        case sw_start:
+            ctx->window_update = (ch & 0x7f) << 24;
+            state = sw_size_2;
+            break;
+
+        case sw_size_2:
+            ctx->window_update |= ch << 16;
+            state = sw_size_3;
+            break;
+
+        case sw_size_3:
+            ctx->window_update |= ch << 8;
+            state = sw_size_4;
+            break;
+
+        case sw_size_4:
+            ctx->window_update |= ch;
+            state = sw_start;
+            break;
+        }
+    }
+
+    ctx->rest -= p - b->pos;
+    ctx->frame_state = state;
+    b->pos = p;
+
+    if (ctx->rest > 0) {
+        return NGX_AGAIN;
+    }
+
+    ctx->state = ngx_http_grpc_st_start;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "grpc window update: %ui", ctx->window_update);
+
+    if (ctx->stream_id) {
+
+        if (ctx->window_update > (size_t) NGX_HTTP_V2_MAX_WINDOW
+                                 - ctx->send_window)
+        {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "upstream sent too large window update");
+            return NGX_ERROR;
+        }
+
+        ctx->send_window += ctx->window_update;
+
+    } else {
+
+        if (ctx->window_update > NGX_HTTP_V2_MAX_WINDOW
+                                 - ctx->connection->send_window)
+        {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "upstream sent too large window update");
+            return NGX_ERROR;
+        }
+
+        ctx->connection->send_window += ctx->window_update;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_grpc_parse_settings(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx,
+    ngx_buf_t *b)
+{
+    u_char   ch, *p, *last;
+    ssize_t  window_update;
+    enum {
+        sw_start = 0,
+        sw_id,
+        sw_id_2,
+        sw_value,
+        sw_value_2,
+        sw_value_3,
+        sw_value_4
+    } state;
+
+    if (b->last - b->pos < (ssize_t) ctx->rest) {
+        last = b->last;
+
+    } else {
+        last = b->pos + ctx->rest;
+    }
+
+    state = ctx->frame_state;
+
+    if (state == sw_start) {
+
+        if (ctx->stream_id) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "upstream sent settings frame "
+                          "with non-zero stream id: %ui",
+                          ctx->stream_id);
+            return NGX_ERROR;
+        }
+
+        if (ctx->flags & NGX_HTTP_V2_ACK_FLAG) {
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "grpc settings ack");
+
+            if (ctx->rest != 0) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream sent settings frame "
+                              "with ack flag and non-zero length: %uz",
+                              ctx->rest);
+                return NGX_ERROR;
+            }
+
+            ctx->state = ngx_http_grpc_st_start;
+
+            return NGX_OK;
+        }
+
+        if (ctx->rest % 6 != 0) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "upstream sent settings frame "
+                          "with invalid length: %uz",
+                          ctx->rest);
+            return NGX_ERROR;
+        }
+
+        if (ctx->free == NULL && ctx->settings++ > 1000) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "upstream sent too many settings frames");
+            return NGX_ERROR;
+        }
+    }
+
+    for (p = b->pos; p < last; p++) {
+        ch = *p;
+
+#if 0
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc settings byte: %02Xd s:%d", ch, state);
+#endif
+
+        switch (state) {
+
+        case sw_start:
+        case sw_id:
+            ctx->setting_id = ch << 8;
+            state = sw_id_2;
+            break;
+
+        case sw_id_2:
+            ctx->setting_id |= ch;
+            state = sw_value;
+            break;
+
+        case sw_value:
+            ctx->setting_value = (ngx_uint_t) ch << 24;
+            state = sw_value_2;
+            break;
+
+        case sw_value_2:
+            ctx->setting_value |= ch << 16;
+            state = sw_value_3;
+            break;
+
+        case sw_value_3:
+            ctx->setting_value |= ch << 8;
+            state = sw_value_4;
+            break;
+
+        case sw_value_4:
+            ctx->setting_value |= ch;
+            state = sw_id;
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "grpc setting: %ui %ui",
+                           ctx->setting_id, ctx->setting_value);
+
+            /*
+             * The following settings are defined by the protocol:
+             *
+             * SETTINGS_HEADER_TABLE_SIZE, SETTINGS_ENABLE_PUSH,
+             * SETTINGS_MAX_CONCURRENT_STREAMS, SETTINGS_INITIAL_WINDOW_SIZE,
+             * SETTINGS_MAX_FRAME_SIZE, SETTINGS_MAX_HEADER_LIST_SIZE
+             *
+             * Only SETTINGS_INITIAL_WINDOW_SIZE seems to be needed in
+             * a simple client.
+             */
+
+            if (ctx->setting_id == 0x04) {
+                /* SETTINGS_INITIAL_WINDOW_SIZE */
+
+                if (ctx->setting_value > NGX_HTTP_V2_MAX_WINDOW) {
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                  "upstream sent settings frame "
+                                  "with too large initial window size: %ui",
+                                  ctx->setting_value);
+                    return NGX_ERROR;
+                }
+
+                window_update = ctx->setting_value
+                                - ctx->connection->init_window;
+                ctx->connection->init_window = ctx->setting_value;
+
+                if (ctx->send_window > 0
+                    && window_update > (ssize_t) NGX_HTTP_V2_MAX_WINDOW
+                                       - ctx->send_window)
+                {
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                  "upstream sent settings frame "
+                                  "with too large initial window size: %ui",
+                                  ctx->setting_value);
+                    return NGX_ERROR;
+                }
+
+                ctx->send_window += window_update;
+            }
+
+            break;
+        }
+    }
+
+    ctx->rest -= p - b->pos;
+    ctx->frame_state = state;
+    b->pos = p;
+
+    if (ctx->rest > 0) {
+        return NGX_AGAIN;
+    }
+
+    ctx->state = ngx_http_grpc_st_start;
+
+    return ngx_http_grpc_send_settings_ack(r, ctx);
+}
+
+
+static ngx_int_t
+ngx_http_grpc_parse_ping(ngx_http_request_t *r,
+    ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b)
+{
+    u_char  ch, *p, *last;
+    enum {
+        sw_start = 0,
+        sw_data_2,
+        sw_data_3,
+        sw_data_4,
+        sw_data_5,
+        sw_data_6,
+        sw_data_7,
+        sw_data_8
+    } state;
+
+    if (b->last - b->pos < (ssize_t) ctx->rest) {
+        last = b->last;
+
+    } else {
+        last = b->pos + ctx->rest;
+    }
+
+    state = ctx->frame_state;
+
+    if (state == sw_start) {
+
+        if (ctx->stream_id) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "upstream sent ping frame "
+                          "with non-zero stream id: %ui",
+                          ctx->stream_id);
+            return NGX_ERROR;
+        }
+
+        if (ctx->rest != 8) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "upstream sent ping frame "
+                          "with invalid length: %uz",
+                          ctx->rest);
+            return NGX_ERROR;
+        }
+
+        if (ctx->flags & NGX_HTTP_V2_ACK_FLAG) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "upstream sent ping frame with ack flag");
+            return NGX_ERROR;
+        }
+
+        if (ctx->free == NULL && ctx->pings++ > 1000) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "upstream sent too many ping frames");
+            return NGX_ERROR;
+        }
+    }
+
+    for (p = b->pos; p < last; p++) {
+        ch = *p;
+
+#if 0
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "grpc ping byte: %02Xd s:%d", ch, state);
+#endif
+
+        if (state < sw_data_8) {
+            ctx->ping_data[state] = ch;
+            state++;
+
+        } else {
+            ctx->ping_data[7] = ch;
+            state = sw_start;
+
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "grpc ping");
+        }
+    }
+
+    ctx->rest -= p - b->pos;
+    ctx->frame_state = state;
+    b->pos = p;
+
+    if (ctx->rest > 0) {
+        return NGX_AGAIN;
+    }
+
+    ctx->state = ngx_http_grpc_st_start;
+
+    return ngx_http_grpc_send_ping_ack(r, ctx);
+}
+
+
+static ngx_int_t
+ngx_http_grpc_send_settings_ack(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx)
+{
+    ngx_chain_t            *cl, **ll;
+    ngx_http_grpc_frame_t  *f;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "grpc send settings ack");
+
+    for (cl = ctx->out, ll = &ctx->out; cl; cl = cl->next) {
+        ll = &cl->next;
+    }
+
+    cl = ngx_http_grpc_get_buf(r, ctx);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    f = (ngx_http_grpc_frame_t *) cl->buf->last;
+    cl->buf->last += sizeof(ngx_http_grpc_frame_t);
+
+    f->length_0 = 0;
+    f->length_1 = 0;
+    f->length_2 = 0;
+    f->type = NGX_HTTP_V2_SETTINGS_FRAME;
+    f->flags = NGX_HTTP_V2_ACK_FLAG;
+    f->stream_id_0 = 0;
+    f->stream_id_1 = 0;
+    f->stream_id_2 = 0;
+    f->stream_id_3 = 0;
+
+    *ll = cl;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_grpc_send_ping_ack(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx)
+{
+    ngx_chain_t            *cl, **ll;
+    ngx_http_grpc_frame_t  *f;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "grpc send ping ack");
+
+    for (cl = ctx->out, ll = &ctx->out; cl; cl = cl->next) {
+        ll = &cl->next;
+    }
+
+    cl = ngx_http_grpc_get_buf(r, ctx);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    f = (ngx_http_grpc_frame_t *) cl->buf->last;
+    cl->buf->last += sizeof(ngx_http_grpc_frame_t);
+
+    f->length_0 = 0;
+    f->length_1 = 0;
+    f->length_2 = 8;
+    f->type = NGX_HTTP_V2_PING_FRAME;
+    f->flags = NGX_HTTP_V2_ACK_FLAG;
+    f->stream_id_0 = 0;
+    f->stream_id_1 = 0;
+    f->stream_id_2 = 0;
+    f->stream_id_3 = 0;
+
+    cl->buf->last = ngx_copy(cl->buf->last, ctx->ping_data, 8);
+
+    *ll = cl;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_grpc_send_window_update(ngx_http_request_t *r,
+    ngx_http_grpc_ctx_t *ctx)
+{
+    size_t                  n;
+    ngx_chain_t            *cl, **ll;
+    ngx_http_grpc_frame_t  *f;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "grpc send window update: %uz %uz",
+                   ctx->connection->recv_window, ctx->recv_window);
+
+    for (cl = ctx->out, ll = &ctx->out; cl; cl = cl->next) {
+        ll = &cl->next;
+    }
+
+    cl = ngx_http_grpc_get_buf(r, ctx);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    f = (ngx_http_grpc_frame_t *) cl->buf->last;
+    cl->buf->last += sizeof(ngx_http_grpc_frame_t);
+
+    f->length_0 = 0;
+    f->length_1 = 0;
+    f->length_2 = 4;
+    f->type = NGX_HTTP_V2_WINDOW_UPDATE_FRAME;
+    f->flags = 0;
+    f->stream_id_0 = 0;
+    f->stream_id_1 = 0;
+    f->stream_id_2 = 0;
+    f->stream_id_3 = 0;
+
+    n = NGX_HTTP_V2_MAX_WINDOW - ctx->connection->recv_window;
+    ctx->connection->recv_window = NGX_HTTP_V2_MAX_WINDOW;
+
+    *cl->buf->last++ = (u_char) ((n >> 24) & 0xff);
+    *cl->buf->last++ = (u_char) ((n >> 16) & 0xff);
+    *cl->buf->last++ = (u_char) ((n >> 8) & 0xff);
+    *cl->buf->last++ = (u_char) (n & 0xff);
+
+    f = (ngx_http_grpc_frame_t *) cl->buf->last;
+    cl->buf->last += sizeof(ngx_http_grpc_frame_t);
+
+    f->length_0 = 0;
+    f->length_1 = 0;
+    f->length_2 = 4;
+    f->type = NGX_HTTP_V2_WINDOW_UPDATE_FRAME;
+    f->flags = 0;
+    f->stream_id_0 = (u_char) ((ctx->id >> 24) & 0xff);
+    f->stream_id_1 = (u_char) ((ctx->id >> 16) & 0xff);
+    f->stream_id_2 = (u_char) ((ctx->id >> 8) & 0xff);
+    f->stream_id_3 = (u_char) (ctx->id & 0xff);
+
+    n = NGX_HTTP_V2_MAX_WINDOW - ctx->recv_window;
+    ctx->recv_window = NGX_HTTP_V2_MAX_WINDOW;
+
+    *cl->buf->last++ = (u_char) ((n >> 24) & 0xff);
+    *cl->buf->last++ = (u_char) ((n >> 16) & 0xff);
+    *cl->buf->last++ = (u_char) ((n >> 8) & 0xff);
+    *cl->buf->last++ = (u_char) (n & 0xff);
+
+    *ll = cl;
+
+    return NGX_OK;
+}
+
+
+static ngx_chain_t *
+ngx_http_grpc_get_buf(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx)
+{
+    u_char       *start;
+    ngx_buf_t    *b;
+    ngx_chain_t  *cl;
+
+    cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+    if (cl == NULL) {
+        return NULL;
+    }
+
+    b = cl->buf;
+    start = b->start;
+
+    if (start == NULL) {
+
+        /*
+         * each buffer is large enough to hold two window update
+         * frames in a row
+         */
+
+        start = ngx_palloc(r->pool, 2 * sizeof(ngx_http_grpc_frame_t) + 8);
+        if (start == NULL) {
+            return NULL;
+        }
+
+    }
+
+    ngx_memzero(b, sizeof(ngx_buf_t));
+
+    b->start = start;
+    b->pos = start;
+    b->last = start;
+    b->end = start + 2 * sizeof(ngx_http_grpc_frame_t) + 8;
+
+    b->tag = (ngx_buf_tag_t) &ngx_http_grpc_body_output_filter;
+    b->temporary = 1;
+    b->flush = 1;
+
+    return cl;
+}
+
+
+static ngx_http_grpc_ctx_t *
+ngx_http_grpc_get_ctx(ngx_http_request_t *r)
+{
+    ngx_http_grpc_ctx_t  *ctx;
+    ngx_http_upstream_t  *u;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_grpc_module);
+
+    if (ctx->connection == NULL) {
+        u = r->upstream;
+
+        if (ngx_http_grpc_get_connection_data(r, ctx, &u->peer) != NGX_OK) {
+            return NULL;
+        }
+    }
+
+    return ctx;
+}
+
+
+static ngx_int_t
+ngx_http_grpc_get_connection_data(ngx_http_request_t *r,
+    ngx_http_grpc_ctx_t *ctx, ngx_peer_connection_t *pc)
+{
+    ngx_connection_t    *c;
+    ngx_pool_cleanup_t  *cln;
+
+    c = pc->connection;
+
+    if (pc->cached) {
+
+        /*
+         * for cached connections, connection data can be found
+         * in the cleanup handler
+         */
+
+        for (cln = c->pool->cleanup; cln; cln = cln->next) {
+            if (cln->handler == ngx_http_grpc_cleanup) {
+                ctx->connection = cln->data;
+                break;
+            }
+        }
+
+        if (ctx->connection == NULL) {
+            ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                          "no connection data found for "
+                          "keepalive http2 connection");
+            return NGX_ERROR;
+        }
+
+        ctx->send_window = ctx->connection->init_window;
+        ctx->recv_window = NGX_HTTP_V2_MAX_WINDOW;
+
+        ctx->connection->last_stream_id += 2;
+        ctx->id = ctx->connection->last_stream_id;
+
+        return NGX_OK;
+    }
+
+    cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_http_grpc_conn_t));
+    if (cln == NULL) {
+        return NGX_ERROR;
+    }
+
+    cln->handler = ngx_http_grpc_cleanup;
+    ctx->connection = cln->data;
+
+    ctx->connection->init_window = NGX_HTTP_V2_DEFAULT_WINDOW;
+    ctx->connection->send_window = NGX_HTTP_V2_DEFAULT_WINDOW;
+    ctx->connection->recv_window = NGX_HTTP_V2_MAX_WINDOW;
+
+    ctx->send_window = NGX_HTTP_V2_DEFAULT_WINDOW;
+    ctx->recv_window = NGX_HTTP_V2_MAX_WINDOW;
+
+    ctx->id = 1;
+    ctx->connection->last_stream_id = 1;
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_grpc_cleanup(void *data)
+{
+#if 0
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "grpc cleanup");
+#endif
+    return;
+}
+
+
+static void
+ngx_http_grpc_abort_request(ngx_http_request_t *r)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "abort grpc request");
+    return;
+}
+
+
+static void
+ngx_http_grpc_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "finalize grpc request");
+    return;
+}
+
+
+static ngx_int_t
+ngx_http_grpc_internal_trailers_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_table_elt_t  *te;
+
+    te = r->headers_in.te;
+
+    if (te == NULL) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    if (ngx_strlcasestrn(te->value.data, te->value.data + te->value.len,
+                         (u_char *) "trailers", 8 - 1)
+        == NULL)
+    {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    v->data = (u_char *) "trailers";
+    v->len = sizeof("trailers") - 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_grpc_add_variables(ngx_conf_t *cf)
+{
+    ngx_http_variable_t  *var, *v;
+
+    for (v = ngx_http_grpc_vars; v->name.len; v++) {
+        var = ngx_http_add_variable(cf, &v->name, v->flags);
+        if (var == NULL) {
+            return NGX_ERROR;
+        }
+
+        var->get_handler = v->get_handler;
+        var->data = v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_grpc_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_grpc_loc_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_grpc_loc_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->upstream.ignore_headers = 0;
+     *     conf->upstream.next_upstream = 0;
+     *     conf->upstream.hide_headers_hash = { NULL, 0 };
+     *     conf->upstream.ssl_name = NULL;
+     *
+     *     conf->headers_source = NULL;
+     *     conf->headers.lengths = NULL;
+     *     conf->headers.values = NULL;
+     *     conf->headers.hash = { NULL, 0 };
+     *     conf->host = { 0, NULL };
+     *     conf->host_set = 0;
+     *     conf->ssl = 0;
+     *     conf->ssl_protocols = 0;
+     *     conf->ssl_ciphers = { 0, NULL };
+     *     conf->ssl_trusted_certificate = { 0, NULL };
+     *     conf->ssl_crl = { 0, NULL };
+     *     conf->ssl_certificate = { 0, NULL };
+     *     conf->ssl_certificate_key = { 0, NULL };
+     */
+
+    conf->upstream.local = NGX_CONF_UNSET_PTR;
+    conf->upstream.next_upstream_tries = NGX_CONF_UNSET_UINT;
+    conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.next_upstream_timeout = NGX_CONF_UNSET_MSEC;
+
+    conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE;
+
+    conf->upstream.hide_headers = NGX_CONF_UNSET_PTR;
+    conf->upstream.pass_headers = NGX_CONF_UNSET_PTR;
+
+    conf->upstream.intercept_errors = NGX_CONF_UNSET;
+
+#if (NGX_HTTP_SSL)
+    conf->upstream.ssl_session_reuse = NGX_CONF_UNSET;
+    conf->upstream.ssl_server_name = NGX_CONF_UNSET;
+    conf->upstream.ssl_verify = NGX_CONF_UNSET;
+    conf->ssl_verify_depth = NGX_CONF_UNSET_UINT;
+    conf->ssl_passwords = NGX_CONF_UNSET_PTR;
+#endif
+
+    /* the hardcoded values */
+    conf->upstream.cyclic_temp_file = 0;
+    conf->upstream.buffering = 0;
+    conf->upstream.ignore_client_abort = 0;
+    conf->upstream.send_lowat = 0;
+    conf->upstream.bufs.num = 0;
+    conf->upstream.busy_buffers_size = 0;
+    conf->upstream.max_temp_file_size = 0;
+    conf->upstream.temp_file_write_size = 0;
+    conf->upstream.pass_request_headers = 1;
+    conf->upstream.pass_request_body = 1;
+    conf->upstream.force_ranges = 0;
+    conf->upstream.pass_trailers = 1;
+    conf->upstream.preserve_output = 1;
+
+    ngx_str_set(&conf->upstream.module, "grpc");
+
+    return conf;
+}
+
+
+static char *
+ngx_http_grpc_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_grpc_loc_conf_t *prev = parent;
+    ngx_http_grpc_loc_conf_t *conf = child;
+
+    ngx_int_t                  rc;
+    ngx_hash_init_t            hash;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    ngx_conf_merge_ptr_value(conf->upstream.local,
+                              prev->upstream.local, NULL);
+
+    ngx_conf_merge_uint_value(conf->upstream.next_upstream_tries,
+                              prev->upstream.next_upstream_tries, 0);
+
+    ngx_conf_merge_msec_value(conf->upstream.connect_timeout,
+                              prev->upstream.connect_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->upstream.send_timeout,
+                              prev->upstream.send_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->upstream.read_timeout,
+                              prev->upstream.read_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->upstream.next_upstream_timeout,
+                              prev->upstream.next_upstream_timeout, 0);
+
+    ngx_conf_merge_size_value(conf->upstream.buffer_size,
+                              prev->upstream.buffer_size,
+                              (size_t) ngx_pagesize);
+
+    ngx_conf_merge_bitmask_value(conf->upstream.ignore_headers,
+                              prev->upstream.ignore_headers,
+                              NGX_CONF_BITMASK_SET);
+
+    ngx_conf_merge_bitmask_value(conf->upstream.next_upstream,
+                              prev->upstream.next_upstream,
+                              (NGX_CONF_BITMASK_SET
+                               |NGX_HTTP_UPSTREAM_FT_ERROR
+                               |NGX_HTTP_UPSTREAM_FT_TIMEOUT));
+
+    if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) {
+        conf->upstream.next_upstream = NGX_CONF_BITMASK_SET
+                                       |NGX_HTTP_UPSTREAM_FT_OFF;
+    }
+
+    ngx_conf_merge_value(conf->upstream.intercept_errors,
+                              prev->upstream.intercept_errors, 0);
+
+#if (NGX_HTTP_SSL)
+
+    ngx_conf_merge_value(conf->upstream.ssl_session_reuse,
+                              prev->upstream.ssl_session_reuse, 1);
+
+    ngx_conf_merge_bitmask_value(conf->ssl_protocols, prev->ssl_protocols,
+                                 (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1
+                                  |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2));
+
+    ngx_conf_merge_str_value(conf->ssl_ciphers, prev->ssl_ciphers,
+                             "DEFAULT");
+
+    if (conf->upstream.ssl_name == NULL) {
+        conf->upstream.ssl_name = prev->upstream.ssl_name;
+    }
+
+    ngx_conf_merge_value(conf->upstream.ssl_server_name,
+                              prev->upstream.ssl_server_name, 0);
+    ngx_conf_merge_value(conf->upstream.ssl_verify,
+                              prev->upstream.ssl_verify, 0);
+    ngx_conf_merge_uint_value(conf->ssl_verify_depth,
+                              prev->ssl_verify_depth, 1);
+    ngx_conf_merge_str_value(conf->ssl_trusted_certificate,
+                              prev->ssl_trusted_certificate, "");
+    ngx_conf_merge_str_value(conf->ssl_crl, prev->ssl_crl, "");
+
+    ngx_conf_merge_str_value(conf->ssl_certificate,
+                              prev->ssl_certificate, "");
+    ngx_conf_merge_str_value(conf->ssl_certificate_key,
+                              prev->ssl_certificate_key, "");
+    ngx_conf_merge_ptr_value(conf->ssl_passwords, prev->ssl_passwords, NULL);
+
+    if (conf->ssl && ngx_http_grpc_set_ssl(cf, conf) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+#endif
+
+    hash.max_size = 512;
+    hash.bucket_size = ngx_align(64, ngx_cacheline_size);
+    hash.name = "grpc_headers_hash";
+
+    if (ngx_http_upstream_hide_headers_hash(cf, &conf->upstream,
+            &prev->upstream, ngx_http_grpc_hide_headers, &hash)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+    if (clcf->noname && conf->upstream.upstream == NULL) {
+        conf->upstream.upstream = prev->upstream.upstream;
+        conf->host = prev->host;
+#if (NGX_HTTP_SSL)
+        conf->upstream.ssl = prev->upstream.ssl;
+#endif
+    }
+
+    if (clcf->lmt_excpt && clcf->handler == NULL && conf->upstream.upstream) {
+        clcf->handler = ngx_http_grpc_handler;
+    }
+
+    if (conf->headers_source == NULL) {
+        conf->headers = prev->headers;
+        conf->headers_source = prev->headers_source;
+        conf->host_set = prev->host_set;
+    }
+
+    rc = ngx_http_grpc_init_headers(cf, conf, &conf->headers,
+                                    ngx_http_grpc_headers);
+    if (rc != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    /*
+     * special handling to preserve conf->headers in the "http" section
+     * to inherit it to all servers
+     */
+
+    if (prev->headers.hash.buckets == NULL
+        && conf->headers_source == prev->headers_source)
+    {
+        prev->headers = conf->headers;
+        prev->host_set = conf->host_set;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_grpc_init_headers(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *conf,
+    ngx_http_grpc_headers_t *headers, ngx_keyval_t *default_headers)
+{
+    u_char                       *p;
+    size_t                        size;
+    uintptr_t                    *code;
+    ngx_uint_t                    i;
+    ngx_array_t                   headers_names, headers_merged;
+    ngx_keyval_t                 *src, *s, *h;
+    ngx_hash_key_t               *hk;
+    ngx_hash_init_t               hash;
+    ngx_http_script_compile_t     sc;
+    ngx_http_script_copy_code_t  *copy;
+
+    if (headers->hash.buckets) {
+        return NGX_OK;
+    }
+
+    if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&headers_merged, cf->temp_pool, 4, sizeof(ngx_keyval_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    headers->lengths = ngx_array_create(cf->pool, 64, 1);
+    if (headers->lengths == NULL) {
+        return NGX_ERROR;
+    }
+
+    headers->values = ngx_array_create(cf->pool, 512, 1);
+    if (headers->values == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (conf->headers_source) {
+
+        src = conf->headers_source->elts;
+        for (i = 0; i < conf->headers_source->nelts; i++) {
+
+            if (src[i].key.len == 4
+                && ngx_strncasecmp(src[i].key.data, (u_char *) "Host", 4) == 0)
+            {
+                conf->host_set = 1;
+            }
+
+            s = ngx_array_push(&headers_merged);
+            if (s == NULL) {
+                return NGX_ERROR;
+            }
+
+            *s = src[i];
+        }
+    }
+
+    h = default_headers;
+
+    while (h->key.len) {
+
+        src = headers_merged.elts;
+        for (i = 0; i < headers_merged.nelts; i++) {
+            if (ngx_strcasecmp(h->key.data, src[i].key.data) == 0) {
+                goto next;
+            }
+        }
+
+        s = ngx_array_push(&headers_merged);
+        if (s == NULL) {
+            return NGX_ERROR;
+        }
+
+        *s = *h;
+
+    next:
+
+        h++;
+    }
+
+
+    src = headers_merged.elts;
+    for (i = 0; i < headers_merged.nelts; i++) {
+
+        hk = ngx_array_push(&headers_names);
+        if (hk == NULL) {
+            return NGX_ERROR;
+        }
+
+        hk->key = src[i].key;
+        hk->key_hash = ngx_hash_key_lc(src[i].key.data, src[i].key.len);
+        hk->value = (void *) 1;
+
+        if (src[i].value.len == 0) {
+            continue;
+        }
+
+        copy = ngx_array_push_n(headers->lengths,
+                                sizeof(ngx_http_script_copy_code_t));
+        if (copy == NULL) {
+            return NGX_ERROR;
+        }
+
+        copy->code = (ngx_http_script_code_pt) (void *)
+                                                 ngx_http_script_copy_len_code;
+        copy->len = src[i].key.len;
+
+        size = (sizeof(ngx_http_script_copy_code_t)
+                + src[i].key.len + sizeof(uintptr_t) - 1)
+               & ~(sizeof(uintptr_t) - 1);
+
+        copy = ngx_array_push_n(headers->values, size);
+        if (copy == NULL) {
+            return NGX_ERROR;
+        }
+
+        copy->code = ngx_http_script_copy_code;
+        copy->len = src[i].key.len;
+
+        p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t);
+        ngx_memcpy(p, src[i].key.data, src[i].key.len);
+
+        ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+        sc.cf = cf;
+        sc.source = &src[i].value;
+        sc.flushes = &headers->flushes;
+        sc.lengths = &headers->lengths;
+        sc.values = &headers->values;
+
+        if (ngx_http_script_compile(&sc) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        code = ngx_array_push_n(headers->lengths, sizeof(uintptr_t));
+        if (code == NULL) {
+            return NGX_ERROR;
+        }
+
+        *code = (uintptr_t) NULL;
+
+        code = ngx_array_push_n(headers->values, sizeof(uintptr_t));
+        if (code == NULL) {
+            return NGX_ERROR;
+        }
+
+        *code = (uintptr_t) NULL;
+    }
+
+    code = ngx_array_push_n(headers->lengths, sizeof(uintptr_t));
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    *code = (uintptr_t) NULL;
+
+
+    hash.hash = &headers->hash;
+    hash.key = ngx_hash_key_lc;
+    hash.max_size = 512;
+    hash.bucket_size = 64;
+    hash.name = "grpc_headers_hash";
+    hash.pool = cf->pool;
+    hash.temp_pool = NULL;
+
+    return ngx_hash_init(&hash, headers_names.elts, headers_names.nelts);
+}
+
+
+static char *
+ngx_http_grpc_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_grpc_loc_conf_t *glcf = conf;
+
+    size_t                     add;
+    ngx_str_t                 *value, *url;
+    ngx_url_t                  u;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    if (glcf->upstream.upstream) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+    url = &value[1];
+
+    if (ngx_strncasecmp(url->data, (u_char *) "grpc://", 7) == 0) {
+        add = 7;
+
+    } else if (ngx_strncasecmp(url->data, (u_char *) "grpcs://", 8) == 0) {
+
+#if (NGX_HTTP_SSL)
+        glcf->ssl = 1;
+
+        add = 8;
+#else
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "grpcs protocol requires SSL support");
+        return NGX_CONF_ERROR;
+#endif
+
+    } else {
+        add = 0;
+    }
+
+    ngx_memzero(&u, sizeof(ngx_url_t));
+
+    u.url.len = url->len - add;
+    u.url.data = url->data + add;
+    u.no_resolve = 1;
+
+    glcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);
+    if (glcf->upstream.upstream == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (u.family != AF_UNIX) {
+
+        if (u.no_port) {
+            glcf->host = u.host;
+
+        } else {
+            glcf->host.len = u.host.len + 1 + u.port_text.len;
+            glcf->host.data = u.host.data;
+        }
+
+    } else {
+        ngx_str_set(&glcf->host, "localhost");
+    }
+
+    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+    clcf->handler = ngx_http_grpc_handler;
+
+    if (clcf->name.data[clcf->name.len - 1] == '/') {
+        clcf->auto_redirect = 1;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+#if (NGX_HTTP_SSL)
+
+static char *
+ngx_http_grpc_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_grpc_loc_conf_t *glcf = conf;
+
+    ngx_str_t  *value;
+
+    if (glcf->ssl_passwords != NGX_CONF_UNSET_PTR) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    glcf->ssl_passwords = ngx_ssl_read_password_file(cf, &value[1]);
+
+    if (glcf->ssl_passwords == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_grpc_set_ssl(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *glcf)
+{
+    ngx_pool_cleanup_t  *cln;
+
+    glcf->upstream.ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t));
+    if (glcf->upstream.ssl == NULL) {
+        return NGX_ERROR;
+    }
+
+    glcf->upstream.ssl->log = cf->log;
+
+    if (ngx_ssl_create(glcf->upstream.ssl, glcf->ssl_protocols, NULL)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    cln = ngx_pool_cleanup_add(cf->pool, 0);
+    if (cln == NULL) {
+        return NGX_ERROR;
+    }
+
+    cln->handler = ngx_ssl_cleanup_ctx;
+    cln->data = glcf->upstream.ssl;
+
+    if (glcf->ssl_certificate.len) {
+
+        if (glcf->ssl_certificate_key.len == 0) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "no \"grpc_ssl_certificate_key\" is defined "
+                          "for certificate \"%V\"", &glcf->ssl_certificate);
+            return NGX_ERROR;
+        }
+
+        if (ngx_ssl_certificate(cf, glcf->upstream.ssl, &glcf->ssl_certificate,
+                                &glcf->ssl_certificate_key, glcf->ssl_passwords)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    if (ngx_ssl_ciphers(cf, glcf->upstream.ssl, &glcf->ssl_ciphers, 0)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (glcf->upstream.ssl_verify) {
+        if (glcf->ssl_trusted_certificate.len == 0) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                      "no grpc_ssl_trusted_certificate for grpc_ssl_verify");
+            return NGX_ERROR;
+        }
+
+        if (ngx_ssl_trusted_certificate(cf, glcf->upstream.ssl,
+                                        &glcf->ssl_trusted_certificate,
+                                        glcf->ssl_verify_depth)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+
+        if (ngx_ssl_crl(cf, glcf->upstream.ssl, &glcf->ssl_crl) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
+
+    if (SSL_CTX_set_alpn_protos(glcf->upstream.ssl->ctx,
+                                (u_char *) "\x02h2", 3)
+        != 0)
+    {
+        ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+                      "SSL_CTX_set_alpn_protos() failed");
+        return NGX_ERROR;
+    }
+
+#endif
+
+    return NGX_OK;
+}
+
+#endif
diff --git a/nginx/src/http/modules/ngx_http_gunzip_filter_module.c b/nginx/src/http/modules/ngx_http_gunzip_filter_module.c
new file mode 100644 (file)
index 0000000..c1341f5
--- /dev/null
@@ -0,0 +1,687 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Maxim Dounin
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#include <zlib.h>
+
+
+typedef struct {
+    ngx_flag_t           enable;
+    ngx_bufs_t           bufs;
+} ngx_http_gunzip_conf_t;
+
+
+typedef struct {
+    ngx_chain_t         *in;
+    ngx_chain_t         *free;
+    ngx_chain_t         *busy;
+    ngx_chain_t         *out;
+    ngx_chain_t        **last_out;
+
+    ngx_buf_t           *in_buf;
+    ngx_buf_t           *out_buf;
+    ngx_int_t            bufs;
+
+    unsigned             started:1;
+    unsigned             flush:4;
+    unsigned             redo:1;
+    unsigned             done:1;
+    unsigned             nomem:1;
+
+    z_stream             zstream;
+    ngx_http_request_t  *request;
+} ngx_http_gunzip_ctx_t;
+
+
+static ngx_int_t ngx_http_gunzip_filter_inflate_start(ngx_http_request_t *r,
+    ngx_http_gunzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gunzip_filter_add_data(ngx_http_request_t *r,
+    ngx_http_gunzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gunzip_filter_get_buf(ngx_http_request_t *r,
+    ngx_http_gunzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gunzip_filter_inflate(ngx_http_request_t *r,
+    ngx_http_gunzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gunzip_filter_inflate_end(ngx_http_request_t *r,
+    ngx_http_gunzip_ctx_t *ctx);
+
+static void *ngx_http_gunzip_filter_alloc(void *opaque, u_int items,
+    u_int size);
+static void ngx_http_gunzip_filter_free(void *opaque, void *address);
+
+static ngx_int_t ngx_http_gunzip_filter_init(ngx_conf_t *cf);
+static void *ngx_http_gunzip_create_conf(ngx_conf_t *cf);
+static char *ngx_http_gunzip_merge_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+
+
+static ngx_command_t  ngx_http_gunzip_filter_commands[] = {
+
+    { ngx_string("gunzip"),
+      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_gunzip_conf_t, enable),
+      NULL },
+
+    { ngx_string("gunzip_buffers"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+      ngx_conf_set_bufs_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_gunzip_conf_t, bufs),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_gunzip_filter_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_gunzip_filter_init,           /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_gunzip_create_conf,           /* create location configuration */
+    ngx_http_gunzip_merge_conf             /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_gunzip_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_gunzip_filter_module_ctx,    /* module context */
+    ngx_http_gunzip_filter_commands,       /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_gunzip_header_filter(ngx_http_request_t *r)
+{
+    ngx_http_gunzip_ctx_t   *ctx;
+    ngx_http_gunzip_conf_t  *conf;
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_gunzip_filter_module);
+
+    /* TODO support multiple content-codings */
+    /* TODO always gunzip - due to configuration or module request */
+    /* TODO ignore content encoding? */
+
+    if (!conf->enable
+        || r->headers_out.content_encoding == NULL
+        || r->headers_out.content_encoding->value.len != 4
+        || ngx_strncasecmp(r->headers_out.content_encoding->value.data,
+                           (u_char *) "gzip", 4) != 0)
+    {
+        return ngx_http_next_header_filter(r);
+    }
+
+    r->gzip_vary = 1;
+
+    if (!r->gzip_tested) {
+        if (ngx_http_gzip_ok(r) == NGX_OK) {
+            return ngx_http_next_header_filter(r);
+        }
+
+    } else if (r->gzip_ok) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_gunzip_ctx_t));
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_http_set_ctx(r, ctx, ngx_http_gunzip_filter_module);
+
+    ctx->request = r;
+
+    r->filter_need_in_memory = 1;
+
+    r->headers_out.content_encoding->hash = 0;
+    r->headers_out.content_encoding = NULL;
+
+    ngx_http_clear_content_length(r);
+    ngx_http_clear_accept_ranges(r);
+    ngx_http_weak_etag(r);
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_gunzip_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    int                     rc;
+    ngx_uint_t              flush;
+    ngx_chain_t            *cl;
+    ngx_http_gunzip_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_gunzip_filter_module);
+
+    if (ctx == NULL || ctx->done) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http gunzip filter");
+
+    if (!ctx->started) {
+        if (ngx_http_gunzip_filter_inflate_start(r, ctx) != NGX_OK) {
+            goto failed;
+        }
+    }
+
+    if (in) {
+        if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) {
+            goto failed;
+        }
+    }
+
+    if (ctx->nomem) {
+
+        /* flush busy buffers */
+
+        if (ngx_http_next_body_filter(r, NULL) == NGX_ERROR) {
+            goto failed;
+        }
+
+        cl = NULL;
+
+        ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &cl,
+                                (ngx_buf_tag_t) &ngx_http_gunzip_filter_module);
+        ctx->nomem = 0;
+        flush = 0;
+
+    } else {
+        flush = ctx->busy ? 1 : 0;
+    }
+
+    for ( ;; ) {
+
+        /* cycle while we can write to a client */
+
+        for ( ;; ) {
+
+            /* cycle while there is data to feed zlib and ... */
+
+            rc = ngx_http_gunzip_filter_add_data(r, ctx);
+
+            if (rc == NGX_DECLINED) {
+                break;
+            }
+
+            if (rc == NGX_AGAIN) {
+                continue;
+            }
+
+
+            /* ... there are buffers to write zlib output */
+
+            rc = ngx_http_gunzip_filter_get_buf(r, ctx);
+
+            if (rc == NGX_DECLINED) {
+                break;
+            }
+
+            if (rc == NGX_ERROR) {
+                goto failed;
+            }
+
+            rc = ngx_http_gunzip_filter_inflate(r, ctx);
+
+            if (rc == NGX_OK) {
+                break;
+            }
+
+            if (rc == NGX_ERROR) {
+                goto failed;
+            }
+
+            /* rc == NGX_AGAIN */
+        }
+
+        if (ctx->out == NULL && !flush) {
+            return ctx->busy ? NGX_AGAIN : NGX_OK;
+        }
+
+        rc = ngx_http_next_body_filter(r, ctx->out);
+
+        if (rc == NGX_ERROR) {
+            goto failed;
+        }
+
+        ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &ctx->out,
+                                (ngx_buf_tag_t) &ngx_http_gunzip_filter_module);
+        ctx->last_out = &ctx->out;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "gunzip out: %p", ctx->out);
+
+        ctx->nomem = 0;
+        flush = 0;
+
+        if (ctx->done) {
+            return rc;
+        }
+    }
+
+    /* unreachable */
+
+failed:
+
+    ctx->done = 1;
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_gunzip_filter_inflate_start(ngx_http_request_t *r,
+    ngx_http_gunzip_ctx_t *ctx)
+{
+    int  rc;
+
+    ctx->zstream.next_in = Z_NULL;
+    ctx->zstream.avail_in = 0;
+
+    ctx->zstream.zalloc = ngx_http_gunzip_filter_alloc;
+    ctx->zstream.zfree = ngx_http_gunzip_filter_free;
+    ctx->zstream.opaque = ctx;
+
+    /* windowBits +16 to decode gzip, zlib 1.2.0.4+ */
+    rc = inflateInit2(&ctx->zstream, MAX_WBITS + 16);
+
+    if (rc != Z_OK) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                      "inflateInit2() failed: %d", rc);
+        return NGX_ERROR;
+    }
+
+    ctx->started = 1;
+
+    ctx->last_out = &ctx->out;
+    ctx->flush = Z_NO_FLUSH;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gunzip_filter_add_data(ngx_http_request_t *r,
+    ngx_http_gunzip_ctx_t *ctx)
+{
+    if (ctx->zstream.avail_in || ctx->flush != Z_NO_FLUSH || ctx->redo) {
+        return NGX_OK;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "gunzip in: %p", ctx->in);
+
+    if (ctx->in == NULL) {
+        return NGX_DECLINED;
+    }
+
+    ctx->in_buf = ctx->in->buf;
+    ctx->in = ctx->in->next;
+
+    ctx->zstream.next_in = ctx->in_buf->pos;
+    ctx->zstream.avail_in = ctx->in_buf->last - ctx->in_buf->pos;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "gunzip in_buf:%p ni:%p ai:%ud",
+                   ctx->in_buf,
+                   ctx->zstream.next_in, ctx->zstream.avail_in);
+
+    if (ctx->in_buf->last_buf || ctx->in_buf->last_in_chain) {
+        ctx->flush = Z_FINISH;
+
+    } else if (ctx->in_buf->flush) {
+        ctx->flush = Z_SYNC_FLUSH;
+
+    } else if (ctx->zstream.avail_in == 0) {
+        /* ctx->flush == Z_NO_FLUSH */
+        return NGX_AGAIN;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gunzip_filter_get_buf(ngx_http_request_t *r,
+    ngx_http_gunzip_ctx_t *ctx)
+{
+    ngx_http_gunzip_conf_t  *conf;
+
+    if (ctx->zstream.avail_out) {
+        return NGX_OK;
+    }
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_gunzip_filter_module);
+
+    if (ctx->free) {
+        ctx->out_buf = ctx->free->buf;
+        ctx->free = ctx->free->next;
+
+        ctx->out_buf->flush = 0;
+
+    } else if (ctx->bufs < conf->bufs.num) {
+
+        ctx->out_buf = ngx_create_temp_buf(r->pool, conf->bufs.size);
+        if (ctx->out_buf == NULL) {
+            return NGX_ERROR;
+        }
+
+        ctx->out_buf->tag = (ngx_buf_tag_t) &ngx_http_gunzip_filter_module;
+        ctx->out_buf->recycled = 1;
+        ctx->bufs++;
+
+    } else {
+        ctx->nomem = 1;
+        return NGX_DECLINED;
+    }
+
+    ctx->zstream.next_out = ctx->out_buf->pos;
+    ctx->zstream.avail_out = conf->bufs.size;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gunzip_filter_inflate(ngx_http_request_t *r,
+    ngx_http_gunzip_ctx_t *ctx)
+{
+    int           rc;
+    ngx_buf_t    *b;
+    ngx_chain_t  *cl;
+
+    ngx_log_debug6(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "inflate in: ni:%p no:%p ai:%ud ao:%ud fl:%d redo:%d",
+                   ctx->zstream.next_in, ctx->zstream.next_out,
+                   ctx->zstream.avail_in, ctx->zstream.avail_out,
+                   ctx->flush, ctx->redo);
+
+    rc = inflate(&ctx->zstream, ctx->flush);
+
+    if (rc != Z_OK && rc != Z_STREAM_END && rc != Z_BUF_ERROR) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "inflate() failed: %d, %d", ctx->flush, rc);
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d",
+                   ctx->zstream.next_in, ctx->zstream.next_out,
+                   ctx->zstream.avail_in, ctx->zstream.avail_out,
+                   rc);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "gunzip in_buf:%p pos:%p",
+                   ctx->in_buf, ctx->in_buf->pos);
+
+    if (ctx->zstream.next_in) {
+        ctx->in_buf->pos = ctx->zstream.next_in;
+
+        if (ctx->zstream.avail_in == 0) {
+            ctx->zstream.next_in = NULL;
+        }
+    }
+
+    ctx->out_buf->last = ctx->zstream.next_out;
+
+    if (ctx->zstream.avail_out == 0) {
+
+        /* zlib wants to output some more data */
+
+        cl = ngx_alloc_chain_link(r->pool);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+        cl->buf = ctx->out_buf;
+        cl->next = NULL;
+        *ctx->last_out = cl;
+        ctx->last_out = &cl->next;
+
+        ctx->redo = 1;
+
+        return NGX_AGAIN;
+    }
+
+    ctx->redo = 0;
+
+    if (ctx->flush == Z_SYNC_FLUSH) {
+
+        ctx->flush = Z_NO_FLUSH;
+
+        cl = ngx_alloc_chain_link(r->pool);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+        b = ctx->out_buf;
+
+        if (ngx_buf_size(b) == 0) {
+
+            b = ngx_calloc_buf(ctx->request->pool);
+            if (b == NULL) {
+                return NGX_ERROR;
+            }
+
+        } else {
+            ctx->zstream.avail_out = 0;
+        }
+
+        b->flush = 1;
+
+        cl->buf = b;
+        cl->next = NULL;
+        *ctx->last_out = cl;
+        ctx->last_out = &cl->next;
+
+        return NGX_OK;
+    }
+
+    if (ctx->flush == Z_FINISH && ctx->zstream.avail_in == 0) {
+
+        if (rc != Z_STREAM_END) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "inflate() returned %d on response end", rc);
+            return NGX_ERROR;
+        }
+
+        if (ngx_http_gunzip_filter_inflate_end(r, ctx) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        return NGX_OK;
+    }
+
+    if (rc == Z_STREAM_END && ctx->zstream.avail_in > 0) {
+
+        rc = inflateReset(&ctx->zstream);
+
+        if (rc != Z_OK) {
+            ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                          "inflateReset() failed: %d", rc);
+            return NGX_ERROR;
+        }
+
+        ctx->redo = 1;
+
+        return NGX_AGAIN;
+    }
+
+    if (ctx->in == NULL) {
+
+        b = ctx->out_buf;
+
+        if (ngx_buf_size(b) == 0) {
+            return NGX_OK;
+        }
+
+        cl = ngx_alloc_chain_link(r->pool);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+        ctx->zstream.avail_out = 0;
+
+        cl->buf = b;
+        cl->next = NULL;
+        *ctx->last_out = cl;
+        ctx->last_out = &cl->next;
+
+        return NGX_OK;
+    }
+
+    return NGX_AGAIN;
+}
+
+
+static ngx_int_t
+ngx_http_gunzip_filter_inflate_end(ngx_http_request_t *r,
+    ngx_http_gunzip_ctx_t *ctx)
+{
+    int           rc;
+    ngx_buf_t    *b;
+    ngx_chain_t  *cl;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "gunzip inflate end");
+
+    rc = inflateEnd(&ctx->zstream);
+
+    if (rc != Z_OK) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                      "inflateEnd() failed: %d", rc);
+        return NGX_ERROR;
+    }
+
+    b = ctx->out_buf;
+
+    if (ngx_buf_size(b) == 0) {
+
+        b = ngx_calloc_buf(ctx->request->pool);
+        if (b == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    cl = ngx_alloc_chain_link(r->pool);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl->buf = b;
+    cl->next = NULL;
+    *ctx->last_out = cl;
+    ctx->last_out = &cl->next;
+
+    b->last_buf = (r == r->main) ? 1 : 0;
+    b->last_in_chain = 1;
+    b->sync = 1;
+
+    ctx->done = 1;
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_gunzip_filter_alloc(void *opaque, u_int items, u_int size)
+{
+    ngx_http_gunzip_ctx_t *ctx = opaque;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
+                   "gunzip alloc: n:%ud s:%ud",
+                   items, size);
+
+    return ngx_palloc(ctx->request->pool, items * size);
+}
+
+
+static void
+ngx_http_gunzip_filter_free(void *opaque, void *address)
+{
+#if 0
+    ngx_http_gunzip_ctx_t *ctx = opaque;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
+                   "gunzip free: %p", address);
+#endif
+}
+
+
+static void *
+ngx_http_gunzip_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_gunzip_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_gunzip_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->bufs.num = 0;
+     */
+
+    conf->enable = NGX_CONF_UNSET;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_gunzip_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_gunzip_conf_t *prev = parent;
+    ngx_http_gunzip_conf_t *conf = child;
+
+    ngx_conf_merge_value(conf->enable, prev->enable, 0);
+
+    ngx_conf_merge_bufs_value(conf->bufs, prev->bufs,
+                              (128 * 1024) / ngx_pagesize, ngx_pagesize);
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gunzip_filter_init(ngx_conf_t *cf)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_gunzip_header_filter;
+
+    ngx_http_next_body_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_gunzip_body_filter;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_gzip_filter_module.c b/nginx/src/http/modules/ngx_http_gzip_filter_module.c
new file mode 100644 (file)
index 0000000..e4c343c
--- /dev/null
@@ -0,0 +1,1273 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#include <zlib.h>
+
+
+typedef struct {
+    ngx_flag_t           enable;
+    ngx_flag_t           no_buffer;
+
+    ngx_hash_t           types;
+
+    ngx_bufs_t           bufs;
+
+    size_t               postpone_gzipping;
+    ngx_int_t            level;
+    size_t               wbits;
+    size_t               memlevel;
+    ssize_t              min_length;
+
+    ngx_array_t         *types_keys;
+} ngx_http_gzip_conf_t;
+
+
+typedef struct {
+    ngx_chain_t         *in;
+    ngx_chain_t         *free;
+    ngx_chain_t         *busy;
+    ngx_chain_t         *out;
+    ngx_chain_t        **last_out;
+
+    ngx_chain_t         *copied;
+    ngx_chain_t         *copy_buf;
+
+    ngx_buf_t           *in_buf;
+    ngx_buf_t           *out_buf;
+    ngx_int_t            bufs;
+
+    void                *preallocated;
+    char                *free_mem;
+    ngx_uint_t           allocated;
+
+    int                  wbits;
+    int                  memlevel;
+
+    unsigned             flush:4;
+    unsigned             redo:1;
+    unsigned             done:1;
+    unsigned             nomem:1;
+    unsigned             gzheader:1;
+    unsigned             buffering:1;
+    unsigned             intel:1;
+
+    size_t               zin;
+    size_t               zout;
+
+    uint32_t             crc32;
+    z_stream             zstream;
+    ngx_http_request_t  *request;
+} ngx_http_gzip_ctx_t;
+
+
+#if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED)
+
+struct gztrailer {
+    uint32_t  crc32;
+    uint32_t  zlen;
+};
+
+#else /* NGX_HAVE_BIG_ENDIAN || !NGX_HAVE_NONALIGNED */
+
+struct gztrailer {
+    u_char  crc32[4];
+    u_char  zlen[4];
+};
+
+#endif
+
+
+static void ngx_http_gzip_filter_memory(ngx_http_request_t *r,
+    ngx_http_gzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gzip_filter_buffer(ngx_http_gzip_ctx_t *ctx,
+    ngx_chain_t *in);
+static ngx_int_t ngx_http_gzip_filter_deflate_start(ngx_http_request_t *r,
+    ngx_http_gzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gzip_filter_gzheader(ngx_http_request_t *r,
+    ngx_http_gzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gzip_filter_add_data(ngx_http_request_t *r,
+    ngx_http_gzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gzip_filter_get_buf(ngx_http_request_t *r,
+    ngx_http_gzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gzip_filter_deflate(ngx_http_request_t *r,
+    ngx_http_gzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gzip_filter_deflate_end(ngx_http_request_t *r,
+    ngx_http_gzip_ctx_t *ctx);
+
+static void *ngx_http_gzip_filter_alloc(void *opaque, u_int items,
+    u_int size);
+static void ngx_http_gzip_filter_free(void *opaque, void *address);
+static void ngx_http_gzip_filter_free_copy_buf(ngx_http_request_t *r,
+    ngx_http_gzip_ctx_t *ctx);
+
+static ngx_int_t ngx_http_gzip_add_variables(ngx_conf_t *cf);
+static ngx_int_t ngx_http_gzip_ratio_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+
+static ngx_int_t ngx_http_gzip_filter_init(ngx_conf_t *cf);
+static void *ngx_http_gzip_create_conf(ngx_conf_t *cf);
+static char *ngx_http_gzip_merge_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+static char *ngx_http_gzip_window(ngx_conf_t *cf, void *post, void *data);
+static char *ngx_http_gzip_hash(ngx_conf_t *cf, void *post, void *data);
+
+
+static ngx_conf_num_bounds_t  ngx_http_gzip_comp_level_bounds = {
+    ngx_conf_check_num_bounds, 1, 9
+};
+
+static ngx_conf_post_handler_pt  ngx_http_gzip_window_p = ngx_http_gzip_window;
+static ngx_conf_post_handler_pt  ngx_http_gzip_hash_p = ngx_http_gzip_hash;
+
+
+static ngx_command_t  ngx_http_gzip_filter_commands[] = {
+
+    { ngx_string("gzip"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+                        |NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_gzip_conf_t, enable),
+      NULL },
+
+    { ngx_string("gzip_buffers"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+      ngx_conf_set_bufs_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_gzip_conf_t, bufs),
+      NULL },
+
+    { ngx_string("gzip_types"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_types_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_gzip_conf_t, types_keys),
+      &ngx_http_html_default_types[0] },
+
+    { ngx_string("gzip_comp_level"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_gzip_conf_t, level),
+      &ngx_http_gzip_comp_level_bounds },
+
+    { ngx_string("gzip_window"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_gzip_conf_t, wbits),
+      &ngx_http_gzip_window_p },
+
+    { ngx_string("gzip_hash"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_gzip_conf_t, memlevel),
+      &ngx_http_gzip_hash_p },
+
+    { ngx_string("postpone_gzipping"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_gzip_conf_t, postpone_gzipping),
+      NULL },
+
+    { ngx_string("gzip_no_buffer"),
+      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_gzip_conf_t, no_buffer),
+      NULL },
+
+    { ngx_string("gzip_min_length"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_gzip_conf_t, min_length),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_gzip_filter_module_ctx = {
+    ngx_http_gzip_add_variables,           /* preconfiguration */
+    ngx_http_gzip_filter_init,             /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_gzip_create_conf,             /* create location configuration */
+    ngx_http_gzip_merge_conf               /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_gzip_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_gzip_filter_module_ctx,      /* module context */
+    ngx_http_gzip_filter_commands,         /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static 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_int_t
+ngx_http_gzip_header_filter(ngx_http_request_t *r)
+{
+    ngx_table_elt_t       *h;
+    ngx_http_gzip_ctx_t   *ctx;
+    ngx_http_gzip_conf_t  *conf;
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
+
+    if (!conf->enable
+        || (r->headers_out.status != NGX_HTTP_OK
+            && r->headers_out.status != NGX_HTTP_FORBIDDEN
+            && r->headers_out.status != NGX_HTTP_NOT_FOUND)
+        || (r->headers_out.content_encoding
+            && r->headers_out.content_encoding->value.len)
+        || (r->headers_out.content_length_n != -1
+            && r->headers_out.content_length_n < conf->min_length)
+        || ngx_http_test_content_type(r, &conf->types) == NULL
+        || r->header_only)
+    {
+        return ngx_http_next_header_filter(r);
+    }
+
+    r->gzip_vary = 1;
+
+#if (NGX_HTTP_DEGRADATION)
+    {
+    ngx_http_core_loc_conf_t  *clcf;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (clcf->gzip_disable_degradation && ngx_http_degraded(r)) {
+        return ngx_http_next_header_filter(r);
+    }
+    }
+#endif
+
+    if (!r->gzip_tested) {
+        if (ngx_http_gzip_ok(r) != NGX_OK) {
+            return ngx_http_next_header_filter(r);
+        }
+
+    } else if (!r->gzip_ok) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_gzip_ctx_t));
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_http_set_ctx(r, ctx, ngx_http_gzip_filter_module);
+
+    ctx->request = r;
+    ctx->buffering = (conf->postpone_gzipping != 0);
+
+    ngx_http_gzip_filter_memory(r, ctx);
+
+    h = ngx_list_push(&r->headers_out.headers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    h->hash = 1;
+    ngx_str_set(&h->key, "Content-Encoding");
+    ngx_str_set(&h->value, "gzip");
+    r->headers_out.content_encoding = h;
+
+    r->main_filter_need_in_memory = 1;
+
+    ngx_http_clear_content_length(r);
+    ngx_http_clear_accept_ranges(r);
+    ngx_http_weak_etag(r);
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_gzip_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    int                   rc;
+    ngx_uint_t            flush;
+    ngx_chain_t          *cl;
+    ngx_http_gzip_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_gzip_filter_module);
+
+    if (ctx == NULL || ctx->done || r->header_only) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http gzip filter");
+
+    if (ctx->buffering) {
+
+        /*
+         * With default memory settings zlib starts to output gzipped data
+         * only after it has got about 90K, so it makes sense to allocate
+         * zlib memory (200-400K) only after we have enough data to compress.
+         * Although we copy buffers, nevertheless for not big responses
+         * this allows to allocate zlib memory, to compress and to output
+         * the response in one step using hot CPU cache.
+         */
+
+        if (in) {
+            switch (ngx_http_gzip_filter_buffer(ctx, in)) {
+
+            case NGX_OK:
+                return NGX_OK;
+
+            case NGX_DONE:
+                in = NULL;
+                break;
+
+            default:  /* NGX_ERROR */
+                goto failed;
+            }
+
+        } else {
+            ctx->buffering = 0;
+        }
+    }
+
+    if (ctx->preallocated == NULL) {
+        if (ngx_http_gzip_filter_deflate_start(r, ctx) != NGX_OK) {
+            goto failed;
+        }
+    }
+
+    if (in) {
+        if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) {
+            goto failed;
+        }
+
+        r->connection->buffered |= NGX_HTTP_GZIP_BUFFERED;
+    }
+
+    if (ctx->nomem) {
+
+        /* flush busy buffers */
+
+        if (ngx_http_next_body_filter(r, NULL) == NGX_ERROR) {
+            goto failed;
+        }
+
+        cl = NULL;
+
+        ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &cl,
+                                (ngx_buf_tag_t) &ngx_http_gzip_filter_module);
+        ctx->nomem = 0;
+        flush = 0;
+
+    } else {
+        flush = ctx->busy ? 1 : 0;
+    }
+
+    for ( ;; ) {
+
+        /* cycle while we can write to a client */
+
+        for ( ;; ) {
+
+            /* cycle while there is data to feed zlib and ... */
+
+            rc = ngx_http_gzip_filter_add_data(r, ctx);
+
+            if (rc == NGX_DECLINED) {
+                break;
+            }
+
+            if (rc == NGX_AGAIN) {
+                continue;
+            }
+
+
+            /* ... there are buffers to write zlib output */
+
+            rc = ngx_http_gzip_filter_get_buf(r, ctx);
+
+            if (rc == NGX_DECLINED) {
+                break;
+            }
+
+            if (rc == NGX_ERROR) {
+                goto failed;
+            }
+
+
+            rc = ngx_http_gzip_filter_deflate(r, ctx);
+
+            if (rc == NGX_OK) {
+                break;
+            }
+
+            if (rc == NGX_ERROR) {
+                goto failed;
+            }
+
+            /* rc == NGX_AGAIN */
+        }
+
+        if (ctx->out == NULL && !flush) {
+            ngx_http_gzip_filter_free_copy_buf(r, ctx);
+
+            return ctx->busy ? NGX_AGAIN : NGX_OK;
+        }
+
+        if (!ctx->gzheader) {
+            if (ngx_http_gzip_filter_gzheader(r, ctx) != NGX_OK) {
+                goto failed;
+            }
+        }
+
+        rc = ngx_http_next_body_filter(r, ctx->out);
+
+        if (rc == NGX_ERROR) {
+            goto failed;
+        }
+
+        ngx_http_gzip_filter_free_copy_buf(r, ctx);
+
+        ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &ctx->out,
+                                (ngx_buf_tag_t) &ngx_http_gzip_filter_module);
+        ctx->last_out = &ctx->out;
+
+        ctx->nomem = 0;
+        flush = 0;
+
+        if (ctx->done) {
+            return rc;
+        }
+    }
+
+    /* unreachable */
+
+failed:
+
+    ctx->done = 1;
+
+    if (ctx->preallocated) {
+        deflateEnd(&ctx->zstream);
+
+        ngx_pfree(r->pool, ctx->preallocated);
+    }
+
+    ngx_http_gzip_filter_free_copy_buf(r, ctx);
+
+    return NGX_ERROR;
+}
+
+
+static void
+ngx_http_gzip_filter_memory(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
+{
+    int                    wbits, memlevel;
+    ngx_http_gzip_conf_t  *conf;
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
+
+    wbits = conf->wbits;
+    memlevel = conf->memlevel;
+
+    if (r->headers_out.content_length_n > 0) {
+
+        /* the actual zlib window size is smaller by 262 bytes */
+
+        while (r->headers_out.content_length_n < ((1 << (wbits - 1)) - 262)) {
+            wbits--;
+            memlevel--;
+        }
+
+        if (memlevel < 1) {
+            memlevel = 1;
+        }
+    }
+
+    ctx->wbits = wbits;
+    ctx->memlevel = memlevel;
+
+    /*
+     * We preallocate a memory for zlib in one buffer (200K-400K), this
+     * decreases a number of malloc() and free() calls and also probably
+     * decreases a number of syscalls (sbrk()/mmap() and so on).
+     * Besides we free the memory as soon as a gzipping will complete
+     * and do not wait while a whole response will be sent to a client.
+     *
+     * 8K is for zlib deflate_state, it takes
+     *  *) 5816 bytes on i386 and sparc64 (32-bit mode)
+     *  *) 5920 bytes on amd64 and sparc64
+     */
+
+    if (!ngx_http_gzip_assume_intel) {
+        ctx->allocated = 8192 + (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.
+         */
+
+        if (conf->level == 1) {
+            wbits = ngx_max(wbits, 13);
+        }
+
+        ctx->allocated = 8192 + 16 + (1 << (wbits + 2))
+                         + (1 << (ngx_max(memlevel, 8) + 8))
+                         + (1 << (memlevel + 8));
+        ctx->intel = 1;
+    }
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_buffer(ngx_http_gzip_ctx_t *ctx, ngx_chain_t *in)
+{
+    size_t                 size, buffered;
+    ngx_buf_t             *b, *buf;
+    ngx_chain_t           *cl, **ll;
+    ngx_http_request_t    *r;
+    ngx_http_gzip_conf_t  *conf;
+
+    r = ctx->request;
+
+    r->connection->buffered |= NGX_HTTP_GZIP_BUFFERED;
+
+    buffered = 0;
+    ll = &ctx->in;
+
+    for (cl = ctx->in; cl; cl = cl->next) {
+        buffered += cl->buf->last - cl->buf->pos;
+        ll = &cl->next;
+    }
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
+
+    while (in) {
+        cl = ngx_alloc_chain_link(r->pool);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+        b = in->buf;
+
+        size = b->last - b->pos;
+        buffered += size;
+
+        if (b->flush || b->last_buf || buffered > conf->postpone_gzipping) {
+            ctx->buffering = 0;
+        }
+
+        if (ctx->buffering && size) {
+
+            buf = ngx_create_temp_buf(r->pool, size);
+            if (buf == NULL) {
+                return NGX_ERROR;
+            }
+
+            buf->last = ngx_cpymem(buf->pos, b->pos, size);
+            b->pos = b->last;
+
+            buf->last_buf = b->last_buf;
+            buf->tag = (ngx_buf_tag_t) &ngx_http_gzip_filter_module;
+
+            cl->buf = buf;
+
+        } else {
+            cl->buf = b;
+        }
+
+        *ll = cl;
+        ll = &cl->next;
+        in = in->next;
+    }
+
+    *ll = NULL;
+
+    return ctx->buffering ? NGX_OK : NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_deflate_start(ngx_http_request_t *r,
+    ngx_http_gzip_ctx_t *ctx)
+{
+    int                    rc;
+    ngx_http_gzip_conf_t  *conf;
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
+
+    ctx->preallocated = ngx_palloc(r->pool, ctx->allocated);
+    if (ctx->preallocated == NULL) {
+        return NGX_ERROR;
+    }
+
+    ctx->free_mem = ctx->preallocated;
+
+    ctx->zstream.zalloc = ngx_http_gzip_filter_alloc;
+    ctx->zstream.zfree = ngx_http_gzip_filter_free;
+    ctx->zstream.opaque = ctx;
+
+    rc = deflateInit2(&ctx->zstream, (int) conf->level, Z_DEFLATED,
+                      - ctx->wbits, ctx->memlevel, Z_DEFAULT_STRATEGY);
+
+    if (rc != Z_OK) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                      "deflateInit2() failed: %d", rc);
+        return NGX_ERROR;
+    }
+
+    ctx->last_out = &ctx->out;
+    ctx->crc32 = crc32(0L, Z_NULL, 0);
+    ctx->flush = Z_NO_FLUSH;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_gzheader(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
+{
+    ngx_buf_t      *b;
+    ngx_chain_t    *cl;
+    static u_char  gzheader[10] =
+                               { 0x1f, 0x8b, Z_DEFLATED, 0, 0, 0, 0, 0, 0, 3 };
+
+    b = ngx_calloc_buf(r->pool);
+    if (b == NULL) {
+        return NGX_ERROR;
+    }
+
+    b->memory = 1;
+    b->pos = gzheader;
+    b->last = b->pos + 10;
+
+    cl = ngx_alloc_chain_link(r->pool);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl->buf = b;
+    cl->next = ctx->out;
+    ctx->out = cl;
+
+    ctx->gzheader = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_add_data(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
+{
+    ngx_chain_t  *cl;
+
+    if (ctx->zstream.avail_in || ctx->flush != Z_NO_FLUSH || ctx->redo) {
+        return NGX_OK;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "gzip in: %p", ctx->in);
+
+    if (ctx->in == NULL) {
+        return NGX_DECLINED;
+    }
+
+    if (ctx->copy_buf) {
+
+        /*
+         * to avoid CPU cache trashing we do not free() just quit buf,
+         * but postpone free()ing after zlib compressing and data output
+         */
+
+        ctx->copy_buf->next = ctx->copied;
+        ctx->copied = ctx->copy_buf;
+        ctx->copy_buf = NULL;
+    }
+
+    cl = ctx->in;
+    ctx->in_buf = cl->buf;
+    ctx->in = cl->next;
+
+    if (ctx->in_buf->tag == (ngx_buf_tag_t) &ngx_http_gzip_filter_module) {
+        ctx->copy_buf = cl;
+
+    } else {
+        ngx_free_chain(r->pool, cl);
+    }
+
+    ctx->zstream.next_in = ctx->in_buf->pos;
+    ctx->zstream.avail_in = ctx->in_buf->last - ctx->in_buf->pos;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "gzip in_buf:%p ni:%p ai:%ud",
+                   ctx->in_buf,
+                   ctx->zstream.next_in, ctx->zstream.avail_in);
+
+    if (ctx->in_buf->last_buf) {
+        ctx->flush = Z_FINISH;
+
+    } else if (ctx->in_buf->flush) {
+        ctx->flush = Z_SYNC_FLUSH;
+    }
+
+    if (ctx->zstream.avail_in) {
+
+        ctx->crc32 = crc32(ctx->crc32, ctx->zstream.next_in,
+                           ctx->zstream.avail_in);
+
+    } else if (ctx->flush == Z_NO_FLUSH) {
+        return NGX_AGAIN;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_get_buf(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
+{
+    ngx_chain_t           *cl;
+    ngx_http_gzip_conf_t  *conf;
+
+    if (ctx->zstream.avail_out) {
+        return NGX_OK;
+    }
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
+
+    if (ctx->free) {
+
+        cl = ctx->free;
+        ctx->out_buf = cl->buf;
+        ctx->free = cl->next;
+
+        ngx_free_chain(r->pool, cl);
+
+    } else if (ctx->bufs < conf->bufs.num) {
+
+        ctx->out_buf = ngx_create_temp_buf(r->pool, conf->bufs.size);
+        if (ctx->out_buf == NULL) {
+            return NGX_ERROR;
+        }
+
+        ctx->out_buf->tag = (ngx_buf_tag_t) &ngx_http_gzip_filter_module;
+        ctx->out_buf->recycled = 1;
+        ctx->bufs++;
+
+    } else {
+        ctx->nomem = 1;
+        return NGX_DECLINED;
+    }
+
+    ctx->zstream.next_out = ctx->out_buf->pos;
+    ctx->zstream.avail_out = conf->bufs.size;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_deflate(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
+{
+    int                    rc;
+    ngx_buf_t             *b;
+    ngx_chain_t           *cl;
+    ngx_http_gzip_conf_t  *conf;
+
+    ngx_log_debug6(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                 "deflate in: ni:%p no:%p ai:%ud ao:%ud fl:%d redo:%d",
+                 ctx->zstream.next_in, ctx->zstream.next_out,
+                 ctx->zstream.avail_in, ctx->zstream.avail_out,
+                 ctx->flush, ctx->redo);
+
+    rc = deflate(&ctx->zstream, ctx->flush);
+
+    if (rc != Z_OK && rc != Z_STREAM_END && rc != Z_BUF_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                      "deflate() failed: %d, %d", ctx->flush, rc);
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "deflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d",
+                   ctx->zstream.next_in, ctx->zstream.next_out,
+                   ctx->zstream.avail_in, ctx->zstream.avail_out,
+                   rc);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "gzip in_buf:%p pos:%p",
+                   ctx->in_buf, ctx->in_buf->pos);
+
+    if (ctx->zstream.next_in) {
+        ctx->in_buf->pos = ctx->zstream.next_in;
+
+        if (ctx->zstream.avail_in == 0) {
+            ctx->zstream.next_in = NULL;
+        }
+    }
+
+    ctx->out_buf->last = ctx->zstream.next_out;
+
+    if (ctx->zstream.avail_out == 0) {
+
+        /* zlib wants to output some more gzipped data */
+
+        cl = ngx_alloc_chain_link(r->pool);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+        cl->buf = ctx->out_buf;
+        cl->next = NULL;
+        *ctx->last_out = cl;
+        ctx->last_out = &cl->next;
+
+        ctx->redo = 1;
+
+        return NGX_AGAIN;
+    }
+
+    ctx->redo = 0;
+
+    if (ctx->flush == Z_SYNC_FLUSH) {
+
+        ctx->flush = Z_NO_FLUSH;
+
+        cl = ngx_alloc_chain_link(r->pool);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+        b = ctx->out_buf;
+
+        if (ngx_buf_size(b) == 0) {
+
+            b = ngx_calloc_buf(ctx->request->pool);
+            if (b == NULL) {
+                return NGX_ERROR;
+            }
+
+        } else {
+            ctx->zstream.avail_out = 0;
+        }
+
+        b->flush = 1;
+
+        cl->buf = b;
+        cl->next = NULL;
+        *ctx->last_out = cl;
+        ctx->last_out = &cl->next;
+
+        r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED;
+
+        return NGX_OK;
+    }
+
+    if (rc == Z_STREAM_END) {
+
+        if (ngx_http_gzip_filter_deflate_end(r, ctx) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        return NGX_OK;
+    }
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
+
+    if (conf->no_buffer && ctx->in == NULL) {
+
+        cl = ngx_alloc_chain_link(r->pool);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+        cl->buf = ctx->out_buf;
+        cl->next = NULL;
+        *ctx->last_out = cl;
+        ctx->last_out = &cl->next;
+
+        return NGX_OK;
+    }
+
+    return NGX_AGAIN;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_deflate_end(ngx_http_request_t *r,
+    ngx_http_gzip_ctx_t *ctx)
+{
+    int                rc;
+    ngx_buf_t         *b;
+    ngx_chain_t       *cl;
+    struct gztrailer  *trailer;
+
+    ctx->zin = ctx->zstream.total_in;
+    ctx->zout = 10 + ctx->zstream.total_out + 8;
+
+    rc = deflateEnd(&ctx->zstream);
+
+    if (rc != Z_OK) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                      "deflateEnd() failed: %d", rc);
+        return NGX_ERROR;
+    }
+
+    ngx_pfree(r->pool, ctx->preallocated);
+
+    cl = ngx_alloc_chain_link(r->pool);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl->buf = ctx->out_buf;
+    cl->next = NULL;
+    *ctx->last_out = cl;
+    ctx->last_out = &cl->next;
+
+    if (ctx->zstream.avail_out >= 8) {
+        trailer = (struct gztrailer *) ctx->out_buf->last;
+        ctx->out_buf->last += 8;
+        ctx->out_buf->last_buf = 1;
+
+    } else {
+        b = ngx_create_temp_buf(r->pool, 8);
+        if (b == NULL) {
+            return NGX_ERROR;
+        }
+
+        b->last_buf = 1;
+
+        cl = ngx_alloc_chain_link(r->pool);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+        cl->buf = b;
+        cl->next = NULL;
+        *ctx->last_out = cl;
+        ctx->last_out = &cl->next;
+        trailer = (struct gztrailer *) b->pos;
+        b->last += 8;
+    }
+
+#if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED)
+
+    trailer->crc32 = ctx->crc32;
+    trailer->zlen = ctx->zin;
+
+#else
+
+    trailer->crc32[0] = (u_char) (ctx->crc32 & 0xff);
+    trailer->crc32[1] = (u_char) ((ctx->crc32 >> 8) & 0xff);
+    trailer->crc32[2] = (u_char) ((ctx->crc32 >> 16) & 0xff);
+    trailer->crc32[3] = (u_char) ((ctx->crc32 >> 24) & 0xff);
+
+    trailer->zlen[0] = (u_char) (ctx->zin & 0xff);
+    trailer->zlen[1] = (u_char) ((ctx->zin >> 8) & 0xff);
+    trailer->zlen[2] = (u_char) ((ctx->zin >> 16) & 0xff);
+    trailer->zlen[3] = (u_char) ((ctx->zin >> 24) & 0xff);
+
+#endif
+
+    ctx->zstream.avail_in = 0;
+    ctx->zstream.avail_out = 0;
+
+    ctx->done = 1;
+
+    r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED;
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_gzip_filter_alloc(void *opaque, u_int items, u_int size)
+{
+    ngx_http_gzip_ctx_t *ctx = opaque;
+
+    void        *p;
+    ngx_uint_t   alloc;
+
+    alloc = items * size;
+
+    if (items == 1 && alloc % 512 != 0 && alloc < 8192) {
+
+        /*
+         * The zlib deflate_state allocation, it takes about 6K,
+         * we allocate 8K.  Other allocations are divisible by 512.
+         */
+
+        alloc = 8192;
+    }
+
+    if (alloc <= ctx->allocated) {
+        p = ctx->free_mem;
+        ctx->free_mem += alloc;
+        ctx->allocated -= alloc;
+
+        ngx_log_debug4(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
+                       "gzip alloc: n:%ud s:%ud a:%ui p:%p",
+                       items, size, alloc, p);
+
+        return p;
+    }
+
+    if (ctx->intel) {
+        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;
+    }
+
+    p = ngx_palloc(ctx->request->pool, items * size);
+
+    return p;
+}
+
+
+static void
+ngx_http_gzip_filter_free(void *opaque, void *address)
+{
+#if 0
+    ngx_http_gzip_ctx_t *ctx = opaque;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
+                   "gzip free: %p", address);
+#endif
+}
+
+
+static void
+ngx_http_gzip_filter_free_copy_buf(ngx_http_request_t *r,
+    ngx_http_gzip_ctx_t *ctx)
+{
+    ngx_chain_t  *cl;
+
+    for (cl = ctx->copied; cl; cl = cl->next) {
+        ngx_pfree(r->pool, cl->buf->start);
+    }
+
+    ctx->copied = NULL;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_add_variables(ngx_conf_t *cf)
+{
+    ngx_http_variable_t  *var;
+
+    var = ngx_http_add_variable(cf, &ngx_http_gzip_ratio, NGX_HTTP_VAR_NOHASH);
+    if (var == NULL) {
+        return NGX_ERROR;
+    }
+
+    var->get_handler = ngx_http_gzip_ratio_variable;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_ratio_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_uint_t            zint, zfrac;
+    ngx_http_gzip_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_gzip_filter_module);
+
+    if (ctx == NULL || ctx->zout == 0) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    v->data = ngx_pnalloc(r->pool, NGX_INT32_LEN + 3);
+    if (v->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    zint = (ngx_uint_t) (ctx->zin / ctx->zout);
+    zfrac = (ngx_uint_t) ((ctx->zin * 100 / ctx->zout) % 100);
+
+    if ((ctx->zin * 1000 / ctx->zout) % 10 > 4) {
+
+        /* the rounding, e.g., 2.125 to 2.13 */
+
+        zfrac++;
+
+        if (zfrac > 99) {
+            zint++;
+            zfrac = 0;
+        }
+    }
+
+    v->len = ngx_sprintf(v->data, "%ui.%02ui", zint, zfrac) - v->data;
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_gzip_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_gzip_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_gzip_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->bufs.num = 0;
+     *     conf->types = { NULL };
+     *     conf->types_keys = NULL;
+     */
+
+    conf->enable = NGX_CONF_UNSET;
+    conf->no_buffer = NGX_CONF_UNSET;
+
+    conf->postpone_gzipping = NGX_CONF_UNSET_SIZE;
+    conf->level = NGX_CONF_UNSET;
+    conf->wbits = NGX_CONF_UNSET_SIZE;
+    conf->memlevel = NGX_CONF_UNSET_SIZE;
+    conf->min_length = NGX_CONF_UNSET;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_gzip_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_gzip_conf_t *prev = parent;
+    ngx_http_gzip_conf_t *conf = child;
+
+    ngx_conf_merge_value(conf->enable, prev->enable, 0);
+    ngx_conf_merge_value(conf->no_buffer, prev->no_buffer, 0);
+
+    ngx_conf_merge_bufs_value(conf->bufs, prev->bufs,
+                              (128 * 1024) / ngx_pagesize, ngx_pagesize);
+
+    ngx_conf_merge_size_value(conf->postpone_gzipping, prev->postpone_gzipping,
+                              0);
+    ngx_conf_merge_value(conf->level, prev->level, 1);
+    ngx_conf_merge_size_value(conf->wbits, prev->wbits, MAX_WBITS);
+    ngx_conf_merge_size_value(conf->memlevel, prev->memlevel,
+                              MAX_MEM_LEVEL - 1);
+    ngx_conf_merge_value(conf->min_length, prev->min_length, 20);
+
+    if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types,
+                             &prev->types_keys, &prev->types,
+                             ngx_http_html_default_types)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_init(ngx_conf_t *cf)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_gzip_header_filter;
+
+    ngx_http_next_body_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_gzip_body_filter;
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_http_gzip_window(ngx_conf_t *cf, void *post, void *data)
+{
+    size_t *np = data;
+
+    size_t  wbits, wsize;
+
+    wbits = 15;
+
+    for (wsize = 32 * 1024; wsize > 256; wsize >>= 1) {
+
+        if (wsize == *np) {
+            *np = wbits;
+
+            return NGX_CONF_OK;
+        }
+
+        wbits--;
+    }
+
+    return "must be 512, 1k, 2k, 4k, 8k, 16k, or 32k";
+}
+
+
+static char *
+ngx_http_gzip_hash(ngx_conf_t *cf, void *post, void *data)
+{
+    size_t *np = data;
+
+    size_t  memlevel, hsize;
+
+    memlevel = 9;
+
+    for (hsize = 128 * 1024; hsize > 256; hsize >>= 1) {
+
+        if (hsize == *np) {
+            *np = memlevel;
+
+            return NGX_CONF_OK;
+        }
+
+        memlevel--;
+    }
+
+    return "must be 512, 1k, 2k, 4k, 8k, 16k, 32k, 64k, or 128k";
+}
diff --git a/nginx/src/http/modules/ngx_http_gzip_static_module.c b/nginx/src/http/modules/ngx_http_gzip_static_module.c
new file mode 100644 (file)
index 0000000..7652a9a
--- /dev/null
@@ -0,0 +1,331 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_GZIP_STATIC_OFF     0
+#define NGX_HTTP_GZIP_STATIC_ON      1
+#define NGX_HTTP_GZIP_STATIC_ALWAYS  2
+
+
+typedef struct {
+    ngx_uint_t  enable;
+} ngx_http_gzip_static_conf_t;
+
+
+static ngx_int_t ngx_http_gzip_static_handler(ngx_http_request_t *r);
+static void *ngx_http_gzip_static_create_conf(ngx_conf_t *cf);
+static char *ngx_http_gzip_static_merge_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static ngx_int_t ngx_http_gzip_static_init(ngx_conf_t *cf);
+
+
+static ngx_conf_enum_t  ngx_http_gzip_static[] = {
+    { ngx_string("off"), NGX_HTTP_GZIP_STATIC_OFF },
+    { ngx_string("on"), NGX_HTTP_GZIP_STATIC_ON },
+    { ngx_string("always"), NGX_HTTP_GZIP_STATIC_ALWAYS },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t  ngx_http_gzip_static_commands[] = {
+
+    { ngx_string("gzip_static"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_gzip_static_conf_t, enable),
+      &ngx_http_gzip_static },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_gzip_static_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_gzip_static_init,             /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_gzip_static_create_conf,      /* create location configuration */
+    ngx_http_gzip_static_merge_conf        /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_gzip_static_module = {
+    NGX_MODULE_V1,
+    &ngx_http_gzip_static_module_ctx,      /* module context */
+    ngx_http_gzip_static_commands,         /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_gzip_static_handler(ngx_http_request_t *r)
+{
+    u_char                       *p;
+    size_t                        root;
+    ngx_str_t                     path;
+    ngx_int_t                     rc;
+    ngx_uint_t                    level;
+    ngx_log_t                    *log;
+    ngx_buf_t                    *b;
+    ngx_chain_t                   out;
+    ngx_table_elt_t              *h;
+    ngx_open_file_info_t          of;
+    ngx_http_core_loc_conf_t     *clcf;
+    ngx_http_gzip_static_conf_t  *gzcf;
+
+    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
+        return NGX_DECLINED;
+    }
+
+    if (r->uri.data[r->uri.len - 1] == '/') {
+        return NGX_DECLINED;
+    }
+
+    gzcf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_static_module);
+
+    if (gzcf->enable == NGX_HTTP_GZIP_STATIC_OFF) {
+        return NGX_DECLINED;
+    }
+
+    if (gzcf->enable == NGX_HTTP_GZIP_STATIC_ON) {
+        rc = ngx_http_gzip_ok(r);
+
+    } else {
+        /* always */
+        rc = NGX_OK;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (!clcf->gzip_vary && rc != NGX_OK) {
+        return NGX_DECLINED;
+    }
+
+    log = r->connection->log;
+
+    p = ngx_http_map_uri_to_path(r, &path, &root, sizeof(".gz") - 1);
+    if (p == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    *p++ = '.';
+    *p++ = 'g';
+    *p++ = 'z';
+    *p = '\0';
+
+    path.len = p - path.data;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+                   "http filename: \"%s\"", path.data);
+
+    ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+    of.read_ahead = clcf->read_ahead;
+    of.directio = clcf->directio;
+    of.valid = clcf->open_file_cache_valid;
+    of.min_uses = clcf->open_file_cache_min_uses;
+    of.errors = clcf->open_file_cache_errors;
+    of.events = clcf->open_file_cache_events;
+
+    if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
+        != NGX_OK)
+    {
+        switch (of.err) {
+
+        case 0:
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+
+        case NGX_ENOENT:
+        case NGX_ENOTDIR:
+        case NGX_ENAMETOOLONG:
+
+            return NGX_DECLINED;
+
+        case NGX_EACCES:
+#if (NGX_HAVE_OPENAT)
+        case NGX_EMLINK:
+        case NGX_ELOOP:
+#endif
+
+            level = NGX_LOG_ERR;
+            break;
+
+        default:
+
+            level = NGX_LOG_CRIT;
+            break;
+        }
+
+        ngx_log_error(level, log, of.err,
+                      "%s \"%s\" failed", of.failed, path.data);
+
+        return NGX_DECLINED;
+    }
+
+    if (gzcf->enable == NGX_HTTP_GZIP_STATIC_ON) {
+        r->gzip_vary = 1;
+
+        if (rc != NGX_OK) {
+            return NGX_DECLINED;
+        }
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http static fd: %d", of.fd);
+
+    if (of.is_dir) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http dir");
+        return NGX_DECLINED;
+    }
+
+#if !(NGX_WIN32) /* the not regular files are probably Unix specific */
+
+    if (!of.is_file) {
+        ngx_log_error(NGX_LOG_CRIT, log, 0,
+                      "\"%s\" is not a regular file", path.data);
+
+        return NGX_HTTP_NOT_FOUND;
+    }
+
+#endif
+
+    r->root_tested = !r->error_page;
+
+    rc = ngx_http_discard_request_body(r);
+
+    if (rc != NGX_OK) {
+        return rc;
+    }
+
+    log->action = "sending response to client";
+
+    r->headers_out.status = NGX_HTTP_OK;
+    r->headers_out.content_length_n = of.size;
+    r->headers_out.last_modified_time = of.mtime;
+
+    if (ngx_http_set_etag(r) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (ngx_http_set_content_type(r) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    h = ngx_list_push(&r->headers_out.headers);
+    if (h == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    h->hash = 1;
+    ngx_str_set(&h->key, "Content-Encoding");
+    ngx_str_set(&h->value, "gzip");
+    r->headers_out.content_encoding = h;
+
+    /* we need to allocate all before the header would be sent */
+
+    b = ngx_calloc_buf(r->pool);
+    if (b == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
+    if (b->file == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    rc = ngx_http_send_header(r);
+
+    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+        return rc;
+    }
+
+    b->file_pos = 0;
+    b->file_last = of.size;
+
+    b->in_file = b->file_last ? 1 : 0;
+    b->last_buf = (r == r->main) ? 1 : 0;
+    b->last_in_chain = 1;
+
+    b->file->fd = of.fd;
+    b->file->name = path;
+    b->file->log = log;
+    b->file->directio = of.is_directio;
+
+    out.buf = b;
+    out.next = NULL;
+
+    return ngx_http_output_filter(r, &out);
+}
+
+
+static void *
+ngx_http_gzip_static_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_gzip_static_conf_t  *conf;
+
+    conf = ngx_palloc(cf->pool, sizeof(ngx_http_gzip_static_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    conf->enable = NGX_CONF_UNSET_UINT;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_gzip_static_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_gzip_static_conf_t *prev = parent;
+    ngx_http_gzip_static_conf_t *conf = child;
+
+    ngx_conf_merge_uint_value(conf->enable, prev->enable,
+                              NGX_HTTP_GZIP_STATIC_OFF);
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_static_init(ngx_conf_t *cf)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_gzip_static_handler;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_headers_filter_module.c b/nginx/src/http/modules/ngx_http_headers_filter_module.c
new file mode 100644 (file)
index 0000000..a4c8cc2
--- /dev/null
@@ -0,0 +1,867 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct ngx_http_header_val_s  ngx_http_header_val_t;
+
+typedef ngx_int_t (*ngx_http_set_header_pt)(ngx_http_request_t *r,
+    ngx_http_header_val_t *hv, ngx_str_t *value);
+
+
+typedef struct {
+    ngx_str_t                  name;
+    ngx_uint_t                 offset;
+    ngx_http_set_header_pt     handler;
+} ngx_http_set_header_t;
+
+
+struct ngx_http_header_val_s {
+    ngx_http_complex_value_t   value;
+    ngx_str_t                  key;
+    ngx_http_set_header_pt     handler;
+    ngx_uint_t                 offset;
+    ngx_uint_t                 always;  /* unsigned  always:1 */
+};
+
+
+typedef enum {
+    NGX_HTTP_EXPIRES_OFF,
+    NGX_HTTP_EXPIRES_EPOCH,
+    NGX_HTTP_EXPIRES_MAX,
+    NGX_HTTP_EXPIRES_ACCESS,
+    NGX_HTTP_EXPIRES_MODIFIED,
+    NGX_HTTP_EXPIRES_DAILY,
+    NGX_HTTP_EXPIRES_UNSET
+} ngx_http_expires_t;
+
+
+typedef struct {
+    ngx_http_expires_t         expires;
+    time_t                     expires_time;
+    ngx_http_complex_value_t  *expires_value;
+    ngx_array_t               *headers;
+    ngx_array_t               *trailers;
+} ngx_http_headers_conf_t;
+
+
+static ngx_int_t ngx_http_set_expires(ngx_http_request_t *r,
+    ngx_http_headers_conf_t *conf);
+static ngx_int_t ngx_http_parse_expires(ngx_str_t *value,
+    ngx_http_expires_t *expires, time_t *expires_time, char **err);
+static ngx_int_t ngx_http_add_multi_header_lines(ngx_http_request_t *r,
+    ngx_http_header_val_t *hv, ngx_str_t *value);
+static ngx_int_t ngx_http_add_header(ngx_http_request_t *r,
+    ngx_http_header_val_t *hv, ngx_str_t *value);
+static ngx_int_t ngx_http_set_last_modified(ngx_http_request_t *r,
+    ngx_http_header_val_t *hv, ngx_str_t *value);
+static ngx_int_t ngx_http_set_response_header(ngx_http_request_t *r,
+    ngx_http_header_val_t *hv, ngx_str_t *value);
+
+static void *ngx_http_headers_create_conf(ngx_conf_t *cf);
+static char *ngx_http_headers_merge_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+static ngx_int_t ngx_http_headers_filter_init(ngx_conf_t *cf);
+static char *ngx_http_headers_expires(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_headers_add(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+
+static ngx_http_set_header_t  ngx_http_set_headers[] = {
+
+    { ngx_string("Cache-Control"),
+                 offsetof(ngx_http_headers_out_t, cache_control),
+                 ngx_http_add_multi_header_lines },
+
+    { ngx_string("Link"),
+                 offsetof(ngx_http_headers_out_t, link),
+                 ngx_http_add_multi_header_lines },
+
+    { ngx_string("Last-Modified"),
+                 offsetof(ngx_http_headers_out_t, last_modified),
+                 ngx_http_set_last_modified },
+
+    { ngx_string("ETag"),
+                 offsetof(ngx_http_headers_out_t, etag),
+                 ngx_http_set_response_header },
+
+    { ngx_null_string, 0, NULL }
+};
+
+
+static ngx_command_t  ngx_http_headers_filter_commands[] = {
+
+    { ngx_string("expires"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+                        |NGX_CONF_TAKE12,
+      ngx_http_headers_expires,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("add_header"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+                        |NGX_CONF_TAKE23,
+      ngx_http_headers_add,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_headers_conf_t, headers),
+      NULL },
+
+    { ngx_string("add_trailer"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+                        |NGX_CONF_TAKE23,
+      ngx_http_headers_add,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_headers_conf_t, trailers),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_headers_filter_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_headers_filter_init,          /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_headers_create_conf,          /* create location configuration */
+    ngx_http_headers_merge_conf            /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_headers_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_headers_filter_module_ctx,   /* module context */
+    ngx_http_headers_filter_commands,      /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_headers_filter(ngx_http_request_t *r)
+{
+    ngx_str_t                 value;
+    ngx_uint_t                i, safe_status;
+    ngx_http_header_val_t    *h;
+    ngx_http_headers_conf_t  *conf;
+
+    if (r != r->main) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_headers_filter_module);
+
+    if (conf->expires == NGX_HTTP_EXPIRES_OFF
+        && conf->headers == NULL
+        && conf->trailers == NULL)
+    {
+        return ngx_http_next_header_filter(r);
+    }
+
+    switch (r->headers_out.status) {
+
+    case NGX_HTTP_OK:
+    case NGX_HTTP_CREATED:
+    case NGX_HTTP_NO_CONTENT:
+    case NGX_HTTP_PARTIAL_CONTENT:
+    case NGX_HTTP_MOVED_PERMANENTLY:
+    case NGX_HTTP_MOVED_TEMPORARILY:
+    case NGX_HTTP_SEE_OTHER:
+    case NGX_HTTP_NOT_MODIFIED:
+    case NGX_HTTP_TEMPORARY_REDIRECT:
+    case NGX_HTTP_PERMANENT_REDIRECT:
+        safe_status = 1;
+        break;
+
+    default:
+        safe_status = 0;
+        break;
+    }
+
+    if (conf->expires != NGX_HTTP_EXPIRES_OFF && safe_status) {
+        if (ngx_http_set_expires(r, conf) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    if (conf->headers) {
+        h = conf->headers->elts;
+        for (i = 0; i < conf->headers->nelts; i++) {
+
+            if (!safe_status && !h[i].always) {
+                continue;
+            }
+
+            if (ngx_http_complex_value(r, &h[i].value, &value) != NGX_OK) {
+                return NGX_ERROR;
+            }
+
+            if (h[i].handler(r, &h[i], &value) != NGX_OK) {
+                return NGX_ERROR;
+            }
+        }
+    }
+
+    if (conf->trailers) {
+        h = conf->trailers->elts;
+        for (i = 0; i < conf->trailers->nelts; i++) {
+
+            if (!safe_status && !h[i].always) {
+                continue;
+            }
+
+            r->expect_trailers = 1;
+            break;
+        }
+    }
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_trailers_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    ngx_str_t                 value;
+    ngx_uint_t                i, safe_status;
+    ngx_chain_t              *cl;
+    ngx_table_elt_t          *t;
+    ngx_http_header_val_t    *h;
+    ngx_http_headers_conf_t  *conf;
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_headers_filter_module);
+
+    if (in == NULL
+        || conf->trailers == NULL
+        || !r->expect_trailers
+        || r->header_only)
+    {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    for (cl = in; cl; cl = cl->next) {
+        if (cl->buf->last_buf) {
+            break;
+        }
+    }
+
+    if (cl == NULL) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    switch (r->headers_out.status) {
+
+    case NGX_HTTP_OK:
+    case NGX_HTTP_CREATED:
+    case NGX_HTTP_NO_CONTENT:
+    case NGX_HTTP_PARTIAL_CONTENT:
+    case NGX_HTTP_MOVED_PERMANENTLY:
+    case NGX_HTTP_MOVED_TEMPORARILY:
+    case NGX_HTTP_SEE_OTHER:
+    case NGX_HTTP_NOT_MODIFIED:
+    case NGX_HTTP_TEMPORARY_REDIRECT:
+    case NGX_HTTP_PERMANENT_REDIRECT:
+        safe_status = 1;
+        break;
+
+    default:
+        safe_status = 0;
+        break;
+    }
+
+    h = conf->trailers->elts;
+    for (i = 0; i < conf->trailers->nelts; i++) {
+
+        if (!safe_status && !h[i].always) {
+            continue;
+        }
+
+        if (ngx_http_complex_value(r, &h[i].value, &value) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        if (value.len) {
+            t = ngx_list_push(&r->headers_out.trailers);
+            if (t == NULL) {
+                return NGX_ERROR;
+            }
+
+            t->key = h[i].key;
+            t->value = value;
+            t->hash = 1;
+        }
+    }
+
+    return ngx_http_next_body_filter(r, in);
+}
+
+
+static ngx_int_t
+ngx_http_set_expires(ngx_http_request_t *r, ngx_http_headers_conf_t *conf)
+{
+    char                *err;
+    size_t               len;
+    time_t               now, expires_time, max_age;
+    ngx_str_t            value;
+    ngx_int_t            rc;
+    ngx_uint_t           i;
+    ngx_table_elt_t     *e, *cc, **ccp;
+    ngx_http_expires_t   expires;
+
+    expires = conf->expires;
+    expires_time = conf->expires_time;
+
+    if (conf->expires_value != NULL) {
+
+        if (ngx_http_complex_value(r, conf->expires_value, &value) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        rc = ngx_http_parse_expires(&value, &expires, &expires_time, &err);
+
+        if (rc != NGX_OK) {
+            return NGX_OK;
+        }
+
+        if (expires == NGX_HTTP_EXPIRES_OFF) {
+            return NGX_OK;
+        }
+    }
+
+    e = r->headers_out.expires;
+
+    if (e == NULL) {
+
+        e = ngx_list_push(&r->headers_out.headers);
+        if (e == NULL) {
+            return NGX_ERROR;
+        }
+
+        r->headers_out.expires = e;
+
+        e->hash = 1;
+        ngx_str_set(&e->key, "Expires");
+    }
+
+    len = sizeof("Mon, 28 Sep 1970 06:00:00 GMT");
+    e->value.len = len - 1;
+
+    ccp = r->headers_out.cache_control.elts;
+
+    if (ccp == NULL) {
+
+        if (ngx_array_init(&r->headers_out.cache_control, r->pool,
+                           1, sizeof(ngx_table_elt_t *))
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+
+        cc = ngx_list_push(&r->headers_out.headers);
+        if (cc == NULL) {
+            return NGX_ERROR;
+        }
+
+        cc->hash = 1;
+        ngx_str_set(&cc->key, "Cache-Control");
+
+        ccp = ngx_array_push(&r->headers_out.cache_control);
+        if (ccp == NULL) {
+            return NGX_ERROR;
+        }
+
+        *ccp = cc;
+
+    } else {
+        for (i = 1; i < r->headers_out.cache_control.nelts; i++) {
+            ccp[i]->hash = 0;
+        }
+
+        cc = ccp[0];
+    }
+
+    if (expires == NGX_HTTP_EXPIRES_EPOCH) {
+        e->value.data = (u_char *) "Thu, 01 Jan 1970 00:00:01 GMT";
+        ngx_str_set(&cc->value, "no-cache");
+        return NGX_OK;
+    }
+
+    if (expires == NGX_HTTP_EXPIRES_MAX) {
+        e->value.data = (u_char *) "Thu, 31 Dec 2037 23:55:55 GMT";
+        /* 10 years */
+        ngx_str_set(&cc->value, "max-age=315360000");
+        return NGX_OK;
+    }
+
+    e->value.data = ngx_pnalloc(r->pool, len);
+    if (e->value.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (expires_time == 0 && expires != NGX_HTTP_EXPIRES_DAILY) {
+        ngx_memcpy(e->value.data, ngx_cached_http_time.data,
+                   ngx_cached_http_time.len + 1);
+        ngx_str_set(&cc->value, "max-age=0");
+        return NGX_OK;
+    }
+
+    now = ngx_time();
+
+    if (expires == NGX_HTTP_EXPIRES_DAILY) {
+        expires_time = ngx_next_time(expires_time);
+        max_age = expires_time - now;
+
+    } else if (expires == NGX_HTTP_EXPIRES_ACCESS
+               || r->headers_out.last_modified_time == -1)
+    {
+        max_age = expires_time;
+        expires_time += now;
+
+    } else {
+        expires_time += r->headers_out.last_modified_time;
+        max_age = expires_time - now;
+    }
+
+    ngx_http_time(e->value.data, expires_time);
+
+    if (conf->expires_time < 0 || max_age < 0) {
+        ngx_str_set(&cc->value, "no-cache");
+        return NGX_OK;
+    }
+
+    cc->value.data = ngx_pnalloc(r->pool,
+                                 sizeof("max-age=") + NGX_TIME_T_LEN + 1);
+    if (cc->value.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    cc->value.len = ngx_sprintf(cc->value.data, "max-age=%T", max_age)
+                    - cc->value.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_parse_expires(ngx_str_t *value, ngx_http_expires_t *expires,
+    time_t *expires_time, char **err)
+{
+    ngx_uint_t  minus;
+
+    if (*expires != NGX_HTTP_EXPIRES_MODIFIED) {
+
+        if (value->len == 5 && ngx_strncmp(value->data, "epoch", 5) == 0) {
+            *expires = NGX_HTTP_EXPIRES_EPOCH;
+            return NGX_OK;
+        }
+
+        if (value->len == 3 && ngx_strncmp(value->data, "max", 3) == 0) {
+            *expires = NGX_HTTP_EXPIRES_MAX;
+            return NGX_OK;
+        }
+
+        if (value->len == 3 && ngx_strncmp(value->data, "off", 3) == 0) {
+            *expires = NGX_HTTP_EXPIRES_OFF;
+            return NGX_OK;
+        }
+    }
+
+    if (value->len && value->data[0] == '@') {
+        value->data++;
+        value->len--;
+        minus = 0;
+
+        if (*expires == NGX_HTTP_EXPIRES_MODIFIED) {
+            *err = "daily time cannot be used with \"modified\" parameter";
+            return NGX_ERROR;
+        }
+
+        *expires = NGX_HTTP_EXPIRES_DAILY;
+
+    } else if (value->len && value->data[0] == '+') {
+        value->data++;
+        value->len--;
+        minus = 0;
+
+    } else if (value->len && value->data[0] == '-') {
+        value->data++;
+        value->len--;
+        minus = 1;
+
+    } else {
+        minus = 0;
+    }
+
+    *expires_time = ngx_parse_time(value, 1);
+
+    if (*expires_time == (time_t) NGX_ERROR) {
+        *err = "invalid value";
+        return NGX_ERROR;
+    }
+
+    if (*expires == NGX_HTTP_EXPIRES_DAILY
+        && *expires_time > 24 * 60 * 60)
+    {
+        *err = "daily time value must be less than 24 hours";
+        return NGX_ERROR;
+    }
+
+    if (minus) {
+        *expires_time = - *expires_time;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_add_header(ngx_http_request_t *r, ngx_http_header_val_t *hv,
+    ngx_str_t *value)
+{
+    ngx_table_elt_t  *h;
+
+    if (value->len) {
+        h = ngx_list_push(&r->headers_out.headers);
+        if (h == NULL) {
+            return NGX_ERROR;
+        }
+
+        h->hash = 1;
+        h->key = hv->key;
+        h->value = *value;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_add_multi_header_lines(ngx_http_request_t *r,
+    ngx_http_header_val_t *hv, ngx_str_t *value)
+{
+    ngx_array_t      *pa;
+    ngx_table_elt_t  *h, **ph;
+
+    if (value->len == 0) {
+        return NGX_OK;
+    }
+
+    pa = (ngx_array_t *) ((char *) &r->headers_out + hv->offset);
+
+    if (pa->elts == NULL) {
+        if (ngx_array_init(pa, r->pool, 1, sizeof(ngx_table_elt_t *)) != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    h = ngx_list_push(&r->headers_out.headers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    h->hash = 1;
+    h->key = hv->key;
+    h->value = *value;
+
+    ph = ngx_array_push(pa);
+    if (ph == NULL) {
+        return NGX_ERROR;
+    }
+
+    *ph = h;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_set_last_modified(ngx_http_request_t *r, ngx_http_header_val_t *hv,
+    ngx_str_t *value)
+{
+    if (ngx_http_set_response_header(r, hv, value) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    r->headers_out.last_modified_time =
+        (value->len) ? ngx_parse_http_time(value->data, value->len) : -1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_set_response_header(ngx_http_request_t *r, ngx_http_header_val_t *hv,
+    ngx_str_t *value)
+{
+    ngx_table_elt_t  *h, **old;
+
+    old = (ngx_table_elt_t **) ((char *) &r->headers_out + hv->offset);
+
+    if (value->len == 0) {
+        if (*old) {
+            (*old)->hash = 0;
+            *old = NULL;
+        }
+
+        return NGX_OK;
+    }
+
+    if (*old) {
+        h = *old;
+
+    } else {
+        h = ngx_list_push(&r->headers_out.headers);
+        if (h == NULL) {
+            return NGX_ERROR;
+        }
+
+        *old = h;
+    }
+
+    h->hash = 1;
+    h->key = hv->key;
+    h->value = *value;
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_headers_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_headers_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_headers_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->headers = NULL;
+     *     conf->trailers = NULL;
+     *     conf->expires_time = 0;
+     *     conf->expires_value = NULL;
+     */
+
+    conf->expires = NGX_HTTP_EXPIRES_UNSET;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_headers_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_headers_conf_t *prev = parent;
+    ngx_http_headers_conf_t *conf = child;
+
+    if (conf->expires == NGX_HTTP_EXPIRES_UNSET) {
+        conf->expires = prev->expires;
+        conf->expires_time = prev->expires_time;
+        conf->expires_value = prev->expires_value;
+
+        if (conf->expires == NGX_HTTP_EXPIRES_UNSET) {
+            conf->expires = NGX_HTTP_EXPIRES_OFF;
+        }
+    }
+
+    if (conf->headers == NULL) {
+        conf->headers = prev->headers;
+    }
+
+    if (conf->trailers == NULL) {
+        conf->trailers = prev->trailers;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_headers_filter_init(ngx_conf_t *cf)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_headers_filter;
+
+    ngx_http_next_body_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_trailers_filter;
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_http_headers_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_headers_conf_t *hcf = conf;
+
+    char                              *err;
+    ngx_str_t                         *value;
+    ngx_int_t                          rc;
+    ngx_uint_t                         n;
+    ngx_http_complex_value_t           cv;
+    ngx_http_compile_complex_value_t   ccv;
+
+    if (hcf->expires != NGX_HTTP_EXPIRES_UNSET) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    if (cf->args->nelts == 2) {
+
+        hcf->expires = NGX_HTTP_EXPIRES_ACCESS;
+
+        n = 1;
+
+    } else { /* cf->args->nelts == 3 */
+
+        if (ngx_strcmp(value[1].data, "modified") != 0) {
+            return "invalid value";
+        }
+
+        hcf->expires = NGX_HTTP_EXPIRES_MODIFIED;
+
+        n = 2;
+    }
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[n];
+    ccv.complex_value = &cv;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (cv.lengths != NULL) {
+
+        hcf->expires_value = ngx_palloc(cf->pool,
+                                        sizeof(ngx_http_complex_value_t));
+        if (hcf->expires_value == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        *hcf->expires_value = cv;
+
+        return NGX_CONF_OK;
+    }
+
+    rc = ngx_http_parse_expires(&value[n], &hcf->expires, &hcf->expires_time,
+                                &err);
+    if (rc != NGX_OK) {
+        return err;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_headers_add(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_headers_conf_t *hcf = conf;
+
+    ngx_str_t                          *value;
+    ngx_uint_t                          i;
+    ngx_array_t                       **headers;
+    ngx_http_header_val_t              *hv;
+    ngx_http_set_header_t              *set;
+    ngx_http_compile_complex_value_t    ccv;
+
+    value = cf->args->elts;
+
+    headers = (ngx_array_t **) ((char *) hcf + cmd->offset);
+
+    if (*headers == NULL) {
+        *headers = ngx_array_create(cf->pool, 1,
+                                    sizeof(ngx_http_header_val_t));
+        if (*headers == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    hv = ngx_array_push(*headers);
+    if (hv == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    hv->key = value[1];
+    hv->handler = NULL;
+    hv->offset = 0;
+    hv->always = 0;
+
+    if (headers == &hcf->headers) {
+        hv->handler = ngx_http_add_header;
+
+        set = ngx_http_set_headers;
+        for (i = 0; set[i].name.len; i++) {
+            if (ngx_strcasecmp(value[1].data, set[i].name.data) != 0) {
+                continue;
+            }
+
+            hv->offset = set[i].offset;
+            hv->handler = set[i].handler;
+
+            break;
+        }
+    }
+
+    if (value[2].len == 0) {
+        ngx_memzero(&hv->value, sizeof(ngx_http_complex_value_t));
+
+    } else {
+        ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+        ccv.cf = cf;
+        ccv.value = &value[2];
+        ccv.complex_value = &hv->value;
+
+        if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (cf->args->nelts == 3) {
+        return NGX_CONF_OK;
+    }
+
+    if (ngx_strcmp(value[3].data, "always") != 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[3]);
+        return NGX_CONF_ERROR;
+    }
+
+    hv->always = 1;
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_image_filter_module.c b/nginx/src/http/modules/ngx_http_image_filter_module.c
new file mode 100644 (file)
index 0000000..6c03e8a
--- /dev/null
@@ -0,0 +1,1675 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#include <gd.h>
+
+
+#define NGX_HTTP_IMAGE_OFF       0
+#define NGX_HTTP_IMAGE_TEST      1
+#define NGX_HTTP_IMAGE_SIZE      2
+#define NGX_HTTP_IMAGE_RESIZE    3
+#define NGX_HTTP_IMAGE_CROP      4
+#define NGX_HTTP_IMAGE_ROTATE    5
+
+
+#define NGX_HTTP_IMAGE_START     0
+#define NGX_HTTP_IMAGE_READ      1
+#define NGX_HTTP_IMAGE_PROCESS   2
+#define NGX_HTTP_IMAGE_PASS      3
+#define NGX_HTTP_IMAGE_DONE      4
+
+
+#define NGX_HTTP_IMAGE_NONE      0
+#define NGX_HTTP_IMAGE_JPEG      1
+#define NGX_HTTP_IMAGE_GIF       2
+#define NGX_HTTP_IMAGE_PNG       3
+#define NGX_HTTP_IMAGE_WEBP      4
+
+
+#define NGX_HTTP_IMAGE_BUFFERED  0x08
+
+
+typedef struct {
+    ngx_uint_t                   filter;
+    ngx_uint_t                   width;
+    ngx_uint_t                   height;
+    ngx_uint_t                   angle;
+    ngx_uint_t                   jpeg_quality;
+    ngx_uint_t                   webp_quality;
+    ngx_uint_t                   sharpen;
+
+    ngx_flag_t                   transparency;
+    ngx_flag_t                   interlace;
+
+    ngx_http_complex_value_t    *wcv;
+    ngx_http_complex_value_t    *hcv;
+    ngx_http_complex_value_t    *acv;
+    ngx_http_complex_value_t    *jqcv;
+    ngx_http_complex_value_t    *wqcv;
+    ngx_http_complex_value_t    *shcv;
+
+    size_t                       buffer_size;
+} ngx_http_image_filter_conf_t;
+
+
+typedef struct {
+    u_char                      *image;
+    u_char                      *last;
+
+    size_t                       length;
+
+    ngx_uint_t                   width;
+    ngx_uint_t                   height;
+    ngx_uint_t                   max_width;
+    ngx_uint_t                   max_height;
+    ngx_uint_t                   angle;
+
+    ngx_uint_t                   phase;
+    ngx_uint_t                   type;
+    ngx_uint_t                   force;
+} ngx_http_image_filter_ctx_t;
+
+
+static ngx_int_t ngx_http_image_send(ngx_http_request_t *r,
+    ngx_http_image_filter_ctx_t *ctx, ngx_chain_t *in);
+static ngx_uint_t ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in);
+static ngx_int_t ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in);
+static ngx_buf_t *ngx_http_image_process(ngx_http_request_t *r);
+static ngx_buf_t *ngx_http_image_json(ngx_http_request_t *r,
+    ngx_http_image_filter_ctx_t *ctx);
+static ngx_buf_t *ngx_http_image_asis(ngx_http_request_t *r,
+    ngx_http_image_filter_ctx_t *ctx);
+static void ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b);
+static ngx_int_t ngx_http_image_size(ngx_http_request_t *r,
+    ngx_http_image_filter_ctx_t *ctx);
+
+static ngx_buf_t *ngx_http_image_resize(ngx_http_request_t *r,
+    ngx_http_image_filter_ctx_t *ctx);
+static gdImagePtr ngx_http_image_source(ngx_http_request_t *r,
+    ngx_http_image_filter_ctx_t *ctx);
+static gdImagePtr ngx_http_image_new(ngx_http_request_t *r, int w, int h,
+    int colors);
+static u_char *ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type,
+    gdImagePtr img, int *size);
+static void ngx_http_image_cleanup(void *data);
+static ngx_uint_t ngx_http_image_filter_get_value(ngx_http_request_t *r,
+    ngx_http_complex_value_t *cv, ngx_uint_t v);
+static ngx_uint_t ngx_http_image_filter_value(ngx_str_t *value);
+
+
+static void *ngx_http_image_filter_create_conf(ngx_conf_t *cf);
+static char *ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static char *ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf,
+    ngx_command_t *cmd, void *conf);
+static char *ngx_http_image_filter_webp_quality(ngx_conf_t *cf,
+    ngx_command_t *cmd, void *conf);
+static char *ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static ngx_int_t ngx_http_image_filter_init(ngx_conf_t *cf);
+
+
+static ngx_command_t  ngx_http_image_filter_commands[] = {
+
+    { ngx_string("image_filter"),
+      NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
+      ngx_http_image_filter,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("image_filter_jpeg_quality"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_image_filter_jpeg_quality,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("image_filter_webp_quality"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_image_filter_webp_quality,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("image_filter_sharpen"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_image_filter_sharpen,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("image_filter_transparency"),
+      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_image_filter_conf_t, transparency),
+      NULL },
+
+    { ngx_string("image_filter_interlace"),
+      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_image_filter_conf_t, interlace),
+      NULL },
+
+    { ngx_string("image_filter_buffer"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_image_filter_conf_t, buffer_size),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_image_filter_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_image_filter_init,            /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_image_filter_create_conf,     /* create location configuration */
+    ngx_http_image_filter_merge_conf       /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_image_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_image_filter_module_ctx,     /* module context */
+    ngx_http_image_filter_commands,        /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static 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_str_t  ngx_http_image_types[] = {
+    ngx_string("image/jpeg"),
+    ngx_string("image/gif"),
+    ngx_string("image/png"),
+    ngx_string("image/webp")
+};
+
+
+static ngx_int_t
+ngx_http_image_header_filter(ngx_http_request_t *r)
+{
+    off_t                          len;
+    ngx_http_image_filter_ctx_t   *ctx;
+    ngx_http_image_filter_conf_t  *conf;
+
+    if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
+
+    if (ctx) {
+        ngx_http_set_ctx(r, NULL, ngx_http_image_filter_module);
+        return ngx_http_next_header_filter(r);
+    }
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
+
+    if (conf->filter == NGX_HTTP_IMAGE_OFF) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    if (r->headers_out.content_type.len
+            >= sizeof("multipart/x-mixed-replace") - 1
+        && ngx_strncasecmp(r->headers_out.content_type.data,
+                           (u_char *) "multipart/x-mixed-replace",
+                           sizeof("multipart/x-mixed-replace") - 1)
+           == 0)
+    {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "image filter: multipart/x-mixed-replace response");
+
+        return NGX_ERROR;
+    }
+
+    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_image_filter_ctx_t));
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_http_set_ctx(r, ctx, ngx_http_image_filter_module);
+
+    len = r->headers_out.content_length_n;
+
+    if (len != -1 && len > (off_t) conf->buffer_size) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "image filter: too big response: %O", len);
+
+        return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE;
+    }
+
+    if (len == -1) {
+        ctx->length = conf->buffer_size;
+
+    } else {
+        ctx->length = (size_t) len;
+    }
+
+    if (r->headers_out.refresh) {
+        r->headers_out.refresh->hash = 0;
+    }
+
+    r->main_filter_need_in_memory = 1;
+    r->allow_ranges = 0;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_image_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    ngx_int_t                      rc;
+    ngx_str_t                     *ct;
+    ngx_chain_t                    out;
+    ngx_http_image_filter_ctx_t   *ctx;
+    ngx_http_image_filter_conf_t  *conf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image filter");
+
+    if (in == NULL) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
+
+    if (ctx == NULL) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    switch (ctx->phase) {
+
+    case NGX_HTTP_IMAGE_START:
+
+        ctx->type = ngx_http_image_test(r, in);
+
+        conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
+
+        if (ctx->type == NGX_HTTP_IMAGE_NONE) {
+
+            if (conf->filter == NGX_HTTP_IMAGE_SIZE) {
+                out.buf = ngx_http_image_json(r, NULL);
+
+                if (out.buf) {
+                    out.next = NULL;
+                    ctx->phase = NGX_HTTP_IMAGE_DONE;
+
+                    return ngx_http_image_send(r, ctx, &out);
+                }
+            }
+
+            return ngx_http_filter_finalize_request(r,
+                                              &ngx_http_image_filter_module,
+                                              NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
+        }
+
+        /* override content type */
+
+        ct = &ngx_http_image_types[ctx->type - 1];
+        r->headers_out.content_type_len = ct->len;
+        r->headers_out.content_type = *ct;
+        r->headers_out.content_type_lowcase = NULL;
+
+        if (conf->filter == NGX_HTTP_IMAGE_TEST) {
+            ctx->phase = NGX_HTTP_IMAGE_PASS;
+
+            return ngx_http_image_send(r, ctx, in);
+        }
+
+        ctx->phase = NGX_HTTP_IMAGE_READ;
+
+        /* fall through */
+
+    case NGX_HTTP_IMAGE_READ:
+
+        rc = ngx_http_image_read(r, in);
+
+        if (rc == NGX_AGAIN) {
+            return NGX_OK;
+        }
+
+        if (rc == NGX_ERROR) {
+            return ngx_http_filter_finalize_request(r,
+                                              &ngx_http_image_filter_module,
+                                              NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
+        }
+
+        /* fall through */
+
+    case NGX_HTTP_IMAGE_PROCESS:
+
+        out.buf = ngx_http_image_process(r);
+
+        if (out.buf == NULL) {
+            return ngx_http_filter_finalize_request(r,
+                                              &ngx_http_image_filter_module,
+                                              NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
+        }
+
+        out.next = NULL;
+        ctx->phase = NGX_HTTP_IMAGE_PASS;
+
+        return ngx_http_image_send(r, ctx, &out);
+
+    case NGX_HTTP_IMAGE_PASS:
+
+        return ngx_http_next_body_filter(r, in);
+
+    default: /* NGX_HTTP_IMAGE_DONE */
+
+        rc = ngx_http_next_body_filter(r, NULL);
+
+        /* NGX_ERROR resets any pending data */
+        return (rc == NGX_OK) ? NGX_ERROR : rc;
+    }
+}
+
+
+static ngx_int_t
+ngx_http_image_send(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx,
+    ngx_chain_t *in)
+{
+    ngx_int_t  rc;
+
+    rc = ngx_http_next_header_filter(r);
+
+    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+        return NGX_ERROR;
+    }
+
+    rc = ngx_http_next_body_filter(r, in);
+
+    if (ctx->phase == NGX_HTTP_IMAGE_DONE) {
+        /* NGX_ERROR resets any pending data */
+        return (rc == NGX_OK) ? NGX_ERROR : rc;
+    }
+
+    return rc;
+}
+
+
+static ngx_uint_t
+ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    u_char  *p;
+
+    p = in->buf->pos;
+
+    if (in->buf->last - p < 16) {
+        return NGX_HTTP_IMAGE_NONE;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "image filter: \"%c%c\"", p[0], p[1]);
+
+    if (p[0] == 0xff && p[1] == 0xd8) {
+
+        /* JPEG */
+
+        return NGX_HTTP_IMAGE_JPEG;
+
+    } else if (p[0] == 'G' && p[1] == 'I' && p[2] == 'F' && p[3] == '8'
+               && p[5] == 'a')
+    {
+        if (p[4] == '9' || p[4] == '7') {
+            /* GIF */
+            return NGX_HTTP_IMAGE_GIF;
+        }
+
+    } else if (p[0] == 0x89 && p[1] == 'P' && p[2] == 'N' && p[3] == 'G'
+               && p[4] == 0x0d && p[5] == 0x0a && p[6] == 0x1a && p[7] == 0x0a)
+    {
+        /* PNG */
+
+        return NGX_HTTP_IMAGE_PNG;
+
+    } else if (p[0] == 'R' && p[1] == 'I' && p[2] == 'F' && p[3] == 'F'
+               && p[8] == 'W' && p[9] == 'E' && p[10] == 'B' && p[11] == 'P')
+    {
+        /* WebP */
+
+        return NGX_HTTP_IMAGE_WEBP;
+    }
+
+    return NGX_HTTP_IMAGE_NONE;
+}
+
+
+static ngx_int_t
+ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    u_char                       *p;
+    size_t                        size, rest;
+    ngx_buf_t                    *b;
+    ngx_chain_t                  *cl;
+    ngx_http_image_filter_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
+
+    if (ctx->image == NULL) {
+        ctx->image = ngx_palloc(r->pool, ctx->length);
+        if (ctx->image == NULL) {
+            return NGX_ERROR;
+        }
+
+        ctx->last = ctx->image;
+    }
+
+    p = ctx->last;
+
+    for (cl = in; cl; cl = cl->next) {
+
+        b = cl->buf;
+        size = b->last - b->pos;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "image buf: %uz", size);
+
+        rest = ctx->image + ctx->length - p;
+
+        if (size > rest) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "image filter: too big response");
+            return NGX_ERROR;
+        }
+
+        p = ngx_cpymem(p, b->pos, size);
+        b->pos += size;
+
+        if (b->last_buf) {
+            ctx->last = p;
+            return NGX_OK;
+        }
+    }
+
+    ctx->last = p;
+    r->connection->buffered |= NGX_HTTP_IMAGE_BUFFERED;
+
+    return NGX_AGAIN;
+}
+
+
+static ngx_buf_t *
+ngx_http_image_process(ngx_http_request_t *r)
+{
+    ngx_int_t                      rc;
+    ngx_http_image_filter_ctx_t   *ctx;
+    ngx_http_image_filter_conf_t  *conf;
+
+    r->connection->buffered &= ~NGX_HTTP_IMAGE_BUFFERED;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
+
+    rc = ngx_http_image_size(r, ctx);
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
+
+    if (conf->filter == NGX_HTTP_IMAGE_SIZE) {
+        return ngx_http_image_json(r, rc == NGX_OK ? ctx : NULL);
+    }
+
+    ctx->angle = ngx_http_image_filter_get_value(r, conf->acv, conf->angle);
+
+    if (conf->filter == NGX_HTTP_IMAGE_ROTATE) {
+
+        if (ctx->angle != 90 && ctx->angle != 180 && ctx->angle != 270) {
+            return NULL;
+        }
+
+        return ngx_http_image_resize(r, ctx);
+    }
+
+    ctx->max_width = ngx_http_image_filter_get_value(r, conf->wcv, conf->width);
+    if (ctx->max_width == 0) {
+        return NULL;
+    }
+
+    ctx->max_height = ngx_http_image_filter_get_value(r, conf->hcv,
+                                                      conf->height);
+    if (ctx->max_height == 0) {
+        return NULL;
+    }
+
+    if (rc == NGX_OK
+        && ctx->width <= ctx->max_width
+        && ctx->height <= ctx->max_height
+        && ctx->angle == 0
+        && !ctx->force)
+    {
+        return ngx_http_image_asis(r, ctx);
+    }
+
+    return ngx_http_image_resize(r, ctx);
+}
+
+
+static ngx_buf_t *
+ngx_http_image_json(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
+{
+    size_t      len;
+    ngx_buf_t  *b;
+
+    b = ngx_calloc_buf(r->pool);
+    if (b == NULL) {
+        return NULL;
+    }
+
+    b->memory = 1;
+    b->last_buf = 1;
+
+    ngx_http_clean_header(r);
+
+    r->headers_out.status = NGX_HTTP_OK;
+    r->headers_out.content_type_len = sizeof("application/json") - 1;
+    ngx_str_set(&r->headers_out.content_type, "application/json");
+    r->headers_out.content_type_lowcase = NULL;
+
+    if (ctx == NULL) {
+        b->pos = (u_char *) "{}" CRLF;
+        b->last = b->pos + sizeof("{}" CRLF) - 1;
+
+        ngx_http_image_length(r, b);
+
+        return b;
+    }
+
+    len = sizeof("{ \"img\" : "
+                 "{ \"width\": , \"height\": , \"type\": \"jpeg\" } }" CRLF) - 1
+          + 2 * NGX_SIZE_T_LEN;
+
+    b->pos = ngx_pnalloc(r->pool, len);
+    if (b->pos == NULL) {
+        return NULL;
+    }
+
+    b->last = ngx_sprintf(b->pos,
+                          "{ \"img\" : "
+                                       "{ \"width\": %uz,"
+                                        " \"height\": %uz,"
+                                        " \"type\": \"%s\" } }" CRLF,
+                          ctx->width, ctx->height,
+                          ngx_http_image_types[ctx->type - 1].data + 6);
+
+    ngx_http_image_length(r, b);
+
+    return b;
+}
+
+
+static ngx_buf_t *
+ngx_http_image_asis(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
+{
+    ngx_buf_t  *b;
+
+    b = ngx_calloc_buf(r->pool);
+    if (b == NULL) {
+        return NULL;
+    }
+
+    b->pos = ctx->image;
+    b->last = ctx->last;
+    b->memory = 1;
+    b->last_buf = 1;
+
+    ngx_http_image_length(r, b);
+
+    return b;
+}
+
+
+static void
+ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b)
+{
+    r->headers_out.content_length_n = b->last - b->pos;
+
+    if (r->headers_out.content_length) {
+        r->headers_out.content_length->hash = 0;
+    }
+
+    r->headers_out.content_length = NULL;
+}
+
+
+static ngx_int_t
+ngx_http_image_size(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
+{
+    u_char      *p, *last;
+    size_t       len, app;
+    ngx_uint_t   width, height;
+
+    p = ctx->image;
+
+    switch (ctx->type) {
+
+    case NGX_HTTP_IMAGE_JPEG:
+
+        p += 2;
+        last = ctx->image + ctx->length - 10;
+        width = 0;
+        height = 0;
+        app = 0;
+
+        while (p < last) {
+
+            if (p[0] == 0xff && p[1] != 0xff) {
+
+                ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                               "JPEG: %02xd %02xd", p[0], p[1]);
+
+                p++;
+
+                if ((*p == 0xc0 || *p == 0xc1 || *p == 0xc2 || *p == 0xc3
+                     || *p == 0xc9 || *p == 0xca || *p == 0xcb)
+                    && (width == 0 || height == 0))
+                {
+                    width = p[6] * 256 + p[7];
+                    height = p[4] * 256 + p[5];
+                }
+
+                ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                               "JPEG: %02xd %02xd", p[1], p[2]);
+
+                len = p[1] * 256 + p[2];
+
+                if (*p >= 0xe1 && *p <= 0xef) {
+                    /* application data, e.g., EXIF, Adobe XMP, etc. */
+                    app += len;
+                }
+
+                p += len;
+
+                continue;
+            }
+
+            p++;
+        }
+
+        if (width == 0 || height == 0) {
+            return NGX_DECLINED;
+        }
+
+        if (ctx->length / 20 < app) {
+            /* force conversion if application data consume more than 5% */
+            ctx->force = 1;
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "app data size: %uz", app);
+        }
+
+        break;
+
+    case NGX_HTTP_IMAGE_GIF:
+
+        if (ctx->length < 10) {
+            return NGX_DECLINED;
+        }
+
+        width = p[7] * 256 + p[6];
+        height = p[9] * 256 + p[8];
+
+        break;
+
+    case NGX_HTTP_IMAGE_PNG:
+
+        if (ctx->length < 24) {
+            return NGX_DECLINED;
+        }
+
+        width = p[18] * 256 + p[19];
+        height = p[22] * 256 + p[23];
+
+        break;
+
+    case NGX_HTTP_IMAGE_WEBP:
+
+        if (ctx->length < 30) {
+            return NGX_DECLINED;
+        }
+
+        if (p[12] != 'V' || p[13] != 'P' || p[14] != '8') {
+            return NGX_DECLINED;
+        }
+
+        switch (p[15]) {
+
+        case ' ':
+            if (p[20] & 1) {
+                /* not a key frame */
+                return NGX_DECLINED;
+            }
+
+            if (p[23] != 0x9d || p[24] != 0x01 || p[25] != 0x2a) {
+                /* invalid start code */
+                return NGX_DECLINED;
+            }
+
+            width = (p[26] | p[27] << 8) & 0x3fff;
+            height = (p[28] | p[29] << 8) & 0x3fff;
+
+            break;
+
+        case 'L':
+            if (p[20] != 0x2f) {
+                /* invalid signature */
+                return NGX_DECLINED;
+            }
+
+            width = ((p[21] | p[22] << 8) & 0x3fff) + 1;
+            height = ((p[22] >> 6 | p[23] << 2 | p[24] << 10) & 0x3fff) + 1;
+
+            break;
+
+        case 'X':
+            width = (p[24] | p[25] << 8 | p[26] << 16) + 1;
+            height = (p[27] | p[28] << 8 | p[29] << 16) + 1;
+            break;
+
+        default:
+            return NGX_DECLINED;
+        }
+
+        break;
+
+    default:
+
+        return NGX_DECLINED;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "image size: %d x %d", (int) width, (int) height);
+
+    ctx->width = width;
+    ctx->height = height;
+
+    return NGX_OK;
+}
+
+
+static ngx_buf_t *
+ngx_http_image_resize(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
+{
+    int                            sx, sy, dx, dy, ox, oy, ax, ay, size,
+                                   colors, palette, transparent, sharpen,
+                                   red, green, blue, t;
+    u_char                        *out;
+    ngx_buf_t                     *b;
+    ngx_uint_t                     resize;
+    gdImagePtr                     src, dst;
+    ngx_pool_cleanup_t            *cln;
+    ngx_http_image_filter_conf_t  *conf;
+
+    src = ngx_http_image_source(r, ctx);
+
+    if (src == NULL) {
+        return NULL;
+    }
+
+    sx = gdImageSX(src);
+    sy = gdImageSY(src);
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
+
+    if (!ctx->force
+        && ctx->angle == 0
+        && (ngx_uint_t) sx <= ctx->max_width
+        && (ngx_uint_t) sy <= ctx->max_height)
+    {
+        gdImageDestroy(src);
+        return ngx_http_image_asis(r, ctx);
+    }
+
+    colors = gdImageColorsTotal(src);
+
+    if (colors && conf->transparency) {
+        transparent = gdImageGetTransparent(src);
+
+        if (transparent != -1) {
+            palette = colors;
+            red = gdImageRed(src, transparent);
+            green = gdImageGreen(src, transparent);
+            blue = gdImageBlue(src, transparent);
+
+            goto transparent;
+        }
+    }
+
+    palette = 0;
+    transparent = -1;
+    red = 0;
+    green = 0;
+    blue = 0;
+
+transparent:
+
+    gdImageColorTransparent(src, -1);
+
+    dx = sx;
+    dy = sy;
+
+    if (conf->filter == NGX_HTTP_IMAGE_RESIZE) {
+
+        if ((ngx_uint_t) dx > ctx->max_width) {
+            dy = dy * ctx->max_width / dx;
+            dy = dy ? dy : 1;
+            dx = ctx->max_width;
+        }
+
+        if ((ngx_uint_t) dy > ctx->max_height) {
+            dx = dx * ctx->max_height / dy;
+            dx = dx ? dx : 1;
+            dy = ctx->max_height;
+        }
+
+        resize = 1;
+
+    } else if (conf->filter == NGX_HTTP_IMAGE_ROTATE) {
+
+        resize = 0;
+
+    } else { /* NGX_HTTP_IMAGE_CROP */
+
+        resize = 0;
+
+        if ((double) dx / dy < (double) ctx->max_width / ctx->max_height) {
+            if ((ngx_uint_t) dx > ctx->max_width) {
+                dy = dy * ctx->max_width / dx;
+                dy = dy ? dy : 1;
+                dx = ctx->max_width;
+                resize = 1;
+            }
+
+        } else {
+            if ((ngx_uint_t) dy > ctx->max_height) {
+                dx = dx * ctx->max_height / dy;
+                dx = dx ? dx : 1;
+                dy = ctx->max_height;
+                resize = 1;
+            }
+        }
+    }
+
+    if (resize) {
+        dst = ngx_http_image_new(r, dx, dy, palette);
+        if (dst == NULL) {
+            gdImageDestroy(src);
+            return NULL;
+        }
+
+        if (colors == 0) {
+            gdImageSaveAlpha(dst, 1);
+            gdImageAlphaBlending(dst, 0);
+        }
+
+        gdImageCopyResampled(dst, src, 0, 0, 0, 0, dx, dy, sx, sy);
+
+        if (colors) {
+            gdImageTrueColorToPalette(dst, 1, 256);
+        }
+
+        gdImageDestroy(src);
+
+    } else {
+        dst = src;
+    }
+
+    if (ctx->angle) {
+        src = dst;
+
+        ax = (dx % 2 == 0) ? 1 : 0;
+        ay = (dy % 2 == 0) ? 1 : 0;
+
+        switch (ctx->angle) {
+
+        case 90:
+        case 270:
+            dst = ngx_http_image_new(r, dy, dx, palette);
+            if (dst == NULL) {
+                gdImageDestroy(src);
+                return NULL;
+            }
+            if (ctx->angle == 90) {
+                ox = dy / 2 + ay;
+                oy = dx / 2 - ax;
+
+            } else {
+                ox = dy / 2 - ay;
+                oy = dx / 2 + ax;
+            }
+
+            gdImageCopyRotated(dst, src, ox, oy, 0, 0,
+                               dx + ax, dy + ay, ctx->angle);
+            gdImageDestroy(src);
+
+            t = dx;
+            dx = dy;
+            dy = t;
+            break;
+
+        case 180:
+            dst = ngx_http_image_new(r, dx, dy, palette);
+            if (dst == NULL) {
+                gdImageDestroy(src);
+                return NULL;
+            }
+            gdImageCopyRotated(dst, src, dx / 2 - ax, dy / 2 - ay, 0, 0,
+                               dx + ax, dy + ay, ctx->angle);
+            gdImageDestroy(src);
+            break;
+        }
+    }
+
+    if (conf->filter == NGX_HTTP_IMAGE_CROP) {
+
+        src = dst;
+
+        if ((ngx_uint_t) dx > ctx->max_width) {
+            ox = dx - ctx->max_width;
+
+        } else {
+            ox = 0;
+        }
+
+        if ((ngx_uint_t) dy > ctx->max_height) {
+            oy = dy - ctx->max_height;
+
+        } else {
+            oy = 0;
+        }
+
+        if (ox || oy) {
+
+            dst = ngx_http_image_new(r, dx - ox, dy - oy, colors);
+
+            if (dst == NULL) {
+                gdImageDestroy(src);
+                return NULL;
+            }
+
+            ox /= 2;
+            oy /= 2;
+
+            ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "image crop: %d x %d @ %d x %d",
+                           dx, dy, ox, oy);
+
+            if (colors == 0) {
+                gdImageSaveAlpha(dst, 1);
+                gdImageAlphaBlending(dst, 0);
+            }
+
+            gdImageCopy(dst, src, 0, 0, ox, oy, dx - ox, dy - oy);
+
+            if (colors) {
+                gdImageTrueColorToPalette(dst, 1, 256);
+            }
+
+            gdImageDestroy(src);
+        }
+    }
+
+    if (transparent != -1 && colors) {
+        gdImageColorTransparent(dst, gdImageColorExact(dst, red, green, blue));
+    }
+
+    sharpen = ngx_http_image_filter_get_value(r, conf->shcv, conf->sharpen);
+    if (sharpen > 0) {
+        gdImageSharpen(dst, sharpen);
+    }
+
+    gdImageInterlace(dst, (int) conf->interlace);
+
+    out = ngx_http_image_out(r, ctx->type, dst, &size);
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "image: %d x %d %d", sx, sy, colors);
+
+    gdImageDestroy(dst);
+    ngx_pfree(r->pool, ctx->image);
+
+    if (out == NULL) {
+        return NULL;
+    }
+
+    cln = ngx_pool_cleanup_add(r->pool, 0);
+    if (cln == NULL) {
+        gdFree(out);
+        return NULL;
+    }
+
+    b = ngx_calloc_buf(r->pool);
+    if (b == NULL) {
+        gdFree(out);
+        return NULL;
+    }
+
+    cln->handler = ngx_http_image_cleanup;
+    cln->data = out;
+
+    b->pos = out;
+    b->last = out + size;
+    b->memory = 1;
+    b->last_buf = 1;
+
+    ngx_http_image_length(r, b);
+    ngx_http_weak_etag(r);
+
+    return b;
+}
+
+
+static gdImagePtr
+ngx_http_image_source(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
+{
+    char        *failed;
+    gdImagePtr   img;
+
+    img = NULL;
+
+    switch (ctx->type) {
+
+    case NGX_HTTP_IMAGE_JPEG:
+        img = gdImageCreateFromJpegPtr(ctx->length, ctx->image);
+        failed = "gdImageCreateFromJpegPtr() failed";
+        break;
+
+    case NGX_HTTP_IMAGE_GIF:
+        img = gdImageCreateFromGifPtr(ctx->length, ctx->image);
+        failed = "gdImageCreateFromGifPtr() failed";
+        break;
+
+    case NGX_HTTP_IMAGE_PNG:
+        img = gdImageCreateFromPngPtr(ctx->length, ctx->image);
+        failed = "gdImageCreateFromPngPtr() failed";
+        break;
+
+    case NGX_HTTP_IMAGE_WEBP:
+#if (NGX_HAVE_GD_WEBP)
+        img = gdImageCreateFromWebpPtr(ctx->length, ctx->image);
+        failed = "gdImageCreateFromWebpPtr() failed";
+#else
+        failed = "nginx was built without GD WebP support";
+#endif
+        break;
+
+    default:
+        failed = "unknown image type";
+        break;
+    }
+
+    if (img == NULL) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed);
+    }
+
+    return img;
+}
+
+
+static gdImagePtr
+ngx_http_image_new(ngx_http_request_t *r, int w, int h, int colors)
+{
+    gdImagePtr  img;
+
+    if (colors == 0) {
+        img = gdImageCreateTrueColor(w, h);
+
+        if (img == NULL) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "gdImageCreateTrueColor() failed");
+            return NULL;
+        }
+
+    } else {
+        img = gdImageCreate(w, h);
+
+        if (img == NULL) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "gdImageCreate() failed");
+            return NULL;
+        }
+    }
+
+    return img;
+}
+
+
+static u_char *
+ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type, gdImagePtr img,
+    int *size)
+{
+    char                          *failed;
+    u_char                        *out;
+    ngx_int_t                      q;
+    ngx_http_image_filter_conf_t  *conf;
+
+    out = NULL;
+
+    switch (type) {
+
+    case NGX_HTTP_IMAGE_JPEG:
+        conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
+
+        q = ngx_http_image_filter_get_value(r, conf->jqcv, conf->jpeg_quality);
+        if (q <= 0) {
+            return NULL;
+        }
+
+        out = gdImageJpegPtr(img, size, q);
+        failed = "gdImageJpegPtr() failed";
+        break;
+
+    case NGX_HTTP_IMAGE_GIF:
+        out = gdImageGifPtr(img, size);
+        failed = "gdImageGifPtr() failed";
+        break;
+
+    case NGX_HTTP_IMAGE_PNG:
+        out = gdImagePngPtr(img, size);
+        failed = "gdImagePngPtr() failed";
+        break;
+
+    case NGX_HTTP_IMAGE_WEBP:
+#if (NGX_HAVE_GD_WEBP)
+        conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
+
+        q = ngx_http_image_filter_get_value(r, conf->wqcv, conf->webp_quality);
+        if (q <= 0) {
+            return NULL;
+        }
+
+        out = gdImageWebpPtrEx(img, size, q);
+        failed = "gdImageWebpPtrEx() failed";
+#else
+        failed = "nginx was built without GD WebP support";
+#endif
+        break;
+
+    default:
+        failed = "unknown image type";
+        break;
+    }
+
+    if (out == NULL) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed);
+    }
+
+    return out;
+}
+
+
+static void
+ngx_http_image_cleanup(void *data)
+{
+    gdFree(data);
+}
+
+
+static ngx_uint_t
+ngx_http_image_filter_get_value(ngx_http_request_t *r,
+    ngx_http_complex_value_t *cv, ngx_uint_t v)
+{
+    ngx_str_t  val;
+
+    if (cv == NULL) {
+        return v;
+    }
+
+    if (ngx_http_complex_value(r, cv, &val) != NGX_OK) {
+        return 0;
+    }
+
+    return ngx_http_image_filter_value(&val);
+}
+
+
+static ngx_uint_t
+ngx_http_image_filter_value(ngx_str_t *value)
+{
+    ngx_int_t  n;
+
+    if (value->len == 1 && value->data[0] == '-') {
+        return (ngx_uint_t) -1;
+    }
+
+    n = ngx_atoi(value->data, value->len);
+
+    if (n > 0) {
+        return (ngx_uint_t) n;
+    }
+
+    return 0;
+}
+
+
+static void *
+ngx_http_image_filter_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_image_filter_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_image_filter_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->width = 0;
+     *     conf->height = 0;
+     *     conf->angle = 0;
+     *     conf->wcv = NULL;
+     *     conf->hcv = NULL;
+     *     conf->acv = NULL;
+     *     conf->jqcv = NULL;
+     *     conf->wqcv = NULL;
+     *     conf->shcv = NULL;
+     */
+
+    conf->filter = NGX_CONF_UNSET_UINT;
+    conf->jpeg_quality = NGX_CONF_UNSET_UINT;
+    conf->webp_quality = NGX_CONF_UNSET_UINT;
+    conf->sharpen = NGX_CONF_UNSET_UINT;
+    conf->transparency = NGX_CONF_UNSET;
+    conf->interlace = NGX_CONF_UNSET;
+    conf->buffer_size = NGX_CONF_UNSET_SIZE;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_image_filter_conf_t *prev = parent;
+    ngx_http_image_filter_conf_t *conf = child;
+
+    if (conf->filter == NGX_CONF_UNSET_UINT) {
+
+        if (prev->filter == NGX_CONF_UNSET_UINT) {
+            conf->filter = NGX_HTTP_IMAGE_OFF;
+
+        } else {
+            conf->filter = prev->filter;
+            conf->width = prev->width;
+            conf->height = prev->height;
+            conf->angle = prev->angle;
+            conf->wcv = prev->wcv;
+            conf->hcv = prev->hcv;
+            conf->acv = prev->acv;
+        }
+    }
+
+    if (conf->jpeg_quality == NGX_CONF_UNSET_UINT) {
+
+        /* 75 is libjpeg default quality */
+        ngx_conf_merge_uint_value(conf->jpeg_quality, prev->jpeg_quality, 75);
+
+        if (conf->jqcv == NULL) {
+            conf->jqcv = prev->jqcv;
+        }
+    }
+
+    if (conf->webp_quality == NGX_CONF_UNSET_UINT) {
+
+        /* 80 is libwebp default quality */
+        ngx_conf_merge_uint_value(conf->webp_quality, prev->webp_quality, 80);
+
+        if (conf->wqcv == NULL) {
+            conf->wqcv = prev->wqcv;
+        }
+    }
+
+    if (conf->sharpen == NGX_CONF_UNSET_UINT) {
+        ngx_conf_merge_uint_value(conf->sharpen, prev->sharpen, 0);
+
+        if (conf->shcv == NULL) {
+            conf->shcv = prev->shcv;
+        }
+    }
+
+    ngx_conf_merge_value(conf->transparency, prev->transparency, 1);
+
+    ngx_conf_merge_value(conf->interlace, prev->interlace, 0);
+
+    ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size,
+                              1 * 1024 * 1024);
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_image_filter_conf_t *imcf = conf;
+
+    ngx_str_t                         *value;
+    ngx_int_t                          n;
+    ngx_uint_t                         i;
+    ngx_http_complex_value_t           cv;
+    ngx_http_compile_complex_value_t   ccv;
+
+    value = cf->args->elts;
+
+    i = 1;
+
+    if (cf->args->nelts == 2) {
+        if (ngx_strcmp(value[i].data, "off") == 0) {
+            imcf->filter = NGX_HTTP_IMAGE_OFF;
+
+        } else if (ngx_strcmp(value[i].data, "test") == 0) {
+            imcf->filter = NGX_HTTP_IMAGE_TEST;
+
+        } else if (ngx_strcmp(value[i].data, "size") == 0) {
+            imcf->filter = NGX_HTTP_IMAGE_SIZE;
+
+        } else {
+            goto failed;
+        }
+
+        return NGX_CONF_OK;
+
+    } else if (cf->args->nelts == 3) {
+
+        if (ngx_strcmp(value[i].data, "rotate") == 0) {
+            if (imcf->filter != NGX_HTTP_IMAGE_RESIZE
+                && imcf->filter != NGX_HTTP_IMAGE_CROP)
+            {
+                imcf->filter = NGX_HTTP_IMAGE_ROTATE;
+            }
+
+            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;
+            }
+
+            if (cv.lengths == NULL) {
+                n = ngx_http_image_filter_value(&value[i]);
+
+                if (n != 90 && n != 180 && n != 270) {
+                    goto failed;
+                }
+
+                imcf->angle = (ngx_uint_t) n;
+
+            } else {
+                imcf->acv = ngx_palloc(cf->pool,
+                                       sizeof(ngx_http_complex_value_t));
+                if (imcf->acv == NULL) {
+                    return NGX_CONF_ERROR;
+                }
+
+                *imcf->acv = cv;
+            }
+
+            return NGX_CONF_OK;
+
+        } else {
+            goto failed;
+        }
+    }
+
+    if (ngx_strcmp(value[i].data, "resize") == 0) {
+        imcf->filter = NGX_HTTP_IMAGE_RESIZE;
+
+    } else if (ngx_strcmp(value[i].data, "crop") == 0) {
+        imcf->filter = NGX_HTTP_IMAGE_CROP;
+
+    } else {
+        goto failed;
+    }
+
+    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;
+    }
+
+    if (cv.lengths == NULL) {
+        n = ngx_http_image_filter_value(&value[i]);
+
+        if (n == 0) {
+            goto failed;
+        }
+
+        imcf->width = (ngx_uint_t) n;
+
+    } else {
+        imcf->wcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
+        if (imcf->wcv == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        *imcf->wcv = cv;
+    }
+
+    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;
+    }
+
+    if (cv.lengths == NULL) {
+        n = ngx_http_image_filter_value(&value[i]);
+
+        if (n == 0) {
+            goto failed;
+        }
+
+        imcf->height = (ngx_uint_t) n;
+
+    } else {
+        imcf->hcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
+        if (imcf->hcv == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        *imcf->hcv = cv;
+    }
+
+    return NGX_CONF_OK;
+
+failed:
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"",
+                       &value[i]);
+
+    return NGX_CONF_ERROR;
+}
+
+
+static char *
+ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf)
+{
+    ngx_http_image_filter_conf_t *imcf = conf;
+
+    ngx_str_t                         *value;
+    ngx_int_t                          n;
+    ngx_http_complex_value_t           cv;
+    ngx_http_compile_complex_value_t   ccv;
+
+    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;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (cv.lengths == NULL) {
+        n = ngx_http_image_filter_value(&value[1]);
+
+        if (n <= 0) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid value \"%V\"", &value[1]);
+            return NGX_CONF_ERROR;
+        }
+
+        imcf->jpeg_quality = (ngx_uint_t) n;
+
+    } else {
+        imcf->jqcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
+        if (imcf->jqcv == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        *imcf->jqcv = cv;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_image_filter_webp_quality(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf)
+{
+    ngx_http_image_filter_conf_t *imcf = conf;
+
+    ngx_str_t                         *value;
+    ngx_int_t                          n;
+    ngx_http_complex_value_t           cv;
+    ngx_http_compile_complex_value_t   ccv;
+
+    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;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (cv.lengths == NULL) {
+        n = ngx_http_image_filter_value(&value[1]);
+
+        if (n <= 0) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid value \"%V\"", &value[1]);
+            return NGX_CONF_ERROR;
+        }
+
+        imcf->webp_quality = (ngx_uint_t) n;
+
+    } else {
+        imcf->wqcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
+        if (imcf->wqcv == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        *imcf->wqcv = cv;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf)
+{
+    ngx_http_image_filter_conf_t *imcf = conf;
+
+    ngx_str_t                         *value;
+    ngx_int_t                          n;
+    ngx_http_complex_value_t           cv;
+    ngx_http_compile_complex_value_t   ccv;
+
+    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;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (cv.lengths == NULL) {
+        n = ngx_http_image_filter_value(&value[1]);
+
+        if (n < 0) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid value \"%V\"", &value[1]);
+            return NGX_CONF_ERROR;
+        }
+
+        imcf->sharpen = (ngx_uint_t) n;
+
+    } else {
+        imcf->shcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
+        if (imcf->shcv == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        *imcf->shcv = cv;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_image_filter_init(ngx_conf_t *cf)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_image_header_filter;
+
+    ngx_http_next_body_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_image_body_filter;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_index_module.c b/nginx/src/http/modules/ngx_http_index_module.c
new file mode 100644 (file)
index 0000000..c144b31
--- /dev/null
@@ -0,0 +1,540 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_str_t                name;
+    ngx_array_t             *lengths;
+    ngx_array_t             *values;
+} ngx_http_index_t;
+
+
+typedef struct {
+    ngx_array_t             *indices;    /* array of ngx_http_index_t */
+    size_t                   max_index_len;
+} ngx_http_index_loc_conf_t;
+
+
+#define NGX_HTTP_DEFAULT_INDEX   "index.html"
+
+
+static ngx_int_t ngx_http_index_test_dir(ngx_http_request_t *r,
+    ngx_http_core_loc_conf_t *clcf, u_char *path, u_char *last);
+static ngx_int_t ngx_http_index_error(ngx_http_request_t *r,
+    ngx_http_core_loc_conf_t *clcf, u_char *file, ngx_err_t err);
+
+static ngx_int_t ngx_http_index_init(ngx_conf_t *cf);
+static void *ngx_http_index_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_index_merge_loc_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+static char *ngx_http_index_set_index(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+
+static ngx_command_t  ngx_http_index_commands[] = {
+
+    { ngx_string("index"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_index_set_index,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_index_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_index_init,                   /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_index_create_loc_conf,        /* create location configuration */
+    ngx_http_index_merge_loc_conf          /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_index_module = {
+    NGX_MODULE_V1,
+    &ngx_http_index_module_ctx,            /* module context */
+    ngx_http_index_commands,               /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+/*
+ * Try to open/test the first index file before the test of directory
+ * existence because valid requests should prevail over invalid ones.
+ * If open()/stat() of a file will fail then stat() of a directory
+ * should be faster because kernel may have already cached some data.
+ * Besides, Win32 may return ERROR_PATH_NOT_FOUND (NGX_ENOTDIR) at once.
+ * Unix has ENOTDIR error; however, it's less helpful than Win32's one:
+ * it only indicates that path points to a regular file, not a directory.
+ */
+
+static ngx_int_t
+ngx_http_index_handler(ngx_http_request_t *r)
+{
+    u_char                       *p, *name;
+    size_t                        len, root, reserve, allocated;
+    ngx_int_t                     rc;
+    ngx_str_t                     path, uri;
+    ngx_uint_t                    i, dir_tested;
+    ngx_http_index_t             *index;
+    ngx_open_file_info_t          of;
+    ngx_http_script_code_pt       code;
+    ngx_http_script_engine_t      e;
+    ngx_http_core_loc_conf_t     *clcf;
+    ngx_http_index_loc_conf_t    *ilcf;
+    ngx_http_script_len_code_pt   lcode;
+
+    if (r->uri.data[r->uri.len - 1] != '/') {
+        return NGX_DECLINED;
+    }
+
+    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_POST))) {
+        return NGX_DECLINED;
+    }
+
+    ilcf = ngx_http_get_module_loc_conf(r, ngx_http_index_module);
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    allocated = 0;
+    root = 0;
+    dir_tested = 0;
+    name = NULL;
+    /* suppress MSVC warning */
+    path.data = NULL;
+
+    index = ilcf->indices->elts;
+    for (i = 0; i < ilcf->indices->nelts; i++) {
+
+        if (index[i].lengths == NULL) {
+
+            if (index[i].name.data[0] == '/') {
+                return ngx_http_internal_redirect(r, &index[i].name, &r->args);
+            }
+
+            reserve = ilcf->max_index_len;
+            len = index[i].name.len;
+
+        } else {
+            ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
+
+            e.ip = index[i].lengths->elts;
+            e.request = r;
+            e.flushed = 1;
+
+            /* 1 is for terminating '\0' as in static names */
+            len = 1;
+
+            while (*(uintptr_t *) e.ip) {
+                lcode = *(ngx_http_script_len_code_pt *) e.ip;
+                len += lcode(&e);
+            }
+
+            /* 16 bytes are preallocation */
+
+            reserve = len + 16;
+        }
+
+        if (reserve > allocated) {
+
+            name = ngx_http_map_uri_to_path(r, &path, &root, reserve);
+            if (name == NULL) {
+                return NGX_ERROR;
+            }
+
+            allocated = path.data + path.len - name;
+        }
+
+        if (index[i].values == NULL) {
+
+            /* index[i].name.len includes the terminating '\0' */
+
+            ngx_memcpy(name, index[i].name.data, index[i].name.len);
+
+            path.len = (name + index[i].name.len - 1) - path.data;
+
+        } else {
+            e.ip = index[i].values->elts;
+            e.pos = name;
+
+            while (*(uintptr_t *) e.ip) {
+                code = *(ngx_http_script_code_pt *) e.ip;
+                code((ngx_http_script_engine_t *) &e);
+            }
+
+            if (*name == '/') {
+                uri.len = len - 1;
+                uri.data = name;
+                return ngx_http_internal_redirect(r, &uri, &r->args);
+            }
+
+            path.len = e.pos - path.data;
+
+            *e.pos = '\0';
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "open index \"%V\"", &path);
+
+        ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+        of.read_ahead = clcf->read_ahead;
+        of.directio = clcf->directio;
+        of.valid = clcf->open_file_cache_valid;
+        of.min_uses = clcf->open_file_cache_min_uses;
+        of.test_only = 1;
+        of.errors = clcf->open_file_cache_errors;
+        of.events = clcf->open_file_cache_events;
+
+        if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
+            != NGX_OK)
+        {
+            if (of.err == 0) {
+                return NGX_HTTP_INTERNAL_SERVER_ERROR;
+            }
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, of.err,
+                           "%s \"%s\" failed", of.failed, path.data);
+
+#if (NGX_HAVE_OPENAT)
+            if (of.err == NGX_EMLINK
+                || of.err == NGX_ELOOP)
+            {
+                return NGX_HTTP_FORBIDDEN;
+            }
+#endif
+
+            if (of.err == NGX_ENOTDIR
+                || of.err == NGX_ENAMETOOLONG
+                || of.err == NGX_EACCES)
+            {
+                return ngx_http_index_error(r, clcf, path.data, of.err);
+            }
+
+            if (!dir_tested) {
+                rc = ngx_http_index_test_dir(r, clcf, path.data, name - 1);
+
+                if (rc != NGX_OK) {
+                    return rc;
+                }
+
+                dir_tested = 1;
+            }
+
+            if (of.err == NGX_ENOENT) {
+                continue;
+            }
+
+            ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err,
+                          "%s \"%s\" failed", of.failed, path.data);
+
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        uri.len = r->uri.len + len - 1;
+
+        if (!clcf->alias) {
+            uri.data = path.data + root;
+
+        } else {
+            uri.data = ngx_pnalloc(r->pool, uri.len);
+            if (uri.data == NULL) {
+                return NGX_HTTP_INTERNAL_SERVER_ERROR;
+            }
+
+            p = ngx_copy(uri.data, r->uri.data, r->uri.len);
+            ngx_memcpy(p, name, len - 1);
+        }
+
+        return ngx_http_internal_redirect(r, &uri, &r->args);
+    }
+
+    return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_http_index_test_dir(ngx_http_request_t *r, ngx_http_core_loc_conf_t *clcf,
+    u_char *path, u_char *last)
+{
+    u_char                c;
+    ngx_str_t             dir;
+    ngx_open_file_info_t  of;
+
+    c = *last;
+    if (c != '/' || path == last) {
+        /* "alias" without trailing slash */
+        c = *(++last);
+    }
+    *last = '\0';
+
+    dir.len = last - path;
+    dir.data = path;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http index check dir: \"%V\"", &dir);
+
+    ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+    of.test_dir = 1;
+    of.test_only = 1;
+    of.valid = clcf->open_file_cache_valid;
+    of.errors = clcf->open_file_cache_errors;
+
+    if (ngx_http_set_disable_symlinks(r, clcf, &dir, &of) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (ngx_open_cached_file(clcf->open_file_cache, &dir, &of, r->pool)
+        != NGX_OK)
+    {
+        if (of.err) {
+
+#if (NGX_HAVE_OPENAT)
+            if (of.err == NGX_EMLINK
+                || of.err == NGX_ELOOP)
+            {
+                return NGX_HTTP_FORBIDDEN;
+            }
+#endif
+
+            if (of.err == NGX_ENOENT) {
+                *last = c;
+                return ngx_http_index_error(r, clcf, dir.data, NGX_ENOENT);
+            }
+
+            if (of.err == NGX_EACCES) {
+
+                *last = c;
+
+                /*
+                 * ngx_http_index_test_dir() is called after the first index
+                 * file testing has returned an error distinct from NGX_EACCES.
+                 * This means that directory searching is allowed.
+                 */
+
+                return NGX_OK;
+            }
+
+            ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err,
+                          "%s \"%s\" failed", of.failed, dir.data);
+        }
+
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    *last = c;
+
+    if (of.is_dir) {
+        return NGX_OK;
+    }
+
+    ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                  "\"%s\" is not a directory", dir.data);
+
+    return NGX_HTTP_INTERNAL_SERVER_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_index_error(ngx_http_request_t *r, ngx_http_core_loc_conf_t  *clcf,
+    u_char *file, ngx_err_t err)
+{
+    if (err == NGX_EACCES) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, err,
+                      "\"%s\" is forbidden", file);
+
+        return NGX_HTTP_FORBIDDEN;
+    }
+
+    if (clcf->log_not_found) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, err,
+                      "\"%s\" is not found", file);
+    }
+
+    return NGX_HTTP_NOT_FOUND;
+}
+
+
+static void *
+ngx_http_index_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_index_loc_conf_t  *conf;
+
+    conf = ngx_palloc(cf->pool, sizeof(ngx_http_index_loc_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    conf->indices = NULL;
+    conf->max_index_len = 0;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_index_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_index_loc_conf_t  *prev = parent;
+    ngx_http_index_loc_conf_t  *conf = child;
+
+    ngx_http_index_t  *index;
+
+    if (conf->indices == NULL) {
+        conf->indices = prev->indices;
+        conf->max_index_len = prev->max_index_len;
+    }
+
+    if (conf->indices == NULL) {
+        conf->indices = ngx_array_create(cf->pool, 1, sizeof(ngx_http_index_t));
+        if (conf->indices == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        index = ngx_array_push(conf->indices);
+        if (index == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        index->name.len = sizeof(NGX_HTTP_DEFAULT_INDEX);
+        index->name.data = (u_char *) NGX_HTTP_DEFAULT_INDEX;
+        index->lengths = NULL;
+        index->values = NULL;
+
+        conf->max_index_len = sizeof(NGX_HTTP_DEFAULT_INDEX);
+
+        return NGX_CONF_OK;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_index_init(ngx_conf_t *cf)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_index_handler;
+
+    return NGX_OK;
+}
+
+
+/* TODO: warn about duplicate indices */
+
+static char *
+ngx_http_index_set_index(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_index_loc_conf_t *ilcf = conf;
+
+    ngx_str_t                  *value;
+    ngx_uint_t                  i, n;
+    ngx_http_index_t           *index;
+    ngx_http_script_compile_t   sc;
+
+    if (ilcf->indices == NULL) {
+        ilcf->indices = ngx_array_create(cf->pool, 2, sizeof(ngx_http_index_t));
+        if (ilcf->indices == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    value = cf->args->elts;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+
+        if (value[i].data[0] == '/' && i != cf->args->nelts - 1) {
+            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                               "only the last index in \"index\" directive "
+                               "should be absolute");
+        }
+
+        if (value[i].len == 0) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "index \"%V\" in \"index\" directive is invalid",
+                               &value[1]);
+            return NGX_CONF_ERROR;
+        }
+
+        index = ngx_array_push(ilcf->indices);
+        if (index == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        index->name.len = value[i].len;
+        index->name.data = value[i].data;
+        index->lengths = NULL;
+        index->values = NULL;
+
+        n = ngx_http_script_variables_count(&value[i]);
+
+        if (n == 0) {
+            if (ilcf->max_index_len < index->name.len) {
+                ilcf->max_index_len = index->name.len;
+            }
+
+            if (index->name.data[0] == '/') {
+                continue;
+            }
+
+            /* include the terminating '\0' to the length to use ngx_memcpy() */
+            index->name.len++;
+
+            continue;
+        }
+
+        ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+        sc.cf = cf;
+        sc.source = &value[i];
+        sc.lengths = &index->lengths;
+        sc.values = &index->values;
+        sc.variables = n;
+        sc.complete_lengths = 1;
+        sc.complete_values = 1;
+
+        if (ngx_http_script_compile(&sc) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_limit_conn_module.c b/nginx/src/http/modules/ngx_http_limit_conn_module.c
new file mode 100644 (file)
index 0000000..913d599
--- /dev/null
@@ -0,0 +1,670 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    u_char                     color;
+    u_char                     len;
+    u_short                    conn;
+    u_char                     data[1];
+} ngx_http_limit_conn_node_t;
+
+
+typedef struct {
+    ngx_shm_zone_t            *shm_zone;
+    ngx_rbtree_node_t         *node;
+} ngx_http_limit_conn_cleanup_t;
+
+
+typedef struct {
+    ngx_rbtree_t              *rbtree;
+    ngx_http_complex_value_t   key;
+} ngx_http_limit_conn_ctx_t;
+
+
+typedef struct {
+    ngx_shm_zone_t            *shm_zone;
+    ngx_uint_t                 conn;
+} ngx_http_limit_conn_limit_t;
+
+
+typedef struct {
+    ngx_array_t                limits;
+    ngx_uint_t                 log_level;
+    ngx_uint_t                 status_code;
+} ngx_http_limit_conn_conf_t;
+
+
+static ngx_rbtree_node_t *ngx_http_limit_conn_lookup(ngx_rbtree_t *rbtree,
+    ngx_str_t *key, uint32_t hash);
+static void ngx_http_limit_conn_cleanup(void *data);
+static ngx_inline void ngx_http_limit_conn_cleanup_all(ngx_pool_t *pool);
+
+static void *ngx_http_limit_conn_create_conf(ngx_conf_t *cf);
+static char *ngx_http_limit_conn_merge_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static char *ngx_http_limit_conn_zone(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_limit_conn(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static ngx_int_t ngx_http_limit_conn_init(ngx_conf_t *cf);
+
+
+static ngx_conf_enum_t  ngx_http_limit_conn_log_levels[] = {
+    { ngx_string("info"), NGX_LOG_INFO },
+    { ngx_string("notice"), NGX_LOG_NOTICE },
+    { ngx_string("warn"), NGX_LOG_WARN },
+    { ngx_string("error"), NGX_LOG_ERR },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_num_bounds_t  ngx_http_limit_conn_status_bounds = {
+    ngx_conf_check_num_bounds, 400, 599
+};
+
+
+static ngx_command_t  ngx_http_limit_conn_commands[] = {
+
+    { ngx_string("limit_conn_zone"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE2,
+      ngx_http_limit_conn_zone,
+      0,
+      0,
+      NULL },
+
+    { ngx_string("limit_conn"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+      ngx_http_limit_conn,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("limit_conn_log_level"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_limit_conn_conf_t, log_level),
+      &ngx_http_limit_conn_log_levels },
+
+    { ngx_string("limit_conn_status"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_limit_conn_conf_t, status_code),
+      &ngx_http_limit_conn_status_bounds },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_limit_conn_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_limit_conn_init,              /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_limit_conn_create_conf,       /* create location configuration */
+    ngx_http_limit_conn_merge_conf         /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_limit_conn_module = {
+    NGX_MODULE_V1,
+    &ngx_http_limit_conn_module_ctx,       /* module context */
+    ngx_http_limit_conn_commands,          /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_limit_conn_handler(ngx_http_request_t *r)
+{
+    size_t                          n;
+    uint32_t                        hash;
+    ngx_str_t                       key;
+    ngx_uint_t                      i;
+    ngx_slab_pool_t                *shpool;
+    ngx_rbtree_node_t              *node;
+    ngx_pool_cleanup_t             *cln;
+    ngx_http_limit_conn_ctx_t      *ctx;
+    ngx_http_limit_conn_node_t     *lc;
+    ngx_http_limit_conn_conf_t     *lccf;
+    ngx_http_limit_conn_limit_t    *limits;
+    ngx_http_limit_conn_cleanup_t  *lccln;
+
+    if (r->main->limit_conn_set) {
+        return NGX_DECLINED;
+    }
+
+    lccf = ngx_http_get_module_loc_conf(r, ngx_http_limit_conn_module);
+    limits = lccf->limits.elts;
+
+    for (i = 0; i < lccf->limits.nelts; i++) {
+        ctx = limits[i].shm_zone->data;
+
+        if (ngx_http_complex_value(r, &ctx->key, &key) != NGX_OK) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        if (key.len == 0) {
+            continue;
+        }
+
+        if (key.len > 255) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "the value of the \"%V\" key "
+                          "is more than 255 bytes: \"%V\"",
+                          &ctx->key.value, &key);
+            continue;
+        }
+
+        r->main->limit_conn_set = 1;
+
+        hash = ngx_crc32_short(key.data, key.len);
+
+        shpool = (ngx_slab_pool_t *) limits[i].shm_zone->shm.addr;
+
+        ngx_shmtx_lock(&shpool->mutex);
+
+        node = ngx_http_limit_conn_lookup(ctx->rbtree, &key, hash);
+
+        if (node == NULL) {
+
+            n = offsetof(ngx_rbtree_node_t, color)
+                + offsetof(ngx_http_limit_conn_node_t, data)
+                + key.len;
+
+            node = ngx_slab_alloc_locked(shpool, n);
+
+            if (node == NULL) {
+                ngx_shmtx_unlock(&shpool->mutex);
+                ngx_http_limit_conn_cleanup_all(r->pool);
+                return lccf->status_code;
+            }
+
+            lc = (ngx_http_limit_conn_node_t *) &node->color;
+
+            node->key = hash;
+            lc->len = (u_char) key.len;
+            lc->conn = 1;
+            ngx_memcpy(lc->data, key.data, key.len);
+
+            ngx_rbtree_insert(ctx->rbtree, node);
+
+        } else {
+
+            lc = (ngx_http_limit_conn_node_t *) &node->color;
+
+            if ((ngx_uint_t) lc->conn >= limits[i].conn) {
+
+                ngx_shmtx_unlock(&shpool->mutex);
+
+                ngx_log_error(lccf->log_level, r->connection->log, 0,
+                              "limiting connections by zone \"%V\"",
+                              &limits[i].shm_zone->shm.name);
+
+                ngx_http_limit_conn_cleanup_all(r->pool);
+                return lccf->status_code;
+            }
+
+            lc->conn++;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "limit conn: %08Xi %d", node->key, lc->conn);
+
+        ngx_shmtx_unlock(&shpool->mutex);
+
+        cln = ngx_pool_cleanup_add(r->pool,
+                                   sizeof(ngx_http_limit_conn_cleanup_t));
+        if (cln == NULL) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        cln->handler = ngx_http_limit_conn_cleanup;
+        lccln = cln->data;
+
+        lccln->shm_zone = limits[i].shm_zone;
+        lccln->node = node;
+    }
+
+    return NGX_DECLINED;
+}
+
+
+static void
+ngx_http_limit_conn_rbtree_insert_value(ngx_rbtree_node_t *temp,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
+{
+    ngx_rbtree_node_t           **p;
+    ngx_http_limit_conn_node_t   *lcn, *lcnt;
+
+    for ( ;; ) {
+
+        if (node->key < temp->key) {
+
+            p = &temp->left;
+
+        } else if (node->key > temp->key) {
+
+            p = &temp->right;
+
+        } else { /* node->key == temp->key */
+
+            lcn = (ngx_http_limit_conn_node_t *) &node->color;
+            lcnt = (ngx_http_limit_conn_node_t *) &temp->color;
+
+            p = (ngx_memn2cmp(lcn->data, lcnt->data, lcn->len, lcnt->len) < 0)
+                ? &temp->left : &temp->right;
+        }
+
+        if (*p == sentinel) {
+            break;
+        }
+
+        temp = *p;
+    }
+
+    *p = node;
+    node->parent = temp;
+    node->left = sentinel;
+    node->right = sentinel;
+    ngx_rbt_red(node);
+}
+
+
+static ngx_rbtree_node_t *
+ngx_http_limit_conn_lookup(ngx_rbtree_t *rbtree, ngx_str_t *key, uint32_t hash)
+{
+    ngx_int_t                    rc;
+    ngx_rbtree_node_t           *node, *sentinel;
+    ngx_http_limit_conn_node_t  *lcn;
+
+    node = rbtree->root;
+    sentinel = rbtree->sentinel;
+
+    while (node != sentinel) {
+
+        if (hash < node->key) {
+            node = node->left;
+            continue;
+        }
+
+        if (hash > node->key) {
+            node = node->right;
+            continue;
+        }
+
+        /* hash == node->key */
+
+        lcn = (ngx_http_limit_conn_node_t *) &node->color;
+
+        rc = ngx_memn2cmp(key->data, lcn->data, key->len, (size_t) lcn->len);
+
+        if (rc == 0) {
+            return node;
+        }
+
+        node = (rc < 0) ? node->left : node->right;
+    }
+
+    return NULL;
+}
+
+
+static void
+ngx_http_limit_conn_cleanup(void *data)
+{
+    ngx_http_limit_conn_cleanup_t  *lccln = data;
+
+    ngx_slab_pool_t             *shpool;
+    ngx_rbtree_node_t           *node;
+    ngx_http_limit_conn_ctx_t   *ctx;
+    ngx_http_limit_conn_node_t  *lc;
+
+    ctx = lccln->shm_zone->data;
+    shpool = (ngx_slab_pool_t *) lccln->shm_zone->shm.addr;
+    node = lccln->node;
+    lc = (ngx_http_limit_conn_node_t *) &node->color;
+
+    ngx_shmtx_lock(&shpool->mutex);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, lccln->shm_zone->shm.log, 0,
+                   "limit conn cleanup: %08Xi %d", node->key, lc->conn);
+
+    lc->conn--;
+
+    if (lc->conn == 0) {
+        ngx_rbtree_delete(ctx->rbtree, node);
+        ngx_slab_free_locked(shpool, node);
+    }
+
+    ngx_shmtx_unlock(&shpool->mutex);
+}
+
+
+static ngx_inline void
+ngx_http_limit_conn_cleanup_all(ngx_pool_t *pool)
+{
+    ngx_pool_cleanup_t  *cln;
+
+    cln = pool->cleanup;
+
+    while (cln && cln->handler == ngx_http_limit_conn_cleanup) {
+        ngx_http_limit_conn_cleanup(cln->data);
+        cln = cln->next;
+    }
+
+    pool->cleanup = cln;
+}
+
+
+static ngx_int_t
+ngx_http_limit_conn_init_zone(ngx_shm_zone_t *shm_zone, void *data)
+{
+    ngx_http_limit_conn_ctx_t  *octx = data;
+
+    size_t                      len;
+    ngx_slab_pool_t            *shpool;
+    ngx_rbtree_node_t          *sentinel;
+    ngx_http_limit_conn_ctx_t  *ctx;
+
+    ctx = shm_zone->data;
+
+    if (octx) {
+        if (ctx->key.value.len != octx->key.value.len
+            || ngx_strncmp(ctx->key.value.data, octx->key.value.data,
+                           ctx->key.value.len)
+               != 0)
+        {
+            ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
+                          "limit_conn_zone \"%V\" uses the \"%V\" key "
+                          "while previously it used the \"%V\" key",
+                          &shm_zone->shm.name, &ctx->key.value,
+                          &octx->key.value);
+            return NGX_ERROR;
+        }
+
+        ctx->rbtree = octx->rbtree;
+
+        return NGX_OK;
+    }
+
+    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+    if (shm_zone->shm.exists) {
+        ctx->rbtree = shpool->data;
+
+        return NGX_OK;
+    }
+
+    ctx->rbtree = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_t));
+    if (ctx->rbtree == NULL) {
+        return NGX_ERROR;
+    }
+
+    shpool->data = ctx->rbtree;
+
+    sentinel = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_node_t));
+    if (sentinel == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_rbtree_init(ctx->rbtree, sentinel,
+                    ngx_http_limit_conn_rbtree_insert_value);
+
+    len = sizeof(" in limit_conn_zone \"\"") + shm_zone->shm.name.len;
+
+    shpool->log_ctx = ngx_slab_alloc(shpool, len);
+    if (shpool->log_ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_sprintf(shpool->log_ctx, " in limit_conn_zone \"%V\"%Z",
+                &shm_zone->shm.name);
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_limit_conn_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_limit_conn_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_conn_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->limits.elts = NULL;
+     */
+
+    conf->log_level = NGX_CONF_UNSET_UINT;
+    conf->status_code = NGX_CONF_UNSET_UINT;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_limit_conn_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_limit_conn_conf_t *prev = parent;
+    ngx_http_limit_conn_conf_t *conf = child;
+
+    if (conf->limits.elts == NULL) {
+        conf->limits = prev->limits;
+    }
+
+    ngx_conf_merge_uint_value(conf->log_level, prev->log_level, NGX_LOG_ERR);
+    ngx_conf_merge_uint_value(conf->status_code, prev->status_code,
+                              NGX_HTTP_SERVICE_UNAVAILABLE);
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_limit_conn_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    u_char                            *p;
+    ssize_t                            size;
+    ngx_str_t                         *value, name, s;
+    ngx_uint_t                         i;
+    ngx_shm_zone_t                    *shm_zone;
+    ngx_http_limit_conn_ctx_t         *ctx;
+    ngx_http_compile_complex_value_t   ccv;
+
+    value = cf->args->elts;
+
+    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_conn_ctx_t));
+    if (ctx == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[1];
+    ccv.complex_value = &ctx->key;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    size = 0;
+    name.len = 0;
+
+    for (i = 2; i < cf->args->nelts; i++) {
+
+        if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
+
+            name.data = value[i].data + 5;
+
+            p = (u_char *) ngx_strchr(name.data, ':');
+
+            if (p == NULL) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid zone size \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            name.len = p - name.data;
+
+            s.data = p + 1;
+            s.len = value[i].data + value[i].len - s.data;
+
+            size = ngx_parse_size(&s);
+
+            if (size == NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid zone size \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            if (size < (ssize_t) (8 * ngx_pagesize)) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "zone \"%V\" is too small", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[i]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (name.len == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"%V\" must have \"zone\" parameter",
+                           &cmd->name);
+        return NGX_CONF_ERROR;
+    }
+
+    shm_zone = ngx_shared_memory_add(cf, &name, size,
+                                     &ngx_http_limit_conn_module);
+    if (shm_zone == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (shm_zone->data) {
+        ctx = shm_zone->data;
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "%V \"%V\" is already bound to key \"%V\"",
+                           &cmd->name, &name, &ctx->key.value);
+        return NGX_CONF_ERROR;
+    }
+
+    shm_zone->init = ngx_http_limit_conn_init_zone;
+    shm_zone->data = ctx;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_limit_conn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_shm_zone_t               *shm_zone;
+    ngx_http_limit_conn_conf_t   *lccf = conf;
+    ngx_http_limit_conn_limit_t  *limit, *limits;
+
+    ngx_str_t  *value;
+    ngx_int_t   n;
+    ngx_uint_t  i;
+
+    value = cf->args->elts;
+
+    shm_zone = ngx_shared_memory_add(cf, &value[1], 0,
+                                     &ngx_http_limit_conn_module);
+    if (shm_zone == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    limits = lccf->limits.elts;
+
+    if (limits == NULL) {
+        if (ngx_array_init(&lccf->limits, cf->pool, 1,
+                           sizeof(ngx_http_limit_conn_limit_t))
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    for (i = 0; i < lccf->limits.nelts; i++) {
+        if (shm_zone == limits[i].shm_zone) {
+            return "is duplicate";
+        }
+    }
+
+    n = ngx_atoi(value[2].data, value[2].len);
+    if (n <= 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid number of connections \"%V\"", &value[2]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (n > 65535) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "connection limit must be less 65536");
+        return NGX_CONF_ERROR;
+    }
+
+    limit = ngx_array_push(&lccf->limits);
+    if (limit == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    limit->conn = n;
+    limit->shm_zone = shm_zone;
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_limit_conn_init(ngx_conf_t *cf)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_limit_conn_handler;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_limit_req_module.c b/nginx/src/http/modules/ngx_http_limit_req_module.c
new file mode 100644 (file)
index 0000000..579b13c
--- /dev/null
@@ -0,0 +1,960 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    u_char                       color;
+    u_char                       dummy;
+    u_short                      len;
+    ngx_queue_t                  queue;
+    ngx_msec_t                   last;
+    /* integer value, 1 corresponds to 0.001 r/s */
+    ngx_uint_t                   excess;
+    ngx_uint_t                   count;
+    u_char                       data[1];
+} ngx_http_limit_req_node_t;
+
+
+typedef struct {
+    ngx_rbtree_t                  rbtree;
+    ngx_rbtree_node_t             sentinel;
+    ngx_queue_t                   queue;
+} ngx_http_limit_req_shctx_t;
+
+
+typedef struct {
+    ngx_http_limit_req_shctx_t  *sh;
+    ngx_slab_pool_t             *shpool;
+    /* integer value, 1 corresponds to 0.001 r/s */
+    ngx_uint_t                   rate;
+    ngx_http_complex_value_t     key;
+    ngx_http_limit_req_node_t   *node;
+} ngx_http_limit_req_ctx_t;
+
+
+typedef struct {
+    ngx_shm_zone_t              *shm_zone;
+    /* integer value, 1 corresponds to 0.001 r/s */
+    ngx_uint_t                   burst;
+    ngx_uint_t                   nodelay; /* unsigned  nodelay:1 */
+} ngx_http_limit_req_limit_t;
+
+
+typedef struct {
+    ngx_array_t                  limits;
+    ngx_uint_t                   limit_log_level;
+    ngx_uint_t                   delay_log_level;
+    ngx_uint_t                   status_code;
+} ngx_http_limit_req_conf_t;
+
+
+static void ngx_http_limit_req_delay(ngx_http_request_t *r);
+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_expire(ngx_http_limit_req_ctx_t *ctx,
+    ngx_uint_t n);
+
+static void *ngx_http_limit_req_create_conf(ngx_conf_t *cf);
+static char *ngx_http_limit_req_merge_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static char *ngx_http_limit_req_zone(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_limit_req(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static ngx_int_t ngx_http_limit_req_init(ngx_conf_t *cf);
+
+
+static ngx_conf_enum_t  ngx_http_limit_req_log_levels[] = {
+    { ngx_string("info"), NGX_LOG_INFO },
+    { ngx_string("notice"), NGX_LOG_NOTICE },
+    { ngx_string("warn"), NGX_LOG_WARN },
+    { ngx_string("error"), NGX_LOG_ERR },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_num_bounds_t  ngx_http_limit_req_status_bounds = {
+    ngx_conf_check_num_bounds, 400, 599
+};
+
+
+static ngx_command_t  ngx_http_limit_req_commands[] = {
+
+    { ngx_string("limit_req_zone"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE3,
+      ngx_http_limit_req_zone,
+      0,
+      0,
+      NULL },
+
+    { ngx_string("limit_req"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
+      ngx_http_limit_req,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("limit_req_log_level"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_limit_req_conf_t, limit_log_level),
+      &ngx_http_limit_req_log_levels },
+
+    { ngx_string("limit_req_status"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_limit_req_conf_t, status_code),
+      &ngx_http_limit_req_status_bounds },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_limit_req_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_limit_req_init,               /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_limit_req_create_conf,        /* create location configuration */
+    ngx_http_limit_req_merge_conf          /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_limit_req_module = {
+    NGX_MODULE_V1,
+    &ngx_http_limit_req_module_ctx,        /* module context */
+    ngx_http_limit_req_commands,           /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_limit_req_handler(ngx_http_request_t *r)
+{
+    uint32_t                     hash;
+    ngx_str_t                    key;
+    ngx_int_t                    rc;
+    ngx_uint_t                   n, excess;
+    ngx_msec_t                   delay;
+    ngx_http_limit_req_ctx_t    *ctx;
+    ngx_http_limit_req_conf_t   *lrcf;
+    ngx_http_limit_req_limit_t  *limit, *limits;
+
+    if (r->main->limit_req_set) {
+        return NGX_DECLINED;
+    }
+
+    lrcf = ngx_http_get_module_loc_conf(r, ngx_http_limit_req_module);
+    limits = lrcf->limits.elts;
+
+    excess = 0;
+
+    rc = NGX_DECLINED;
+
+#if (NGX_SUPPRESS_WARN)
+    limit = NULL;
+#endif
+
+    for (n = 0; n < lrcf->limits.nelts; n++) {
+
+        limit = &limits[n];
+
+        ctx = limit->shm_zone->data;
+
+        if (ngx_http_complex_value(r, &ctx->key, &key) != NGX_OK) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        if (key.len == 0) {
+            continue;
+        }
+
+        if (key.len > 65535) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "the value of the \"%V\" key "
+                          "is more than 65535 bytes: \"%V\"",
+                          &ctx->key.value, &key);
+            continue;
+        }
+
+        hash = ngx_crc32_short(key.data, key.len);
+
+        ngx_shmtx_lock(&ctx->shpool->mutex);
+
+        rc = ngx_http_limit_req_lookup(limit, hash, &key, &excess,
+                                       (n == lrcf->limits.nelts - 1));
+
+        ngx_shmtx_unlock(&ctx->shpool->mutex);
+
+        ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "limit_req[%ui]: %i %ui.%03ui",
+                       n, rc, excess / 1000, excess % 1000);
+
+        if (rc != NGX_AGAIN) {
+            break;
+        }
+    }
+
+    if (rc == NGX_DECLINED) {
+        return NGX_DECLINED;
+    }
+
+    r->main->limit_req_set = 1;
+
+    if (rc == NGX_BUSY || rc == NGX_ERROR) {
+
+        if (rc == NGX_BUSY) {
+            ngx_log_error(lrcf->limit_log_level, r->connection->log, 0,
+                          "limiting requests, excess: %ui.%03ui by zone \"%V\"",
+                          excess / 1000, excess % 1000,
+                          &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;
+        }
+
+        return lrcf->status_code;
+    }
+
+    /* rc == NGX_AGAIN || rc == NGX_OK */
+
+    if (rc == NGX_AGAIN) {
+        excess = 0;
+    }
+
+    delay = ngx_http_limit_req_account(limits, n, &excess, &limit);
+
+    if (!delay) {
+        return NGX_DECLINED;
+    }
+
+    ngx_log_error(lrcf->delay_log_level, r->connection->log, 0,
+                  "delaying request, excess: %ui.%03ui, by zone \"%V\"",
+                  excess / 1000, excess % 1000, &limit->shm_zone->shm.name);
+
+    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;
+    r->write_event_handler = ngx_http_limit_req_delay;
+
+    r->connection->write->delayed = 1;
+    ngx_add_timer(r->connection->write, delay);
+
+    return NGX_AGAIN;
+}
+
+
+static void
+ngx_http_limit_req_delay(ngx_http_request_t *r)
+{
+    ngx_event_t  *wev;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "limit_req delay");
+
+    wev = r->connection->write;
+
+    if (wev->delayed) {
+
+        if (ngx_handle_write_event(wev, 0) != NGX_OK) {
+            ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        }
+
+        return;
+    }
+
+    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_block_reading;
+    r->write_event_handler = ngx_http_core_run_phases;
+
+    ngx_http_core_run_phases(r);
+}
+
+
+static void
+ngx_http_limit_req_rbtree_insert_value(ngx_rbtree_node_t *temp,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
+{
+    ngx_rbtree_node_t          **p;
+    ngx_http_limit_req_node_t   *lrn, *lrnt;
+
+    for ( ;; ) {
+
+        if (node->key < temp->key) {
+
+            p = &temp->left;
+
+        } else if (node->key > temp->key) {
+
+            p = &temp->right;
+
+        } else { /* node->key == temp->key */
+
+            lrn = (ngx_http_limit_req_node_t *) &node->color;
+            lrnt = (ngx_http_limit_req_node_t *) &temp->color;
+
+            p = (ngx_memn2cmp(lrn->data, lrnt->data, lrn->len, lrnt->len) < 0)
+                ? &temp->left : &temp->right;
+        }
+
+        if (*p == sentinel) {
+            break;
+        }
+
+        temp = *p;
+    }
+
+    *p = node;
+    node->parent = temp;
+    node->left = sentinel;
+    node->right = sentinel;
+    ngx_rbt_red(node);
+}
+
+
+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)
+{
+    size_t                      size;
+    ngx_int_t                   rc, excess;
+    ngx_msec_t                  now;
+    ngx_msec_int_t              ms;
+    ngx_rbtree_node_t          *node, *sentinel;
+    ngx_http_limit_req_ctx_t   *ctx;
+    ngx_http_limit_req_node_t  *lr;
+
+    now = ngx_current_msec;
+
+    ctx = limit->shm_zone->data;
+
+    node = ctx->sh->rbtree.root;
+    sentinel = ctx->sh->rbtree.sentinel;
+
+    while (node != sentinel) {
+
+        if (hash < node->key) {
+            node = node->left;
+            continue;
+        }
+
+        if (hash > node->key) {
+            node = node->right;
+            continue;
+        }
+
+        /* hash == node->key */
+
+        lr = (ngx_http_limit_req_node_t *) &node->color;
+
+        rc = ngx_memn2cmp(key->data, lr->data, key->len, (size_t) lr->len);
+
+        if (rc == 0) {
+            ngx_queue_remove(&lr->queue);
+            ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);
+
+            ms = (ngx_msec_int_t) (now - lr->last);
+
+            excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000;
+
+            if (excess < 0) {
+                excess = 0;
+            }
+
+            *ep = excess;
+
+            if ((ngx_uint_t) excess > limit->burst) {
+                return NGX_BUSY;
+            }
+
+            if (account) {
+                lr->excess = excess;
+                lr->last = now;
+                return NGX_OK;
+            }
+
+            lr->count++;
+
+            ctx->node = lr;
+
+            return NGX_AGAIN;
+        }
+
+        node = (rc < 0) ? node->left : node->right;
+    }
+
+    *ep = 0;
+
+    size = offsetof(ngx_rbtree_node_t, color)
+           + offsetof(ngx_http_limit_req_node_t, data)
+           + key->len;
+
+    ngx_http_limit_req_expire(ctx, 1);
+
+    node = ngx_slab_alloc_locked(ctx->shpool, size);
+
+    if (node == NULL) {
+        ngx_http_limit_req_expire(ctx, 0);
+
+        node = ngx_slab_alloc_locked(ctx->shpool, size);
+        if (node == NULL) {
+            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+                          "could not allocate node%s", ctx->shpool->log_ctx);
+            return NGX_ERROR;
+        }
+    }
+
+    node->key = hash;
+
+    lr = (ngx_http_limit_req_node_t *) &node->color;
+
+    lr->len = (u_short) key->len;
+    lr->excess = 0;
+
+    ngx_memcpy(lr->data, key->data, key->len);
+
+    ngx_rbtree_insert(&ctx->sh->rbtree, node);
+
+    ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);
+
+    if (account) {
+        lr->last = now;
+        lr->count = 0;
+        return NGX_OK;
+    }
+
+    lr->last = 0;
+    lr->count = 1;
+
+    ctx->node = lr;
+
+    return NGX_AGAIN;
+}
+
+
+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)
+{
+    ngx_int_t                   excess;
+    ngx_msec_t                  now, delay, max_delay;
+    ngx_msec_int_t              ms;
+    ngx_http_limit_req_ctx_t   *ctx;
+    ngx_http_limit_req_node_t  *lr;
+
+    excess = *ep;
+
+    if (excess == 0 || (*limit)->nodelay) {
+        max_delay = 0;
+
+    } else {
+        ctx = (*limit)->shm_zone->data;
+        max_delay = excess * 1000 / ctx->rate;
+    }
+
+    while (n--) {
+        ctx = limits[n].shm_zone->data;
+        lr = ctx->node;
+
+        if (lr == NULL) {
+            continue;
+        }
+
+        ngx_shmtx_lock(&ctx->shpool->mutex);
+
+        now = ngx_current_msec;
+        ms = (ngx_msec_int_t) (now - lr->last);
+
+        excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000;
+
+        if (excess < 0) {
+            excess = 0;
+        }
+
+        lr->last = now;
+        lr->excess = excess;
+        lr->count--;
+
+        ngx_shmtx_unlock(&ctx->shpool->mutex);
+
+        ctx->node = NULL;
+
+        if (limits[n].nodelay) {
+            continue;
+        }
+
+        delay = excess * 1000 / ctx->rate;
+
+        if (delay > max_delay) {
+            max_delay = delay;
+            *ep = excess;
+            *limit = &limits[n];
+        }
+    }
+
+    return max_delay;
+}
+
+
+static void
+ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, ngx_uint_t n)
+{
+    ngx_int_t                   excess;
+    ngx_msec_t                  now;
+    ngx_queue_t                *q;
+    ngx_msec_int_t              ms;
+    ngx_rbtree_node_t          *node;
+    ngx_http_limit_req_node_t  *lr;
+
+    now = ngx_current_msec;
+
+    /*
+     * n == 1 deletes one or two zero rate entries
+     * n == 0 deletes oldest entry by force
+     *        and one or two zero rate entries
+     */
+
+    while (n < 3) {
+
+        if (ngx_queue_empty(&ctx->sh->queue)) {
+            return;
+        }
+
+        q = ngx_queue_last(&ctx->sh->queue);
+
+        lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue);
+
+        if (lr->count) {
+
+            /*
+             * There is not much sense in looking further,
+             * because we bump nodes on the lookup stage.
+             */
+
+            return;
+        }
+
+        if (n++ != 0) {
+
+            ms = (ngx_msec_int_t) (now - lr->last);
+            ms = ngx_abs(ms);
+
+            if (ms < 60000) {
+                return;
+            }
+
+            excess = lr->excess - ctx->rate * ms / 1000;
+
+            if (excess > 0) {
+                return;
+            }
+        }
+
+        ngx_queue_remove(q);
+
+        node = (ngx_rbtree_node_t *)
+                   ((u_char *) lr - offsetof(ngx_rbtree_node_t, color));
+
+        ngx_rbtree_delete(&ctx->sh->rbtree, node);
+
+        ngx_slab_free_locked(ctx->shpool, node);
+    }
+}
+
+
+static ngx_int_t
+ngx_http_limit_req_init_zone(ngx_shm_zone_t *shm_zone, void *data)
+{
+    ngx_http_limit_req_ctx_t  *octx = data;
+
+    size_t                     len;
+    ngx_http_limit_req_ctx_t  *ctx;
+
+    ctx = shm_zone->data;
+
+    if (octx) {
+        if (ctx->key.value.len != octx->key.value.len
+            || ngx_strncmp(ctx->key.value.data, octx->key.value.data,
+                           ctx->key.value.len)
+               != 0)
+        {
+            ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
+                          "limit_req \"%V\" uses the \"%V\" key "
+                          "while previously it used the \"%V\" key",
+                          &shm_zone->shm.name, &ctx->key.value,
+                          &octx->key.value);
+            return NGX_ERROR;
+        }
+
+        ctx->sh = octx->sh;
+        ctx->shpool = octx->shpool;
+
+        return NGX_OK;
+    }
+
+    ctx->shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+    if (shm_zone->shm.exists) {
+        ctx->sh = ctx->shpool->data;
+
+        return NGX_OK;
+    }
+
+    ctx->sh = ngx_slab_alloc(ctx->shpool, sizeof(ngx_http_limit_req_shctx_t));
+    if (ctx->sh == NULL) {
+        return NGX_ERROR;
+    }
+
+    ctx->shpool->data = ctx->sh;
+
+    ngx_rbtree_init(&ctx->sh->rbtree, &ctx->sh->sentinel,
+                    ngx_http_limit_req_rbtree_insert_value);
+
+    ngx_queue_init(&ctx->sh->queue);
+
+    len = sizeof(" in limit_req zone \"\"") + shm_zone->shm.name.len;
+
+    ctx->shpool->log_ctx = ngx_slab_alloc(ctx->shpool, len);
+    if (ctx->shpool->log_ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_sprintf(ctx->shpool->log_ctx, " in limit_req zone \"%V\"%Z",
+                &shm_zone->shm.name);
+
+    ctx->shpool->log_nomem = 0;
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_limit_req_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_limit_req_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_req_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->limits.elts = NULL;
+     */
+
+    conf->limit_log_level = NGX_CONF_UNSET_UINT;
+    conf->status_code = NGX_CONF_UNSET_UINT;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_limit_req_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_limit_req_conf_t *prev = parent;
+    ngx_http_limit_req_conf_t *conf = child;
+
+    if (conf->limits.elts == NULL) {
+        conf->limits = prev->limits;
+    }
+
+    ngx_conf_merge_uint_value(conf->limit_log_level, prev->limit_log_level,
+                              NGX_LOG_ERR);
+
+    conf->delay_log_level = (conf->limit_log_level == NGX_LOG_INFO) ?
+                                NGX_LOG_INFO : conf->limit_log_level + 1;
+
+    ngx_conf_merge_uint_value(conf->status_code, prev->status_code,
+                              NGX_HTTP_SERVICE_UNAVAILABLE);
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_limit_req_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    u_char                            *p;
+    size_t                             len;
+    ssize_t                            size;
+    ngx_str_t                         *value, name, s;
+    ngx_int_t                          rate, scale;
+    ngx_uint_t                         i;
+    ngx_shm_zone_t                    *shm_zone;
+    ngx_http_limit_req_ctx_t          *ctx;
+    ngx_http_compile_complex_value_t   ccv;
+
+    value = cf->args->elts;
+
+    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_req_ctx_t));
+    if (ctx == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[1];
+    ccv.complex_value = &ctx->key;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    size = 0;
+    rate = 1;
+    scale = 1;
+    name.len = 0;
+
+    for (i = 2; i < cf->args->nelts; i++) {
+
+        if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
+
+            name.data = value[i].data + 5;
+
+            p = (u_char *) ngx_strchr(name.data, ':');
+
+            if (p == NULL) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid zone size \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            name.len = p - name.data;
+
+            s.data = p + 1;
+            s.len = value[i].data + value[i].len - s.data;
+
+            size = ngx_parse_size(&s);
+
+            if (size == NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid zone size \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            if (size < (ssize_t) (8 * ngx_pagesize)) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "zone \"%V\" is too small", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "rate=", 5) == 0) {
+
+            len = value[i].len;
+            p = value[i].data + len - 3;
+
+            if (ngx_strncmp(p, "r/s", 3) == 0) {
+                scale = 1;
+                len -= 3;
+
+            } else if (ngx_strncmp(p, "r/m", 3) == 0) {
+                scale = 60;
+                len -= 3;
+            }
+
+            rate = ngx_atoi(value[i].data + 5, len - 5);
+            if (rate <= 0) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid rate \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[i]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (name.len == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"%V\" must have \"zone\" parameter",
+                           &cmd->name);
+        return NGX_CONF_ERROR;
+    }
+
+    ctx->rate = rate * 1000 / scale;
+
+    shm_zone = ngx_shared_memory_add(cf, &name, size,
+                                     &ngx_http_limit_req_module);
+    if (shm_zone == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (shm_zone->data) {
+        ctx = shm_zone->data;
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "%V \"%V\" is already bound to key \"%V\"",
+                           &cmd->name, &name, &ctx->key.value);
+        return NGX_CONF_ERROR;
+    }
+
+    shm_zone->init = ngx_http_limit_req_init_zone;
+    shm_zone->data = ctx;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_limit_req(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_limit_req_conf_t  *lrcf = conf;
+
+    ngx_int_t                    burst;
+    ngx_str_t                   *value, s;
+    ngx_uint_t                   i, nodelay;
+    ngx_shm_zone_t              *shm_zone;
+    ngx_http_limit_req_limit_t  *limit, *limits;
+
+    value = cf->args->elts;
+
+    shm_zone = NULL;
+    burst = 0;
+    nodelay = 0;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+
+        if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
+
+            s.len = value[i].len - 5;
+            s.data = value[i].data + 5;
+
+            shm_zone = ngx_shared_memory_add(cf, &s, 0,
+                                             &ngx_http_limit_req_module);
+            if (shm_zone == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "burst=", 6) == 0) {
+
+            burst = ngx_atoi(value[i].data + 6, value[i].len - 6);
+            if (burst <= 0) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid burst rate \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strcmp(value[i].data, "nodelay") == 0) {
+            nodelay = 1;
+            continue;
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[i]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (shm_zone == NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"%V\" must have \"zone\" parameter",
+                           &cmd->name);
+        return NGX_CONF_ERROR;
+    }
+
+    limits = lrcf->limits.elts;
+
+    if (limits == NULL) {
+        if (ngx_array_init(&lrcf->limits, cf->pool, 1,
+                           sizeof(ngx_http_limit_req_limit_t))
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    for (i = 0; i < lrcf->limits.nelts; i++) {
+        if (shm_zone == limits[i].shm_zone) {
+            return "is duplicate";
+        }
+    }
+
+    limit = ngx_array_push(&lrcf->limits);
+    if (limit == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    limit->shm_zone = shm_zone;
+    limit->burst = burst * 1000;
+    limit->nodelay = nodelay;
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_limit_req_init(ngx_conf_t *cf)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_limit_req_handler;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_log_module.c b/nginx/src/http/modules/ngx_http_log_module.c
new file mode 100644 (file)
index 0000000..f7c4bd2
--- /dev/null
@@ -0,0 +1,1909 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#if (NGX_ZLIB)
+#include <zlib.h>
+#endif
+
+
+typedef struct ngx_http_log_op_s  ngx_http_log_op_t;
+
+typedef u_char *(*ngx_http_log_op_run_pt) (ngx_http_request_t *r, u_char *buf,
+    ngx_http_log_op_t *op);
+
+typedef size_t (*ngx_http_log_op_getlen_pt) (ngx_http_request_t *r,
+    uintptr_t data);
+
+
+struct ngx_http_log_op_s {
+    size_t                      len;
+    ngx_http_log_op_getlen_pt   getlen;
+    ngx_http_log_op_run_pt      run;
+    uintptr_t                   data;
+};
+
+
+typedef struct {
+    ngx_str_t                   name;
+    ngx_array_t                *flushes;
+    ngx_array_t                *ops;        /* array of ngx_http_log_op_t */
+} ngx_http_log_fmt_t;
+
+
+typedef struct {
+    ngx_array_t                 formats;    /* array of ngx_http_log_fmt_t */
+    ngx_uint_t                  combined_used; /* unsigned  combined_used:1 */
+} ngx_http_log_main_conf_t;
+
+
+typedef struct {
+    u_char                     *start;
+    u_char                     *pos;
+    u_char                     *last;
+
+    ngx_event_t                *event;
+    ngx_msec_t                  flush;
+    ngx_int_t                   gzip;
+} ngx_http_log_buf_t;
+
+
+typedef struct {
+    ngx_array_t                *lengths;
+    ngx_array_t                *values;
+} ngx_http_log_script_t;
+
+
+typedef struct {
+    ngx_open_file_t            *file;
+    ngx_http_log_script_t      *script;
+    time_t                      disk_full_time;
+    time_t                      error_log_time;
+    ngx_syslog_peer_t          *syslog_peer;
+    ngx_http_log_fmt_t         *format;
+    ngx_http_complex_value_t   *filter;
+} ngx_http_log_t;
+
+
+typedef struct {
+    ngx_array_t                *logs;       /* array of ngx_http_log_t */
+
+    ngx_open_file_cache_t      *open_file_cache;
+    time_t                      open_file_cache_valid;
+    ngx_uint_t                  open_file_cache_min_uses;
+
+    ngx_uint_t                  off;        /* unsigned  off:1 */
+} ngx_http_log_loc_conf_t;
+
+
+typedef struct {
+    ngx_str_t                   name;
+    size_t                      len;
+    ngx_http_log_op_run_pt      run;
+} ngx_http_log_var_t;
+
+
+#define NGX_HTTP_LOG_ESCAPE_DEFAULT  0
+#define NGX_HTTP_LOG_ESCAPE_JSON     1
+#define NGX_HTTP_LOG_ESCAPE_NONE     2
+
+
+static void ngx_http_log_write(ngx_http_request_t *r, ngx_http_log_t *log,
+    u_char *buf, size_t len);
+static ssize_t ngx_http_log_script_write(ngx_http_request_t *r,
+    ngx_http_log_script_t *script, u_char **name, u_char *buf, size_t len);
+
+#if (NGX_ZLIB)
+static ssize_t ngx_http_log_gzip(ngx_fd_t fd, u_char *buf, size_t len,
+    ngx_int_t level, ngx_log_t *log);
+
+static void *ngx_http_log_gzip_alloc(void *opaque, u_int items, u_int size);
+static void ngx_http_log_gzip_free(void *opaque, void *address);
+#endif
+
+static void ngx_http_log_flush(ngx_open_file_t *file, ngx_log_t *log);
+static void ngx_http_log_flush_handler(ngx_event_t *ev);
+
+static u_char *ngx_http_log_pipe(ngx_http_request_t *r, u_char *buf,
+    ngx_http_log_op_t *op);
+static u_char *ngx_http_log_time(ngx_http_request_t *r, u_char *buf,
+    ngx_http_log_op_t *op);
+static u_char *ngx_http_log_iso8601(ngx_http_request_t *r, u_char *buf,
+    ngx_http_log_op_t *op);
+static u_char *ngx_http_log_msec(ngx_http_request_t *r, u_char *buf,
+    ngx_http_log_op_t *op);
+static u_char *ngx_http_log_request_time(ngx_http_request_t *r, u_char *buf,
+    ngx_http_log_op_t *op);
+static u_char *ngx_http_log_status(ngx_http_request_t *r, u_char *buf,
+    ngx_http_log_op_t *op);
+static u_char *ngx_http_log_bytes_sent(ngx_http_request_t *r, u_char *buf,
+    ngx_http_log_op_t *op);
+static u_char *ngx_http_log_body_bytes_sent(ngx_http_request_t *r,
+    u_char *buf, ngx_http_log_op_t *op);
+static u_char *ngx_http_log_request_length(ngx_http_request_t *r, u_char *buf,
+    ngx_http_log_op_t *op);
+
+static ngx_int_t ngx_http_log_variable_compile(ngx_conf_t *cf,
+    ngx_http_log_op_t *op, ngx_str_t *value, ngx_uint_t escape);
+static size_t ngx_http_log_variable_getlen(ngx_http_request_t *r,
+    uintptr_t data);
+static u_char *ngx_http_log_variable(ngx_http_request_t *r, u_char *buf,
+    ngx_http_log_op_t *op);
+static uintptr_t ngx_http_log_escape(u_char *dst, u_char *src, size_t size);
+static size_t ngx_http_log_json_variable_getlen(ngx_http_request_t *r,
+    uintptr_t data);
+static u_char *ngx_http_log_json_variable(ngx_http_request_t *r, u_char *buf,
+    ngx_http_log_op_t *op);
+static size_t ngx_http_log_unescaped_variable_getlen(ngx_http_request_t *r,
+    uintptr_t data);
+static u_char *ngx_http_log_unescaped_variable(ngx_http_request_t *r,
+    u_char *buf, ngx_http_log_op_t *op);
+
+
+static void *ngx_http_log_create_main_conf(ngx_conf_t *cf);
+static void *ngx_http_log_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_log_merge_loc_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static char *ngx_http_log_set_log(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_log_set_format(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_log_compile_format(ngx_conf_t *cf,
+    ngx_array_t *flushes, ngx_array_t *ops, ngx_array_t *args, ngx_uint_t s);
+static char *ngx_http_log_open_file_cache(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static ngx_int_t ngx_http_log_init(ngx_conf_t *cf);
+
+
+static ngx_command_t  ngx_http_log_commands[] = {
+
+    { ngx_string("log_format"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_2MORE,
+      ngx_http_log_set_format,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("access_log"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+                        |NGX_HTTP_LMT_CONF|NGX_CONF_1MORE,
+      ngx_http_log_set_log,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("open_log_file_cache"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234,
+      ngx_http_log_open_file_cache,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_log_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_log_init,                     /* postconfiguration */
+
+    ngx_http_log_create_main_conf,         /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_log_create_loc_conf,          /* create location configuration */
+    ngx_http_log_merge_loc_conf            /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_log_module = {
+    NGX_MODULE_V1,
+    &ngx_http_log_module_ctx,              /* module context */
+    ngx_http_log_commands,                 /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t  ngx_http_access_log = ngx_string(NGX_HTTP_LOG_PATH);
+
+
+static ngx_str_t  ngx_http_combined_fmt =
+    ngx_string("$remote_addr - $remote_user [$time_local] "
+               "\"$request\" $status $body_bytes_sent "
+               "\"$http_referer\" \"$http_user_agent\"");
+
+
+static ngx_http_log_var_t  ngx_http_log_vars[] = {
+    { ngx_string("pipe"), 1, ngx_http_log_pipe },
+    { ngx_string("time_local"), sizeof("28/Sep/1970:12:00:00 +0600") - 1,
+                          ngx_http_log_time },
+    { ngx_string("time_iso8601"), sizeof("1970-09-28T12:00:00+06:00") - 1,
+                          ngx_http_log_iso8601 },
+    { ngx_string("msec"), NGX_TIME_T_LEN + 4, ngx_http_log_msec },
+    { ngx_string("request_time"), NGX_TIME_T_LEN + 4,
+                          ngx_http_log_request_time },
+    { ngx_string("status"), NGX_INT_T_LEN, ngx_http_log_status },
+    { ngx_string("bytes_sent"), NGX_OFF_T_LEN, ngx_http_log_bytes_sent },
+    { ngx_string("body_bytes_sent"), NGX_OFF_T_LEN,
+                          ngx_http_log_body_bytes_sent },
+    { ngx_string("request_length"), NGX_SIZE_T_LEN,
+                          ngx_http_log_request_length },
+
+    { ngx_null_string, 0, NULL }
+};
+
+
+static ngx_int_t
+ngx_http_log_handler(ngx_http_request_t *r)
+{
+    u_char                   *line, *p;
+    size_t                    len, size;
+    ssize_t                   n;
+    ngx_str_t                 val;
+    ngx_uint_t                i, l;
+    ngx_http_log_t           *log;
+    ngx_http_log_op_t        *op;
+    ngx_http_log_buf_t       *buffer;
+    ngx_http_log_loc_conf_t  *lcf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http log handler");
+
+    lcf = ngx_http_get_module_loc_conf(r, ngx_http_log_module);
+
+    if (lcf->off) {
+        return NGX_OK;
+    }
+
+    log = lcf->logs->elts;
+    for (l = 0; l < lcf->logs->nelts; l++) {
+
+        if (log[l].filter) {
+            if (ngx_http_complex_value(r, log[l].filter, &val) != NGX_OK) {
+                return NGX_ERROR;
+            }
+
+            if (val.len == 0 || (val.len == 1 && val.data[0] == '0')) {
+                continue;
+            }
+        }
+
+        if (ngx_time() == log[l].disk_full_time) {
+
+            /*
+             * on FreeBSD writing to a full filesystem with enabled softupdates
+             * may block process for much longer time than writing to non-full
+             * filesystem, so we skip writing to a log for one second
+             */
+
+            continue;
+        }
+
+        ngx_http_script_flush_no_cacheable_variables(r, log[l].format->flushes);
+
+        len = 0;
+        op = log[l].format->ops->elts;
+        for (i = 0; i < log[l].format->ops->nelts; i++) {
+            if (op[i].len == 0) {
+                len += op[i].getlen(r, op[i].data);
+
+            } else {
+                len += op[i].len;
+            }
+        }
+
+        if (log[l].syslog_peer) {
+
+            /* length of syslog's PRI and HEADER message parts */
+            len += sizeof("<255>Jan 01 00:00:00 ") - 1
+                   + ngx_cycle->hostname.len + 1
+                   + log[l].syslog_peer->tag.len + 2;
+
+            goto alloc_line;
+        }
+
+        len += NGX_LINEFEED_SIZE;
+
+        buffer = log[l].file ? log[l].file->data : NULL;
+
+        if (buffer) {
+
+            if (len > (size_t) (buffer->last - buffer->pos)) {
+
+                ngx_http_log_write(r, &log[l], buffer->start,
+                                   buffer->pos - buffer->start);
+
+                buffer->pos = buffer->start;
+            }
+
+            if (len <= (size_t) (buffer->last - buffer->pos)) {
+
+                p = buffer->pos;
+
+                if (buffer->event && p == buffer->start) {
+                    ngx_add_timer(buffer->event, buffer->flush);
+                }
+
+                for (i = 0; i < log[l].format->ops->nelts; i++) {
+                    p = op[i].run(r, p, &op[i]);
+                }
+
+                ngx_linefeed(p);
+
+                buffer->pos = p;
+
+                continue;
+            }
+
+            if (buffer->event && buffer->event->timer_set) {
+                ngx_del_timer(buffer->event);
+            }
+        }
+
+    alloc_line:
+
+        line = ngx_pnalloc(r->pool, len);
+        if (line == NULL) {
+            return NGX_ERROR;
+        }
+
+        p = line;
+
+        if (log[l].syslog_peer) {
+            p = ngx_syslog_add_header(log[l].syslog_peer, line);
+        }
+
+        for (i = 0; i < log[l].format->ops->nelts; i++) {
+            p = op[i].run(r, p, &op[i]);
+        }
+
+        if (log[l].syslog_peer) {
+
+            size = p - line;
+
+            n = ngx_syslog_send(log[l].syslog_peer, line, size);
+
+            if (n < 0) {
+                ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
+                              "send() to syslog failed");
+
+            } else if ((size_t) n != size) {
+                ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
+                              "send() to syslog has written only %z of %uz",
+                              n, size);
+            }
+
+            continue;
+        }
+
+        ngx_linefeed(p);
+
+        ngx_http_log_write(r, &log[l], line, p - line);
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_log_write(ngx_http_request_t *r, ngx_http_log_t *log, u_char *buf,
+    size_t len)
+{
+    u_char              *name;
+    time_t               now;
+    ssize_t              n;
+    ngx_err_t            err;
+#if (NGX_ZLIB)
+    ngx_http_log_buf_t  *buffer;
+#endif
+
+    if (log->script == NULL) {
+        name = log->file->name.data;
+
+#if (NGX_ZLIB)
+        buffer = log->file->data;
+
+        if (buffer && buffer->gzip) {
+            n = ngx_http_log_gzip(log->file->fd, buf, len, buffer->gzip,
+                                  r->connection->log);
+        } else {
+            n = ngx_write_fd(log->file->fd, buf, len);
+        }
+#else
+        n = ngx_write_fd(log->file->fd, buf, len);
+#endif
+
+    } else {
+        name = NULL;
+        n = ngx_http_log_script_write(r, log->script, &name, buf, len);
+    }
+
+    if (n == (ssize_t) len) {
+        return;
+    }
+
+    now = ngx_time();
+
+    if (n == -1) {
+        err = ngx_errno;
+
+        if (err == NGX_ENOSPC) {
+            log->disk_full_time = now;
+        }
+
+        if (now - log->error_log_time > 59) {
+            ngx_log_error(NGX_LOG_ALERT, r->connection->log, err,
+                          ngx_write_fd_n " to \"%s\" failed", name);
+
+            log->error_log_time = now;
+        }
+
+        return;
+    }
+
+    if (now - log->error_log_time > 59) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                      ngx_write_fd_n " to \"%s\" was incomplete: %z of %uz",
+                      name, n, len);
+
+        log->error_log_time = now;
+    }
+}
+
+
+static ssize_t
+ngx_http_log_script_write(ngx_http_request_t *r, ngx_http_log_script_t *script,
+    u_char **name, u_char *buf, size_t len)
+{
+    size_t                     root;
+    ssize_t                    n;
+    ngx_str_t                  log, path;
+    ngx_open_file_info_t       of;
+    ngx_http_log_loc_conf_t   *llcf;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (!r->root_tested) {
+
+        /* test root directory existence */
+
+        if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) {
+            /* simulate successful logging */
+            return len;
+        }
+
+        path.data[root] = '\0';
+
+        ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+        of.valid = clcf->open_file_cache_valid;
+        of.min_uses = clcf->open_file_cache_min_uses;
+        of.test_dir = 1;
+        of.test_only = 1;
+        of.errors = clcf->open_file_cache_errors;
+        of.events = clcf->open_file_cache_events;
+
+        if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
+            /* simulate successful logging */
+            return len;
+        }
+
+        if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
+            != NGX_OK)
+        {
+            if (of.err == 0) {
+                /* simulate successful logging */
+                return len;
+            }
+
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, of.err,
+                          "testing \"%s\" existence failed", path.data);
+
+            /* simulate successful logging */
+            return len;
+        }
+
+        if (!of.is_dir) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_ENOTDIR,
+                          "testing \"%s\" existence failed", path.data);
+
+            /* simulate successful logging */
+            return len;
+        }
+    }
+
+    if (ngx_http_script_run(r, &log, script->lengths->elts, 1,
+                            script->values->elts)
+        == NULL)
+    {
+        /* simulate successful logging */
+        return len;
+    }
+
+    log.data[log.len - 1] = '\0';
+    *name = log.data;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http log \"%s\"", log.data);
+
+    llcf = ngx_http_get_module_loc_conf(r, ngx_http_log_module);
+
+    ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+    of.log = 1;
+    of.valid = llcf->open_file_cache_valid;
+    of.min_uses = llcf->open_file_cache_min_uses;
+    of.directio = NGX_OPEN_FILE_DIRECTIO_OFF;
+
+    if (ngx_http_set_disable_symlinks(r, clcf, &log, &of) != NGX_OK) {
+        /* simulate successful logging */
+        return len;
+    }
+
+    if (ngx_open_cached_file(llcf->open_file_cache, &log, &of, r->pool)
+        != NGX_OK)
+    {
+        if (of.err == 0) {
+            /* simulate successful logging */
+            return len;
+        }
+
+        ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+                      "%s \"%s\" failed", of.failed, log.data);
+        /* simulate successful logging */
+        return len;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http log #%d", of.fd);
+
+    n = ngx_write_fd(of.fd, buf, len);
+
+    return n;
+}
+
+
+#if (NGX_ZLIB)
+
+static ssize_t
+ngx_http_log_gzip(ngx_fd_t fd, u_char *buf, size_t len, ngx_int_t level,
+    ngx_log_t *log)
+{
+    int          rc, wbits, memlevel;
+    u_char      *out;
+    size_t       size;
+    ssize_t      n;
+    z_stream     zstream;
+    ngx_err_t    err;
+    ngx_pool_t  *pool;
+
+    wbits = MAX_WBITS;
+    memlevel = MAX_MEM_LEVEL - 1;
+
+    while ((ssize_t) len < ((1 << (wbits - 1)) - 262)) {
+        wbits--;
+        memlevel--;
+    }
+
+    /*
+     * This is a formula from deflateBound() for conservative upper bound of
+     * compressed data plus 18 bytes of gzip wrapper.
+     */
+
+    size = len + ((len + 7) >> 3) + ((len + 63) >> 6) + 5 + 18;
+
+    ngx_memzero(&zstream, sizeof(z_stream));
+
+    pool = ngx_create_pool(256, log);
+    if (pool == NULL) {
+        /* simulate successful logging */
+        return len;
+    }
+
+    pool->log = log;
+
+    zstream.zalloc = ngx_http_log_gzip_alloc;
+    zstream.zfree = ngx_http_log_gzip_free;
+    zstream.opaque = pool;
+
+    out = ngx_pnalloc(pool, size);
+    if (out == NULL) {
+        goto done;
+    }
+
+    zstream.next_in = buf;
+    zstream.avail_in = len;
+    zstream.next_out = out;
+    zstream.avail_out = size;
+
+    rc = deflateInit2(&zstream, (int) level, Z_DEFLATED, wbits + 16, memlevel,
+                      Z_DEFAULT_STRATEGY);
+
+    if (rc != Z_OK) {
+        ngx_log_error(NGX_LOG_ALERT, log, 0, "deflateInit2() failed: %d", rc);
+        goto done;
+    }
+
+    ngx_log_debug4(NGX_LOG_DEBUG_HTTP, log, 0,
+                   "deflate in: ni:%p no:%p ai:%ud ao:%ud",
+                   zstream.next_in, zstream.next_out,
+                   zstream.avail_in, zstream.avail_out);
+
+    rc = deflate(&zstream, Z_FINISH);
+
+    if (rc != Z_STREAM_END) {
+        ngx_log_error(NGX_LOG_ALERT, log, 0,
+                      "deflate(Z_FINISH) failed: %d", rc);
+        goto done;
+    }
+
+    ngx_log_debug5(NGX_LOG_DEBUG_HTTP, log, 0,
+                   "deflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d",
+                   zstream.next_in, zstream.next_out,
+                   zstream.avail_in, zstream.avail_out,
+                   rc);
+
+    size -= zstream.avail_out;
+
+    rc = deflateEnd(&zstream);
+
+    if (rc != Z_OK) {
+        ngx_log_error(NGX_LOG_ALERT, log, 0, "deflateEnd() failed: %d", rc);
+        goto done;
+    }
+
+    n = ngx_write_fd(fd, out, size);
+
+    if (n != (ssize_t) size) {
+        err = (n == -1) ? ngx_errno : 0;
+
+        ngx_destroy_pool(pool);
+
+        ngx_set_errno(err);
+        return -1;
+    }
+
+done:
+
+    ngx_destroy_pool(pool);
+
+    /* simulate successful logging */
+    return len;
+}
+
+
+static void *
+ngx_http_log_gzip_alloc(void *opaque, u_int items, u_int size)
+{
+    ngx_pool_t *pool = opaque;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pool->log, 0,
+                   "gzip alloc: n:%ud s:%ud", items, size);
+
+    return ngx_palloc(pool, items * size);
+}
+
+
+static void
+ngx_http_log_gzip_free(void *opaque, void *address)
+{
+#if 0
+    ngx_pool_t *pool = opaque;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pool->log, 0, "gzip free: %p", address);
+#endif
+}
+
+#endif
+
+
+static void
+ngx_http_log_flush(ngx_open_file_t *file, ngx_log_t *log)
+{
+    size_t               len;
+    ssize_t              n;
+    ngx_http_log_buf_t  *buffer;
+
+    buffer = file->data;
+
+    len = buffer->pos - buffer->start;
+
+    if (len == 0) {
+        return;
+    }
+
+#if (NGX_ZLIB)
+    if (buffer->gzip) {
+        n = ngx_http_log_gzip(file->fd, buffer->start, len, buffer->gzip, log);
+    } else {
+        n = ngx_write_fd(file->fd, buffer->start, len);
+    }
+#else
+    n = ngx_write_fd(file->fd, buffer->start, len);
+#endif
+
+    if (n == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      ngx_write_fd_n " to \"%s\" failed",
+                      file->name.data);
+
+    } else if ((size_t) n != len) {
+        ngx_log_error(NGX_LOG_ALERT, log, 0,
+                      ngx_write_fd_n " to \"%s\" was incomplete: %z of %uz",
+                      file->name.data, n, len);
+    }
+
+    buffer->pos = buffer->start;
+
+    if (buffer->event && buffer->event->timer_set) {
+        ngx_del_timer(buffer->event);
+    }
+}
+
+
+static void
+ngx_http_log_flush_handler(ngx_event_t *ev)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "http log buffer flush handler");
+
+    ngx_http_log_flush(ev->data, ev->log);
+}
+
+
+static u_char *
+ngx_http_log_copy_short(ngx_http_request_t *r, u_char *buf,
+    ngx_http_log_op_t *op)
+{
+    size_t     len;
+    uintptr_t  data;
+
+    len = op->len;
+    data = op->data;
+
+    while (len--) {
+        *buf++ = (u_char) (data & 0xff);
+        data >>= 8;
+    }
+
+    return buf;
+}
+
+
+static u_char *
+ngx_http_log_copy_long(ngx_http_request_t *r, u_char *buf,
+    ngx_http_log_op_t *op)
+{
+    return ngx_cpymem(buf, (u_char *) op->data, op->len);
+}
+
+
+static u_char *
+ngx_http_log_pipe(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op)
+{
+    if (r->pipeline) {
+        *buf = 'p';
+    } else {
+        *buf = '.';
+    }
+
+    return buf + 1;
+}
+
+
+static u_char *
+ngx_http_log_time(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op)
+{
+    return ngx_cpymem(buf, ngx_cached_http_log_time.data,
+                      ngx_cached_http_log_time.len);
+}
+
+static u_char *
+ngx_http_log_iso8601(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op)
+{
+    return ngx_cpymem(buf, ngx_cached_http_log_iso8601.data,
+                      ngx_cached_http_log_iso8601.len);
+}
+
+static u_char *
+ngx_http_log_msec(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op)
+{
+    ngx_time_t  *tp;
+
+    tp = ngx_timeofday();
+
+    return ngx_sprintf(buf, "%T.%03M", tp->sec, tp->msec);
+}
+
+
+static u_char *
+ngx_http_log_request_time(ngx_http_request_t *r, u_char *buf,
+    ngx_http_log_op_t *op)
+{
+    ngx_time_t      *tp;
+    ngx_msec_int_t   ms;
+
+    tp = ngx_timeofday();
+
+    ms = (ngx_msec_int_t)
+             ((tp->sec - r->start_sec) * 1000 + (tp->msec - r->start_msec));
+    ms = ngx_max(ms, 0);
+
+    return ngx_sprintf(buf, "%T.%03M", (time_t) ms / 1000, ms % 1000);
+}
+
+
+static u_char *
+ngx_http_log_status(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op)
+{
+    ngx_uint_t  status;
+
+    if (r->err_status) {
+        status = r->err_status;
+
+    } else if (r->headers_out.status) {
+        status = r->headers_out.status;
+
+    } else if (r->http_version == NGX_HTTP_VERSION_9) {
+        status = 9;
+
+    } else {
+        status = 0;
+    }
+
+    return ngx_sprintf(buf, "%03ui", status);
+}
+
+
+static u_char *
+ngx_http_log_bytes_sent(ngx_http_request_t *r, u_char *buf,
+    ngx_http_log_op_t *op)
+{
+    return ngx_sprintf(buf, "%O", r->connection->sent);
+}
+
+
+/*
+ * although there is a real $body_bytes_sent variable,
+ * this log operation code function is more optimized for logging
+ */
+
+static u_char *
+ngx_http_log_body_bytes_sent(ngx_http_request_t *r, u_char *buf,
+    ngx_http_log_op_t *op)
+{
+    off_t  length;
+
+    length = r->connection->sent - r->header_size;
+
+    if (length > 0) {
+        return ngx_sprintf(buf, "%O", length);
+    }
+
+    *buf = '0';
+
+    return buf + 1;
+}
+
+
+static u_char *
+ngx_http_log_request_length(ngx_http_request_t *r, u_char *buf,
+    ngx_http_log_op_t *op)
+{
+    return ngx_sprintf(buf, "%O", r->request_length);
+}
+
+
+static ngx_int_t
+ngx_http_log_variable_compile(ngx_conf_t *cf, ngx_http_log_op_t *op,
+    ngx_str_t *value, ngx_uint_t escape)
+{
+    ngx_int_t  index;
+
+    index = ngx_http_get_variable_index(cf, value);
+    if (index == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    op->len = 0;
+
+    switch (escape) {
+    case NGX_HTTP_LOG_ESCAPE_JSON:
+        op->getlen = ngx_http_log_json_variable_getlen;
+        op->run = ngx_http_log_json_variable;
+        break;
+
+    case NGX_HTTP_LOG_ESCAPE_NONE:
+        op->getlen = ngx_http_log_unescaped_variable_getlen;
+        op->run = ngx_http_log_unescaped_variable;
+        break;
+
+    default: /* NGX_HTTP_LOG_ESCAPE_DEFAULT */
+        op->getlen = ngx_http_log_variable_getlen;
+        op->run = ngx_http_log_variable;
+    }
+
+    op->data = index;
+
+    return NGX_OK;
+}
+
+
+static size_t
+ngx_http_log_variable_getlen(ngx_http_request_t *r, uintptr_t data)
+{
+    uintptr_t                   len;
+    ngx_http_variable_value_t  *value;
+
+    value = ngx_http_get_indexed_variable(r, data);
+
+    if (value == NULL || value->not_found) {
+        return 1;
+    }
+
+    len = ngx_http_log_escape(NULL, value->data, value->len);
+
+    value->escape = len ? 1 : 0;
+
+    return value->len + len * 3;
+}
+
+
+static u_char *
+ngx_http_log_variable(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op)
+{
+    ngx_http_variable_value_t  *value;
+
+    value = ngx_http_get_indexed_variable(r, op->data);
+
+    if (value == NULL || value->not_found) {
+        *buf = '-';
+        return buf + 1;
+    }
+
+    if (value->escape == 0) {
+        return ngx_cpymem(buf, value->data, value->len);
+
+    } else {
+        return (u_char *) ngx_http_log_escape(buf, value->data, value->len);
+    }
+}
+
+
+static uintptr_t
+ngx_http_log_escape(u_char *dst, u_char *src, size_t size)
+{
+    ngx_uint_t      n;
+    static u_char   hex[] = "0123456789ABCDEF";
+
+    static uint32_t   escape[] = {
+        0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
+
+                    /* ?>=< ;:98 7654 3210  /.-, +*)( '&%$ #"!  */
+        0x00000004, /* 0000 0000 0000 0000  0000 0000 0000 0100 */
+
+                    /* _^]\ [ZYX WVUT SRQP  ONML KJIH GFED CBA@ */
+        0x10000000, /* 0001 0000 0000 0000  0000 0000 0000 0000 */
+
+                    /*  ~}| {zyx wvut srqp  onml kjih gfed cba` */
+        0x80000000, /* 1000 0000 0000 0000  0000 0000 0000 0000 */
+
+        0xffffffff, /* 1111 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 */
+        0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
+    };
+
+
+    if (dst == NULL) {
+
+        /* find the number of the characters to be escaped */
+
+        n = 0;
+
+        while (size) {
+            if (escape[*src >> 5] & (1U << (*src & 0x1f))) {
+                n++;
+            }
+            src++;
+            size--;
+        }
+
+        return (uintptr_t) n;
+    }
+
+    while (size) {
+        if (escape[*src >> 5] & (1U << (*src & 0x1f))) {
+            *dst++ = '\\';
+            *dst++ = 'x';
+            *dst++ = hex[*src >> 4];
+            *dst++ = hex[*src & 0xf];
+            src++;
+
+        } else {
+            *dst++ = *src++;
+        }
+        size--;
+    }
+
+    return (uintptr_t) dst;
+}
+
+
+static size_t
+ngx_http_log_json_variable_getlen(ngx_http_request_t *r, uintptr_t data)
+{
+    uintptr_t                   len;
+    ngx_http_variable_value_t  *value;
+
+    value = ngx_http_get_indexed_variable(r, data);
+
+    if (value == NULL || value->not_found) {
+        return 0;
+    }
+
+    len = ngx_escape_json(NULL, value->data, value->len);
+
+    value->escape = len ? 1 : 0;
+
+    return value->len + len;
+}
+
+
+static u_char *
+ngx_http_log_json_variable(ngx_http_request_t *r, u_char *buf,
+    ngx_http_log_op_t *op)
+{
+    ngx_http_variable_value_t  *value;
+
+    value = ngx_http_get_indexed_variable(r, op->data);
+
+    if (value == NULL || value->not_found) {
+        return buf;
+    }
+
+    if (value->escape == 0) {
+        return ngx_cpymem(buf, value->data, value->len);
+
+    } else {
+        return (u_char *) ngx_escape_json(buf, value->data, value->len);
+    }
+}
+
+
+static size_t
+ngx_http_log_unescaped_variable_getlen(ngx_http_request_t *r, uintptr_t data)
+{
+    ngx_http_variable_value_t  *value;
+
+    value = ngx_http_get_indexed_variable(r, data);
+
+    if (value == NULL || value->not_found) {
+        return 0;
+    }
+
+    value->escape = 0;
+
+    return value->len;
+}
+
+
+static u_char *
+ngx_http_log_unescaped_variable(ngx_http_request_t *r, u_char *buf,
+    ngx_http_log_op_t *op)
+{
+    ngx_http_variable_value_t  *value;
+
+    value = ngx_http_get_indexed_variable(r, op->data);
+
+    if (value == NULL || value->not_found) {
+        return buf;
+    }
+
+    return ngx_cpymem(buf, value->data, value->len);
+}
+
+
+static void *
+ngx_http_log_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_http_log_main_conf_t  *conf;
+
+    ngx_http_log_fmt_t  *fmt;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_log_main_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    if (ngx_array_init(&conf->formats, cf->pool, 4, sizeof(ngx_http_log_fmt_t))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    fmt = ngx_array_push(&conf->formats);
+    if (fmt == NULL) {
+        return NULL;
+    }
+
+    ngx_str_set(&fmt->name, "combined");
+
+    fmt->flushes = NULL;
+
+    fmt->ops = ngx_array_create(cf->pool, 16, sizeof(ngx_http_log_op_t));
+    if (fmt->ops == NULL) {
+        return NULL;
+    }
+
+    return conf;
+}
+
+
+static void *
+ngx_http_log_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_log_loc_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_log_loc_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    conf->open_file_cache = NGX_CONF_UNSET_PTR;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_log_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_log_loc_conf_t *prev = parent;
+    ngx_http_log_loc_conf_t *conf = child;
+
+    ngx_http_log_t            *log;
+    ngx_http_log_fmt_t        *fmt;
+    ngx_http_log_main_conf_t  *lmcf;
+
+    if (conf->open_file_cache == NGX_CONF_UNSET_PTR) {
+
+        conf->open_file_cache = prev->open_file_cache;
+        conf->open_file_cache_valid = prev->open_file_cache_valid;
+        conf->open_file_cache_min_uses = prev->open_file_cache_min_uses;
+
+        if (conf->open_file_cache == NGX_CONF_UNSET_PTR) {
+            conf->open_file_cache = NULL;
+        }
+    }
+
+    if (conf->logs || conf->off) {
+        return NGX_CONF_OK;
+    }
+
+    conf->logs = prev->logs;
+    conf->off = prev->off;
+
+    if (conf->logs || conf->off) {
+        return NGX_CONF_OK;
+    }
+
+    conf->logs = ngx_array_create(cf->pool, 2, sizeof(ngx_http_log_t));
+    if (conf->logs == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    log = ngx_array_push(conf->logs);
+    if (log == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memzero(log, sizeof(ngx_http_log_t));
+
+    log->file = ngx_conf_open_file(cf->cycle, &ngx_http_access_log);
+    if (log->file == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_log_module);
+    fmt = lmcf->formats.elts;
+
+    /* the default "combined" format */
+    log->format = &fmt[0];
+    lmcf->combined_used = 1;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_log_set_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_log_loc_conf_t *llcf = conf;
+
+    ssize_t                            size;
+    ngx_int_t                          gzip;
+    ngx_uint_t                         i, n;
+    ngx_msec_t                         flush;
+    ngx_str_t                         *value, name, s;
+    ngx_http_log_t                    *log;
+    ngx_syslog_peer_t                 *peer;
+    ngx_http_log_buf_t                *buffer;
+    ngx_http_log_fmt_t                *fmt;
+    ngx_http_log_main_conf_t          *lmcf;
+    ngx_http_script_compile_t          sc;
+    ngx_http_compile_complex_value_t   ccv;
+
+    value = cf->args->elts;
+
+    if (ngx_strcmp(value[1].data, "off") == 0) {
+        llcf->off = 1;
+        if (cf->args->nelts == 2) {
+            return NGX_CONF_OK;
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[2]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (llcf->logs == NULL) {
+        llcf->logs = ngx_array_create(cf->pool, 2, sizeof(ngx_http_log_t));
+        if (llcf->logs == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_log_module);
+
+    log = ngx_array_push(llcf->logs);
+    if (log == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memzero(log, sizeof(ngx_http_log_t));
+
+
+    if (ngx_strncmp(value[1].data, "syslog:", 7) == 0) {
+
+        peer = ngx_pcalloc(cf->pool, sizeof(ngx_syslog_peer_t));
+        if (peer == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_syslog_process_conf(cf, peer) != NGX_CONF_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+        log->syslog_peer = peer;
+
+        goto process_formats;
+    }
+
+    n = ngx_http_script_variables_count(&value[1]);
+
+    if (n == 0) {
+        log->file = ngx_conf_open_file(cf->cycle, &value[1]);
+        if (log->file == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+    } else {
+        if (ngx_conf_full_name(cf->cycle, &value[1], 0) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+        log->script = ngx_pcalloc(cf->pool, sizeof(ngx_http_log_script_t));
+        if (log->script == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+        sc.cf = cf;
+        sc.source = &value[1];
+        sc.lengths = &log->script->lengths;
+        sc.values = &log->script->values;
+        sc.variables = n;
+        sc.complete_lengths = 1;
+        sc.complete_values = 1;
+
+        if (ngx_http_script_compile(&sc) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+process_formats:
+
+    if (cf->args->nelts >= 3) {
+        name = value[2];
+
+        if (ngx_strcmp(name.data, "combined") == 0) {
+            lmcf->combined_used = 1;
+        }
+
+    } else {
+        ngx_str_set(&name, "combined");
+        lmcf->combined_used = 1;
+    }
+
+    fmt = lmcf->formats.elts;
+    for (i = 0; i < lmcf->formats.nelts; i++) {
+        if (fmt[i].name.len == name.len
+            && ngx_strcasecmp(fmt[i].name.data, name.data) == 0)
+        {
+            log->format = &fmt[i];
+            break;
+        }
+    }
+
+    if (log->format == NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "unknown log format \"%V\"", &name);
+        return NGX_CONF_ERROR;
+    }
+
+    size = 0;
+    flush = 0;
+    gzip = 0;
+
+    for (i = 3; i < cf->args->nelts; i++) {
+
+        if (ngx_strncmp(value[i].data, "buffer=", 7) == 0) {
+            s.len = value[i].len - 7;
+            s.data = value[i].data + 7;
+
+            size = ngx_parse_size(&s);
+
+            if (size == NGX_ERROR || size == 0) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid buffer size \"%V\"", &s);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "flush=", 6) == 0) {
+            s.len = value[i].len - 6;
+            s.data = value[i].data + 6;
+
+            flush = ngx_parse_time(&s, 0);
+
+            if (flush == (ngx_msec_t) NGX_ERROR || flush == 0) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid flush time \"%V\"", &s);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "gzip", 4) == 0
+            && (value[i].len == 4 || value[i].data[4] == '='))
+        {
+#if (NGX_ZLIB)
+            if (size == 0) {
+                size = 64 * 1024;
+            }
+
+            if (value[i].len == 4) {
+                gzip = Z_BEST_SPEED;
+                continue;
+            }
+
+            s.len = value[i].len - 5;
+            s.data = value[i].data + 5;
+
+            gzip = ngx_atoi(s.data, s.len);
+
+            if (gzip < 1 || gzip > 9) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid compression level \"%V\"", &s);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "nginx was built without zlib support");
+            return NGX_CONF_ERROR;
+#endif
+        }
+
+        if (ngx_strncmp(value[i].data, "if=", 3) == 0) {
+            s.len = value[i].len - 3;
+            s.data = value[i].data + 3;
+
+            ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+            ccv.cf = cf;
+            ccv.value = &s;
+            ccv.complex_value = ngx_palloc(cf->pool,
+                                           sizeof(ngx_http_complex_value_t));
+            if (ccv.complex_value == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+                return NGX_CONF_ERROR;
+            }
+
+            log->filter = ccv.complex_value;
+
+            continue;
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[i]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (flush && size == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "no buffer is defined for access_log \"%V\"",
+                           &value[1]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (size) {
+
+        if (log->script) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "buffered logs cannot have variables in name");
+            return NGX_CONF_ERROR;
+        }
+
+        if (log->syslog_peer) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "logs to syslog cannot be buffered");
+            return NGX_CONF_ERROR;
+        }
+
+        if (log->file->data) {
+            buffer = log->file->data;
+
+            if (buffer->last - buffer->start != size
+                || buffer->flush != flush
+                || buffer->gzip != gzip)
+            {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "access_log \"%V\" already defined "
+                                   "with conflicting parameters",
+                                   &value[1]);
+                return NGX_CONF_ERROR;
+            }
+
+            return NGX_CONF_OK;
+        }
+
+        buffer = ngx_pcalloc(cf->pool, sizeof(ngx_http_log_buf_t));
+        if (buffer == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        buffer->start = ngx_pnalloc(cf->pool, size);
+        if (buffer->start == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        buffer->pos = buffer->start;
+        buffer->last = buffer->start + size;
+
+        if (flush) {
+            buffer->event = ngx_pcalloc(cf->pool, sizeof(ngx_event_t));
+            if (buffer->event == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            buffer->event->data = log->file;
+            buffer->event->handler = ngx_http_log_flush_handler;
+            buffer->event->log = &cf->cycle->new_log;
+            buffer->event->cancelable = 1;
+
+            buffer->flush = flush;
+        }
+
+        buffer->gzip = gzip;
+
+        log->file->flush = ngx_http_log_flush;
+        log->file->data = buffer;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_log_set_format(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_log_main_conf_t *lmcf = conf;
+
+    ngx_str_t           *value;
+    ngx_uint_t           i;
+    ngx_http_log_fmt_t  *fmt;
+
+    value = cf->args->elts;
+
+    fmt = lmcf->formats.elts;
+    for (i = 0; i < lmcf->formats.nelts; i++) {
+        if (fmt[i].name.len == value[1].len
+            && ngx_strcmp(fmt[i].name.data, value[1].data) == 0)
+        {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "duplicate \"log_format\" name \"%V\"",
+                               &value[1]);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    fmt = ngx_array_push(&lmcf->formats);
+    if (fmt == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    fmt->name = value[1];
+
+    fmt->flushes = ngx_array_create(cf->pool, 4, sizeof(ngx_int_t));
+    if (fmt->flushes == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    fmt->ops = ngx_array_create(cf->pool, 16, sizeof(ngx_http_log_op_t));
+    if (fmt->ops == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    return ngx_http_log_compile_format(cf, fmt->flushes, fmt->ops, cf->args, 2);
+}
+
+
+static char *
+ngx_http_log_compile_format(ngx_conf_t *cf, ngx_array_t *flushes,
+    ngx_array_t *ops, ngx_array_t *args, ngx_uint_t s)
+{
+    u_char              *data, *p, ch;
+    size_t               i, len;
+    ngx_str_t           *value, var;
+    ngx_int_t           *flush;
+    ngx_uint_t           bracket, escape;
+    ngx_http_log_op_t   *op;
+    ngx_http_log_var_t  *v;
+
+    escape = NGX_HTTP_LOG_ESCAPE_DEFAULT;
+    value = args->elts;
+
+    if (s < args->nelts && ngx_strncmp(value[s].data, "escape=", 7) == 0) {
+        data = value[s].data + 7;
+
+        if (ngx_strcmp(data, "json") == 0) {
+            escape = NGX_HTTP_LOG_ESCAPE_JSON;
+
+        } else if (ngx_strcmp(data, "none") == 0) {
+            escape = NGX_HTTP_LOG_ESCAPE_NONE;
+
+        } else if (ngx_strcmp(data, "default") != 0) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "unknown log format escaping \"%s\"", data);
+            return NGX_CONF_ERROR;
+        }
+
+        s++;
+    }
+
+    for ( /* void */ ; s < args->nelts; s++) {
+
+        i = 0;
+
+        while (i < value[s].len) {
+
+            op = ngx_array_push(ops);
+            if (op == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            data = &value[s].data[i];
+
+            if (value[s].data[i] == '$') {
+
+                if (++i == value[s].len) {
+                    goto invalid;
+                }
+
+                if (value[s].data[i] == '{') {
+                    bracket = 1;
+
+                    if (++i == value[s].len) {
+                        goto invalid;
+                    }
+
+                    var.data = &value[s].data[i];
+
+                } else {
+                    bracket = 0;
+                    var.data = &value[s].data[i];
+                }
+
+                for (var.len = 0; i < value[s].len; i++, var.len++) {
+                    ch = value[s].data[i];
+
+                    if (ch == '}' && bracket) {
+                        i++;
+                        bracket = 0;
+                        break;
+                    }
+
+                    if ((ch >= 'A' && ch <= 'Z')
+                        || (ch >= 'a' && ch <= 'z')
+                        || (ch >= '0' && ch <= '9')
+                        || ch == '_')
+                    {
+                        continue;
+                    }
+
+                    break;
+                }
+
+                if (bracket) {
+                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                       "the closing bracket in \"%V\" "
+                                       "variable is missing", &var);
+                    return NGX_CONF_ERROR;
+                }
+
+                if (var.len == 0) {
+                    goto invalid;
+                }
+
+                for (v = ngx_http_log_vars; v->name.len; v++) {
+
+                    if (v->name.len == var.len
+                        && ngx_strncmp(v->name.data, var.data, var.len) == 0)
+                    {
+                        op->len = v->len;
+                        op->getlen = NULL;
+                        op->run = v->run;
+                        op->data = 0;
+
+                        goto found;
+                    }
+                }
+
+                if (ngx_http_log_variable_compile(cf, op, &var, escape)
+                    != NGX_OK)
+                {
+                    return NGX_CONF_ERROR;
+                }
+
+                if (flushes) {
+
+                    flush = ngx_array_push(flushes);
+                    if (flush == NULL) {
+                        return NGX_CONF_ERROR;
+                    }
+
+                    *flush = op->data; /* variable index */
+                }
+
+            found:
+
+                continue;
+            }
+
+            i++;
+
+            while (i < value[s].len && value[s].data[i] != '$') {
+                i++;
+            }
+
+            len = &value[s].data[i] - data;
+
+            if (len) {
+
+                op->len = len;
+                op->getlen = NULL;
+
+                if (len <= sizeof(uintptr_t)) {
+                    op->run = ngx_http_log_copy_short;
+                    op->data = 0;
+
+                    while (len--) {
+                        op->data <<= 8;
+                        op->data |= data[len];
+                    }
+
+                } else {
+                    op->run = ngx_http_log_copy_long;
+
+                    p = ngx_pnalloc(cf->pool, len);
+                    if (p == NULL) {
+                        return NGX_CONF_ERROR;
+                    }
+
+                    ngx_memcpy(p, data, len);
+                    op->data = (uintptr_t) p;
+                }
+            }
+        }
+    }
+
+    return NGX_CONF_OK;
+
+invalid:
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%s\"", data);
+
+    return NGX_CONF_ERROR;
+}
+
+
+static char *
+ngx_http_log_open_file_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_log_loc_conf_t *llcf = conf;
+
+    time_t       inactive, valid;
+    ngx_str_t   *value, s;
+    ngx_int_t    max, min_uses;
+    ngx_uint_t   i;
+
+    if (llcf->open_file_cache != NGX_CONF_UNSET_PTR) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    max = 0;
+    inactive = 10;
+    valid = 60;
+    min_uses = 1;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+
+        if (ngx_strncmp(value[i].data, "max=", 4) == 0) {
+
+            max = ngx_atoi(value[i].data + 4, value[i].len - 4);
+            if (max == NGX_ERROR) {
+                goto failed;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "inactive=", 9) == 0) {
+
+            s.len = value[i].len - 9;
+            s.data = value[i].data + 9;
+
+            inactive = ngx_parse_time(&s, 1);
+            if (inactive == (time_t) NGX_ERROR) {
+                goto failed;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "min_uses=", 9) == 0) {
+
+            min_uses = ngx_atoi(value[i].data + 9, value[i].len - 9);
+            if (min_uses == NGX_ERROR) {
+                goto failed;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "valid=", 6) == 0) {
+
+            s.len = value[i].len - 6;
+            s.data = value[i].data + 6;
+
+            valid = ngx_parse_time(&s, 1);
+            if (valid == (time_t) NGX_ERROR) {
+                goto failed;
+            }
+
+            continue;
+        }
+
+        if (ngx_strcmp(value[i].data, "off") == 0) {
+
+            llcf->open_file_cache = NULL;
+
+            continue;
+        }
+
+    failed:
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid \"open_log_file_cache\" parameter \"%V\"",
+                           &value[i]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (llcf->open_file_cache == NULL) {
+        return NGX_CONF_OK;
+    }
+
+    if (max == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                        "\"open_log_file_cache\" must have \"max\" parameter");
+        return NGX_CONF_ERROR;
+    }
+
+    llcf->open_file_cache = ngx_open_file_cache_init(cf->pool, max, inactive);
+
+    if (llcf->open_file_cache) {
+
+        llcf->open_file_cache_valid = valid;
+        llcf->open_file_cache_min_uses = min_uses;
+
+        return NGX_CONF_OK;
+    }
+
+    return NGX_CONF_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_log_init(ngx_conf_t *cf)
+{
+    ngx_str_t                  *value;
+    ngx_array_t                 a;
+    ngx_http_handler_pt        *h;
+    ngx_http_log_fmt_t         *fmt;
+    ngx_http_log_main_conf_t   *lmcf;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_log_module);
+
+    if (lmcf->combined_used) {
+        if (ngx_array_init(&a, cf->pool, 1, sizeof(ngx_str_t)) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        value = ngx_array_push(&a);
+        if (value == NULL) {
+            return NGX_ERROR;
+        }
+
+        *value = ngx_http_combined_fmt;
+        fmt = lmcf->formats.elts;
+
+        if (ngx_http_log_compile_format(cf, NULL, fmt->ops, &a, 0)
+            != NGX_CONF_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_LOG_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_log_handler;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_map_module.c b/nginx/src/http/modules/ngx_http_map_module.c
new file mode 100644 (file)
index 0000000..2fc9be9
--- /dev/null
@@ -0,0 +1,589 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_uint_t                  hash_max_size;
+    ngx_uint_t                  hash_bucket_size;
+} ngx_http_map_conf_t;
+
+
+typedef struct {
+    ngx_hash_keys_arrays_t      keys;
+
+    ngx_array_t                *values_hash;
+#if (NGX_PCRE)
+    ngx_array_t                 regexes;
+#endif
+
+    ngx_http_variable_value_t  *default_value;
+    ngx_conf_t                 *cf;
+    unsigned                    hostnames:1;
+    unsigned                    no_cacheable:1;
+} ngx_http_map_conf_ctx_t;
+
+
+typedef struct {
+    ngx_http_map_t              map;
+    ngx_http_complex_value_t    value;
+    ngx_http_variable_value_t  *default_value;
+    ngx_uint_t                  hostnames;      /* unsigned  hostnames:1 */
+} ngx_http_map_ctx_t;
+
+
+static int ngx_libc_cdecl ngx_http_map_cmp_dns_wildcards(const void *one,
+    const void *two);
+static void *ngx_http_map_create_conf(ngx_conf_t *cf);
+static char *ngx_http_map_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_http_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf);
+
+
+static ngx_command_t  ngx_http_map_commands[] = {
+
+    { ngx_string("map"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE2,
+      ngx_http_map_block,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("map_hash_max_size"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      offsetof(ngx_http_map_conf_t, hash_max_size),
+      NULL },
+
+    { ngx_string("map_hash_bucket_size"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      offsetof(ngx_http_map_conf_t, hash_bucket_size),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_map_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    ngx_http_map_create_conf,              /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_map_module = {
+    NGX_MODULE_V1,
+    &ngx_http_map_module_ctx,              /* module context */
+    ngx_http_map_commands,                 /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_map_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+    uintptr_t data)
+{
+    ngx_http_map_ctx_t  *map = (ngx_http_map_ctx_t *) data;
+
+    ngx_str_t                   val, str;
+    ngx_http_complex_value_t   *cv;
+    ngx_http_variable_value_t  *value;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http map started");
+
+    if (ngx_http_complex_value(r, &map->value, &val) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (map->hostnames && val.len > 0 && val.data[val.len - 1] == '.') {
+        val.len--;
+    }
+
+    value = ngx_http_map_find(r, &map->map, &val);
+
+    if (value == NULL) {
+        value = map->default_value;
+    }
+
+    if (!value->valid) {
+        cv = (ngx_http_complex_value_t *) value->data;
+
+        if (ngx_http_complex_value(r, cv, &str) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->len = str.len;
+        v->data = str.data;
+
+    } else {
+        *v = *value;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http map: \"%V\" \"%v\"", &val, v);
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_map_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_map_conf_t  *mcf;
+
+    mcf = ngx_palloc(cf->pool, sizeof(ngx_http_map_conf_t));
+    if (mcf == NULL) {
+        return NULL;
+    }
+
+    mcf->hash_max_size = NGX_CONF_UNSET_UINT;
+    mcf->hash_bucket_size = NGX_CONF_UNSET_UINT;
+
+    return mcf;
+}
+
+
+static char *
+ngx_http_map_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_map_conf_t  *mcf = conf;
+
+    char                              *rv;
+    ngx_str_t                         *value, name;
+    ngx_conf_t                         save;
+    ngx_pool_t                        *pool;
+    ngx_hash_init_t                    hash;
+    ngx_http_map_ctx_t                *map;
+    ngx_http_variable_t               *var;
+    ngx_http_map_conf_ctx_t            ctx;
+    ngx_http_compile_complex_value_t   ccv;
+
+    if (mcf->hash_max_size == NGX_CONF_UNSET_UINT) {
+        mcf->hash_max_size = 2048;
+    }
+
+    if (mcf->hash_bucket_size == NGX_CONF_UNSET_UINT) {
+        mcf->hash_bucket_size = ngx_cacheline_size;
+
+    } else {
+        mcf->hash_bucket_size = ngx_align(mcf->hash_bucket_size,
+                                          ngx_cacheline_size);
+    }
+
+    map = ngx_pcalloc(cf->pool, sizeof(ngx_http_map_ctx_t));
+    if (map == 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 = &map->value;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    name = value[2];
+
+    if (name.data[0] != '$') {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid variable name \"%V\"", &name);
+        return NGX_CONF_ERROR;
+    }
+
+    name.len--;
+    name.data++;
+
+    var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
+    if (var == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    var->get_handler = ngx_http_map_variable;
+    var->data = (uintptr_t) map;
+
+    pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
+    if (pool == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ctx.keys.pool = cf->pool;
+    ctx.keys.temp_pool = pool;
+
+    if (ngx_hash_keys_array_init(&ctx.keys, NGX_HASH_LARGE) != NGX_OK) {
+        ngx_destroy_pool(pool);
+        return NGX_CONF_ERROR;
+    }
+
+    ctx.values_hash = ngx_pcalloc(pool, sizeof(ngx_array_t) * ctx.keys.hsize);
+    if (ctx.values_hash == NULL) {
+        ngx_destroy_pool(pool);
+        return NGX_CONF_ERROR;
+    }
+
+#if (NGX_PCRE)
+    if (ngx_array_init(&ctx.regexes, cf->pool, 2, sizeof(ngx_http_map_regex_t))
+        != NGX_OK)
+    {
+        ngx_destroy_pool(pool);
+        return NGX_CONF_ERROR;
+    }
+#endif
+
+    ctx.default_value = NULL;
+    ctx.cf = &save;
+    ctx.hostnames = 0;
+    ctx.no_cacheable = 0;
+
+    save = *cf;
+    cf->pool = pool;
+    cf->ctx = &ctx;
+    cf->handler = ngx_http_map;
+    cf->handler_conf = conf;
+
+    rv = ngx_conf_parse(cf, NULL);
+
+    *cf = save;
+
+    if (rv != NGX_CONF_OK) {
+        ngx_destroy_pool(pool);
+        return rv;
+    }
+
+    if (ctx.no_cacheable) {
+        var->flags |= NGX_HTTP_VAR_NOCACHEABLE;
+    }
+
+    map->default_value = ctx.default_value ? ctx.default_value:
+                                             &ngx_http_variable_null_value;
+
+    map->hostnames = ctx.hostnames;
+
+    hash.key = ngx_hash_key_lc;
+    hash.max_size = mcf->hash_max_size;
+    hash.bucket_size = mcf->hash_bucket_size;
+    hash.name = "map_hash";
+    hash.pool = cf->pool;
+
+    if (ctx.keys.keys.nelts) {
+        hash.hash = &map->map.hash.hash;
+        hash.temp_pool = NULL;
+
+        if (ngx_hash_init(&hash, ctx.keys.keys.elts, ctx.keys.keys.nelts)
+            != NGX_OK)
+        {
+            ngx_destroy_pool(pool);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (ctx.keys.dns_wc_head.nelts) {
+
+        ngx_qsort(ctx.keys.dns_wc_head.elts,
+                  (size_t) ctx.keys.dns_wc_head.nelts,
+                  sizeof(ngx_hash_key_t), ngx_http_map_cmp_dns_wildcards);
+
+        hash.hash = NULL;
+        hash.temp_pool = pool;
+
+        if (ngx_hash_wildcard_init(&hash, ctx.keys.dns_wc_head.elts,
+                                   ctx.keys.dns_wc_head.nelts)
+            != NGX_OK)
+        {
+            ngx_destroy_pool(pool);
+            return NGX_CONF_ERROR;
+        }
+
+        map->map.hash.wc_head = (ngx_hash_wildcard_t *) hash.hash;
+    }
+
+    if (ctx.keys.dns_wc_tail.nelts) {
+
+        ngx_qsort(ctx.keys.dns_wc_tail.elts,
+                  (size_t) ctx.keys.dns_wc_tail.nelts,
+                  sizeof(ngx_hash_key_t), ngx_http_map_cmp_dns_wildcards);
+
+        hash.hash = NULL;
+        hash.temp_pool = pool;
+
+        if (ngx_hash_wildcard_init(&hash, ctx.keys.dns_wc_tail.elts,
+                                   ctx.keys.dns_wc_tail.nelts)
+            != NGX_OK)
+        {
+            ngx_destroy_pool(pool);
+            return NGX_CONF_ERROR;
+        }
+
+        map->map.hash.wc_tail = (ngx_hash_wildcard_t *) hash.hash;
+    }
+
+#if (NGX_PCRE)
+
+    if (ctx.regexes.nelts) {
+        map->map.regex = ctx.regexes.elts;
+        map->map.nregex = ctx.regexes.nelts;
+    }
+
+#endif
+
+    ngx_destroy_pool(pool);
+
+    return rv;
+}
+
+
+static int ngx_libc_cdecl
+ngx_http_map_cmp_dns_wildcards(const void *one, const void *two)
+{
+    ngx_hash_key_t  *first, *second;
+
+    first = (ngx_hash_key_t *) one;
+    second = (ngx_hash_key_t *) two;
+
+    return ngx_dns_strcmp(first->key.data, second->key.data);
+}
+
+
+static char *
+ngx_http_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
+{
+    u_char                            *data;
+    size_t                             len;
+    ngx_int_t                          rv;
+    ngx_str_t                         *value, v;
+    ngx_uint_t                         i, key;
+    ngx_http_map_conf_ctx_t           *ctx;
+    ngx_http_complex_value_t           cv, *cvp;
+    ngx_http_variable_value_t         *var, **vp;
+    ngx_http_compile_complex_value_t   ccv;
+
+    ctx = cf->ctx;
+
+    value = cf->args->elts;
+
+    if (cf->args->nelts == 1
+        && ngx_strcmp(value[0].data, "hostnames") == 0)
+    {
+        ctx->hostnames = 1;
+        return NGX_CONF_OK;
+    }
+
+    if (cf->args->nelts == 1
+        && ngx_strcmp(value[0].data, "volatile") == 0)
+    {
+        ctx->no_cacheable = 1;
+        return NGX_CONF_OK;
+    }
+
+    if (cf->args->nelts != 2) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid number of the map parameters");
+        return NGX_CONF_ERROR;
+    }
+
+    if (ngx_strcmp(value[0].data, "include") == 0) {
+        return ngx_conf_include(cf, dummy, conf);
+    }
+
+    key = 0;
+
+    for (i = 0; i < value[1].len; i++) {
+        key = ngx_hash(key, value[1].data[i]);
+    }
+
+    key %= ctx->keys.hsize;
+
+    vp = ctx->values_hash[key].elts;
+
+    if (vp) {
+        for (i = 0; i < ctx->values_hash[key].nelts; i++) {
+
+            if (vp[i]->valid) {
+                data = vp[i]->data;
+                len = vp[i]->len;
+
+            } else {
+                cvp = (ngx_http_complex_value_t *) vp[i]->data;
+                data = cvp->value.data;
+                len = cvp->value.len;
+            }
+
+            if (value[1].len != len) {
+                continue;
+            }
+
+            if (ngx_strncmp(value[1].data, data, len) == 0) {
+                var = vp[i];
+                goto found;
+            }
+        }
+
+    } else {
+        if (ngx_array_init(&ctx->values_hash[key], cf->pool, 4,
+                           sizeof(ngx_http_variable_value_t *))
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    var = ngx_palloc(ctx->keys.pool, sizeof(ngx_http_variable_value_t));
+    if (var == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    v.len = value[1].len;
+    v.data = ngx_pstrdup(ctx->keys.pool, &value[1]);
+    if (v.data == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = ctx->cf;
+    ccv.value = &v;
+    ccv.complex_value = &cv;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (cv.lengths != NULL) {
+        cvp = ngx_palloc(ctx->keys.pool, sizeof(ngx_http_complex_value_t));
+        if (cvp == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        *cvp = cv;
+
+        var->len = 0;
+        var->data = (u_char *) cvp;
+        var->valid = 0;
+
+    } else {
+        var->len = v.len;
+        var->data = v.data;
+        var->valid = 1;
+    }
+
+    var->no_cacheable = 0;
+    var->not_found = 0;
+
+    vp = ngx_array_push(&ctx->values_hash[key]);
+    if (vp == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    *vp = var;
+
+found:
+
+    if (ngx_strcmp(value[0].data, "default") == 0) {
+
+        if (ctx->default_value) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "duplicate default map parameter");
+            return NGX_CONF_ERROR;
+        }
+
+        ctx->default_value = var;
+
+        return NGX_CONF_OK;
+    }
+
+#if (NGX_PCRE)
+
+    if (value[0].len && value[0].data[0] == '~') {
+        ngx_regex_compile_t    rc;
+        ngx_http_map_regex_t  *regex;
+        u_char                 errstr[NGX_MAX_CONF_ERRSTR];
+
+        regex = ngx_array_push(&ctx->regexes);
+        if (regex == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        value[0].len--;
+        value[0].data++;
+
+        ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
+
+        if (value[0].data[0] == '*') {
+            value[0].len--;
+            value[0].data++;
+            rc.options = NGX_REGEX_CASELESS;
+        }
+
+        rc.pattern = value[0];
+        rc.err.len = NGX_MAX_CONF_ERRSTR;
+        rc.err.data = errstr;
+
+        regex->regex = ngx_http_regex_compile(ctx->cf, &rc);
+        if (regex->regex == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        regex->value = var;
+
+        return NGX_CONF_OK;
+    }
+
+#endif
+
+    if (value[0].len && value[0].data[0] == '\\') {
+        value[0].len--;
+        value[0].data++;
+    }
+
+    rv = ngx_hash_add_key(&ctx->keys, &value[0], var,
+                          (ctx->hostnames) ? NGX_HASH_WILDCARD_KEY : 0);
+
+    if (rv == NGX_OK) {
+        return NGX_CONF_OK;
+    }
+
+    if (rv == NGX_DECLINED) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid hostname or wildcard \"%V\"", &value[0]);
+    }
+
+    if (rv == NGX_BUSY) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "conflicting parameter \"%V\"", &value[0]);
+    }
+
+    return NGX_CONF_ERROR;
+}
diff --git a/nginx/src/http/modules/ngx_http_memcached_module.c b/nginx/src/http/modules/ngx_http_memcached_module.c
new file mode 100644 (file)
index 0000000..82fa713
--- /dev/null
@@ -0,0 +1,721 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_http_upstream_conf_t   upstream;
+    ngx_int_t                  index;
+    ngx_uint_t                 gzip_flag;
+} ngx_http_memcached_loc_conf_t;
+
+
+typedef struct {
+    size_t                     rest;
+    ngx_http_request_t        *request;
+    ngx_str_t                  key;
+} ngx_http_memcached_ctx_t;
+
+
+static ngx_int_t ngx_http_memcached_create_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_memcached_reinit_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_memcached_process_header(ngx_http_request_t *r);
+static ngx_int_t ngx_http_memcached_filter_init(void *data);
+static ngx_int_t ngx_http_memcached_filter(void *data, ssize_t bytes);
+static void ngx_http_memcached_abort_request(ngx_http_request_t *r);
+static void ngx_http_memcached_finalize_request(ngx_http_request_t *r,
+    ngx_int_t rc);
+
+static void *ngx_http_memcached_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_memcached_merge_loc_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+
+static char *ngx_http_memcached_pass(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+
+static ngx_conf_bitmask_t  ngx_http_memcached_next_upstream_masks[] = {
+    { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR },
+    { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT },
+    { ngx_string("invalid_response"), NGX_HTTP_UPSTREAM_FT_INVALID_HEADER },
+    { ngx_string("not_found"), NGX_HTTP_UPSTREAM_FT_HTTP_404 },
+    { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t  ngx_http_memcached_commands[] = {
+
+    { ngx_string("memcached_pass"),
+      NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
+      ngx_http_memcached_pass,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("memcached_bind"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+      ngx_http_upstream_bind_set_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_memcached_loc_conf_t, upstream.local),
+      NULL },
+
+    { ngx_string("memcached_connect_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_memcached_loc_conf_t, upstream.connect_timeout),
+      NULL },
+
+    { ngx_string("memcached_send_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_memcached_loc_conf_t, upstream.send_timeout),
+      NULL },
+
+    { ngx_string("memcached_buffer_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_memcached_loc_conf_t, upstream.buffer_size),
+      NULL },
+
+    { ngx_string("memcached_read_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_memcached_loc_conf_t, upstream.read_timeout),
+      NULL },
+
+    { ngx_string("memcached_next_upstream"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_memcached_loc_conf_t, upstream.next_upstream),
+      &ngx_http_memcached_next_upstream_masks },
+
+    { ngx_string("memcached_next_upstream_tries"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_memcached_loc_conf_t, upstream.next_upstream_tries),
+      NULL },
+
+    { ngx_string("memcached_next_upstream_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_memcached_loc_conf_t, upstream.next_upstream_timeout),
+      NULL },
+
+    { ngx_string("memcached_gzip_flag"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_memcached_loc_conf_t, gzip_flag),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_memcached_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_memcached_create_loc_conf,    /* create location configuration */
+    ngx_http_memcached_merge_loc_conf      /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_memcached_module = {
+    NGX_MODULE_V1,
+    &ngx_http_memcached_module_ctx,        /* module context */
+    ngx_http_memcached_commands,           /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t  ngx_http_memcached_key = ngx_string("memcached_key");
+
+
+#define NGX_HTTP_MEMCACHED_END   (sizeof(ngx_http_memcached_end) - 1)
+static u_char  ngx_http_memcached_end[] = CRLF "END" CRLF;
+
+
+static ngx_int_t
+ngx_http_memcached_handler(ngx_http_request_t *r)
+{
+    ngx_int_t                       rc;
+    ngx_http_upstream_t            *u;
+    ngx_http_memcached_ctx_t       *ctx;
+    ngx_http_memcached_loc_conf_t  *mlcf;
+
+    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
+        return NGX_HTTP_NOT_ALLOWED;
+    }
+
+    rc = ngx_http_discard_request_body(r);
+
+    if (rc != NGX_OK) {
+        return rc;
+    }
+
+    if (ngx_http_set_content_type(r) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (ngx_http_upstream_create(r) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    u = r->upstream;
+
+    ngx_str_set(&u->schema, "memcached://");
+    u->output.tag = (ngx_buf_tag_t) &ngx_http_memcached_module;
+
+    mlcf = ngx_http_get_module_loc_conf(r, ngx_http_memcached_module);
+
+    u->conf = &mlcf->upstream;
+
+    u->create_request = ngx_http_memcached_create_request;
+    u->reinit_request = ngx_http_memcached_reinit_request;
+    u->process_header = ngx_http_memcached_process_header;
+    u->abort_request = ngx_http_memcached_abort_request;
+    u->finalize_request = ngx_http_memcached_finalize_request;
+
+    ctx = ngx_palloc(r->pool, sizeof(ngx_http_memcached_ctx_t));
+    if (ctx == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    ctx->request = r;
+
+    ngx_http_set_ctx(r, ctx, ngx_http_memcached_module);
+
+    u->input_filter_init = ngx_http_memcached_filter_init;
+    u->input_filter = ngx_http_memcached_filter;
+    u->input_filter_ctx = ctx;
+
+    r->main->count++;
+
+    ngx_http_upstream_init(r);
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_memcached_create_request(ngx_http_request_t *r)
+{
+    size_t                          len;
+    uintptr_t                       escape;
+    ngx_buf_t                      *b;
+    ngx_chain_t                    *cl;
+    ngx_http_memcached_ctx_t       *ctx;
+    ngx_http_variable_value_t      *vv;
+    ngx_http_memcached_loc_conf_t  *mlcf;
+
+    mlcf = ngx_http_get_module_loc_conf(r, ngx_http_memcached_module);
+
+    vv = ngx_http_get_indexed_variable(r, mlcf->index);
+
+    if (vv == NULL || vv->not_found || vv->len == 0) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "the \"$memcached_key\" variable is not set");
+        return NGX_ERROR;
+    }
+
+    escape = 2 * ngx_escape_uri(NULL, vv->data, vv->len, NGX_ESCAPE_MEMCACHED);
+
+    len = sizeof("get ") - 1 + vv->len + escape + sizeof(CRLF) - 1;
+
+    b = ngx_create_temp_buf(r->pool, len);
+    if (b == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl = ngx_alloc_chain_link(r->pool);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl->buf = b;
+    cl->next = NULL;
+
+    r->upstream->request_bufs = cl;
+
+    *b->last++ = 'g'; *b->last++ = 'e'; *b->last++ = 't'; *b->last++ = ' ';
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_memcached_module);
+
+    ctx->key.data = b->last;
+
+    if (escape == 0) {
+        b->last = ngx_copy(b->last, vv->data, vv->len);
+
+    } else {
+        b->last = (u_char *) ngx_escape_uri(b->last, vv->data, vv->len,
+                                            NGX_ESCAPE_MEMCACHED);
+    }
+
+    ctx->key.len = b->last - ctx->key.data;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http memcached request: \"%V\"", &ctx->key);
+
+    *b->last++ = CR; *b->last++ = LF;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_memcached_reinit_request(ngx_http_request_t *r)
+{
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_memcached_process_header(ngx_http_request_t *r)
+{
+    u_char                         *p, *start;
+    ngx_str_t                       line;
+    ngx_uint_t                      flags;
+    ngx_table_elt_t                *h;
+    ngx_http_upstream_t            *u;
+    ngx_http_memcached_ctx_t       *ctx;
+    ngx_http_memcached_loc_conf_t  *mlcf;
+
+    u = r->upstream;
+
+    for (p = u->buffer.pos; p < u->buffer.last; p++) {
+        if (*p == LF) {
+            goto found;
+        }
+    }
+
+    return NGX_AGAIN;
+
+found:
+
+    line.data = u->buffer.pos;
+    line.len = p - u->buffer.pos;
+
+    if (line.len == 0 || *(p - 1) != CR) {
+        goto no_valid;
+    }
+
+    *p = '\0';
+    line.len--;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "memcached: \"%V\"", &line);
+
+    p = u->buffer.pos;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_memcached_module);
+    mlcf = ngx_http_get_module_loc_conf(r, ngx_http_memcached_module);
+
+    if (ngx_strncmp(p, "VALUE ", sizeof("VALUE ") - 1) == 0) {
+
+        p += sizeof("VALUE ") - 1;
+
+        if (ngx_strncmp(p, ctx->key.data, ctx->key.len) != 0) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "memcached sent invalid key in response \"%V\" "
+                          "for key \"%V\"",
+                          &line, &ctx->key);
+
+            return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+        }
+
+        p += ctx->key.len;
+
+        if (*p++ != ' ') {
+            goto no_valid;
+        }
+
+        /* flags */
+
+        start = p;
+
+        while (*p) {
+            if (*p++ == ' ') {
+                if (mlcf->gzip_flag) {
+                    goto flags;
+                } else {
+                    goto length;
+                }
+            }
+        }
+
+        goto no_valid;
+
+    flags:
+
+        flags = ngx_atoi(start, p - start - 1);
+
+        if (flags == (ngx_uint_t) NGX_ERROR) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "memcached sent invalid flags in response \"%V\" "
+                          "for key \"%V\"",
+                          &line, &ctx->key);
+            return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+        }
+
+        if (flags & mlcf->gzip_flag) {
+            h = ngx_list_push(&r->headers_out.headers);
+            if (h == NULL) {
+                return NGX_ERROR;
+            }
+
+            h->hash = 1;
+            ngx_str_set(&h->key, "Content-Encoding");
+            ngx_str_set(&h->value, "gzip");
+            r->headers_out.content_encoding = h;
+        }
+
+    length:
+
+        start = p;
+        p = line.data + line.len;
+
+        u->headers_in.content_length_n = ngx_atoof(start, p - start);
+        if (u->headers_in.content_length_n == NGX_ERROR) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "memcached sent invalid length in response \"%V\" "
+                          "for key \"%V\"",
+                          &line, &ctx->key);
+            return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+        }
+
+        u->headers_in.status_n = 200;
+        u->state->status = 200;
+        u->buffer.pos = p + sizeof(CRLF) - 1;
+
+        return NGX_OK;
+    }
+
+    if (ngx_strcmp(p, "END\x0d") == 0) {
+        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                      "key: \"%V\" was not found by memcached", &ctx->key);
+
+        u->headers_in.content_length_n = 0;
+        u->headers_in.status_n = 404;
+        u->state->status = 404;
+        u->buffer.pos = p + sizeof("END" CRLF) - 1;
+        u->keepalive = 1;
+
+        return NGX_OK;
+    }
+
+no_valid:
+
+    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                  "memcached sent invalid response: \"%V\"", &line);
+
+    return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+}
+
+
+static ngx_int_t
+ngx_http_memcached_filter_init(void *data)
+{
+    ngx_http_memcached_ctx_t  *ctx = data;
+
+    ngx_http_upstream_t  *u;
+
+    u = ctx->request->upstream;
+
+    if (u->headers_in.status_n != 404) {
+        u->length = u->headers_in.content_length_n + NGX_HTTP_MEMCACHED_END;
+        ctx->rest = NGX_HTTP_MEMCACHED_END;
+
+    } else {
+        u->length = 0;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_memcached_filter(void *data, ssize_t bytes)
+{
+    ngx_http_memcached_ctx_t  *ctx = data;
+
+    u_char               *last;
+    ngx_buf_t            *b;
+    ngx_chain_t          *cl, **ll;
+    ngx_http_upstream_t  *u;
+
+    u = ctx->request->upstream;
+    b = &u->buffer;
+
+    if (u->length == (ssize_t) ctx->rest) {
+
+        if (ngx_strncmp(b->last,
+                   ngx_http_memcached_end + NGX_HTTP_MEMCACHED_END - ctx->rest,
+                   bytes)
+            != 0)
+        {
+            ngx_log_error(NGX_LOG_ERR, ctx->request->connection->log, 0,
+                          "memcached sent invalid trailer");
+
+            u->length = 0;
+            ctx->rest = 0;
+
+            return NGX_OK;
+        }
+
+        u->length -= bytes;
+        ctx->rest -= bytes;
+
+        if (u->length == 0) {
+            u->keepalive = 1;
+        }
+
+        return NGX_OK;
+    }
+
+    for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) {
+        ll = &cl->next;
+    }
+
+    cl = ngx_chain_get_free_buf(ctx->request->pool, &u->free_bufs);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl->buf->flush = 1;
+    cl->buf->memory = 1;
+
+    *ll = cl;
+
+    last = b->last;
+    cl->buf->pos = last;
+    b->last += bytes;
+    cl->buf->last = b->last;
+    cl->buf->tag = u->output.tag;
+
+    ngx_log_debug4(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
+                   "memcached filter bytes:%z size:%z length:%O rest:%z",
+                   bytes, b->last - b->pos, u->length, ctx->rest);
+
+    if (bytes <= (ssize_t) (u->length - NGX_HTTP_MEMCACHED_END)) {
+        u->length -= bytes;
+        return NGX_OK;
+    }
+
+    last += (size_t) (u->length - NGX_HTTP_MEMCACHED_END);
+
+    if (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");
+
+        b->last = last;
+        cl->buf->last = last;
+        u->length = 0;
+        ctx->rest = 0;
+
+        return NGX_OK;
+    }
+
+    ctx->rest -= b->last - last;
+    b->last = last;
+    cl->buf->last = last;
+    u->length = ctx->rest;
+
+    if (u->length == 0) {
+        u->keepalive = 1;
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_memcached_abort_request(ngx_http_request_t *r)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "abort http memcached request");
+    return;
+}
+
+
+static void
+ngx_http_memcached_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "finalize http memcached request");
+    return;
+}
+
+
+static void *
+ngx_http_memcached_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_memcached_loc_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_memcached_loc_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->upstream.bufs.num = 0;
+     *     conf->upstream.next_upstream = 0;
+     *     conf->upstream.temp_path = NULL;
+     */
+
+    conf->upstream.local = NGX_CONF_UNSET_PTR;
+    conf->upstream.next_upstream_tries = NGX_CONF_UNSET_UINT;
+    conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.next_upstream_timeout = NGX_CONF_UNSET_MSEC;
+
+    conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE;
+
+    /* the hardcoded values */
+    conf->upstream.cyclic_temp_file = 0;
+    conf->upstream.buffering = 0;
+    conf->upstream.ignore_client_abort = 0;
+    conf->upstream.send_lowat = 0;
+    conf->upstream.bufs.num = 0;
+    conf->upstream.busy_buffers_size = 0;
+    conf->upstream.max_temp_file_size = 0;
+    conf->upstream.temp_file_write_size = 0;
+    conf->upstream.intercept_errors = 1;
+    conf->upstream.intercept_404 = 1;
+    conf->upstream.pass_request_headers = 0;
+    conf->upstream.pass_request_body = 0;
+    conf->upstream.force_ranges = 1;
+
+    conf->index = NGX_CONF_UNSET;
+    conf->gzip_flag = NGX_CONF_UNSET_UINT;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_memcached_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_memcached_loc_conf_t *prev = parent;
+    ngx_http_memcached_loc_conf_t *conf = child;
+
+    ngx_conf_merge_ptr_value(conf->upstream.local,
+                              prev->upstream.local, NULL);
+
+    ngx_conf_merge_uint_value(conf->upstream.next_upstream_tries,
+                              prev->upstream.next_upstream_tries, 0);
+
+    ngx_conf_merge_msec_value(conf->upstream.connect_timeout,
+                              prev->upstream.connect_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->upstream.send_timeout,
+                              prev->upstream.send_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->upstream.read_timeout,
+                              prev->upstream.read_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->upstream.next_upstream_timeout,
+                              prev->upstream.next_upstream_timeout, 0);
+
+    ngx_conf_merge_size_value(conf->upstream.buffer_size,
+                              prev->upstream.buffer_size,
+                              (size_t) ngx_pagesize);
+
+    ngx_conf_merge_bitmask_value(conf->upstream.next_upstream,
+                              prev->upstream.next_upstream,
+                              (NGX_CONF_BITMASK_SET
+                               |NGX_HTTP_UPSTREAM_FT_ERROR
+                               |NGX_HTTP_UPSTREAM_FT_TIMEOUT));
+
+    if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) {
+        conf->upstream.next_upstream = NGX_CONF_BITMASK_SET
+                                       |NGX_HTTP_UPSTREAM_FT_OFF;
+    }
+
+    if (conf->upstream.upstream == NULL) {
+        conf->upstream.upstream = prev->upstream.upstream;
+    }
+
+    if (conf->index == NGX_CONF_UNSET) {
+        conf->index = prev->index;
+    }
+
+    ngx_conf_merge_uint_value(conf->gzip_flag, prev->gzip_flag, 0);
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_memcached_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_memcached_loc_conf_t *mlcf = conf;
+
+    ngx_str_t                 *value;
+    ngx_url_t                  u;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    if (mlcf->upstream.upstream) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    ngx_memzero(&u, sizeof(ngx_url_t));
+
+    u.url = value[1];
+    u.no_resolve = 1;
+
+    mlcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);
+    if (mlcf->upstream.upstream == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+    clcf->handler = ngx_http_memcached_handler;
+
+    if (clcf->name.data[clcf->name.len - 1] == '/') {
+        clcf->auto_redirect = 1;
+    }
+
+    mlcf->index = ngx_http_get_variable_index(cf, &ngx_http_memcached_key);
+
+    if (mlcf->index == NGX_ERROR) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_mirror_module.c b/nginx/src/http/modules/ngx_http_mirror_module.c
new file mode 100644 (file)
index 0000000..787adb3
--- /dev/null
@@ -0,0 +1,264 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_array_t  *mirror;
+    ngx_flag_t    request_body;
+} ngx_http_mirror_loc_conf_t;
+
+
+typedef struct {
+    ngx_int_t     status;
+} ngx_http_mirror_ctx_t;
+
+
+static ngx_int_t ngx_http_mirror_handler(ngx_http_request_t *r);
+static void ngx_http_mirror_body_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_mirror_handler_internal(ngx_http_request_t *r);
+static void *ngx_http_mirror_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_mirror_merge_loc_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static char *ngx_http_mirror(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static ngx_int_t ngx_http_mirror_init(ngx_conf_t *cf);
+
+
+static ngx_command_t  ngx_http_mirror_commands[] = {
+
+    { ngx_string("mirror"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_mirror,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("mirror_request_body"),
+      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_mirror_loc_conf_t, request_body),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_mirror_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_mirror_init,                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_mirror_create_loc_conf,       /* create location configuration */
+    ngx_http_mirror_merge_loc_conf         /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_mirror_module = {
+    NGX_MODULE_V1,
+    &ngx_http_mirror_module_ctx,           /* module context */
+    ngx_http_mirror_commands,              /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_mirror_handler(ngx_http_request_t *r)
+{
+    ngx_int_t                    rc;
+    ngx_http_mirror_ctx_t       *ctx;
+    ngx_http_mirror_loc_conf_t  *mlcf;
+
+    if (r != r->main) {
+        return NGX_DECLINED;
+    }
+
+    mlcf = ngx_http_get_module_loc_conf(r, ngx_http_mirror_module);
+
+    if (mlcf->mirror == NULL) {
+        return NGX_DECLINED;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "mirror handler");
+
+    if (mlcf->request_body) {
+        ctx = ngx_http_get_module_ctx(r, ngx_http_mirror_module);
+
+        if (ctx) {
+            return ctx->status;
+        }
+
+        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_mirror_ctx_t));
+        if (ctx == NULL) {
+            return NGX_ERROR;
+        }
+
+        ctx->status = NGX_DONE;
+
+        ngx_http_set_ctx(r, ctx, ngx_http_mirror_module);
+
+        rc = ngx_http_read_client_request_body(r, ngx_http_mirror_body_handler);
+        if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+            return rc;
+        }
+
+        ngx_http_finalize_request(r, NGX_DONE);
+        return NGX_DONE;
+    }
+
+    return ngx_http_mirror_handler_internal(r);
+}
+
+
+static void
+ngx_http_mirror_body_handler(ngx_http_request_t *r)
+{
+    ngx_http_mirror_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_mirror_module);
+
+    ctx->status = ngx_http_mirror_handler_internal(r);
+
+    r->preserve_body = 1;
+
+    r->write_event_handler = ngx_http_core_run_phases;
+    ngx_http_core_run_phases(r);
+}
+
+
+static ngx_int_t
+ngx_http_mirror_handler_internal(ngx_http_request_t *r)
+{
+    ngx_str_t                   *name;
+    ngx_uint_t                   i;
+    ngx_http_request_t          *sr;
+    ngx_http_mirror_loc_conf_t  *mlcf;
+
+    mlcf = ngx_http_get_module_loc_conf(r, ngx_http_mirror_module);
+
+    name = mlcf->mirror->elts;
+
+    for (i = 0; i < mlcf->mirror->nelts; i++) {
+        if (ngx_http_subrequest(r, &name[i], &r->args, &sr, NULL,
+                                NGX_HTTP_SUBREQUEST_BACKGROUND)
+            != NGX_OK)
+        {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        sr->header_only = 1;
+        sr->method = r->method;
+        sr->method_name = r->method_name;
+    }
+
+    return NGX_DECLINED;
+}
+
+
+static void *
+ngx_http_mirror_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_mirror_loc_conf_t  *mlcf;
+
+    mlcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_mirror_loc_conf_t));
+    if (mlcf == NULL) {
+        return NULL;
+    }
+
+    mlcf->mirror = NGX_CONF_UNSET_PTR;
+    mlcf->request_body = NGX_CONF_UNSET;
+
+    return mlcf;
+}
+
+
+static char *
+ngx_http_mirror_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_mirror_loc_conf_t *prev = parent;
+    ngx_http_mirror_loc_conf_t *conf = child;
+
+    ngx_conf_merge_ptr_value(conf->mirror, prev->mirror, NULL);
+    ngx_conf_merge_value(conf->request_body, prev->request_body, 1);
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_mirror(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_mirror_loc_conf_t *mlcf = conf;
+
+    ngx_str_t  *value, *s;
+
+    value = cf->args->elts;
+
+    if (ngx_strcmp(value[1].data, "off") == 0) {
+        if (mlcf->mirror != NGX_CONF_UNSET_PTR) {
+            return "is duplicate";
+        }
+
+        mlcf->mirror = NULL;
+        return NGX_CONF_OK;
+    }
+
+    if (mlcf->mirror == NULL) {
+        return "is duplicate";
+    }
+
+    if (mlcf->mirror == NGX_CONF_UNSET_PTR) {
+        mlcf->mirror = ngx_array_create(cf->pool, 4, sizeof(ngx_str_t));
+        if (mlcf->mirror == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    s = ngx_array_push(mlcf->mirror);
+    if (s == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    *s = value[1];
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mirror_init(ngx_conf_t *cf)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_PRECONTENT_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_mirror_handler;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_mp4_module.c b/nginx/src/http/modules/ngx_http_mp4_module.c
new file mode 100644 (file)
index 0000000..618bf78
--- /dev/null
@@ -0,0 +1,3562 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#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_LAST_ATOM    NGX_HTTP_MP4_CO64_DATA
+
+
+typedef struct {
+    size_t                buffer_size;
+    size_t                max_buffer_size;
+} ngx_http_mp4_conf_t;
+
+
+typedef struct {
+    u_char                chunk[4];
+    u_char                samples[4];
+    u_char                id[4];
+} ngx_mp4_stsc_entry_t;
+
+
+typedef struct {
+    uint32_t              timescale;
+    uint32_t              time_to_sample_entries;
+    uint32_t              sample_to_chunk_entries;
+    uint32_t              sync_samples_entries;
+    uint32_t              composition_offset_entries;
+    uint32_t              sample_sizes_entries;
+    uint32_t              chunks;
+
+    ngx_uint_t            start_sample;
+    ngx_uint_t            end_sample;
+    ngx_uint_t            start_chunk;
+    ngx_uint_t            end_chunk;
+    ngx_uint_t            start_chunk_samples;
+    ngx_uint_t            end_chunk_samples;
+    uint64_t              start_chunk_samples_size;
+    uint64_t              end_chunk_samples_size;
+    off_t                 start_offset;
+    off_t                 end_offset;
+
+    size_t                tkhd_size;
+    size_t                mdhd_size;
+    size_t                hdlr_size;
+    size_t                vmhd_size;
+    size_t                smhd_size;
+    size_t                dinf_size;
+    size_t                size;
+
+    ngx_chain_t           out[NGX_HTTP_MP4_LAST_ATOM + 1];
+
+    ngx_buf_t             trak_atom_buf;
+    ngx_buf_t             tkhd_atom_buf;
+    ngx_buf_t             mdia_atom_buf;
+    ngx_buf_t             mdhd_atom_buf;
+    ngx_buf_t             hdlr_atom_buf;
+    ngx_buf_t             minf_atom_buf;
+    ngx_buf_t             vmhd_atom_buf;
+    ngx_buf_t             smhd_atom_buf;
+    ngx_buf_t             dinf_atom_buf;
+    ngx_buf_t             stbl_atom_buf;
+    ngx_buf_t             stsd_atom_buf;
+    ngx_buf_t             stts_atom_buf;
+    ngx_buf_t             stts_data_buf;
+    ngx_buf_t             stss_atom_buf;
+    ngx_buf_t             stss_data_buf;
+    ngx_buf_t             ctts_atom_buf;
+    ngx_buf_t             ctts_data_buf;
+    ngx_buf_t             stsc_atom_buf;
+    ngx_buf_t             stsc_start_chunk_buf;
+    ngx_buf_t             stsc_end_chunk_buf;
+    ngx_buf_t             stsc_data_buf;
+    ngx_buf_t             stsz_atom_buf;
+    ngx_buf_t             stsz_data_buf;
+    ngx_buf_t             stco_atom_buf;
+    ngx_buf_t             stco_data_buf;
+    ngx_buf_t             co64_atom_buf;
+    ngx_buf_t             co64_data_buf;
+
+    ngx_mp4_stsc_entry_t  stsc_start_chunk_entry;
+    ngx_mp4_stsc_entry_t  stsc_end_chunk_entry;
+} ngx_http_mp4_trak_t;
+
+
+typedef struct {
+    ngx_file_t            file;
+
+    u_char               *buffer;
+    u_char               *buffer_start;
+    u_char               *buffer_pos;
+    u_char               *buffer_end;
+    size_t                buffer_size;
+
+    off_t                 offset;
+    off_t                 end;
+    off_t                 content_length;
+    ngx_uint_t            start;
+    ngx_uint_t            length;
+    uint32_t              timescale;
+    ngx_http_request_t   *request;
+    ngx_array_t           trak;
+    ngx_http_mp4_trak_t   traks[2];
+
+    size_t                ftyp_size;
+    size_t                moov_size;
+
+    ngx_chain_t          *out;
+    ngx_chain_t           ftyp_atom;
+    ngx_chain_t           moov_atom;
+    ngx_chain_t           mvhd_atom;
+    ngx_chain_t           mdat_atom;
+    ngx_chain_t           mdat_data;
+
+    ngx_buf_t             ftyp_atom_buf;
+    ngx_buf_t             moov_atom_buf;
+    ngx_buf_t             mvhd_atom_buf;
+    ngx_buf_t             mdat_atom_buf;
+    ngx_buf_t             mdat_data_buf;
+
+    u_char                moov_atom_header[8];
+    u_char                mdat_atom_header[16];
+} ngx_http_mp4_file_t;
+
+
+typedef struct {
+    char                 *name;
+    ngx_int_t           (*handler)(ngx_http_mp4_file_t *mp4,
+                                   uint64_t atom_data_size);
+} ngx_http_mp4_atom_handler_t;
+
+
+#define ngx_mp4_atom_header(mp4)   (mp4->buffer_pos - 8)
+#define ngx_mp4_atom_data(mp4)     mp4->buffer_pos
+#define ngx_mp4_atom_data_size(t)  (uint64_t) (sizeof(t) - 8)
+
+
+#define ngx_mp4_atom_next(mp4, n)                                             \
+                                                                              \
+    if (n > (size_t) (mp4->buffer_end - mp4->buffer_pos)) {                   \
+        mp4->buffer_pos = mp4->buffer_end;                                    \
+                                                                              \
+    } else {                                                                  \
+        mp4->buffer_pos += (size_t) n;                                        \
+    }                                                                         \
+                                                                              \
+    mp4->offset += n
+
+
+#define ngx_mp4_set_atom_name(p, n1, n2, n3, n4)                              \
+    ((u_char *) (p))[4] = n1;                                                 \
+    ((u_char *) (p))[5] = n2;                                                 \
+    ((u_char *) (p))[6] = n3;                                                 \
+    ((u_char *) (p))[7] = n4
+
+#define ngx_mp4_get_32value(p)                                                \
+    ( ((uint32_t) ((u_char *) (p))[0] << 24)                                  \
+    + (           ((u_char *) (p))[1] << 16)                                  \
+    + (           ((u_char *) (p))[2] << 8)                                   \
+    + (           ((u_char *) (p))[3]) )
+
+#define ngx_mp4_set_32value(p, n)                                             \
+    ((u_char *) (p))[0] = (u_char) ((n) >> 24);                               \
+    ((u_char *) (p))[1] = (u_char) ((n) >> 16);                               \
+    ((u_char *) (p))[2] = (u_char) ((n) >> 8);                                \
+    ((u_char *) (p))[3] = (u_char)  (n)
+
+#define ngx_mp4_get_64value(p)                                                \
+    ( ((uint64_t) ((u_char *) (p))[0] << 56)                                  \
+    + ((uint64_t) ((u_char *) (p))[1] << 48)                                  \
+    + ((uint64_t) ((u_char *) (p))[2] << 40)                                  \
+    + ((uint64_t) ((u_char *) (p))[3] << 32)                                  \
+    + ((uint64_t) ((u_char *) (p))[4] << 24)                                  \
+    + (           ((u_char *) (p))[5] << 16)                                  \
+    + (           ((u_char *) (p))[6] << 8)                                   \
+    + (           ((u_char *) (p))[7]) )
+
+#define ngx_mp4_set_64value(p, n)                                             \
+    ((u_char *) (p))[0] = (u_char) ((uint64_t) (n) >> 56);                    \
+    ((u_char *) (p))[1] = (u_char) ((uint64_t) (n) >> 48);                    \
+    ((u_char *) (p))[2] = (u_char) ((uint64_t) (n) >> 40);                    \
+    ((u_char *) (p))[3] = (u_char) ((uint64_t) (n) >> 32);                    \
+    ((u_char *) (p))[4] = (u_char) (           (n) >> 24);                    \
+    ((u_char *) (p))[5] = (u_char) (           (n) >> 16);                    \
+    ((u_char *) (p))[6] = (u_char) (           (n) >> 8);                     \
+    ((u_char *) (p))[7] = (u_char)             (n)
+
+#define ngx_mp4_last_trak(mp4)                                                \
+    &((ngx_http_mp4_trak_t *) mp4->trak.elts)[mp4->trak.nelts - 1]
+
+
+static ngx_int_t ngx_http_mp4_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_mp4_atofp(u_char *line, size_t n, size_t point);
+
+static ngx_int_t ngx_http_mp4_process(ngx_http_mp4_file_t *mp4);
+static ngx_int_t ngx_http_mp4_read_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_atom_handler_t *atom, uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_read(ngx_http_mp4_file_t *mp4, size_t size);
+static ngx_int_t ngx_http_mp4_read_ftyp_atom(ngx_http_mp4_file_t *mp4,
+    uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_read_moov_atom(ngx_http_mp4_file_t *mp4,
+    uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_read_mdat_atom(ngx_http_mp4_file_t *mp4,
+    uint64_t atom_data_size);
+static size_t ngx_http_mp4_update_mdat_atom(ngx_http_mp4_file_t *mp4,
+    off_t start_offset, off_t end_offset);
+static ngx_int_t ngx_http_mp4_read_mvhd_atom(ngx_http_mp4_file_t *mp4,
+    uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_read_trak_atom(ngx_http_mp4_file_t *mp4,
+    uint64_t atom_data_size);
+static void ngx_http_mp4_update_trak_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak);
+static ngx_int_t ngx_http_mp4_read_cmov_atom(ngx_http_mp4_file_t *mp4,
+    uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_read_tkhd_atom(ngx_http_mp4_file_t *mp4,
+    uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_read_mdia_atom(ngx_http_mp4_file_t *mp4,
+    uint64_t atom_data_size);
+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 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,
+    uint64_t atom_data_size);
+static void ngx_http_mp4_update_minf_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak);
+static ngx_int_t ngx_http_mp4_read_dinf_atom(ngx_http_mp4_file_t *mp4,
+    uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_read_vmhd_atom(ngx_http_mp4_file_t *mp4,
+    uint64_t atom_data_size);
+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_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,
+    uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_read_stts_atom(ngx_http_mp4_file_t *mp4,
+    uint64_t atom_data_size);
+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 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,
+    ngx_http_mp4_trak_t *trak);
+static void ngx_http_mp4_crop_stss_data(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak, ngx_uint_t start);
+static ngx_int_t ngx_http_mp4_read_ctts_atom(ngx_http_mp4_file_t *mp4,
+    uint64_t atom_data_size);
+static void ngx_http_mp4_update_ctts_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak);
+static void ngx_http_mp4_crop_ctts_data(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak, ngx_uint_t start);
+static ngx_int_t ngx_http_mp4_read_stsc_atom(ngx_http_mp4_file_t *mp4,
+    uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_update_stsc_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak);
+static ngx_int_t ngx_http_mp4_crop_stsc_data(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak, ngx_uint_t start);
+static ngx_int_t ngx_http_mp4_read_stsz_atom(ngx_http_mp4_file_t *mp4,
+    uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_update_stsz_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak);
+static ngx_int_t ngx_http_mp4_read_stco_atom(ngx_http_mp4_file_t *mp4,
+    uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_update_stco_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak);
+static void ngx_http_mp4_adjust_stco_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak, int32_t adjustment);
+static ngx_int_t ngx_http_mp4_read_co64_atom(ngx_http_mp4_file_t *mp4,
+    uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_update_co64_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak);
+static void ngx_http_mp4_adjust_co64_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak, off_t adjustment);
+
+static char *ngx_http_mp4(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static void *ngx_http_mp4_create_conf(ngx_conf_t *cf);
+static char *ngx_http_mp4_merge_conf(ngx_conf_t *cf, void *parent, void *child);
+
+
+static ngx_command_t  ngx_http_mp4_commands[] = {
+
+    { ngx_string("mp4"),
+      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
+      ngx_http_mp4,
+      0,
+      0,
+      NULL },
+
+    { ngx_string("mp4_buffer_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_mp4_conf_t, buffer_size),
+      NULL },
+
+    { ngx_string("mp4_max_buffer_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_mp4_conf_t, max_buffer_size),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_mp4_module_ctx = {
+    NULL,                          /* preconfiguration */
+    NULL,                          /* postconfiguration */
+
+    NULL,                          /* create main configuration */
+    NULL,                          /* init main configuration */
+
+    NULL,                          /* create server configuration */
+    NULL,                          /* merge server configuration */
+
+    ngx_http_mp4_create_conf,      /* create location configuration */
+    ngx_http_mp4_merge_conf        /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_mp4_module = {
+    NGX_MODULE_V1,
+    &ngx_http_mp4_module_ctx,      /* module context */
+    ngx_http_mp4_commands,         /* module directives */
+    NGX_HTTP_MODULE,               /* module type */
+    NULL,                          /* init master */
+    NULL,                          /* init module */
+    NULL,                          /* init process */
+    NULL,                          /* init thread */
+    NULL,                          /* exit thread */
+    NULL,                          /* exit process */
+    NULL,                          /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_mp4_atom_handler_t  ngx_http_mp4_atoms[] = {
+    { "ftyp", ngx_http_mp4_read_ftyp_atom },
+    { "moov", ngx_http_mp4_read_moov_atom },
+    { "mdat", ngx_http_mp4_read_mdat_atom },
+    { NULL, NULL }
+};
+
+static ngx_http_mp4_atom_handler_t  ngx_http_mp4_moov_atoms[] = {
+    { "mvhd", ngx_http_mp4_read_mvhd_atom },
+    { "trak", ngx_http_mp4_read_trak_atom },
+    { "cmov", ngx_http_mp4_read_cmov_atom },
+    { NULL, NULL }
+};
+
+static ngx_http_mp4_atom_handler_t  ngx_http_mp4_trak_atoms[] = {
+    { "tkhd", ngx_http_mp4_read_tkhd_atom },
+    { "mdia", ngx_http_mp4_read_mdia_atom },
+    { NULL, NULL }
+};
+
+static ngx_http_mp4_atom_handler_t  ngx_http_mp4_mdia_atoms[] = {
+    { "mdhd", ngx_http_mp4_read_mdhd_atom },
+    { "hdlr", ngx_http_mp4_read_hdlr_atom },
+    { "minf", ngx_http_mp4_read_minf_atom },
+    { NULL, NULL }
+};
+
+static ngx_http_mp4_atom_handler_t  ngx_http_mp4_minf_atoms[] = {
+    { "vmhd", ngx_http_mp4_read_vmhd_atom },
+    { "smhd", ngx_http_mp4_read_smhd_atom },
+    { "dinf", ngx_http_mp4_read_dinf_atom },
+    { "stbl", ngx_http_mp4_read_stbl_atom },
+    { NULL, NULL }
+};
+
+static ngx_http_mp4_atom_handler_t  ngx_http_mp4_stbl_atoms[] = {
+    { "stsd", ngx_http_mp4_read_stsd_atom },
+    { "stts", ngx_http_mp4_read_stts_atom },
+    { "stss", ngx_http_mp4_read_stss_atom },
+    { "ctts", ngx_http_mp4_read_ctts_atom },
+    { "stsc", ngx_http_mp4_read_stsc_atom },
+    { "stsz", ngx_http_mp4_read_stsz_atom },
+    { "stco", ngx_http_mp4_read_stco_atom },
+    { "co64", ngx_http_mp4_read_co64_atom },
+    { NULL, NULL }
+};
+
+
+static ngx_int_t
+ngx_http_mp4_handler(ngx_http_request_t *r)
+{
+    u_char                    *last;
+    size_t                     root;
+    ngx_int_t                  rc, start, end;
+    ngx_uint_t                 level, length;
+    ngx_str_t                  path, value;
+    ngx_log_t                 *log;
+    ngx_buf_t                 *b;
+    ngx_chain_t                out;
+    ngx_http_mp4_file_t       *mp4;
+    ngx_open_file_info_t       of;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
+        return NGX_HTTP_NOT_ALLOWED;
+    }
+
+    if (r->uri.data[r->uri.len - 1] == '/') {
+        return NGX_DECLINED;
+    }
+
+    rc = ngx_http_discard_request_body(r);
+
+    if (rc != NGX_OK) {
+        return rc;
+    }
+
+    last = ngx_http_map_uri_to_path(r, &path, &root, 0);
+    if (last == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    log = r->connection->log;
+
+    path.len = last - path.data;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+                   "http mp4 filename: \"%V\"", &path);
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+    of.read_ahead = clcf->read_ahead;
+    of.directio = NGX_MAX_OFF_T_VALUE;
+    of.valid = clcf->open_file_cache_valid;
+    of.min_uses = clcf->open_file_cache_min_uses;
+    of.errors = clcf->open_file_cache_errors;
+    of.events = clcf->open_file_cache_events;
+
+    if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
+        != NGX_OK)
+    {
+        switch (of.err) {
+
+        case 0:
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+
+        case NGX_ENOENT:
+        case NGX_ENOTDIR:
+        case NGX_ENAMETOOLONG:
+
+            level = NGX_LOG_ERR;
+            rc = NGX_HTTP_NOT_FOUND;
+            break;
+
+        case NGX_EACCES:
+#if (NGX_HAVE_OPENAT)
+        case NGX_EMLINK:
+        case NGX_ELOOP:
+#endif
+
+            level = NGX_LOG_ERR;
+            rc = NGX_HTTP_FORBIDDEN;
+            break;
+
+        default:
+
+            level = NGX_LOG_CRIT;
+            rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+            break;
+        }
+
+        if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) {
+            ngx_log_error(level, log, of.err,
+                          "%s \"%s\" failed", of.failed, path.data);
+        }
+
+        return rc;
+    }
+
+    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;
+    }
+
+    r->root_tested = !r->error_page;
+    r->allow_ranges = 1;
+
+    start = -1;
+    length = 0;
+    r->headers_out.content_length_n = of.size;
+    mp4 = NULL;
+    b = NULL;
+
+    if (r->args.len) {
+
+        if (ngx_http_arg(r, (u_char *) "start", 5, &value) == NGX_OK) {
+
+            /*
+             * A Flash player may send start value with a lot of digits
+             * after dot so a custom function is used instead of ngx_atofp().
+             */
+
+            start = ngx_http_mp4_atofp(value.data, value.len, 3);
+        }
+
+        if (ngx_http_arg(r, (u_char *) "end", 3, &value) == NGX_OK) {
+
+            end = ngx_http_mp4_atofp(value.data, value.len, 3);
+
+            if (end > 0) {
+                if (start < 0) {
+                    start = 0;
+                }
+
+                if (end > start) {
+                    length = end - start;
+                }
+            }
+        }
+    }
+
+    if (start >= 0) {
+        r->single_range = 1;
+
+        mp4 = ngx_pcalloc(r->pool, sizeof(ngx_http_mp4_file_t));
+        if (mp4 == NULL) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        mp4->file.fd = of.fd;
+        mp4->file.name = path;
+        mp4->file.log = r->connection->log;
+        mp4->end = of.size;
+        mp4->start = (ngx_uint_t) start;
+        mp4->length = length;
+        mp4->request = r;
+
+        switch (ngx_http_mp4_process(mp4)) {
+
+        case NGX_DECLINED:
+            if (mp4->buffer) {
+                ngx_pfree(r->pool, mp4->buffer);
+            }
+
+            ngx_pfree(r->pool, mp4);
+            mp4 = NULL;
+
+            break;
+
+        case NGX_OK:
+            r->headers_out.content_length_n = mp4->content_length;
+            break;
+
+        default: /* NGX_ERROR */
+            if (mp4->buffer) {
+                ngx_pfree(r->pool, mp4->buffer);
+            }
+
+            ngx_pfree(r->pool, mp4);
+
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+    }
+
+    log->action = "sending mp4 to client";
+
+    if (clcf->directio <= of.size) {
+
+        /*
+         * DIRECTIO is set on transfer only
+         * to allow kernel to cache "moov" atom
+         */
+
+        if (ngx_directio_on(of.fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                          ngx_directio_on_n " \"%s\" failed", path.data);
+        }
+
+        of.is_directio = 1;
+
+        if (mp4) {
+            mp4->file.directio = 1;
+        }
+    }
+
+    r->headers_out.status = NGX_HTTP_OK;
+    r->headers_out.last_modified_time = of.mtime;
+
+    if (ngx_http_set_etag(r) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (ngx_http_set_content_type(r) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (mp4 == NULL) {
+        b = ngx_calloc_buf(r->pool);
+        if (b == NULL) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
+        if (b->file == NULL) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+    }
+
+    rc = ngx_http_send_header(r);
+
+    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+        return rc;
+    }
+
+    if (mp4) {
+        return ngx_http_output_filter(r, mp4->out);
+    }
+
+    b->file_pos = 0;
+    b->file_last = of.size;
+
+    b->in_file = b->file_last ? 1 : 0;
+    b->last_buf = (r == r->main) ? 1 : 0;
+    b->last_in_chain = 1;
+
+    b->file->fd = of.fd;
+    b->file->name = path;
+    b->file->log = log;
+    b->file->directio = of.is_directio;
+
+    out.buf = b;
+    out.next = NULL;
+
+    return ngx_http_output_filter(r, &out);
+}
+
+
+static ngx_int_t
+ngx_http_mp4_atofp(u_char *line, size_t n, size_t point)
+{
+    ngx_int_t   value, cutoff, cutlim;
+    ngx_uint_t  dot;
+
+    /* same as ngx_atofp(), but allows additional digits */
+
+    if (n == 0) {
+        return NGX_ERROR;
+    }
+
+    cutoff = NGX_MAX_INT_T_VALUE / 10;
+    cutlim = NGX_MAX_INT_T_VALUE % 10;
+
+    dot = 0;
+
+    for (value = 0; n--; line++) {
+
+        if (*line == '.') {
+            if (dot) {
+                return NGX_ERROR;
+            }
+
+            dot = 1;
+            continue;
+        }
+
+        if (*line < '0' || *line > '9') {
+            return NGX_ERROR;
+        }
+
+        if (point == 0) {
+            continue;
+        }
+
+        if (value >= cutoff && (value > cutoff || *line - '0' > cutlim)) {
+            return NGX_ERROR;
+        }
+
+        value = value * 10 + (*line - '0');
+        point -= dot;
+    }
+
+    while (point--) {
+        if (value > cutoff) {
+            return NGX_ERROR;
+        }
+
+        value = value * 10;
+    }
+
+    return value;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_process(ngx_http_mp4_file_t *mp4)
+{
+    off_t                  start_offset, end_offset, adjustment;
+    ngx_int_t              rc;
+    ngx_uint_t             i, j;
+    ngx_chain_t          **prev;
+    ngx_http_mp4_trak_t   *trak;
+    ngx_http_mp4_conf_t   *conf;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mp4 start:%ui, length:%ui", mp4->start, mp4->length);
+
+    conf = ngx_http_get_module_loc_conf(mp4->request, ngx_http_mp4_module);
+
+    mp4->buffer_size = conf->buffer_size;
+
+    rc = ngx_http_mp4_read_atom(mp4, ngx_http_mp4_atoms, mp4->end);
+    if (rc != NGX_OK) {
+        return rc;
+    }
+
+    if (mp4->trak.nelts == 0) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "no mp4 trak atoms were found in \"%s\"",
+                      mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    if (mp4->mdat_atom.buf == NULL) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "no mp4 mdat atom was found in \"%s\"",
+                      mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    prev = &mp4->out;
+
+    if (mp4->ftyp_atom.buf) {
+        *prev = &mp4->ftyp_atom;
+        prev = &mp4->ftyp_atom.next;
+    }
+
+    *prev = &mp4->moov_atom;
+    prev = &mp4->moov_atom.next;
+
+    if (mp4->mvhd_atom.buf) {
+        mp4->moov_size += mp4->mvhd_atom_buf.last - mp4->mvhd_atom_buf.pos;
+        *prev = &mp4->mvhd_atom;
+        prev = &mp4->mvhd_atom.next;
+    }
+
+    start_offset = mp4->end;
+    end_offset = 0;
+    trak = mp4->trak.elts;
+
+    for (i = 0; i < mp4->trak.nelts; i++) {
+
+        if (ngx_http_mp4_update_stts_atom(mp4, &trak[i]) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        if (ngx_http_mp4_update_stss_atom(mp4, &trak[i]) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        ngx_http_mp4_update_ctts_atom(mp4, &trak[i]);
+
+        if (ngx_http_mp4_update_stsc_atom(mp4, &trak[i]) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        if (ngx_http_mp4_update_stsz_atom(mp4, &trak[i]) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        if (trak[i].out[NGX_HTTP_MP4_CO64_DATA].buf) {
+            if (ngx_http_mp4_update_co64_atom(mp4, &trak[i]) != NGX_OK) {
+                return NGX_ERROR;
+            }
+
+        } else {
+            if (ngx_http_mp4_update_stco_atom(mp4, &trak[i]) != NGX_OK) {
+                return NGX_ERROR;
+            }
+        }
+
+        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;
+        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_trak_atom(mp4, &trak[i]);
+
+        mp4->moov_size += trak[i].size;
+
+        if (start_offset > trak[i].start_offset) {
+            start_offset = trak[i].start_offset;
+        }
+
+        if (end_offset < trak[i].end_offset) {
+            end_offset = trak[i].end_offset;
+        }
+
+        *prev = &trak[i].out[NGX_HTTP_MP4_TRAK_ATOM];
+        prev = &trak[i].out[NGX_HTTP_MP4_TRAK_ATOM].next;
+
+        for (j = 0; j < NGX_HTTP_MP4_LAST_ATOM + 1; j++) {
+            if (trak[i].out[j].buf) {
+                *prev = &trak[i].out[j];
+                prev = &trak[i].out[j].next;
+            }
+        }
+    }
+
+    if (end_offset < start_offset) {
+        end_offset = start_offset;
+    }
+
+    mp4->moov_size += 8;
+
+    ngx_mp4_set_32value(mp4->moov_atom_header, mp4->moov_size);
+    ngx_mp4_set_atom_name(mp4->moov_atom_header, 'm', 'o', 'o', 'v');
+    mp4->content_length += mp4->moov_size;
+
+    *prev = &mp4->mdat_atom;
+
+    if (start_offset > mp4->mdat_data.buf->file_last) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "start time is out mp4 mdat atom in \"%s\"",
+                      mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    adjustment = mp4->ftyp_size + mp4->moov_size
+                 + ngx_http_mp4_update_mdat_atom(mp4, start_offset, end_offset)
+                 - start_offset;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mp4 adjustment:%O", adjustment);
+
+    for (i = 0; i < mp4->trak.nelts; i++) {
+        if (trak[i].out[NGX_HTTP_MP4_CO64_DATA].buf) {
+            ngx_http_mp4_adjust_co64_atom(mp4, &trak[i], adjustment);
+        } else {
+            ngx_http_mp4_adjust_stco_atom(mp4, &trak[i], (int32_t) adjustment);
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+typedef struct {
+    u_char    size[4];
+    u_char    name[4];
+} ngx_mp4_atom_header_t;
+
+typedef struct {
+    u_char    size[4];
+    u_char    name[4];
+    u_char    size64[8];
+} ngx_mp4_atom_header64_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_atom_handler_t *atom, uint64_t atom_data_size)
+{
+    off_t        end;
+    size_t       atom_header_size;
+    u_char      *atom_header, *atom_name;
+    uint64_t     atom_size;
+    ngx_int_t    rc;
+    ngx_uint_t   n;
+
+    end = mp4->offset + atom_data_size;
+
+    while (mp4->offset < end) {
+
+        if (ngx_http_mp4_read(mp4, sizeof(uint32_t)) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        atom_header = mp4->buffer_pos;
+        atom_size = ngx_mp4_get_32value(atom_header);
+        atom_header_size = sizeof(ngx_mp4_atom_header_t);
+
+        if (atom_size == 0) {
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                           "mp4 atom end");
+            return NGX_OK;
+        }
+
+        if (atom_size < sizeof(ngx_mp4_atom_header_t)) {
+
+            if (atom_size == 1) {
+
+                if (ngx_http_mp4_read(mp4, sizeof(ngx_mp4_atom_header64_t))
+                    != NGX_OK)
+                {
+                    return NGX_ERROR;
+                }
+
+                /* 64-bit atom size */
+                atom_header = mp4->buffer_pos;
+                atom_size = ngx_mp4_get_64value(atom_header + 8);
+                atom_header_size = sizeof(ngx_mp4_atom_header64_t);
+
+                if (atom_size < sizeof(ngx_mp4_atom_header64_t)) {
+                    ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                                  "\"%s\" mp4 atom is too small:%uL",
+                                  mp4->file.name.data, atom_size);
+                    return NGX_ERROR;
+                }
+
+            } else {
+                ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                              "\"%s\" mp4 atom is too small:%uL",
+                              mp4->file.name.data, atom_size);
+                return NGX_ERROR;
+            }
+        }
+
+        if (ngx_http_mp4_read(mp4, sizeof(ngx_mp4_atom_header_t)) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        atom_header = mp4->buffer_pos;
+        atom_name = atom_header + sizeof(uint32_t);
+
+        ngx_log_debug4(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                       "mp4 atom: %*s @%O:%uL",
+                       (size_t) 4, atom_name, mp4->offset, atom_size);
+
+        if (atom_size > (uint64_t) (NGX_MAX_OFF_T_VALUE - mp4->offset)
+            || mp4->offset + (off_t) atom_size > end)
+        {
+            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                          "\"%s\" mp4 atom too large:%uL",
+                          mp4->file.name.data, atom_size);
+            return NGX_ERROR;
+        }
+
+        for (n = 0; atom[n].name; n++) {
+
+            if (ngx_strncmp(atom_name, atom[n].name, 4) == 0) {
+
+                ngx_mp4_atom_next(mp4, atom_header_size);
+
+                rc = atom[n].handler(mp4, atom_size - atom_header_size);
+                if (rc != NGX_OK) {
+                    return rc;
+                }
+
+                goto next;
+            }
+        }
+
+        ngx_mp4_atom_next(mp4, atom_size);
+
+    next:
+        continue;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read(ngx_http_mp4_file_t *mp4, size_t size)
+{
+    ssize_t  n;
+
+    if (mp4->buffer_pos + size <= mp4->buffer_end) {
+        return NGX_OK;
+    }
+
+    if (mp4->offset + (off_t) mp4->buffer_size > mp4->end) {
+        mp4->buffer_size = (size_t) (mp4->end - mp4->offset);
+    }
+
+    if (mp4->buffer_size < size) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "\"%s\" mp4 file truncated", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    if (mp4->buffer == NULL) {
+        mp4->buffer = ngx_palloc(mp4->request->pool, mp4->buffer_size);
+        if (mp4->buffer == NULL) {
+            return NGX_ERROR;
+        }
+
+        mp4->buffer_start = mp4->buffer;
+    }
+
+    n = ngx_read_file(&mp4->file, mp4->buffer_start, mp4->buffer_size,
+                      mp4->offset);
+
+    if (n == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if ((size_t) n != mp4->buffer_size) {
+        ngx_log_error(NGX_LOG_CRIT, mp4->file.log, 0,
+                      ngx_read_file_n " read only %z of %z from \"%s\"",
+                      n, mp4->buffer_size, mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    mp4->buffer_pos = mp4->buffer_start;
+    mp4->buffer_end = mp4->buffer_start + mp4->buffer_size;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_ftyp_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    u_char     *ftyp_atom;
+    size_t      atom_size;
+    ngx_buf_t  *atom;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 ftyp atom");
+
+    if (atom_data_size > 1024
+        || ngx_mp4_atom_data(mp4) + (size_t) atom_data_size > mp4->buffer_end)
+    {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "\"%s\" mp4 ftyp atom is too large:%uL",
+                      mp4->file.name.data, atom_data_size);
+        return NGX_ERROR;
+    }
+
+    atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
+
+    ftyp_atom = ngx_palloc(mp4->request->pool, atom_size);
+    if (ftyp_atom == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_mp4_set_32value(ftyp_atom, atom_size);
+    ngx_mp4_set_atom_name(ftyp_atom, 'f', 't', 'y', 'p');
+
+    /*
+     * only moov atom content is guaranteed to be in mp4->buffer
+     * during sending response, so ftyp atom content should be copied
+     */
+    ngx_memcpy(ftyp_atom + sizeof(ngx_mp4_atom_header_t),
+               ngx_mp4_atom_data(mp4), (size_t) atom_data_size);
+
+    atom = &mp4->ftyp_atom_buf;
+    atom->temporary = 1;
+    atom->pos = ftyp_atom;
+    atom->last = ftyp_atom + atom_size;
+
+    mp4->ftyp_atom.buf = atom;
+    mp4->ftyp_size = atom_size;
+    mp4->content_length = atom_size;
+
+    ngx_mp4_atom_next(mp4, atom_data_size);
+
+    return NGX_OK;
+}
+
+
+/*
+ * Small excess buffer to process atoms after moov atom, mp4->buffer_start
+ * will be set to this buffer part after moov atom processing.
+ */
+#define NGX_HTTP_MP4_MOOV_BUFFER_EXCESS  (4 * 1024)
+
+static ngx_int_t
+ngx_http_mp4_read_moov_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    ngx_int_t             rc;
+    ngx_uint_t            no_mdat;
+    ngx_buf_t            *atom;
+    ngx_http_mp4_conf_t  *conf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 moov atom");
+
+    no_mdat = (mp4->mdat_atom.buf == NULL);
+
+    if (no_mdat && mp4->start == 0 && mp4->length == 0) {
+        /*
+         * send original file if moov atom resides before
+         * mdat atom and client requests integral file
+         */
+        return NGX_DECLINED;
+    }
+
+    conf = ngx_http_get_module_loc_conf(mp4->request, ngx_http_mp4_module);
+
+    if (atom_data_size > mp4->buffer_size) {
+
+        if (atom_data_size > conf->max_buffer_size) {
+            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                          "\"%s\" mp4 moov atom is too large:%uL, "
+                          "you may want to increase mp4_max_buffer_size",
+                          mp4->file.name.data, atom_data_size);
+            return NGX_ERROR;
+        }
+
+        ngx_pfree(mp4->request->pool, mp4->buffer);
+        mp4->buffer = NULL;
+        mp4->buffer_pos = NULL;
+        mp4->buffer_end = NULL;
+
+        mp4->buffer_size = (size_t) atom_data_size
+                         + NGX_HTTP_MP4_MOOV_BUFFER_EXCESS * no_mdat;
+    }
+
+    if (ngx_http_mp4_read(mp4, (size_t) atom_data_size) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    mp4->trak.elts = &mp4->traks;
+    mp4->trak.size = sizeof(ngx_http_mp4_trak_t);
+    mp4->trak.nalloc = 2;
+    mp4->trak.pool = mp4->request->pool;
+
+    atom = &mp4->moov_atom_buf;
+    atom->temporary = 1;
+    atom->pos = mp4->moov_atom_header;
+    atom->last = mp4->moov_atom_header + 8;
+
+    mp4->moov_atom.buf = &mp4->moov_atom_buf;
+
+    rc = ngx_http_mp4_read_atom(mp4, ngx_http_mp4_moov_atoms, atom_data_size);
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 moov atom done");
+
+    if (no_mdat) {
+        mp4->buffer_start = mp4->buffer_pos;
+        mp4->buffer_size = NGX_HTTP_MP4_MOOV_BUFFER_EXCESS;
+
+        if (mp4->buffer_start + mp4->buffer_size > mp4->buffer_end) {
+            mp4->buffer = NULL;
+            mp4->buffer_pos = NULL;
+            mp4->buffer_end = NULL;
+        }
+
+    } else {
+        /* skip atoms after moov atom */
+        mp4->offset = mp4->end;
+    }
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_mdat_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    ngx_buf_t  *data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 mdat atom");
+
+    data = &mp4->mdat_data_buf;
+    data->file = &mp4->file;
+    data->in_file = 1;
+    data->last_buf = (mp4->request == mp4->request->main) ? 1 : 0;
+    data->last_in_chain = 1;
+    data->file_last = mp4->offset + atom_data_size;
+
+    mp4->mdat_atom.buf = &mp4->mdat_atom_buf;
+    mp4->mdat_atom.next = &mp4->mdat_data;
+    mp4->mdat_data.buf = data;
+
+    if (mp4->trak.nelts) {
+        /* skip atoms after mdat atom */
+        mp4->offset = mp4->end;
+
+    } else {
+        ngx_mp4_atom_next(mp4, atom_data_size);
+    }
+
+    return NGX_OK;
+}
+
+
+static size_t
+ngx_http_mp4_update_mdat_atom(ngx_http_mp4_file_t *mp4, off_t start_offset,
+    off_t end_offset)
+{
+    off_t       atom_data_size;
+    u_char     *atom_header;
+    uint32_t    atom_header_size;
+    uint64_t    atom_size;
+    ngx_buf_t  *atom;
+
+    atom_data_size = end_offset - start_offset;
+    mp4->mdat_data.buf->file_pos = start_offset;
+    mp4->mdat_data.buf->file_last = end_offset;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mdat new offset @%O:%O", start_offset, atom_data_size);
+
+    atom_header = mp4->mdat_atom_header;
+
+    if ((uint64_t) atom_data_size
+        > (uint64_t) 0xffffffff - sizeof(ngx_mp4_atom_header_t))
+    {
+        atom_size = 1;
+        atom_header_size = sizeof(ngx_mp4_atom_header64_t);
+        ngx_mp4_set_64value(atom_header + sizeof(ngx_mp4_atom_header_t),
+                            sizeof(ngx_mp4_atom_header64_t) + atom_data_size);
+    } else {
+        atom_size = sizeof(ngx_mp4_atom_header_t) + atom_data_size;
+        atom_header_size = sizeof(ngx_mp4_atom_header_t);
+    }
+
+    mp4->content_length += atom_header_size + atom_data_size;
+
+    ngx_mp4_set_32value(atom_header, atom_size);
+    ngx_mp4_set_atom_name(atom_header, 'm', 'd', 'a', 't');
+
+    atom = &mp4->mdat_atom_buf;
+    atom->temporary = 1;
+    atom->pos = atom_header;
+    atom->last = atom_header + atom_header_size;
+
+    return atom_header_size;
+}
+
+
+typedef struct {
+    u_char    size[4];
+    u_char    name[4];
+    u_char    version[1];
+    u_char    flags[3];
+    u_char    creation_time[4];
+    u_char    modification_time[4];
+    u_char    timescale[4];
+    u_char    duration[4];
+    u_char    rate[4];
+    u_char    volume[2];
+    u_char    reserved[10];
+    u_char    matrix[36];
+    u_char    preview_time[4];
+    u_char    preview_duration[4];
+    u_char    poster_time[4];
+    u_char    selection_time[4];
+    u_char    selection_duration[4];
+    u_char    current_time[4];
+    u_char    next_track_id[4];
+} ngx_mp4_mvhd_atom_t;
+
+typedef struct {
+    u_char    size[4];
+    u_char    name[4];
+    u_char    version[1];
+    u_char    flags[3];
+    u_char    creation_time[8];
+    u_char    modification_time[8];
+    u_char    timescale[4];
+    u_char    duration[8];
+    u_char    rate[4];
+    u_char    volume[2];
+    u_char    reserved[10];
+    u_char    matrix[36];
+    u_char    preview_time[4];
+    u_char    preview_duration[4];
+    u_char    poster_time[4];
+    u_char    selection_time[4];
+    u_char    selection_duration[4];
+    u_char    current_time[4];
+    u_char    next_track_id[4];
+} ngx_mp4_mvhd64_atom_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_mvhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    u_char                 *atom_header;
+    size_t                  atom_size;
+    uint32_t                timescale;
+    uint64_t                duration, start_time, length_time;
+    ngx_buf_t              *atom;
+    ngx_mp4_mvhd_atom_t    *mvhd_atom;
+    ngx_mp4_mvhd64_atom_t  *mvhd64_atom;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 mvhd atom");
+
+    atom_header = ngx_mp4_atom_header(mp4);
+    mvhd_atom = (ngx_mp4_mvhd_atom_t *) atom_header;
+    mvhd64_atom = (ngx_mp4_mvhd64_atom_t *) atom_header;
+    ngx_mp4_set_atom_name(atom_header, 'm', 'v', 'h', 'd');
+
+    if (ngx_mp4_atom_data_size(ngx_mp4_mvhd_atom_t) > atom_data_size) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "\"%s\" mp4 mvhd atom too small", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    if (mvhd_atom->version[0] == 0) {
+        /* version 0: 32-bit duration */
+        timescale = ngx_mp4_get_32value(mvhd_atom->timescale);
+        duration = ngx_mp4_get_32value(mvhd_atom->duration);
+
+    } else {
+        /* version 1: 64-bit duration */
+
+        if (ngx_mp4_atom_data_size(ngx_mp4_mvhd64_atom_t) > atom_data_size) {
+            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                          "\"%s\" mp4 mvhd atom too small",
+                          mp4->file.name.data);
+            return NGX_ERROR;
+        }
+
+        timescale = ngx_mp4_get_32value(mvhd64_atom->timescale);
+        duration = ngx_mp4_get_64value(mvhd64_atom->duration);
+    }
+
+    mp4->timescale = timescale;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mvhd timescale:%uD, duration:%uL, time:%.3fs",
+                   timescale, duration, (double) duration / timescale);
+
+    start_time = (uint64_t) mp4->start * timescale / 1000;
+
+    if (duration < start_time) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "\"%s\" mp4 start time exceeds file duration",
+                      mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    duration -= start_time;
+
+    if (mp4->length) {
+        length_time = (uint64_t) mp4->length * timescale / 1000;
+
+        if (duration > length_time) {
+            duration = length_time;
+        }
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mvhd new duration:%uL, time:%.3fs",
+                   duration, (double) duration / timescale);
+
+    atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
+    ngx_mp4_set_32value(mvhd_atom->size, atom_size);
+
+    if (mvhd_atom->version[0] == 0) {
+        ngx_mp4_set_32value(mvhd_atom->duration, duration);
+
+    } else {
+        ngx_mp4_set_64value(mvhd64_atom->duration, duration);
+    }
+
+    atom = &mp4->mvhd_atom_buf;
+    atom->temporary = 1;
+    atom->pos = atom_header;
+    atom->last = atom_header + atom_size;
+
+    mp4->mvhd_atom.buf = atom;
+
+    ngx_mp4_atom_next(mp4, atom_data_size);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_trak_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    u_char               *atom_header, *atom_end;
+    off_t                 atom_file_end;
+    ngx_int_t             rc;
+    ngx_buf_t            *atom;
+    ngx_http_mp4_trak_t  *trak;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 trak atom");
+
+    trak = ngx_array_push(&mp4->trak);
+    if (trak == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memzero(trak, sizeof(ngx_http_mp4_trak_t));
+
+    atom_header = ngx_mp4_atom_header(mp4);
+    ngx_mp4_set_atom_name(atom_header, 't', 'r', 'a', 'k');
+
+    atom = &trak->trak_atom_buf;
+    atom->temporary = 1;
+    atom->pos = atom_header;
+    atom->last = atom_header + sizeof(ngx_mp4_atom_header_t);
+
+    trak->out[NGX_HTTP_MP4_TRAK_ATOM].buf = atom;
+
+    atom_end = mp4->buffer_pos + (size_t) atom_data_size;
+    atom_file_end = mp4->offset + atom_data_size;
+
+    rc = ngx_http_mp4_read_atom(mp4, ngx_http_mp4_trak_atoms, atom_data_size);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mp4 trak atom: %i", rc);
+
+    if (rc == NGX_DECLINED) {
+        /* skip this trak */
+        ngx_memzero(trak, sizeof(ngx_http_mp4_trak_t));
+        mp4->trak.nelts--;
+        mp4->buffer_pos = atom_end;
+        mp4->offset = atom_file_end;
+        return NGX_OK;
+    }
+
+    return rc;
+}
+
+
+static void
+ngx_http_mp4_update_trak_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak)
+{
+    ngx_buf_t  *atom;
+
+    trak->size += sizeof(ngx_mp4_atom_header_t);
+    atom = &trak->trak_atom_buf;
+    ngx_mp4_set_32value(atom->pos, trak->size);
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_cmov_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                  "\"%s\" mp4 compressed moov atom (cmov) is not supported",
+                  mp4->file.name.data);
+
+    return NGX_ERROR;
+}
+
+
+typedef struct {
+    u_char    size[4];
+    u_char    name[4];
+    u_char    version[1];
+    u_char    flags[3];
+    u_char    creation_time[4];
+    u_char    modification_time[4];
+    u_char    track_id[4];
+    u_char    reserved1[4];
+    u_char    duration[4];
+    u_char    reserved2[8];
+    u_char    layer[2];
+    u_char    group[2];
+    u_char    volume[2];
+    u_char    reserved3[2];
+    u_char    matrix[36];
+    u_char    width[4];
+    u_char    height[4];
+} ngx_mp4_tkhd_atom_t;
+
+typedef struct {
+    u_char    size[4];
+    u_char    name[4];
+    u_char    version[1];
+    u_char    flags[3];
+    u_char    creation_time[8];
+    u_char    modification_time[8];
+    u_char    track_id[4];
+    u_char    reserved1[4];
+    u_char    duration[8];
+    u_char    reserved2[8];
+    u_char    layer[2];
+    u_char    group[2];
+    u_char    volume[2];
+    u_char    reserved3[2];
+    u_char    matrix[36];
+    u_char    width[4];
+    u_char    height[4];
+} ngx_mp4_tkhd64_atom_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_tkhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    u_char                 *atom_header;
+    size_t                  atom_size;
+    uint64_t                duration, start_time, length_time;
+    ngx_buf_t              *atom;
+    ngx_http_mp4_trak_t    *trak;
+    ngx_mp4_tkhd_atom_t    *tkhd_atom;
+    ngx_mp4_tkhd64_atom_t  *tkhd64_atom;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 tkhd atom");
+
+    atom_header = ngx_mp4_atom_header(mp4);
+    tkhd_atom = (ngx_mp4_tkhd_atom_t *) atom_header;
+    tkhd64_atom = (ngx_mp4_tkhd64_atom_t *) atom_header;
+    ngx_mp4_set_atom_name(tkhd_atom, 't', 'k', 'h', 'd');
+
+    if (ngx_mp4_atom_data_size(ngx_mp4_tkhd_atom_t) > atom_data_size) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "\"%s\" mp4 tkhd atom too small", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    if (tkhd_atom->version[0] == 0) {
+        /* version 0: 32-bit duration */
+        duration = ngx_mp4_get_32value(tkhd_atom->duration);
+
+    } else {
+        /* version 1: 64-bit duration */
+
+        if (ngx_mp4_atom_data_size(ngx_mp4_tkhd64_atom_t) > atom_data_size) {
+            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                          "\"%s\" mp4 tkhd atom too small",
+                          mp4->file.name.data);
+            return NGX_ERROR;
+        }
+
+        duration = ngx_mp4_get_64value(tkhd64_atom->duration);
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "tkhd duration:%uL, time:%.3fs",
+                   duration, (double) duration / mp4->timescale);
+
+    start_time = (uint64_t) mp4->start * mp4->timescale / 1000;
+
+    if (duration <= start_time) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                       "tkhd duration is less than start time");
+        return NGX_DECLINED;
+    }
+
+    duration -= start_time;
+
+    if (mp4->length) {
+        length_time = (uint64_t) mp4->length * mp4->timescale / 1000;
+
+        if (duration > length_time) {
+            duration = length_time;
+        }
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "tkhd new duration:%uL, time:%.3fs",
+                   duration, (double) duration / mp4->timescale);
+
+    atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
+
+    trak = ngx_mp4_last_trak(mp4);
+    trak->tkhd_size = atom_size;
+
+    ngx_mp4_set_32value(tkhd_atom->size, atom_size);
+
+    if (tkhd_atom->version[0] == 0) {
+        ngx_mp4_set_32value(tkhd_atom->duration, duration);
+
+    } else {
+        ngx_mp4_set_64value(tkhd64_atom->duration, duration);
+    }
+
+    atom = &trak->tkhd_atom_buf;
+    atom->temporary = 1;
+    atom->pos = atom_header;
+    atom->last = atom_header + atom_size;
+
+    trak->out[NGX_HTTP_MP4_TKHD_ATOM].buf = atom;
+
+    ngx_mp4_atom_next(mp4, atom_data_size);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_mdia_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    u_char               *atom_header;
+    ngx_buf_t            *atom;
+    ngx_http_mp4_trak_t  *trak;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "process mdia atom");
+
+    atom_header = ngx_mp4_atom_header(mp4);
+    ngx_mp4_set_atom_name(atom_header, 'm', 'd', 'i', 'a');
+
+    trak = ngx_mp4_last_trak(mp4);
+
+    atom = &trak->mdia_atom_buf;
+    atom->temporary = 1;
+    atom->pos = atom_header;
+    atom->last = atom_header + sizeof(ngx_mp4_atom_header_t);
+
+    trak->out[NGX_HTTP_MP4_MDIA_ATOM].buf = atom;
+
+    return ngx_http_mp4_read_atom(mp4, ngx_http_mp4_mdia_atoms, atom_data_size);
+}
+
+
+static void
+ngx_http_mp4_update_mdia_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak)
+{
+    ngx_buf_t  *atom;
+
+    trak->size += sizeof(ngx_mp4_atom_header_t);
+    atom = &trak->mdia_atom_buf;
+    ngx_mp4_set_32value(atom->pos, trak->size);
+}
+
+
+typedef struct {
+    u_char    size[4];
+    u_char    name[4];
+    u_char    version[1];
+    u_char    flags[3];
+    u_char    creation_time[4];
+    u_char    modification_time[4];
+    u_char    timescale[4];
+    u_char    duration[4];
+    u_char    language[2];
+    u_char    quality[2];
+} ngx_mp4_mdhd_atom_t;
+
+typedef struct {
+    u_char    size[4];
+    u_char    name[4];
+    u_char    version[1];
+    u_char    flags[3];
+    u_char    creation_time[8];
+    u_char    modification_time[8];
+    u_char    timescale[4];
+    u_char    duration[8];
+    u_char    language[2];
+    u_char    quality[2];
+} ngx_mp4_mdhd64_atom_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_mdhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    u_char                 *atom_header;
+    size_t                  atom_size;
+    uint32_t                timescale;
+    uint64_t                duration, start_time, length_time;
+    ngx_buf_t              *atom;
+    ngx_http_mp4_trak_t    *trak;
+    ngx_mp4_mdhd_atom_t    *mdhd_atom;
+    ngx_mp4_mdhd64_atom_t  *mdhd64_atom;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 mdhd atom");
+
+    atom_header = ngx_mp4_atom_header(mp4);
+    mdhd_atom = (ngx_mp4_mdhd_atom_t *) atom_header;
+    mdhd64_atom = (ngx_mp4_mdhd64_atom_t *) atom_header;
+    ngx_mp4_set_atom_name(mdhd_atom, 'm', 'd', 'h', 'd');
+
+    if (ngx_mp4_atom_data_size(ngx_mp4_mdhd_atom_t) > atom_data_size) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "\"%s\" mp4 mdhd atom too small", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    if (mdhd_atom->version[0] == 0) {
+        /* version 0: everything is 32-bit */
+        timescale = ngx_mp4_get_32value(mdhd_atom->timescale);
+        duration = ngx_mp4_get_32value(mdhd_atom->duration);
+
+    } else {
+        /* version 1: 64-bit duration and 32-bit timescale */
+
+        if (ngx_mp4_atom_data_size(ngx_mp4_mdhd64_atom_t) > atom_data_size) {
+            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                          "\"%s\" mp4 mdhd atom too small",
+                          mp4->file.name.data);
+            return NGX_ERROR;
+        }
+
+        timescale = ngx_mp4_get_32value(mdhd64_atom->timescale);
+        duration = ngx_mp4_get_64value(mdhd64_atom->duration);
+    }
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mdhd timescale:%uD, duration:%uL, time:%.3fs",
+                   timescale, duration, (double) duration / timescale);
+
+    start_time = (uint64_t) mp4->start * timescale / 1000;
+
+    if (duration <= start_time) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                       "mdhd duration is less than start time");
+        return NGX_DECLINED;
+    }
+
+    duration -= start_time;
+
+    if (mp4->length) {
+        length_time = (uint64_t) mp4->length * timescale / 1000;
+
+        if (duration > length_time) {
+            duration = length_time;
+        }
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mdhd new duration:%uL, time:%.3fs",
+                   duration, (double) duration / timescale);
+
+    atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
+
+    trak = ngx_mp4_last_trak(mp4);
+    trak->mdhd_size = atom_size;
+    trak->timescale = timescale;
+
+    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;
+    atom->last = atom_header + atom_size;
+
+    trak->out[NGX_HTTP_MP4_MDHD_ATOM].buf = atom;
+
+    ngx_mp4_atom_next(mp4, atom_data_size);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_hdlr_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    u_char              *atom_header;
+    size_t               atom_size;
+    ngx_buf_t            *atom;
+    ngx_http_mp4_trak_t  *trak;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 hdlr atom");
+
+    atom_header = ngx_mp4_atom_header(mp4);
+    atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
+    ngx_mp4_set_32value(atom_header, atom_size);
+    ngx_mp4_set_atom_name(atom_header, 'h', 'd', 'l', 'r');
+
+    trak = ngx_mp4_last_trak(mp4);
+
+    atom = &trak->hdlr_atom_buf;
+    atom->temporary = 1;
+    atom->pos = atom_header;
+    atom->last = atom_header + atom_size;
+
+    trak->hdlr_size = atom_size;
+    trak->out[NGX_HTTP_MP4_HDLR_ATOM].buf = atom;
+
+    ngx_mp4_atom_next(mp4, atom_data_size);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_minf_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    u_char               *atom_header;
+    ngx_buf_t            *atom;
+    ngx_http_mp4_trak_t  *trak;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "process minf atom");
+
+    atom_header = ngx_mp4_atom_header(mp4);
+    ngx_mp4_set_atom_name(atom_header, 'm', 'i', 'n', 'f');
+
+    trak = ngx_mp4_last_trak(mp4);
+
+    atom = &trak->minf_atom_buf;
+    atom->temporary = 1;
+    atom->pos = atom_header;
+    atom->last = atom_header + sizeof(ngx_mp4_atom_header_t);
+
+    trak->out[NGX_HTTP_MP4_MINF_ATOM].buf = atom;
+
+    return ngx_http_mp4_read_atom(mp4, ngx_http_mp4_minf_atoms, atom_data_size);
+}
+
+
+static void
+ngx_http_mp4_update_minf_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak)
+{
+    ngx_buf_t  *atom;
+
+    trak->size += sizeof(ngx_mp4_atom_header_t)
+               + trak->vmhd_size
+               + trak->smhd_size
+               + trak->dinf_size;
+    atom = &trak->minf_atom_buf;
+    ngx_mp4_set_32value(atom->pos, trak->size);
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_vmhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    u_char              *atom_header;
+    size_t               atom_size;
+    ngx_buf_t            *atom;
+    ngx_http_mp4_trak_t  *trak;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 vmhd atom");
+
+    atom_header = ngx_mp4_atom_header(mp4);
+    atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
+    ngx_mp4_set_32value(atom_header, atom_size);
+    ngx_mp4_set_atom_name(atom_header, 'v', 'm', 'h', 'd');
+
+    trak = ngx_mp4_last_trak(mp4);
+
+    atom = &trak->vmhd_atom_buf;
+    atom->temporary = 1;
+    atom->pos = atom_header;
+    atom->last = atom_header + atom_size;
+
+    trak->vmhd_size += atom_size;
+    trak->out[NGX_HTTP_MP4_VMHD_ATOM].buf = atom;
+
+    ngx_mp4_atom_next(mp4, atom_data_size);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_smhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    u_char              *atom_header;
+    size_t               atom_size;
+    ngx_buf_t            *atom;
+    ngx_http_mp4_trak_t  *trak;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 smhd atom");
+
+    atom_header = ngx_mp4_atom_header(mp4);
+    atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
+    ngx_mp4_set_32value(atom_header, atom_size);
+    ngx_mp4_set_atom_name(atom_header, 's', 'm', 'h', 'd');
+
+    trak = ngx_mp4_last_trak(mp4);
+
+    atom = &trak->smhd_atom_buf;
+    atom->temporary = 1;
+    atom->pos = atom_header;
+    atom->last = atom_header + atom_size;
+
+    trak->smhd_size += atom_size;
+    trak->out[NGX_HTTP_MP4_SMHD_ATOM].buf = atom;
+
+    ngx_mp4_atom_next(mp4, atom_data_size);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_dinf_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    u_char              *atom_header;
+    size_t               atom_size;
+    ngx_buf_t            *atom;
+    ngx_http_mp4_trak_t  *trak;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 dinf atom");
+
+    atom_header = ngx_mp4_atom_header(mp4);
+    atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
+    ngx_mp4_set_32value(atom_header, atom_size);
+    ngx_mp4_set_atom_name(atom_header, 'd', 'i', 'n', 'f');
+
+    trak = ngx_mp4_last_trak(mp4);
+
+    atom = &trak->dinf_atom_buf;
+    atom->temporary = 1;
+    atom->pos = atom_header;
+    atom->last = atom_header + atom_size;
+
+    trak->dinf_size += atom_size;
+    trak->out[NGX_HTTP_MP4_DINF_ATOM].buf = atom;
+
+    ngx_mp4_atom_next(mp4, atom_data_size);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_stbl_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    u_char               *atom_header;
+    ngx_buf_t            *atom;
+    ngx_http_mp4_trak_t  *trak;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "process stbl atom");
+
+    atom_header = ngx_mp4_atom_header(mp4);
+    ngx_mp4_set_atom_name(atom_header, 's', 't', 'b', 'l');
+
+    trak = ngx_mp4_last_trak(mp4);
+
+    atom = &trak->stbl_atom_buf;
+    atom->temporary = 1;
+    atom->pos = atom_header;
+    atom->last = atom_header + sizeof(ngx_mp4_atom_header_t);
+
+    trak->out[NGX_HTTP_MP4_STBL_ATOM].buf = atom;
+
+    return ngx_http_mp4_read_atom(mp4, ngx_http_mp4_stbl_atoms, atom_data_size);
+}
+
+
+static void
+ngx_http_mp4_update_stbl_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak)
+{
+    ngx_buf_t  *atom;
+
+    trak->size += sizeof(ngx_mp4_atom_header_t);
+    atom = &trak->stbl_atom_buf;
+    ngx_mp4_set_32value(atom->pos, trak->size);
+}
+
+
+typedef struct {
+    u_char    size[4];
+    u_char    name[4];
+    u_char    version[1];
+    u_char    flags[3];
+    u_char    entries[4];
+
+    u_char    media_size[4];
+    u_char    media_name[4];
+} ngx_mp4_stsd_atom_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_stsd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    u_char               *atom_header, *atom_table;
+    size_t                atom_size;
+    ngx_buf_t            *atom;
+    ngx_mp4_stsd_atom_t  *stsd_atom;
+    ngx_http_mp4_trak_t  *trak;
+
+    /* sample description atom */
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stsd atom");
+
+    atom_header = ngx_mp4_atom_header(mp4);
+    stsd_atom = (ngx_mp4_stsd_atom_t *) atom_header;
+    atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
+    atom_table = atom_header + atom_size;
+    ngx_mp4_set_32value(stsd_atom->size, atom_size);
+    ngx_mp4_set_atom_name(stsd_atom, 's', 't', 's', 'd');
+
+    if (ngx_mp4_atom_data_size(ngx_mp4_stsd_atom_t) > atom_data_size) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "\"%s\" mp4 stsd atom too small", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "stsd entries:%uD, media:%*s",
+                   ngx_mp4_get_32value(stsd_atom->entries),
+                   (size_t) 4, stsd_atom->media_name);
+
+    trak = ngx_mp4_last_trak(mp4);
+
+    atom = &trak->stsd_atom_buf;
+    atom->temporary = 1;
+    atom->pos = atom_header;
+    atom->last = atom_table;
+
+    trak->out[NGX_HTTP_MP4_STSD_ATOM].buf = atom;
+    trak->size += atom_size;
+
+    ngx_mp4_atom_next(mp4, atom_data_size);
+
+    return NGX_OK;
+}
+
+
+typedef struct {
+    u_char    size[4];
+    u_char    name[4];
+    u_char    version[1];
+    u_char    flags[3];
+    u_char    entries[4];
+} ngx_mp4_stts_atom_t;
+
+typedef struct {
+    u_char    count[4];
+    u_char    duration[4];
+} ngx_mp4_stts_entry_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_stts_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    u_char               *atom_header, *atom_table, *atom_end;
+    uint32_t              entries;
+    ngx_buf_t            *atom, *data;
+    ngx_mp4_stts_atom_t  *stts_atom;
+    ngx_http_mp4_trak_t  *trak;
+
+    /* time-to-sample atom */
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stts atom");
+
+    atom_header = ngx_mp4_atom_header(mp4);
+    stts_atom = (ngx_mp4_stts_atom_t *) atom_header;
+    ngx_mp4_set_atom_name(stts_atom, 's', 't', 't', 's');
+
+    if (ngx_mp4_atom_data_size(ngx_mp4_stts_atom_t) > atom_data_size) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "\"%s\" mp4 stts atom too small", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    entries = ngx_mp4_get_32value(stts_atom->entries);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mp4 time-to-sample entries:%uD", entries);
+
+    if (ngx_mp4_atom_data_size(ngx_mp4_stts_atom_t)
+        + entries * sizeof(ngx_mp4_stts_entry_t) > atom_data_size)
+    {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "\"%s\" mp4 stts atom too small", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    atom_table = atom_header + sizeof(ngx_mp4_stts_atom_t);
+    atom_end = atom_table + entries * sizeof(ngx_mp4_stts_entry_t);
+
+    trak = ngx_mp4_last_trak(mp4);
+    trak->time_to_sample_entries = entries;
+
+    atom = &trak->stts_atom_buf;
+    atom->temporary = 1;
+    atom->pos = atom_header;
+    atom->last = atom_table;
+
+    data = &trak->stts_data_buf;
+    data->temporary = 1;
+    data->pos = atom_table;
+    data->last = atom_end;
+
+    trak->out[NGX_HTTP_MP4_STTS_ATOM].buf = atom;
+    trak->out[NGX_HTTP_MP4_STTS_DATA].buf = data;
+
+    ngx_mp4_atom_next(mp4, atom_data_size);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_update_stts_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak)
+{
+    size_t                atom_size;
+    ngx_buf_t            *atom, *data;
+    ngx_mp4_stts_atom_t  *stts_atom;
+
+    /*
+     * mdia.minf.stbl.stts updating requires trak->timescale
+     * from mdia.mdhd atom which may reside after mdia.minf
+     */
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mp4 stts atom update");
+
+    data = trak->out[NGX_HTTP_MP4_STTS_DATA].buf;
+
+    if (data == NULL) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "no mp4 stts atoms were found in \"%s\"",
+                      mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    if (ngx_http_mp4_crop_stts_data(mp4, trak, 1) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_http_mp4_crop_stts_data(mp4, trak, 0) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "time-to-sample entries:%uD", trak->time_to_sample_entries);
+
+    atom_size = sizeof(ngx_mp4_stts_atom_t) + (data->last - data->pos);
+    trak->size += atom_size;
+
+    atom = trak->out[NGX_HTTP_MP4_STTS_ATOM].buf;
+    stts_atom = (ngx_mp4_stts_atom_t *) atom->pos;
+    ngx_mp4_set_32value(stts_atom->size, atom_size);
+    ngx_mp4_set_32value(stts_atom->entries, trak->time_to_sample_entries);
+
+    return NGX_OK;
+}
+
+
+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;
+    uint64_t               start_time;
+    ngx_buf_t             *data;
+    ngx_uint_t             start_sample, entries, start_sec;
+    ngx_mp4_stts_entry_t  *entry, *end;
+
+    if (start) {
+        start_sec = mp4->start;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                       "mp4 stts crop start_time:%ui", start_sec);
+
+    } else if (mp4->length) {
+        start_sec = mp4->length;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                       "mp4 stts crop end_time:%ui", start_sec);
+
+    } else {
+        return NGX_OK;
+    }
+
+    data = trak->out[NGX_HTTP_MP4_STTS_DATA].buf;
+
+    start_time = (uint64_t) start_sec * trak->timescale / 1000;
+
+    entries = trak->time_to_sample_entries;
+    start_sample = 0;
+    entry = (ngx_mp4_stts_entry_t *) data->pos;
+    end = (ngx_mp4_stts_entry_t *) data->last;
+
+    while (entry < end) {
+        count = ngx_mp4_get_32value(entry->count);
+        duration = ngx_mp4_get_32value(entry->duration);
+
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                       "time:%uL, count:%uD, duration:%uD",
+                       start_time, count, duration);
+
+        if (start_time < (uint64_t) count * duration) {
+            start_sample += (ngx_uint_t) (start_time / duration);
+            rest = (uint32_t) (start_time / duration);
+            goto found;
+        }
+
+        start_sample += count;
+        start_time -= count * duration;
+        entries--;
+        entry++;
+    }
+
+    if (start) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "start time is out mp4 stts samples in \"%s\"",
+                      mp4->file.name.data);
+
+        return NGX_ERROR;
+
+    } else {
+        trak->end_sample = trak->start_sample + start_sample;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                       "end_sample:%ui", trak->end_sample);
+
+        return NGX_OK;
+    }
+
+found:
+
+    if (start) {
+        ngx_mp4_set_32value(entry->count, count - rest);
+        data->pos = (u_char *) entry;
+        trak->time_to_sample_entries = entries;
+        trak->start_sample = start_sample;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                       "start_sample:%ui, new count:%uD",
+                       trak->start_sample, count - rest);
+
+    } else {
+        ngx_mp4_set_32value(entry->count, rest);
+        data->last = (u_char *) (entry + 1);
+        trak->time_to_sample_entries -= entries - 1;
+        trak->end_sample = trak->start_sample + start_sample;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                       "end_sample:%ui, new count:%uD",
+                       trak->end_sample, rest);
+    }
+
+    return NGX_OK;
+}
+
+
+typedef struct {
+    u_char    size[4];
+    u_char    name[4];
+    u_char    version[1];
+    u_char    flags[3];
+    u_char    entries[4];
+} ngx_http_mp4_stss_atom_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_stss_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    u_char                    *atom_header, *atom_table, *atom_end;
+    uint32_t                   entries;
+    ngx_buf_t                 *atom, *data;
+    ngx_http_mp4_trak_t       *trak;
+    ngx_http_mp4_stss_atom_t  *stss_atom;
+
+    /* sync samples atom */
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stss atom");
+
+    atom_header = ngx_mp4_atom_header(mp4);
+    stss_atom = (ngx_http_mp4_stss_atom_t *) atom_header;
+    ngx_mp4_set_atom_name(stss_atom, 's', 't', 's', 's');
+
+    if (ngx_mp4_atom_data_size(ngx_http_mp4_stss_atom_t) > atom_data_size) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "\"%s\" mp4 stss atom too small", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    entries = ngx_mp4_get_32value(stss_atom->entries);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "sync sample entries:%uD", entries);
+
+    trak = ngx_mp4_last_trak(mp4);
+    trak->sync_samples_entries = entries;
+
+    atom_table = atom_header + sizeof(ngx_http_mp4_stss_atom_t);
+
+    atom = &trak->stss_atom_buf;
+    atom->temporary = 1;
+    atom->pos = atom_header;
+    atom->last = atom_table;
+
+    if (ngx_mp4_atom_data_size(ngx_http_mp4_stss_atom_t)
+        + entries * sizeof(uint32_t) > atom_data_size)
+    {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "\"%s\" mp4 stss atom too small", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    atom_end = atom_table + entries * sizeof(uint32_t);
+
+    data = &trak->stss_data_buf;
+    data->temporary = 1;
+    data->pos = atom_table;
+    data->last = atom_end;
+
+    trak->out[NGX_HTTP_MP4_STSS_ATOM].buf = atom;
+    trak->out[NGX_HTTP_MP4_STSS_DATA].buf = data;
+
+    ngx_mp4_atom_next(mp4, atom_data_size);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_update_stss_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak)
+{
+    size_t                     atom_size;
+    uint32_t                   sample, start_sample, *entry, *end;
+    ngx_buf_t                 *atom, *data;
+    ngx_http_mp4_stss_atom_t  *stss_atom;
+
+    /*
+     * mdia.minf.stbl.stss updating requires trak->start_sample
+     * from mdia.minf.stbl.stts which depends on value from mdia.mdhd
+     * atom which may reside after mdia.minf
+     */
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mp4 stss atom update");
+
+    data = trak->out[NGX_HTTP_MP4_STSS_DATA].buf;
+
+    if (data == NULL) {
+        return NGX_OK;
+    }
+
+    ngx_http_mp4_crop_stss_data(mp4, trak, 1);
+    ngx_http_mp4_crop_stss_data(mp4, trak, 0);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "sync sample entries:%uD", trak->sync_samples_entries);
+
+    if (trak->sync_samples_entries) {
+        entry = (uint32_t *) data->pos;
+        end = (uint32_t *) data->last;
+
+        start_sample = trak->start_sample;
+
+        while (entry < end) {
+            sample = ngx_mp4_get_32value(entry);
+            sample -= start_sample;
+            ngx_mp4_set_32value(entry, sample);
+            entry++;
+        }
+
+    } else {
+        trak->out[NGX_HTTP_MP4_STSS_DATA].buf = NULL;
+    }
+
+    atom_size = sizeof(ngx_http_mp4_stss_atom_t) + (data->last - data->pos);
+    trak->size += atom_size;
+
+    atom = trak->out[NGX_HTTP_MP4_STSS_ATOM].buf;
+    stss_atom = (ngx_http_mp4_stss_atom_t *) atom->pos;
+
+    ngx_mp4_set_32value(stss_atom->size, atom_size);
+    ngx_mp4_set_32value(stss_atom->entries, trak->sync_samples_entries);
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_mp4_crop_stss_data(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak, ngx_uint_t start)
+{
+    uint32_t     sample, start_sample, *entry, *end;
+    ngx_buf_t   *data;
+    ngx_uint_t   entries;
+
+    /* sync samples starts from 1 */
+
+    if (start) {
+        start_sample = trak->start_sample + 1;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                       "mp4 stss crop start_sample:%uD", start_sample);
+
+    } else if (mp4->length) {
+        start_sample = trak->end_sample + 1;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                       "mp4 stss crop end_sample:%uD", start_sample);
+
+    } else {
+        return;
+    }
+
+    data = trak->out[NGX_HTTP_MP4_STSS_DATA].buf;
+
+    entries = trak->sync_samples_entries;
+    entry = (uint32_t *) data->pos;
+    end = (uint32_t *) data->last;
+
+    while (entry < end) {
+        sample = ngx_mp4_get_32value(entry);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                       "sync:%uD", sample);
+
+        if (sample >= start_sample) {
+            goto found;
+        }
+
+        entries--;
+        entry++;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "sample is out of mp4 stss atom");
+
+found:
+
+    if (start) {
+        data->pos = (u_char *) entry;
+        trak->sync_samples_entries = entries;
+
+    } else {
+        data->last = (u_char *) entry;
+        trak->sync_samples_entries -= entries;
+    }
+}
+
+
+typedef struct {
+    u_char    size[4];
+    u_char    name[4];
+    u_char    version[1];
+    u_char    flags[3];
+    u_char    entries[4];
+} ngx_mp4_ctts_atom_t;
+
+typedef struct {
+    u_char    count[4];
+    u_char    offset[4];
+} ngx_mp4_ctts_entry_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_ctts_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    u_char               *atom_header, *atom_table, *atom_end;
+    uint32_t              entries;
+    ngx_buf_t            *atom, *data;
+    ngx_mp4_ctts_atom_t  *ctts_atom;
+    ngx_http_mp4_trak_t  *trak;
+
+    /* composition offsets atom */
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 ctts atom");
+
+    atom_header = ngx_mp4_atom_header(mp4);
+    ctts_atom = (ngx_mp4_ctts_atom_t *) atom_header;
+    ngx_mp4_set_atom_name(ctts_atom, 'c', 't', 't', 's');
+
+    if (ngx_mp4_atom_data_size(ngx_mp4_ctts_atom_t) > atom_data_size) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "\"%s\" mp4 ctts atom too small", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    entries = ngx_mp4_get_32value(ctts_atom->entries);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "composition offset entries:%uD", entries);
+
+    trak = ngx_mp4_last_trak(mp4);
+    trak->composition_offset_entries = entries;
+
+    atom_table = atom_header + sizeof(ngx_mp4_ctts_atom_t);
+
+    atom = &trak->ctts_atom_buf;
+    atom->temporary = 1;
+    atom->pos = atom_header;
+    atom->last = atom_table;
+
+    if (ngx_mp4_atom_data_size(ngx_mp4_ctts_atom_t)
+        + entries * sizeof(ngx_mp4_ctts_entry_t) > atom_data_size)
+    {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "\"%s\" mp4 ctts atom too small", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    atom_end = atom_table + entries * sizeof(ngx_mp4_ctts_entry_t);
+
+    data = &trak->ctts_data_buf;
+    data->temporary = 1;
+    data->pos = atom_table;
+    data->last = atom_end;
+
+    trak->out[NGX_HTTP_MP4_CTTS_ATOM].buf = atom;
+    trak->out[NGX_HTTP_MP4_CTTS_DATA].buf = data;
+
+    ngx_mp4_atom_next(mp4, atom_data_size);
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_mp4_update_ctts_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak)
+{
+    size_t                atom_size;
+    ngx_buf_t            *atom, *data;
+    ngx_mp4_ctts_atom_t  *ctts_atom;
+
+    /*
+     * mdia.minf.stbl.ctts updating requires trak->start_sample
+     * from mdia.minf.stbl.stts which depends on value from mdia.mdhd
+     * atom which may reside after mdia.minf
+     */
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mp4 ctts atom update");
+
+    data = trak->out[NGX_HTTP_MP4_CTTS_DATA].buf;
+
+    if (data == NULL) {
+        return;
+    }
+
+    ngx_http_mp4_crop_ctts_data(mp4, trak, 1);
+    ngx_http_mp4_crop_ctts_data(mp4, trak, 0);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "composition offset entries:%uD",
+                   trak->composition_offset_entries);
+
+    if (trak->composition_offset_entries == 0) {
+        trak->out[NGX_HTTP_MP4_CTTS_ATOM].buf = NULL;
+        trak->out[NGX_HTTP_MP4_CTTS_DATA].buf = NULL;
+        return;
+    }
+
+    atom_size = sizeof(ngx_mp4_ctts_atom_t) + (data->last - data->pos);
+    trak->size += atom_size;
+
+    atom = trak->out[NGX_HTTP_MP4_CTTS_ATOM].buf;
+    ctts_atom = (ngx_mp4_ctts_atom_t *) atom->pos;
+
+    ngx_mp4_set_32value(ctts_atom->size, atom_size);
+    ngx_mp4_set_32value(ctts_atom->entries, trak->composition_offset_entries);
+
+    return;
+}
+
+
+static void
+ngx_http_mp4_crop_ctts_data(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak, ngx_uint_t start)
+{
+    uint32_t               count, start_sample, rest;
+    ngx_buf_t             *data;
+    ngx_uint_t             entries;
+    ngx_mp4_ctts_entry_t  *entry, *end;
+
+    /* sync samples starts from 1 */
+
+    if (start) {
+        start_sample = trak->start_sample + 1;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                       "mp4 ctts crop start_sample:%uD", start_sample);
+
+    } else if (mp4->length) {
+        start_sample = trak->end_sample - trak->start_sample + 1;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                       "mp4 ctts crop end_sample:%uD", start_sample);
+
+    } else {
+        return;
+    }
+
+    data = trak->out[NGX_HTTP_MP4_CTTS_DATA].buf;
+
+    entries = trak->composition_offset_entries;
+    entry = (ngx_mp4_ctts_entry_t *) data->pos;
+    end = (ngx_mp4_ctts_entry_t *) data->last;
+
+    while (entry < end) {
+        count = ngx_mp4_get_32value(entry->count);
+
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                       "sample:%uD, count:%uD, offset:%uD",
+                       start_sample, count, ngx_mp4_get_32value(entry->offset));
+
+        if (start_sample <= count) {
+            rest = start_sample - 1;
+            goto found;
+        }
+
+        start_sample -= count;
+        entries--;
+        entry++;
+    }
+
+    if (start) {
+        data->pos = (u_char *) end;
+        trak->composition_offset_entries = 0;
+    }
+
+    return;
+
+found:
+
+    if (start) {
+        ngx_mp4_set_32value(entry->count, count - rest);
+        data->pos = (u_char *) entry;
+        trak->composition_offset_entries = entries;
+
+    } else {
+        ngx_mp4_set_32value(entry->count, rest);
+        data->last = (u_char *) (entry + 1);
+        trak->composition_offset_entries -= entries - 1;
+    }
+}
+
+
+typedef struct {
+    u_char    size[4];
+    u_char    name[4];
+    u_char    version[1];
+    u_char    flags[3];
+    u_char    entries[4];
+} ngx_mp4_stsc_atom_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_stsc_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    u_char               *atom_header, *atom_table, *atom_end;
+    uint32_t              entries;
+    ngx_buf_t            *atom, *data;
+    ngx_mp4_stsc_atom_t  *stsc_atom;
+    ngx_http_mp4_trak_t  *trak;
+
+    /* sample-to-chunk atom */
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stsc atom");
+
+    atom_header = ngx_mp4_atom_header(mp4);
+    stsc_atom = (ngx_mp4_stsc_atom_t *) atom_header;
+    ngx_mp4_set_atom_name(stsc_atom, 's', 't', 's', 'c');
+
+    if (ngx_mp4_atom_data_size(ngx_mp4_stsc_atom_t) > atom_data_size) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "\"%s\" mp4 stsc atom too small", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    entries = ngx_mp4_get_32value(stsc_atom->entries);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "sample-to-chunk entries:%uD", entries);
+
+    if (ngx_mp4_atom_data_size(ngx_mp4_stsc_atom_t)
+        + entries * sizeof(ngx_mp4_stsc_entry_t) > atom_data_size)
+    {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "\"%s\" mp4 stsc atom too small", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    atom_table = atom_header + sizeof(ngx_mp4_stsc_atom_t);
+    atom_end = atom_table + entries * sizeof(ngx_mp4_stsc_entry_t);
+
+    trak = ngx_mp4_last_trak(mp4);
+    trak->sample_to_chunk_entries = entries;
+
+    atom = &trak->stsc_atom_buf;
+    atom->temporary = 1;
+    atom->pos = atom_header;
+    atom->last = atom_table;
+
+    data = &trak->stsc_data_buf;
+    data->temporary = 1;
+    data->pos = atom_table;
+    data->last = atom_end;
+
+    trak->out[NGX_HTTP_MP4_STSC_ATOM].buf = atom;
+    trak->out[NGX_HTTP_MP4_STSC_DATA].buf = data;
+
+    ngx_mp4_atom_next(mp4, atom_data_size);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_update_stsc_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak)
+{
+    size_t                 atom_size;
+    uint32_t               chunk;
+    ngx_buf_t             *atom, *data;
+    ngx_mp4_stsc_atom_t   *stsc_atom;
+    ngx_mp4_stsc_entry_t  *entry, *end;
+
+    /*
+     * mdia.minf.stbl.stsc updating requires trak->start_sample
+     * from mdia.minf.stbl.stts which depends on value from mdia.mdhd
+     * atom which may reside after mdia.minf
+     */
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mp4 stsc atom update");
+
+    data = trak->out[NGX_HTTP_MP4_STSC_DATA].buf;
+
+    if (data == NULL) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "no mp4 stsc atoms were found in \"%s\"",
+                      mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    if (trak->sample_to_chunk_entries == 0) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "zero number of entries in stsc atom in \"%s\"",
+                      mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    if (ngx_http_mp4_crop_stsc_data(mp4, trak, 1) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_http_mp4_crop_stsc_data(mp4, trak, 0) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "sample-to-chunk entries:%uD",
+                   trak->sample_to_chunk_entries);
+
+    entry = (ngx_mp4_stsc_entry_t *) data->pos;
+    end = (ngx_mp4_stsc_entry_t *) data->last;
+
+    while (entry < end) {
+        chunk = ngx_mp4_get_32value(entry->chunk);
+        chunk -= trak->start_chunk;
+        ngx_mp4_set_32value(entry->chunk, chunk);
+        entry++;
+    }
+
+    atom_size = sizeof(ngx_mp4_stsc_atom_t)
+                + trak->sample_to_chunk_entries * sizeof(ngx_mp4_stsc_entry_t);
+
+    trak->size += atom_size;
+
+    atom = trak->out[NGX_HTTP_MP4_STSC_ATOM].buf;
+    stsc_atom = (ngx_mp4_stsc_atom_t *) atom->pos;
+
+    ngx_mp4_set_32value(stsc_atom->size, atom_size);
+    ngx_mp4_set_32value(stsc_atom->entries, trak->sample_to_chunk_entries);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_crop_stsc_data(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak, ngx_uint_t start)
+{
+    uint32_t               start_sample, chunk, samples, id, next_chunk, n,
+                           prev_samples;
+    ngx_buf_t             *data, *buf;
+    ngx_uint_t             entries, target_chunk, chunk_samples;
+    ngx_mp4_stsc_entry_t  *entry, *end, *first;
+
+    entries = trak->sample_to_chunk_entries - 1;
+
+    if (start) {
+        start_sample = (uint32_t) trak->start_sample;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                       "mp4 stsc crop start_sample:%uD", start_sample);
+
+    } else if (mp4->length) {
+        start_sample = (uint32_t) (trak->end_sample - trak->start_sample);
+        samples = 0;
+
+        data = trak->out[NGX_HTTP_MP4_STSC_START].buf;
+
+        if (data) {
+            entry = (ngx_mp4_stsc_entry_t *) data->pos;
+            samples = ngx_mp4_get_32value(entry->samples);
+            entries--;
+
+            if (samples > start_sample) {
+                samples = start_sample;
+                ngx_mp4_set_32value(entry->samples, samples);
+            }
+
+            start_sample -= samples;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                       "mp4 stsc crop end_sample:%uD, ext_samples:%uD",
+                       start_sample, samples);
+
+    } else {
+        return NGX_OK;
+    }
+
+    data = trak->out[NGX_HTTP_MP4_STSC_DATA].buf;
+
+    entry = (ngx_mp4_stsc_entry_t *) data->pos;
+    end = (ngx_mp4_stsc_entry_t *) data->last;
+
+    chunk = ngx_mp4_get_32value(entry->chunk);
+    samples = ngx_mp4_get_32value(entry->samples);
+    id = ngx_mp4_get_32value(entry->id);
+    prev_samples = 0;
+    entry++;
+
+    while (entry < end) {
+
+        next_chunk = ngx_mp4_get_32value(entry->chunk);
+
+        ngx_log_debug5(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                       "sample:%uD, chunk:%uD, chunks:%uD, "
+                       "samples:%uD, id:%uD",
+                       start_sample, chunk, next_chunk - chunk, samples, id);
+
+        n = (next_chunk - chunk) * samples;
+
+        if (start_sample < n) {
+            goto found;
+        }
+
+        start_sample -= n;
+
+        prev_samples = samples;
+        chunk = next_chunk;
+        samples = ngx_mp4_get_32value(entry->samples);
+        id = ngx_mp4_get_32value(entry->id);
+        entries--;
+        entry++;
+    }
+
+    next_chunk = trak->chunks + 1;
+
+    ngx_log_debug4(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "sample:%uD, chunk:%uD, chunks:%uD, samples:%uD",
+                   start_sample, chunk, next_chunk - chunk, samples);
+
+    n = (next_chunk - chunk) * samples;
+
+    if (start_sample > n) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "%s time is out mp4 stsc chunks in \"%s\"",
+                      start ? "start" : "end", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+found:
+
+    entries++;
+    entry--;
+
+    if (samples == 0) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "zero number of samples in \"%s\"",
+                      mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    target_chunk = chunk - 1;
+    target_chunk += start_sample / samples;
+    chunk_samples = start_sample % samples;
+
+    if (start) {
+        data->pos = (u_char *) entry;
+
+        trak->sample_to_chunk_entries = entries;
+        trak->start_chunk = target_chunk;
+        trak->start_chunk_samples = chunk_samples;
+
+        ngx_mp4_set_32value(entry->chunk, trak->start_chunk + 1);
+
+        samples -= chunk_samples;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                       "start_chunk:%ui, start_chunk_samples:%ui",
+                       trak->start_chunk, trak->start_chunk_samples);
+
+    } else {
+        if (start_sample) {
+            data->last = (u_char *) (entry + 1);
+            trak->sample_to_chunk_entries -= entries - 1;
+            trak->end_chunk_samples = samples;
+
+        } else {
+            data->last = (u_char *) entry;
+            trak->sample_to_chunk_entries -= entries;
+            trak->end_chunk_samples = prev_samples;
+        }
+
+        if (chunk_samples) {
+            trak->end_chunk = target_chunk + 1;
+            trak->end_chunk_samples = chunk_samples;
+
+        } else {
+            trak->end_chunk = target_chunk;
+        }
+
+        samples = chunk_samples;
+        next_chunk = chunk + 1;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                       "end_chunk:%ui, end_chunk_samples:%ui",
+                       trak->end_chunk, trak->end_chunk_samples);
+    }
+
+    if (chunk_samples && next_chunk - target_chunk == 2) {
+
+        ngx_mp4_set_32value(entry->samples, samples);
+
+    } else if (chunk_samples && start) {
+
+        first = &trak->stsc_start_chunk_entry;
+        ngx_mp4_set_32value(first->chunk, 1);
+        ngx_mp4_set_32value(first->samples, samples);
+        ngx_mp4_set_32value(first->id, id);
+
+        buf = &trak->stsc_start_chunk_buf;
+        buf->temporary = 1;
+        buf->pos = (u_char *) first;
+        buf->last = (u_char *) first + sizeof(ngx_mp4_stsc_entry_t);
+
+        trak->out[NGX_HTTP_MP4_STSC_START].buf = buf;
+
+        ngx_mp4_set_32value(entry->chunk, trak->start_chunk + 2);
+
+        trak->sample_to_chunk_entries++;
+
+    } else if (chunk_samples) {
+
+        first = &trak->stsc_end_chunk_entry;
+        ngx_mp4_set_32value(first->chunk, trak->end_chunk - trak->start_chunk);
+        ngx_mp4_set_32value(first->samples, samples);
+        ngx_mp4_set_32value(first->id, id);
+
+        buf = &trak->stsc_end_chunk_buf;
+        buf->temporary = 1;
+        buf->pos = (u_char *) first;
+        buf->last = (u_char *) first + sizeof(ngx_mp4_stsc_entry_t);
+
+        trak->out[NGX_HTTP_MP4_STSC_END].buf = buf;
+
+        trak->sample_to_chunk_entries++;
+    }
+
+    return NGX_OK;
+}
+
+
+typedef struct {
+    u_char    size[4];
+    u_char    name[4];
+    u_char    version[1];
+    u_char    flags[3];
+    u_char    uniform_size[4];
+    u_char    entries[4];
+} ngx_mp4_stsz_atom_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_stsz_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    u_char               *atom_header, *atom_table, *atom_end;
+    size_t                atom_size;
+    uint32_t              entries, size;
+    ngx_buf_t            *atom, *data;
+    ngx_mp4_stsz_atom_t  *stsz_atom;
+    ngx_http_mp4_trak_t  *trak;
+
+    /* sample sizes atom */
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stsz atom");
+
+    atom_header = ngx_mp4_atom_header(mp4);
+    stsz_atom = (ngx_mp4_stsz_atom_t *) atom_header;
+    ngx_mp4_set_atom_name(stsz_atom, 's', 't', 's', 'z');
+
+    if (ngx_mp4_atom_data_size(ngx_mp4_stsz_atom_t) > atom_data_size) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "\"%s\" mp4 stsz atom too small", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    size = ngx_mp4_get_32value(stsz_atom->uniform_size);
+    entries = ngx_mp4_get_32value(stsz_atom->entries);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "sample uniform size:%uD, entries:%uD", size, entries);
+
+    trak = ngx_mp4_last_trak(mp4);
+    trak->sample_sizes_entries = entries;
+
+    atom_table = atom_header + sizeof(ngx_mp4_stsz_atom_t);
+
+    atom = &trak->stsz_atom_buf;
+    atom->temporary = 1;
+    atom->pos = atom_header;
+    atom->last = atom_table;
+
+    trak->out[NGX_HTTP_MP4_STSZ_ATOM].buf = atom;
+
+    if (size == 0) {
+        if (ngx_mp4_atom_data_size(ngx_mp4_stsz_atom_t)
+            + entries * sizeof(uint32_t) > atom_data_size)
+        {
+            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                          "\"%s\" mp4 stsz atom too small",
+                          mp4->file.name.data);
+            return NGX_ERROR;
+        }
+
+        atom_end = atom_table + entries * sizeof(uint32_t);
+
+        data = &trak->stsz_data_buf;
+        data->temporary = 1;
+        data->pos = atom_table;
+        data->last = atom_end;
+
+        trak->out[NGX_HTTP_MP4_STSZ_DATA].buf = data;
+
+    } else {
+        /* if size != 0 then all samples are the same size */
+        /* TODO : chunk samples */
+        atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
+        ngx_mp4_set_32value(atom_header, atom_size);
+        trak->size += atom_size;
+    }
+
+    ngx_mp4_atom_next(mp4, atom_data_size);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_update_stsz_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak)
+{
+    size_t                atom_size;
+    uint32_t             *pos, *end, entries;
+    ngx_buf_t            *atom, *data;
+    ngx_mp4_stsz_atom_t  *stsz_atom;
+
+    /*
+     * mdia.minf.stbl.stsz updating requires trak->start_sample
+     * from mdia.minf.stbl.stts which depends on value from mdia.mdhd
+     * atom which may reside after mdia.minf
+     */
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mp4 stsz atom update");
+
+    data = trak->out[NGX_HTTP_MP4_STSZ_DATA].buf;
+
+    if (data) {
+        entries = trak->sample_sizes_entries;
+
+        if (trak->start_sample > entries) {
+            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                          "start time is out mp4 stsz samples in \"%s\"",
+                          mp4->file.name.data);
+            return NGX_ERROR;
+        }
+
+        entries -= trak->start_sample;
+        data->pos += trak->start_sample * sizeof(uint32_t);
+        end = (uint32_t *) data->pos;
+
+        for (pos = end - trak->start_chunk_samples; pos < end; pos++) {
+            trak->start_chunk_samples_size += ngx_mp4_get_32value(pos);
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                       "chunk samples sizes:%uL",
+                       trak->start_chunk_samples_size);
+
+        if (mp4->length) {
+            if (trak->end_sample - trak->start_sample > entries) {
+                ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                              "end time is out mp4 stsz samples in \"%s\"",
+                              mp4->file.name.data);
+                return NGX_ERROR;
+            }
+
+            entries = trak->end_sample - trak->start_sample;
+            data->last = data->pos + entries * sizeof(uint32_t);
+            end = (uint32_t *) data->last;
+
+            for (pos = end - trak->end_chunk_samples; pos < end; pos++) {
+                trak->end_chunk_samples_size += ngx_mp4_get_32value(pos);
+            }
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                           "mp4 stsz end_chunk_samples_size:%uL",
+                           trak->end_chunk_samples_size);
+        }
+
+        atom_size = sizeof(ngx_mp4_stsz_atom_t) + (data->last - data->pos);
+        trak->size += atom_size;
+
+        atom = trak->out[NGX_HTTP_MP4_STSZ_ATOM].buf;
+        stsz_atom = (ngx_mp4_stsz_atom_t *) atom->pos;
+
+        ngx_mp4_set_32value(stsz_atom->size, atom_size);
+        ngx_mp4_set_32value(stsz_atom->entries, entries);
+    }
+
+    return NGX_OK;
+}
+
+
+typedef struct {
+    u_char    size[4];
+    u_char    name[4];
+    u_char    version[1];
+    u_char    flags[3];
+    u_char    entries[4];
+} ngx_mp4_stco_atom_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_stco_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    u_char               *atom_header, *atom_table, *atom_end;
+    uint32_t              entries;
+    ngx_buf_t            *atom, *data;
+    ngx_mp4_stco_atom_t  *stco_atom;
+    ngx_http_mp4_trak_t  *trak;
+
+    /* chunk offsets atom */
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stco atom");
+
+    atom_header = ngx_mp4_atom_header(mp4);
+    stco_atom = (ngx_mp4_stco_atom_t *) atom_header;
+    ngx_mp4_set_atom_name(stco_atom, 's', 't', 'c', 'o');
+
+    if (ngx_mp4_atom_data_size(ngx_mp4_stco_atom_t) > atom_data_size) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "\"%s\" mp4 stco atom too small", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    entries = ngx_mp4_get_32value(stco_atom->entries);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "chunks:%uD", entries);
+
+    if (ngx_mp4_atom_data_size(ngx_mp4_stco_atom_t)
+        + entries * sizeof(uint32_t) > atom_data_size)
+    {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "\"%s\" mp4 stco atom too small", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    atom_table = atom_header + sizeof(ngx_mp4_stco_atom_t);
+    atom_end = atom_table + entries * sizeof(uint32_t);
+
+    trak = ngx_mp4_last_trak(mp4);
+    trak->chunks = entries;
+
+    atom = &trak->stco_atom_buf;
+    atom->temporary = 1;
+    atom->pos = atom_header;
+    atom->last = atom_table;
+
+    data = &trak->stco_data_buf;
+    data->temporary = 1;
+    data->pos = atom_table;
+    data->last = atom_end;
+
+    trak->out[NGX_HTTP_MP4_STCO_ATOM].buf = atom;
+    trak->out[NGX_HTTP_MP4_STCO_DATA].buf = data;
+
+    ngx_mp4_atom_next(mp4, atom_data_size);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_update_stco_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak)
+{
+    size_t                atom_size;
+    uint32_t              entries;
+    ngx_buf_t            *atom, *data;
+    ngx_mp4_stco_atom_t  *stco_atom;
+
+    /*
+     * mdia.minf.stbl.stco updating requires trak->start_chunk
+     * from mdia.minf.stbl.stsc which depends on value from mdia.mdhd
+     * atom which may reside after mdia.minf
+     */
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mp4 stco atom update");
+
+    data = trak->out[NGX_HTTP_MP4_STCO_DATA].buf;
+
+    if (data == NULL) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "no mp4 stco atoms were found in \"%s\"",
+                      mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    if (trak->start_chunk > trak->chunks) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "start time is out mp4 stco chunks in \"%s\"",
+                      mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    data->pos += trak->start_chunk * sizeof(uint32_t);
+
+    trak->start_offset = ngx_mp4_get_32value(data->pos);
+    trak->start_offset += trak->start_chunk_samples_size;
+    ngx_mp4_set_32value(data->pos, trak->start_offset);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "start chunk offset:%O", trak->start_offset);
+
+    if (mp4->length) {
+
+        if (trak->end_chunk > trak->chunks) {
+            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                          "end time is out mp4 stco chunks in \"%s\"",
+                          mp4->file.name.data);
+            return NGX_ERROR;
+        }
+
+        entries = trak->end_chunk - trak->start_chunk;
+        data->last = data->pos + entries * sizeof(uint32_t);
+
+        if (entries) {
+            trak->end_offset =
+                            ngx_mp4_get_32value(data->last - sizeof(uint32_t));
+            trak->end_offset += trak->end_chunk_samples_size;
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                           "end chunk offset:%O", trak->end_offset);
+        }
+
+    } else {
+        entries = trak->chunks - trak->start_chunk;
+        trak->end_offset = mp4->mdat_data.buf->file_last;
+    }
+
+    if (entries == 0) {
+        trak->start_offset = mp4->end;
+        trak->end_offset = 0;
+    }
+
+    atom_size = sizeof(ngx_mp4_stco_atom_t) + (data->last - data->pos);
+    trak->size += atom_size;
+
+    atom = trak->out[NGX_HTTP_MP4_STCO_ATOM].buf;
+    stco_atom = (ngx_mp4_stco_atom_t *) atom->pos;
+
+    ngx_mp4_set_32value(stco_atom->size, atom_size);
+    ngx_mp4_set_32value(stco_atom->entries, entries);
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_mp4_adjust_stco_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak, int32_t adjustment)
+{
+    uint32_t    offset, *entry, *end;
+    ngx_buf_t  *data;
+
+    /*
+     * moov.trak.mdia.minf.stbl.stco adjustment requires
+     * minimal start offset of all traks and new moov atom size
+     */
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mp4 stco atom adjustment");
+
+    data = trak->out[NGX_HTTP_MP4_STCO_DATA].buf;
+    entry = (uint32_t *) data->pos;
+    end = (uint32_t *) data->last;
+
+    while (entry < end) {
+        offset = ngx_mp4_get_32value(entry);
+        offset += adjustment;
+        ngx_mp4_set_32value(entry, offset);
+        entry++;
+    }
+}
+
+
+typedef struct {
+    u_char    size[4];
+    u_char    name[4];
+    u_char    version[1];
+    u_char    flags[3];
+    u_char    entries[4];
+} ngx_mp4_co64_atom_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_co64_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+    u_char               *atom_header, *atom_table, *atom_end;
+    uint32_t              entries;
+    ngx_buf_t            *atom, *data;
+    ngx_mp4_co64_atom_t  *co64_atom;
+    ngx_http_mp4_trak_t  *trak;
+
+    /* chunk offsets atom */
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 co64 atom");
+
+    atom_header = ngx_mp4_atom_header(mp4);
+    co64_atom = (ngx_mp4_co64_atom_t *) atom_header;
+    ngx_mp4_set_atom_name(co64_atom, 'c', 'o', '6', '4');
+
+    if (ngx_mp4_atom_data_size(ngx_mp4_co64_atom_t) > atom_data_size) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "\"%s\" mp4 co64 atom too small", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    entries = ngx_mp4_get_32value(co64_atom->entries);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "chunks:%uD", entries);
+
+    if (ngx_mp4_atom_data_size(ngx_mp4_co64_atom_t)
+        + entries * sizeof(uint64_t) > atom_data_size)
+    {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "\"%s\" mp4 co64 atom too small", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    atom_table = atom_header + sizeof(ngx_mp4_co64_atom_t);
+    atom_end = atom_table + entries * sizeof(uint64_t);
+
+    trak = ngx_mp4_last_trak(mp4);
+    trak->chunks = entries;
+
+    atom = &trak->co64_atom_buf;
+    atom->temporary = 1;
+    atom->pos = atom_header;
+    atom->last = atom_table;
+
+    data = &trak->co64_data_buf;
+    data->temporary = 1;
+    data->pos = atom_table;
+    data->last = atom_end;
+
+    trak->out[NGX_HTTP_MP4_CO64_ATOM].buf = atom;
+    trak->out[NGX_HTTP_MP4_CO64_DATA].buf = data;
+
+    ngx_mp4_atom_next(mp4, atom_data_size);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_update_co64_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak)
+{
+    size_t                atom_size;
+    uint64_t              entries;
+    ngx_buf_t            *atom, *data;
+    ngx_mp4_co64_atom_t  *co64_atom;
+
+    /*
+     * mdia.minf.stbl.co64 updating requires trak->start_chunk
+     * from mdia.minf.stbl.stsc which depends on value from mdia.mdhd
+     * atom which may reside after mdia.minf
+     */
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mp4 co64 atom update");
+
+    data = trak->out[NGX_HTTP_MP4_CO64_DATA].buf;
+
+    if (data == NULL) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "no mp4 co64 atoms were found in \"%s\"",
+                      mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    if (trak->start_chunk > trak->chunks) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "start time is out mp4 co64 chunks in \"%s\"",
+                      mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
+    data->pos += trak->start_chunk * sizeof(uint64_t);
+
+    trak->start_offset = ngx_mp4_get_64value(data->pos);
+    trak->start_offset += trak->start_chunk_samples_size;
+    ngx_mp4_set_64value(data->pos, trak->start_offset);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "start chunk offset:%O", trak->start_offset);
+
+    if (mp4->length) {
+
+        if (trak->end_chunk > trak->chunks) {
+            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                          "end time is out mp4 co64 chunks in \"%s\"",
+                          mp4->file.name.data);
+            return NGX_ERROR;
+        }
+
+        entries = trak->end_chunk - trak->start_chunk;
+        data->last = data->pos + entries * sizeof(uint64_t);
+
+        if (entries) {
+            trak->end_offset =
+                            ngx_mp4_get_64value(data->last - sizeof(uint64_t));
+            trak->end_offset += trak->end_chunk_samples_size;
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                           "end chunk offset:%O", trak->end_offset);
+        }
+
+    } else {
+        entries = trak->chunks - trak->start_chunk;
+        trak->end_offset = mp4->mdat_data.buf->file_last;
+    }
+
+    if (entries == 0) {
+        trak->start_offset = mp4->end;
+        trak->end_offset = 0;
+    }
+
+    atom_size = sizeof(ngx_mp4_co64_atom_t) + (data->last - data->pos);
+    trak->size += atom_size;
+
+    atom = trak->out[NGX_HTTP_MP4_CO64_ATOM].buf;
+    co64_atom = (ngx_mp4_co64_atom_t *) atom->pos;
+
+    ngx_mp4_set_32value(co64_atom->size, atom_size);
+    ngx_mp4_set_32value(co64_atom->entries, entries);
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_mp4_adjust_co64_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak, off_t adjustment)
+{
+    uint64_t    offset, *entry, *end;
+    ngx_buf_t  *data;
+
+    /*
+     * moov.trak.mdia.minf.stbl.co64 adjustment requires
+     * minimal start offset of all traks and new moov atom size
+     */
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mp4 co64 atom adjustment");
+
+    data = trak->out[NGX_HTTP_MP4_CO64_DATA].buf;
+    entry = (uint64_t *) data->pos;
+    end = (uint64_t *) data->last;
+
+    while (entry < end) {
+        offset = ngx_mp4_get_64value(entry);
+        offset += adjustment;
+        ngx_mp4_set_64value(entry, offset);
+        entry++;
+    }
+}
+
+
+static char *
+ngx_http_mp4(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_loc_conf_t  *clcf;
+
+    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+    clcf->handler = ngx_http_mp4_handler;
+
+    return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_http_mp4_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_mp4_conf_t  *conf;
+
+    conf = ngx_palloc(cf->pool, sizeof(ngx_http_mp4_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    conf->buffer_size = NGX_CONF_UNSET_SIZE;
+    conf->max_buffer_size = NGX_CONF_UNSET_SIZE;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_mp4_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_mp4_conf_t *prev = parent;
+    ngx_http_mp4_conf_t *conf = 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);
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_not_modified_filter_module.c b/nginx/src/http/modules/ngx_http_not_modified_filter_module.c
new file mode 100644 (file)
index 0000000..032ba96
--- /dev/null
@@ -0,0 +1,266 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_uint_t ngx_http_test_if_unmodified(ngx_http_request_t *r);
+static ngx_uint_t ngx_http_test_if_modified(ngx_http_request_t *r);
+static ngx_uint_t ngx_http_test_if_match(ngx_http_request_t *r,
+    ngx_table_elt_t *header, ngx_uint_t weak);
+static ngx_int_t ngx_http_not_modified_filter_init(ngx_conf_t *cf);
+
+
+static ngx_http_module_t  ngx_http_not_modified_filter_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_not_modified_filter_init,     /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_not_modified_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_not_modified_filter_module_ctx, /* module context */
+    NULL,                                  /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+
+
+static ngx_int_t
+ngx_http_not_modified_header_filter(ngx_http_request_t *r)
+{
+    if (r->headers_out.status != NGX_HTTP_OK
+        || r != r->main
+        || r->disable_not_modified)
+    {
+        return ngx_http_next_header_filter(r);
+    }
+
+    if (r->headers_in.if_unmodified_since
+        && !ngx_http_test_if_unmodified(r))
+    {
+        return ngx_http_filter_finalize_request(r, NULL,
+                                                NGX_HTTP_PRECONDITION_FAILED);
+    }
+
+    if (r->headers_in.if_match
+        && !ngx_http_test_if_match(r, r->headers_in.if_match, 0))
+    {
+        return ngx_http_filter_finalize_request(r, NULL,
+                                                NGX_HTTP_PRECONDITION_FAILED);
+    }
+
+    if (r->headers_in.if_modified_since || r->headers_in.if_none_match) {
+
+        if (r->headers_in.if_modified_since
+            && ngx_http_test_if_modified(r))
+        {
+            return ngx_http_next_header_filter(r);
+        }
+
+        if (r->headers_in.if_none_match
+            && !ngx_http_test_if_match(r, r->headers_in.if_none_match, 1))
+        {
+            return ngx_http_next_header_filter(r);
+        }
+
+        /* not modified */
+
+        r->headers_out.status = NGX_HTTP_NOT_MODIFIED;
+        r->headers_out.status_line.len = 0;
+        r->headers_out.content_type.len = 0;
+        ngx_http_clear_content_length(r);
+        ngx_http_clear_accept_ranges(r);
+
+        if (r->headers_out.content_encoding) {
+            r->headers_out.content_encoding->hash = 0;
+            r->headers_out.content_encoding = NULL;
+        }
+
+        return ngx_http_next_header_filter(r);
+    }
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_uint_t
+ngx_http_test_if_unmodified(ngx_http_request_t *r)
+{
+    time_t  iums;
+
+    if (r->headers_out.last_modified_time == (time_t) -1) {
+        return 0;
+    }
+
+    iums = ngx_parse_http_time(r->headers_in.if_unmodified_since->value.data,
+                               r->headers_in.if_unmodified_since->value.len);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                 "http iums:%T lm:%T", iums, r->headers_out.last_modified_time);
+
+    if (iums >= r->headers_out.last_modified_time) {
+        return 1;
+    }
+
+    return 0;
+}
+
+
+static ngx_uint_t
+ngx_http_test_if_modified(ngx_http_request_t *r)
+{
+    time_t                     ims;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    if (r->headers_out.last_modified_time == (time_t) -1) {
+        return 1;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (clcf->if_modified_since == NGX_HTTP_IMS_OFF) {
+        return 1;
+    }
+
+    ims = ngx_parse_http_time(r->headers_in.if_modified_since->value.data,
+                              r->headers_in.if_modified_since->value.len);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http ims:%T lm:%T", ims, r->headers_out.last_modified_time);
+
+    if (ims == r->headers_out.last_modified_time) {
+        return 0;
+    }
+
+    if (clcf->if_modified_since == NGX_HTTP_IMS_EXACT
+        || ims < r->headers_out.last_modified_time)
+    {
+        return 1;
+    }
+
+    return 0;
+}
+
+
+static ngx_uint_t
+ngx_http_test_if_match(ngx_http_request_t *r, ngx_table_elt_t *header,
+    ngx_uint_t weak)
+{
+    u_char     *start, *end, ch;
+    ngx_str_t   etag, *list;
+
+    list = &header->value;
+
+    if (list->len == 1 && list->data[0] == '*') {
+        return 1;
+    }
+
+    if (r->headers_out.etag == NULL) {
+        return 0;
+    }
+
+    etag = r->headers_out.etag->value;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http im:\"%V\" etag:%V", list, &etag);
+
+    if (weak
+        && etag.len > 2
+        && etag.data[0] == 'W'
+        && etag.data[1] == '/')
+    {
+        etag.len -= 2;
+        etag.data += 2;
+    }
+
+    start = list->data;
+    end = list->data + list->len;
+
+    while (start < end) {
+
+        if (weak
+            && end - start > 2
+            && start[0] == 'W'
+            && start[1] == '/')
+        {
+            start += 2;
+        }
+
+        if (etag.len > (size_t) (end - start)) {
+            return 0;
+        }
+
+        if (ngx_strncmp(start, etag.data, etag.len) != 0) {
+            goto skip;
+        }
+
+        start += etag.len;
+
+        while (start < end) {
+            ch = *start;
+
+            if (ch == ' ' || ch == '\t') {
+                start++;
+                continue;
+            }
+
+            break;
+        }
+
+        if (start == end || *start == ',') {
+            return 1;
+        }
+
+    skip:
+
+        while (start < end && *start != ',') { start++; }
+        while (start < end) {
+            ch = *start;
+
+            if (ch == ' ' || ch == '\t' || ch == ',') {
+                start++;
+                continue;
+            }
+
+            break;
+        }
+    }
+
+    return 0;
+}
+
+
+static ngx_int_t
+ngx_http_not_modified_filter_init(ngx_conf_t *cf)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_not_modified_header_filter;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_proxy_module.c b/nginx/src/http/modules/ngx_http_proxy_module.c
new file mode 100644 (file)
index 0000000..e7f829d
--- /dev/null
@@ -0,0 +1,4348 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_array_t                    caches;  /* ngx_http_file_cache_t * */
+} ngx_http_proxy_main_conf_t;
+
+
+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_http_proxy_rewrite_t *pr);
+
+struct ngx_http_proxy_rewrite_s {
+    ngx_http_proxy_rewrite_pt      handler;
+
+    union {
+        ngx_http_complex_value_t   complex;
+#if (NGX_PCRE)
+        ngx_http_regex_t          *regex;
+#endif
+    } pattern;
+
+    ngx_http_complex_value_t       replacement;
+};
+
+
+typedef struct {
+    ngx_str_t                      key_start;
+    ngx_str_t                      schema;
+    ngx_str_t                      host_header;
+    ngx_str_t                      port;
+    ngx_str_t                      uri;
+} ngx_http_proxy_vars_t;
+
+
+typedef struct {
+    ngx_array_t                   *flushes;
+    ngx_array_t                   *lengths;
+    ngx_array_t                   *values;
+    ngx_hash_t                     hash;
+} ngx_http_proxy_headers_t;
+
+
+typedef struct {
+    ngx_http_upstream_conf_t       upstream;
+
+    ngx_array_t                   *body_flushes;
+    ngx_array_t                   *body_lengths;
+    ngx_array_t                   *body_values;
+    ngx_str_t                      body_source;
+
+    ngx_http_proxy_headers_t       headers;
+#if (NGX_HTTP_CACHE)
+    ngx_http_proxy_headers_t       headers_cache;
+#endif
+    ngx_array_t                   *headers_source;
+
+    ngx_array_t                   *proxy_lengths;
+    ngx_array_t                   *proxy_values;
+
+    ngx_array_t                   *redirects;
+    ngx_array_t                   *cookie_domains;
+    ngx_array_t                   *cookie_paths;
+
+    ngx_http_complex_value_t      *method;
+    ngx_str_t                      location;
+    ngx_str_t                      url;
+
+#if (NGX_HTTP_CACHE)
+    ngx_http_complex_value_t       cache_key;
+#endif
+
+    ngx_http_proxy_vars_t          vars;
+
+    ngx_flag_t                     redirect;
+
+    ngx_uint_t                     http_version;
+
+    ngx_uint_t                     headers_hash_max_size;
+    ngx_uint_t                     headers_hash_bucket_size;
+
+#if (NGX_HTTP_SSL)
+    ngx_uint_t                     ssl;
+    ngx_uint_t                     ssl_protocols;
+    ngx_str_t                      ssl_ciphers;
+    ngx_uint_t                     ssl_verify_depth;
+    ngx_str_t                      ssl_trusted_certificate;
+    ngx_str_t                      ssl_crl;
+    ngx_str_t                      ssl_certificate;
+    ngx_str_t                      ssl_certificate_key;
+    ngx_array_t                   *ssl_passwords;
+#endif
+} ngx_http_proxy_loc_conf_t;
+
+
+typedef struct {
+    ngx_http_status_t              status;
+    ngx_http_chunked_t             chunked;
+    ngx_http_proxy_vars_t          vars;
+    off_t                          internal_body_length;
+
+    ngx_chain_t                   *free;
+    ngx_chain_t                   *busy;
+
+    unsigned                       head:1;
+    unsigned                       internal_chunked:1;
+    unsigned                       header_sent:1;
+} ngx_http_proxy_ctx_t;
+
+
+static ngx_int_t ngx_http_proxy_eval(ngx_http_request_t *r,
+    ngx_http_proxy_ctx_t *ctx, ngx_http_proxy_loc_conf_t *plcf);
+#if (NGX_HTTP_CACHE)
+static ngx_int_t ngx_http_proxy_create_key(ngx_http_request_t *r);
+#endif
+static ngx_int_t ngx_http_proxy_create_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_proxy_reinit_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_proxy_body_output_filter(void *data, ngx_chain_t *in);
+static ngx_int_t ngx_http_proxy_process_status_line(ngx_http_request_t *r);
+static ngx_int_t ngx_http_proxy_process_header(ngx_http_request_t *r);
+static ngx_int_t ngx_http_proxy_input_filter_init(void *data);
+static ngx_int_t ngx_http_proxy_copy_filter(ngx_event_pipe_t *p,
+    ngx_buf_t *buf);
+static ngx_int_t ngx_http_proxy_chunked_filter(ngx_event_pipe_t *p,
+    ngx_buf_t *buf);
+static ngx_int_t ngx_http_proxy_non_buffered_copy_filter(void *data,
+    ssize_t bytes);
+static ngx_int_t ngx_http_proxy_non_buffered_chunked_filter(void *data,
+    ssize_t bytes);
+static void ngx_http_proxy_abort_request(ngx_http_request_t *r);
+static void ngx_http_proxy_finalize_request(ngx_http_request_t *r,
+    ngx_int_t rc);
+
+static ngx_int_t ngx_http_proxy_host_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_proxy_port_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t
+    ngx_http_proxy_add_x_forwarded_for_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t
+    ngx_http_proxy_internal_body_length_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_proxy_internal_chunked_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+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_rewrite_cookie_value(ngx_http_request_t *r,
+    ngx_table_elt_t *h, u_char *value, ngx_array_t *rewrites);
+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);
+
+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);
+static void *ngx_http_proxy_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+static ngx_int_t ngx_http_proxy_init_headers(ngx_conf_t *cf,
+    ngx_http_proxy_loc_conf_t *conf, ngx_http_proxy_headers_t *headers,
+    ngx_keyval_t *default_headers);
+
+static char *ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_proxy_redirect(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+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_store(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+#if (NGX_HTTP_CACHE)
+static char *ngx_http_proxy_cache(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_proxy_cache_key(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+#endif
+#if (NGX_HTTP_SSL)
+static char *ngx_http_proxy_ssl_password_file(ngx_conf_t *cf,
+    ngx_command_t *cmd, void *conf);
+#endif
+
+static char *ngx_http_proxy_lowat_check(ngx_conf_t *cf, void *post, void *data);
+
+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);
+
+#if (NGX_HTTP_SSL)
+static ngx_int_t ngx_http_proxy_set_ssl(ngx_conf_t *cf,
+    ngx_http_proxy_loc_conf_t *plcf);
+#endif
+static void ngx_http_proxy_set_vars(ngx_url_t *u, ngx_http_proxy_vars_t *v);
+
+
+static ngx_conf_post_t  ngx_http_proxy_lowat_post =
+    { ngx_http_proxy_lowat_check };
+
+
+static ngx_conf_bitmask_t  ngx_http_proxy_next_upstream_masks[] = {
+    { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR },
+    { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT },
+    { ngx_string("invalid_header"), NGX_HTTP_UPSTREAM_FT_INVALID_HEADER },
+    { ngx_string("non_idempotent"), NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT },
+    { ngx_string("http_500"), NGX_HTTP_UPSTREAM_FT_HTTP_500 },
+    { ngx_string("http_502"), NGX_HTTP_UPSTREAM_FT_HTTP_502 },
+    { ngx_string("http_503"), NGX_HTTP_UPSTREAM_FT_HTTP_503 },
+    { ngx_string("http_504"), NGX_HTTP_UPSTREAM_FT_HTTP_504 },
+    { ngx_string("http_403"), NGX_HTTP_UPSTREAM_FT_HTTP_403 },
+    { ngx_string("http_404"), NGX_HTTP_UPSTREAM_FT_HTTP_404 },
+    { ngx_string("http_429"), NGX_HTTP_UPSTREAM_FT_HTTP_429 },
+    { ngx_string("updating"), NGX_HTTP_UPSTREAM_FT_UPDATING },
+    { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF },
+    { ngx_null_string, 0 }
+};
+
+
+#if (NGX_HTTP_SSL)
+
+static ngx_conf_bitmask_t  ngx_http_proxy_ssl_protocols[] = {
+    { ngx_string("SSLv2"), NGX_SSL_SSLv2 },
+    { ngx_string("SSLv3"), NGX_SSL_SSLv3 },
+    { ngx_string("TLSv1"), NGX_SSL_TLSv1 },
+    { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 },
+    { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 },
+    { ngx_string("TLSv1.3"), NGX_SSL_TLSv1_3 },
+    { ngx_null_string, 0 }
+};
+
+#endif
+
+
+static ngx_conf_enum_t  ngx_http_proxy_http_version[] = {
+    { ngx_string("1.0"), NGX_HTTP_VERSION_10 },
+    { ngx_string("1.1"), NGX_HTTP_VERSION_11 },
+    { ngx_null_string, 0 }
+};
+
+
+ngx_module_t  ngx_http_proxy_module;
+
+
+static ngx_command_t  ngx_http_proxy_commands[] = {
+
+    { ngx_string("proxy_pass"),
+      NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1,
+      ngx_http_proxy_pass,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("proxy_redirect"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+      ngx_http_proxy_redirect,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("proxy_cookie_domain"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+      ngx_http_proxy_cookie_domain,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("proxy_cookie_path"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+      ngx_http_proxy_cookie_path,
+      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,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("proxy_store_access"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
+      ngx_conf_set_access_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.store_access),
+      NULL },
+
+    { ngx_string("proxy_buffering"),
+      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_proxy_loc_conf_t, upstream.buffering),
+      NULL },
+
+    { ngx_string("proxy_request_buffering"),
+      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_proxy_loc_conf_t, upstream.request_buffering),
+      NULL },
+
+    { ngx_string("proxy_ignore_client_abort"),
+      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_proxy_loc_conf_t, upstream.ignore_client_abort),
+      NULL },
+
+    { ngx_string("proxy_bind"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+      ngx_http_upstream_bind_set_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.local),
+      NULL },
+
+    { ngx_string("proxy_connect_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.connect_timeout),
+      NULL },
+
+    { ngx_string("proxy_send_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.send_timeout),
+      NULL },
+
+    { ngx_string("proxy_send_lowat"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.send_lowat),
+      &ngx_http_proxy_lowat_post },
+
+    { ngx_string("proxy_intercept_errors"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.intercept_errors),
+      NULL },
+
+    { ngx_string("proxy_set_header"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+      ngx_conf_set_keyval_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, headers_source),
+      NULL },
+
+    { ngx_string("proxy_headers_hash_max_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, headers_hash_max_size),
+      NULL },
+
+    { ngx_string("proxy_headers_hash_bucket_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, headers_hash_bucket_size),
+      NULL },
+
+    { ngx_string("proxy_set_body"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, body_source),
+      NULL },
+
+    { ngx_string("proxy_method"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_set_complex_value_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, method),
+      NULL },
+
+    { ngx_string("proxy_pass_request_headers"),
+      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_proxy_loc_conf_t, upstream.pass_request_headers),
+      NULL },
+
+    { ngx_string("proxy_pass_request_body"),
+      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_proxy_loc_conf_t, upstream.pass_request_body),
+      NULL },
+
+    { ngx_string("proxy_buffer_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.buffer_size),
+      NULL },
+
+    { ngx_string("proxy_read_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.read_timeout),
+      NULL },
+
+    { ngx_string("proxy_buffers"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+      ngx_conf_set_bufs_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.bufs),
+      NULL },
+
+    { ngx_string("proxy_busy_buffers_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.busy_buffers_size_conf),
+      NULL },
+
+    { ngx_string("proxy_force_ranges"),
+      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_proxy_loc_conf_t, upstream.force_ranges),
+      NULL },
+
+    { ngx_string("proxy_limit_rate"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.limit_rate),
+      NULL },
+
+#if (NGX_HTTP_CACHE)
+
+    { ngx_string("proxy_cache"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_proxy_cache,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("proxy_cache_key"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_proxy_cache_key,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("proxy_cache_path"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_2MORE,
+      ngx_http_file_cache_set_slot,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      offsetof(ngx_http_proxy_main_conf_t, caches),
+      &ngx_http_proxy_module },
+
+    { ngx_string("proxy_cache_bypass"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_set_predicate_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_bypass),
+      NULL },
+
+    { ngx_string("proxy_no_cache"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_set_predicate_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.no_cache),
+      NULL },
+
+    { ngx_string("proxy_cache_valid"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_file_cache_valid_set_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_valid),
+      NULL },
+
+    { ngx_string("proxy_cache_min_uses"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_min_uses),
+      NULL },
+
+    { ngx_string("proxy_cache_max_range_offset"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_off_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_max_range_offset),
+      NULL },
+
+    { ngx_string("proxy_cache_use_stale"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_use_stale),
+      &ngx_http_proxy_next_upstream_masks },
+
+    { ngx_string("proxy_cache_methods"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_methods),
+      &ngx_http_upstream_cache_method_mask },
+
+    { ngx_string("proxy_cache_lock"),
+      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_proxy_loc_conf_t, upstream.cache_lock),
+      NULL },
+
+    { ngx_string("proxy_cache_lock_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_lock_timeout),
+      NULL },
+
+    { ngx_string("proxy_cache_lock_age"),
+      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_proxy_loc_conf_t, upstream.cache_lock_age),
+      NULL },
+
+    { ngx_string("proxy_cache_revalidate"),
+      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_proxy_loc_conf_t, upstream.cache_revalidate),
+      NULL },
+
+    { ngx_string("proxy_cache_convert_head"),
+      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_proxy_loc_conf_t, upstream.cache_convert_head),
+      NULL },
+
+    { ngx_string("proxy_cache_background_update"),
+      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_proxy_loc_conf_t, upstream.cache_background_update),
+      NULL },
+
+#endif
+
+    { ngx_string("proxy_temp_path"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234,
+      ngx_conf_set_path_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.temp_path),
+      NULL },
+
+    { ngx_string("proxy_max_temp_file_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.max_temp_file_size_conf),
+      NULL },
+
+    { ngx_string("proxy_temp_file_write_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.temp_file_write_size_conf),
+      NULL },
+
+    { ngx_string("proxy_next_upstream"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.next_upstream),
+      &ngx_http_proxy_next_upstream_masks },
+
+    { ngx_string("proxy_next_upstream_tries"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.next_upstream_tries),
+      NULL },
+
+    { ngx_string("proxy_next_upstream_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.next_upstream_timeout),
+      NULL },
+
+    { ngx_string("proxy_pass_header"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.pass_headers),
+      NULL },
+
+    { ngx_string("proxy_hide_header"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.hide_headers),
+      NULL },
+
+    { ngx_string("proxy_ignore_headers"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.ignore_headers),
+      &ngx_http_upstream_ignore_headers_masks },
+
+    { ngx_string("proxy_http_version"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, http_version),
+      &ngx_http_proxy_http_version },
+
+#if (NGX_HTTP_SSL)
+
+    { ngx_string("proxy_ssl_session_reuse"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_session_reuse),
+      NULL },
+
+    { ngx_string("proxy_ssl_protocols"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, ssl_protocols),
+      &ngx_http_proxy_ssl_protocols },
+
+    { ngx_string("proxy_ssl_ciphers"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, ssl_ciphers),
+      NULL },
+
+    { ngx_string("proxy_ssl_name"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_set_complex_value_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_name),
+      NULL },
+
+    { ngx_string("proxy_ssl_server_name"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_server_name),
+      NULL },
+
+    { ngx_string("proxy_ssl_verify"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_verify),
+      NULL },
+
+    { ngx_string("proxy_ssl_verify_depth"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, ssl_verify_depth),
+      NULL },
+
+    { ngx_string("proxy_ssl_trusted_certificate"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, ssl_trusted_certificate),
+      NULL },
+
+    { ngx_string("proxy_ssl_crl"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, ssl_crl),
+      NULL },
+
+    { 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_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, 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_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, ssl_certificate_key),
+      NULL },
+
+    { ngx_string("proxy_ssl_password_file"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_proxy_ssl_password_file,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+#endif
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_proxy_module_ctx = {
+    ngx_http_proxy_add_variables,          /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    ngx_http_proxy_create_main_conf,       /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_proxy_create_loc_conf,        /* create location configuration */
+    ngx_http_proxy_merge_loc_conf          /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_proxy_module = {
+    NGX_MODULE_V1,
+    &ngx_http_proxy_module_ctx,            /* module context */
+    ngx_http_proxy_commands,               /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static char  ngx_http_proxy_version[] = " HTTP/1.0" CRLF;
+static char  ngx_http_proxy_version_11[] = " HTTP/1.1" CRLF;
+
+
+static ngx_keyval_t  ngx_http_proxy_headers[] = {
+    { ngx_string("Host"), ngx_string("$proxy_host") },
+    { ngx_string("Connection"), ngx_string("close") },
+    { ngx_string("Content-Length"), ngx_string("$proxy_internal_body_length") },
+    { ngx_string("Transfer-Encoding"), ngx_string("$proxy_internal_chunked") },
+    { ngx_string("TE"), ngx_string("") },
+    { ngx_string("Keep-Alive"), ngx_string("") },
+    { ngx_string("Expect"), ngx_string("") },
+    { ngx_string("Upgrade"), ngx_string("") },
+    { ngx_null_string, ngx_null_string }
+};
+
+
+static ngx_str_t  ngx_http_proxy_hide_headers[] = {
+    ngx_string("Date"),
+    ngx_string("Server"),
+    ngx_string("X-Pad"),
+    ngx_string("X-Accel-Expires"),
+    ngx_string("X-Accel-Redirect"),
+    ngx_string("X-Accel-Limit-Rate"),
+    ngx_string("X-Accel-Buffering"),
+    ngx_string("X-Accel-Charset"),
+    ngx_null_string
+};
+
+
+#if (NGX_HTTP_CACHE)
+
+static ngx_keyval_t  ngx_http_proxy_cache_headers[] = {
+    { ngx_string("Host"), ngx_string("$proxy_host") },
+    { ngx_string("Connection"), ngx_string("close") },
+    { ngx_string("Content-Length"), ngx_string("$proxy_internal_body_length") },
+    { ngx_string("Transfer-Encoding"), ngx_string("$proxy_internal_chunked") },
+    { ngx_string("TE"), ngx_string("") },
+    { ngx_string("Keep-Alive"), ngx_string("") },
+    { ngx_string("Expect"), ngx_string("") },
+    { ngx_string("Upgrade"), ngx_string("") },
+    { ngx_string("If-Modified-Since"),
+      ngx_string("$upstream_cache_last_modified") },
+    { ngx_string("If-Unmodified-Since"), ngx_string("") },
+    { ngx_string("If-None-Match"), ngx_string("$upstream_cache_etag") },
+    { ngx_string("If-Match"), ngx_string("") },
+    { ngx_string("Range"), ngx_string("") },
+    { ngx_string("If-Range"), ngx_string("") },
+    { ngx_null_string, ngx_null_string }
+};
+
+#endif
+
+
+static ngx_http_variable_t  ngx_http_proxy_vars[] = {
+
+    { ngx_string("proxy_host"), NULL, ngx_http_proxy_host_variable, 0,
+      NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
+
+    { ngx_string("proxy_port"), NULL, ngx_http_proxy_port_variable, 0,
+      NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
+
+    { ngx_string("proxy_add_x_forwarded_for"), NULL,
+      ngx_http_proxy_add_x_forwarded_for_variable, 0, NGX_HTTP_VAR_NOHASH, 0 },
+
+#if 0
+    { ngx_string("proxy_add_via"), NULL, NULL, 0, NGX_HTTP_VAR_NOHASH, 0 },
+#endif
+
+    { ngx_string("proxy_internal_body_length"), NULL,
+      ngx_http_proxy_internal_body_length_variable, 0,
+      NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
+
+    { ngx_string("proxy_internal_chunked"), NULL,
+      ngx_http_proxy_internal_chunked_variable, 0,
+      NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
+
+      ngx_http_null_variable
+};
+
+
+static ngx_path_init_t  ngx_http_proxy_temp_path = {
+    ngx_string(NGX_HTTP_PROXY_TEMP_PATH), { 1, 2, 0 }
+};
+
+
+static ngx_int_t
+ngx_http_proxy_handler(ngx_http_request_t *r)
+{
+    ngx_int_t                    rc;
+    ngx_http_upstream_t         *u;
+    ngx_http_proxy_ctx_t        *ctx;
+    ngx_http_proxy_loc_conf_t   *plcf;
+#if (NGX_HTTP_CACHE)
+    ngx_http_proxy_main_conf_t  *pmcf;
+#endif
+
+    if (ngx_http_upstream_create(r) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_proxy_ctx_t));
+    if (ctx == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    ngx_http_set_ctx(r, ctx, ngx_http_proxy_module);
+
+    plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module);
+
+    u = r->upstream;
+
+    if (plcf->proxy_lengths == NULL) {
+        ctx->vars = plcf->vars;
+        u->schema = plcf->vars.schema;
+#if (NGX_HTTP_SSL)
+        u->ssl = (plcf->upstream.ssl != NULL);
+#endif
+
+    } else {
+        if (ngx_http_proxy_eval(r, ctx, plcf) != NGX_OK) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+    }
+
+    u->output.tag = (ngx_buf_tag_t) &ngx_http_proxy_module;
+
+    u->conf = &plcf->upstream;
+
+#if (NGX_HTTP_CACHE)
+    pmcf = ngx_http_get_module_main_conf(r, ngx_http_proxy_module);
+
+    u->caches = &pmcf->caches;
+    u->create_key = ngx_http_proxy_create_key;
+#endif
+
+    u->create_request = ngx_http_proxy_create_request;
+    u->reinit_request = ngx_http_proxy_reinit_request;
+    u->process_header = ngx_http_proxy_process_status_line;
+    u->abort_request = ngx_http_proxy_abort_request;
+    u->finalize_request = ngx_http_proxy_finalize_request;
+    r->state = 0;
+
+    if (plcf->redirects) {
+        u->rewrite_redirect = ngx_http_proxy_rewrite_redirect;
+    }
+
+    if (plcf->cookie_domains || plcf->cookie_paths) {
+        u->rewrite_cookie = ngx_http_proxy_rewrite_cookie;
+    }
+
+    u->buffering = plcf->upstream.buffering;
+
+    u->pipe = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t));
+    if (u->pipe == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    u->pipe->input_filter = ngx_http_proxy_copy_filter;
+    u->pipe->input_ctx = r;
+
+    u->input_filter_init = ngx_http_proxy_input_filter_init;
+    u->input_filter = ngx_http_proxy_non_buffered_copy_filter;
+    u->input_filter_ctx = r;
+
+    u->accel = 1;
+
+    if (!plcf->upstream.request_buffering
+        && plcf->body_values == NULL && plcf->upstream.pass_request_body
+        && (!r->headers_in.chunked
+            || plcf->http_version == NGX_HTTP_VERSION_11))
+    {
+        r->request_body_no_buffering = 1;
+    }
+
+    rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);
+
+    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+        return rc;
+    }
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_eval(ngx_http_request_t *r, ngx_http_proxy_ctx_t *ctx,
+    ngx_http_proxy_loc_conf_t *plcf)
+{
+    u_char               *p;
+    size_t                add;
+    u_short               port;
+    ngx_str_t             proxy;
+    ngx_url_t             url;
+    ngx_http_upstream_t  *u;
+
+    if (ngx_http_script_run(r, &proxy, plcf->proxy_lengths->elts, 0,
+                            plcf->proxy_values->elts)
+        == NULL)
+    {
+        return NGX_ERROR;
+    }
+
+    if (proxy.len > 7
+        && ngx_strncasecmp(proxy.data, (u_char *) "http://", 7) == 0)
+    {
+        add = 7;
+        port = 80;
+
+#if (NGX_HTTP_SSL)
+
+    } else if (proxy.len > 8
+               && ngx_strncasecmp(proxy.data, (u_char *) "https://", 8) == 0)
+    {
+        add = 8;
+        port = 443;
+        r->upstream->ssl = 1;
+
+#endif
+
+    } else {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "invalid URL prefix in \"%V\"", &proxy);
+        return NGX_ERROR;
+    }
+
+    u = r->upstream;
+
+    u->schema.len = add;
+    u->schema.data = proxy.data;
+
+    ngx_memzero(&url, sizeof(ngx_url_t));
+
+    url.url.len = proxy.len - add;
+    url.url.data = proxy.data + add;
+    url.default_port = port;
+    url.uri_part = 1;
+    url.no_resolve = 1;
+
+    if (ngx_parse_url(r->pool, &url) != NGX_OK) {
+        if (url.err) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "%s in upstream \"%V\"", url.err, &url.url);
+        }
+
+        return NGX_ERROR;
+    }
+
+    if (url.uri.len) {
+        if (url.uri.data[0] == '?') {
+            p = ngx_pnalloc(r->pool, url.uri.len + 1);
+            if (p == NULL) {
+                return NGX_ERROR;
+            }
+
+            *p++ = '/';
+            ngx_memcpy(p, url.uri.data, url.uri.len);
+
+            url.uri.len++;
+            url.uri.data = p - 1;
+        }
+    }
+
+    ctx->vars.key_start = u->schema;
+
+    ngx_http_proxy_set_vars(&url, &ctx->vars);
+
+    u->resolved = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t));
+    if (u->resolved == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (url.addrs) {
+        u->resolved->sockaddr = url.addrs[0].sockaddr;
+        u->resolved->socklen = url.addrs[0].socklen;
+        u->resolved->name = url.addrs[0].name;
+        u->resolved->naddrs = 1;
+    }
+
+    u->resolved->host = url.host;
+    u->resolved->port = (in_port_t) (url.no_port ? port : url.port);
+    u->resolved->no_port = url.no_port;
+
+    return NGX_OK;
+}
+
+
+#if (NGX_HTTP_CACHE)
+
+static ngx_int_t
+ngx_http_proxy_create_key(ngx_http_request_t *r)
+{
+    size_t                      len, loc_len;
+    u_char                     *p;
+    uintptr_t                   escape;
+    ngx_str_t                  *key;
+    ngx_http_upstream_t        *u;
+    ngx_http_proxy_ctx_t       *ctx;
+    ngx_http_proxy_loc_conf_t  *plcf;
+
+    u = r->upstream;
+
+    plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module);
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+    key = ngx_array_push(&r->cache->keys);
+    if (key == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (plcf->cache_key.value.data) {
+
+        if (ngx_http_complex_value(r, &plcf->cache_key, key) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        return NGX_OK;
+    }
+
+    *key = ctx->vars.key_start;
+
+    key = ngx_array_push(&r->cache->keys);
+    if (key == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (plcf->proxy_lengths && ctx->vars.uri.len) {
+
+        *key = ctx->vars.uri;
+        u->uri = ctx->vars.uri;
+
+        return NGX_OK;
+
+    } else if (ctx->vars.uri.len == 0 && r->valid_unparsed_uri) {
+        *key = r->unparsed_uri;
+        u->uri = r->unparsed_uri;
+
+        return NGX_OK;
+    }
+
+    loc_len = (r->valid_location && ctx->vars.uri.len) ? plcf->location.len : 0;
+
+    if (r->quoted_uri || r->space_in_uri || r->internal) {
+        escape = 2 * ngx_escape_uri(NULL, r->uri.data + loc_len,
+                                    r->uri.len - loc_len, NGX_ESCAPE_URI);
+    } else {
+        escape = 0;
+    }
+
+    len = ctx->vars.uri.len + r->uri.len - loc_len + escape
+          + sizeof("?") - 1 + r->args.len;
+
+    p = ngx_pnalloc(r->pool, len);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    key->data = p;
+
+    if (r->valid_location) {
+        p = ngx_copy(p, ctx->vars.uri.data, ctx->vars.uri.len);
+    }
+
+    if (escape) {
+        ngx_escape_uri(p, r->uri.data + loc_len,
+                       r->uri.len - loc_len, NGX_ESCAPE_URI);
+        p += r->uri.len - loc_len + escape;
+
+    } else {
+        p = ngx_copy(p, r->uri.data + loc_len, r->uri.len - loc_len);
+    }
+
+    if (r->args.len > 0) {
+        *p++ = '?';
+        p = ngx_copy(p, r->args.data, r->args.len);
+    }
+
+    key->len = p - key->data;
+    u->uri = *key;
+
+    return NGX_OK;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_proxy_create_request(ngx_http_request_t *r)
+{
+    size_t                        len, uri_len, loc_len, body_len,
+                                  key_len, val_len;
+    uintptr_t                     escape;
+    ngx_buf_t                    *b;
+    ngx_str_t                     method;
+    ngx_uint_t                    i, unparsed_uri;
+    ngx_chain_t                  *cl, *body;
+    ngx_list_part_t              *part;
+    ngx_table_elt_t              *header;
+    ngx_http_upstream_t          *u;
+    ngx_http_proxy_ctx_t         *ctx;
+    ngx_http_script_code_pt       code;
+    ngx_http_proxy_headers_t     *headers;
+    ngx_http_script_engine_t      e, le;
+    ngx_http_proxy_loc_conf_t    *plcf;
+    ngx_http_script_len_code_pt   lcode;
+
+    u = r->upstream;
+
+    plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module);
+
+#if (NGX_HTTP_CACHE)
+    headers = u->cacheable ? &plcf->headers_cache : &plcf->headers;
+#else
+    headers = &plcf->headers;
+#endif
+
+    if (u->method.len) {
+        /* HEAD was changed to GET to cache response */
+        method = u->method;
+
+    } else if (plcf->method) {
+        if (ngx_http_complex_value(r, plcf->method, &method) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+    } else {
+        method = r->method_name;
+    }
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+    if (method.len == 4
+        && ngx_strncasecmp(method.data, (u_char *) "HEAD", 4) == 0)
+    {
+        ctx->head = 1;
+    }
+
+    len = method.len + 1 + sizeof(ngx_http_proxy_version) - 1
+          + sizeof(CRLF) - 1;
+
+    escape = 0;
+    loc_len = 0;
+    unparsed_uri = 0;
+
+    if (plcf->proxy_lengths && ctx->vars.uri.len) {
+        uri_len = ctx->vars.uri.len;
+
+    } else if (ctx->vars.uri.len == 0 && r->valid_unparsed_uri) {
+        unparsed_uri = 1;
+        uri_len = r->unparsed_uri.len;
+
+    } else {
+        loc_len = (r->valid_location && ctx->vars.uri.len) ?
+                      plcf->location.len : 0;
+
+        if (r->quoted_uri || r->space_in_uri || r->internal) {
+            escape = 2 * ngx_escape_uri(NULL, r->uri.data + loc_len,
+                                        r->uri.len - loc_len, NGX_ESCAPE_URI);
+        }
+
+        uri_len = ctx->vars.uri.len + r->uri.len - loc_len + escape
+                  + sizeof("?") - 1 + r->args.len;
+    }
+
+    if (uri_len == 0) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "zero length URI to proxy");
+        return NGX_ERROR;
+    }
+
+    len += uri_len;
+
+    ngx_memzero(&le, sizeof(ngx_http_script_engine_t));
+
+    ngx_http_script_flush_no_cacheable_variables(r, plcf->body_flushes);
+    ngx_http_script_flush_no_cacheable_variables(r, headers->flushes);
+
+    if (plcf->body_lengths) {
+        le.ip = plcf->body_lengths->elts;
+        le.request = r;
+        le.flushed = 1;
+        body_len = 0;
+
+        while (*(uintptr_t *) le.ip) {
+            lcode = *(ngx_http_script_len_code_pt *) le.ip;
+            body_len += lcode(&le);
+        }
+
+        ctx->internal_body_length = body_len;
+        len += body_len;
+
+    } else if (r->headers_in.chunked && r->reading_body) {
+        ctx->internal_body_length = -1;
+        ctx->internal_chunked = 1;
+
+    } else {
+        ctx->internal_body_length = r->headers_in.content_length_n;
+    }
+
+    le.ip = headers->lengths->elts;
+    le.request = r;
+    le.flushed = 1;
+
+    while (*(uintptr_t *) le.ip) {
+
+        lcode = *(ngx_http_script_len_code_pt *) le.ip;
+        key_len = lcode(&le);
+
+        for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) {
+            lcode = *(ngx_http_script_len_code_pt *) le.ip;
+        }
+        le.ip += sizeof(uintptr_t);
+
+        if (val_len == 0) {
+            continue;
+        }
+
+        len += key_len + sizeof(": ") - 1 + val_len + sizeof(CRLF) - 1;
+    }
+
+
+    if (plcf->upstream.pass_request_headers) {
+        part = &r->headers_in.headers.part;
+        header = part->elts;
+
+        for (i = 0; /* void */; i++) {
+
+            if (i >= part->nelts) {
+                if (part->next == NULL) {
+                    break;
+                }
+
+                part = part->next;
+                header = part->elts;
+                i = 0;
+            }
+
+            if (ngx_hash_find(&headers->hash, header[i].hash,
+                              header[i].lowcase_key, header[i].key.len))
+            {
+                continue;
+            }
+
+            len += header[i].key.len + sizeof(": ") - 1
+                + header[i].value.len + sizeof(CRLF) - 1;
+        }
+    }
+
+
+    b = ngx_create_temp_buf(r->pool, len);
+    if (b == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl = ngx_alloc_chain_link(r->pool);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl->buf = b;
+
+
+    /* the request line */
+
+    b->last = ngx_copy(b->last, method.data, method.len);
+    *b->last++ = ' ';
+
+    u->uri.data = b->last;
+
+    if (plcf->proxy_lengths && ctx->vars.uri.len) {
+        b->last = ngx_copy(b->last, ctx->vars.uri.data, ctx->vars.uri.len);
+
+    } else if (unparsed_uri) {
+        b->last = ngx_copy(b->last, r->unparsed_uri.data, r->unparsed_uri.len);
+
+    } else {
+        if (r->valid_location) {
+            b->last = ngx_copy(b->last, ctx->vars.uri.data, ctx->vars.uri.len);
+        }
+
+        if (escape) {
+            ngx_escape_uri(b->last, r->uri.data + loc_len,
+                           r->uri.len - loc_len, NGX_ESCAPE_URI);
+            b->last += r->uri.len - loc_len + escape;
+
+        } else {
+            b->last = ngx_copy(b->last, r->uri.data + loc_len,
+                               r->uri.len - loc_len);
+        }
+
+        if (r->args.len > 0) {
+            *b->last++ = '?';
+            b->last = ngx_copy(b->last, r->args.data, r->args.len);
+        }
+    }
+
+    u->uri.len = b->last - u->uri.data;
+
+    if (plcf->http_version == NGX_HTTP_VERSION_11) {
+        b->last = ngx_cpymem(b->last, ngx_http_proxy_version_11,
+                             sizeof(ngx_http_proxy_version_11) - 1);
+
+    } else {
+        b->last = ngx_cpymem(b->last, ngx_http_proxy_version,
+                             sizeof(ngx_http_proxy_version) - 1);
+    }
+
+    ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
+
+    e.ip = headers->values->elts;
+    e.pos = b->last;
+    e.request = r;
+    e.flushed = 1;
+
+    le.ip = headers->lengths->elts;
+
+    while (*(uintptr_t *) le.ip) {
+
+        lcode = *(ngx_http_script_len_code_pt *) le.ip;
+        (void) lcode(&le);
+
+        for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) {
+            lcode = *(ngx_http_script_len_code_pt *) le.ip;
+        }
+        le.ip += sizeof(uintptr_t);
+
+        if (val_len == 0) {
+            e.skip = 1;
+
+            while (*(uintptr_t *) e.ip) {
+                code = *(ngx_http_script_code_pt *) e.ip;
+                code((ngx_http_script_engine_t *) &e);
+            }
+            e.ip += sizeof(uintptr_t);
+
+            e.skip = 0;
+
+            continue;
+        }
+
+        code = *(ngx_http_script_code_pt *) e.ip;
+        code((ngx_http_script_engine_t *) &e);
+
+        *e.pos++ = ':'; *e.pos++ = ' ';
+
+        while (*(uintptr_t *) e.ip) {
+            code = *(ngx_http_script_code_pt *) e.ip;
+            code((ngx_http_script_engine_t *) &e);
+        }
+        e.ip += sizeof(uintptr_t);
+
+        *e.pos++ = CR; *e.pos++ = LF;
+    }
+
+    b->last = e.pos;
+
+
+    if (plcf->upstream.pass_request_headers) {
+        part = &r->headers_in.headers.part;
+        header = part->elts;
+
+        for (i = 0; /* void */; i++) {
+
+            if (i >= part->nelts) {
+                if (part->next == NULL) {
+                    break;
+                }
+
+                part = part->next;
+                header = part->elts;
+                i = 0;
+            }
+
+            if (ngx_hash_find(&headers->hash, header[i].hash,
+                              header[i].lowcase_key, header[i].key.len))
+            {
+                continue;
+            }
+
+            b->last = ngx_copy(b->last, header[i].key.data, header[i].key.len);
+
+            *b->last++ = ':'; *b->last++ = ' ';
+
+            b->last = ngx_copy(b->last, header[i].value.data,
+                               header[i].value.len);
+
+            *b->last++ = CR; *b->last++ = LF;
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http proxy header: \"%V: %V\"",
+                           &header[i].key, &header[i].value);
+        }
+    }
+
+
+    /* add "\r\n" at the header end */
+    *b->last++ = CR; *b->last++ = LF;
+
+    if (plcf->body_values) {
+        e.ip = plcf->body_values->elts;
+        e.pos = b->last;
+        e.skip = 0;
+
+        while (*(uintptr_t *) e.ip) {
+            code = *(ngx_http_script_code_pt *) e.ip;
+            code((ngx_http_script_engine_t *) &e);
+        }
+
+        b->last = e.pos;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http proxy header:%N\"%*s\"",
+                   (size_t) (b->last - b->pos), b->pos);
+
+    if (r->request_body_no_buffering) {
+
+        u->request_bufs = cl;
+
+        if (ctx->internal_chunked) {
+            u->output.output_filter = ngx_http_proxy_body_output_filter;
+            u->output.filter_ctx = r;
+        }
+
+    } else if (plcf->body_values == NULL && plcf->upstream.pass_request_body) {
+
+        body = u->request_bufs;
+        u->request_bufs = cl;
+
+        while (body) {
+            b = ngx_alloc_buf(r->pool);
+            if (b == NULL) {
+                return NGX_ERROR;
+            }
+
+            ngx_memcpy(b, body->buf, sizeof(ngx_buf_t));
+
+            cl->next = ngx_alloc_chain_link(r->pool);
+            if (cl->next == NULL) {
+                return NGX_ERROR;
+            }
+
+            cl = cl->next;
+            cl->buf = b;
+
+            body = body->next;
+        }
+
+    } else {
+        u->request_bufs = cl;
+    }
+
+    b->flush = 1;
+    cl->next = NULL;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_reinit_request(ngx_http_request_t *r)
+{
+    ngx_http_proxy_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+    if (ctx == NULL) {
+        return NGX_OK;
+    }
+
+    ctx->status.code = 0;
+    ctx->status.count = 0;
+    ctx->status.start = NULL;
+    ctx->status.end = NULL;
+    ctx->chunked.state = 0;
+
+    r->upstream->process_header = ngx_http_proxy_process_status_line;
+    r->upstream->pipe->input_filter = ngx_http_proxy_copy_filter;
+    r->upstream->input_filter = ngx_http_proxy_non_buffered_copy_filter;
+    r->state = 0;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_body_output_filter(void *data, ngx_chain_t *in)
+{
+    ngx_http_request_t  *r = data;
+
+    off_t                  size;
+    u_char                *chunk;
+    ngx_int_t              rc;
+    ngx_buf_t             *b;
+    ngx_chain_t           *out, *cl, *tl, **ll, **fl;
+    ngx_http_proxy_ctx_t  *ctx;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "proxy output filter");
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+    if (in == NULL) {
+        out = in;
+        goto out;
+    }
+
+    out = NULL;
+    ll = &out;
+
+    if (!ctx->header_sent) {
+        /* first buffer contains headers, pass it unmodified */
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "proxy output header");
+
+        ctx->header_sent = 1;
+
+        tl = ngx_alloc_chain_link(r->pool);
+        if (tl == NULL) {
+            return NGX_ERROR;
+        }
+
+        tl->buf = in->buf;
+        *ll = tl;
+        ll = &tl->next;
+
+        in = in->next;
+
+        if (in == NULL) {
+            tl->next = NULL;
+            goto out;
+        }
+    }
+
+    size = 0;
+    cl = in;
+    fl = ll;
+
+    for ( ;; ) {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "proxy output chunk: %O", ngx_buf_size(cl->buf));
+
+        size += ngx_buf_size(cl->buf);
+
+        if (cl->buf->flush
+            || cl->buf->sync
+            || ngx_buf_in_memory(cl->buf)
+            || cl->buf->in_file)
+        {
+            tl = ngx_alloc_chain_link(r->pool);
+            if (tl == NULL) {
+                return NGX_ERROR;
+            }
+
+            tl->buf = cl->buf;
+            *ll = tl;
+            ll = &tl->next;
+        }
+
+        if (cl->next == NULL) {
+            break;
+        }
+
+        cl = cl->next;
+    }
+
+    if (size) {
+        tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+        if (tl == NULL) {
+            return NGX_ERROR;
+        }
+
+        b = tl->buf;
+        chunk = b->start;
+
+        if (chunk == NULL) {
+            /* the "0000000000000000" is 64-bit hexadecimal string */
+
+            chunk = ngx_palloc(r->pool, sizeof("0000000000000000" CRLF) - 1);
+            if (chunk == NULL) {
+                return NGX_ERROR;
+            }
+
+            b->start = chunk;
+            b->end = chunk + sizeof("0000000000000000" CRLF) - 1;
+        }
+
+        b->tag = (ngx_buf_tag_t) &ngx_http_proxy_body_output_filter;
+        b->memory = 0;
+        b->temporary = 1;
+        b->pos = chunk;
+        b->last = ngx_sprintf(chunk, "%xO" CRLF, size);
+
+        tl->next = *fl;
+        *fl = tl;
+    }
+
+    if (cl->buf->last_buf) {
+        tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+        if (tl == NULL) {
+            return NGX_ERROR;
+        }
+
+        b = tl->buf;
+
+        b->tag = (ngx_buf_tag_t) &ngx_http_proxy_body_output_filter;
+        b->temporary = 0;
+        b->memory = 1;
+        b->last_buf = 1;
+        b->pos = (u_char *) CRLF "0" CRLF CRLF;
+        b->last = b->pos + 7;
+
+        cl->buf->last_buf = 0;
+
+        *ll = tl;
+
+        if (size == 0) {
+            b->pos += 2;
+        }
+
+    } else if (size > 0) {
+        tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+        if (tl == NULL) {
+            return NGX_ERROR;
+        }
+
+        b = tl->buf;
+
+        b->tag = (ngx_buf_tag_t) &ngx_http_proxy_body_output_filter;
+        b->temporary = 0;
+        b->memory = 1;
+        b->pos = (u_char *) CRLF;
+        b->last = b->pos + 2;
+
+        *ll = tl;
+
+    } else {
+        *ll = NULL;
+    }
+
+out:
+
+    rc = ngx_chain_writer(&r->upstream->writer, out);
+
+    ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out,
+                            (ngx_buf_tag_t) &ngx_http_proxy_body_output_filter);
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_process_status_line(ngx_http_request_t *r)
+{
+    size_t                 len;
+    ngx_int_t              rc;
+    ngx_http_upstream_t   *u;
+    ngx_http_proxy_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    u = r->upstream;
+
+    rc = ngx_http_parse_status_line(r, &u->buffer, &ctx->status);
+
+    if (rc == NGX_AGAIN) {
+        return rc;
+    }
+
+    if (rc == NGX_ERROR) {
+
+#if (NGX_HTTP_CACHE)
+
+        if (r->cache) {
+            r->http_version = NGX_HTTP_VERSION_9;
+            return NGX_OK;
+        }
+
+#endif
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "upstream sent no valid HTTP/1.0 header");
+
+#if 0
+        if (u->accel) {
+            return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+        }
+#endif
+
+        r->http_version = NGX_HTTP_VERSION_9;
+        u->state->status = NGX_HTTP_OK;
+        u->headers_in.connection_close = 1;
+
+        return NGX_OK;
+    }
+
+    if (u->state && u->state->status == 0) {
+        u->state->status = ctx->status.code;
+    }
+
+    u->headers_in.status_n = ctx->status.code;
+
+    len = ctx->status.end - ctx->status.start;
+    u->headers_in.status_line.len = len;
+
+    u->headers_in.status_line.data = ngx_pnalloc(r->pool, len);
+    if (u->headers_in.status_line.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(u->headers_in.status_line.data, ctx->status.start, len);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http proxy status %ui \"%V\"",
+                   u->headers_in.status_n, &u->headers_in.status_line);
+
+    if (ctx->status.http_version < NGX_HTTP_VERSION_11) {
+        u->headers_in.connection_close = 1;
+    }
+
+    u->process_header = ngx_http_proxy_process_header;
+
+    return ngx_http_proxy_process_header(r);
+}
+
+
+static ngx_int_t
+ngx_http_proxy_process_header(ngx_http_request_t *r)
+{
+    ngx_int_t                       rc;
+    ngx_table_elt_t                *h;
+    ngx_http_upstream_t            *u;
+    ngx_http_proxy_ctx_t           *ctx;
+    ngx_http_upstream_header_t     *hh;
+    ngx_http_upstream_main_conf_t  *umcf;
+
+    umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);
+
+    for ( ;; ) {
+
+        rc = ngx_http_parse_header_line(r, &r->upstream->buffer, 1);
+
+        if (rc == NGX_OK) {
+
+            /* a header line has been parsed successfully */
+
+            h = ngx_list_push(&r->upstream->headers_in.headers);
+            if (h == NULL) {
+                return NGX_ERROR;
+            }
+
+            h->hash = r->header_hash;
+
+            h->key.len = r->header_name_end - r->header_name_start;
+            h->value.len = r->header_end - r->header_start;
+
+            h->key.data = ngx_pnalloc(r->pool,
+                               h->key.len + 1 + h->value.len + 1 + h->key.len);
+            if (h->key.data == NULL) {
+                h->hash = 0;
+                return NGX_ERROR;
+            }
+
+            h->value.data = h->key.data + h->key.len + 1;
+            h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1;
+
+            ngx_memcpy(h->key.data, r->header_name_start, h->key.len);
+            h->key.data[h->key.len] = '\0';
+            ngx_memcpy(h->value.data, r->header_start, h->value.len);
+            h->value.data[h->value.len] = '\0';
+
+            if (h->key.len == r->lowcase_index) {
+                ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);
+
+            } else {
+                ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
+            }
+
+            hh = ngx_hash_find(&umcf->headers_in_hash, h->hash,
+                               h->lowcase_key, h->key.len);
+
+            if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
+                return NGX_ERROR;
+            }
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http proxy header: \"%V: %V\"",
+                           &h->key, &h->value);
+
+            continue;
+        }
+
+        if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
+
+            /* a whole header has been parsed successfully */
+
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http proxy header done");
+
+            /*
+             * if no "Server" and "Date" in header line,
+             * then add the special empty headers
+             */
+
+            if (r->upstream->headers_in.server == NULL) {
+                h = ngx_list_push(&r->upstream->headers_in.headers);
+                if (h == NULL) {
+                    return NGX_ERROR;
+                }
+
+                h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash(
+                                    ngx_hash('s', 'e'), 'r'), 'v'), 'e'), 'r');
+
+                ngx_str_set(&h->key, "Server");
+                ngx_str_null(&h->value);
+                h->lowcase_key = (u_char *) "server";
+            }
+
+            if (r->upstream->headers_in.date == NULL) {
+                h = ngx_list_push(&r->upstream->headers_in.headers);
+                if (h == NULL) {
+                    return NGX_ERROR;
+                }
+
+                h->hash = ngx_hash(ngx_hash(ngx_hash('d', 'a'), 't'), 'e');
+
+                ngx_str_set(&h->key, "Date");
+                ngx_str_null(&h->value);
+                h->lowcase_key = (u_char *) "date";
+            }
+
+            /* clear content length if response is chunked */
+
+            u = r->upstream;
+
+            if (u->headers_in.chunked) {
+                u->headers_in.content_length_n = -1;
+            }
+
+            /*
+             * set u->keepalive if response has no body; this allows to keep
+             * connections alive in case of r->header_only or X-Accel-Redirect
+             */
+
+            ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+            if (u->headers_in.status_n == NGX_HTTP_NO_CONTENT
+                || u->headers_in.status_n == NGX_HTTP_NOT_MODIFIED
+                || ctx->head
+                || (!u->headers_in.chunked
+                    && u->headers_in.content_length_n == 0))
+            {
+                u->keepalive = !u->headers_in.connection_close;
+            }
+
+            if (u->headers_in.status_n == NGX_HTTP_SWITCHING_PROTOCOLS) {
+                u->keepalive = 0;
+
+                if (r->headers_in.upgrade) {
+                    u->upgrade = 1;
+                }
+            }
+
+            return NGX_OK;
+        }
+
+        if (rc == NGX_AGAIN) {
+            return NGX_AGAIN;
+        }
+
+        /* there was error while a header line parsing */
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "upstream sent invalid header");
+
+        return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+    }
+}
+
+
+static ngx_int_t
+ngx_http_proxy_input_filter_init(void *data)
+{
+    ngx_http_request_t    *r = data;
+    ngx_http_upstream_t   *u;
+    ngx_http_proxy_ctx_t  *ctx;
+
+    u = r->upstream;
+    ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http proxy filter init s:%ui h:%d c:%d l:%O",
+                   u->headers_in.status_n, ctx->head, u->headers_in.chunked,
+                   u->headers_in.content_length_n);
+
+    /* as per RFC2616, 4.4 Message Length */
+
+    if (u->headers_in.status_n == NGX_HTTP_NO_CONTENT
+        || u->headers_in.status_n == NGX_HTTP_NOT_MODIFIED
+        || ctx->head)
+    {
+        /* 1xx, 204, and 304 and replies to HEAD requests */
+        /* no 1xx since we don't send Expect and Upgrade */
+
+        u->pipe->length = 0;
+        u->length = 0;
+        u->keepalive = !u->headers_in.connection_close;
+
+    } else if (u->headers_in.chunked) {
+        /* chunked */
+
+        u->pipe->input_filter = ngx_http_proxy_chunked_filter;
+        u->pipe->length = 3; /* "0" LF LF */
+
+        u->input_filter = ngx_http_proxy_non_buffered_chunked_filter;
+        u->length = 1;
+
+    } else if (u->headers_in.content_length_n == 0) {
+        /* empty body: special case as filter won't be called */
+
+        u->pipe->length = 0;
+        u->length = 0;
+        u->keepalive = !u->headers_in.connection_close;
+
+    } else {
+        /* content length or connection close */
+
+        u->pipe->length = u->headers_in.content_length_n;
+        u->length = u->headers_in.content_length_n;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_copy_filter(ngx_event_pipe_t *p, ngx_buf_t *buf)
+{
+    ngx_buf_t           *b;
+    ngx_chain_t         *cl;
+    ngx_http_request_t  *r;
+
+    if (buf->pos == buf->last) {
+        return NGX_OK;
+    }
+
+    cl = ngx_chain_get_free_buf(p->pool, &p->free);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    b = cl->buf;
+
+    ngx_memcpy(b, buf, sizeof(ngx_buf_t));
+    b->shadow = buf;
+    b->tag = p->tag;
+    b->last_shadow = 1;
+    b->recycled = 1;
+    buf->shadow = b;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0, "input buf #%d", b->num);
+
+    if (p->in) {
+        *p->last_in = cl;
+    } else {
+        p->in = cl;
+    }
+    p->last_in = &cl->next;
+
+    if (p->length == -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;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_chunked_filter(ngx_event_pipe_t *p, ngx_buf_t *buf)
+{
+    ngx_int_t              rc;
+    ngx_buf_t             *b, **prev;
+    ngx_chain_t           *cl;
+    ngx_http_request_t    *r;
+    ngx_http_proxy_ctx_t  *ctx;
+
+    if (buf->pos == buf->last) {
+        return NGX_OK;
+    }
+
+    r = p->input_ctx;
+    ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    b = NULL;
+    prev = &buf->shadow;
+
+    for ( ;; ) {
+
+        rc = ngx_http_parse_chunked(r, buf, &ctx->chunked);
+
+        if (rc == NGX_OK) {
+
+            /* a chunk has been parsed successfully */
+
+            cl = ngx_chain_get_free_buf(p->pool, &p->free);
+            if (cl == NULL) {
+                return NGX_ERROR;
+            }
+
+            b = cl->buf;
+
+            ngx_memzero(b, sizeof(ngx_buf_t));
+
+            b->pos = buf->pos;
+            b->start = buf->start;
+            b->end = buf->end;
+            b->tag = p->tag;
+            b->temporary = 1;
+            b->recycled = 1;
+
+            *prev = b;
+            prev = &b->shadow;
+
+            if (p->in) {
+                *p->last_in = cl;
+            } else {
+                p->in = cl;
+            }
+            p->last_in = &cl->next;
+
+            /* STUB */ b->num = buf->num;
+
+            ngx_log_debug2(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                           "input buf #%d %p", b->num, b->pos);
+
+            if (buf->last - buf->pos >= ctx->chunked.size) {
+
+                buf->pos += (size_t) ctx->chunked.size;
+                b->last = buf->pos;
+                ctx->chunked.size = 0;
+
+                continue;
+            }
+
+            ctx->chunked.size -= buf->last - buf->pos;
+            buf->pos = buf->last;
+            b->last = buf->last;
+
+            continue;
+        }
+
+        if (rc == NGX_DONE) {
+
+            /* a whole response has been parsed successfully */
+
+            p->upstream_done = 1;
+            r->upstream->keepalive = !r->upstream->headers_in.connection_close;
+
+            break;
+        }
+
+        if (rc == NGX_AGAIN) {
+
+            /* set p->length, minimal amount of data we want to see */
+
+            p->length = ctx->chunked.length;
+
+            break;
+        }
+
+        /* invalid response */
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "upstream sent invalid chunked response");
+
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http proxy chunked state %ui, length %O",
+                   ctx->chunked.state, p->length);
+
+    if (b) {
+        b->shadow = buf;
+        b->last_shadow = 1;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                       "input buf %p %z", b->pos, b->last - b->pos);
+
+        return NGX_OK;
+    }
+
+    /* there is no data record in the buf, add it to free chain */
+
+    if (ngx_event_pipe_add_free_buf(p, buf) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_non_buffered_copy_filter(void *data, ssize_t bytes)
+{
+    ngx_http_request_t   *r = data;
+
+    ngx_buf_t            *b;
+    ngx_chain_t          *cl, **ll;
+    ngx_http_upstream_t  *u;
+
+    u = r->upstream;
+
+    for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) {
+        ll = &cl->next;
+    }
+
+    cl = ngx_chain_get_free_buf(r->pool, &u->free_bufs);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    *ll = cl;
+
+    cl->buf->flush = 1;
+    cl->buf->memory = 1;
+
+    b = &u->buffer;
+
+    cl->buf->pos = b->last;
+    b->last += bytes;
+    cl->buf->last = b->last;
+    cl->buf->tag = u->output.tag;
+
+    if (u->length == -1) {
+        return NGX_OK;
+    }
+
+    u->length -= bytes;
+
+    if (u->length == 0) {
+        u->keepalive = !u->headers_in.connection_close;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_non_buffered_chunked_filter(void *data, ssize_t bytes)
+{
+    ngx_http_request_t   *r = data;
+
+    ngx_int_t              rc;
+    ngx_buf_t             *b, *buf;
+    ngx_chain_t           *cl, **ll;
+    ngx_http_upstream_t   *u;
+    ngx_http_proxy_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    u = r->upstream;
+    buf = &u->buffer;
+
+    buf->pos = buf->last;
+    buf->last += bytes;
+
+    for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) {
+        ll = &cl->next;
+    }
+
+    for ( ;; ) {
+
+        rc = ngx_http_parse_chunked(r, buf, &ctx->chunked);
+
+        if (rc == NGX_OK) {
+
+            /* a chunk has been parsed successfully */
+
+            cl = ngx_chain_get_free_buf(r->pool, &u->free_bufs);
+            if (cl == NULL) {
+                return NGX_ERROR;
+            }
+
+            *ll = cl;
+            ll = &cl->next;
+
+            b = cl->buf;
+
+            b->flush = 1;
+            b->memory = 1;
+
+            b->pos = buf->pos;
+            b->tag = u->output.tag;
+
+            if (buf->last - buf->pos >= ctx->chunked.size) {
+                buf->pos += (size_t) ctx->chunked.size;
+                b->last = buf->pos;
+                ctx->chunked.size = 0;
+
+            } else {
+                ctx->chunked.size -= buf->last - buf->pos;
+                buf->pos = buf->last;
+                b->last = buf->last;
+            }
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http proxy out buf %p %z",
+                           b->pos, b->last - b->pos);
+
+            continue;
+        }
+
+        if (rc == NGX_DONE) {
+
+            /* a whole response has been parsed successfully */
+
+            u->keepalive = !u->headers_in.connection_close;
+            u->length = 0;
+
+            break;
+        }
+
+        if (rc == NGX_AGAIN) {
+            break;
+        }
+
+        /* invalid response */
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "upstream sent invalid chunked response");
+
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_proxy_abort_request(ngx_http_request_t *r)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "abort http proxy request");
+
+    return;
+}
+
+
+static void
+ngx_http_proxy_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "finalize http proxy request");
+
+    return;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_host_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_http_proxy_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+    if (ctx == NULL) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    v->len = ctx->vars.host_header.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = ctx->vars.host_header.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_port_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_http_proxy_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+    if (ctx == NULL) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    v->len = ctx->vars.port.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = ctx->vars.port.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_add_x_forwarded_for_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    size_t             len;
+    u_char            *p;
+    ngx_uint_t         i, n;
+    ngx_table_elt_t  **h;
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    n = r->headers_in.x_forwarded_for.nelts;
+    h = r->headers_in.x_forwarded_for.elts;
+
+    len = 0;
+
+    for (i = 0; i < n; i++) {
+        len += h[i]->value.len + sizeof(", ") - 1;
+    }
+
+    if (len == 0) {
+        v->len = r->connection->addr_text.len;
+        v->data = r->connection->addr_text.data;
+        return NGX_OK;
+    }
+
+    len += r->connection->addr_text.len;
+
+    p = ngx_pnalloc(r->pool, len);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->len = len;
+    v->data = p;
+
+    for (i = 0; i < n; i++) {
+        p = ngx_copy(p, h[i]->value.data, h[i]->value.len);
+        *p++ = ','; *p++ = ' ';
+    }
+
+    ngx_memcpy(p, r->connection->addr_text.data, r->connection->addr_text.len);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_internal_body_length_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_http_proxy_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+    if (ctx == NULL || ctx->internal_body_length < 0) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    v->data = ngx_pnalloc(r->pool, NGX_OFF_T_LEN);
+
+    if (v->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->len = ngx_sprintf(v->data, "%O", ctx->internal_body_length) - v->data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_internal_chunked_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_http_proxy_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+    if (ctx == NULL || !ctx->internal_chunked) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    v->data = (u_char *) "chunked";
+    v->len = sizeof("chunked") - 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_rewrite_redirect(ngx_http_request_t *r, ngx_table_elt_t *h,
+    size_t prefix)
+{
+    size_t                      len;
+    ngx_int_t                   rc;
+    ngx_uint_t                  i;
+    ngx_http_proxy_rewrite_t   *pr;
+    ngx_http_proxy_loc_conf_t  *plcf;
+
+    plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module);
+
+    pr = plcf->redirects->elts;
+
+    if (pr == NULL) {
+        return NGX_DECLINED;
+    }
+
+    len = h->value.len - prefix;
+
+    for (i = 0; i < plcf->redirects->nelts; i++) {
+        rc = pr[i].handler(r, h, prefix, len, &pr[i]);
+
+        if (rc != NGX_DECLINED) {
+            return rc;
+        }
+    }
+
+    return NGX_DECLINED;
+}
+
+
+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;
+    ngx_int_t                   rc, rv;
+    ngx_http_proxy_loc_conf_t  *plcf;
+
+    p = (u_char *) ngx_strchr(h->value.data, ';');
+    if (p == NULL) {
+        return NGX_DECLINED;
+    }
+
+    prefix = p + 1 - h->value.data;
+
+    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);
+
+        if (p) {
+            rc = ngx_http_proxy_rewrite_cookie_value(r, h, p + 7,
+                                                     plcf->cookie_domains);
+            if (rc == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            if (rc != NGX_DECLINED) {
+                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,
+                                                     plcf->cookie_paths);
+            if (rc == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            if (rc != NGX_DECLINED) {
+                rv = rc;
+            }
+        }
+    }
+
+    return rv;
+}
+
+
+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)
+{
+    size_t                     len, prefix;
+    u_char                    *p;
+    ngx_int_t                  rc;
+    ngx_uint_t                 i;
+    ngx_http_proxy_rewrite_t  *pr;
+
+    prefix = value - h->value.data;
+
+    p = (u_char *) ngx_strchr(value, ';');
+
+    len = p ? (size_t) (p - value) : (h->value.len - prefix);
+
+    pr = rewrites->elts;
+
+    for (i = 0; i < rewrites->nelts; i++) {
+        rc = pr[i].handler(r, h, prefix, len, &pr[i]);
+
+        if (rc != NGX_DECLINED) {
+            return rc;
+        }
+    }
+
+    return NGX_DECLINED;
+}
+
+
+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_str_t  pattern, replacement;
+
+    if (ngx_http_complex_value(r, &pr->pattern.complex, &pattern) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (pattern.len > len
+        || ngx_rstrncmp(h->value.data + prefix, pattern.data,
+                        pattern.len) != 0)
+    {
+        return NGX_DECLINED;
+    }
+
+    if (ngx_http_complex_value(r, &pr->replacement, &replacement) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    return ngx_http_proxy_rewrite(r, h, 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,
+    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;
+
+    if (ngx_http_regex_exec(r, pr->pattern.regex, &pattern) != NGX_OK) {
+        return NGX_DECLINED;
+    }
+
+    if (ngx_http_complex_value(r, &pr->replacement, &replacement) != NGX_OK) {
+        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);
+}
+
+#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)
+{
+    u_char     *p;
+    ngx_str_t   pattern, replacement;
+
+    if (ngx_http_complex_value(r, &pr->pattern.complex, &pattern) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    p = h->value.data + prefix;
+
+    if (p[0] == '.') {
+        p++;
+        prefix++;
+        len--;
+    }
+
+    if (pattern.len != len || ngx_rstrncasecmp(pattern.data, p, len) != 0) {
+        return NGX_DECLINED;
+    }
+
+    if (ngx_http_complex_value(r, &pr->replacement, &replacement) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    return ngx_http_proxy_rewrite(r, h, prefix, len, &replacement);
+}
+
+
+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)
+{
+    u_char  *p, *data;
+    size_t   new_len;
+
+    new_len = replacement->len + h->value.len - len;
+
+    if (replacement->len > len) {
+
+        data = ngx_pnalloc(r->pool, new_len + 1);
+        if (data == NULL) {
+            return NGX_ERROR;
+        }
+
+        p = ngx_copy(data, h->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);
+
+        h->value.data = data;
+
+    } else {
+        p = ngx_copy(h->value.data + prefix, replacement->data,
+                     replacement->len);
+
+        ngx_memmove(p, h->value.data + prefix + len,
+                    h->value.len - len - prefix + 1);
+    }
+
+    h->value.len = new_len;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_add_variables(ngx_conf_t *cf)
+{
+    ngx_http_variable_t  *var, *v;
+
+    for (v = ngx_http_proxy_vars; v->name.len; v++) {
+        var = ngx_http_add_variable(cf, &v->name, v->flags);
+        if (var == NULL) {
+            return NGX_ERROR;
+        }
+
+        var->get_handler = v->get_handler;
+        var->data = v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_proxy_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_http_proxy_main_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_proxy_main_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+#if (NGX_HTTP_CACHE)
+    if (ngx_array_init(&conf->caches, cf->pool, 4,
+                       sizeof(ngx_http_file_cache_t *))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+#endif
+
+    return conf;
+}
+
+
+static void *
+ngx_http_proxy_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_proxy_loc_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_proxy_loc_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->upstream.bufs.num = 0;
+     *     conf->upstream.ignore_headers = 0;
+     *     conf->upstream.next_upstream = 0;
+     *     conf->upstream.cache_zone = NULL;
+     *     conf->upstream.cache_use_stale = 0;
+     *     conf->upstream.cache_methods = 0;
+     *     conf->upstream.temp_path = NULL;
+     *     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 };
+     *     conf->headers_cache.lengths = NULL;
+     *     conf->headers_cache.values = NULL;
+     *     conf->headers_cache.hash = { NULL, 0 };
+     *     conf->body_lengths = NULL;
+     *     conf->body_values = NULL;
+     *     conf->body_source = { 0, NULL };
+     *     conf->redirects = NULL;
+     *     conf->ssl = 0;
+     *     conf->ssl_protocols = 0;
+     *     conf->ssl_ciphers = { 0, NULL };
+     *     conf->ssl_trusted_certificate = { 0, NULL };
+     *     conf->ssl_crl = { 0, NULL };
+     *     conf->ssl_certificate = { 0, NULL };
+     *     conf->ssl_certificate_key = { 0, NULL };
+     */
+
+    conf->upstream.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;
+    conf->upstream.force_ranges = NGX_CONF_UNSET;
+
+    conf->upstream.local = NGX_CONF_UNSET_PTR;
+
+    conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.next_upstream_timeout = NGX_CONF_UNSET_MSEC;
+
+    conf->upstream.send_lowat = NGX_CONF_UNSET_SIZE;
+    conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE;
+    conf->upstream.limit_rate = NGX_CONF_UNSET_SIZE;
+
+    conf->upstream.busy_buffers_size_conf = NGX_CONF_UNSET_SIZE;
+    conf->upstream.max_temp_file_size_conf = NGX_CONF_UNSET_SIZE;
+    conf->upstream.temp_file_write_size_conf = NGX_CONF_UNSET_SIZE;
+
+    conf->upstream.pass_request_headers = NGX_CONF_UNSET;
+    conf->upstream.pass_request_body = NGX_CONF_UNSET;
+
+#if (NGX_HTTP_CACHE)
+    conf->upstream.cache = NGX_CONF_UNSET;
+    conf->upstream.cache_min_uses = NGX_CONF_UNSET_UINT;
+    conf->upstream.cache_max_range_offset = NGX_CONF_UNSET;
+    conf->upstream.cache_bypass = NGX_CONF_UNSET_PTR;
+    conf->upstream.no_cache = NGX_CONF_UNSET_PTR;
+    conf->upstream.cache_valid = NGX_CONF_UNSET_PTR;
+    conf->upstream.cache_lock = NGX_CONF_UNSET;
+    conf->upstream.cache_lock_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.cache_lock_age = NGX_CONF_UNSET_MSEC;
+    conf->upstream.cache_revalidate = NGX_CONF_UNSET;
+    conf->upstream.cache_convert_head = NGX_CONF_UNSET;
+    conf->upstream.cache_background_update = NGX_CONF_UNSET;
+#endif
+
+    conf->upstream.hide_headers = NGX_CONF_UNSET_PTR;
+    conf->upstream.pass_headers = NGX_CONF_UNSET_PTR;
+
+    conf->upstream.intercept_errors = NGX_CONF_UNSET;
+
+#if (NGX_HTTP_SSL)
+    conf->upstream.ssl_session_reuse = NGX_CONF_UNSET;
+    conf->upstream.ssl_server_name = NGX_CONF_UNSET;
+    conf->upstream.ssl_verify = NGX_CONF_UNSET;
+    conf->ssl_verify_depth = NGX_CONF_UNSET_UINT;
+    conf->ssl_passwords = NGX_CONF_UNSET_PTR;
+#endif
+
+    /* "proxy_cyclic_temp_file" is disabled */
+    conf->upstream.cyclic_temp_file = 0;
+
+    conf->redirect = NGX_CONF_UNSET;
+    conf->upstream.change_buffering = 1;
+
+    conf->cookie_domains = NGX_CONF_UNSET_PTR;
+    conf->cookie_paths = NGX_CONF_UNSET_PTR;
+
+    conf->http_version = NGX_CONF_UNSET_UINT;
+
+    conf->headers_hash_max_size = NGX_CONF_UNSET_UINT;
+    conf->headers_hash_bucket_size = NGX_CONF_UNSET_UINT;
+
+    ngx_str_set(&conf->upstream.module, "proxy");
+
+    return conf;
+}
+
+
+static char *
+ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_proxy_loc_conf_t *prev = parent;
+    ngx_http_proxy_loc_conf_t *conf = child;
+
+    u_char                     *p;
+    size_t                      size;
+    ngx_int_t                   rc;
+    ngx_hash_init_t             hash;
+    ngx_http_core_loc_conf_t   *clcf;
+    ngx_http_proxy_rewrite_t   *pr;
+    ngx_http_script_compile_t   sc;
+
+#if (NGX_HTTP_CACHE)
+
+    if (conf->upstream.store > 0) {
+        conf->upstream.cache = 0;
+    }
+
+    if (conf->upstream.cache > 0) {
+        conf->upstream.store = 0;
+    }
+
+#endif
+
+    if (conf->upstream.store == NGX_CONF_UNSET) {
+        ngx_conf_merge_value(conf->upstream.store,
+                              prev->upstream.store, 0);
+
+        conf->upstream.store_lengths = prev->upstream.store_lengths;
+        conf->upstream.store_values = prev->upstream.store_values;
+    }
+
+    ngx_conf_merge_uint_value(conf->upstream.store_access,
+                              prev->upstream.store_access, 0600);
+
+    ngx_conf_merge_uint_value(conf->upstream.next_upstream_tries,
+                              prev->upstream.next_upstream_tries, 0);
+
+    ngx_conf_merge_value(conf->upstream.buffering,
+                              prev->upstream.buffering, 1);
+
+    ngx_conf_merge_value(conf->upstream.request_buffering,
+                              prev->upstream.request_buffering, 1);
+
+    ngx_conf_merge_value(conf->upstream.ignore_client_abort,
+                              prev->upstream.ignore_client_abort, 0);
+
+    ngx_conf_merge_value(conf->upstream.force_ranges,
+                              prev->upstream.force_ranges, 0);
+
+    ngx_conf_merge_ptr_value(conf->upstream.local,
+                              prev->upstream.local, NULL);
+
+    ngx_conf_merge_msec_value(conf->upstream.connect_timeout,
+                              prev->upstream.connect_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->upstream.send_timeout,
+                              prev->upstream.send_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->upstream.read_timeout,
+                              prev->upstream.read_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->upstream.next_upstream_timeout,
+                              prev->upstream.next_upstream_timeout, 0);
+
+    ngx_conf_merge_size_value(conf->upstream.send_lowat,
+                              prev->upstream.send_lowat, 0);
+
+    ngx_conf_merge_size_value(conf->upstream.buffer_size,
+                              prev->upstream.buffer_size,
+                              (size_t) ngx_pagesize);
+
+    ngx_conf_merge_size_value(conf->upstream.limit_rate,
+                              prev->upstream.limit_rate, 0);
+
+    ngx_conf_merge_bufs_value(conf->upstream.bufs, prev->upstream.bufs,
+                              8, ngx_pagesize);
+
+    if (conf->upstream.bufs.num < 2) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "there must be at least 2 \"proxy_buffers\"");
+        return NGX_CONF_ERROR;
+    }
+
+
+    size = conf->upstream.buffer_size;
+    if (size < conf->upstream.bufs.size) {
+        size = conf->upstream.bufs.size;
+    }
+
+
+    ngx_conf_merge_size_value(conf->upstream.busy_buffers_size_conf,
+                              prev->upstream.busy_buffers_size_conf,
+                              NGX_CONF_UNSET_SIZE);
+
+    if (conf->upstream.busy_buffers_size_conf == NGX_CONF_UNSET_SIZE) {
+        conf->upstream.busy_buffers_size = 2 * size;
+    } else {
+        conf->upstream.busy_buffers_size =
+                                         conf->upstream.busy_buffers_size_conf;
+    }
+
+    if (conf->upstream.busy_buffers_size < size) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+             "\"proxy_busy_buffers_size\" must be equal to or greater than "
+             "the maximum of the value of \"proxy_buffer_size\" and "
+             "one of the \"proxy_buffers\"");
+
+        return NGX_CONF_ERROR;
+    }
+
+    if (conf->upstream.busy_buffers_size
+        > (conf->upstream.bufs.num - 1) * conf->upstream.bufs.size)
+    {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+             "\"proxy_busy_buffers_size\" must be less than "
+             "the size of all \"proxy_buffers\" minus one buffer");
+
+        return NGX_CONF_ERROR;
+    }
+
+
+    ngx_conf_merge_size_value(conf->upstream.temp_file_write_size_conf,
+                              prev->upstream.temp_file_write_size_conf,
+                              NGX_CONF_UNSET_SIZE);
+
+    if (conf->upstream.temp_file_write_size_conf == NGX_CONF_UNSET_SIZE) {
+        conf->upstream.temp_file_write_size = 2 * size;
+    } else {
+        conf->upstream.temp_file_write_size =
+                                      conf->upstream.temp_file_write_size_conf;
+    }
+
+    if (conf->upstream.temp_file_write_size < size) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+             "\"proxy_temp_file_write_size\" must be equal to or greater "
+             "than the maximum of the value of \"proxy_buffer_size\" and "
+             "one of the \"proxy_buffers\"");
+
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_conf_merge_size_value(conf->upstream.max_temp_file_size_conf,
+                              prev->upstream.max_temp_file_size_conf,
+                              NGX_CONF_UNSET_SIZE);
+
+    if (conf->upstream.max_temp_file_size_conf == NGX_CONF_UNSET_SIZE) {
+        conf->upstream.max_temp_file_size = 1024 * 1024 * 1024;
+    } else {
+        conf->upstream.max_temp_file_size =
+                                        conf->upstream.max_temp_file_size_conf;
+    }
+
+    if (conf->upstream.max_temp_file_size != 0
+        && conf->upstream.max_temp_file_size < size)
+    {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+             "\"proxy_max_temp_file_size\" must be equal to zero to disable "
+             "temporary files usage or must be equal to or greater than "
+             "the maximum of the value of \"proxy_buffer_size\" and "
+             "one of the \"proxy_buffers\"");
+
+        return NGX_CONF_ERROR;
+    }
+
+
+    ngx_conf_merge_bitmask_value(conf->upstream.ignore_headers,
+                              prev->upstream.ignore_headers,
+                              NGX_CONF_BITMASK_SET);
+
+
+    ngx_conf_merge_bitmask_value(conf->upstream.next_upstream,
+                              prev->upstream.next_upstream,
+                              (NGX_CONF_BITMASK_SET
+                               |NGX_HTTP_UPSTREAM_FT_ERROR
+                               |NGX_HTTP_UPSTREAM_FT_TIMEOUT));
+
+    if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) {
+        conf->upstream.next_upstream = NGX_CONF_BITMASK_SET
+                                       |NGX_HTTP_UPSTREAM_FT_OFF;
+    }
+
+    if (ngx_conf_merge_path_value(cf, &conf->upstream.temp_path,
+                              prev->upstream.temp_path,
+                              &ngx_http_proxy_temp_path)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+
+#if (NGX_HTTP_CACHE)
+
+    if (conf->upstream.cache == NGX_CONF_UNSET) {
+        ngx_conf_merge_value(conf->upstream.cache,
+                              prev->upstream.cache, 0);
+
+        conf->upstream.cache_zone = prev->upstream.cache_zone;
+        conf->upstream.cache_value = prev->upstream.cache_value;
+    }
+
+    if (conf->upstream.cache_zone && conf->upstream.cache_zone->data == NULL) {
+        ngx_shm_zone_t  *shm_zone;
+
+        shm_zone = conf->upstream.cache_zone;
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"proxy_cache\" zone \"%V\" is unknown",
+                           &shm_zone->shm.name);
+
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_conf_merge_uint_value(conf->upstream.cache_min_uses,
+                              prev->upstream.cache_min_uses, 1);
+
+    ngx_conf_merge_off_value(conf->upstream.cache_max_range_offset,
+                              prev->upstream.cache_max_range_offset,
+                              NGX_MAX_OFF_T_VALUE);
+
+    ngx_conf_merge_bitmask_value(conf->upstream.cache_use_stale,
+                              prev->upstream.cache_use_stale,
+                              (NGX_CONF_BITMASK_SET
+                               |NGX_HTTP_UPSTREAM_FT_OFF));
+
+    if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_OFF) {
+        conf->upstream.cache_use_stale = NGX_CONF_BITMASK_SET
+                                         |NGX_HTTP_UPSTREAM_FT_OFF;
+    }
+
+    if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_ERROR) {
+        conf->upstream.cache_use_stale |= NGX_HTTP_UPSTREAM_FT_NOLIVE;
+    }
+
+    if (conf->upstream.cache_methods == 0) {
+        conf->upstream.cache_methods = prev->upstream.cache_methods;
+    }
+
+    conf->upstream.cache_methods |= NGX_HTTP_GET|NGX_HTTP_HEAD;
+
+    ngx_conf_merge_ptr_value(conf->upstream.cache_bypass,
+                             prev->upstream.cache_bypass, NULL);
+
+    ngx_conf_merge_ptr_value(conf->upstream.no_cache,
+                             prev->upstream.no_cache, NULL);
+
+    ngx_conf_merge_ptr_value(conf->upstream.cache_valid,
+                             prev->upstream.cache_valid, NULL);
+
+    if (conf->cache_key.value.data == NULL) {
+        conf->cache_key = prev->cache_key;
+    }
+
+    ngx_conf_merge_value(conf->upstream.cache_lock,
+                              prev->upstream.cache_lock, 0);
+
+    ngx_conf_merge_msec_value(conf->upstream.cache_lock_timeout,
+                              prev->upstream.cache_lock_timeout, 5000);
+
+    ngx_conf_merge_msec_value(conf->upstream.cache_lock_age,
+                              prev->upstream.cache_lock_age, 5000);
+
+    ngx_conf_merge_value(conf->upstream.cache_revalidate,
+                              prev->upstream.cache_revalidate, 0);
+
+    ngx_conf_merge_value(conf->upstream.cache_convert_head,
+                              prev->upstream.cache_convert_head, 1);
+
+    ngx_conf_merge_value(conf->upstream.cache_background_update,
+                              prev->upstream.cache_background_update, 0);
+
+#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,
+                              prev->upstream.pass_request_body, 1);
+
+    ngx_conf_merge_value(conf->upstream.intercept_errors,
+                              prev->upstream.intercept_errors, 0);
+
+#if (NGX_HTTP_SSL)
+
+    ngx_conf_merge_value(conf->upstream.ssl_session_reuse,
+                              prev->upstream.ssl_session_reuse, 1);
+
+    ngx_conf_merge_bitmask_value(conf->ssl_protocols, prev->ssl_protocols,
+                                 (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1
+                                  |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2));
+
+    ngx_conf_merge_str_value(conf->ssl_ciphers, prev->ssl_ciphers,
+                             "DEFAULT");
+
+    if (conf->upstream.ssl_name == NULL) {
+        conf->upstream.ssl_name = prev->upstream.ssl_name;
+    }
+
+    ngx_conf_merge_value(conf->upstream.ssl_server_name,
+                              prev->upstream.ssl_server_name, 0);
+    ngx_conf_merge_value(conf->upstream.ssl_verify,
+                              prev->upstream.ssl_verify, 0);
+    ngx_conf_merge_uint_value(conf->ssl_verify_depth,
+                              prev->ssl_verify_depth, 1);
+    ngx_conf_merge_str_value(conf->ssl_trusted_certificate,
+                              prev->ssl_trusted_certificate, "");
+    ngx_conf_merge_str_value(conf->ssl_crl, prev->ssl_crl, "");
+
+    ngx_conf_merge_str_value(conf->ssl_certificate,
+                              prev->ssl_certificate, "");
+    ngx_conf_merge_str_value(conf->ssl_certificate_key,
+                              prev->ssl_certificate_key, "");
+    ngx_conf_merge_ptr_value(conf->ssl_passwords, prev->ssl_passwords, NULL);
+
+    if (conf->ssl && ngx_http_proxy_set_ssl(cf, conf) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+#endif
+
+    ngx_conf_merge_value(conf->redirect, prev->redirect, 1);
+
+    if (conf->redirect) {
+
+        if (conf->redirects == NULL) {
+            conf->redirects = prev->redirects;
+        }
+
+        if (conf->redirects == NULL && conf->url.data) {
+
+            conf->redirects = ngx_array_create(cf->pool, 1,
+                                             sizeof(ngx_http_proxy_rewrite_t));
+            if (conf->redirects == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            pr = ngx_array_push(conf->redirects);
+            if (pr == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            ngx_memzero(&pr->pattern.complex,
+                        sizeof(ngx_http_complex_value_t));
+
+            ngx_memzero(&pr->replacement, sizeof(ngx_http_complex_value_t));
+
+            pr->handler = ngx_http_proxy_rewrite_complex_handler;
+
+            if (conf->vars.uri.len) {
+                pr->pattern.complex.value = conf->url;
+                pr->replacement.value = conf->location;
+
+            } else {
+                pr->pattern.complex.value.len = conf->url.len
+                                                + sizeof("/") - 1;
+
+                p = ngx_pnalloc(cf->pool, pr->pattern.complex.value.len);
+                if (p == NULL) {
+                    return NGX_CONF_ERROR;
+                }
+
+                pr->pattern.complex.value.data = p;
+
+                p = ngx_cpymem(p, conf->url.data, conf->url.len);
+                *p = '/';
+
+                ngx_str_set(&pr->replacement.value, "/");
+            }
+        }
+    }
+
+    ngx_conf_merge_ptr_value(conf->cookie_domains, prev->cookie_domains, NULL);
+
+    ngx_conf_merge_ptr_value(conf->cookie_paths, prev->cookie_paths, NULL);
+
+    ngx_conf_merge_uint_value(conf->http_version, prev->http_version,
+                              NGX_HTTP_VERSION_10);
+
+    ngx_conf_merge_uint_value(conf->headers_hash_max_size,
+                              prev->headers_hash_max_size, 512);
+
+    ngx_conf_merge_uint_value(conf->headers_hash_bucket_size,
+                              prev->headers_hash_bucket_size, 64);
+
+    conf->headers_hash_bucket_size = ngx_align(conf->headers_hash_bucket_size,
+                                               ngx_cacheline_size);
+
+    hash.max_size = conf->headers_hash_max_size;
+    hash.bucket_size = conf->headers_hash_bucket_size;
+    hash.name = "proxy_headers_hash";
+
+    if (ngx_http_upstream_hide_headers_hash(cf, &conf->upstream,
+            &prev->upstream, ngx_http_proxy_hide_headers, &hash)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+    if (clcf->noname
+        && conf->upstream.upstream == NULL && conf->proxy_lengths == NULL)
+    {
+        conf->upstream.upstream = prev->upstream.upstream;
+        conf->location = prev->location;
+        conf->vars = prev->vars;
+
+        conf->proxy_lengths = prev->proxy_lengths;
+        conf->proxy_values = prev->proxy_values;
+
+#if (NGX_HTTP_SSL)
+        conf->upstream.ssl = prev->upstream.ssl;
+#endif
+    }
+
+    if (clcf->lmt_excpt && clcf->handler == NULL
+        && (conf->upstream.upstream || conf->proxy_lengths))
+    {
+        clcf->handler = ngx_http_proxy_handler;
+    }
+
+    if (conf->body_source.data == NULL) {
+        conf->body_flushes = prev->body_flushes;
+        conf->body_source = prev->body_source;
+        conf->body_lengths = prev->body_lengths;
+        conf->body_values = prev->body_values;
+    }
+
+    if (conf->body_source.data && conf->body_lengths == NULL) {
+
+        ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+        sc.cf = cf;
+        sc.source = &conf->body_source;
+        sc.flushes = &conf->body_flushes;
+        sc.lengths = &conf->body_lengths;
+        sc.values = &conf->body_values;
+        sc.complete_lengths = 1;
+        sc.complete_values = 1;
+
+        if (ngx_http_script_compile(&sc) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (conf->headers_source == NULL) {
+        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,
+                                     ngx_http_proxy_headers);
+    if (rc != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+#if (NGX_HTTP_CACHE)
+
+    if (conf->upstream.cache) {
+        rc = ngx_http_proxy_init_headers(cf, conf, &conf->headers_cache,
+                                         ngx_http_proxy_cache_headers);
+        if (rc != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+#endif
+
+    /*
+     * special handling to preserve conf->headers in the "http" section
+     * to inherit it to all servers
+     */
+
+    if (prev->headers.hash.buckets == NULL
+        && conf->headers_source == prev->headers_source)
+    {
+        prev->headers = conf->headers;
+#if (NGX_HTTP_CACHE)
+        prev->headers_cache = conf->headers_cache;
+#endif
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_init_headers(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *conf,
+    ngx_http_proxy_headers_t *headers, ngx_keyval_t *default_headers)
+{
+    u_char                       *p;
+    size_t                        size;
+    uintptr_t                    *code;
+    ngx_uint_t                    i;
+    ngx_array_t                   headers_names, headers_merged;
+    ngx_keyval_t                 *src, *s, *h;
+    ngx_hash_key_t               *hk;
+    ngx_hash_init_t               hash;
+    ngx_http_script_compile_t     sc;
+    ngx_http_script_copy_code_t  *copy;
+
+    if (headers->hash.buckets) {
+        return NGX_OK;
+    }
+
+    if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&headers_merged, cf->temp_pool, 4, sizeof(ngx_keyval_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    headers->lengths = ngx_array_create(cf->pool, 64, 1);
+    if (headers->lengths == NULL) {
+        return NGX_ERROR;
+    }
+
+    headers->values = ngx_array_create(cf->pool, 512, 1);
+    if (headers->values == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (conf->headers_source) {
+
+        src = conf->headers_source->elts;
+        for (i = 0; i < conf->headers_source->nelts; i++) {
+
+            s = ngx_array_push(&headers_merged);
+            if (s == NULL) {
+                return NGX_ERROR;
+            }
+
+            *s = src[i];
+        }
+    }
+
+    h = default_headers;
+
+    while (h->key.len) {
+
+        src = headers_merged.elts;
+        for (i = 0; i < headers_merged.nelts; i++) {
+            if (ngx_strcasecmp(h->key.data, src[i].key.data) == 0) {
+                goto next;
+            }
+        }
+
+        s = ngx_array_push(&headers_merged);
+        if (s == NULL) {
+            return NGX_ERROR;
+        }
+
+        *s = *h;
+
+    next:
+
+        h++;
+    }
+
+
+    src = headers_merged.elts;
+    for (i = 0; i < headers_merged.nelts; i++) {
+
+        hk = ngx_array_push(&headers_names);
+        if (hk == NULL) {
+            return NGX_ERROR;
+        }
+
+        hk->key = src[i].key;
+        hk->key_hash = ngx_hash_key_lc(src[i].key.data, src[i].key.len);
+        hk->value = (void *) 1;
+
+        if (src[i].value.len == 0) {
+            continue;
+        }
+
+        copy = ngx_array_push_n(headers->lengths,
+                                sizeof(ngx_http_script_copy_code_t));
+        if (copy == NULL) {
+            return NGX_ERROR;
+        }
+
+        copy->code = (ngx_http_script_code_pt) (void *)
+                                                 ngx_http_script_copy_len_code;
+        copy->len = src[i].key.len;
+
+        size = (sizeof(ngx_http_script_copy_code_t)
+                + src[i].key.len + sizeof(uintptr_t) - 1)
+               & ~(sizeof(uintptr_t) - 1);
+
+        copy = ngx_array_push_n(headers->values, size);
+        if (copy == NULL) {
+            return NGX_ERROR;
+        }
+
+        copy->code = ngx_http_script_copy_code;
+        copy->len = src[i].key.len;
+
+        p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t);
+        ngx_memcpy(p, src[i].key.data, src[i].key.len);
+
+        ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+        sc.cf = cf;
+        sc.source = &src[i].value;
+        sc.flushes = &headers->flushes;
+        sc.lengths = &headers->lengths;
+        sc.values = &headers->values;
+
+        if (ngx_http_script_compile(&sc) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        code = ngx_array_push_n(headers->lengths, sizeof(uintptr_t));
+        if (code == NULL) {
+            return NGX_ERROR;
+        }
+
+        *code = (uintptr_t) NULL;
+
+        code = ngx_array_push_n(headers->values, sizeof(uintptr_t));
+        if (code == NULL) {
+            return NGX_ERROR;
+        }
+
+        *code = (uintptr_t) NULL;
+    }
+
+    code = ngx_array_push_n(headers->lengths, sizeof(uintptr_t));
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    *code = (uintptr_t) NULL;
+
+
+    hash.hash = &headers->hash;
+    hash.key = ngx_hash_key_lc;
+    hash.max_size = conf->headers_hash_max_size;
+    hash.bucket_size = conf->headers_hash_bucket_size;
+    hash.name = "proxy_headers_hash";
+    hash.pool = cf->pool;
+    hash.temp_pool = NULL;
+
+    return ngx_hash_init(&hash, headers_names.elts, headers_names.nelts);
+}
+
+
+static char *
+ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_proxy_loc_conf_t *plcf = conf;
+
+    size_t                      add;
+    u_short                     port;
+    ngx_str_t                  *value, *url;
+    ngx_url_t                   u;
+    ngx_uint_t                  n;
+    ngx_http_core_loc_conf_t   *clcf;
+    ngx_http_script_compile_t   sc;
+
+    if (plcf->upstream.upstream || plcf->proxy_lengths) {
+        return "is duplicate";
+    }
+
+    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+    clcf->handler = ngx_http_proxy_handler;
+
+    if (clcf->name.data[clcf->name.len - 1] == '/') {
+        clcf->auto_redirect = 1;
+    }
+
+    value = cf->args->elts;
+
+    url = &value[1];
+
+    n = ngx_http_script_variables_count(url);
+
+    if (n) {
+
+        ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+        sc.cf = cf;
+        sc.source = url;
+        sc.lengths = &plcf->proxy_lengths;
+        sc.values = &plcf->proxy_values;
+        sc.variables = n;
+        sc.complete_lengths = 1;
+        sc.complete_values = 1;
+
+        if (ngx_http_script_compile(&sc) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+#if (NGX_HTTP_SSL)
+        plcf->ssl = 1;
+#endif
+
+        return NGX_CONF_OK;
+    }
+
+    if (ngx_strncasecmp(url->data, (u_char *) "http://", 7) == 0) {
+        add = 7;
+        port = 80;
+
+    } else if (ngx_strncasecmp(url->data, (u_char *) "https://", 8) == 0) {
+
+#if (NGX_HTTP_SSL)
+        plcf->ssl = 1;
+
+        add = 8;
+        port = 443;
+#else
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "https protocol requires SSL support");
+        return NGX_CONF_ERROR;
+#endif
+
+    } else {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid URL prefix");
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memzero(&u, sizeof(ngx_url_t));
+
+    u.url.len = url->len - add;
+    u.url.data = url->data + add;
+    u.default_port = port;
+    u.uri_part = 1;
+    u.no_resolve = 1;
+
+    plcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);
+    if (plcf->upstream.upstream == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    plcf->vars.schema.len = add;
+    plcf->vars.schema.data = url->data;
+    plcf->vars.key_start = plcf->vars.schema;
+
+    ngx_http_proxy_set_vars(&u, &plcf->vars);
+
+    plcf->location = clcf->name;
+
+    if (clcf->named
+#if (NGX_PCRE)
+        || clcf->regex
+#endif
+        || clcf->noname)
+    {
+        if (plcf->vars.uri.len) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "\"proxy_pass\" cannot have URI part in "
+                               "location given by regular expression, "
+                               "or inside named location, "
+                               "or inside \"if\" statement, "
+                               "or inside \"limit_except\" block");
+            return NGX_CONF_ERROR;
+        }
+
+        plcf->location.len = 0;
+    }
+
+    plcf->url = *url;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_proxy_redirect(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_proxy_loc_conf_t *plcf = conf;
+
+    u_char                            *p;
+    ngx_str_t                         *value;
+    ngx_http_proxy_rewrite_t          *pr;
+    ngx_http_compile_complex_value_t   ccv;
+
+    if (plcf->redirect == 0) {
+        return NGX_CONF_OK;
+    }
+
+    plcf->redirect = 1;
+
+    value = cf->args->elts;
+
+    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");
+            plcf->redirect = 0;
+            plcf->redirects = NULL;
+            return NGX_CONF_OK;
+        }
+
+        if (ngx_strcmp(value[1].data, "default") != 0) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid parameter \"%V\"", &value[1]);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (plcf->redirects == NULL) {
+        plcf->redirects = ngx_array_create(cf->pool, 1,
+                                           sizeof(ngx_http_proxy_rewrite_t));
+        if (plcf->redirects == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    pr = ngx_array_push(plcf->redirects);
+    if (pr == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (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 "
+                               "with \"proxy_pass\" directive with variables");
+            return NGX_CONF_ERROR;
+        }
+
+        if (plcf->url.data == NULL) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "\"proxy_redirect default\" should be placed "
+                               "after the \"proxy_pass\" directive");
+            return NGX_CONF_ERROR;
+        }
+
+        pr->handler = ngx_http_proxy_rewrite_complex_handler;
+
+        ngx_memzero(&pr->pattern.complex, sizeof(ngx_http_complex_value_t));
+
+        ngx_memzero(&pr->replacement, sizeof(ngx_http_complex_value_t));
+
+        if (plcf->vars.uri.len) {
+            pr->pattern.complex.value = plcf->url;
+            pr->replacement.value = plcf->location;
+
+        } else {
+            pr->pattern.complex.value.len = plcf->url.len + sizeof("/") - 1;
+
+            p = ngx_pnalloc(cf->pool, pr->pattern.complex.value.len);
+            if (p == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            pr->pattern.complex.value.data = p;
+
+            p = ngx_cpymem(p, plcf->url.data, plcf->url.len);
+            *p = '/';
+
+            ngx_str_set(&pr->replacement.value, "/");
+        }
+
+        return NGX_CONF_OK;
+    }
+
+
+    if (value[1].data[0] == '~') {
+        value[1].len--;
+        value[1].data++;
+
+        if (value[1].data[0] == '*') {
+            value[1].len--;
+            value[1].data++;
+
+            if (ngx_http_proxy_rewrite_regex(cf, pr, &value[1], 1) != NGX_OK) {
+                return NGX_CONF_ERROR;
+            }
+
+        } else {
+            if (ngx_http_proxy_rewrite_regex(cf, pr, &value[1], 0) != NGX_OK) {
+                return NGX_CONF_ERROR;
+            }
+        }
+
+    } else {
+
+        ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+        ccv.cf = cf;
+        ccv.value = &value[1];
+        ccv.complex_value = &pr->pattern.complex;
+
+        if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+        pr->handler = ngx_http_proxy_rewrite_complex_handler;
+    }
+
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[2];
+    ccv.complex_value = &pr->replacement;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_proxy_cookie_domain(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_proxy_loc_conf_t *plcf = conf;
+
+    ngx_str_t                         *value;
+    ngx_http_proxy_rewrite_t          *pr;
+    ngx_http_compile_complex_value_t   ccv;
+
+    if (plcf->cookie_domains == NULL) {
+        return NGX_CONF_OK;
+    }
+
+    value = cf->args->elts;
+
+    if (cf->args->nelts == 2) {
+
+        if (ngx_strcmp(value[1].data, "off") == 0) {
+            plcf->cookie_domains = 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_domains == NGX_CONF_UNSET_PTR) {
+        plcf->cookie_domains = ngx_array_create(cf->pool, 1,
+                                     sizeof(ngx_http_proxy_rewrite_t));
+        if (plcf->cookie_domains == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    pr = ngx_array_push(plcf->cookie_domains);
+    if (pr == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (value[1].data[0] == '~') {
+        value[1].len--;
+        value[1].data++;
+
+        if (ngx_http_proxy_rewrite_regex(cf, pr, &value[1], 1) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+    } else {
+
+        if (value[1].data[0] == '.') {
+            value[1].len--;
+            value[1].data++;
+        }
+
+        ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+        ccv.cf = cf;
+        ccv.value = &value[1];
+        ccv.complex_value = &pr->pattern.complex;
+
+        if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+        pr->handler = ngx_http_proxy_rewrite_domain_handler;
+
+        if (value[2].data[0] == '.') {
+            value[2].len--;
+            value[2].data++;
+        }
+    }
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[2];
+    ccv.complex_value = &pr->replacement;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_proxy_cookie_path(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_proxy_loc_conf_t *plcf = conf;
+
+    ngx_str_t                         *value;
+    ngx_http_proxy_rewrite_t          *pr;
+    ngx_http_compile_complex_value_t   ccv;
+
+    if (plcf->cookie_paths == NULL) {
+        return NGX_CONF_OK;
+    }
+
+    value = cf->args->elts;
+
+    if (cf->args->nelts == 2) {
+
+        if (ngx_strcmp(value[1].data, "off") == 0) {
+            plcf->cookie_paths = 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_paths == NGX_CONF_UNSET_PTR) {
+        plcf->cookie_paths = ngx_array_create(cf->pool, 1,
+                                     sizeof(ngx_http_proxy_rewrite_t));
+        if (plcf->cookie_paths == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    pr = ngx_array_push(plcf->cookie_paths);
+    if (pr == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (value[1].data[0] == '~') {
+        value[1].len--;
+        value[1].data++;
+
+        if (value[1].data[0] == '*') {
+            value[1].len--;
+            value[1].data++;
+
+            if (ngx_http_proxy_rewrite_regex(cf, pr, &value[1], 1) != NGX_OK) {
+                return NGX_CONF_ERROR;
+            }
+
+        } else {
+            if (ngx_http_proxy_rewrite_regex(cf, pr, &value[1], 0) != NGX_OK) {
+                return NGX_CONF_ERROR;
+            }
+        }
+
+    } else {
+
+        ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+        ccv.cf = cf;
+        ccv.value = &value[1];
+        ccv.complex_value = &pr->pattern.complex;
+
+        if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+        pr->handler = ngx_http_proxy_rewrite_complex_handler;
+    }
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[2];
+    ccv.complex_value = &pr->replacement;
+
+    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)
+{
+#if (NGX_PCRE)
+    u_char               errstr[NGX_MAX_CONF_ERRSTR];
+    ngx_regex_compile_t  rc;
+
+    ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
+
+    rc.pattern = *regex;
+    rc.err.len = NGX_MAX_CONF_ERRSTR;
+    rc.err.data = errstr;
+
+    if (caseless) {
+        rc.options = NGX_REGEX_CASELESS;
+    }
+
+    pr->pattern.regex = ngx_http_regex_compile(cf, &rc);
+    if (pr->pattern.regex == NULL) {
+        return NGX_ERROR;
+    }
+
+    pr->handler = ngx_http_proxy_rewrite_regex_handler;
+
+    return NGX_OK;
+
+#else
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "using regex \"%V\" requires PCRE library", regex);
+    return NGX_ERROR;
+
+#endif
+}
+
+
+static char *
+ngx_http_proxy_store(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_proxy_loc_conf_t *plcf = conf;
+
+    ngx_str_t                  *value;
+    ngx_http_script_compile_t   sc;
+
+    if (plcf->upstream.store != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    if (ngx_strcmp(value[1].data, "off") == 0) {
+        plcf->upstream.store = 0;
+        return NGX_CONF_OK;
+    }
+
+#if (NGX_HTTP_CACHE)
+    if (plcf->upstream.cache > 0) {
+        return "is incompatible with \"proxy_cache\"";
+    }
+#endif
+
+    plcf->upstream.store = 1;
+
+    if (ngx_strcmp(value[1].data, "on") == 0) {
+        return NGX_CONF_OK;
+    }
+
+    /* include the terminating '\0' into script */
+    value[1].len++;
+
+    ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+    sc.cf = cf;
+    sc.source = &value[1];
+    sc.lengths = &plcf->upstream.store_lengths;
+    sc.values = &plcf->upstream.store_values;
+    sc.variables = ngx_http_script_variables_count(&value[1]);
+    sc.complete_lengths = 1;
+    sc.complete_values = 1;
+
+    if (ngx_http_script_compile(&sc) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+#if (NGX_HTTP_CACHE)
+
+static char *
+ngx_http_proxy_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_proxy_loc_conf_t *plcf = conf;
+
+    ngx_str_t                         *value;
+    ngx_http_complex_value_t           cv;
+    ngx_http_compile_complex_value_t   ccv;
+
+    value = cf->args->elts;
+
+    if (plcf->upstream.cache != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    if (ngx_strcmp(value[1].data, "off") == 0) {
+        plcf->upstream.cache = 0;
+        return NGX_CONF_OK;
+    }
+
+    if (plcf->upstream.store > 0) {
+        return "is incompatible with \"proxy_store\"";
+    }
+
+    plcf->upstream.cache = 1;
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[1];
+    ccv.complex_value = &cv;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (cv.lengths != NULL) {
+
+        plcf->upstream.cache_value = ngx_palloc(cf->pool,
+                                             sizeof(ngx_http_complex_value_t));
+        if (plcf->upstream.cache_value == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        *plcf->upstream.cache_value = cv;
+
+        return NGX_CONF_OK;
+    }
+
+    plcf->upstream.cache_zone = ngx_shared_memory_add(cf, &value[1], 0,
+                                                      &ngx_http_proxy_module);
+    if (plcf->upstream.cache_zone == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_proxy_cache_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_proxy_loc_conf_t *plcf = conf;
+
+    ngx_str_t                         *value;
+    ngx_http_compile_complex_value_t   ccv;
+
+    value = cf->args->elts;
+
+    if (plcf->cache_key.value.data) {
+        return "is duplicate";
+    }
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[1];
+    ccv.complex_value = &plcf->cache_key;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+#endif
+
+
+#if (NGX_HTTP_SSL)
+
+static char *
+ngx_http_proxy_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_proxy_loc_conf_t *plcf = conf;
+
+    ngx_str_t  *value;
+
+    if (plcf->ssl_passwords != NGX_CONF_UNSET_PTR) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    plcf->ssl_passwords = ngx_ssl_read_password_file(cf, &value[1]);
+
+    if (plcf->ssl_passwords == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+#endif
+
+
+static char *
+ngx_http_proxy_lowat_check(ngx_conf_t *cf, void *post, void *data)
+{
+#if (NGX_FREEBSD)
+    ssize_t *np = data;
+
+    if ((u_long) *np >= ngx_freebsd_net_inet_tcp_sendspace) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"proxy_send_lowat\" must be less than %d "
+                           "(sysctl net.inet.tcp.sendspace)",
+                           ngx_freebsd_net_inet_tcp_sendspace);
+
+        return NGX_CONF_ERROR;
+    }
+
+#elif !(NGX_HAVE_SO_SNDLOWAT)
+    ssize_t *np = data;
+
+    ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                       "\"proxy_send_lowat\" is not supported, ignored");
+
+    *np = 0;
+
+#endif
+
+    return NGX_CONF_OK;
+}
+
+
+#if (NGX_HTTP_SSL)
+
+static ngx_int_t
+ngx_http_proxy_set_ssl(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *plcf)
+{
+    ngx_pool_cleanup_t  *cln;
+
+    plcf->upstream.ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t));
+    if (plcf->upstream.ssl == NULL) {
+        return NGX_ERROR;
+    }
+
+    plcf->upstream.ssl->log = cf->log;
+
+    if (ngx_ssl_create(plcf->upstream.ssl, plcf->ssl_protocols, NULL)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    cln = ngx_pool_cleanup_add(cf->pool, 0);
+    if (cln == NULL) {
+        return NGX_ERROR;
+    }
+
+    cln->handler = ngx_ssl_cleanup_ctx;
+    cln->data = plcf->upstream.ssl;
+
+    if (plcf->ssl_certificate.len) {
+
+        if (plcf->ssl_certificate_key.len == 0) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "no \"proxy_ssl_certificate_key\" is defined "
+                          "for certificate \"%V\"", &plcf->ssl_certificate);
+            return NGX_ERROR;
+        }
+
+        if (ngx_ssl_certificate(cf, plcf->upstream.ssl, &plcf->ssl_certificate,
+                                &plcf->ssl_certificate_key, plcf->ssl_passwords)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    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,
+                      "no proxy_ssl_trusted_certificate for proxy_ssl_verify");
+            return NGX_ERROR;
+        }
+
+        if (ngx_ssl_trusted_certificate(cf, plcf->upstream.ssl,
+                                        &plcf->ssl_trusted_certificate,
+                                        plcf->ssl_verify_depth)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+
+        if (ngx_ssl_crl(cf, plcf->upstream.ssl, &plcf->ssl_crl) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+#endif
+
+
+static void
+ngx_http_proxy_set_vars(ngx_url_t *u, ngx_http_proxy_vars_t *v)
+{
+    if (u->family != AF_UNIX) {
+
+        if (u->no_port || u->port == u->default_port) {
+
+            v->host_header = u->host;
+
+            if (u->default_port == 80) {
+                ngx_str_set(&v->port, "80");
+
+            } else {
+                ngx_str_set(&v->port, "443");
+            }
+
+        } else {
+            v->host_header.len = u->host.len + 1 + u->port_text.len;
+            v->host_header.data = u->host.data;
+            v->port = u->port_text;
+        }
+
+        v->key_start.len += v->host_header.len;
+
+    } else {
+        ngx_str_set(&v->host_header, "localhost");
+        ngx_str_null(&v->port);
+        v->key_start.len += sizeof("unix:") - 1 + u->host.len + 1;
+    }
+
+    v->uri = u->uri;
+}
diff --git a/nginx/src/http/modules/ngx_http_random_index_module.c b/nginx/src/http/modules/ngx_http_random_index_module.c
new file mode 100644 (file)
index 0000000..b47ee4f
--- /dev/null
@@ -0,0 +1,317 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_flag_t  enable;
+} ngx_http_random_index_loc_conf_t;
+
+
+#define NGX_HTTP_RANDOM_INDEX_PREALLOCATE  50
+
+
+static ngx_int_t ngx_http_random_index_error(ngx_http_request_t *r,
+    ngx_dir_t *dir, ngx_str_t *name);
+static ngx_int_t ngx_http_random_index_init(ngx_conf_t *cf);
+static void *ngx_http_random_index_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_random_index_merge_loc_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+
+
+static ngx_command_t  ngx_http_random_index_commands[] = {
+
+    { ngx_string("random_index"),
+      NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_random_index_loc_conf_t, enable),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_random_index_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_random_index_init,            /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_random_index_create_loc_conf, /* create location configuration */
+    ngx_http_random_index_merge_loc_conf   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_random_index_module = {
+    NGX_MODULE_V1,
+    &ngx_http_random_index_module_ctx,     /* module context */
+    ngx_http_random_index_commands,        /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_random_index_handler(ngx_http_request_t *r)
+{
+    u_char                            *last, *filename;
+    size_t                             len, allocated, root;
+    ngx_err_t                          err;
+    ngx_int_t                          rc;
+    ngx_str_t                          path, uri, *name;
+    ngx_dir_t                          dir;
+    ngx_uint_t                         n, level;
+    ngx_array_t                        names;
+    ngx_http_random_index_loc_conf_t  *rlcf;
+
+    if (r->uri.data[r->uri.len - 1] != '/') {
+        return NGX_DECLINED;
+    }
+
+    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_POST))) {
+        return NGX_DECLINED;
+    }
+
+    rlcf = ngx_http_get_module_loc_conf(r, ngx_http_random_index_module);
+
+    if (!rlcf->enable) {
+        return NGX_DECLINED;
+    }
+
+#if (NGX_HAVE_D_TYPE)
+    len = NGX_DIR_MASK_LEN;
+#else
+    len = NGX_HTTP_RANDOM_INDEX_PREALLOCATE;
+#endif
+
+    last = ngx_http_map_uri_to_path(r, &path, &root, len);
+    if (last == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    allocated = path.len;
+
+    path.len = last - path.data - 1;
+    path.data[path.len] = '\0';
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http random index: \"%s\"", path.data);
+
+    if (ngx_open_dir(&path, &dir) == NGX_ERROR) {
+        err = ngx_errno;
+
+        if (err == NGX_ENOENT
+            || err == NGX_ENOTDIR
+            || err == NGX_ENAMETOOLONG)
+        {
+            level = NGX_LOG_ERR;
+            rc = NGX_HTTP_NOT_FOUND;
+
+        } else if (err == NGX_EACCES) {
+            level = NGX_LOG_ERR;
+            rc = NGX_HTTP_FORBIDDEN;
+
+        } else {
+            level = NGX_LOG_CRIT;
+            rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        ngx_log_error(level, r->connection->log, err,
+                      ngx_open_dir_n " \"%s\" failed", path.data);
+
+        return rc;
+    }
+
+    if (ngx_array_init(&names, r->pool, 32, sizeof(ngx_str_t)) != NGX_OK) {
+        return ngx_http_random_index_error(r, &dir, &path);
+    }
+
+    filename = path.data;
+    filename[path.len] = '/';
+
+    for ( ;; ) {
+        ngx_set_errno(0);
+
+        if (ngx_read_dir(&dir) == NGX_ERROR) {
+            err = ngx_errno;
+
+            if (err != NGX_ENOMOREFILES) {
+                ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
+                              ngx_read_dir_n " \"%V\" failed", &path);
+                return ngx_http_random_index_error(r, &dir, &path);
+            }
+
+            break;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http random index file: \"%s\"", ngx_de_name(&dir));
+
+        if (ngx_de_name(&dir)[0] == '.') {
+            continue;
+        }
+
+        len = ngx_de_namelen(&dir);
+
+        if (dir.type == 0 || ngx_de_is_link(&dir)) {
+
+            /* 1 byte for '/' and 1 byte for terminating '\0' */
+
+            if (path.len + 1 + len + 1 > allocated) {
+                allocated = path.len + 1 + len + 1
+                                     + NGX_HTTP_RANDOM_INDEX_PREALLOCATE;
+
+                filename = ngx_pnalloc(r->pool, allocated);
+                if (filename == NULL) {
+                    return ngx_http_random_index_error(r, &dir, &path);
+                }
+
+                last = ngx_cpystrn(filename, path.data, path.len + 1);
+                *last++ = '/';
+            }
+
+            ngx_cpystrn(last, ngx_de_name(&dir), len + 1);
+
+            if (ngx_de_info(filename, &dir) == NGX_FILE_ERROR) {
+                err = ngx_errno;
+
+                if (err != NGX_ENOENT) {
+                    ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
+                                  ngx_de_info_n " \"%s\" failed", filename);
+                    return ngx_http_random_index_error(r, &dir, &path);
+                }
+
+                if (ngx_de_link_info(filename, &dir) == NGX_FILE_ERROR) {
+                    ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+                                  ngx_de_link_info_n " \"%s\" failed",
+                                  filename);
+                    return ngx_http_random_index_error(r, &dir, &path);
+                }
+            }
+        }
+
+        if (!ngx_de_is_file(&dir)) {
+            continue;
+        }
+
+        name = ngx_array_push(&names);
+        if (name == NULL) {
+            return ngx_http_random_index_error(r, &dir, &path);
+        }
+
+        name->len = len;
+
+        name->data = ngx_pnalloc(r->pool, len);
+        if (name->data == NULL) {
+            return ngx_http_random_index_error(r, &dir, &path);
+        }
+
+        ngx_memcpy(name->data, ngx_de_name(&dir), len);
+    }
+
+    if (ngx_close_dir(&dir) == NGX_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
+                      ngx_close_dir_n " \"%V\" failed", &path);
+    }
+
+    n = names.nelts;
+
+    if (n == 0) {
+        return NGX_DECLINED;
+    }
+
+    name = names.elts;
+
+    n = (ngx_uint_t) (((uint64_t) ngx_random() * n) / 0x80000000);
+
+    uri.len = r->uri.len + name[n].len;
+
+    uri.data = ngx_pnalloc(r->pool, uri.len);
+    if (uri.data == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    last = ngx_copy(uri.data, r->uri.data, r->uri.len);
+    ngx_memcpy(last, name[n].data, name[n].len);
+
+    return ngx_http_internal_redirect(r, &uri, &r->args);
+}
+
+
+static ngx_int_t
+ngx_http_random_index_error(ngx_http_request_t *r, ngx_dir_t *dir,
+    ngx_str_t *name)
+{
+    if (ngx_close_dir(dir) == NGX_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
+                      ngx_close_dir_n " \"%V\" failed", name);
+    }
+
+    return NGX_HTTP_INTERNAL_SERVER_ERROR;
+}
+
+
+static void *
+ngx_http_random_index_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_random_index_loc_conf_t  *conf;
+
+    conf = ngx_palloc(cf->pool, sizeof(ngx_http_random_index_loc_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    conf->enable = NGX_CONF_UNSET;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_random_index_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_random_index_loc_conf_t *prev = parent;
+    ngx_http_random_index_loc_conf_t *conf = child;
+
+    ngx_conf_merge_value(conf->enable, prev->enable, 0);
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_random_index_init(ngx_conf_t *cf)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_random_index_handler;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_range_filter_module.c b/nginx/src/http/modules/ngx_http_range_filter_module.c
new file mode 100644 (file)
index 0000000..819c5c9
--- /dev/null
@@ -0,0 +1,932 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+/*
+ * the single part format:
+ *
+ * "HTTP/1.0 206 Partial Content" CRLF
+ * ... header ...
+ * "Content-Type: image/jpeg" CRLF
+ * "Content-Length: SIZE" CRLF
+ * "Content-Range: bytes START-END/SIZE" CRLF
+ * CRLF
+ * ... data ...
+ *
+ *
+ * the multipart format:
+ *
+ * "HTTP/1.0 206 Partial Content" CRLF
+ * ... header ...
+ * "Content-Type: multipart/byteranges; boundary=0123456789" CRLF
+ * CRLF
+ * CRLF
+ * "--0123456789" CRLF
+ * "Content-Type: image/jpeg" CRLF
+ * "Content-Range: bytes START0-END0/SIZE" CRLF
+ * CRLF
+ * ... data ...
+ * CRLF
+ * "--0123456789" CRLF
+ * "Content-Type: image/jpeg" CRLF
+ * "Content-Range: bytes START1-END1/SIZE" CRLF
+ * CRLF
+ * ... data ...
+ * CRLF
+ * "--0123456789--" CRLF
+ */
+
+
+typedef struct {
+    off_t        start;
+    off_t        end;
+    ngx_str_t    content_range;
+} ngx_http_range_t;
+
+
+typedef struct {
+    off_t        offset;
+    ngx_str_t    boundary_header;
+    ngx_array_t  ranges;
+} ngx_http_range_filter_ctx_t;
+
+
+static ngx_int_t ngx_http_range_parse(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx, ngx_uint_t ranges);
+static ngx_int_t ngx_http_range_singlepart_header(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx);
+static ngx_int_t ngx_http_range_multipart_header(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx);
+static ngx_int_t ngx_http_range_not_satisfiable(ngx_http_request_t *r);
+static ngx_int_t ngx_http_range_test_overlapped(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in);
+static ngx_int_t ngx_http_range_singlepart_body(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in);
+static ngx_int_t ngx_http_range_multipart_body(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in);
+
+static ngx_int_t ngx_http_range_header_filter_init(ngx_conf_t *cf);
+static ngx_int_t ngx_http_range_body_filter_init(ngx_conf_t *cf);
+
+
+static ngx_http_module_t  ngx_http_range_header_filter_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_range_header_filter_init,     /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL,                                  /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_range_header_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_range_header_filter_module_ctx, /* module context */
+    NULL,                                  /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_module_t  ngx_http_range_body_filter_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_range_body_filter_init,       /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL,                                  /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_range_body_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_range_body_filter_module_ctx, /* module context */
+    NULL,                                  /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_range_header_filter(ngx_http_request_t *r)
+{
+    time_t                        if_range_time;
+    ngx_str_t                    *if_range, *etag;
+    ngx_uint_t                    ranges;
+    ngx_http_core_loc_conf_t     *clcf;
+    ngx_http_range_filter_ctx_t  *ctx;
+
+    if (r->http_version < NGX_HTTP_VERSION_10
+        || r->headers_out.status != NGX_HTTP_OK
+        || (r != r->main && !r->subrequest_ranges)
+        || r->headers_out.content_length_n == -1
+        || !r->allow_ranges)
+    {
+        return ngx_http_next_header_filter(r);
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (clcf->max_ranges == 0) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    if (r->headers_in.range == NULL
+        || r->headers_in.range->value.len < 7
+        || ngx_strncasecmp(r->headers_in.range->value.data,
+                           (u_char *) "bytes=", 6)
+           != 0)
+    {
+        goto next_filter;
+    }
+
+    if (r->headers_in.if_range) {
+
+        if_range = &r->headers_in.if_range->value;
+
+        if (if_range->len >= 2 && if_range->data[if_range->len - 1] == '"') {
+
+            if (r->headers_out.etag == NULL) {
+                goto next_filter;
+            }
+
+            etag = &r->headers_out.etag->value;
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http ir:%V etag:%V", if_range, etag);
+
+            if (if_range->len != etag->len
+                || ngx_strncmp(if_range->data, etag->data, etag->len) != 0)
+            {
+                goto next_filter;
+            }
+
+            goto parse;
+        }
+
+        if (r->headers_out.last_modified_time == (time_t) -1) {
+            goto next_filter;
+        }
+
+        if_range_time = ngx_parse_http_time(if_range->data, if_range->len);
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http ir:%T lm:%T",
+                       if_range_time, r->headers_out.last_modified_time);
+
+        if (if_range_time != r->headers_out.last_modified_time) {
+            goto next_filter;
+        }
+    }
+
+parse:
+
+    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_range_filter_ctx_t));
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    ctx->offset = r->headers_out.content_offset;
+
+    ranges = r->single_range ? 1 : clcf->max_ranges;
+
+    switch (ngx_http_range_parse(r, ctx, ranges)) {
+
+    case NGX_OK:
+        ngx_http_set_ctx(r, ctx, ngx_http_range_body_filter_module);
+
+        r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
+        r->headers_out.status_line.len = 0;
+
+        if (ctx->ranges.nelts == 1) {
+            return ngx_http_range_singlepart_header(r, ctx);
+        }
+
+        return ngx_http_range_multipart_header(r, ctx);
+
+    case NGX_HTTP_RANGE_NOT_SATISFIABLE:
+        return ngx_http_range_not_satisfiable(r);
+
+    case NGX_ERROR:
+        return NGX_ERROR;
+
+    default: /* NGX_DECLINED */
+        break;
+    }
+
+next_filter:
+
+    r->headers_out.accept_ranges = ngx_list_push(&r->headers_out.headers);
+    if (r->headers_out.accept_ranges == NULL) {
+        return NGX_ERROR;
+    }
+
+    r->headers_out.accept_ranges->hash = 1;
+    ngx_str_set(&r->headers_out.accept_ranges->key, "Accept-Ranges");
+    ngx_str_set(&r->headers_out.accept_ranges->value, "bytes");
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_range_parse(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx,
+    ngx_uint_t ranges)
+{
+    u_char                       *p;
+    off_t                         start, end, size, content_length, cutoff,
+                                  cutlim;
+    ngx_uint_t                    suffix;
+    ngx_http_range_t             *range;
+    ngx_http_range_filter_ctx_t  *mctx;
+
+    if (r != r->main) {
+        mctx = ngx_http_get_module_ctx(r->main,
+                                       ngx_http_range_body_filter_module);
+        if (mctx) {
+            ctx->ranges = mctx->ranges;
+            return NGX_OK;
+        }
+    }
+
+    if (ngx_array_init(&ctx->ranges, r->pool, 1, sizeof(ngx_http_range_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    p = r->headers_in.range->value.data + 6;
+    size = 0;
+    content_length = r->headers_out.content_length_n;
+
+    cutoff = NGX_MAX_OFF_T_VALUE / 10;
+    cutlim = NGX_MAX_OFF_T_VALUE % 10;
+
+    for ( ;; ) {
+        start = 0;
+        end = 0;
+        suffix = 0;
+
+        while (*p == ' ') { p++; }
+
+        if (*p != '-') {
+            if (*p < '0' || *p > '9') {
+                return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+            }
+
+            while (*p >= '0' && *p <= '9') {
+                if (start >= cutoff && (start > cutoff || *p - '0' > cutlim)) {
+                    return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+                }
+
+                start = start * 10 + (*p++ - '0');
+            }
+
+            while (*p == ' ') { p++; }
+
+            if (*p++ != '-') {
+                return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+            }
+
+            while (*p == ' ') { p++; }
+
+            if (*p == ',' || *p == '\0') {
+                end = content_length;
+                goto found;
+            }
+
+        } else {
+            suffix = 1;
+            p++;
+        }
+
+        if (*p < '0' || *p > '9') {
+            return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+        }
+
+        while (*p >= '0' && *p <= '9') {
+            if (end >= cutoff && (end > cutoff || *p - '0' > cutlim)) {
+                return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+            }
+
+            end = end * 10 + (*p++ - '0');
+        }
+
+        while (*p == ' ') { p++; }
+
+        if (*p != ',' && *p != '\0') {
+            return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+        }
+
+        if (suffix) {
+            start = (end < content_length) ? content_length - end : 0;
+            end = content_length - 1;
+        }
+
+        if (end >= content_length) {
+            end = content_length;
+
+        } else {
+            end++;
+        }
+
+    found:
+
+        if (start < end) {
+            range = ngx_array_push(&ctx->ranges);
+            if (range == NULL) {
+                return NGX_ERROR;
+            }
+
+            range->start = start;
+            range->end = end;
+
+            if (size > NGX_MAX_OFF_T_VALUE - (end - start)) {
+                return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+            }
+
+            size += end - start;
+
+            if (ranges-- == 0) {
+                return NGX_DECLINED;
+            }
+
+        } else if (start == 0) {
+            return NGX_DECLINED;
+        }
+
+        if (*p++ != ',') {
+            break;
+        }
+    }
+
+    if (ctx->ranges.nelts == 0) {
+        return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+    }
+
+    if (size > content_length) {
+        return NGX_DECLINED;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_range_singlepart_header(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx)
+{
+    ngx_table_elt_t   *content_range;
+    ngx_http_range_t  *range;
+
+    if (r != r->main) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    content_range = ngx_list_push(&r->headers_out.headers);
+    if (content_range == NULL) {
+        return NGX_ERROR;
+    }
+
+    r->headers_out.content_range = content_range;
+
+    content_range->hash = 1;
+    ngx_str_set(&content_range->key, "Content-Range");
+
+    content_range->value.data = ngx_pnalloc(r->pool,
+                                    sizeof("bytes -/") - 1 + 3 * NGX_OFF_T_LEN);
+    if (content_range->value.data == NULL) {
+        content_range->hash = 0;
+        r->headers_out.content_range = NULL;
+        return NGX_ERROR;
+    }
+
+    /* "Content-Range: bytes SSSS-EEEE/TTTT" header */
+
+    range = ctx->ranges.elts;
+
+    content_range->value.len = ngx_sprintf(content_range->value.data,
+                                           "bytes %O-%O/%O",
+                                           range->start, range->end - 1,
+                                           r->headers_out.content_length_n)
+                               - content_range->value.data;
+
+    r->headers_out.content_length_n = range->end - range->start;
+    r->headers_out.content_offset = range->start;
+
+    if (r->headers_out.content_length) {
+        r->headers_out.content_length->hash = 0;
+        r->headers_out.content_length = NULL;
+    }
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_range_multipart_header(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx)
+{
+    off_t               len;
+    size_t              size;
+    ngx_uint_t          i;
+    ngx_http_range_t   *range;
+    ngx_atomic_uint_t   boundary;
+
+    size = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN
+           + sizeof(CRLF "Content-Type: ") - 1
+           + r->headers_out.content_type.len
+           + sizeof(CRLF "Content-Range: bytes ") - 1;
+
+    if (r->headers_out.content_type_len == r->headers_out.content_type.len
+        && r->headers_out.charset.len)
+    {
+        size += sizeof("; charset=") - 1 + r->headers_out.charset.len;
+    }
+
+    ctx->boundary_header.data = ngx_pnalloc(r->pool, size);
+    if (ctx->boundary_header.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    boundary = ngx_next_temp_number(0);
+
+    /*
+     * The boundary header of the range:
+     * CRLF
+     * "--0123456789" CRLF
+     * "Content-Type: image/jpeg" CRLF
+     * "Content-Range: bytes "
+     */
+
+    if (r->headers_out.content_type_len == r->headers_out.content_type.len
+        && r->headers_out.charset.len)
+    {
+        ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
+                                           CRLF "--%0muA" CRLF
+                                           "Content-Type: %V; charset=%V" CRLF
+                                           "Content-Range: bytes ",
+                                           boundary,
+                                           &r->headers_out.content_type,
+                                           &r->headers_out.charset)
+                                   - ctx->boundary_header.data;
+
+    } else if (r->headers_out.content_type.len) {
+        ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
+                                           CRLF "--%0muA" CRLF
+                                           "Content-Type: %V" CRLF
+                                           "Content-Range: bytes ",
+                                           boundary,
+                                           &r->headers_out.content_type)
+                                   - ctx->boundary_header.data;
+
+    } else {
+        ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
+                                           CRLF "--%0muA" CRLF
+                                           "Content-Range: bytes ",
+                                           boundary)
+                                   - ctx->boundary_header.data;
+    }
+
+    r->headers_out.content_type.data =
+        ngx_pnalloc(r->pool,
+                    sizeof("Content-Type: multipart/byteranges; boundary=") - 1
+                    + NGX_ATOMIC_T_LEN);
+
+    if (r->headers_out.content_type.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    r->headers_out.content_type_lowcase = NULL;
+
+    /* "Content-Type: multipart/byteranges; boundary=0123456789" */
+
+    r->headers_out.content_type.len =
+                           ngx_sprintf(r->headers_out.content_type.data,
+                                       "multipart/byteranges; boundary=%0muA",
+                                       boundary)
+                           - r->headers_out.content_type.data;
+
+    r->headers_out.content_type_len = r->headers_out.content_type.len;
+
+    r->headers_out.charset.len = 0;
+
+    /* the size of the last boundary CRLF "--0123456789--" CRLF */
+
+    len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN + sizeof("--" CRLF) - 1;
+
+    range = ctx->ranges.elts;
+    for (i = 0; i < ctx->ranges.nelts; i++) {
+
+        /* the size of the range: "SSSS-EEEE/TTTT" CRLF CRLF */
+
+        range[i].content_range.data =
+                               ngx_pnalloc(r->pool, 3 * NGX_OFF_T_LEN + 2 + 4);
+
+        if (range[i].content_range.data == NULL) {
+            return NGX_ERROR;
+        }
+
+        range[i].content_range.len = ngx_sprintf(range[i].content_range.data,
+                                               "%O-%O/%O" CRLF CRLF,
+                                               range[i].start, range[i].end - 1,
+                                               r->headers_out.content_length_n)
+                                     - range[i].content_range.data;
+
+        len += ctx->boundary_header.len + range[i].content_range.len
+                                             + (range[i].end - range[i].start);
+    }
+
+    r->headers_out.content_length_n = len;
+
+    if (r->headers_out.content_length) {
+        r->headers_out.content_length->hash = 0;
+        r->headers_out.content_length = NULL;
+    }
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_range_not_satisfiable(ngx_http_request_t *r)
+{
+    ngx_table_elt_t  *content_range;
+
+    r->headers_out.status = NGX_HTTP_RANGE_NOT_SATISFIABLE;
+
+    content_range = ngx_list_push(&r->headers_out.headers);
+    if (content_range == NULL) {
+        return NGX_ERROR;
+    }
+
+    r->headers_out.content_range = content_range;
+
+    content_range->hash = 1;
+    ngx_str_set(&content_range->key, "Content-Range");
+
+    content_range->value.data = ngx_pnalloc(r->pool,
+                                       sizeof("bytes */") - 1 + NGX_OFF_T_LEN);
+    if (content_range->value.data == NULL) {
+        content_range->hash = 0;
+        r->headers_out.content_range = NULL;
+        return NGX_ERROR;
+    }
+
+    content_range->value.len = ngx_sprintf(content_range->value.data,
+                                           "bytes */%O",
+                                           r->headers_out.content_length_n)
+                               - content_range->value.data;
+
+    ngx_http_clear_content_length(r);
+
+    return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+}
+
+
+static ngx_int_t
+ngx_http_range_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    ngx_http_range_filter_ctx_t  *ctx;
+
+    if (in == NULL) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_range_body_filter_module);
+
+    if (ctx == NULL) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    if (ctx->ranges.nelts == 1) {
+        return ngx_http_range_singlepart_body(r, ctx, in);
+    }
+
+    /*
+     * multipart ranges are supported only if whole body is in a single buffer
+     */
+
+    if (ngx_buf_special(in->buf)) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    if (ngx_http_range_test_overlapped(r, ctx, in) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    return ngx_http_range_multipart_body(r, ctx, in);
+}
+
+
+static ngx_int_t
+ngx_http_range_test_overlapped(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
+{
+    off_t              start, last;
+    ngx_buf_t         *buf;
+    ngx_uint_t         i;
+    ngx_http_range_t  *range;
+
+    if (ctx->offset) {
+        goto overlapped;
+    }
+
+    buf = in->buf;
+
+    if (!buf->last_buf) {
+        start = ctx->offset;
+        last = ctx->offset + ngx_buf_size(buf);
+
+        range = ctx->ranges.elts;
+        for (i = 0; i < ctx->ranges.nelts; i++) {
+            if (start > range[i].start || last < range[i].end) {
+                goto overlapped;
+            }
+        }
+    }
+
+    ctx->offset = ngx_buf_size(buf);
+
+    return NGX_OK;
+
+overlapped:
+
+    ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                  "range in overlapped buffers");
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_range_singlepart_body(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
+{
+    off_t              start, last;
+    ngx_buf_t         *buf;
+    ngx_chain_t       *out, *cl, **ll;
+    ngx_http_range_t  *range;
+
+    out = NULL;
+    ll = &out;
+    range = ctx->ranges.elts;
+
+    for (cl = in; cl; cl = cl->next) {
+
+        buf = cl->buf;
+
+        start = ctx->offset;
+        last = ctx->offset + ngx_buf_size(buf);
+
+        ctx->offset = last;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http range body buf: %O-%O", start, last);
+
+        if (ngx_buf_special(buf)) {
+            *ll = cl;
+            ll = &cl->next;
+            continue;
+        }
+
+        if (range->end <= start || range->start >= last) {
+
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http range body skip");
+
+            if (buf->in_file) {
+                buf->file_pos = buf->file_last;
+            }
+
+            buf->pos = buf->last;
+            buf->sync = 1;
+
+            continue;
+        }
+
+        if (range->start > start) {
+
+            if (buf->in_file) {
+                buf->file_pos += range->start - start;
+            }
+
+            if (ngx_buf_in_memory(buf)) {
+                buf->pos += (size_t) (range->start - start);
+            }
+        }
+
+        if (range->end <= last) {
+
+            if (buf->in_file) {
+                buf->file_last -= last - range->end;
+            }
+
+            if (ngx_buf_in_memory(buf)) {
+                buf->last -= (size_t) (last - range->end);
+            }
+
+            buf->last_buf = (r == r->main) ? 1 : 0;
+            buf->last_in_chain = 1;
+            *ll = cl;
+            cl->next = NULL;
+
+            break;
+        }
+
+        *ll = cl;
+        ll = &cl->next;
+    }
+
+    if (out == NULL) {
+        return NGX_OK;
+    }
+
+    return ngx_http_next_body_filter(r, out);
+}
+
+
+static ngx_int_t
+ngx_http_range_multipart_body(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
+{
+    ngx_buf_t         *b, *buf;
+    ngx_uint_t         i;
+    ngx_chain_t       *out, *hcl, *rcl, *dcl, **ll;
+    ngx_http_range_t  *range;
+
+    ll = &out;
+    buf = in->buf;
+    range = ctx->ranges.elts;
+
+    for (i = 0; i < ctx->ranges.nelts; i++) {
+
+        /*
+         * The boundary header of the range:
+         * CRLF
+         * "--0123456789" CRLF
+         * "Content-Type: image/jpeg" CRLF
+         * "Content-Range: bytes "
+         */
+
+        b = ngx_calloc_buf(r->pool);
+        if (b == NULL) {
+            return NGX_ERROR;
+        }
+
+        b->memory = 1;
+        b->pos = ctx->boundary_header.data;
+        b->last = ctx->boundary_header.data + ctx->boundary_header.len;
+
+        hcl = ngx_alloc_chain_link(r->pool);
+        if (hcl == NULL) {
+            return NGX_ERROR;
+        }
+
+        hcl->buf = b;
+
+
+        /* "SSSS-EEEE/TTTT" CRLF CRLF */
+
+        b = ngx_calloc_buf(r->pool);
+        if (b == NULL) {
+            return NGX_ERROR;
+        }
+
+        b->temporary = 1;
+        b->pos = range[i].content_range.data;
+        b->last = range[i].content_range.data + range[i].content_range.len;
+
+        rcl = ngx_alloc_chain_link(r->pool);
+        if (rcl == NULL) {
+            return NGX_ERROR;
+        }
+
+        rcl->buf = b;
+
+
+        /* the range data */
+
+        b = ngx_calloc_buf(r->pool);
+        if (b == NULL) {
+            return NGX_ERROR;
+        }
+
+        b->in_file = buf->in_file;
+        b->temporary = buf->temporary;
+        b->memory = buf->memory;
+        b->mmap = buf->mmap;
+        b->file = buf->file;
+
+        if (buf->in_file) {
+            b->file_pos = buf->file_pos + range[i].start;
+            b->file_last = buf->file_pos + range[i].end;
+        }
+
+        if (ngx_buf_in_memory(buf)) {
+            b->pos = buf->pos + (size_t) range[i].start;
+            b->last = buf->pos + (size_t) range[i].end;
+        }
+
+        dcl = ngx_alloc_chain_link(r->pool);
+        if (dcl == NULL) {
+            return NGX_ERROR;
+        }
+
+        dcl->buf = b;
+
+        *ll = hcl;
+        hcl->next = rcl;
+        rcl->next = dcl;
+        ll = &dcl->next;
+    }
+
+    /* the last boundary CRLF "--0123456789--" CRLF  */
+
+    b = ngx_calloc_buf(r->pool);
+    if (b == NULL) {
+        return NGX_ERROR;
+    }
+
+    b->temporary = 1;
+    b->last_buf = 1;
+
+    b->pos = ngx_pnalloc(r->pool, sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN
+                                  + sizeof("--" CRLF) - 1);
+    if (b->pos == NULL) {
+        return NGX_ERROR;
+    }
+
+    b->last = ngx_cpymem(b->pos, ctx->boundary_header.data,
+                         sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN);
+    *b->last++ = '-'; *b->last++ = '-';
+    *b->last++ = CR; *b->last++ = LF;
+
+    hcl = ngx_alloc_chain_link(r->pool);
+    if (hcl == NULL) {
+        return NGX_ERROR;
+    }
+
+    hcl->buf = b;
+    hcl->next = NULL;
+
+    *ll = hcl;
+
+    return ngx_http_next_body_filter(r, out);
+}
+
+
+static ngx_int_t
+ngx_http_range_header_filter_init(ngx_conf_t *cf)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_range_header_filter;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_range_body_filter_init(ngx_conf_t *cf)
+{
+    ngx_http_next_body_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_range_body_filter;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_realip_module.c b/nginx/src/http/modules/ngx_http_realip_module.c
new file mode 100644 (file)
index 0000000..7d3f2a9
--- /dev/null
@@ -0,0 +1,623 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_REALIP_XREALIP  0
+#define NGX_HTTP_REALIP_XFWD     1
+#define NGX_HTTP_REALIP_HEADER   2
+#define NGX_HTTP_REALIP_PROXY    3
+
+
+typedef struct {
+    ngx_array_t       *from;     /* array of ngx_cidr_t */
+    ngx_uint_t         type;
+    ngx_uint_t         hash;
+    ngx_str_t          header;
+    ngx_flag_t         recursive;
+} ngx_http_realip_loc_conf_t;
+
+
+typedef struct {
+    ngx_connection_t  *connection;
+    struct sockaddr   *sockaddr;
+    socklen_t          socklen;
+    ngx_str_t          addr_text;
+} ngx_http_realip_ctx_t;
+
+
+static ngx_int_t ngx_http_realip_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_realip_set_addr(ngx_http_request_t *r,
+    ngx_addr_t *addr);
+static void ngx_http_realip_cleanup(void *data);
+static char *ngx_http_realip_from(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_realip(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static void *ngx_http_realip_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_realip_merge_loc_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+static ngx_int_t ngx_http_realip_add_variables(ngx_conf_t *cf);
+static ngx_int_t ngx_http_realip_init(ngx_conf_t *cf);
+static ngx_http_realip_ctx_t *ngx_http_realip_get_module_ctx(
+    ngx_http_request_t *r);
+
+
+static ngx_int_t ngx_http_realip_remote_addr_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_realip_remote_port_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+
+
+static ngx_command_t  ngx_http_realip_commands[] = {
+
+    { ngx_string("set_real_ip_from"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_realip_from,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("real_ip_header"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_realip,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("real_ip_recursive"),
+      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_realip_loc_conf_t, recursive),
+      NULL },
+
+      ngx_null_command
+};
+
+
+
+static ngx_http_module_t  ngx_http_realip_module_ctx = {
+    ngx_http_realip_add_variables,         /* preconfiguration */
+    ngx_http_realip_init,                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_realip_create_loc_conf,       /* create location configuration */
+    ngx_http_realip_merge_loc_conf         /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_realip_module = {
+    NGX_MODULE_V1,
+    &ngx_http_realip_module_ctx,           /* module context */
+    ngx_http_realip_commands,              /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_variable_t  ngx_http_realip_vars[] = {
+
+    { ngx_string("realip_remote_addr"), NULL,
+      ngx_http_realip_remote_addr_variable, 0, 0, 0 },
+
+    { ngx_string("realip_remote_port"), NULL,
+      ngx_http_realip_remote_port_variable, 0, 0, 0 },
+
+      ngx_http_null_variable
+};
+
+
+static ngx_int_t
+ngx_http_realip_handler(ngx_http_request_t *r)
+{
+    u_char                      *p;
+    size_t                       len;
+    ngx_str_t                   *value;
+    ngx_uint_t                   i, hash;
+    ngx_addr_t                   addr;
+    ngx_array_t                 *xfwd;
+    ngx_list_part_t             *part;
+    ngx_table_elt_t             *header;
+    ngx_connection_t            *c;
+    ngx_http_realip_ctx_t       *ctx;
+    ngx_http_realip_loc_conf_t  *rlcf;
+
+    rlcf = ngx_http_get_module_loc_conf(r, ngx_http_realip_module);
+
+    if (rlcf->from == NULL) {
+        return NGX_DECLINED;
+    }
+
+    ctx = ngx_http_realip_get_module_ctx(r);
+
+    if (ctx) {
+        return NGX_DECLINED;
+    }
+
+    switch (rlcf->type) {
+
+    case NGX_HTTP_REALIP_XREALIP:
+
+        if (r->headers_in.x_real_ip == NULL) {
+            return NGX_DECLINED;
+        }
+
+        value = &r->headers_in.x_real_ip->value;
+        xfwd = NULL;
+
+        break;
+
+    case NGX_HTTP_REALIP_XFWD:
+
+        xfwd = &r->headers_in.x_forwarded_for;
+
+        if (xfwd->elts == NULL) {
+            return NGX_DECLINED;
+        }
+
+        value = NULL;
+
+        break;
+
+    case NGX_HTTP_REALIP_PROXY:
+
+        value = &r->connection->proxy_protocol_addr;
+
+        if (value->len == 0) {
+            return NGX_DECLINED;
+        }
+
+        xfwd = NULL;
+
+        break;
+
+    default: /* NGX_HTTP_REALIP_HEADER */
+
+        part = &r->headers_in.headers.part;
+        header = part->elts;
+
+        hash = rlcf->hash;
+        len = rlcf->header.len;
+        p = rlcf->header.data;
+
+        for (i = 0; /* void */ ; i++) {
+
+            if (i >= part->nelts) {
+                if (part->next == NULL) {
+                    break;
+                }
+
+                part = part->next;
+                header = part->elts;
+                i = 0;
+            }
+
+            if (hash == header[i].hash
+                && len == header[i].key.len
+                && ngx_strncmp(p, header[i].lowcase_key, len) == 0)
+            {
+                value = &header[i].value;
+                xfwd = NULL;
+
+                goto found;
+            }
+        }
+
+        return NGX_DECLINED;
+    }
+
+found:
+
+    c = r->connection;
+
+    addr.sockaddr = c->sockaddr;
+    addr.socklen = c->socklen;
+    /* addr.name = c->addr_text; */
+
+    if (ngx_http_get_forwarded_addr(r, &addr, xfwd, value, rlcf->from,
+                                    rlcf->recursive)
+        != NGX_DECLINED)
+    {
+        if (rlcf->type == NGX_HTTP_REALIP_PROXY) {
+            ngx_inet_set_port(addr.sockaddr, c->proxy_protocol_port);
+        }
+
+        return ngx_http_realip_set_addr(r, &addr);
+    }
+
+    return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_http_realip_set_addr(ngx_http_request_t *r, ngx_addr_t *addr)
+{
+    size_t                  len;
+    u_char                 *p;
+    u_char                  text[NGX_SOCKADDR_STRLEN];
+    ngx_connection_t       *c;
+    ngx_pool_cleanup_t     *cln;
+    ngx_http_realip_ctx_t  *ctx;
+
+    cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_http_realip_ctx_t));
+    if (cln == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    ctx = cln->data;
+
+    c = r->connection;
+
+    len = ngx_sock_ntop(addr->sockaddr, addr->socklen, text,
+                        NGX_SOCKADDR_STRLEN, 0);
+    if (len == 0) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    p = ngx_pnalloc(c->pool, len);
+    if (p == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    ngx_memcpy(p, text, len);
+
+    cln->handler = ngx_http_realip_cleanup;
+    ngx_http_set_ctx(r, ctx, ngx_http_realip_module);
+
+    ctx->connection = c;
+    ctx->sockaddr = c->sockaddr;
+    ctx->socklen = c->socklen;
+    ctx->addr_text = c->addr_text;
+
+    c->sockaddr = addr->sockaddr;
+    c->socklen = addr->socklen;
+    c->addr_text.len = len;
+    c->addr_text.data = p;
+
+    return NGX_DECLINED;
+}
+
+
+static void
+ngx_http_realip_cleanup(void *data)
+{
+    ngx_http_realip_ctx_t *ctx = data;
+
+    ngx_connection_t  *c;
+
+    c = ctx->connection;
+
+    c->sockaddr = ctx->sockaddr;
+    c->socklen = ctx->socklen;
+    c->addr_text = ctx->addr_text;
+}
+
+
+static char *
+ngx_http_realip_from(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_realip_loc_conf_t *rlcf = 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 (rlcf->from == NULL) {
+        rlcf->from = ngx_array_create(cf->pool, 2,
+                                      sizeof(ngx_cidr_t));
+        if (rlcf->from == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+
+    if (ngx_strcmp(value[1].data, "unix:") == 0) {
+        cidr = ngx_array_push(rlcf->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(rlcf->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(rlcf->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 char *
+ngx_http_realip(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_realip_loc_conf_t *rlcf = conf;
+
+    ngx_str_t  *value;
+
+    if (rlcf->type != NGX_CONF_UNSET_UINT) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    if (ngx_strcmp(value[1].data, "X-Real-IP") == 0) {
+        rlcf->type = NGX_HTTP_REALIP_XREALIP;
+        return NGX_CONF_OK;
+    }
+
+    if (ngx_strcmp(value[1].data, "X-Forwarded-For") == 0) {
+        rlcf->type = NGX_HTTP_REALIP_XFWD;
+        return NGX_CONF_OK;
+    }
+
+    if (ngx_strcmp(value[1].data, "proxy_protocol") == 0) {
+        rlcf->type = NGX_HTTP_REALIP_PROXY;
+        return NGX_CONF_OK;
+    }
+
+    rlcf->type = NGX_HTTP_REALIP_HEADER;
+    rlcf->hash = ngx_hash_strlow(value[1].data, value[1].data, value[1].len);
+    rlcf->header = value[1];
+
+    return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_http_realip_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_realip_loc_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_realip_loc_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->from = NULL;
+     *     conf->hash = 0;
+     *     conf->header = { 0, NULL };
+     */
+
+    conf->type = NGX_CONF_UNSET_UINT;
+    conf->recursive = NGX_CONF_UNSET;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_realip_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_realip_loc_conf_t  *prev = parent;
+    ngx_http_realip_loc_conf_t  *conf = child;
+
+    if (conf->from == NULL) {
+        conf->from = prev->from;
+    }
+
+    ngx_conf_merge_uint_value(conf->type, prev->type, NGX_HTTP_REALIP_XREALIP);
+    ngx_conf_merge_value(conf->recursive, prev->recursive, 0);
+
+    if (conf->header.len == 0) {
+        conf->hash = prev->hash;
+        conf->header = prev->header;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_realip_add_variables(ngx_conf_t *cf)
+{
+    ngx_http_variable_t  *var, *v;
+
+    for (v = ngx_http_realip_vars; v->name.len; v++) {
+        var = ngx_http_add_variable(cf, &v->name, v->flags);
+        if (var == NULL) {
+            return NGX_ERROR;
+        }
+
+        var->get_handler = v->get_handler;
+        var->data = v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_realip_init(ngx_conf_t *cf)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_POST_READ_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_realip_handler;
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_realip_handler;
+
+    return NGX_OK;
+}
+
+
+static ngx_http_realip_ctx_t *
+ngx_http_realip_get_module_ctx(ngx_http_request_t *r)
+{
+    ngx_pool_cleanup_t     *cln;
+    ngx_http_realip_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_realip_module);
+
+    if (ctx == NULL && (r->internal || r->filter_finalize)) {
+
+        /*
+         * if module context was reset, the original address
+         * can still be found in the cleanup handler
+         */
+
+        for (cln = r->pool->cleanup; cln; cln = cln->next) {
+            if (cln->handler == ngx_http_realip_cleanup) {
+                ctx = cln->data;
+                break;
+            }
+        }
+    }
+
+    return ctx;
+}
+
+
+static ngx_int_t
+ngx_http_realip_remote_addr_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_str_t              *addr_text;
+    ngx_http_realip_ctx_t  *ctx;
+
+    ctx = ngx_http_realip_get_module_ctx(r);
+
+    addr_text = ctx ? &ctx->addr_text : &r->connection->addr_text;
+
+    v->len = addr_text->len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = addr_text->data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_realip_remote_port_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_uint_t              port;
+    struct sockaddr        *sa;
+    ngx_http_realip_ctx_t  *ctx;
+
+    ctx = ngx_http_realip_get_module_ctx(r);
+
+    sa = ctx ? ctx->sockaddr : r->connection->sockaddr;
+
+    v->len = 0;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    v->data = ngx_pnalloc(r->pool, sizeof("65535") - 1);
+    if (v->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    port = ngx_inet_get_port(sa);
+
+    if (port > 0 && port < 65536) {
+        v->len = ngx_sprintf(v->data, "%ui", port) - v->data;
+    }
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_referer_module.c b/nginx/src/http/modules/ngx_http_referer_module.c
new file mode 100644 (file)
index 0000000..599dd3a
--- /dev/null
@@ -0,0 +1,682 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_REFERER_NO_URI_PART  ((void *) 4)
+
+
+typedef struct {
+    ngx_hash_combined_t      hash;
+
+#if (NGX_PCRE)
+    ngx_array_t             *regex;
+    ngx_array_t             *server_name_regex;
+#endif
+
+    ngx_flag_t               no_referer;
+    ngx_flag_t               blocked_referer;
+    ngx_flag_t               server_names;
+
+    ngx_hash_keys_arrays_t  *keys;
+
+    ngx_uint_t               referer_hash_max_size;
+    ngx_uint_t               referer_hash_bucket_size;
+} ngx_http_referer_conf_t;
+
+
+static ngx_int_t ngx_http_referer_add_variables(ngx_conf_t *cf);
+static void * ngx_http_referer_create_conf(ngx_conf_t *cf);
+static char * ngx_http_referer_merge_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static char *ngx_http_valid_referers(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static ngx_int_t ngx_http_add_referer(ngx_conf_t *cf,
+    ngx_hash_keys_arrays_t *keys, ngx_str_t *value, ngx_str_t *uri);
+static ngx_int_t ngx_http_add_regex_referer(ngx_conf_t *cf,
+    ngx_http_referer_conf_t *rlcf, ngx_str_t *name);
+#if (NGX_PCRE)
+static ngx_int_t ngx_http_add_regex_server_name(ngx_conf_t *cf,
+    ngx_http_referer_conf_t *rlcf, ngx_http_regex_t *regex);
+#endif
+static int ngx_libc_cdecl ngx_http_cmp_referer_wildcards(const void *one,
+    const void *two);
+
+
+static ngx_command_t  ngx_http_referer_commands[] = {
+
+    { ngx_string("valid_referers"),
+      NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_valid_referers,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("referer_hash_max_size"),
+      NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_referer_conf_t, referer_hash_max_size),
+      NULL },
+
+    { ngx_string("referer_hash_bucket_size"),
+      NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_referer_conf_t, referer_hash_bucket_size),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_referer_module_ctx = {
+    ngx_http_referer_add_variables,        /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_referer_create_conf,          /* create location configuration */
+    ngx_http_referer_merge_conf            /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_referer_module = {
+    NGX_MODULE_V1,
+    &ngx_http_referer_module_ctx,          /* module context */
+    ngx_http_referer_commands,             /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t  ngx_http_invalid_referer_name = ngx_string("invalid_referer");
+
+
+static ngx_int_t
+ngx_http_referer_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+    uintptr_t data)
+{
+    u_char                    *p, *ref, *last;
+    size_t                     len;
+    ngx_str_t                 *uri;
+    ngx_uint_t                 i, key;
+    ngx_http_referer_conf_t   *rlcf;
+    u_char                     buf[256];
+#if (NGX_PCRE)
+    ngx_int_t                  rc;
+    ngx_str_t                  referer;
+#endif
+
+    rlcf = ngx_http_get_module_loc_conf(r, ngx_http_referer_module);
+
+    if (rlcf->hash.hash.buckets == NULL
+        && rlcf->hash.wc_head == NULL
+        && rlcf->hash.wc_tail == NULL
+#if (NGX_PCRE)
+        && rlcf->regex == NULL
+        && rlcf->server_name_regex == NULL
+#endif
+       )
+    {
+        goto valid;
+    }
+
+    if (r->headers_in.referer == NULL) {
+        if (rlcf->no_referer) {
+            goto valid;
+        }
+
+        goto invalid;
+    }
+
+    len = r->headers_in.referer->value.len;
+    ref = r->headers_in.referer->value.data;
+
+    if (len >= sizeof("http://i.ru") - 1) {
+        last = ref + len;
+
+        if (ngx_strncasecmp(ref, (u_char *) "http://", 7) == 0) {
+            ref += 7;
+            len -= 7;
+            goto valid_scheme;
+
+        } else if (ngx_strncasecmp(ref, (u_char *) "https://", 8) == 0) {
+            ref += 8;
+            len -= 8;
+            goto valid_scheme;
+        }
+    }
+
+    if (rlcf->blocked_referer) {
+        goto valid;
+    }
+
+    goto invalid;
+
+valid_scheme:
+
+    i = 0;
+    key = 0;
+
+    for (p = ref; p < last; p++) {
+        if (*p == '/' || *p == ':') {
+            break;
+        }
+
+        if (i == 256) {
+            goto invalid;
+        }
+
+        buf[i] = ngx_tolower(*p);
+        key = ngx_hash(key, buf[i++]);
+    }
+
+    uri = ngx_hash_find_combined(&rlcf->hash, key, buf, p - ref);
+
+    if (uri) {
+        goto uri;
+    }
+
+#if (NGX_PCRE)
+
+    if (rlcf->server_name_regex) {
+        referer.len = p - ref;
+        referer.data = buf;
+
+        rc = ngx_regex_exec_array(rlcf->server_name_regex, &referer,
+                                  r->connection->log);
+
+        if (rc == NGX_OK) {
+            goto valid;
+        }
+
+        if (rc == NGX_ERROR) {
+            return rc;
+        }
+
+        /* NGX_DECLINED */
+    }
+
+    if (rlcf->regex) {
+        referer.len = len;
+        referer.data = ref;
+
+        rc = ngx_regex_exec_array(rlcf->regex, &referer, r->connection->log);
+
+        if (rc == NGX_OK) {
+            goto valid;
+        }
+
+        if (rc == NGX_ERROR) {
+            return rc;
+        }
+
+        /* NGX_DECLINED */
+    }
+
+#endif
+
+invalid:
+
+    *v = ngx_http_variable_true_value;
+
+    return NGX_OK;
+
+uri:
+
+    for ( /* void */ ; p < last; p++) {
+        if (*p == '/') {
+            break;
+        }
+    }
+
+    len = last - p;
+
+    if (uri == NGX_HTTP_REFERER_NO_URI_PART) {
+        goto valid;
+    }
+
+    if (len < uri->len || ngx_strncmp(uri->data, p, uri->len) != 0) {
+        goto invalid;
+    }
+
+valid:
+
+    *v = ngx_http_variable_null_value;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_referer_add_variables(ngx_conf_t *cf)
+{
+    ngx_http_variable_t  *var;
+
+    var = ngx_http_add_variable(cf, &ngx_http_invalid_referer_name,
+                                NGX_HTTP_VAR_CHANGEABLE);
+    if (var == NULL) {
+        return NGX_ERROR;
+    }
+
+    var->get_handler = ngx_http_referer_variable;
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_referer_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_referer_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_referer_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->hash = { NULL };
+     *     conf->server_names = 0;
+     *     conf->keys = NULL;
+     */
+
+#if (NGX_PCRE)
+    conf->regex = NGX_CONF_UNSET_PTR;
+    conf->server_name_regex = NGX_CONF_UNSET_PTR;
+#endif
+
+    conf->no_referer = NGX_CONF_UNSET;
+    conf->blocked_referer = NGX_CONF_UNSET;
+    conf->referer_hash_max_size = NGX_CONF_UNSET_UINT;
+    conf->referer_hash_bucket_size = NGX_CONF_UNSET_UINT;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_referer_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_referer_conf_t *prev = parent;
+    ngx_http_referer_conf_t *conf = child;
+
+    ngx_uint_t                 n;
+    ngx_hash_init_t            hash;
+    ngx_http_server_name_t    *sn;
+    ngx_http_core_srv_conf_t  *cscf;
+
+    if (conf->keys == NULL) {
+        conf->hash = prev->hash;
+
+#if (NGX_PCRE)
+        ngx_conf_merge_ptr_value(conf->regex, prev->regex, NULL);
+        ngx_conf_merge_ptr_value(conf->server_name_regex,
+                                 prev->server_name_regex, NULL);
+#endif
+        ngx_conf_merge_value(conf->no_referer, prev->no_referer, 0);
+        ngx_conf_merge_value(conf->blocked_referer, prev->blocked_referer, 0);
+        ngx_conf_merge_uint_value(conf->referer_hash_max_size,
+                                  prev->referer_hash_max_size, 2048);
+        ngx_conf_merge_uint_value(conf->referer_hash_bucket_size,
+                                  prev->referer_hash_bucket_size, 64);
+
+        return NGX_CONF_OK;
+    }
+
+    if (conf->server_names == 1) {
+        cscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_core_module);
+
+        sn = cscf->server_names.elts;
+        for (n = 0; n < cscf->server_names.nelts; n++) {
+
+#if (NGX_PCRE)
+            if (sn[n].regex) {
+
+                if (ngx_http_add_regex_server_name(cf, conf, sn[n].regex)
+                    != NGX_OK)
+                {
+                    return NGX_CONF_ERROR;
+                }
+
+                continue;
+            }
+#endif
+
+            if (ngx_http_add_referer(cf, conf->keys, &sn[n].name, NULL)
+                != NGX_OK)
+            {
+                return NGX_CONF_ERROR;
+            }
+        }
+    }
+
+    if ((conf->no_referer == 1 || conf->blocked_referer == 1)
+        && conf->keys->keys.nelts == 0
+        && conf->keys->dns_wc_head.nelts == 0
+        && conf->keys->dns_wc_tail.nelts == 0)
+    {
+        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                      "the \"none\" or \"blocked\" referers are specified "
+                      "in the \"valid_referers\" directive "
+                      "without any valid referer");
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_conf_merge_uint_value(conf->referer_hash_max_size,
+                              prev->referer_hash_max_size, 2048);
+    ngx_conf_merge_uint_value(conf->referer_hash_bucket_size,
+                              prev->referer_hash_bucket_size, 64);
+    conf->referer_hash_bucket_size = ngx_align(conf->referer_hash_bucket_size,
+                                               ngx_cacheline_size);
+
+    hash.key = ngx_hash_key_lc;
+    hash.max_size = conf->referer_hash_max_size;
+    hash.bucket_size = conf->referer_hash_bucket_size;
+    hash.name = "referer_hash";
+    hash.pool = cf->pool;
+
+    if (conf->keys->keys.nelts) {
+        hash.hash = &conf->hash.hash;
+        hash.temp_pool = NULL;
+
+        if (ngx_hash_init(&hash, conf->keys->keys.elts, conf->keys->keys.nelts)
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (conf->keys->dns_wc_head.nelts) {
+
+        ngx_qsort(conf->keys->dns_wc_head.elts,
+                  (size_t) conf->keys->dns_wc_head.nelts,
+                  sizeof(ngx_hash_key_t),
+                  ngx_http_cmp_referer_wildcards);
+
+        hash.hash = NULL;
+        hash.temp_pool = cf->temp_pool;
+
+        if (ngx_hash_wildcard_init(&hash, conf->keys->dns_wc_head.elts,
+                                   conf->keys->dns_wc_head.nelts)
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+
+        conf->hash.wc_head = (ngx_hash_wildcard_t *) hash.hash;
+    }
+
+    if (conf->keys->dns_wc_tail.nelts) {
+
+        ngx_qsort(conf->keys->dns_wc_tail.elts,
+                  (size_t) conf->keys->dns_wc_tail.nelts,
+                  sizeof(ngx_hash_key_t),
+                  ngx_http_cmp_referer_wildcards);
+
+        hash.hash = NULL;
+        hash.temp_pool = cf->temp_pool;
+
+        if (ngx_hash_wildcard_init(&hash, conf->keys->dns_wc_tail.elts,
+                                   conf->keys->dns_wc_tail.nelts)
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+
+        conf->hash.wc_tail = (ngx_hash_wildcard_t *) hash.hash;
+    }
+
+#if (NGX_PCRE)
+    ngx_conf_merge_ptr_value(conf->regex, prev->regex, NULL);
+    ngx_conf_merge_ptr_value(conf->server_name_regex, prev->server_name_regex,
+                             NULL);
+#endif
+
+    if (conf->no_referer == NGX_CONF_UNSET) {
+        conf->no_referer = 0;
+    }
+
+    if (conf->blocked_referer == NGX_CONF_UNSET) {
+        conf->blocked_referer = 0;
+    }
+
+    conf->keys = NULL;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_valid_referers(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_referer_conf_t  *rlcf = conf;
+
+    u_char      *p;
+    ngx_str_t   *value, uri;
+    ngx_uint_t   i;
+
+    if (rlcf->keys == NULL) {
+        rlcf->keys = ngx_pcalloc(cf->temp_pool, sizeof(ngx_hash_keys_arrays_t));
+        if (rlcf->keys == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        rlcf->keys->pool = cf->pool;
+        rlcf->keys->temp_pool = cf->pool;
+
+        if (ngx_hash_keys_array_init(rlcf->keys, NGX_HASH_SMALL) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    value = cf->args->elts;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+        if (value[i].len == 0) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid referer \"%V\"", &value[i]);
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_strcmp(value[i].data, "none") == 0) {
+            rlcf->no_referer = 1;
+            continue;
+        }
+
+        if (ngx_strcmp(value[i].data, "blocked") == 0) {
+            rlcf->blocked_referer = 1;
+            continue;
+        }
+
+        if (ngx_strcmp(value[i].data, "server_names") == 0) {
+            rlcf->server_names = 1;
+            continue;
+        }
+
+        if (value[i].data[0] == '~') {
+            if (ngx_http_add_regex_referer(cf, rlcf, &value[i]) != NGX_OK) {
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        ngx_str_null(&uri);
+
+        p = (u_char *) ngx_strchr(value[i].data, '/');
+
+        if (p) {
+            uri.len = (value[i].data + value[i].len) - p;
+            uri.data = p;
+            value[i].len = p - value[i].data;
+        }
+
+        if (ngx_http_add_referer(cf, rlcf->keys, &value[i], &uri) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_add_referer(ngx_conf_t *cf, ngx_hash_keys_arrays_t *keys,
+    ngx_str_t *value, ngx_str_t *uri)
+{
+    ngx_int_t   rc;
+    ngx_str_t  *u;
+
+    if (uri == NULL || uri->len == 0) {
+        u = NGX_HTTP_REFERER_NO_URI_PART;
+
+    } else {
+        u = ngx_palloc(cf->pool, sizeof(ngx_str_t));
+        if (u == NULL) {
+            return NGX_ERROR;
+        }
+
+        *u = *uri;
+    }
+
+    rc = ngx_hash_add_key(keys, value, u, NGX_HASH_WILDCARD_KEY);
+
+    if (rc == NGX_OK) {
+        return NGX_OK;
+    }
+
+    if (rc == NGX_DECLINED) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid hostname or wildcard \"%V\"", value);
+    }
+
+    if (rc == NGX_BUSY) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "conflicting parameter \"%V\"", value);
+    }
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_add_regex_referer(ngx_conf_t *cf, ngx_http_referer_conf_t *rlcf,
+    ngx_str_t *name)
+{
+#if (NGX_PCRE)
+    ngx_regex_elt_t      *re;
+    ngx_regex_compile_t   rc;
+    u_char                errstr[NGX_MAX_CONF_ERRSTR];
+
+    if (name->len == 1) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "empty regex in \"%V\"", name);
+        return NGX_ERROR;
+    }
+
+    if (rlcf->regex == NGX_CONF_UNSET_PTR) {
+        rlcf->regex = ngx_array_create(cf->pool, 2, sizeof(ngx_regex_elt_t));
+        if (rlcf->regex == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    re = ngx_array_push(rlcf->regex);
+    if (re == NULL) {
+        return NGX_ERROR;
+    }
+
+    name->len--;
+    name->data++;
+
+    ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
+
+    rc.pattern = *name;
+    rc.pool = cf->pool;
+    rc.options = NGX_REGEX_CASELESS;
+    rc.err.len = NGX_MAX_CONF_ERRSTR;
+    rc.err.data = errstr;
+
+    if (ngx_regex_compile(&rc) != NGX_OK) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err);
+        return NGX_ERROR;
+    }
+
+    re->regex = rc.regex;
+    re->name = name->data;
+
+    return NGX_OK;
+
+#else
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "the using of the regex \"%V\" requires PCRE library",
+                       name);
+
+    return NGX_ERROR;
+
+#endif
+}
+
+
+#if (NGX_PCRE)
+
+static ngx_int_t
+ngx_http_add_regex_server_name(ngx_conf_t *cf, ngx_http_referer_conf_t *rlcf,
+    ngx_http_regex_t *regex)
+{
+    ngx_regex_elt_t  *re;
+
+    if (rlcf->server_name_regex == NGX_CONF_UNSET_PTR) {
+        rlcf->server_name_regex = ngx_array_create(cf->pool, 2,
+                                                   sizeof(ngx_regex_elt_t));
+        if (rlcf->server_name_regex == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    re = ngx_array_push(rlcf->server_name_regex);
+    if (re == NULL) {
+        return NGX_ERROR;
+    }
+
+    re->regex = regex->regex;
+    re->name = regex->name.data;
+
+    return NGX_OK;
+}
+
+#endif
+
+
+static int ngx_libc_cdecl
+ngx_http_cmp_referer_wildcards(const void *one, const void *two)
+{
+    ngx_hash_key_t  *first, *second;
+
+    first = (ngx_hash_key_t *) one;
+    second = (ngx_hash_key_t *) two;
+
+    return ngx_dns_strcmp(first->key.data, second->key.data);
+}
diff --git a/nginx/src/http/modules/ngx_http_rewrite_module.c b/nginx/src/http/modules/ngx_http_rewrite_module.c
new file mode 100644 (file)
index 0000000..a6f1fc8
--- /dev/null
@@ -0,0 +1,1022 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_array_t  *codes;        /* uintptr_t */
+
+    ngx_uint_t    stack_size;
+
+    ngx_flag_t    log;
+    ngx_flag_t    uninitialized_variable_warn;
+} ngx_http_rewrite_loc_conf_t;
+
+
+static void *ngx_http_rewrite_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_rewrite_merge_loc_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+static ngx_int_t ngx_http_rewrite_init(ngx_conf_t *cf);
+static char *ngx_http_rewrite(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_http_rewrite_return(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_rewrite_break(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_rewrite_if(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char * ngx_http_rewrite_if_condition(ngx_conf_t *cf,
+    ngx_http_rewrite_loc_conf_t *lcf);
+static char *ngx_http_rewrite_variable(ngx_conf_t *cf,
+    ngx_http_rewrite_loc_conf_t *lcf, ngx_str_t *value);
+static char *ngx_http_rewrite_set(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char * ngx_http_rewrite_value(ngx_conf_t *cf,
+    ngx_http_rewrite_loc_conf_t *lcf, ngx_str_t *value);
+
+
+static ngx_command_t  ngx_http_rewrite_commands[] = {
+
+    { ngx_string("rewrite"),
+      NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+                       |NGX_CONF_TAKE23,
+      ngx_http_rewrite,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("return"),
+      NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+                       |NGX_CONF_TAKE12,
+      ngx_http_rewrite_return,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("break"),
+      NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+                       |NGX_CONF_NOARGS,
+      ngx_http_rewrite_break,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("if"),
+      NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_1MORE,
+      ngx_http_rewrite_if,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("set"),
+      NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+                       |NGX_CONF_TAKE2,
+      ngx_http_rewrite_set,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("rewrite_log"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF
+                        |NGX_HTTP_LIF_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_rewrite_loc_conf_t, log),
+      NULL },
+
+    { ngx_string("uninitialized_variable_warn"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF
+                        |NGX_HTTP_LIF_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_rewrite_loc_conf_t, uninitialized_variable_warn),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_rewrite_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_rewrite_init,                 /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_rewrite_create_loc_conf,      /* create location configuration */
+    ngx_http_rewrite_merge_loc_conf        /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_rewrite_module = {
+    NGX_MODULE_V1,
+    &ngx_http_rewrite_module_ctx,          /* module context */
+    ngx_http_rewrite_commands,             /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_rewrite_handler(ngx_http_request_t *r)
+{
+    ngx_int_t                     index;
+    ngx_http_script_code_pt       code;
+    ngx_http_script_engine_t     *e;
+    ngx_http_core_srv_conf_t     *cscf;
+    ngx_http_core_main_conf_t    *cmcf;
+    ngx_http_rewrite_loc_conf_t  *rlcf;
+
+    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+    index = cmcf->phase_engine.location_rewrite_index;
+
+    if (r->phase_handler == index && r->loc_conf == cscf->ctx->loc_conf) {
+        /* skipping location rewrite phase for server null location */
+        return NGX_DECLINED;
+    }
+
+    rlcf = ngx_http_get_module_loc_conf(r, ngx_http_rewrite_module);
+
+    if (rlcf->codes == NULL) {
+        return NGX_DECLINED;
+    }
+
+    e = ngx_pcalloc(r->pool, sizeof(ngx_http_script_engine_t));
+    if (e == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    e->sp = ngx_pcalloc(r->pool,
+                        rlcf->stack_size * sizeof(ngx_http_variable_value_t));
+    if (e->sp == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    e->ip = rlcf->codes->elts;
+    e->request = r;
+    e->quote = 1;
+    e->log = rlcf->log;
+    e->status = NGX_DECLINED;
+
+    while (*(uintptr_t *) e->ip) {
+        code = *(ngx_http_script_code_pt *) e->ip;
+        code(e);
+    }
+
+    if (e->status < NGX_HTTP_BAD_REQUEST) {
+        return e->status;
+    }
+
+    if (r->err_status == 0) {
+        return e->status;
+    }
+
+    return r->err_status;
+}
+
+
+static ngx_int_t
+ngx_http_rewrite_var(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+    uintptr_t data)
+{
+    ngx_http_variable_t          *var;
+    ngx_http_core_main_conf_t    *cmcf;
+    ngx_http_rewrite_loc_conf_t  *rlcf;
+
+    rlcf = ngx_http_get_module_loc_conf(r, ngx_http_rewrite_module);
+
+    if (rlcf->uninitialized_variable_warn == 0) {
+        *v = ngx_http_variable_null_value;
+        return NGX_OK;
+    }
+
+    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+    var = cmcf->variables.elts;
+
+    /*
+     * the ngx_http_rewrite_module sets variables directly in r->variables,
+     * and they should be handled by ngx_http_get_indexed_variable(),
+     * so the handler is called only if the variable is not initialized
+     */
+
+    ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
+                  "using uninitialized \"%V\" variable", &var[data].name);
+
+    *v = ngx_http_variable_null_value;
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_rewrite_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_rewrite_loc_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_rewrite_loc_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    conf->stack_size = NGX_CONF_UNSET_UINT;
+    conf->log = NGX_CONF_UNSET;
+    conf->uninitialized_variable_warn = NGX_CONF_UNSET;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_rewrite_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_rewrite_loc_conf_t *prev = parent;
+    ngx_http_rewrite_loc_conf_t *conf = child;
+
+    uintptr_t  *code;
+
+    ngx_conf_merge_value(conf->log, prev->log, 0);
+    ngx_conf_merge_value(conf->uninitialized_variable_warn,
+                         prev->uninitialized_variable_warn, 1);
+    ngx_conf_merge_uint_value(conf->stack_size, prev->stack_size, 10);
+
+    if (conf->codes == NULL) {
+        return NGX_CONF_OK;
+    }
+
+    if (conf->codes == prev->codes) {
+        return NGX_CONF_OK;
+    }
+
+    code = ngx_array_push_n(conf->codes, sizeof(uintptr_t));
+    if (code == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    *code = (uintptr_t) NULL;
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_rewrite_init(ngx_conf_t *cf)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_SERVER_REWRITE_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_rewrite_handler;
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_rewrite_handler;
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_http_rewrite(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_rewrite_loc_conf_t  *lcf = conf;
+
+    ngx_str_t                         *value;
+    ngx_uint_t                         last;
+    ngx_regex_compile_t                rc;
+    ngx_http_script_code_pt           *code;
+    ngx_http_script_compile_t          sc;
+    ngx_http_script_regex_code_t      *regex;
+    ngx_http_script_regex_end_code_t  *regex_end;
+    u_char                             errstr[NGX_MAX_CONF_ERRSTR];
+
+    regex = ngx_http_script_start_code(cf->pool, &lcf->codes,
+                                       sizeof(ngx_http_script_regex_code_t));
+    if (regex == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memzero(regex, sizeof(ngx_http_script_regex_code_t));
+
+    value = cf->args->elts;
+
+    ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
+
+    rc.pattern = value[1];
+    rc.err.len = NGX_MAX_CONF_ERRSTR;
+    rc.err.data = errstr;
+
+    /* TODO: NGX_REGEX_CASELESS */
+
+    regex->regex = ngx_http_regex_compile(cf, &rc);
+    if (regex->regex == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    regex->code = ngx_http_script_regex_start_code;
+    regex->uri = 1;
+    regex->name = value[1];
+
+    if (value[2].data[value[2].len - 1] == '?') {
+
+        /* the last "?" drops the original arguments */
+        value[2].len--;
+
+    } else {
+        regex->add_args = 1;
+    }
+
+    last = 0;
+
+    if (ngx_strncmp(value[2].data, "http://", sizeof("http://") - 1) == 0
+        || ngx_strncmp(value[2].data, "https://", sizeof("https://") - 1) == 0
+        || ngx_strncmp(value[2].data, "$scheme", sizeof("$scheme") - 1) == 0)
+    {
+        regex->status = NGX_HTTP_MOVED_TEMPORARILY;
+        regex->redirect = 1;
+        last = 1;
+    }
+
+    if (cf->args->nelts == 4) {
+        if (ngx_strcmp(value[3].data, "last") == 0) {
+            last = 1;
+
+        } else if (ngx_strcmp(value[3].data, "break") == 0) {
+            regex->break_cycle = 1;
+            last = 1;
+
+        } else if (ngx_strcmp(value[3].data, "redirect") == 0) {
+            regex->status = NGX_HTTP_MOVED_TEMPORARILY;
+            regex->redirect = 1;
+            last = 1;
+
+        } else if (ngx_strcmp(value[3].data, "permanent") == 0) {
+            regex->status = NGX_HTTP_MOVED_PERMANENTLY;
+            regex->redirect = 1;
+            last = 1;
+
+        } else {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid parameter \"%V\"", &value[3]);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+    sc.cf = cf;
+    sc.source = &value[2];
+    sc.lengths = &regex->lengths;
+    sc.values = &lcf->codes;
+    sc.variables = ngx_http_script_variables_count(&value[2]);
+    sc.main = regex;
+    sc.complete_lengths = 1;
+    sc.compile_args = !regex->redirect;
+
+    if (ngx_http_script_compile(&sc) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    regex = sc.main;
+
+    regex->size = sc.size;
+    regex->args = sc.args;
+
+    if (sc.variables == 0 && !sc.dup_capture) {
+        regex->lengths = NULL;
+    }
+
+    regex_end = ngx_http_script_add_code(lcf->codes,
+                                      sizeof(ngx_http_script_regex_end_code_t),
+                                      &regex);
+    if (regex_end == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    regex_end->code = ngx_http_script_regex_end_code;
+    regex_end->uri = regex->uri;
+    regex_end->args = regex->args;
+    regex_end->add_args = regex->add_args;
+    regex_end->redirect = regex->redirect;
+
+    if (last) {
+        code = ngx_http_script_add_code(lcf->codes, sizeof(uintptr_t), &regex);
+        if (code == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        *code = NULL;
+    }
+
+    regex->next = (u_char *) lcf->codes->elts + lcf->codes->nelts
+                                              - (u_char *) regex;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_rewrite_return(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_rewrite_loc_conf_t  *lcf = conf;
+
+    u_char                            *p;
+    ngx_str_t                         *value, *v;
+    ngx_http_script_return_code_t     *ret;
+    ngx_http_compile_complex_value_t   ccv;
+
+    ret = ngx_http_script_start_code(cf->pool, &lcf->codes,
+                                     sizeof(ngx_http_script_return_code_t));
+    if (ret == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    value = cf->args->elts;
+
+    ngx_memzero(ret, sizeof(ngx_http_script_return_code_t));
+
+    ret->code = ngx_http_script_return_code;
+
+    p = value[1].data;
+
+    ret->status = ngx_atoi(p, value[1].len);
+
+    if (ret->status == (uintptr_t) NGX_ERROR) {
+
+        if (cf->args->nelts == 2
+            && (ngx_strncmp(p, "http://", sizeof("http://") - 1) == 0
+                || ngx_strncmp(p, "https://", sizeof("https://") - 1) == 0
+                || ngx_strncmp(p, "$scheme", sizeof("$scheme") - 1) == 0))
+        {
+            ret->status = NGX_HTTP_MOVED_TEMPORARILY;
+            v = &value[1];
+
+        } else {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid return code \"%V\"", &value[1]);
+            return NGX_CONF_ERROR;
+        }
+
+    } else {
+
+        if (ret->status > 999) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid return code \"%V\"", &value[1]);
+            return NGX_CONF_ERROR;
+        }
+
+        if (cf->args->nelts == 2) {
+            return NGX_CONF_OK;
+        }
+
+        v = &value[2];
+    }
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = v;
+    ccv.complex_value = &ret->text;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_rewrite_break(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_rewrite_loc_conf_t *lcf = conf;
+
+    ngx_http_script_code_pt  *code;
+
+    code = ngx_http_script_start_code(cf->pool, &lcf->codes, sizeof(uintptr_t));
+    if (code == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    *code = ngx_http_script_break_code;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_rewrite_if(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_rewrite_loc_conf_t  *lcf = conf;
+
+    void                         *mconf;
+    char                         *rv;
+    u_char                       *elts;
+    ngx_uint_t                    i;
+    ngx_conf_t                    save;
+    ngx_http_module_t            *module;
+    ngx_http_conf_ctx_t          *ctx, *pctx;
+    ngx_http_core_loc_conf_t     *clcf, *pclcf;
+    ngx_http_script_if_code_t    *if_code;
+    ngx_http_rewrite_loc_conf_t  *nlcf;
+
+    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
+    if (ctx == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    pctx = cf->ctx;
+    ctx->main_conf = pctx->main_conf;
+    ctx->srv_conf = pctx->srv_conf;
+
+    ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
+    if (ctx->loc_conf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    for (i = 0; cf->cycle->modules[i]; i++) {
+        if (cf->cycle->modules[i]->type != NGX_HTTP_MODULE) {
+            continue;
+        }
+
+        module = cf->cycle->modules[i]->ctx;
+
+        if (module->create_loc_conf) {
+
+            mconf = module->create_loc_conf(cf);
+            if (mconf == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            ctx->loc_conf[cf->cycle->modules[i]->ctx_index] = mconf;
+        }
+    }
+
+    pclcf = pctx->loc_conf[ngx_http_core_module.ctx_index];
+
+    clcf = ctx->loc_conf[ngx_http_core_module.ctx_index];
+    clcf->loc_conf = ctx->loc_conf;
+    clcf->name = pclcf->name;
+    clcf->noname = 1;
+
+    if (ngx_http_add_location(cf, &pclcf->locations, clcf) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (ngx_http_rewrite_if_condition(cf, lcf) != NGX_CONF_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if_code = ngx_array_push_n(lcf->codes, sizeof(ngx_http_script_if_code_t));
+    if (if_code == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    if_code->code = ngx_http_script_if_code;
+
+    elts = lcf->codes->elts;
+
+
+    /* the inner directives must be compiled to the same code array */
+
+    nlcf = ctx->loc_conf[ngx_http_rewrite_module.ctx_index];
+    nlcf->codes = lcf->codes;
+
+
+    save = *cf;
+    cf->ctx = ctx;
+
+    if (cf->cmd_type == NGX_HTTP_SRV_CONF) {
+        if_code->loc_conf = NULL;
+        cf->cmd_type = NGX_HTTP_SIF_CONF;
+
+    } else {
+        if_code->loc_conf = ctx->loc_conf;
+        cf->cmd_type = NGX_HTTP_LIF_CONF;
+    }
+
+    rv = ngx_conf_parse(cf, NULL);
+
+    *cf = save;
+
+    if (rv != NGX_CONF_OK) {
+        return rv;
+    }
+
+
+    if (elts != lcf->codes->elts) {
+        if_code = (ngx_http_script_if_code_t *)
+                   ((u_char *) if_code + ((u_char *) lcf->codes->elts - elts));
+    }
+
+    if_code->next = (u_char *) lcf->codes->elts + lcf->codes->nelts
+                                                - (u_char *) if_code;
+
+    /* the code array belong to parent block */
+
+    nlcf->codes = NULL;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_rewrite_if_condition(ngx_conf_t *cf, ngx_http_rewrite_loc_conf_t *lcf)
+{
+    u_char                        *p;
+    size_t                         len;
+    ngx_str_t                     *value;
+    ngx_uint_t                     cur, last;
+    ngx_regex_compile_t            rc;
+    ngx_http_script_code_pt       *code;
+    ngx_http_script_file_code_t   *fop;
+    ngx_http_script_regex_code_t  *regex;
+    u_char                         errstr[NGX_MAX_CONF_ERRSTR];
+
+    value = cf->args->elts;
+    last = cf->args->nelts - 1;
+
+    if (value[1].len < 1 || value[1].data[0] != '(') {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid condition \"%V\"", &value[1]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (value[1].len == 1) {
+        cur = 2;
+
+    } else {
+        cur = 1;
+        value[1].len--;
+        value[1].data++;
+    }
+
+    if (value[last].len < 1 || value[last].data[value[last].len - 1] != ')') {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid condition \"%V\"", &value[last]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (value[last].len == 1) {
+        last--;
+
+    } else {
+        value[last].len--;
+        value[last].data[value[last].len] = '\0';
+    }
+
+    len = value[cur].len;
+    p = value[cur].data;
+
+    if (len > 1 && p[0] == '$') {
+
+        if (cur != last && cur + 2 != last) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid condition \"%V\"", &value[cur]);
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_http_rewrite_variable(cf, lcf, &value[cur]) != NGX_CONF_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+        if (cur == last) {
+            return NGX_CONF_OK;
+        }
+
+        cur++;
+
+        len = value[cur].len;
+        p = value[cur].data;
+
+        if (len == 1 && p[0] == '=') {
+
+            if (ngx_http_rewrite_value(cf, lcf, &value[last]) != NGX_CONF_OK) {
+                return NGX_CONF_ERROR;
+            }
+
+            code = ngx_http_script_start_code(cf->pool, &lcf->codes,
+                                              sizeof(uintptr_t));
+            if (code == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            *code = ngx_http_script_equal_code;
+
+            return NGX_CONF_OK;
+        }
+
+        if (len == 2 && p[0] == '!' && p[1] == '=') {
+
+            if (ngx_http_rewrite_value(cf, lcf, &value[last]) != NGX_CONF_OK) {
+                return NGX_CONF_ERROR;
+            }
+
+            code = ngx_http_script_start_code(cf->pool, &lcf->codes,
+                                              sizeof(uintptr_t));
+            if (code == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            *code = ngx_http_script_not_equal_code;
+            return NGX_CONF_OK;
+        }
+
+        if ((len == 1 && p[0] == '~')
+            || (len == 2 && p[0] == '~' && p[1] == '*')
+            || (len == 2 && p[0] == '!' && p[1] == '~')
+            || (len == 3 && p[0] == '!' && p[1] == '~' && p[2] == '*'))
+        {
+            regex = ngx_http_script_start_code(cf->pool, &lcf->codes,
+                                         sizeof(ngx_http_script_regex_code_t));
+            if (regex == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            ngx_memzero(regex, sizeof(ngx_http_script_regex_code_t));
+
+            ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
+
+            rc.pattern = value[last];
+            rc.options = (p[len - 1] == '*') ? NGX_REGEX_CASELESS : 0;
+            rc.err.len = NGX_MAX_CONF_ERRSTR;
+            rc.err.data = errstr;
+
+            regex->regex = ngx_http_regex_compile(cf, &rc);
+            if (regex->regex == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            regex->code = ngx_http_script_regex_start_code;
+            regex->next = sizeof(ngx_http_script_regex_code_t);
+            regex->test = 1;
+            if (p[0] == '!') {
+                regex->negative_test = 1;
+            }
+            regex->name = value[last];
+
+            return NGX_CONF_OK;
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "unexpected \"%V\" in condition", &value[cur]);
+        return NGX_CONF_ERROR;
+
+    } else if ((len == 2 && p[0] == '-')
+               || (len == 3 && p[0] == '!' && p[1] == '-'))
+    {
+        if (cur + 1 != last) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid condition \"%V\"", &value[cur]);
+            return NGX_CONF_ERROR;
+        }
+
+        value[last].data[value[last].len] = '\0';
+        value[last].len++;
+
+        if (ngx_http_rewrite_value(cf, lcf, &value[last]) != NGX_CONF_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+        fop = ngx_http_script_start_code(cf->pool, &lcf->codes,
+                                          sizeof(ngx_http_script_file_code_t));
+        if (fop == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        fop->code = ngx_http_script_file_code;
+
+        if (p[1] == 'f') {
+            fop->op = ngx_http_script_file_plain;
+            return NGX_CONF_OK;
+        }
+
+        if (p[1] == 'd') {
+            fop->op = ngx_http_script_file_dir;
+            return NGX_CONF_OK;
+        }
+
+        if (p[1] == 'e') {
+            fop->op = ngx_http_script_file_exists;
+            return NGX_CONF_OK;
+        }
+
+        if (p[1] == 'x') {
+            fop->op = ngx_http_script_file_exec;
+            return NGX_CONF_OK;
+        }
+
+        if (p[0] == '!') {
+            if (p[2] == 'f') {
+                fop->op = ngx_http_script_file_not_plain;
+                return NGX_CONF_OK;
+            }
+
+            if (p[2] == 'd') {
+                fop->op = ngx_http_script_file_not_dir;
+                return NGX_CONF_OK;
+            }
+
+            if (p[2] == 'e') {
+                fop->op = ngx_http_script_file_not_exists;
+                return NGX_CONF_OK;
+            }
+
+            if (p[2] == 'x') {
+                fop->op = ngx_http_script_file_not_exec;
+                return NGX_CONF_OK;
+            }
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid condition \"%V\"", &value[cur]);
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "invalid condition \"%V\"", &value[cur]);
+
+    return NGX_CONF_ERROR;
+}
+
+
+static char *
+ngx_http_rewrite_variable(ngx_conf_t *cf, ngx_http_rewrite_loc_conf_t *lcf,
+    ngx_str_t *value)
+{
+    ngx_int_t                    index;
+    ngx_http_script_var_code_t  *var_code;
+
+    value->len--;
+    value->data++;
+
+    index = ngx_http_get_variable_index(cf, value);
+
+    if (index == NGX_ERROR) {
+        return NGX_CONF_ERROR;
+    }
+
+    var_code = ngx_http_script_start_code(cf->pool, &lcf->codes,
+                                          sizeof(ngx_http_script_var_code_t));
+    if (var_code == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    var_code->code = ngx_http_script_var_code;
+    var_code->index = index;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_rewrite_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_rewrite_loc_conf_t  *lcf = conf;
+
+    ngx_int_t                            index;
+    ngx_str_t                           *value;
+    ngx_http_variable_t                 *v;
+    ngx_http_script_var_code_t          *vcode;
+    ngx_http_script_var_handler_code_t  *vhcode;
+
+    value = cf->args->elts;
+
+    if (value[1].data[0] != '$') {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid variable name \"%V\"", &value[1]);
+        return NGX_CONF_ERROR;
+    }
+
+    value[1].len--;
+    value[1].data++;
+
+    v = ngx_http_add_variable(cf, &value[1],
+                              NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_WEAK);
+    if (v == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    index = ngx_http_get_variable_index(cf, &value[1]);
+    if (index == NGX_ERROR) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (v->get_handler == NULL) {
+        v->get_handler = ngx_http_rewrite_var;
+        v->data = index;
+    }
+
+    if (ngx_http_rewrite_value(cf, lcf, &value[2]) != NGX_CONF_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (v->set_handler) {
+        vhcode = ngx_http_script_start_code(cf->pool, &lcf->codes,
+                                   sizeof(ngx_http_script_var_handler_code_t));
+        if (vhcode == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        vhcode->code = ngx_http_script_var_set_handler_code;
+        vhcode->handler = v->set_handler;
+        vhcode->data = v->data;
+
+        return NGX_CONF_OK;
+    }
+
+    vcode = ngx_http_script_start_code(cf->pool, &lcf->codes,
+                                       sizeof(ngx_http_script_var_code_t));
+    if (vcode == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    vcode->code = ngx_http_script_set_var_code;
+    vcode->index = (uintptr_t) index;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_rewrite_value(ngx_conf_t *cf, ngx_http_rewrite_loc_conf_t *lcf,
+    ngx_str_t *value)
+{
+    ngx_int_t                              n;
+    ngx_http_script_compile_t              sc;
+    ngx_http_script_value_code_t          *val;
+    ngx_http_script_complex_value_code_t  *complex;
+
+    n = ngx_http_script_variables_count(value);
+
+    if (n == 0) {
+        val = ngx_http_script_start_code(cf->pool, &lcf->codes,
+                                         sizeof(ngx_http_script_value_code_t));
+        if (val == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        n = ngx_atoi(value->data, value->len);
+
+        if (n == NGX_ERROR) {
+            n = 0;
+        }
+
+        val->code = ngx_http_script_value_code;
+        val->value = (uintptr_t) n;
+        val->text_len = (uintptr_t) value->len;
+        val->text_data = (uintptr_t) value->data;
+
+        return NGX_CONF_OK;
+    }
+
+    complex = ngx_http_script_start_code(cf->pool, &lcf->codes,
+                                 sizeof(ngx_http_script_complex_value_code_t));
+    if (complex == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    complex->code = ngx_http_script_complex_value_code;
+    complex->lengths = NULL;
+
+    ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+    sc.cf = cf;
+    sc.source = value;
+    sc.lengths = &complex->lengths;
+    sc.values = &lcf->codes;
+    sc.variables = n;
+    sc.complete_lengths = 1;
+
+    if (ngx_http_script_compile(&sc) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_scgi_module.c b/nginx/src/http/modules/ngx_http_scgi_module.c
new file mode 100644 (file)
index 0000000..9bd45bd
--- /dev/null
@@ -0,0 +1,2008 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Manlio Perillo ([email protected])
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_array_t                caches;  /* ngx_http_file_cache_t * */
+} ngx_http_scgi_main_conf_t;
+
+
+typedef struct {
+    ngx_array_t               *flushes;
+    ngx_array_t               *lengths;
+    ngx_array_t               *values;
+    ngx_uint_t                 number;
+    ngx_hash_t                 hash;
+} ngx_http_scgi_params_t;
+
+
+typedef struct {
+    ngx_http_upstream_conf_t   upstream;
+
+    ngx_http_scgi_params_t     params;
+#if (NGX_HTTP_CACHE)
+    ngx_http_scgi_params_t     params_cache;
+#endif
+    ngx_array_t               *params_source;
+
+    ngx_array_t               *scgi_lengths;
+    ngx_array_t               *scgi_values;
+
+#if (NGX_HTTP_CACHE)
+    ngx_http_complex_value_t   cache_key;
+#endif
+} ngx_http_scgi_loc_conf_t;
+
+
+static ngx_int_t ngx_http_scgi_eval(ngx_http_request_t *r,
+    ngx_http_scgi_loc_conf_t *scf);
+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 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);
+
+static void *ngx_http_scgi_create_main_conf(ngx_conf_t *cf);
+static void *ngx_http_scgi_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_scgi_merge_loc_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static ngx_int_t ngx_http_scgi_init_params(ngx_conf_t *cf,
+    ngx_http_scgi_loc_conf_t *conf, ngx_http_scgi_params_t *params,
+    ngx_keyval_t *default_params);
+
+static char *ngx_http_scgi_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_http_scgi_store(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+#if (NGX_HTTP_CACHE)
+static ngx_int_t ngx_http_scgi_create_key(ngx_http_request_t *r);
+static char *ngx_http_scgi_cache(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_scgi_cache_key(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+#endif
+
+
+static ngx_conf_bitmask_t ngx_http_scgi_next_upstream_masks[] = {
+    { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR },
+    { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT },
+    { ngx_string("invalid_header"), NGX_HTTP_UPSTREAM_FT_INVALID_HEADER },
+    { ngx_string("non_idempotent"), NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT },
+    { ngx_string("http_500"), NGX_HTTP_UPSTREAM_FT_HTTP_500 },
+    { ngx_string("http_503"), NGX_HTTP_UPSTREAM_FT_HTTP_503 },
+    { ngx_string("http_403"), NGX_HTTP_UPSTREAM_FT_HTTP_403 },
+    { ngx_string("http_404"), NGX_HTTP_UPSTREAM_FT_HTTP_404 },
+    { ngx_string("http_429"), NGX_HTTP_UPSTREAM_FT_HTTP_429 },
+    { ngx_string("updating"), NGX_HTTP_UPSTREAM_FT_UPDATING },
+    { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF },
+    { ngx_null_string, 0 }
+};
+
+
+ngx_module_t  ngx_http_scgi_module;
+
+
+static ngx_command_t ngx_http_scgi_commands[] = {
+
+    { ngx_string("scgi_pass"),
+      NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
+      ngx_http_scgi_pass,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("scgi_store"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_scgi_store,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("scgi_store_access"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
+      ngx_conf_set_access_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.store_access),
+      NULL },
+
+    { ngx_string("scgi_buffering"),
+      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_scgi_loc_conf_t, upstream.buffering),
+      NULL },
+
+    { ngx_string("scgi_request_buffering"),
+      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_scgi_loc_conf_t, upstream.request_buffering),
+      NULL },
+
+    { ngx_string("scgi_ignore_client_abort"),
+      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_scgi_loc_conf_t, upstream.ignore_client_abort),
+      NULL },
+
+    { ngx_string("scgi_bind"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+      ngx_http_upstream_bind_set_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.local),
+      NULL },
+
+    { ngx_string("scgi_connect_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.connect_timeout),
+      NULL },
+
+    { ngx_string("scgi_send_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.send_timeout),
+      NULL },
+
+    { ngx_string("scgi_buffer_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.buffer_size),
+      NULL },
+
+    { ngx_string("scgi_pass_request_headers"),
+      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_scgi_loc_conf_t, upstream.pass_request_headers),
+      NULL },
+
+    { ngx_string("scgi_pass_request_body"),
+      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_scgi_loc_conf_t, upstream.pass_request_body),
+      NULL },
+
+    { ngx_string("scgi_intercept_errors"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.intercept_errors),
+      NULL },
+
+    { ngx_string("scgi_read_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.read_timeout),
+      NULL },
+
+    { ngx_string("scgi_buffers"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+      ngx_conf_set_bufs_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.bufs),
+      NULL },
+
+    { ngx_string("scgi_busy_buffers_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.busy_buffers_size_conf),
+      NULL },
+
+    { ngx_string("scgi_force_ranges"),
+      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_scgi_loc_conf_t, upstream.force_ranges),
+      NULL },
+
+    { ngx_string("scgi_limit_rate"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.limit_rate),
+      NULL },
+
+#if (NGX_HTTP_CACHE)
+
+    { ngx_string("scgi_cache"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_scgi_cache,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("scgi_cache_key"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_scgi_cache_key,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("scgi_cache_path"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_2MORE,
+      ngx_http_file_cache_set_slot,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      offsetof(ngx_http_scgi_main_conf_t, caches),
+      &ngx_http_scgi_module },
+
+    { ngx_string("scgi_cache_bypass"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_set_predicate_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_bypass),
+      NULL },
+
+    { ngx_string("scgi_no_cache"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_set_predicate_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.no_cache),
+      NULL },
+
+    { ngx_string("scgi_cache_valid"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_file_cache_valid_set_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_valid),
+      NULL },
+
+    { ngx_string("scgi_cache_min_uses"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_min_uses),
+      NULL },
+
+    { ngx_string("scgi_cache_max_range_offset"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_off_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_max_range_offset),
+      NULL },
+
+    { ngx_string("scgi_cache_use_stale"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_use_stale),
+      &ngx_http_scgi_next_upstream_masks },
+
+    { ngx_string("scgi_cache_methods"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_methods),
+      &ngx_http_upstream_cache_method_mask },
+
+    { ngx_string("scgi_cache_lock"),
+      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_scgi_loc_conf_t, upstream.cache_lock),
+      NULL },
+
+    { ngx_string("scgi_cache_lock_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_lock_timeout),
+      NULL },
+
+    { ngx_string("scgi_cache_lock_age"),
+      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_scgi_loc_conf_t, upstream.cache_lock_age),
+      NULL },
+
+    { ngx_string("scgi_cache_revalidate"),
+      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_scgi_loc_conf_t, upstream.cache_revalidate),
+      NULL },
+
+    { ngx_string("scgi_cache_background_update"),
+      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_scgi_loc_conf_t, upstream.cache_background_update),
+      NULL },
+
+#endif
+
+    { ngx_string("scgi_temp_path"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234,
+      ngx_conf_set_path_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.temp_path),
+      NULL },
+
+    { ngx_string("scgi_max_temp_file_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.max_temp_file_size_conf),
+      NULL },
+
+    { ngx_string("scgi_temp_file_write_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.temp_file_write_size_conf),
+      NULL },
+
+    { ngx_string("scgi_next_upstream"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.next_upstream),
+      &ngx_http_scgi_next_upstream_masks },
+
+    { ngx_string("scgi_next_upstream_tries"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.next_upstream_tries),
+      NULL },
+
+    { ngx_string("scgi_next_upstream_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.next_upstream_timeout),
+      NULL },
+
+    { ngx_string("scgi_param"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE23,
+      ngx_http_upstream_param_set_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, params_source),
+      NULL },
+
+    { ngx_string("scgi_pass_header"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.pass_headers),
+      NULL },
+
+    { ngx_string("scgi_hide_header"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.hide_headers),
+      NULL },
+
+    { ngx_string("scgi_ignore_headers"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_scgi_loc_conf_t, upstream.ignore_headers),
+      &ngx_http_upstream_ignore_headers_masks },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_scgi_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    ngx_http_scgi_create_main_conf,        /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_scgi_create_loc_conf,         /* create location configuration */
+    ngx_http_scgi_merge_loc_conf           /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_scgi_module = {
+    NGX_MODULE_V1,
+    &ngx_http_scgi_module_ctx,             /* module context */
+    ngx_http_scgi_commands,                /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t ngx_http_scgi_hide_headers[] = {
+    ngx_string("Status"),
+    ngx_string("X-Accel-Expires"),
+    ngx_string("X-Accel-Redirect"),
+    ngx_string("X-Accel-Limit-Rate"),
+    ngx_string("X-Accel-Buffering"),
+    ngx_string("X-Accel-Charset"),
+    ngx_null_string
+};
+
+
+#if (NGX_HTTP_CACHE)
+
+static ngx_keyval_t  ngx_http_scgi_cache_headers[] = {
+    { ngx_string("HTTP_IF_MODIFIED_SINCE"),
+      ngx_string("$upstream_cache_last_modified") },
+    { ngx_string("HTTP_IF_UNMODIFIED_SINCE"), ngx_string("") },
+    { ngx_string("HTTP_IF_NONE_MATCH"), ngx_string("$upstream_cache_etag") },
+    { ngx_string("HTTP_IF_MATCH"), ngx_string("") },
+    { ngx_string("HTTP_RANGE"), ngx_string("") },
+    { ngx_string("HTTP_IF_RANGE"), ngx_string("") },
+    { ngx_null_string, ngx_null_string }
+};
+
+#endif
+
+
+static ngx_path_init_t ngx_http_scgi_temp_path = {
+    ngx_string(NGX_HTTP_SCGI_TEMP_PATH), { 1, 2, 0 }
+};
+
+
+static ngx_int_t
+ngx_http_scgi_handler(ngx_http_request_t *r)
+{
+    ngx_int_t                   rc;
+    ngx_http_status_t          *status;
+    ngx_http_upstream_t        *u;
+    ngx_http_scgi_loc_conf_t   *scf;
+#if (NGX_HTTP_CACHE)
+    ngx_http_scgi_main_conf_t  *smcf;
+#endif
+
+    if (ngx_http_upstream_create(r) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    status = ngx_pcalloc(r->pool, sizeof(ngx_http_status_t));
+    if (status == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    ngx_http_set_ctx(r, status, ngx_http_scgi_module);
+
+    scf = ngx_http_get_module_loc_conf(r, ngx_http_scgi_module);
+
+    if (scf->scgi_lengths) {
+        if (ngx_http_scgi_eval(r, scf) != NGX_OK) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+    }
+
+    u = r->upstream;
+
+    ngx_str_set(&u->schema, "scgi://");
+    u->output.tag = (ngx_buf_tag_t) &ngx_http_scgi_module;
+
+    u->conf = &scf->upstream;
+
+#if (NGX_HTTP_CACHE)
+    smcf = ngx_http_get_module_main_conf(r, ngx_http_scgi_module);
+
+    u->caches = &smcf->caches;
+    u->create_key = ngx_http_scgi_create_key;
+#endif
+
+    u->create_request = ngx_http_scgi_create_request;
+    u->reinit_request = ngx_http_scgi_reinit_request;
+    u->process_header = ngx_http_scgi_process_status_line;
+    u->abort_request = ngx_http_scgi_abort_request;
+    u->finalize_request = ngx_http_scgi_finalize_request;
+    r->state = 0;
+
+    u->buffering = scf->upstream.buffering;
+
+    u->pipe = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t));
+    if (u->pipe == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    u->pipe->input_filter = ngx_event_pipe_copy_input_filter;
+    u->pipe->input_ctx = r;
+
+    if (!scf->upstream.request_buffering
+        && scf->upstream.pass_request_body
+        && !r->headers_in.chunked)
+    {
+        r->request_body_no_buffering = 1;
+    }
+
+    rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);
+
+    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+        return rc;
+    }
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_scgi_eval(ngx_http_request_t *r, ngx_http_scgi_loc_conf_t * scf)
+{
+    ngx_url_t             url;
+    ngx_http_upstream_t  *u;
+
+    ngx_memzero(&url, sizeof(ngx_url_t));
+
+    if (ngx_http_script_run(r, &url.url, scf->scgi_lengths->elts, 0,
+                            scf->scgi_values->elts)
+        == NULL)
+    {
+        return NGX_ERROR;
+    }
+
+    url.no_resolve = 1;
+
+    if (ngx_parse_url(r->pool, &url) != NGX_OK) {
+        if (url.err) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "%s in upstream \"%V\"", url.err, &url.url);
+        }
+
+        return NGX_ERROR;
+    }
+
+    u = r->upstream;
+
+    u->resolved = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t));
+    if (u->resolved == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (url.addrs) {
+        u->resolved->sockaddr = url.addrs[0].sockaddr;
+        u->resolved->socklen = url.addrs[0].socklen;
+        u->resolved->name = url.addrs[0].name;
+        u->resolved->naddrs = 1;
+    }
+
+    u->resolved->host = url.host;
+    u->resolved->port = url.port;
+    u->resolved->no_port = url.no_port;
+
+    return NGX_OK;
+}
+
+
+#if (NGX_HTTP_CACHE)
+
+static ngx_int_t
+ngx_http_scgi_create_key(ngx_http_request_t *r)
+{
+    ngx_str_t                 *key;
+    ngx_http_scgi_loc_conf_t  *scf;
+
+    key = ngx_array_push(&r->cache->keys);
+    if (key == NULL) {
+        return NGX_ERROR;
+    }
+
+    scf = ngx_http_get_module_loc_conf(r, ngx_http_scgi_module);
+
+    if (ngx_http_complex_value(r, &scf->cache_key, key) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_scgi_create_request(ngx_http_request_t *r)
+{
+    off_t                         content_length_n;
+    u_char                        ch, *key, *val, *lowcase_key;
+    size_t                        len, key_len, val_len, allocated;
+    ngx_buf_t                    *b;
+    ngx_str_t                     content_length;
+    ngx_uint_t                    i, n, hash, skip_empty, header_params;
+    ngx_chain_t                  *cl, *body;
+    ngx_list_part_t              *part;
+    ngx_table_elt_t              *header, **ignored;
+    ngx_http_scgi_params_t       *params;
+    ngx_http_script_code_pt       code;
+    ngx_http_script_engine_t      e, le;
+    ngx_http_scgi_loc_conf_t     *scf;
+    ngx_http_script_len_code_pt   lcode;
+    u_char                        buffer[NGX_OFF_T_LEN];
+
+    content_length_n = 0;
+    body = r->upstream->request_bufs;
+
+    while (body) {
+        content_length_n += ngx_buf_size(body->buf);
+        body = body->next;
+    }
+
+    content_length.data = buffer;
+    content_length.len = ngx_sprintf(buffer, "%O", content_length_n) - buffer;
+
+    len = sizeof("CONTENT_LENGTH") + content_length.len + 1;
+
+    header_params = 0;
+    ignored = NULL;
+
+    scf = ngx_http_get_module_loc_conf(r, ngx_http_scgi_module);
+
+#if (NGX_HTTP_CACHE)
+    params = r->upstream->cacheable ? &scf->params_cache : &scf->params;
+#else
+    params = &scf->params;
+#endif
+
+    if (params->lengths) {
+        ngx_memzero(&le, sizeof(ngx_http_script_engine_t));
+
+        ngx_http_script_flush_no_cacheable_variables(r, params->flushes);
+        le.flushed = 1;
+
+        le.ip = params->lengths->elts;
+        le.request = r;
+
+        while (*(uintptr_t *) le.ip) {
+
+            lcode = *(ngx_http_script_len_code_pt *) le.ip;
+            key_len = lcode(&le);
+
+            lcode = *(ngx_http_script_len_code_pt *) le.ip;
+            skip_empty = lcode(&le);
+
+            for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) {
+                lcode = *(ngx_http_script_len_code_pt *) le.ip;
+            }
+            le.ip += sizeof(uintptr_t);
+
+            if (skip_empty && val_len == 0) {
+                continue;
+            }
+
+            len += key_len + val_len + 1;
+        }
+    }
+
+    if (scf->upstream.pass_request_headers) {
+
+        allocated = 0;
+        lowcase_key = NULL;
+
+        if (params->number) {
+            n = 0;
+            part = &r->headers_in.headers.part;
+
+            while (part) {
+                n += part->nelts;
+                part = part->next;
+            }
+
+            ignored = ngx_palloc(r->pool, n * sizeof(void *));
+            if (ignored == NULL) {
+                return NGX_ERROR;
+            }
+        }
+
+        part = &r->headers_in.headers.part;
+        header = part->elts;
+
+        for (i = 0; /* void */; i++) {
+
+            if (i >= part->nelts) {
+                if (part->next == NULL) {
+                    break;
+                }
+
+                part = part->next;
+                header = part->elts;
+                i = 0;
+            }
+
+            if (params->number) {
+                if (allocated < header[i].key.len) {
+                    allocated = header[i].key.len + 16;
+                    lowcase_key = ngx_pnalloc(r->pool, allocated);
+                    if (lowcase_key == NULL) {
+                        return NGX_ERROR;
+                    }
+                }
+
+                hash = 0;
+
+                for (n = 0; n < header[i].key.len; n++) {
+                    ch = header[i].key.data[n];
+
+                    if (ch >= 'A' && ch <= 'Z') {
+                        ch |= 0x20;
+
+                    } else if (ch == '-') {
+                        ch = '_';
+                    }
+
+                    hash = ngx_hash(hash, ch);
+                    lowcase_key[n] = ch;
+                }
+
+                if (ngx_hash_find(&params->hash, hash, lowcase_key, n)) {
+                    ignored[header_params++] = &header[i];
+                    continue;
+                }
+            }
+
+            len += sizeof("HTTP_") - 1 + header[i].key.len + 1
+                + header[i].value.len + 1;
+        }
+    }
+
+    /* netstring: "length:" + packet + "," */
+
+    b = ngx_create_temp_buf(r->pool, NGX_SIZE_T_LEN + 1 + len + 1);
+    if (b == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl = ngx_alloc_chain_link(r->pool);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl->buf = b;
+
+    b->last = ngx_sprintf(b->last, "%ui:CONTENT_LENGTH%Z%V%Z",
+                          len, &content_length);
+
+    if (params->lengths) {
+        ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
+
+        e.ip = params->values->elts;
+        e.pos = b->last;
+        e.request = r;
+        e.flushed = 1;
+
+        le.ip = params->lengths->elts;
+
+        while (*(uintptr_t *) le.ip) {
+
+            lcode = *(ngx_http_script_len_code_pt *) le.ip;
+            lcode(&le); /* key length */
+
+            lcode = *(ngx_http_script_len_code_pt *) le.ip;
+            skip_empty = lcode(&le);
+
+            for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) {
+                lcode = *(ngx_http_script_len_code_pt *) le.ip;
+            }
+            le.ip += sizeof(uintptr_t);
+
+            if (skip_empty && val_len == 0) {
+                e.skip = 1;
+
+                while (*(uintptr_t *) e.ip) {
+                    code = *(ngx_http_script_code_pt *) e.ip;
+                    code((ngx_http_script_engine_t *) &e);
+                }
+                e.ip += sizeof(uintptr_t);
+
+                e.skip = 0;
+
+                continue;
+            }
+
+#if (NGX_DEBUG)
+            key = e.pos;
+#endif
+            code = *(ngx_http_script_code_pt *) e.ip;
+            code((ngx_http_script_engine_t *) &e);
+
+#if (NGX_DEBUG)
+            val = e.pos;
+#endif
+            while (*(uintptr_t *) e.ip) {
+                code = *(ngx_http_script_code_pt *) e.ip;
+                code((ngx_http_script_engine_t *) &e);
+            }
+            *e.pos++ = '\0';
+            e.ip += sizeof(uintptr_t);
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "scgi param: \"%s: %s\"", key, val);
+        }
+
+        b->last = e.pos;
+    }
+
+    if (scf->upstream.pass_request_headers) {
+
+        part = &r->headers_in.headers.part;
+        header = part->elts;
+
+        for (i = 0; /* void */; i++) {
+
+            if (i >= part->nelts) {
+                if (part->next == NULL) {
+                    break;
+                }
+
+                part = part->next;
+                header = part->elts;
+                i = 0;
+            }
+
+            for (n = 0; n < header_params; n++) {
+                if (&header[i] == ignored[n]) {
+                    goto next;
+                }
+            }
+
+            key = b->last;
+            b->last = ngx_cpymem(key, "HTTP_", sizeof("HTTP_") - 1);
+
+            for (n = 0; n < header[i].key.len; n++) {
+                ch = header[i].key.data[n];
+
+                if (ch >= 'a' && ch <= 'z') {
+                    ch &= ~0x20;
+
+                } else if (ch == '-') {
+                    ch = '_';
+                }
+
+                *b->last++ = ch;
+            }
+
+            *b->last++ = (u_char) 0;
+
+            val = b->last;
+            b->last = ngx_copy(val, header[i].value.data, header[i].value.len);
+            *b->last++ = (u_char) 0;
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "scgi param: \"%s: %s\"", key, val);
+
+        next:
+
+            continue;
+        }
+    }
+
+    *b->last++ = (u_char) ',';
+
+    if (r->request_body_no_buffering) {
+        r->upstream->request_bufs = cl;
+
+    } else if (scf->upstream.pass_request_body) {
+        body = r->upstream->request_bufs;
+        r->upstream->request_bufs = cl;
+
+        while (body) {
+            b = ngx_alloc_buf(r->pool);
+            if (b == NULL) {
+                return NGX_ERROR;
+            }
+
+            ngx_memcpy(b, body->buf, sizeof(ngx_buf_t));
+
+            cl->next = ngx_alloc_chain_link(r->pool);
+            if (cl->next == NULL) {
+                return NGX_ERROR;
+            }
+
+            cl = cl->next;
+            cl->buf = b;
+
+            body = body->next;
+        }
+
+    } else {
+        r->upstream->request_bufs = cl;
+    }
+
+    cl->next = NULL;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_scgi_reinit_request(ngx_http_request_t *r)
+{
+    ngx_http_status_t  *status;
+
+    status = ngx_http_get_module_ctx(r, ngx_http_scgi_module);
+
+    if (status == NULL) {
+        return NGX_OK;
+    }
+
+    status->code = 0;
+    status->count = 0;
+    status->start = NULL;
+    status->end = NULL;
+
+    r->upstream->process_header = ngx_http_scgi_process_status_line;
+    r->state = 0;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_scgi_process_status_line(ngx_http_request_t *r)
+{
+    size_t                len;
+    ngx_int_t             rc;
+    ngx_http_status_t    *status;
+    ngx_http_upstream_t  *u;
+
+    status = ngx_http_get_module_ctx(r, ngx_http_scgi_module);
+
+    if (status == NULL) {
+        return NGX_ERROR;
+    }
+
+    u = r->upstream;
+
+    rc = ngx_http_parse_status_line(r, &u->buffer, status);
+
+    if (rc == NGX_AGAIN) {
+        return rc;
+    }
+
+    if (rc == NGX_ERROR) {
+        u->process_header = ngx_http_scgi_process_header;
+        return ngx_http_scgi_process_header(r);
+    }
+
+    if (u->state && u->state->status == 0) {
+        u->state->status = status->code;
+    }
+
+    u->headers_in.status_n = status->code;
+
+    len = status->end - status->start;
+    u->headers_in.status_line.len = len;
+
+    u->headers_in.status_line.data = ngx_pnalloc(r->pool, len);
+    if (u->headers_in.status_line.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(u->headers_in.status_line.data, status->start, len);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http scgi status %ui \"%V\"",
+                   u->headers_in.status_n, &u->headers_in.status_line);
+
+    u->process_header = ngx_http_scgi_process_header;
+
+    return ngx_http_scgi_process_header(r);
+}
+
+
+static ngx_int_t
+ngx_http_scgi_process_header(ngx_http_request_t *r)
+{
+    ngx_str_t                      *status_line;
+    ngx_int_t                       rc, status;
+    ngx_table_elt_t                *h;
+    ngx_http_upstream_t            *u;
+    ngx_http_upstream_header_t     *hh;
+    ngx_http_upstream_main_conf_t  *umcf;
+
+    umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);
+
+    for ( ;; ) {
+
+        rc = ngx_http_parse_header_line(r, &r->upstream->buffer, 1);
+
+        if (rc == NGX_OK) {
+
+            /* a header line has been parsed successfully */
+
+            h = ngx_list_push(&r->upstream->headers_in.headers);
+            if (h == NULL) {
+                return NGX_ERROR;
+            }
+
+            h->hash = r->header_hash;
+
+            h->key.len = r->header_name_end - r->header_name_start;
+            h->value.len = r->header_end - r->header_start;
+
+            h->key.data = ngx_pnalloc(r->pool,
+                                      h->key.len + 1 + h->value.len + 1
+                                      + h->key.len);
+            if (h->key.data == NULL) {
+                h->hash = 0;
+                return NGX_ERROR;
+            }
+
+            h->value.data = h->key.data + h->key.len + 1;
+            h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1;
+
+            ngx_memcpy(h->key.data, r->header_name_start, h->key.len);
+            h->key.data[h->key.len] = '\0';
+            ngx_memcpy(h->value.data, r->header_start, h->value.len);
+            h->value.data[h->value.len] = '\0';
+
+            if (h->key.len == r->lowcase_index) {
+                ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);
+
+            } else {
+                ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
+            }
+
+            hh = ngx_hash_find(&umcf->headers_in_hash, h->hash,
+                               h->lowcase_key, h->key.len);
+
+            if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
+                return NGX_ERROR;
+            }
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http scgi header: \"%V: %V\"", &h->key, &h->value);
+
+            continue;
+        }
+
+        if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
+
+            /* a whole header has been parsed successfully */
+
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http scgi header done");
+
+            u = r->upstream;
+
+            if (u->headers_in.status_n) {
+                goto done;
+            }
+
+            if (u->headers_in.status) {
+                status_line = &u->headers_in.status->value;
+
+                status = ngx_atoi(status_line->data, 3);
+                if (status == NGX_ERROR) {
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                  "upstream sent invalid status \"%V\"",
+                                  status_line);
+                    return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+                }
+
+                u->headers_in.status_n = status;
+                u->headers_in.status_line = *status_line;
+
+            } else if (u->headers_in.location) {
+                u->headers_in.status_n = 302;
+                ngx_str_set(&u->headers_in.status_line,
+                            "302 Moved Temporarily");
+
+            } else {
+                u->headers_in.status_n = 200;
+                ngx_str_set(&u->headers_in.status_line, "200 OK");
+            }
+
+            if (u->state && u->state->status == 0) {
+                u->state->status = u->headers_in.status_n;
+            }
+
+        done:
+
+            if (u->headers_in.status_n == NGX_HTTP_SWITCHING_PROTOCOLS
+                && r->headers_in.upgrade)
+            {
+                u->upgrade = 1;
+            }
+
+            return NGX_OK;
+        }
+
+        if (rc == NGX_AGAIN) {
+            return NGX_AGAIN;
+        }
+
+        /* there was error while a header line parsing */
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "upstream sent invalid header");
+
+        return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+    }
+}
+
+
+static void
+ngx_http_scgi_abort_request(ngx_http_request_t *r)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "abort http scgi request");
+
+    return;
+}
+
+
+static void
+ngx_http_scgi_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "finalize http scgi request");
+
+    return;
+}
+
+
+static void *
+ngx_http_scgi_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_http_scgi_main_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_scgi_main_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+#if (NGX_HTTP_CACHE)
+    if (ngx_array_init(&conf->caches, cf->pool, 4,
+                       sizeof(ngx_http_file_cache_t *))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+#endif
+
+    return conf;
+}
+
+
+static void *
+ngx_http_scgi_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_scgi_loc_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_scgi_loc_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    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;
+    conf->upstream.force_ranges = NGX_CONF_UNSET;
+
+    conf->upstream.local = NGX_CONF_UNSET_PTR;
+
+    conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.next_upstream_timeout = NGX_CONF_UNSET_MSEC;
+
+    conf->upstream.send_lowat = NGX_CONF_UNSET_SIZE;
+    conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE;
+    conf->upstream.limit_rate = NGX_CONF_UNSET_SIZE;
+
+    conf->upstream.busy_buffers_size_conf = NGX_CONF_UNSET_SIZE;
+    conf->upstream.max_temp_file_size_conf = NGX_CONF_UNSET_SIZE;
+    conf->upstream.temp_file_write_size_conf = NGX_CONF_UNSET_SIZE;
+
+    conf->upstream.pass_request_headers = NGX_CONF_UNSET;
+    conf->upstream.pass_request_body = NGX_CONF_UNSET;
+
+#if (NGX_HTTP_CACHE)
+    conf->upstream.cache = NGX_CONF_UNSET;
+    conf->upstream.cache_min_uses = NGX_CONF_UNSET_UINT;
+    conf->upstream.cache_max_range_offset = NGX_CONF_UNSET;
+    conf->upstream.cache_bypass = NGX_CONF_UNSET_PTR;
+    conf->upstream.no_cache = NGX_CONF_UNSET_PTR;
+    conf->upstream.cache_valid = NGX_CONF_UNSET_PTR;
+    conf->upstream.cache_lock = NGX_CONF_UNSET;
+    conf->upstream.cache_lock_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.cache_lock_age = NGX_CONF_UNSET_MSEC;
+    conf->upstream.cache_revalidate = NGX_CONF_UNSET;
+    conf->upstream.cache_background_update = NGX_CONF_UNSET;
+#endif
+
+    conf->upstream.hide_headers = NGX_CONF_UNSET_PTR;
+    conf->upstream.pass_headers = NGX_CONF_UNSET_PTR;
+
+    conf->upstream.intercept_errors = NGX_CONF_UNSET;
+
+    /* "scgi_cyclic_temp_file" is disabled */
+    conf->upstream.cyclic_temp_file = 0;
+
+    conf->upstream.change_buffering = 1;
+
+    ngx_str_set(&conf->upstream.module, "scgi");
+
+    return conf;
+}
+
+
+static char *
+ngx_http_scgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_scgi_loc_conf_t *prev = parent;
+    ngx_http_scgi_loc_conf_t *conf = child;
+
+    size_t                        size;
+    ngx_int_t                     rc;
+    ngx_hash_init_t               hash;
+    ngx_http_core_loc_conf_t     *clcf;
+
+#if (NGX_HTTP_CACHE)
+
+    if (conf->upstream.store > 0) {
+        conf->upstream.cache = 0;
+    }
+
+    if (conf->upstream.cache > 0) {
+        conf->upstream.store = 0;
+    }
+
+#endif
+
+    if (conf->upstream.store == NGX_CONF_UNSET) {
+        ngx_conf_merge_value(conf->upstream.store, prev->upstream.store, 0);
+
+        conf->upstream.store_lengths = prev->upstream.store_lengths;
+        conf->upstream.store_values = prev->upstream.store_values;
+    }
+
+    ngx_conf_merge_uint_value(conf->upstream.store_access,
+                              prev->upstream.store_access, 0600);
+
+    ngx_conf_merge_uint_value(conf->upstream.next_upstream_tries,
+                              prev->upstream.next_upstream_tries, 0);
+
+    ngx_conf_merge_value(conf->upstream.buffering,
+                              prev->upstream.buffering, 1);
+
+    ngx_conf_merge_value(conf->upstream.request_buffering,
+                              prev->upstream.request_buffering, 1);
+
+    ngx_conf_merge_value(conf->upstream.ignore_client_abort,
+                              prev->upstream.ignore_client_abort, 0);
+
+    ngx_conf_merge_value(conf->upstream.force_ranges,
+                              prev->upstream.force_ranges, 0);
+
+    ngx_conf_merge_ptr_value(conf->upstream.local,
+                              prev->upstream.local, NULL);
+
+    ngx_conf_merge_msec_value(conf->upstream.connect_timeout,
+                              prev->upstream.connect_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->upstream.send_timeout,
+                              prev->upstream.send_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->upstream.read_timeout,
+                              prev->upstream.read_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->upstream.next_upstream_timeout,
+                              prev->upstream.next_upstream_timeout, 0);
+
+    ngx_conf_merge_size_value(conf->upstream.send_lowat,
+                              prev->upstream.send_lowat, 0);
+
+    ngx_conf_merge_size_value(conf->upstream.buffer_size,
+                              prev->upstream.buffer_size,
+                              (size_t) ngx_pagesize);
+
+    ngx_conf_merge_size_value(conf->upstream.limit_rate,
+                              prev->upstream.limit_rate, 0);
+
+
+    ngx_conf_merge_bufs_value(conf->upstream.bufs, prev->upstream.bufs,
+                              8, ngx_pagesize);
+
+    if (conf->upstream.bufs.num < 2) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "there must be at least 2 \"scgi_buffers\"");
+        return NGX_CONF_ERROR;
+    }
+
+
+    size = conf->upstream.buffer_size;
+    if (size < conf->upstream.bufs.size) {
+        size = conf->upstream.bufs.size;
+    }
+
+
+    ngx_conf_merge_size_value(conf->upstream.busy_buffers_size_conf,
+                              prev->upstream.busy_buffers_size_conf,
+                              NGX_CONF_UNSET_SIZE);
+
+    if (conf->upstream.busy_buffers_size_conf == NGX_CONF_UNSET_SIZE) {
+        conf->upstream.busy_buffers_size = 2 * size;
+    } else {
+        conf->upstream.busy_buffers_size =
+            conf->upstream.busy_buffers_size_conf;
+    }
+
+    if (conf->upstream.busy_buffers_size < size) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+            "\"scgi_busy_buffers_size\" must be equal to or greater "
+            "than the maximum of the value of \"scgi_buffer_size\" and "
+            "one of the \"scgi_buffers\"");
+
+        return NGX_CONF_ERROR;
+    }
+
+    if (conf->upstream.busy_buffers_size
+        > (conf->upstream.bufs.num - 1) * conf->upstream.bufs.size)
+    {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+            "\"scgi_busy_buffers_size\" must be less than "
+            "the size of all \"scgi_buffers\" minus one buffer");
+
+        return NGX_CONF_ERROR;
+    }
+
+
+    ngx_conf_merge_size_value(conf->upstream.temp_file_write_size_conf,
+                              prev->upstream.temp_file_write_size_conf,
+                              NGX_CONF_UNSET_SIZE);
+
+    if (conf->upstream.temp_file_write_size_conf == NGX_CONF_UNSET_SIZE) {
+        conf->upstream.temp_file_write_size = 2 * size;
+    } else {
+        conf->upstream.temp_file_write_size =
+            conf->upstream.temp_file_write_size_conf;
+    }
+
+    if (conf->upstream.temp_file_write_size < size) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+            "\"scgi_temp_file_write_size\" must be equal to or greater than "
+            "the maximum of the value of \"scgi_buffer_size\" and "
+            "one of the \"scgi_buffers\"");
+
+        return NGX_CONF_ERROR;
+    }
+
+
+    ngx_conf_merge_size_value(conf->upstream.max_temp_file_size_conf,
+                              prev->upstream.max_temp_file_size_conf,
+                              NGX_CONF_UNSET_SIZE);
+
+    if (conf->upstream.max_temp_file_size_conf == NGX_CONF_UNSET_SIZE) {
+        conf->upstream.max_temp_file_size = 1024 * 1024 * 1024;
+    } else {
+        conf->upstream.max_temp_file_size =
+            conf->upstream.max_temp_file_size_conf;
+    }
+
+    if (conf->upstream.max_temp_file_size != 0
+        && conf->upstream.max_temp_file_size < size)
+    {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+            "\"scgi_max_temp_file_size\" must be equal to zero to disable "
+            "temporary files usage or must be equal to or greater than "
+            "the maximum of the value of \"scgi_buffer_size\" and "
+            "one of the \"scgi_buffers\"");
+
+        return NGX_CONF_ERROR;
+    }
+
+
+    ngx_conf_merge_bitmask_value(conf->upstream.ignore_headers,
+                                 prev->upstream.ignore_headers,
+                                 NGX_CONF_BITMASK_SET);
+
+
+    ngx_conf_merge_bitmask_value(conf->upstream.next_upstream,
+                                 prev->upstream.next_upstream,
+                                 (NGX_CONF_BITMASK_SET
+                                  |NGX_HTTP_UPSTREAM_FT_ERROR
+                                  |NGX_HTTP_UPSTREAM_FT_TIMEOUT));
+
+    if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) {
+        conf->upstream.next_upstream = NGX_CONF_BITMASK_SET
+                                       |NGX_HTTP_UPSTREAM_FT_OFF;
+    }
+
+    if (ngx_conf_merge_path_value(cf, &conf->upstream.temp_path,
+                                  prev->upstream.temp_path,
+                                  &ngx_http_scgi_temp_path)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+#if (NGX_HTTP_CACHE)
+
+    if (conf->upstream.cache == NGX_CONF_UNSET) {
+        ngx_conf_merge_value(conf->upstream.cache,
+                              prev->upstream.cache, 0);
+
+        conf->upstream.cache_zone = prev->upstream.cache_zone;
+        conf->upstream.cache_value = prev->upstream.cache_value;
+    }
+
+    if (conf->upstream.cache_zone && conf->upstream.cache_zone->data == NULL) {
+        ngx_shm_zone_t  *shm_zone;
+
+        shm_zone = conf->upstream.cache_zone;
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"scgi_cache\" zone \"%V\" is unknown",
+                           &shm_zone->shm.name);
+
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_conf_merge_uint_value(conf->upstream.cache_min_uses,
+                              prev->upstream.cache_min_uses, 1);
+
+    ngx_conf_merge_off_value(conf->upstream.cache_max_range_offset,
+                              prev->upstream.cache_max_range_offset,
+                              NGX_MAX_OFF_T_VALUE);
+
+    ngx_conf_merge_bitmask_value(conf->upstream.cache_use_stale,
+                              prev->upstream.cache_use_stale,
+                              (NGX_CONF_BITMASK_SET
+                               |NGX_HTTP_UPSTREAM_FT_OFF));
+
+    if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_OFF) {
+        conf->upstream.cache_use_stale = NGX_CONF_BITMASK_SET
+                                         |NGX_HTTP_UPSTREAM_FT_OFF;
+    }
+
+    if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_ERROR) {
+        conf->upstream.cache_use_stale |= NGX_HTTP_UPSTREAM_FT_NOLIVE;
+    }
+
+    if (conf->upstream.cache_methods == 0) {
+        conf->upstream.cache_methods = prev->upstream.cache_methods;
+    }
+
+    conf->upstream.cache_methods |= NGX_HTTP_GET|NGX_HTTP_HEAD;
+
+    ngx_conf_merge_ptr_value(conf->upstream.cache_bypass,
+                             prev->upstream.cache_bypass, NULL);
+
+    ngx_conf_merge_ptr_value(conf->upstream.no_cache,
+                             prev->upstream.no_cache, NULL);
+
+    ngx_conf_merge_ptr_value(conf->upstream.cache_valid,
+                             prev->upstream.cache_valid, NULL);
+
+    if (conf->cache_key.value.data == NULL) {
+        conf->cache_key = prev->cache_key;
+    }
+
+    if (conf->upstream.cache && conf->cache_key.value.data == NULL) {
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                           "no \"scgi_cache_key\" for \"scgi_cache\"");
+    }
+
+    ngx_conf_merge_value(conf->upstream.cache_lock,
+                              prev->upstream.cache_lock, 0);
+
+    ngx_conf_merge_msec_value(conf->upstream.cache_lock_timeout,
+                              prev->upstream.cache_lock_timeout, 5000);
+
+    ngx_conf_merge_msec_value(conf->upstream.cache_lock_age,
+                              prev->upstream.cache_lock_age, 5000);
+
+    ngx_conf_merge_value(conf->upstream.cache_revalidate,
+                              prev->upstream.cache_revalidate, 0);
+
+    ngx_conf_merge_value(conf->upstream.cache_background_update,
+                              prev->upstream.cache_background_update, 0);
+
+#endif
+
+    ngx_conf_merge_value(conf->upstream.pass_request_headers,
+                         prev->upstream.pass_request_headers, 1);
+    ngx_conf_merge_value(conf->upstream.pass_request_body,
+                         prev->upstream.pass_request_body, 1);
+
+    ngx_conf_merge_value(conf->upstream.intercept_errors,
+                         prev->upstream.intercept_errors, 0);
+
+    hash.max_size = 512;
+    hash.bucket_size = ngx_align(64, ngx_cacheline_size);
+    hash.name = "scgi_hide_headers_hash";
+
+    if (ngx_http_upstream_hide_headers_hash(cf, &conf->upstream,
+            &prev->upstream, ngx_http_scgi_hide_headers, &hash)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+    if (clcf->noname
+        && conf->upstream.upstream == NULL && conf->scgi_lengths == NULL)
+    {
+        conf->upstream.upstream = prev->upstream.upstream;
+        conf->scgi_lengths = prev->scgi_lengths;
+        conf->scgi_values = prev->scgi_values;
+    }
+
+    if (clcf->lmt_excpt && clcf->handler == NULL
+        && (conf->upstream.upstream || conf->scgi_lengths))
+    {
+        clcf->handler = ngx_http_scgi_handler;
+    }
+
+    if (conf->params_source == NULL) {
+        conf->params = prev->params;
+#if (NGX_HTTP_CACHE)
+        conf->params_cache = prev->params_cache;
+#endif
+        conf->params_source = prev->params_source;
+    }
+
+    rc = ngx_http_scgi_init_params(cf, conf, &conf->params, NULL);
+    if (rc != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+#if (NGX_HTTP_CACHE)
+
+    if (conf->upstream.cache) {
+        rc = ngx_http_scgi_init_params(cf, conf, &conf->params_cache,
+                                       ngx_http_scgi_cache_headers);
+        if (rc != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+#endif
+
+    /*
+     * special handling to preserve conf->params in the "http" section
+     * to inherit it to all servers
+     */
+
+    if (prev->params.hash.buckets == NULL
+        && conf->params_source == prev->params_source)
+    {
+        prev->params = conf->params;
+#if (NGX_HTTP_CACHE)
+        prev->params_cache = conf->params_cache;
+#endif
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_scgi_init_params(ngx_conf_t *cf, ngx_http_scgi_loc_conf_t *conf,
+    ngx_http_scgi_params_t *params, ngx_keyval_t *default_params)
+{
+    u_char                       *p;
+    size_t                        size;
+    uintptr_t                    *code;
+    ngx_uint_t                    i, nsrc;
+    ngx_array_t                   headers_names, params_merged;
+    ngx_keyval_t                 *h;
+    ngx_hash_key_t               *hk;
+    ngx_hash_init_t               hash;
+    ngx_http_upstream_param_t    *src, *s;
+    ngx_http_script_compile_t     sc;
+    ngx_http_script_copy_code_t  *copy;
+
+    if (params->hash.buckets) {
+        return NGX_OK;
+    }
+
+    if (conf->params_source == NULL && default_params == NULL) {
+        params->hash.buckets = (void *) 1;
+        return NGX_OK;
+    }
+
+    params->lengths = ngx_array_create(cf->pool, 64, 1);
+    if (params->lengths == NULL) {
+        return NGX_ERROR;
+    }
+
+    params->values = ngx_array_create(cf->pool, 512, 1);
+    if (params->values == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (conf->params_source) {
+        src = conf->params_source->elts;
+        nsrc = conf->params_source->nelts;
+
+    } else {
+        src = NULL;
+        nsrc = 0;
+    }
+
+    if (default_params) {
+        if (ngx_array_init(&params_merged, cf->temp_pool, 4,
+                           sizeof(ngx_http_upstream_param_t))
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+
+        for (i = 0; i < nsrc; i++) {
+
+            s = ngx_array_push(&params_merged);
+            if (s == NULL) {
+                return NGX_ERROR;
+            }
+
+            *s = src[i];
+        }
+
+        h = default_params;
+
+        while (h->key.len) {
+
+            src = params_merged.elts;
+            nsrc = params_merged.nelts;
+
+            for (i = 0; i < nsrc; i++) {
+                if (ngx_strcasecmp(h->key.data, src[i].key.data) == 0) {
+                    goto next;
+                }
+            }
+
+            s = ngx_array_push(&params_merged);
+            if (s == NULL) {
+                return NGX_ERROR;
+            }
+
+            s->key = h->key;
+            s->value = h->value;
+            s->skip_empty = 1;
+
+        next:
+
+            h++;
+        }
+
+        src = params_merged.elts;
+        nsrc = params_merged.nelts;
+    }
+
+    for (i = 0; i < nsrc; i++) {
+
+        if (src[i].key.len > sizeof("HTTP_") - 1
+            && ngx_strncmp(src[i].key.data, "HTTP_", sizeof("HTTP_") - 1) == 0)
+        {
+            hk = ngx_array_push(&headers_names);
+            if (hk == NULL) {
+                return NGX_ERROR;
+            }
+
+            hk->key.len = src[i].key.len - 5;
+            hk->key.data = src[i].key.data + 5;
+            hk->key_hash = ngx_hash_key_lc(hk->key.data, hk->key.len);
+            hk->value = (void *) 1;
+
+            if (src[i].value.len == 0) {
+                continue;
+            }
+        }
+
+        copy = ngx_array_push_n(params->lengths,
+                                sizeof(ngx_http_script_copy_code_t));
+        if (copy == NULL) {
+            return NGX_ERROR;
+        }
+
+        copy->code = (ngx_http_script_code_pt) (void *)
+                                                 ngx_http_script_copy_len_code;
+        copy->len = src[i].key.len + 1;
+
+        copy = ngx_array_push_n(params->lengths,
+                                sizeof(ngx_http_script_copy_code_t));
+        if (copy == NULL) {
+            return NGX_ERROR;
+        }
+
+        copy->code = (ngx_http_script_code_pt) (void *)
+                                                 ngx_http_script_copy_len_code;
+        copy->len = src[i].skip_empty;
+
+
+        size = (sizeof(ngx_http_script_copy_code_t)
+                + src[i].key.len + 1 + sizeof(uintptr_t) - 1)
+               & ~(sizeof(uintptr_t) - 1);
+
+        copy = ngx_array_push_n(params->values, size);
+        if (copy == NULL) {
+            return NGX_ERROR;
+        }
+
+        copy->code = ngx_http_script_copy_code;
+        copy->len = src[i].key.len + 1;
+
+        p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t);
+        (void) ngx_cpystrn(p, src[i].key.data, src[i].key.len + 1);
+
+
+        ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+        sc.cf = cf;
+        sc.source = &src[i].value;
+        sc.flushes = &params->flushes;
+        sc.lengths = &params->lengths;
+        sc.values = &params->values;
+
+        if (ngx_http_script_compile(&sc) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        code = ngx_array_push_n(params->lengths, sizeof(uintptr_t));
+        if (code == NULL) {
+            return NGX_ERROR;
+        }
+
+        *code = (uintptr_t) NULL;
+
+
+        code = ngx_array_push_n(params->values, sizeof(uintptr_t));
+        if (code == NULL) {
+            return NGX_ERROR;
+        }
+
+        *code = (uintptr_t) NULL;
+    }
+
+    code = ngx_array_push_n(params->lengths, sizeof(uintptr_t));
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    *code = (uintptr_t) NULL;
+
+    params->number = headers_names.nelts;
+
+    hash.hash = &params->hash;
+    hash.key = ngx_hash_key_lc;
+    hash.max_size = 512;
+    hash.bucket_size = 64;
+    hash.name = "scgi_params_hash";
+    hash.pool = cf->pool;
+    hash.temp_pool = NULL;
+
+    return ngx_hash_init(&hash, headers_names.elts, headers_names.nelts);
+}
+
+
+static char *
+ngx_http_scgi_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_scgi_loc_conf_t *scf = conf;
+
+    ngx_url_t                   u;
+    ngx_str_t                  *value, *url;
+    ngx_uint_t                  n;
+    ngx_http_core_loc_conf_t   *clcf;
+    ngx_http_script_compile_t   sc;
+
+    if (scf->upstream.upstream || scf->scgi_lengths) {
+        return "is duplicate";
+    }
+
+    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+    clcf->handler = ngx_http_scgi_handler;
+
+    value = cf->args->elts;
+
+    url = &value[1];
+
+    n = ngx_http_script_variables_count(url);
+
+    if (n) {
+
+        ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+        sc.cf = cf;
+        sc.source = url;
+        sc.lengths = &scf->scgi_lengths;
+        sc.values = &scf->scgi_values;
+        sc.variables = n;
+        sc.complete_lengths = 1;
+        sc.complete_values = 1;
+
+        if (ngx_http_script_compile(&sc) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+        return NGX_CONF_OK;
+    }
+
+    ngx_memzero(&u, sizeof(ngx_url_t));
+
+    u.url = value[1];
+    u.no_resolve = 1;
+
+    scf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);
+    if (scf->upstream.upstream == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (clcf->name.data[clcf->name.len - 1] == '/') {
+        clcf->auto_redirect = 1;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_scgi_store(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_scgi_loc_conf_t *scf = conf;
+
+    ngx_str_t                  *value;
+    ngx_http_script_compile_t   sc;
+
+    if (scf->upstream.store != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    if (ngx_strcmp(value[1].data, "off") == 0) {
+        scf->upstream.store = 0;
+        return NGX_CONF_OK;
+    }
+
+#if (NGX_HTTP_CACHE)
+    if (scf->upstream.cache > 0) {
+        return "is incompatible with \"scgi_cache\"";
+    }
+#endif
+
+    scf->upstream.store = 1;
+
+    if (ngx_strcmp(value[1].data, "on") == 0) {
+        return NGX_CONF_OK;
+    }
+
+    /* include the terminating '\0' into script */
+    value[1].len++;
+
+    ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+    sc.cf = cf;
+    sc.source = &value[1];
+    sc.lengths = &scf->upstream.store_lengths;
+    sc.values = &scf->upstream.store_values;
+    sc.variables = ngx_http_script_variables_count(&value[1]);
+    sc.complete_lengths = 1;
+    sc.complete_values = 1;
+
+    if (ngx_http_script_compile(&sc) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+#if (NGX_HTTP_CACHE)
+
+static char *
+ngx_http_scgi_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_scgi_loc_conf_t *scf = conf;
+
+    ngx_str_t                         *value;
+    ngx_http_complex_value_t           cv;
+    ngx_http_compile_complex_value_t   ccv;
+
+    value = cf->args->elts;
+
+    if (scf->upstream.cache != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    if (ngx_strcmp(value[1].data, "off") == 0) {
+        scf->upstream.cache = 0;
+        return NGX_CONF_OK;
+    }
+
+    if (scf->upstream.store > 0) {
+        return "is incompatible with \"scgi_store\"";
+    }
+
+    scf->upstream.cache = 1;
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[1];
+    ccv.complex_value = &cv;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (cv.lengths != NULL) {
+
+        scf->upstream.cache_value = ngx_palloc(cf->pool,
+                                             sizeof(ngx_http_complex_value_t));
+        if (scf->upstream.cache_value == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        *scf->upstream.cache_value = cv;
+
+        return NGX_CONF_OK;
+    }
+
+    scf->upstream.cache_zone = ngx_shared_memory_add(cf, &value[1], 0,
+                                                     &ngx_http_scgi_module);
+    if (scf->upstream.cache_zone == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_scgi_cache_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_scgi_loc_conf_t *scf = conf;
+
+    ngx_str_t                         *value;
+    ngx_http_compile_complex_value_t   ccv;
+
+    value = cf->args->elts;
+
+    if (scf->cache_key.value.data) {
+        return "is duplicate";
+    }
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[1];
+    ccv.complex_value = &scf->cache_key;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+#endif
diff --git a/nginx/src/http/modules/ngx_http_secure_link_module.c b/nginx/src/http/modules/ngx_http_secure_link_module.c
new file mode 100644 (file)
index 0000000..536e09a
--- /dev/null
@@ -0,0 +1,367 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_md5.h>
+
+
+typedef struct {
+    ngx_http_complex_value_t  *variable;
+    ngx_http_complex_value_t  *md5;
+    ngx_str_t                  secret;
+} ngx_http_secure_link_conf_t;
+
+
+typedef struct {
+    ngx_str_t                  expires;
+} ngx_http_secure_link_ctx_t;
+
+
+static ngx_int_t ngx_http_secure_link_old_variable(ngx_http_request_t *r,
+    ngx_http_secure_link_conf_t *conf, ngx_http_variable_value_t *v,
+    uintptr_t data);
+static ngx_int_t ngx_http_secure_link_expires_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static void *ngx_http_secure_link_create_conf(ngx_conf_t *cf);
+static char *ngx_http_secure_link_merge_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static ngx_int_t ngx_http_secure_link_add_variables(ngx_conf_t *cf);
+
+
+static ngx_command_t  ngx_http_secure_link_commands[] = {
+
+    { ngx_string("secure_link"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_set_complex_value_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_secure_link_conf_t, variable),
+      NULL },
+
+    { ngx_string("secure_link_md5"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_set_complex_value_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_secure_link_conf_t, md5),
+      NULL },
+
+    { ngx_string("secure_link_secret"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_secure_link_conf_t, secret),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_secure_link_module_ctx = {
+    ngx_http_secure_link_add_variables,    /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_secure_link_create_conf,      /* create location configuration */
+    ngx_http_secure_link_merge_conf        /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_secure_link_module = {
+    NGX_MODULE_V1,
+    &ngx_http_secure_link_module_ctx,      /* module context */
+    ngx_http_secure_link_commands,         /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t  ngx_http_secure_link_name = ngx_string("secure_link");
+static ngx_str_t  ngx_http_secure_link_expires_name =
+    ngx_string("secure_link_expires");
+
+
+static ngx_int_t
+ngx_http_secure_link_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char                       *p, *last;
+    ngx_str_t                     val, hash;
+    time_t                        expires;
+    ngx_md5_t                     md5;
+    ngx_http_secure_link_ctx_t   *ctx;
+    ngx_http_secure_link_conf_t  *conf;
+    u_char                        hash_buf[18], md5_buf[16];
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_secure_link_module);
+
+    if (conf->secret.data) {
+        return ngx_http_secure_link_old_variable(r, conf, v, data);
+    }
+
+    if (conf->variable == NULL || conf->md5 == NULL) {
+        goto not_found;
+    }
+
+    if (ngx_http_complex_value(r, conf->variable, &val) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "secure link: \"%V\"", &val);
+
+    last = val.data + val.len;
+
+    p = ngx_strlchr(val.data, last, ',');
+    expires = 0;
+
+    if (p) {
+        val.len = p++ - val.data;
+
+        expires = ngx_atotm(p, last - p);
+        if (expires <= 0) {
+            goto not_found;
+        }
+
+        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_secure_link_ctx_t));
+        if (ctx == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_http_set_ctx(r, ctx, ngx_http_secure_link_module);
+
+        ctx->expires.len = last - p;
+        ctx->expires.data = p;
+    }
+
+    if (val.len > 24) {
+        goto not_found;
+    }
+
+    hash.data = hash_buf;
+
+    if (ngx_decode_base64url(&hash, &val) != NGX_OK) {
+        goto not_found;
+    }
+
+    if (hash.len != 16) {
+        goto not_found;
+    }
+
+    if (ngx_http_complex_value(r, conf->md5, &val) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "secure link md5: \"%V\"", &val);
+
+    ngx_md5_init(&md5);
+    ngx_md5_update(&md5, val.data, val.len);
+    ngx_md5_final(md5_buf, &md5);
+
+    if (ngx_memcmp(hash_buf, md5_buf, 16) != 0) {
+        goto not_found;
+    }
+
+    v->data = (u_char *) ((expires && expires < ngx_time()) ? "0" : "1");
+    v->len = 1;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    return NGX_OK;
+
+not_found:
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_secure_link_old_variable(ngx_http_request_t *r,
+    ngx_http_secure_link_conf_t *conf, ngx_http_variable_value_t *v,
+    uintptr_t data)
+{
+    u_char      *p, *start, *end, *last;
+    size_t       len;
+    ngx_int_t    n;
+    ngx_uint_t   i;
+    ngx_md5_t    md5;
+    u_char       hash[16];
+
+    p = &r->unparsed_uri.data[1];
+    last = r->unparsed_uri.data + r->unparsed_uri.len;
+
+    while (p < last) {
+        if (*p++ == '/') {
+            start = p;
+            goto md5_start;
+        }
+    }
+
+    goto not_found;
+
+md5_start:
+
+    while (p < last) {
+        if (*p++ == '/') {
+            end = p - 1;
+            goto url_start;
+        }
+    }
+
+    goto not_found;
+
+url_start:
+
+    len = last - p;
+
+    if (end - start != 32 || len == 0) {
+        goto not_found;
+    }
+
+    ngx_md5_init(&md5);
+    ngx_md5_update(&md5, p, len);
+    ngx_md5_update(&md5, conf->secret.data, conf->secret.len);
+    ngx_md5_final(hash, &md5);
+
+    for (i = 0; i < 16; i++) {
+        n = ngx_hextoi(&start[2 * i], 2);
+        if (n == NGX_ERROR || n != hash[i]) {
+            goto not_found;
+        }
+    }
+
+    v->len = len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = p;
+
+    return NGX_OK;
+
+not_found:
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_secure_link_expires_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_http_secure_link_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_secure_link_module);
+
+    if (ctx) {
+        v->len = ctx->expires.len;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = ctx->expires.data;
+
+    } else {
+        v->not_found = 1;
+    }
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_secure_link_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_secure_link_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_secure_link_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->variable = NULL;
+     *     conf->md5 = NULL;
+     *     conf->secret = { 0, NULL };
+     */
+
+    return conf;
+}
+
+
+static char *
+ngx_http_secure_link_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_secure_link_conf_t *prev = parent;
+    ngx_http_secure_link_conf_t *conf = child;
+
+    if (conf->secret.data) {
+        if (conf->variable || conf->md5) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "\"secure_link_secret\" cannot be mixed with "
+                               "\"secure_link\" and \"secure_link_md5\"");
+            return NGX_CONF_ERROR;
+        }
+
+        return NGX_CONF_OK;
+    }
+
+    if (conf->variable == NULL) {
+        conf->variable = prev->variable;
+    }
+
+    if (conf->md5 == NULL) {
+        conf->md5 = prev->md5;
+    }
+
+    if (conf->variable == NULL && conf->md5 == NULL) {
+        conf->secret = prev->secret;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_secure_link_add_variables(ngx_conf_t *cf)
+{
+    ngx_http_variable_t  *var;
+
+    var = ngx_http_add_variable(cf, &ngx_http_secure_link_name, 0);
+    if (var == NULL) {
+        return NGX_ERROR;
+    }
+
+    var->get_handler = ngx_http_secure_link_variable;
+
+    var = ngx_http_add_variable(cf, &ngx_http_secure_link_expires_name, 0);
+    if (var == NULL) {
+        return NGX_ERROR;
+    }
+
+    var->get_handler = ngx_http_secure_link_expires_variable;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_slice_filter_module.c b/nginx/src/http/modules/ngx_http_slice_filter_module.c
new file mode 100644 (file)
index 0000000..c1edbca
--- /dev/null
@@ -0,0 +1,545 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    size_t               size;
+} ngx_http_slice_loc_conf_t;
+
+
+typedef struct {
+    off_t                start;
+    off_t                end;
+    ngx_str_t            range;
+    ngx_str_t            etag;
+    unsigned             last:1;
+    unsigned             active:1;
+    ngx_http_request_t  *sr;
+} ngx_http_slice_ctx_t;
+
+
+typedef struct {
+    off_t                start;
+    off_t                end;
+    off_t                complete_length;
+} ngx_http_slice_content_range_t;
+
+
+static ngx_int_t ngx_http_slice_header_filter(ngx_http_request_t *r);
+static ngx_int_t ngx_http_slice_body_filter(ngx_http_request_t *r,
+    ngx_chain_t *in);
+static ngx_int_t ngx_http_slice_parse_content_range(ngx_http_request_t *r,
+    ngx_http_slice_content_range_t *cr);
+static ngx_int_t ngx_http_slice_range_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static off_t ngx_http_slice_get_start(ngx_http_request_t *r);
+static void *ngx_http_slice_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_slice_merge_loc_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static ngx_int_t ngx_http_slice_add_variables(ngx_conf_t *cf);
+static ngx_int_t ngx_http_slice_init(ngx_conf_t *cf);
+
+
+static ngx_command_t  ngx_http_slice_filter_commands[] = {
+
+    { ngx_string("slice"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_slice_loc_conf_t, size),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_slice_filter_module_ctx = {
+    ngx_http_slice_add_variables,          /* preconfiguration */
+    ngx_http_slice_init,                   /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_slice_create_loc_conf,        /* create location configuration */
+    ngx_http_slice_merge_loc_conf          /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_slice_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_slice_filter_module_ctx,     /* module context */
+    ngx_http_slice_filter_commands,        /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t  ngx_http_slice_range_name = ngx_string("slice_range");
+
+static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_slice_header_filter(ngx_http_request_t *r)
+{
+    off_t                            end;
+    ngx_int_t                        rc;
+    ngx_table_elt_t                 *h;
+    ngx_http_slice_ctx_t            *ctx;
+    ngx_http_slice_loc_conf_t       *slcf;
+    ngx_http_slice_content_range_t   cr;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
+    if (ctx == NULL) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    if (r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT) {
+        if (r == r->main) {
+            ngx_http_set_ctx(r, NULL, ngx_http_slice_filter_module);
+            return ngx_http_next_header_filter(r);
+        }
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "unexpected status code %ui in slice response",
+                      r->headers_out.status);
+        return NGX_ERROR;
+    }
+
+    h = r->headers_out.etag;
+
+    if (ctx->etag.len) {
+        if (h == NULL
+            || h->value.len != ctx->etag.len
+            || ngx_strncmp(h->value.data, ctx->etag.data, ctx->etag.len)
+               != 0)
+        {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "etag mismatch in slice response");
+            return NGX_ERROR;
+        }
+    }
+
+    if (h) {
+        ctx->etag = h->value;
+    }
+
+    if (ngx_http_slice_parse_content_range(r, &cr) != NGX_OK) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "invalid range in slice response");
+        return NGX_ERROR;
+    }
+
+    if (cr.complete_length == -1) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "no complete length in slice response");
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http slice response range: %O-%O/%O",
+                   cr.start, cr.end, cr.complete_length);
+
+    slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module);
+
+    end = ngx_min(cr.start + (off_t) slcf->size, cr.complete_length);
+
+    if (cr.start != ctx->start || cr.end != end) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "unexpected range in slice response: %O-%O",
+                      cr.start, cr.end);
+        return NGX_ERROR;
+    }
+
+    ctx->start = end;
+    ctx->active = 1;
+
+    r->headers_out.status = NGX_HTTP_OK;
+    r->headers_out.status_line.len = 0;
+    r->headers_out.content_length_n = cr.complete_length;
+    r->headers_out.content_offset = cr.start;
+    r->headers_out.content_range->hash = 0;
+    r->headers_out.content_range = NULL;
+
+    r->allow_ranges = 1;
+    r->subrequest_ranges = 1;
+    r->single_range = 1;
+
+    rc = ngx_http_next_header_filter(r);
+
+    if (r != r->main) {
+        return rc;
+    }
+
+    r->preserve_body = 1;
+
+    if (r->headers_out.status == NGX_HTTP_PARTIAL_CONTENT) {
+        if (ctx->start + (off_t) slcf->size <= r->headers_out.content_offset) {
+            ctx->start = slcf->size
+                         * (r->headers_out.content_offset / slcf->size);
+        }
+
+        ctx->end = r->headers_out.content_offset
+                   + r->headers_out.content_length_n;
+
+    } else {
+        ctx->end = cr.complete_length;
+    }
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_http_slice_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    ngx_int_t                   rc;
+    ngx_chain_t                *cl;
+    ngx_http_slice_ctx_t       *ctx;
+    ngx_http_slice_loc_conf_t  *slcf;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
+
+    if (ctx == NULL || r != r->main) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    for (cl = in; cl; cl = cl->next) {
+        if (cl->buf->last_buf) {
+            cl->buf->last_buf = 0;
+            cl->buf->last_in_chain = 1;
+            cl->buf->sync = 1;
+            ctx->last = 1;
+        }
+    }
+
+    rc = ngx_http_next_body_filter(r, in);
+
+    if (rc == NGX_ERROR || !ctx->last) {
+        return rc;
+    }
+
+    if (ctx->sr && !ctx->sr->done) {
+        return rc;
+    }
+
+    if (!ctx->active) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "missing slice response");
+        return NGX_ERROR;
+    }
+
+    if (ctx->start >= ctx->end) {
+        ngx_http_set_ctx(r, NULL, ngx_http_slice_filter_module);
+        ngx_http_send_special(r, NGX_HTTP_LAST);
+        return rc;
+    }
+
+    if (r->buffered) {
+        return rc;
+    }
+
+    if (ngx_http_subrequest(r, &r->uri, &r->args, &ctx->sr, NULL,
+                            NGX_HTTP_SUBREQUEST_CLONE)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    ngx_http_set_ctx(ctx->sr, ctx, ngx_http_slice_filter_module);
+
+    slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module);
+
+    ctx->range.len = ngx_sprintf(ctx->range.data, "bytes=%O-%O", ctx->start,
+                                 ctx->start + (off_t) slcf->size - 1)
+                     - ctx->range.data;
+
+    ctx->active = 0;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http slice subrequest: \"%V\"", &ctx->range);
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_http_slice_parse_content_range(ngx_http_request_t *r,
+    ngx_http_slice_content_range_t *cr)
+{
+    off_t             start, end, complete_length, cutoff, cutlim;
+    u_char           *p;
+    ngx_table_elt_t  *h;
+
+    h = r->headers_out.content_range;
+
+    if (h == NULL
+        || h->value.len < 7
+        || ngx_strncmp(h->value.data, "bytes ", 6) != 0)
+    {
+        return NGX_ERROR;
+    }
+
+    p = h->value.data + 6;
+
+    cutoff = NGX_MAX_OFF_T_VALUE / 10;
+    cutlim = NGX_MAX_OFF_T_VALUE % 10;
+
+    start = 0;
+    end = 0;
+    complete_length = 0;
+
+    while (*p == ' ') { p++; }
+
+    if (*p < '0' || *p > '9') {
+        return NGX_ERROR;
+    }
+
+    while (*p >= '0' && *p <= '9') {
+        if (start >= cutoff && (start > cutoff || *p - '0' > cutlim)) {
+            return NGX_ERROR;
+        }
+
+        start = start * 10 + (*p++ - '0');
+    }
+
+    while (*p == ' ') { p++; }
+
+    if (*p++ != '-') {
+        return NGX_ERROR;
+    }
+
+    while (*p == ' ') { p++; }
+
+    if (*p < '0' || *p > '9') {
+        return NGX_ERROR;
+    }
+
+    while (*p >= '0' && *p <= '9') {
+        if (end >= cutoff && (end > cutoff || *p - '0' > cutlim)) {
+            return NGX_ERROR;
+        }
+
+        end = end * 10 + (*p++ - '0');
+    }
+
+    end++;
+
+    while (*p == ' ') { p++; }
+
+    if (*p++ != '/') {
+        return NGX_ERROR;
+    }
+
+    while (*p == ' ') { p++; }
+
+    if (*p != '*') {
+        if (*p < '0' || *p > '9') {
+            return NGX_ERROR;
+        }
+
+        while (*p >= '0' && *p <= '9') {
+            if (complete_length >= cutoff
+                && (complete_length > cutoff || *p - '0' > cutlim))
+            {
+                return NGX_ERROR;
+            }
+
+            complete_length = complete_length * 10 + (*p++ - '0');
+        }
+
+    } else {
+        complete_length = -1;
+        p++;
+    }
+
+    while (*p == ' ') { p++; }
+
+    if (*p != '\0') {
+        return NGX_ERROR;
+    }
+
+    cr->start = start;
+    cr->end = end;
+    cr->complete_length = complete_length;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_slice_range_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char                     *p;
+    ngx_http_slice_ctx_t       *ctx;
+    ngx_http_slice_loc_conf_t  *slcf;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
+
+    if (ctx == NULL) {
+        if (r != r->main || r->headers_out.status) {
+            v->not_found = 1;
+            return NGX_OK;
+        }
+
+        slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module);
+
+        if (slcf->size == 0) {
+            v->not_found = 1;
+            return NGX_OK;
+        }
+
+        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_slice_ctx_t));
+        if (ctx == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_http_set_ctx(r, ctx, ngx_http_slice_filter_module);
+
+        p = ngx_pnalloc(r->pool, sizeof("bytes=-") - 1 + 2 * NGX_OFF_T_LEN);
+        if (p == NULL) {
+            return NGX_ERROR;
+        }
+
+        ctx->start = slcf->size * (ngx_http_slice_get_start(r) / slcf->size);
+
+        ctx->range.data = p;
+        ctx->range.len = ngx_sprintf(p, "bytes=%O-%O", ctx->start,
+                                     ctx->start + (off_t) slcf->size - 1)
+                         - p;
+    }
+
+    v->data = ctx->range.data;
+    v->valid = 1;
+    v->not_found = 0;
+    v->no_cacheable = 1;
+    v->len = ctx->range.len;
+
+    return NGX_OK;
+}
+
+
+static off_t
+ngx_http_slice_get_start(ngx_http_request_t *r)
+{
+    off_t             start, cutoff, cutlim;
+    u_char           *p;
+    ngx_table_elt_t  *h;
+
+    if (r->headers_in.if_range) {
+        return 0;
+    }
+
+    h = r->headers_in.range;
+
+    if (h == NULL
+        || h->value.len < 7
+        || ngx_strncasecmp(h->value.data, (u_char *) "bytes=", 6) != 0)
+    {
+        return 0;
+    }
+
+    p = h->value.data + 6;
+
+    if (ngx_strchr(p, ',')) {
+        return 0;
+    }
+
+    while (*p == ' ') { p++; }
+
+    if (*p == '-') {
+        return 0;
+    }
+
+    cutoff = NGX_MAX_OFF_T_VALUE / 10;
+    cutlim = NGX_MAX_OFF_T_VALUE % 10;
+
+    start = 0;
+
+    while (*p >= '0' && *p <= '9') {
+        if (start >= cutoff && (start > cutoff || *p - '0' > cutlim)) {
+            return 0;
+        }
+
+        start = start * 10 + (*p++ - '0');
+    }
+
+    return start;
+}
+
+
+static void *
+ngx_http_slice_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_slice_loc_conf_t  *slcf;
+
+    slcf = ngx_palloc(cf->pool, sizeof(ngx_http_slice_loc_conf_t));
+    if (slcf == NULL) {
+        return NULL;
+    }
+
+    slcf->size = NGX_CONF_UNSET_SIZE;
+
+    return slcf;
+}
+
+
+static char *
+ngx_http_slice_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_slice_loc_conf_t *prev = parent;
+    ngx_http_slice_loc_conf_t *conf = child;
+
+    ngx_conf_merge_size_value(conf->size, prev->size, 0);
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_slice_add_variables(ngx_conf_t *cf)
+{
+    ngx_http_variable_t  *var;
+
+    var = ngx_http_add_variable(cf, &ngx_http_slice_range_name, 0);
+    if (var == NULL) {
+        return NGX_ERROR;
+    }
+
+    var->get_handler = ngx_http_slice_range_variable;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_slice_init(ngx_conf_t *cf)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_slice_header_filter;
+
+    ngx_http_next_body_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_slice_body_filter;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_split_clients_module.c b/nginx/src/http/modules/ngx_http_split_clients_module.c
new file mode 100644 (file)
index 0000000..2f92c9e
--- /dev/null
@@ -0,0 +1,246 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    uint32_t                    percent;
+    ngx_http_variable_value_t   value;
+} ngx_http_split_clients_part_t;
+
+
+typedef struct {
+    ngx_http_complex_value_t    value;
+    ngx_array_t                 parts;
+} ngx_http_split_clients_ctx_t;
+
+
+static char *ngx_conf_split_clients_block(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_split_clients(ngx_conf_t *cf, ngx_command_t *dummy,
+    void *conf);
+
+static ngx_command_t  ngx_http_split_clients_commands[] = {
+
+    { ngx_string("split_clients"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE2,
+      ngx_conf_split_clients_block,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_split_clients_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_split_clients_module = {
+    NGX_MODULE_V1,
+    &ngx_http_split_clients_module_ctx,    /* module context */
+    ngx_http_split_clients_commands,       /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_split_clients_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_http_split_clients_ctx_t *ctx = (ngx_http_split_clients_ctx_t *) data;
+
+    uint32_t                        hash;
+    ngx_str_t                       val;
+    ngx_uint_t                      i;
+    ngx_http_split_clients_part_t  *part;
+
+    *v = ngx_http_variable_null_value;
+
+    if (ngx_http_complex_value(r, &ctx->value, &val) != NGX_OK) {
+        return NGX_OK;
+    }
+
+    hash = ngx_murmur_hash2(val.data, val.len);
+
+    part = ctx->parts.elts;
+
+    for (i = 0; i < ctx->parts.nelts; i++) {
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http split: %uD %uD", hash, part[i].percent);
+
+        if (hash < part[i].percent || part[i].percent == 0) {
+            *v = part[i].value;
+            return NGX_OK;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_conf_split_clients_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char                                *rv;
+    uint32_t                             sum, last;
+    ngx_str_t                           *value, name;
+    ngx_uint_t                           i;
+    ngx_conf_t                           save;
+    ngx_http_variable_t                 *var;
+    ngx_http_split_clients_ctx_t        *ctx;
+    ngx_http_split_clients_part_t       *part;
+    ngx_http_compile_complex_value_t     ccv;
+
+    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_split_clients_ctx_t));
+    if (ctx == 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 = &ctx->value;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    name = value[2];
+
+    if (name.data[0] != '$') {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid variable name \"%V\"", &name);
+        return NGX_CONF_ERROR;
+    }
+
+    name.len--;
+    name.data++;
+
+    var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
+    if (var == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    var->get_handler = ngx_http_split_clients_variable;
+    var->data = (uintptr_t) ctx;
+
+    if (ngx_array_init(&ctx->parts, cf->pool, 2,
+                       sizeof(ngx_http_split_clients_part_t))
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    save = *cf;
+    cf->ctx = ctx;
+    cf->handler = ngx_http_split_clients;
+    cf->handler_conf = conf;
+
+    rv = ngx_conf_parse(cf, NULL);
+
+    *cf = save;
+
+    if (rv != NGX_CONF_OK) {
+        return rv;
+    }
+
+    sum = 0;
+    last = 0;
+    part = ctx->parts.elts;
+
+    for (i = 0; i < ctx->parts.nelts; i++) {
+        sum = part[i].percent ? sum + part[i].percent : 10000;
+        if (sum > 10000) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "percent total is greater than 100%%");
+            return NGX_CONF_ERROR;
+        }
+
+        if (part[i].percent) {
+            last += part[i].percent * (uint64_t) 0xffffffff / 10000;
+            part[i].percent = last;
+        }
+    }
+
+    return rv;
+}
+
+
+static char *
+ngx_http_split_clients(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
+{
+    ngx_int_t                       n;
+    ngx_str_t                      *value;
+    ngx_http_split_clients_ctx_t   *ctx;
+    ngx_http_split_clients_part_t  *part;
+
+    ctx = cf->ctx;
+    value = cf->args->elts;
+
+    part = ngx_array_push(&ctx->parts);
+    if (part == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (value[0].len == 1 && value[0].data[0] == '*') {
+        part->percent = 0;
+
+    } else {
+        if (value[0].len == 0 || value[0].data[value[0].len - 1] != '%') {
+            goto invalid;
+        }
+
+        n = ngx_atofp(value[0].data, value[0].len - 1, 2);
+        if (n == NGX_ERROR || n == 0) {
+            goto invalid;
+        }
+
+        part->percent = (uint32_t) n;
+    }
+
+    part->value.len = value[1].len;
+    part->value.valid = 1;
+    part->value.no_cacheable = 0;
+    part->value.not_found = 0;
+    part->value.data = value[1].data;
+
+    return NGX_CONF_OK;
+
+invalid:
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "invalid percent value \"%V\"", &value[0]);
+    return NGX_CONF_ERROR;
+}
diff --git a/nginx/src/http/modules/ngx_http_ssi_filter_module.c b/nginx/src/http/modules/ngx_http_ssi_filter_module.c
new file mode 100644 (file)
index 0000000..d608df9
--- /dev/null
@@ -0,0 +1,2933 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#define NGX_HTTP_SSI_ERROR          1
+
+#define NGX_HTTP_SSI_DATE_LEN       2048
+
+#define NGX_HTTP_SSI_ADD_PREFIX     1
+#define NGX_HTTP_SSI_ADD_ZERO       2
+
+
+typedef struct {
+    ngx_flag_t    enable;
+    ngx_flag_t    silent_errors;
+    ngx_flag_t    ignore_recycled_buffers;
+    ngx_flag_t    last_modified;
+
+    ngx_hash_t    types;
+
+    size_t        min_file_chunk;
+    size_t        value_len;
+
+    ngx_array_t  *types_keys;
+} ngx_http_ssi_loc_conf_t;
+
+
+typedef struct {
+    ngx_str_t     name;
+    ngx_uint_t    key;
+    ngx_str_t     value;
+} ngx_http_ssi_var_t;
+
+
+typedef struct {
+    ngx_str_t     name;
+    ngx_chain_t  *bufs;
+    ngx_uint_t    count;
+} ngx_http_ssi_block_t;
+
+
+typedef enum {
+    ssi_start_state = 0,
+    ssi_tag_state,
+    ssi_comment0_state,
+    ssi_comment1_state,
+    ssi_sharp_state,
+    ssi_precommand_state,
+    ssi_command_state,
+    ssi_preparam_state,
+    ssi_param_state,
+    ssi_preequal_state,
+    ssi_prevalue_state,
+    ssi_double_quoted_value_state,
+    ssi_quoted_value_state,
+    ssi_quoted_symbol_state,
+    ssi_postparam_state,
+    ssi_comment_end0_state,
+    ssi_comment_end1_state,
+    ssi_error_state,
+    ssi_error_end0_state,
+    ssi_error_end1_state
+} ngx_http_ssi_state_e;
+
+
+static ngx_int_t ngx_http_ssi_output(ngx_http_request_t *r,
+    ngx_http_ssi_ctx_t *ctx);
+static void ngx_http_ssi_buffered(ngx_http_request_t *r,
+    ngx_http_ssi_ctx_t *ctx);
+static ngx_int_t ngx_http_ssi_parse(ngx_http_request_t *r,
+    ngx_http_ssi_ctx_t *ctx);
+static ngx_str_t *ngx_http_ssi_get_variable(ngx_http_request_t *r,
+    ngx_str_t *name, ngx_uint_t key);
+static ngx_int_t ngx_http_ssi_evaluate_string(ngx_http_request_t *r,
+    ngx_http_ssi_ctx_t *ctx, ngx_str_t *text, ngx_uint_t flags);
+static ngx_int_t ngx_http_ssi_regex_match(ngx_http_request_t *r,
+    ngx_str_t *pattern, ngx_str_t *str);
+
+static ngx_int_t ngx_http_ssi_include(ngx_http_request_t *r,
+    ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
+static ngx_int_t ngx_http_ssi_stub_output(ngx_http_request_t *r, void *data,
+    ngx_int_t rc);
+static ngx_int_t ngx_http_ssi_set_variable(ngx_http_request_t *r, void *data,
+    ngx_int_t rc);
+static ngx_int_t ngx_http_ssi_echo(ngx_http_request_t *r,
+    ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
+static ngx_int_t ngx_http_ssi_config(ngx_http_request_t *r,
+    ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
+static ngx_int_t ngx_http_ssi_set(ngx_http_request_t *r,
+    ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
+static ngx_int_t ngx_http_ssi_if(ngx_http_request_t *r,
+    ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
+static ngx_int_t ngx_http_ssi_else(ngx_http_request_t *r,
+    ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
+static ngx_int_t ngx_http_ssi_endif(ngx_http_request_t *r,
+    ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
+static ngx_int_t ngx_http_ssi_block(ngx_http_request_t *r,
+    ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
+static ngx_int_t ngx_http_ssi_endblock(ngx_http_request_t *r,
+    ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
+
+static ngx_int_t ngx_http_ssi_date_gmt_local_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t gmt);
+
+static ngx_int_t ngx_http_ssi_preconfiguration(ngx_conf_t *cf);
+static void *ngx_http_ssi_create_main_conf(ngx_conf_t *cf);
+static char *ngx_http_ssi_init_main_conf(ngx_conf_t *cf, void *conf);
+static void *ngx_http_ssi_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_ssi_merge_loc_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+static ngx_int_t ngx_http_ssi_filter_init(ngx_conf_t *cf);
+
+
+static ngx_command_t  ngx_http_ssi_filter_commands[] = {
+
+    { ngx_string("ssi"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+                        |NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_ssi_loc_conf_t, enable),
+      NULL },
+
+    { ngx_string("ssi_silent_errors"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_ssi_loc_conf_t, silent_errors),
+      NULL },
+
+    { ngx_string("ssi_ignore_recycled_buffers"),
+      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_ssi_loc_conf_t, ignore_recycled_buffers),
+      NULL },
+
+    { ngx_string("ssi_min_file_chunk"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_ssi_loc_conf_t, min_file_chunk),
+      NULL },
+
+    { ngx_string("ssi_value_length"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_ssi_loc_conf_t, value_len),
+      NULL },
+
+    { ngx_string("ssi_types"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_types_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_ssi_loc_conf_t, types_keys),
+      &ngx_http_html_default_types[0] },
+
+    { ngx_string("ssi_last_modified"),
+      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_ssi_loc_conf_t, last_modified),
+      NULL },
+
+      ngx_null_command
+};
+
+
+
+static ngx_http_module_t  ngx_http_ssi_filter_module_ctx = {
+    ngx_http_ssi_preconfiguration,         /* preconfiguration */
+    ngx_http_ssi_filter_init,              /* postconfiguration */
+
+    ngx_http_ssi_create_main_conf,         /* create main configuration */
+    ngx_http_ssi_init_main_conf,           /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_ssi_create_loc_conf,          /* create location configuration */
+    ngx_http_ssi_merge_loc_conf            /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_ssi_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_ssi_filter_module_ctx,       /* module context */
+    ngx_http_ssi_filter_commands,          /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
+
+
+static u_char ngx_http_ssi_string[] = "<!--";
+
+static ngx_str_t ngx_http_ssi_none = ngx_string("(none)");
+static ngx_str_t ngx_http_ssi_timefmt = ngx_string("%A, %d-%b-%Y %H:%M:%S %Z");
+static ngx_str_t ngx_http_ssi_null_string = ngx_null_string;
+
+
+#define  NGX_HTTP_SSI_INCLUDE_VIRTUAL  0
+#define  NGX_HTTP_SSI_INCLUDE_FILE     1
+#define  NGX_HTTP_SSI_INCLUDE_WAIT     2
+#define  NGX_HTTP_SSI_INCLUDE_SET      3
+#define  NGX_HTTP_SSI_INCLUDE_STUB     4
+
+#define  NGX_HTTP_SSI_ECHO_VAR         0
+#define  NGX_HTTP_SSI_ECHO_DEFAULT     1
+#define  NGX_HTTP_SSI_ECHO_ENCODING    2
+
+#define  NGX_HTTP_SSI_CONFIG_ERRMSG    0
+#define  NGX_HTTP_SSI_CONFIG_TIMEFMT   1
+
+#define  NGX_HTTP_SSI_SET_VAR          0
+#define  NGX_HTTP_SSI_SET_VALUE        1
+
+#define  NGX_HTTP_SSI_IF_EXPR          0
+
+#define  NGX_HTTP_SSI_BLOCK_NAME       0
+
+
+static ngx_http_ssi_param_t  ngx_http_ssi_include_params[] = {
+    { ngx_string("virtual"), NGX_HTTP_SSI_INCLUDE_VIRTUAL, 0, 0 },
+    { ngx_string("file"), NGX_HTTP_SSI_INCLUDE_FILE, 0, 0 },
+    { ngx_string("wait"), NGX_HTTP_SSI_INCLUDE_WAIT, 0, 0 },
+    { ngx_string("set"), NGX_HTTP_SSI_INCLUDE_SET, 0, 0 },
+    { ngx_string("stub"), NGX_HTTP_SSI_INCLUDE_STUB, 0, 0 },
+    { ngx_null_string, 0, 0, 0 }
+};
+
+
+static ngx_http_ssi_param_t  ngx_http_ssi_echo_params[] = {
+    { ngx_string("var"), NGX_HTTP_SSI_ECHO_VAR, 1, 0 },
+    { ngx_string("default"), NGX_HTTP_SSI_ECHO_DEFAULT, 0, 0 },
+    { ngx_string("encoding"), NGX_HTTP_SSI_ECHO_ENCODING, 0, 0 },
+    { ngx_null_string, 0, 0, 0 }
+};
+
+
+static ngx_http_ssi_param_t  ngx_http_ssi_config_params[] = {
+    { ngx_string("errmsg"), NGX_HTTP_SSI_CONFIG_ERRMSG, 0, 0 },
+    { ngx_string("timefmt"), NGX_HTTP_SSI_CONFIG_TIMEFMT, 0, 0 },
+    { ngx_null_string, 0, 0, 0 }
+};
+
+
+static ngx_http_ssi_param_t  ngx_http_ssi_set_params[] = {
+    { ngx_string("var"), NGX_HTTP_SSI_SET_VAR, 1, 0 },
+    { ngx_string("value"), NGX_HTTP_SSI_SET_VALUE, 1, 0 },
+    { ngx_null_string, 0, 0, 0 }
+};
+
+
+static ngx_http_ssi_param_t  ngx_http_ssi_if_params[] = {
+    { ngx_string("expr"), NGX_HTTP_SSI_IF_EXPR, 1, 0 },
+    { ngx_null_string, 0, 0, 0 }
+};
+
+
+static ngx_http_ssi_param_t  ngx_http_ssi_block_params[] = {
+    { ngx_string("name"), NGX_HTTP_SSI_BLOCK_NAME, 1, 0 },
+    { ngx_null_string, 0, 0, 0 }
+};
+
+
+static ngx_http_ssi_param_t  ngx_http_ssi_no_params[] = {
+    { ngx_null_string, 0, 0, 0 }
+};
+
+
+static ngx_http_ssi_command_t  ngx_http_ssi_commands[] = {
+    { ngx_string("include"), ngx_http_ssi_include,
+                       ngx_http_ssi_include_params, 0, 0, 1 },
+    { ngx_string("echo"), ngx_http_ssi_echo,
+                       ngx_http_ssi_echo_params, 0, 0, 0 },
+    { ngx_string("config"), ngx_http_ssi_config,
+                       ngx_http_ssi_config_params, 0, 0, 0 },
+    { ngx_string("set"), ngx_http_ssi_set, ngx_http_ssi_set_params, 0, 0, 0 },
+
+    { ngx_string("if"), ngx_http_ssi_if, ngx_http_ssi_if_params, 0, 0, 0 },
+    { ngx_string("elif"), ngx_http_ssi_if, ngx_http_ssi_if_params,
+                       NGX_HTTP_SSI_COND_IF, 0, 0 },
+    { ngx_string("else"), ngx_http_ssi_else, ngx_http_ssi_no_params,
+                       NGX_HTTP_SSI_COND_IF, 0, 0 },
+    { ngx_string("endif"), ngx_http_ssi_endif, ngx_http_ssi_no_params,
+                       NGX_HTTP_SSI_COND_ELSE, 0, 0 },
+
+    { ngx_string("block"), ngx_http_ssi_block,
+                       ngx_http_ssi_block_params, 0, 0, 0 },
+    { ngx_string("endblock"), ngx_http_ssi_endblock,
+                       ngx_http_ssi_no_params, 0, 1, 0 },
+
+    { ngx_null_string, NULL, NULL, 0, 0, 0 }
+};
+
+
+static ngx_http_variable_t  ngx_http_ssi_vars[] = {
+
+    { ngx_string("date_local"), NULL, ngx_http_ssi_date_gmt_local_variable, 0,
+      NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("date_gmt"), NULL, ngx_http_ssi_date_gmt_local_variable, 1,
+      NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+      ngx_http_null_variable
+};
+
+
+
+static ngx_int_t
+ngx_http_ssi_header_filter(ngx_http_request_t *r)
+{
+    ngx_http_ssi_ctx_t       *ctx;
+    ngx_http_ssi_loc_conf_t  *slcf;
+
+    slcf = ngx_http_get_module_loc_conf(r, ngx_http_ssi_filter_module);
+
+    if (!slcf->enable
+        || r->headers_out.content_length_n == 0
+        || ngx_http_test_content_type(r, &slcf->types) == NULL)
+    {
+        return ngx_http_next_header_filter(r);
+    }
+
+    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_ssi_ctx_t));
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_http_set_ctx(r, ctx, ngx_http_ssi_filter_module);
+
+
+    ctx->value_len = slcf->value_len;
+    ctx->last_out = &ctx->out;
+
+    ctx->encoding = NGX_HTTP_SSI_ENTITY_ENCODING;
+    ctx->output = 1;
+
+    ctx->params.elts = ctx->params_array;
+    ctx->params.size = sizeof(ngx_table_elt_t);
+    ctx->params.nalloc = NGX_HTTP_SSI_PARAMS_N;
+    ctx->params.pool = r->pool;
+
+    ctx->timefmt = ngx_http_ssi_timefmt;
+    ngx_str_set(&ctx->errmsg,
+                "[an error occurred while processing the directive]");
+
+    r->filter_need_in_memory = 1;
+
+    if (r == r->main) {
+        ngx_http_clear_content_length(r);
+        ngx_http_clear_accept_ranges(r);
+
+        r->preserve_body = 1;
+
+        if (!slcf->last_modified) {
+            ngx_http_clear_last_modified(r);
+            ngx_http_clear_etag(r);
+
+        } else {
+            ngx_http_weak_etag(r);
+        }
+    }
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_ssi_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    size_t                     len;
+    ngx_int_t                  rc;
+    ngx_buf_t                 *b;
+    ngx_uint_t                 i, index;
+    ngx_chain_t               *cl, **ll;
+    ngx_table_elt_t           *param;
+    ngx_http_ssi_ctx_t        *ctx, *mctx;
+    ngx_http_ssi_block_t      *bl;
+    ngx_http_ssi_param_t      *prm;
+    ngx_http_ssi_command_t    *cmd;
+    ngx_http_ssi_loc_conf_t   *slcf;
+    ngx_http_ssi_main_conf_t  *smcf;
+    ngx_str_t                 *params[NGX_HTTP_SSI_MAX_PARAMS + 1];
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_ssi_filter_module);
+
+    if (ctx == NULL
+        || (in == NULL
+            && ctx->buf == NULL
+            && ctx->in == NULL
+            && ctx->busy == NULL))
+    {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    /* add the incoming chain to the chain ctx->in */
+
+    if (in) {
+        if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http ssi filter \"%V?%V\"", &r->uri, &r->args);
+
+    if (ctx->wait) {
+
+        if (r != r->connection->data) {
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http ssi filter wait \"%V?%V\" non-active",
+                           &ctx->wait->uri, &ctx->wait->args);
+
+            return NGX_AGAIN;
+        }
+
+        if (ctx->wait->done) {
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http ssi filter wait \"%V?%V\" done",
+                           &ctx->wait->uri, &ctx->wait->args);
+
+            ctx->wait = NULL;
+
+        } else {
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http ssi filter wait \"%V?%V\"",
+                           &ctx->wait->uri, &ctx->wait->args);
+
+            return ngx_http_next_body_filter(r, NULL);
+        }
+    }
+
+    slcf = ngx_http_get_module_loc_conf(r, ngx_http_ssi_filter_module);
+
+    while (ctx->in || ctx->buf) {
+
+        if (ctx->buf == NULL) {
+            ctx->buf = ctx->in->buf;
+            ctx->in = ctx->in->next;
+            ctx->pos = ctx->buf->pos;
+        }
+
+        if (ctx->state == ssi_start_state) {
+            ctx->copy_start = ctx->pos;
+            ctx->copy_end = ctx->pos;
+        }
+
+        b = NULL;
+
+        while (ctx->pos < ctx->buf->last) {
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "saved: %uz state: %ui", ctx->saved, ctx->state);
+
+            rc = ngx_http_ssi_parse(r, ctx);
+
+            ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "parse: %i, looked: %uz %p-%p",
+                           rc, ctx->looked, ctx->copy_start, ctx->copy_end);
+
+            if (rc == NGX_ERROR) {
+                return rc;
+            }
+
+            if (ctx->copy_start != ctx->copy_end) {
+
+                if (ctx->output) {
+
+                    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                                   "saved: %uz", ctx->saved);
+
+                    if (ctx->saved) {
+
+                        if (ctx->free) {
+                            cl = ctx->free;
+                            ctx->free = ctx->free->next;
+                            b = cl->buf;
+                            ngx_memzero(b, sizeof(ngx_buf_t));
+
+                        } else {
+                            b = ngx_calloc_buf(r->pool);
+                            if (b == NULL) {
+                                return NGX_ERROR;
+                            }
+
+                            cl = ngx_alloc_chain_link(r->pool);
+                            if (cl == NULL) {
+                                return NGX_ERROR;
+                            }
+
+                            cl->buf = b;
+                        }
+
+                        b->memory = 1;
+                        b->pos = ngx_http_ssi_string;
+                        b->last = ngx_http_ssi_string + ctx->saved;
+
+                        *ctx->last_out = cl;
+                        ctx->last_out = &cl->next;
+
+                        ctx->saved = 0;
+                    }
+
+                    if (ctx->free) {
+                        cl = ctx->free;
+                        ctx->free = ctx->free->next;
+                        b = cl->buf;
+
+                    } else {
+                        b = ngx_alloc_buf(r->pool);
+                        if (b == NULL) {
+                            return NGX_ERROR;
+                        }
+
+                        cl = ngx_alloc_chain_link(r->pool);
+                        if (cl == NULL) {
+                            return NGX_ERROR;
+                        }
+
+                        cl->buf = b;
+                    }
+
+                    ngx_memcpy(b, ctx->buf, sizeof(ngx_buf_t));
+
+                    b->pos = ctx->copy_start;
+                    b->last = ctx->copy_end;
+                    b->shadow = NULL;
+                    b->last_buf = 0;
+                    b->recycled = 0;
+
+                    if (b->in_file) {
+                        if (slcf->min_file_chunk < (size_t) (b->last - b->pos))
+                        {
+                            b->file_last = b->file_pos
+                                                   + (b->last - ctx->buf->pos);
+                            b->file_pos += b->pos - ctx->buf->pos;
+
+                        } else {
+                            b->in_file = 0;
+                        }
+                    }
+
+                    cl->next = NULL;
+                    *ctx->last_out = cl;
+                    ctx->last_out = &cl->next;
+
+                } else {
+                    if (ctx->block
+                        && ctx->saved + (ctx->copy_end - ctx->copy_start))
+                    {
+                        b = ngx_create_temp_buf(r->pool,
+                               ctx->saved + (ctx->copy_end - ctx->copy_start));
+
+                        if (b == NULL) {
+                            return NGX_ERROR;
+                        }
+
+                        if (ctx->saved) {
+                            b->last = ngx_cpymem(b->pos, ngx_http_ssi_string,
+                                                 ctx->saved);
+                        }
+
+                        b->last = ngx_cpymem(b->last, ctx->copy_start,
+                                             ctx->copy_end - ctx->copy_start);
+
+                        cl = ngx_alloc_chain_link(r->pool);
+                        if (cl == NULL) {
+                            return NGX_ERROR;
+                        }
+
+                        cl->buf = b;
+                        cl->next = NULL;
+
+                        b = NULL;
+
+                        mctx = ngx_http_get_module_ctx(r->main,
+                                                   ngx_http_ssi_filter_module);
+                        bl = mctx->blocks->elts;
+                        for (ll = &bl[mctx->blocks->nelts - 1].bufs;
+                             *ll;
+                             ll = &(*ll)->next)
+                        {
+                            /* void */
+                        }
+
+                        *ll = cl;
+                    }
+
+                    ctx->saved = 0;
+                }
+            }
+
+            if (ctx->state == ssi_start_state) {
+                ctx->copy_start = ctx->pos;
+                ctx->copy_end = ctx->pos;
+
+            } else {
+                ctx->copy_start = NULL;
+                ctx->copy_end = NULL;
+            }
+
+            if (rc == NGX_AGAIN) {
+                continue;
+            }
+
+
+            b = NULL;
+
+            if (rc == NGX_OK) {
+
+                smcf = ngx_http_get_module_main_conf(r,
+                                                   ngx_http_ssi_filter_module);
+
+                cmd = ngx_hash_find(&smcf->hash, ctx->key, ctx->command.data,
+                                    ctx->command.len);
+
+                if (cmd == NULL) {
+                    if (ctx->output) {
+                        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                      "invalid SSI command: \"%V\"",
+                                      &ctx->command);
+                        goto ssi_error;
+                    }
+
+                    continue;
+                }
+
+                if (!ctx->output && !cmd->block) {
+
+                    if (ctx->block) {
+
+                        /* reconstruct the SSI command text */
+
+                        len = 5 + ctx->command.len + 4;
+
+                        param = ctx->params.elts;
+                        for (i = 0; i < ctx->params.nelts; i++) {
+                            len += 1 + param[i].key.len + 2
+                                + param[i].value.len + 1;
+                        }
+
+                        b = ngx_create_temp_buf(r->pool, len);
+
+                        if (b == NULL) {
+                            return NGX_ERROR;
+                        }
+
+                        cl = ngx_alloc_chain_link(r->pool);
+                        if (cl == NULL) {
+                            return NGX_ERROR;
+                        }
+
+                        cl->buf = b;
+                        cl->next = NULL;
+
+                        *b->last++ = '<';
+                        *b->last++ = '!';
+                        *b->last++ = '-';
+                        *b->last++ = '-';
+                        *b->last++ = '#';
+
+                        b->last = ngx_cpymem(b->last, ctx->command.data,
+                                             ctx->command.len);
+
+                        for (i = 0; i < ctx->params.nelts; i++) {
+                            *b->last++ = ' ';
+                            b->last = ngx_cpymem(b->last, param[i].key.data,
+                                                 param[i].key.len);
+                            *b->last++ = '=';
+                            *b->last++ = '"';
+                            b->last = ngx_cpymem(b->last, param[i].value.data,
+                                                 param[i].value.len);
+                            *b->last++ = '"';
+                        }
+
+                        *b->last++ = ' ';
+                        *b->last++ = '-';
+                        *b->last++ = '-';
+                        *b->last++ = '>';
+
+                        mctx = ngx_http_get_module_ctx(r->main,
+                                                   ngx_http_ssi_filter_module);
+                        bl = mctx->blocks->elts;
+                        for (ll = &bl[mctx->blocks->nelts - 1].bufs;
+                             *ll;
+                             ll = &(*ll)->next)
+                        {
+                            /* void */
+                        }
+
+                        *ll = cl;
+
+                        b = NULL;
+
+                        continue;
+                    }
+
+                    if (cmd->conditional == 0) {
+                        continue;
+                    }
+                }
+
+                if (cmd->conditional
+                    && (ctx->conditional == 0
+                        || ctx->conditional > cmd->conditional))
+                {
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                  "invalid context of SSI command: \"%V\"",
+                                  &ctx->command);
+                    goto ssi_error;
+                }
+
+                if (ctx->params.nelts > NGX_HTTP_SSI_MAX_PARAMS) {
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                  "too many SSI command parameters: \"%V\"",
+                                  &ctx->command);
+                    goto ssi_error;
+                }
+
+                ngx_memzero(params,
+                           (NGX_HTTP_SSI_MAX_PARAMS + 1) * sizeof(ngx_str_t *));
+
+                param = ctx->params.elts;
+
+                for (i = 0; i < ctx->params.nelts; i++) {
+
+                    for (prm = cmd->params; prm->name.len; prm++) {
+
+                        if (param[i].key.len != prm->name.len
+                            || ngx_strncmp(param[i].key.data, prm->name.data,
+                                           prm->name.len) != 0)
+                        {
+                            continue;
+                        }
+
+                        if (!prm->multiple) {
+                            if (params[prm->index]) {
+                                ngx_log_error(NGX_LOG_ERR,
+                                              r->connection->log, 0,
+                                              "duplicate \"%V\" parameter "
+                                              "in \"%V\" SSI command",
+                                              &param[i].key, &ctx->command);
+
+                                goto ssi_error;
+                            }
+
+                            params[prm->index] = &param[i].value;
+
+                            break;
+                        }
+
+                        for (index = prm->index; params[index]; index++) {
+                            /* void */
+                        }
+
+                        params[index] = &param[i].value;
+
+                        break;
+                    }
+
+                    if (prm->name.len == 0) {
+                        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                      "invalid parameter name: \"%V\" "
+                                      "in \"%V\" SSI command",
+                                      &param[i].key, &ctx->command);
+
+                        goto ssi_error;
+                    }
+                }
+
+                for (prm = cmd->params; prm->name.len; prm++) {
+                    if (prm->mandatory && params[prm->index] == 0) {
+                        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                      "mandatory \"%V\" parameter is absent "
+                                      "in \"%V\" SSI command",
+                                      &prm->name, &ctx->command);
+
+                        goto ssi_error;
+                    }
+                }
+
+                if (cmd->flush && ctx->out) {
+
+                    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                                   "ssi flush");
+
+                    if (ngx_http_ssi_output(r, ctx) == NGX_ERROR) {
+                        return NGX_ERROR;
+                    }
+                }
+
+                rc = cmd->handler(r, ctx, params);
+
+                if (rc == NGX_OK) {
+                    continue;
+                }
+
+                if (rc == NGX_DONE || rc == NGX_AGAIN || rc == NGX_ERROR) {
+                    ngx_http_ssi_buffered(r, ctx);
+                    return rc;
+                }
+            }
+
+
+            /* rc == NGX_HTTP_SSI_ERROR */
+
+    ssi_error:
+
+            if (slcf->silent_errors) {
+                continue;
+            }
+
+            if (ctx->free) {
+                cl = ctx->free;
+                ctx->free = ctx->free->next;
+                b = cl->buf;
+                ngx_memzero(b, sizeof(ngx_buf_t));
+
+            } else {
+                b = ngx_calloc_buf(r->pool);
+                if (b == NULL) {
+                    return NGX_ERROR;
+                }
+
+                cl = ngx_alloc_chain_link(r->pool);
+                if (cl == NULL) {
+                    return NGX_ERROR;
+                }
+
+                cl->buf = b;
+            }
+
+            b->memory = 1;
+            b->pos = ctx->errmsg.data;
+            b->last = ctx->errmsg.data + ctx->errmsg.len;
+
+            cl->next = NULL;
+            *ctx->last_out = cl;
+            ctx->last_out = &cl->next;
+
+            continue;
+        }
+
+        if (ctx->buf->last_buf || ngx_buf_in_memory(ctx->buf)) {
+            if (b == NULL) {
+                if (ctx->free) {
+                    cl = ctx->free;
+                    ctx->free = ctx->free->next;
+                    b = cl->buf;
+                    ngx_memzero(b, sizeof(ngx_buf_t));
+
+                } else {
+                    b = ngx_calloc_buf(r->pool);
+                    if (b == NULL) {
+                        return NGX_ERROR;
+                    }
+
+                    cl = ngx_alloc_chain_link(r->pool);
+                    if (cl == NULL) {
+                        return NGX_ERROR;
+                    }
+
+                    cl->buf = b;
+                }
+
+                b->sync = 1;
+
+                cl->next = NULL;
+                *ctx->last_out = cl;
+                ctx->last_out = &cl->next;
+            }
+
+            b->last_buf = ctx->buf->last_buf;
+            b->shadow = ctx->buf;
+
+            if (slcf->ignore_recycled_buffers == 0)  {
+                b->recycled = ctx->buf->recycled;
+            }
+        }
+
+        ctx->buf = NULL;
+
+        ctx->saved = ctx->looked;
+    }
+
+    if (ctx->out == NULL && ctx->busy == NULL) {
+        return NGX_OK;
+    }
+
+    return ngx_http_ssi_output(r, ctx);
+}
+
+
+static ngx_int_t
+ngx_http_ssi_output(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx)
+{
+    ngx_int_t     rc;
+    ngx_buf_t    *b;
+    ngx_chain_t  *cl;
+
+#if 1
+    b = NULL;
+    for (cl = ctx->out; cl; cl = cl->next) {
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "ssi out: %p %p", cl->buf, cl->buf->pos);
+        if (cl->buf == b) {
+            ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                          "the same buf was used in ssi");
+            ngx_debug_point();
+            return NGX_ERROR;
+        }
+        b = cl->buf;
+    }
+#endif
+
+    rc = ngx_http_next_body_filter(r, ctx->out);
+
+    if (ctx->busy == NULL) {
+        ctx->busy = ctx->out;
+
+    } else {
+        for (cl = ctx->busy; cl->next; cl = cl->next) { /* void */ }
+        cl->next = ctx->out;
+    }
+
+    ctx->out = NULL;
+    ctx->last_out = &ctx->out;
+
+    while (ctx->busy) {
+
+        cl = ctx->busy;
+        b = cl->buf;
+
+        if (ngx_buf_size(b) != 0) {
+            break;
+        }
+
+        if (b->shadow) {
+            b->shadow->pos = b->shadow->last;
+        }
+
+        ctx->busy = cl->next;
+
+        if (ngx_buf_in_memory(b) || b->in_file) {
+            /* add data bufs only to the free buf chain */
+
+            cl->next = ctx->free;
+            ctx->free = cl;
+        }
+    }
+
+    ngx_http_ssi_buffered(r, ctx);
+
+    return rc;
+}
+
+
+static void
+ngx_http_ssi_buffered(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx)
+{
+    if (ctx->in || ctx->buf) {
+        r->buffered |= NGX_HTTP_SSI_BUFFERED;
+
+    } else {
+        r->buffered &= ~NGX_HTTP_SSI_BUFFERED;
+    }
+}
+
+
+static ngx_int_t
+ngx_http_ssi_parse(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx)
+{
+    u_char                *p, *value, *last, *copy_end, ch;
+    size_t                 looked;
+    ngx_http_ssi_state_e   state;
+
+    state = ctx->state;
+    looked = ctx->looked;
+    last = ctx->buf->last;
+    copy_end = ctx->copy_end;
+
+    for (p = ctx->pos; p < last; p++) {
+
+        ch = *p;
+
+        if (state == ssi_start_state) {
+
+            /* the tight loop */
+
+            for ( ;; ) {
+                if (ch == '<') {
+                    copy_end = p;
+                    looked = 1;
+                    state = ssi_tag_state;
+
+                    goto tag_started;
+                }
+
+                if (++p == last) {
+                    break;
+                }
+
+                ch = *p;
+            }
+
+            ctx->state = state;
+            ctx->pos = p;
+            ctx->looked = looked;
+            ctx->copy_end = p;
+
+            if (ctx->copy_start == NULL) {
+                ctx->copy_start = ctx->buf->pos;
+            }
+
+            return NGX_AGAIN;
+
+        tag_started:
+
+            continue;
+        }
+
+        switch (state) {
+
+        case ssi_start_state:
+            /* not reached */
+            break;
+
+        case ssi_tag_state:
+            switch (ch) {
+            case '!':
+                looked = 2;
+                state = ssi_comment0_state;
+                break;
+
+            case '<':
+                copy_end = p;
+                break;
+
+            default:
+                copy_end = p;
+                looked = 0;
+                state = ssi_start_state;
+                break;
+            }
+
+            break;
+
+        case ssi_comment0_state:
+            switch (ch) {
+            case '-':
+                looked = 3;
+                state = ssi_comment1_state;
+                break;
+
+            case '<':
+                copy_end = p;
+                looked = 1;
+                state = ssi_tag_state;
+                break;
+
+            default:
+                copy_end = p;
+                looked = 0;
+                state = ssi_start_state;
+                break;
+            }
+
+            break;
+
+        case ssi_comment1_state:
+            switch (ch) {
+            case '-':
+                looked = 4;
+                state = ssi_sharp_state;
+                break;
+
+            case '<':
+                copy_end = p;
+                looked = 1;
+                state = ssi_tag_state;
+                break;
+
+            default:
+                copy_end = p;
+                looked = 0;
+                state = ssi_start_state;
+                break;
+            }
+
+            break;
+
+        case ssi_sharp_state:
+            switch (ch) {
+            case '#':
+                if (p - ctx->pos < 4) {
+                    ctx->saved = 0;
+                }
+                looked = 0;
+                state = ssi_precommand_state;
+                break;
+
+            case '<':
+                copy_end = p;
+                looked = 1;
+                state = ssi_tag_state;
+                break;
+
+            default:
+                copy_end = p;
+                looked = 0;
+                state = ssi_start_state;
+                break;
+            }
+
+            break;
+
+        case ssi_precommand_state:
+            switch (ch) {
+            case ' ':
+            case CR:
+            case LF:
+            case '\t':
+                break;
+
+            default:
+                ctx->command.len = 1;
+                ctx->command.data = ngx_pnalloc(r->pool,
+                                                NGX_HTTP_SSI_COMMAND_LEN);
+                if (ctx->command.data == NULL) {
+                    return NGX_ERROR;
+                }
+
+                ctx->command.data[0] = ch;
+
+                ctx->key = 0;
+                ctx->key = ngx_hash(ctx->key, ch);
+
+                ctx->params.nelts = 0;
+
+                state = ssi_command_state;
+                break;
+            }
+
+            break;
+
+        case ssi_command_state:
+            switch (ch) {
+            case ' ':
+            case CR:
+            case LF:
+            case '\t':
+                state = ssi_preparam_state;
+                break;
+
+            case '-':
+                state = ssi_comment_end0_state;
+                break;
+
+            default:
+                if (ctx->command.len == NGX_HTTP_SSI_COMMAND_LEN) {
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                  "the \"%V%c...\" SSI command is too long",
+                                  &ctx->command, ch);
+
+                    state = ssi_error_state;
+                    break;
+                }
+
+                ctx->command.data[ctx->command.len++] = ch;
+                ctx->key = ngx_hash(ctx->key, ch);
+            }
+
+            break;
+
+        case ssi_preparam_state:
+            switch (ch) {
+            case ' ':
+            case CR:
+            case LF:
+            case '\t':
+                break;
+
+            case '-':
+                state = ssi_comment_end0_state;
+                break;
+
+            default:
+                ctx->param = ngx_array_push(&ctx->params);
+                if (ctx->param == NULL) {
+                    return NGX_ERROR;
+                }
+
+                ctx->param->key.len = 1;
+                ctx->param->key.data = ngx_pnalloc(r->pool,
+                                                   NGX_HTTP_SSI_PARAM_LEN);
+                if (ctx->param->key.data == NULL) {
+                    return NGX_ERROR;
+                }
+
+                ctx->param->key.data[0] = ch;
+
+                ctx->param->value.len = 0;
+
+                if (ctx->value_buf == NULL) {
+                    ctx->param->value.data = ngx_pnalloc(r->pool,
+                                                         ctx->value_len + 1);
+                    if (ctx->param->value.data == NULL) {
+                        return NGX_ERROR;
+                    }
+
+                } else {
+                    ctx->param->value.data = ctx->value_buf;
+                }
+
+                state = ssi_param_state;
+                break;
+            }
+
+            break;
+
+        case ssi_param_state:
+            switch (ch) {
+            case ' ':
+            case CR:
+            case LF:
+            case '\t':
+                state = ssi_preequal_state;
+                break;
+
+            case '=':
+                state = ssi_prevalue_state;
+                break;
+
+            case '-':
+                state = ssi_error_end0_state;
+
+                ctx->param->key.data[ctx->param->key.len++] = ch;
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "invalid \"%V\" parameter in \"%V\" SSI command",
+                              &ctx->param->key, &ctx->command);
+                break;
+
+            default:
+                if (ctx->param->key.len == NGX_HTTP_SSI_PARAM_LEN) {
+                    state = ssi_error_state;
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                  "too long \"%V%c...\" parameter in "
+                                  "\"%V\" SSI command",
+                                  &ctx->param->key, ch, &ctx->command);
+                    break;
+                }
+
+                ctx->param->key.data[ctx->param->key.len++] = ch;
+            }
+
+            break;
+
+        case ssi_preequal_state:
+            switch (ch) {
+            case ' ':
+            case CR:
+            case LF:
+            case '\t':
+                break;
+
+            case '=':
+                state = ssi_prevalue_state;
+                break;
+
+            default:
+                if (ch == '-') {
+                    state = ssi_error_end0_state;
+                } else {
+                    state = ssi_error_state;
+                }
+
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "unexpected \"%c\" symbol after \"%V\" "
+                              "parameter in \"%V\" SSI command",
+                              ch, &ctx->param->key, &ctx->command);
+                break;
+            }
+
+            break;
+
+        case ssi_prevalue_state:
+            switch (ch) {
+            case ' ':
+            case CR:
+            case LF:
+            case '\t':
+                break;
+
+            case '"':
+                state = ssi_double_quoted_value_state;
+                break;
+
+            case '\'':
+                state = ssi_quoted_value_state;
+                break;
+
+            default:
+                if (ch == '-') {
+                    state = ssi_error_end0_state;
+                } else {
+                    state = ssi_error_state;
+                }
+
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "unexpected \"%c\" symbol before value of "
+                              "\"%V\" parameter in \"%V\" SSI command",
+                              ch, &ctx->param->key, &ctx->command);
+                break;
+            }
+
+            break;
+
+        case ssi_double_quoted_value_state:
+            switch (ch) {
+            case '"':
+                state = ssi_postparam_state;
+                break;
+
+            case '\\':
+                ctx->saved_state = ssi_double_quoted_value_state;
+                state = ssi_quoted_symbol_state;
+
+                /* fall through */
+
+            default:
+                if (ctx->param->value.len == ctx->value_len) {
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                  "too long \"%V%c...\" value of \"%V\" "
+                                  "parameter in \"%V\" SSI command",
+                                  &ctx->param->value, ch, &ctx->param->key,
+                                  &ctx->command);
+                    state = ssi_error_state;
+                    break;
+                }
+
+                ctx->param->value.data[ctx->param->value.len++] = ch;
+            }
+
+            break;
+
+        case ssi_quoted_value_state:
+            switch (ch) {
+            case '\'':
+                state = ssi_postparam_state;
+                break;
+
+            case '\\':
+                ctx->saved_state = ssi_quoted_value_state;
+                state = ssi_quoted_symbol_state;
+
+                /* fall through */
+
+            default:
+                if (ctx->param->value.len == ctx->value_len) {
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                  "too long \"%V%c...\" value of \"%V\" "
+                                  "parameter in \"%V\" SSI command",
+                                  &ctx->param->value, ch, &ctx->param->key,
+                                  &ctx->command);
+                    state = ssi_error_state;
+                    break;
+                }
+
+                ctx->param->value.data[ctx->param->value.len++] = ch;
+            }
+
+            break;
+
+        case ssi_quoted_symbol_state:
+            state = ctx->saved_state;
+
+            if (ctx->param->value.len == ctx->value_len) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "too long \"%V%c...\" value of \"%V\" "
+                              "parameter in \"%V\" SSI command",
+                              &ctx->param->value, ch, &ctx->param->key,
+                              &ctx->command);
+                state = ssi_error_state;
+                break;
+            }
+
+            ctx->param->value.data[ctx->param->value.len++] = ch;
+
+            break;
+
+        case ssi_postparam_state:
+
+            if (ctx->param->value.len + 1 < ctx->value_len / 2) {
+                value = ngx_pnalloc(r->pool, ctx->param->value.len + 1);
+                if (value == NULL) {
+                    return NGX_ERROR;
+                }
+
+                ngx_memcpy(value, ctx->param->value.data,
+                           ctx->param->value.len);
+
+                ctx->value_buf = ctx->param->value.data;
+                ctx->param->value.data = value;
+
+            } else {
+                ctx->value_buf = NULL;
+            }
+
+            switch (ch) {
+            case ' ':
+            case CR:
+            case LF:
+            case '\t':
+                state = ssi_preparam_state;
+                break;
+
+            case '-':
+                state = ssi_comment_end0_state;
+                break;
+
+            default:
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "unexpected \"%c\" symbol after \"%V\" value "
+                              "of \"%V\" parameter in \"%V\" SSI command",
+                              ch, &ctx->param->value, &ctx->param->key,
+                              &ctx->command);
+                state = ssi_error_state;
+                break;
+            }
+
+            break;
+
+        case ssi_comment_end0_state:
+            switch (ch) {
+            case '-':
+                state = ssi_comment_end1_state;
+                break;
+
+            default:
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "unexpected \"%c\" symbol in \"%V\" SSI command",
+                              ch, &ctx->command);
+                state = ssi_error_state;
+                break;
+            }
+
+            break;
+
+        case ssi_comment_end1_state:
+            switch (ch) {
+            case '>':
+                ctx->state = ssi_start_state;
+                ctx->pos = p + 1;
+                ctx->looked = looked;
+                ctx->copy_end = copy_end;
+
+                if (ctx->copy_start == NULL && copy_end) {
+                    ctx->copy_start = ctx->buf->pos;
+                }
+
+                return NGX_OK;
+
+            default:
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "unexpected \"%c\" symbol in \"%V\" SSI command",
+                              ch, &ctx->command);
+                state = ssi_error_state;
+                break;
+            }
+
+            break;
+
+        case ssi_error_state:
+            switch (ch) {
+            case '-':
+                state = ssi_error_end0_state;
+                break;
+
+            default:
+                break;
+            }
+
+            break;
+
+        case ssi_error_end0_state:
+            switch (ch) {
+            case '-':
+                state = ssi_error_end1_state;
+                break;
+
+            default:
+                state = ssi_error_state;
+                break;
+            }
+
+            break;
+
+        case ssi_error_end1_state:
+            switch (ch) {
+            case '>':
+                ctx->state = ssi_start_state;
+                ctx->pos = p + 1;
+                ctx->looked = looked;
+                ctx->copy_end = copy_end;
+
+                if (ctx->copy_start == NULL && copy_end) {
+                    ctx->copy_start = ctx->buf->pos;
+                }
+
+                return NGX_HTTP_SSI_ERROR;
+
+            default:
+                state = ssi_error_state;
+                break;
+            }
+
+            break;
+        }
+    }
+
+    ctx->state = state;
+    ctx->pos = p;
+    ctx->looked = looked;
+
+    ctx->copy_end = (state == ssi_start_state) ? p : copy_end;
+
+    if (ctx->copy_start == NULL && ctx->copy_end) {
+        ctx->copy_start = ctx->buf->pos;
+    }
+
+    return NGX_AGAIN;
+}
+
+
+static ngx_str_t *
+ngx_http_ssi_get_variable(ngx_http_request_t *r, ngx_str_t *name,
+    ngx_uint_t key)
+{
+    ngx_uint_t           i;
+    ngx_list_part_t     *part;
+    ngx_http_ssi_var_t  *var;
+    ngx_http_ssi_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r->main, ngx_http_ssi_filter_module);
+
+#if (NGX_PCRE)
+    {
+    ngx_str_t  *value;
+
+    if (key >= '0' && key <= '9') {
+        i = key - '0';
+
+        if (i < ctx->ncaptures) {
+            value = ngx_palloc(r->pool, sizeof(ngx_str_t));
+            if (value == NULL) {
+                return NULL;
+            }
+
+            i *= 2;
+
+            value->data = ctx->captures_data + ctx->captures[i];
+            value->len = ctx->captures[i + 1] - ctx->captures[i];
+
+            return value;
+        }
+    }
+    }
+#endif
+
+    if (ctx->variables == NULL) {
+        return NULL;
+    }
+
+    part = &ctx->variables->part;
+    var = part->elts;
+
+    for (i = 0; /* void */ ; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            var = part->elts;
+            i = 0;
+        }
+
+        if (name->len != var[i].name.len) {
+            continue;
+        }
+
+        if (key != var[i].key) {
+            continue;
+        }
+
+        if (ngx_strncmp(name->data, var[i].name.data, name->len) == 0) {
+            return &var[i].value;
+        }
+    }
+
+    return NULL;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_evaluate_string(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+    ngx_str_t *text, ngx_uint_t flags)
+{
+    u_char                      ch, *p, **value, *data, *part_data;
+    size_t                     *size, len, prefix, part_len;
+    ngx_str_t                   var, *val;
+    ngx_uint_t                  i, n, bracket, quoted, key;
+    ngx_array_t                 lengths, values;
+    ngx_http_variable_value_t  *vv;
+
+    n = ngx_http_script_variables_count(text);
+
+    if (n == 0) {
+
+        data = text->data;
+        p = data;
+
+        if ((flags & NGX_HTTP_SSI_ADD_PREFIX) && text->data[0] != '/') {
+
+            for (prefix = r->uri.len; prefix; prefix--) {
+                if (r->uri.data[prefix - 1] == '/') {
+                    break;
+                }
+            }
+
+            if (prefix) {
+                len = prefix + text->len;
+
+                data = ngx_pnalloc(r->pool, len);
+                if (data == NULL) {
+                    return NGX_ERROR;
+                }
+
+                p = ngx_copy(data, r->uri.data, prefix);
+            }
+        }
+
+        quoted = 0;
+
+        for (i = 0; i < text->len; i++) {
+            ch = text->data[i];
+
+            if (!quoted) {
+
+                if (ch == '\\') {
+                    quoted = 1;
+                    continue;
+                }
+
+            } else {
+                quoted = 0;
+
+                if (ch != '\\' && ch != '\'' && ch != '"' && ch != '$') {
+                    *p++ = '\\';
+                }
+            }
+
+            *p++ = ch;
+        }
+
+        text->len = p - data;
+        text->data = data;
+
+        return NGX_OK;
+    }
+
+    if (ngx_array_init(&lengths, r->pool, 8, sizeof(size_t *)) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&values, r->pool, 8, sizeof(u_char *)) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    len = 0;
+    i = 0;
+
+    while (i < text->len) {
+
+        if (text->data[i] == '$') {
+
+            var.len = 0;
+
+            if (++i == text->len) {
+                goto invalid_variable;
+            }
+
+            if (text->data[i] == '{') {
+                bracket = 1;
+
+                if (++i == text->len) {
+                    goto invalid_variable;
+                }
+
+                var.data = &text->data[i];
+
+            } else {
+                bracket = 0;
+                var.data = &text->data[i];
+            }
+
+            for ( /* void */ ; i < text->len; i++, var.len++) {
+                ch = text->data[i];
+
+                if (ch == '}' && bracket) {
+                    i++;
+                    bracket = 0;
+                    break;
+                }
+
+                if ((ch >= 'A' && ch <= 'Z')
+                    || (ch >= 'a' && ch <= 'z')
+                    || (ch >= '0' && ch <= '9')
+                    || ch == '_')
+                {
+                    continue;
+                }
+
+                break;
+            }
+
+            if (bracket) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "the closing bracket in \"%V\" "
+                              "variable is missing", &var);
+                return NGX_HTTP_SSI_ERROR;
+            }
+
+            if (var.len == 0) {
+                goto invalid_variable;
+            }
+
+            key = ngx_hash_strlow(var.data, var.data, var.len);
+
+            val = ngx_http_ssi_get_variable(r, &var, key);
+
+            if (val == NULL) {
+                vv = ngx_http_get_variable(r, &var, key);
+                if (vv == NULL) {
+                    return NGX_ERROR;
+                }
+
+                if (vv->not_found) {
+                    continue;
+                }
+
+                part_data = vv->data;
+                part_len = vv->len;
+
+            } else {
+                part_data = val->data;
+                part_len = val->len;
+            }
+
+        } else {
+            part_data = &text->data[i];
+            quoted = 0;
+
+            for (p = part_data; i < text->len; i++) {
+                ch = text->data[i];
+
+                if (!quoted) {
+
+                    if (ch == '\\') {
+                        quoted = 1;
+                        continue;
+                    }
+
+                    if (ch == '$') {
+                        break;
+                    }
+
+                } else {
+                    quoted = 0;
+
+                    if (ch != '\\' && ch != '\'' && ch != '"' && ch != '$') {
+                        *p++ = '\\';
+                    }
+                }
+
+                *p++ = ch;
+            }
+
+            part_len = p - part_data;
+        }
+
+        len += part_len;
+
+        size = ngx_array_push(&lengths);
+        if (size == NULL) {
+            return NGX_ERROR;
+        }
+
+        *size = part_len;
+
+        value = ngx_array_push(&values);
+        if (value == NULL) {
+            return NGX_ERROR;
+        }
+
+        *value = part_data;
+    }
+
+    prefix = 0;
+
+    size = lengths.elts;
+    value = values.elts;
+
+    if (flags & NGX_HTTP_SSI_ADD_PREFIX) {
+        for (i = 0; i < values.nelts; i++) {
+            if (size[i] != 0) {
+                if (*value[i] != '/') {
+                    for (prefix = r->uri.len; prefix; prefix--) {
+                        if (r->uri.data[prefix - 1] == '/') {
+                            len += prefix;
+                            break;
+                        }
+                    }
+                }
+
+                break;
+            }
+        }
+    }
+
+    p = ngx_pnalloc(r->pool, len + ((flags & NGX_HTTP_SSI_ADD_ZERO) ? 1 : 0));
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    text->len = len;
+    text->data = p;
+
+    p = ngx_copy(p, r->uri.data, prefix);
+
+    for (i = 0; i < values.nelts; i++) {
+        p = ngx_copy(p, value[i], size[i]);
+    }
+
+    return NGX_OK;
+
+invalid_variable:
+
+    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                  "invalid variable name in \"%V\"", text);
+
+    return NGX_HTTP_SSI_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_regex_match(ngx_http_request_t *r, ngx_str_t *pattern,
+    ngx_str_t *str)
+{
+#if (NGX_PCRE)
+    int                   rc, *captures;
+    u_char               *p, errstr[NGX_MAX_CONF_ERRSTR];
+    size_t                size;
+    ngx_str_t            *vv, name, value;
+    ngx_uint_t            i, n, key;
+    ngx_http_ssi_ctx_t   *ctx;
+    ngx_http_ssi_var_t   *var;
+    ngx_regex_compile_t   rgc;
+
+    ngx_memzero(&rgc, sizeof(ngx_regex_compile_t));
+
+    rgc.pattern = *pattern;
+    rgc.pool = r->pool;
+    rgc.err.len = NGX_MAX_CONF_ERRSTR;
+    rgc.err.data = errstr;
+
+    if (ngx_regex_compile(&rgc) != NGX_OK) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "%V", &rgc.err);
+        return NGX_HTTP_SSI_ERROR;
+    }
+
+    n = (rgc.captures + 1) * 3;
+
+    captures = ngx_palloc(r->pool, n * sizeof(int));
+    if (captures == NULL) {
+        return NGX_ERROR;
+    }
+
+    rc = ngx_regex_exec(rgc.regex, str, captures, n);
+
+    if (rc < NGX_REGEX_NO_MATCHED) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                      ngx_regex_exec_n " failed: %d on \"%V\" using \"%V\"",
+                      rc, str, pattern);
+        return NGX_HTTP_SSI_ERROR;
+    }
+
+    if (rc == NGX_REGEX_NO_MATCHED) {
+        return NGX_DECLINED;
+    }
+
+    ctx = ngx_http_get_module_ctx(r->main, ngx_http_ssi_filter_module);
+
+    ctx->ncaptures = rc;
+    ctx->captures = captures;
+    ctx->captures_data = str->data;
+
+    if (rgc.named_captures > 0) {
+
+        if (ctx->variables == NULL) {
+            ctx->variables = ngx_list_create(r->pool, 4,
+                                             sizeof(ngx_http_ssi_var_t));
+            if (ctx->variables == NULL) {
+                return NGX_ERROR;
+            }
+        }
+
+        size = rgc.name_size;
+        p = rgc.names;
+
+        for (i = 0; i < (ngx_uint_t) rgc.named_captures; i++, p += size) {
+
+            name.data = &p[2];
+            name.len = ngx_strlen(name.data);
+
+            n = 2 * ((p[0] << 8) + p[1]);
+
+            value.data = &str->data[captures[n]];
+            value.len = captures[n + 1] - captures[n];
+
+            key = ngx_hash_strlow(name.data, name.data, name.len);
+
+            vv = ngx_http_ssi_get_variable(r, &name, key);
+
+            if (vv) {
+                *vv = value;
+                continue;
+            }
+
+            var = ngx_list_push(ctx->variables);
+            if (var == NULL) {
+                return NGX_ERROR;
+            }
+
+            var->name = name;
+            var->key = key;
+            var->value = value;
+        }
+    }
+
+    return NGX_OK;
+
+#else
+
+    ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                  "the using of the regex \"%V\" in SSI requires PCRE library",
+                  pattern);
+    return NGX_HTTP_SSI_ERROR;
+
+#endif
+}
+
+
+static ngx_int_t
+ngx_http_ssi_include(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+    ngx_str_t **params)
+{
+    ngx_int_t                    rc;
+    ngx_str_t                   *uri, *file, *wait, *set, *stub, args;
+    ngx_buf_t                   *b;
+    ngx_uint_t                   flags, i, key;
+    ngx_chain_t                 *cl, *tl, **ll, *out;
+    ngx_http_request_t          *sr;
+    ngx_http_ssi_var_t          *var;
+    ngx_http_ssi_ctx_t          *mctx;
+    ngx_http_ssi_block_t        *bl;
+    ngx_http_post_subrequest_t  *psr;
+
+    uri = params[NGX_HTTP_SSI_INCLUDE_VIRTUAL];
+    file = params[NGX_HTTP_SSI_INCLUDE_FILE];
+    wait = params[NGX_HTTP_SSI_INCLUDE_WAIT];
+    set = params[NGX_HTTP_SSI_INCLUDE_SET];
+    stub = params[NGX_HTTP_SSI_INCLUDE_STUB];
+
+    if (uri && file) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "inclusion may be either virtual=\"%V\" or file=\"%V\"",
+                      uri, file);
+        return NGX_HTTP_SSI_ERROR;
+    }
+
+    if (uri == NULL && file == NULL) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "no parameter in \"include\" SSI command");
+        return NGX_HTTP_SSI_ERROR;
+    }
+
+    if (set && stub) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "\"set\" and \"stub\" cannot be used together "
+                      "in \"include\" SSI command");
+        return NGX_HTTP_SSI_ERROR;
+    }
+
+    if (wait) {
+        if (uri == NULL) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "\"wait\" cannot be used with file=\"%V\"", file);
+            return NGX_HTTP_SSI_ERROR;
+        }
+
+        if (wait->len == 2
+            && ngx_strncasecmp(wait->data, (u_char *) "no", 2) == 0)
+        {
+            wait = NULL;
+
+        } else if (wait->len != 3
+                   || ngx_strncasecmp(wait->data, (u_char *) "yes", 3) != 0)
+        {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "invalid value \"%V\" in the \"wait\" parameter",
+                          wait);
+            return NGX_HTTP_SSI_ERROR;
+        }
+    }
+
+    if (uri == NULL) {
+        uri = file;
+        wait = (ngx_str_t *) -1;
+    }
+
+    rc = ngx_http_ssi_evaluate_string(r, ctx, uri, NGX_HTTP_SSI_ADD_PREFIX);
+
+    if (rc != NGX_OK) {
+        return rc;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "ssi include: \"%V\"", uri);
+
+    ngx_str_null(&args);
+    flags = NGX_HTTP_LOG_UNSAFE;
+
+    if (ngx_http_parse_unsafe_uri(r, uri, &args, &flags) != NGX_OK) {
+        return NGX_HTTP_SSI_ERROR;
+    }
+
+    psr = NULL;
+
+    mctx = ngx_http_get_module_ctx(r->main, ngx_http_ssi_filter_module);
+
+    if (stub) {
+        if (mctx->blocks) {
+            bl = mctx->blocks->elts;
+            for (i = 0; i < mctx->blocks->nelts; i++) {
+                if (stub->len == bl[i].name.len
+                    && ngx_strncmp(stub->data, bl[i].name.data, stub->len) == 0)
+                {
+                    goto found;
+                }
+            }
+        }
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "\"stub\"=\"%V\" for \"include\" not found", stub);
+        return NGX_HTTP_SSI_ERROR;
+
+    found:
+
+        psr = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
+        if (psr == NULL) {
+            return NGX_ERROR;
+        }
+
+        psr->handler = ngx_http_ssi_stub_output;
+
+        if (bl[i].count++) {
+
+            out = NULL;
+            ll = &out;
+
+            for (tl = bl[i].bufs; tl; tl = tl->next) {
+
+                if (ctx->free) {
+                    cl = ctx->free;
+                    ctx->free = ctx->free->next;
+                    b = cl->buf;
+
+                } else {
+                    b = ngx_alloc_buf(r->pool);
+                    if (b == NULL) {
+                        return NGX_ERROR;
+                    }
+
+                    cl = ngx_alloc_chain_link(r->pool);
+                    if (cl == NULL) {
+                        return NGX_ERROR;
+                    }
+
+                    cl->buf = b;
+                }
+
+                ngx_memcpy(b, tl->buf, sizeof(ngx_buf_t));
+
+                b->pos = b->start;
+
+                *ll = cl;
+                cl->next = NULL;
+                ll = &cl->next;
+            }
+
+            psr->data = out;
+
+        } else {
+            psr->data = bl[i].bufs;
+        }
+    }
+
+    if (wait) {
+        flags |= NGX_HTTP_SUBREQUEST_WAITED;
+    }
+
+    if (set) {
+        key = ngx_hash_strlow(set->data, set->data, set->len);
+
+        psr = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
+        if (psr == NULL) {
+            return NGX_ERROR;
+        }
+
+        psr->handler = ngx_http_ssi_set_variable;
+        psr->data = ngx_http_ssi_get_variable(r, set, key);
+
+        if (psr->data == NULL) {
+
+            if (mctx->variables == NULL) {
+                mctx->variables = ngx_list_create(r->pool, 4,
+                                                  sizeof(ngx_http_ssi_var_t));
+                if (mctx->variables == NULL) {
+                    return NGX_ERROR;
+                }
+            }
+
+            var = ngx_list_push(mctx->variables);
+            if (var == NULL) {
+                return NGX_ERROR;
+            }
+
+            var->name = *set;
+            var->key = key;
+            var->value = ngx_http_ssi_null_string;
+            psr->data = &var->value;
+        }
+
+        flags |= NGX_HTTP_SUBREQUEST_IN_MEMORY|NGX_HTTP_SUBREQUEST_WAITED;
+    }
+
+    if (ngx_http_subrequest(r, uri, &args, &sr, psr, flags) != NGX_OK) {
+        return NGX_HTTP_SSI_ERROR;
+    }
+
+    if (wait == NULL && set == NULL) {
+        return NGX_OK;
+    }
+
+    if (ctx->wait == NULL) {
+        ctx->wait = sr;
+
+        return NGX_AGAIN;
+
+    } else {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "can only wait for one subrequest at a time");
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_stub_output(ngx_http_request_t *r, void *data, ngx_int_t rc)
+{
+    ngx_chain_t  *out;
+
+    if (rc == NGX_ERROR || r->connection->error || r->request_output) {
+        return rc;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "ssi stub output: \"%V?%V\"", &r->uri, &r->args);
+
+    out = data;
+
+    if (!r->header_sent) {
+        r->headers_out.content_type_len =
+                                      r->parent->headers_out.content_type_len;
+        r->headers_out.content_type = r->parent->headers_out.content_type;
+
+        if (ngx_http_send_header(r) == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+    }
+
+    return ngx_http_output_filter(r, out);
+}
+
+
+static ngx_int_t
+ngx_http_ssi_set_variable(ngx_http_request_t *r, void *data, ngx_int_t rc)
+{
+    ngx_str_t  *value = data;
+
+    if (r->headers_out.status < NGX_HTTP_SPECIAL_RESPONSE
+        && r->out && r->out->buf)
+    {
+        value->len = r->out->buf->last - r->out->buf->pos;
+        value->data = r->out->buf->pos;
+    }
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_echo(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+    ngx_str_t **params)
+{
+    u_char                     *p;
+    uintptr_t                   len;
+    ngx_buf_t                  *b;
+    ngx_str_t                  *var, *value, *enc, text;
+    ngx_uint_t                  key;
+    ngx_chain_t                *cl;
+    ngx_http_variable_value_t  *vv;
+
+    var = params[NGX_HTTP_SSI_ECHO_VAR];
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "ssi echo \"%V\"", var);
+
+    key = ngx_hash_strlow(var->data, var->data, var->len);
+
+    value = ngx_http_ssi_get_variable(r, var, key);
+
+    if (value == NULL) {
+        vv = ngx_http_get_variable(r, var, key);
+
+        if (vv == NULL) {
+            return NGX_HTTP_SSI_ERROR;
+        }
+
+        if (!vv->not_found) {
+            text.data = vv->data;
+            text.len = vv->len;
+            value = &text;
+        }
+    }
+
+    if (value == NULL) {
+        value = params[NGX_HTTP_SSI_ECHO_DEFAULT];
+
+        if (value == NULL) {
+            value = &ngx_http_ssi_none;
+
+        } else if (value->len == 0) {
+            return NGX_OK;
+        }
+
+    } else {
+        if (value->len == 0) {
+            return NGX_OK;
+        }
+    }
+
+    enc = params[NGX_HTTP_SSI_ECHO_ENCODING];
+
+    if (enc) {
+        if (enc->len == 4 && ngx_strncmp(enc->data, "none", 4) == 0) {
+
+            ctx->encoding = NGX_HTTP_SSI_NO_ENCODING;
+
+        } else if (enc->len == 3 && ngx_strncmp(enc->data, "url", 3) == 0) {
+
+            ctx->encoding = NGX_HTTP_SSI_URL_ENCODING;
+
+        } else if (enc->len == 6 && ngx_strncmp(enc->data, "entity", 6) == 0) {
+
+            ctx->encoding = NGX_HTTP_SSI_ENTITY_ENCODING;
+
+        } else {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "unknown encoding \"%V\" in the \"echo\" command",
+                          enc);
+        }
+    }
+
+    p = value->data;
+
+    switch (ctx->encoding) {
+
+    case NGX_HTTP_SSI_URL_ENCODING:
+        len = 2 * ngx_escape_uri(NULL, value->data, value->len,
+                                 NGX_ESCAPE_HTML);
+
+        if (len) {
+            p = ngx_pnalloc(r->pool, value->len + len);
+            if (p == NULL) {
+                return NGX_HTTP_SSI_ERROR;
+            }
+
+            (void) ngx_escape_uri(p, value->data, value->len, NGX_ESCAPE_HTML);
+        }
+
+        len += value->len;
+        break;
+
+    case NGX_HTTP_SSI_ENTITY_ENCODING:
+        len = ngx_escape_html(NULL, value->data, value->len);
+
+        if (len) {
+            p = ngx_pnalloc(r->pool, value->len + len);
+            if (p == NULL) {
+                return NGX_HTTP_SSI_ERROR;
+            }
+
+            (void) ngx_escape_html(p, value->data, value->len);
+        }
+
+        len += value->len;
+        break;
+
+    default: /* NGX_HTTP_SSI_NO_ENCODING */
+        len = value->len;
+        break;
+    }
+
+    b = ngx_calloc_buf(r->pool);
+    if (b == NULL) {
+        return NGX_HTTP_SSI_ERROR;
+    }
+
+    cl = ngx_alloc_chain_link(r->pool);
+    if (cl == NULL) {
+        return NGX_HTTP_SSI_ERROR;
+    }
+
+    b->memory = 1;
+    b->pos = p;
+    b->last = p + len;
+
+    cl->buf = b;
+    cl->next = NULL;
+    *ctx->last_out = cl;
+    ctx->last_out = &cl->next;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_config(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+    ngx_str_t **params)
+{
+    ngx_str_t  *value;
+
+    value = params[NGX_HTTP_SSI_CONFIG_TIMEFMT];
+
+    if (value) {
+        ctx->timefmt.len = value->len;
+        ctx->timefmt.data = ngx_pnalloc(r->pool, value->len + 1);
+        if (ctx->timefmt.data == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_cpystrn(ctx->timefmt.data, value->data, value->len + 1);
+    }
+
+    value = params[NGX_HTTP_SSI_CONFIG_ERRMSG];
+
+    if (value) {
+        ctx->errmsg = *value;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_set(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+    ngx_str_t **params)
+{
+    ngx_int_t            rc;
+    ngx_str_t           *name, *value, *vv;
+    ngx_uint_t           key;
+    ngx_http_ssi_var_t  *var;
+    ngx_http_ssi_ctx_t  *mctx;
+
+    mctx = ngx_http_get_module_ctx(r->main, ngx_http_ssi_filter_module);
+
+    if (mctx->variables == NULL) {
+        mctx->variables = ngx_list_create(r->pool, 4,
+                                          sizeof(ngx_http_ssi_var_t));
+        if (mctx->variables == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    name = params[NGX_HTTP_SSI_SET_VAR];
+    value = params[NGX_HTTP_SSI_SET_VALUE];
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "ssi set \"%V\" \"%V\"", name, value);
+
+    rc = ngx_http_ssi_evaluate_string(r, ctx, value, 0);
+
+    if (rc != NGX_OK) {
+        return rc;
+    }
+
+    key = ngx_hash_strlow(name->data, name->data, name->len);
+
+    vv = ngx_http_ssi_get_variable(r, name, key);
+
+    if (vv) {
+        *vv = *value;
+        return NGX_OK;
+    }
+
+    var = ngx_list_push(mctx->variables);
+    if (var == NULL) {
+        return NGX_ERROR;
+    }
+
+    var->name = *name;
+    var->key = key;
+    var->value = *value;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "set: \"%V\"=\"%V\"", name, value);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_if(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+    ngx_str_t **params)
+{
+    u_char       *p, *last;
+    ngx_str_t    *expr, left, right;
+    ngx_int_t     rc;
+    ngx_uint_t    negative, noregex, flags;
+
+    if (ctx->command.len == 2) {
+        if (ctx->conditional) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "the \"if\" command inside the \"if\" command");
+            return NGX_HTTP_SSI_ERROR;
+        }
+    }
+
+    if (ctx->output_chosen) {
+        ctx->output = 0;
+        return NGX_OK;
+    }
+
+    expr = params[NGX_HTTP_SSI_IF_EXPR];
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "ssi if expr=\"%V\"", expr);
+
+    left.data = expr->data;
+    last = expr->data + expr->len;
+
+    for (p = left.data; p < last; p++) {
+        if (*p >= 'A' && *p <= 'Z') {
+            *p |= 0x20;
+            continue;
+        }
+
+        if ((*p >= 'a' && *p <= 'z')
+             || (*p >= '0' && *p <= '9')
+             || *p == '$' || *p == '{' || *p == '}' || *p == '_'
+             || *p == '"' || *p == '\'')
+        {
+            continue;
+        }
+
+        break;
+    }
+
+    left.len = p - left.data;
+
+    while (p < last && *p == ' ') {
+        p++;
+    }
+
+    flags = 0;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "left: \"%V\"", &left);
+
+    rc = ngx_http_ssi_evaluate_string(r, ctx, &left, flags);
+
+    if (rc != NGX_OK) {
+        return rc;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "evaluated left: \"%V\"", &left);
+
+    if (p == last) {
+        if (left.len) {
+            ctx->output = 1;
+            ctx->output_chosen = 1;
+
+        } else {
+            ctx->output = 0;
+        }
+
+        ctx->conditional = NGX_HTTP_SSI_COND_IF;
+
+        return NGX_OK;
+    }
+
+    if (p < last && *p == '=') {
+        negative = 0;
+        p++;
+
+    } else if (p + 1 < last && *p == '!' && *(p + 1) == '=') {
+        negative = 1;
+        p += 2;
+
+    } else {
+        goto invalid_expression;
+    }
+
+    while (p < last && *p == ' ') {
+        p++;
+    }
+
+    if (p < last - 1 && *p == '/') {
+        if (*(last - 1) != '/') {
+            goto invalid_expression;
+        }
+
+        noregex = 0;
+        flags = NGX_HTTP_SSI_ADD_ZERO;
+        last--;
+        p++;
+
+    } else {
+        noregex = 1;
+        flags = 0;
+
+        if (p < last - 1 && p[0] == '\\' && p[1] == '/') {
+            p++;
+        }
+    }
+
+    right.len = last - p;
+    right.data = p;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "right: \"%V\"", &right);
+
+    rc = ngx_http_ssi_evaluate_string(r, ctx, &right, flags);
+
+    if (rc != NGX_OK) {
+        return rc;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "evaluated right: \"%V\"", &right);
+
+    if (noregex) {
+        if (left.len != right.len) {
+            rc = -1;
+
+        } else {
+            rc = ngx_strncmp(left.data, right.data, right.len);
+        }
+
+    } else {
+        right.data[right.len] = '\0';
+
+        rc = ngx_http_ssi_regex_match(r, &right, &left);
+
+        if (rc == NGX_OK) {
+            rc = 0;
+        } else if (rc == NGX_DECLINED) {
+            rc = -1;
+        } else {
+            return rc;
+        }
+    }
+
+    if ((rc == 0 && !negative) || (rc != 0 && negative)) {
+        ctx->output = 1;
+        ctx->output_chosen = 1;
+
+    } else {
+        ctx->output = 0;
+    }
+
+    ctx->conditional = NGX_HTTP_SSI_COND_IF;
+
+    return NGX_OK;
+
+invalid_expression:
+
+    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                  "invalid expression in \"%V\"", expr);
+
+    return NGX_HTTP_SSI_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_else(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+    ngx_str_t **params)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "ssi else");
+
+    if (ctx->output_chosen) {
+        ctx->output = 0;
+    } else {
+        ctx->output = 1;
+    }
+
+    ctx->conditional = NGX_HTTP_SSI_COND_ELSE;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_endif(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+    ngx_str_t **params)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "ssi endif");
+
+    ctx->output = 1;
+    ctx->output_chosen = 0;
+    ctx->conditional = 0;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_block(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+    ngx_str_t **params)
+{
+    ngx_http_ssi_ctx_t    *mctx;
+    ngx_http_ssi_block_t  *bl;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "ssi block");
+
+    mctx = ngx_http_get_module_ctx(r->main, ngx_http_ssi_filter_module);
+
+    if (mctx->blocks == NULL) {
+        mctx->blocks = ngx_array_create(r->pool, 4,
+                                        sizeof(ngx_http_ssi_block_t));
+        if (mctx->blocks == NULL) {
+            return NGX_HTTP_SSI_ERROR;
+        }
+    }
+
+    bl = ngx_array_push(mctx->blocks);
+    if (bl == NULL) {
+        return NGX_HTTP_SSI_ERROR;
+    }
+
+    bl->name = *params[NGX_HTTP_SSI_BLOCK_NAME];
+    bl->bufs = NULL;
+    bl->count = 0;
+
+    ctx->output = 0;
+    ctx->block = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_endblock(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+    ngx_str_t **params)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "ssi endblock");
+
+    ctx->output = 1;
+    ctx->block = 0;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_date_gmt_local_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t gmt)
+{
+    time_t               now;
+    ngx_http_ssi_ctx_t  *ctx;
+    ngx_str_t           *timefmt;
+    struct tm            tm;
+    char                 buf[NGX_HTTP_SSI_DATE_LEN];
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    now = ngx_time();
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_ssi_filter_module);
+
+    timefmt = ctx ? &ctx->timefmt : &ngx_http_ssi_timefmt;
+
+    if (timefmt->len == sizeof("%s") - 1
+        && timefmt->data[0] == '%' && timefmt->data[1] == 's')
+    {
+        v->data = ngx_pnalloc(r->pool, NGX_TIME_T_LEN);
+        if (v->data == NULL) {
+            return NGX_ERROR;
+        }
+
+        v->len = ngx_sprintf(v->data, "%T", now) - v->data;
+
+        return NGX_OK;
+    }
+
+    if (gmt) {
+        ngx_libc_gmtime(now, &tm);
+    } else {
+        ngx_libc_localtime(now, &tm);
+    }
+
+    v->len = strftime(buf, NGX_HTTP_SSI_DATE_LEN,
+                      (char *) timefmt->data, &tm);
+    if (v->len == 0) {
+        return NGX_ERROR;
+    }
+
+    v->data = ngx_pnalloc(r->pool, v->len);
+    if (v->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(v->data, buf, v->len);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_preconfiguration(ngx_conf_t *cf)
+{
+    ngx_int_t                  rc;
+    ngx_http_variable_t       *var, *v;
+    ngx_http_ssi_command_t    *cmd;
+    ngx_http_ssi_main_conf_t  *smcf;
+
+    for (v = ngx_http_ssi_vars; v->name.len; v++) {
+        var = ngx_http_add_variable(cf, &v->name, v->flags);
+        if (var == NULL) {
+            return NGX_ERROR;
+        }
+
+        var->get_handler = v->get_handler;
+        var->data = v->data;
+    }
+
+    smcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_ssi_filter_module);
+
+    for (cmd = ngx_http_ssi_commands; cmd->name.len; cmd++) {
+        rc = ngx_hash_add_key(&smcf->commands, &cmd->name, cmd,
+                              NGX_HASH_READONLY_KEY);
+
+        if (rc == NGX_OK) {
+            continue;
+        }
+
+        if (rc == NGX_BUSY) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "conflicting SSI command \"%V\"", &cmd->name);
+        }
+
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_ssi_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_http_ssi_main_conf_t  *smcf;
+
+    smcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_ssi_main_conf_t));
+    if (smcf == NULL) {
+        return NULL;
+    }
+
+    smcf->commands.pool = cf->pool;
+    smcf->commands.temp_pool = cf->temp_pool;
+
+    if (ngx_hash_keys_array_init(&smcf->commands, NGX_HASH_SMALL) != NGX_OK) {
+        return NULL;
+    }
+
+    return smcf;
+}
+
+
+static char *
+ngx_http_ssi_init_main_conf(ngx_conf_t *cf, void *conf)
+{
+    ngx_http_ssi_main_conf_t *smcf = conf;
+
+    ngx_hash_init_t  hash;
+
+    hash.hash = &smcf->hash;
+    hash.key = ngx_hash_key;
+    hash.max_size = 1024;
+    hash.bucket_size = ngx_cacheline_size;
+    hash.name = "ssi_command_hash";
+    hash.pool = cf->pool;
+    hash.temp_pool = NULL;
+
+    if (ngx_hash_init(&hash, smcf->commands.keys.elts,
+                      smcf->commands.keys.nelts)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_http_ssi_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_ssi_loc_conf_t  *slcf;
+
+    slcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_ssi_loc_conf_t));
+    if (slcf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->types = { NULL };
+     *     conf->types_keys = NULL;
+     */
+
+    slcf->enable = NGX_CONF_UNSET;
+    slcf->silent_errors = NGX_CONF_UNSET;
+    slcf->ignore_recycled_buffers = NGX_CONF_UNSET;
+    slcf->last_modified = NGX_CONF_UNSET;
+
+    slcf->min_file_chunk = NGX_CONF_UNSET_SIZE;
+    slcf->value_len = NGX_CONF_UNSET_SIZE;
+
+    return slcf;
+}
+
+
+static char *
+ngx_http_ssi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_ssi_loc_conf_t *prev = parent;
+    ngx_http_ssi_loc_conf_t *conf = child;
+
+    ngx_conf_merge_value(conf->enable, prev->enable, 0);
+    ngx_conf_merge_value(conf->silent_errors, prev->silent_errors, 0);
+    ngx_conf_merge_value(conf->ignore_recycled_buffers,
+                         prev->ignore_recycled_buffers, 0);
+    ngx_conf_merge_value(conf->last_modified, prev->last_modified, 0);
+
+    ngx_conf_merge_size_value(conf->min_file_chunk, prev->min_file_chunk, 1024);
+    ngx_conf_merge_size_value(conf->value_len, prev->value_len, 255);
+
+    if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types,
+                             &prev->types_keys, &prev->types,
+                             ngx_http_html_default_types)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_filter_init(ngx_conf_t *cf)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_ssi_header_filter;
+
+    ngx_http_next_body_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_ssi_body_filter;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_ssi_filter_module.h b/nginx/src/http/modules/ngx_http_ssi_filter_module.h
new file mode 100644 (file)
index 0000000..0bd01a0
--- /dev/null
@@ -0,0 +1,114 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_SSI_FILTER_H_INCLUDED_
+#define _NGX_HTTP_SSI_FILTER_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_SSI_MAX_PARAMS       16
+
+#define NGX_HTTP_SSI_COMMAND_LEN      32
+#define NGX_HTTP_SSI_PARAM_LEN        32
+#define NGX_HTTP_SSI_PARAMS_N         4
+
+
+#define NGX_HTTP_SSI_COND_IF          1
+#define NGX_HTTP_SSI_COND_ELSE        2
+
+
+#define NGX_HTTP_SSI_NO_ENCODING      0
+#define NGX_HTTP_SSI_URL_ENCODING     1
+#define NGX_HTTP_SSI_ENTITY_ENCODING  2
+
+
+typedef struct {
+    ngx_hash_t                hash;
+    ngx_hash_keys_arrays_t    commands;
+} ngx_http_ssi_main_conf_t;
+
+
+typedef struct {
+    ngx_buf_t                *buf;
+
+    u_char                   *pos;
+    u_char                   *copy_start;
+    u_char                   *copy_end;
+
+    ngx_uint_t                key;
+    ngx_str_t                 command;
+    ngx_array_t               params;
+    ngx_table_elt_t          *param;
+    ngx_table_elt_t           params_array[NGX_HTTP_SSI_PARAMS_N];
+
+    ngx_chain_t              *in;
+    ngx_chain_t              *out;
+    ngx_chain_t             **last_out;
+    ngx_chain_t              *busy;
+    ngx_chain_t              *free;
+
+    ngx_uint_t                state;
+    ngx_uint_t                saved_state;
+    size_t                    saved;
+    size_t                    looked;
+
+    size_t                    value_len;
+
+    ngx_list_t               *variables;
+    ngx_array_t              *blocks;
+
+#if (NGX_PCRE)
+    ngx_uint_t                ncaptures;
+    int                      *captures;
+    u_char                   *captures_data;
+#endif
+
+    unsigned                  conditional:2;
+    unsigned                  encoding:2;
+    unsigned                  block:1;
+    unsigned                  output:1;
+    unsigned                  output_chosen:1;
+
+    ngx_http_request_t       *wait;
+    void                     *value_buf;
+    ngx_str_t                 timefmt;
+    ngx_str_t                 errmsg;
+} ngx_http_ssi_ctx_t;
+
+
+typedef ngx_int_t (*ngx_http_ssi_command_pt) (ngx_http_request_t *r,
+    ngx_http_ssi_ctx_t *ctx, ngx_str_t **);
+
+
+typedef struct {
+    ngx_str_t                 name;
+    ngx_uint_t                index;
+
+    unsigned                  mandatory:1;
+    unsigned                  multiple:1;
+} ngx_http_ssi_param_t;
+
+
+typedef struct {
+    ngx_str_t                 name;
+    ngx_http_ssi_command_pt   handler;
+    ngx_http_ssi_param_t     *params;
+
+    unsigned                  conditional:2;
+    unsigned                  block:1;
+    unsigned                  flush:1;
+} ngx_http_ssi_command_t;
+
+
+extern ngx_module_t  ngx_http_ssi_filter_module;
+
+
+#endif /* _NGX_HTTP_SSI_FILTER_H_INCLUDED_ */
diff --git a/nginx/src/http/modules/ngx_http_ssl_module.c b/nginx/src/http/modules/ngx_http_ssl_module.c
new file mode 100644 (file)
index 0000000..7d62176
--- /dev/null
@@ -0,0 +1,997 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c,
+    ngx_pool_t *pool, ngx_str_t *s);
+
+
+#define NGX_DEFAULT_CIPHERS     "HIGH:!aNULL:!MD5"
+#define NGX_DEFAULT_ECDH_CURVE  "auto"
+
+#define NGX_HTTP_NPN_ADVERTISE  "\x08http/1.1"
+
+
+#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
+static int ngx_http_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 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,
+    ngx_http_variable_value_t *v, uintptr_t data);
+
+static ngx_int_t ngx_http_ssl_add_variables(ngx_conf_t *cf);
+static void *ngx_http_ssl_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+
+static char *ngx_http_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+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 ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf);
+
+
+static ngx_conf_bitmask_t  ngx_http_ssl_protocols[] = {
+    { ngx_string("SSLv2"), NGX_SSL_SSLv2 },
+    { ngx_string("SSLv3"), NGX_SSL_SSLv3 },
+    { ngx_string("TLSv1"), NGX_SSL_TLSv1 },
+    { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 },
+    { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 },
+    { ngx_string("TLSv1.3"), NGX_SSL_TLSv1_3 },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_enum_t  ngx_http_ssl_verify[] = {
+    { ngx_string("off"), 0 },
+    { ngx_string("on"), 1 },
+    { ngx_string("optional"), 2 },
+    { ngx_string("optional_no_ca"), 3 },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t  ngx_http_ssl_commands[] = {
+
+    { ngx_string("ssl"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+      ngx_http_ssl_enable,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, enable),
+      NULL },
+
+    { ngx_string("ssl_certificate"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, certificates),
+      NULL },
+
+    { ngx_string("ssl_certificate_key"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, certificate_keys),
+      NULL },
+
+    { ngx_string("ssl_password_file"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_http_ssl_password_file,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("ssl_dhparam"),
+      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, dhparam),
+      NULL },
+
+    { ngx_string("ssl_ecdh_curve"),
+      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, ecdh_curve),
+      NULL },
+
+    { ngx_string("ssl_protocols"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, protocols),
+      &ngx_http_ssl_protocols },
+
+    { ngx_string("ssl_ciphers"),
+      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, ciphers),
+      NULL },
+
+    { ngx_string("ssl_buffer_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_ssl_srv_conf_t, buffer_size),
+      NULL },
+
+    { ngx_string("ssl_verify_client"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, verify),
+      &ngx_http_ssl_verify },
+
+    { ngx_string("ssl_verify_depth"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, verify_depth),
+      NULL },
+
+    { ngx_string("ssl_client_certificate"),
+      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, client_certificate),
+      NULL },
+
+    { ngx_string("ssl_trusted_certificate"),
+      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, trusted_certificate),
+      NULL },
+
+    { ngx_string("ssl_prefer_server_ciphers"),
+      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, prefer_server_ciphers),
+      NULL },
+
+    { ngx_string("ssl_session_cache"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE12,
+      ngx_http_ssl_session_cache,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("ssl_session_tickets"),
+      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, session_tickets),
+      NULL },
+
+    { ngx_string("ssl_session_ticket_key"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, session_ticket_keys),
+      NULL },
+
+    { ngx_string("ssl_session_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_sec_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, session_timeout),
+      NULL },
+
+    { ngx_string("ssl_crl"),
+      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, crl),
+      NULL },
+
+    { ngx_string("ssl_stapling"),
+      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, stapling),
+      NULL },
+
+    { ngx_string("ssl_stapling_file"),
+      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, stapling_file),
+      NULL },
+
+    { ngx_string("ssl_stapling_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, stapling_responder),
+      NULL },
+
+    { ngx_string("ssl_stapling_verify"),
+      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, stapling_verify),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_ssl_module_ctx = {
+    ngx_http_ssl_add_variables,            /* preconfiguration */
+    ngx_http_ssl_init,                     /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_http_ssl_create_srv_conf,          /* create server configuration */
+    ngx_http_ssl_merge_srv_conf,           /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_ssl_module = {
+    NGX_MODULE_V1,
+    &ngx_http_ssl_module_ctx,              /* module context */
+    ngx_http_ssl_commands,                 /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_variable_t  ngx_http_ssl_vars[] = {
+
+    { ngx_string("ssl_protocol"), NULL, ngx_http_ssl_static_variable,
+      (uintptr_t) ngx_ssl_get_protocol, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_cipher"), NULL, ngx_http_ssl_static_variable,
+      (uintptr_t) ngx_ssl_get_cipher_name, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_ciphers"), NULL, ngx_http_ssl_variable,
+      (uintptr_t) ngx_ssl_get_ciphers, 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 },
+
+    { ngx_string("ssl_session_id"), NULL, ngx_http_ssl_variable,
+      (uintptr_t) ngx_ssl_get_session_id, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_session_reused"), NULL, ngx_http_ssl_variable,
+      (uintptr_t) ngx_ssl_get_session_reused, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+    { 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_client_cert"), NULL, ngx_http_ssl_variable,
+      (uintptr_t) ngx_ssl_get_certificate, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_raw_cert"), NULL, ngx_http_ssl_variable,
+      (uintptr_t) ngx_ssl_get_raw_certificate,
+      NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_escaped_cert"), NULL, ngx_http_ssl_variable,
+      (uintptr_t) ngx_ssl_get_escaped_certificate,
+      NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_s_dn"), NULL, ngx_http_ssl_variable,
+      (uintptr_t) ngx_ssl_get_subject_dn, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_i_dn"), NULL, ngx_http_ssl_variable,
+      (uintptr_t) ngx_ssl_get_issuer_dn, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_s_dn_legacy"), NULL, ngx_http_ssl_variable,
+      (uintptr_t) ngx_ssl_get_subject_dn_legacy, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_i_dn_legacy"), NULL, ngx_http_ssl_variable,
+      (uintptr_t) ngx_ssl_get_issuer_dn_legacy, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_serial"), NULL, ngx_http_ssl_variable,
+      (uintptr_t) ngx_ssl_get_serial_number, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_fingerprint"), NULL, ngx_http_ssl_variable,
+      (uintptr_t) ngx_ssl_get_fingerprint, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_verify"), NULL, ngx_http_ssl_variable,
+      (uintptr_t) ngx_ssl_get_client_verify, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_v_start"), NULL, ngx_http_ssl_variable,
+      (uintptr_t) ngx_ssl_get_client_v_start, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_v_end"), NULL, ngx_http_ssl_variable,
+      (uintptr_t) ngx_ssl_get_client_v_end, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_v_remain"), NULL, ngx_http_ssl_variable,
+      (uintptr_t) ngx_ssl_get_client_v_remain, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+      ngx_http_null_variable
+};
+
+
+static ngx_str_t ngx_http_ssl_sess_id_ctx = ngx_string("HTTP");
+
+
+#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
+
+static int
+ngx_http_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;
+#if (NGX_DEBUG)
+    unsigned int            i;
+#endif
+#if (NGX_HTTP_V2)
+    ngx_http_connection_t  *hc;
+#endif
+#if (NGX_HTTP_V2 || NGX_DEBUG)
+    ngx_connection_t       *c;
+
+    c = ngx_ssl_get_connection(ssl_conn);
+#endif
+
+#if (NGX_DEBUG)
+    for (i = 0; i < inlen; i += in[i] + 1) {
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "SSL ALPN supported by client: %*s",
+                       (size_t) in[i], &in[i + 1]);
+    }
+#endif
+
+#if (NGX_HTTP_V2)
+    hc = c->data;
+
+    if (hc->addr_conf->http2) {
+        srv =
+           (unsigned char *) NGX_HTTP_V2_ALPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE;
+        srvlen = sizeof(NGX_HTTP_V2_ALPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE) - 1;
+
+    } else
+#endif
+    {
+        srv = (unsigned char *) NGX_HTTP_NPN_ADVERTISE;
+        srvlen = sizeof(NGX_HTTP_NPN_ADVERTISE) - 1;
+    }
+
+    if (SSL_select_next_proto((unsigned char **) out, outlen, srv, srvlen,
+                              in, inlen)
+        != OPENSSL_NPN_NEGOTIATED)
+    {
+        return SSL_TLSEXT_ERR_NOACK;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "SSL ALPN selected: %*s", (size_t) *outlen, *out);
+
+    return SSL_TLSEXT_ERR_OK;
+}
+
+#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;
+
+    hc = c->data;
+
+    if (hc->addr_conf->http2) {
+        *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)
+{
+    ngx_ssl_variable_handler_pt  handler = (ngx_ssl_variable_handler_pt) data;
+
+    size_t     len;
+    ngx_str_t  s;
+
+    if (r->connection->ssl) {
+
+        (void) handler(r->connection, NULL, &s);
+
+        v->data = s.data;
+
+        for (len = 0; v->data[len]; len++) { /* void */ }
+
+        v->len = len;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+
+        return NGX_OK;
+    }
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssl_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+    uintptr_t data)
+{
+    ngx_ssl_variable_handler_pt  handler = (ngx_ssl_variable_handler_pt) data;
+
+    ngx_str_t  s;
+
+    if (r->connection->ssl) {
+
+        if (handler(r->connection, r->pool, &s) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        v->len = s.len;
+        v->data = s.data;
+
+        if (v->len) {
+            v->valid = 1;
+            v->no_cacheable = 0;
+            v->not_found = 0;
+
+            return NGX_OK;
+        }
+    }
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssl_add_variables(ngx_conf_t *cf)
+{
+    ngx_http_variable_t  *var, *v;
+
+    for (v = ngx_http_ssl_vars; v->name.len; v++) {
+        var = ngx_http_add_variable(cf, &v->name, v->flags);
+        if (var == NULL) {
+            return NGX_ERROR;
+        }
+
+        var->get_handler = v->get_handler;
+        var->data = v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_ssl_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_http_ssl_srv_conf_t  *sscf;
+
+    sscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_ssl_srv_conf_t));
+    if (sscf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     sscf->protocols = 0;
+     *     sscf->dhparam = { 0, NULL };
+     *     sscf->ecdh_curve = { 0, NULL };
+     *     sscf->client_certificate = { 0, NULL };
+     *     sscf->trusted_certificate = { 0, NULL };
+     *     sscf->crl = { 0, NULL };
+     *     sscf->ciphers = { 0, NULL };
+     *     sscf->shm_zone = NULL;
+     *     sscf->stapling_file = { 0, NULL };
+     *     sscf->stapling_responder = { 0, NULL };
+     */
+
+    sscf->enable = NGX_CONF_UNSET;
+    sscf->prefer_server_ciphers = 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->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->stapling = NGX_CONF_UNSET;
+    sscf->stapling_verify = NGX_CONF_UNSET;
+
+    return sscf;
+}
+
+
+static char *
+ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_ssl_srv_conf_t *prev = parent;
+    ngx_http_ssl_srv_conf_t *conf = child;
+
+    ngx_pool_cleanup_t  *cln;
+
+    if (conf->enable == NGX_CONF_UNSET) {
+        if (prev->enable == NGX_CONF_UNSET) {
+            conf->enable = 0;
+
+        } else {
+            conf->enable = prev->enable;
+            conf->file = prev->file;
+            conf->line = prev->line;
+        }
+    }
+
+    ngx_conf_merge_value(conf->session_timeout,
+                         prev->session_timeout, 300);
+
+    ngx_conf_merge_value(conf->prefer_server_ciphers,
+                         prev->prefer_server_ciphers, 0);
+
+    ngx_conf_merge_bitmask_value(conf->protocols, prev->protocols,
+                         (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1
+                          |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2));
+
+    ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size,
+                         NGX_SSL_BUFSIZE);
+
+    ngx_conf_merge_uint_value(conf->verify, prev->verify, 0);
+    ngx_conf_merge_uint_value(conf->verify_depth, prev->verify_depth, 1);
+
+    ngx_conf_merge_ptr_value(conf->certificates, prev->certificates, NULL);
+    ngx_conf_merge_ptr_value(conf->certificate_keys, prev->certificate_keys,
+                         NULL);
+
+    ngx_conf_merge_ptr_value(conf->passwords, prev->passwords, NULL);
+
+    ngx_conf_merge_str_value(conf->dhparam, prev->dhparam, "");
+
+    ngx_conf_merge_str_value(conf->client_certificate, prev->client_certificate,
+                         "");
+    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->ecdh_curve, prev->ecdh_curve,
+                         NGX_DEFAULT_ECDH_CURVE);
+
+    ngx_conf_merge_str_value(conf->ciphers, prev->ciphers, NGX_DEFAULT_CIPHERS);
+
+    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, "");
+    ngx_conf_merge_str_value(conf->stapling_responder,
+                         prev->stapling_responder, "");
+
+    conf->ssl.log = cf->log;
+
+    if (conf->enable) {
+
+        if (conf->certificates == NULL) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "no \"ssl_certificate\" is defined for "
+                          "the \"ssl\" directive in %s:%ui",
+                          conf->file, conf->line);
+            return NGX_CONF_ERROR;
+        }
+
+        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->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->certificates == NULL) {
+            return NGX_CONF_OK;
+        }
+
+        if (conf->certificate_keys == NULL
+            || 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\"",
+                          ((ngx_str_t *) conf->certificates->elts)
+                          + conf->certificates->nelts - 1);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (ngx_ssl_create(&conf->ssl, conf->protocols, conf) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+
+    if (SSL_CTX_set_tlsext_servername_callback(conf->ssl.ctx,
+                                               ngx_http_ssl_servername)
+        == 0)
+    {
+        ngx_log_error(NGX_LOG_WARN, cf->log, 0,
+            "nginx was built with SNI support, however, now it is linked "
+            "dynamically to an OpenSSL library which has no tlsext support, "
+            "therefore SNI is not available");
+    }
+
+#endif
+
+#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
+    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
+
+    cln = ngx_pool_cleanup_add(cf->pool, 0);
+    if (cln == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    cln->handler = ngx_ssl_cleanup_ctx;
+    cln->data = &conf->ssl;
+
+    if (ngx_ssl_certificates(cf, &conf->ssl, conf->certificates,
+                             conf->certificate_keys, conf->passwords)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    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;
+
+    if (conf->verify) {
+
+        if (conf->client_certificate.len == 0 && conf->verify != 3) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "no ssl_client_certificate for ssl_client_verify");
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_ssl_client_certificate(cf, &conf->ssl,
+                                       &conf->client_certificate,
+                                       conf->verify_depth)
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (ngx_ssl_trusted_certificate(cf, &conf->ssl,
+                                    &conf->trusted_certificate,
+                                    conf->verify_depth)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    if (ngx_ssl_crl(cf, &conf->ssl, &conf->crl) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (ngx_ssl_dhparam(cf, &conf->ssl, &conf->dhparam) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (ngx_ssl_ecdh_curve(cf, &conf->ssl, &conf->ecdh_curve) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_conf_merge_value(conf->builtin_session_cache,
+                         prev->builtin_session_cache, NGX_SSL_NONE_SCACHE);
+
+    if (conf->shm_zone == NULL) {
+        conf->shm_zone = prev->shm_zone;
+    }
+
+    if (ngx_ssl_session_cache(&conf->ssl, &ngx_http_ssl_sess_id_ctx,
+                              conf->builtin_session_cache,
+                              conf->shm_zone, conf->session_timeout)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_conf_merge_value(conf->session_tickets, prev->session_tickets, 1);
+
+#ifdef SSL_OP_NO_TICKET
+    if (!conf->session_tickets) {
+        SSL_CTX_set_options(conf->ssl.ctx, SSL_OP_NO_TICKET);
+    }
+#endif
+
+    ngx_conf_merge_ptr_value(conf->session_ticket_keys,
+                         prev->session_ticket_keys, NULL);
+
+    if (ngx_ssl_session_ticket_keys(cf, &conf->ssl, conf->session_ticket_keys)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    if (conf->stapling) {
+
+        if (ngx_ssl_stapling(cf, &conf->ssl, &conf->stapling_file,
+                             &conf->stapling_responder, conf->stapling_verify)
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_ssl_srv_conf_t *sscf = conf;
+
+    char  *rv;
+
+    rv = ngx_conf_set_flag_slot(cf, cmd, conf);
+
+    if (rv != NGX_CONF_OK) {
+        return rv;
+    }
+
+    sscf->file = cf->conf_file->file.name.data;
+    sscf->line = cf->conf_file->line;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_ssl_srv_conf_t *sscf = conf;
+
+    ngx_str_t  *value;
+
+    if (sscf->passwords != NGX_CONF_UNSET_PTR) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    sscf->passwords = ngx_ssl_read_password_file(cf, &value[1]);
+
+    if (sscf->passwords == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_ssl_srv_conf_t *sscf = conf;
+
+    size_t       len;
+    ngx_str_t   *value, name, size;
+    ngx_int_t    n;
+    ngx_uint_t   i, j;
+
+    value = cf->args->elts;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+
+        if (ngx_strcmp(value[i].data, "off") == 0) {
+            sscf->builtin_session_cache = NGX_SSL_NO_SCACHE;
+            continue;
+        }
+
+        if (ngx_strcmp(value[i].data, "none") == 0) {
+            sscf->builtin_session_cache = NGX_SSL_NONE_SCACHE;
+            continue;
+        }
+
+        if (ngx_strcmp(value[i].data, "builtin") == 0) {
+            sscf->builtin_session_cache = NGX_SSL_DFLT_BUILTIN_SCACHE;
+            continue;
+        }
+
+        if (value[i].len > sizeof("builtin:") - 1
+            && ngx_strncmp(value[i].data, "builtin:", sizeof("builtin:") - 1)
+               == 0)
+        {
+            n = ngx_atoi(value[i].data + sizeof("builtin:") - 1,
+                         value[i].len - (sizeof("builtin:") - 1));
+
+            if (n == NGX_ERROR) {
+                goto invalid;
+            }
+
+            sscf->builtin_session_cache = n;
+
+            continue;
+        }
+
+        if (value[i].len > sizeof("shared:") - 1
+            && ngx_strncmp(value[i].data, "shared:", sizeof("shared:") - 1)
+               == 0)
+        {
+            len = 0;
+
+            for (j = sizeof("shared:") - 1; j < value[i].len; j++) {
+                if (value[i].data[j] == ':') {
+                    break;
+                }
+
+                len++;
+            }
+
+            if (len == 0) {
+                goto invalid;
+            }
+
+            name.len = len;
+            name.data = value[i].data + sizeof("shared:") - 1;
+
+            size.len = value[i].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,
+                                   "session cache \"%V\" is too small",
+                                   &value[i]);
+
+                return NGX_CONF_ERROR;
+            }
+
+            sscf->shm_zone = ngx_shared_memory_add(cf, &name, n,
+                                                   &ngx_http_ssl_module);
+            if (sscf->shm_zone == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            sscf->shm_zone->init = ngx_ssl_session_cache_init;
+
+            continue;
+        }
+
+        goto invalid;
+    }
+
+    if (sscf->shm_zone && sscf->builtin_session_cache == NGX_CONF_UNSET) {
+        sscf->builtin_session_cache = NGX_SSL_NO_BUILTIN_SCACHE;
+    }
+
+    return NGX_CONF_OK;
+
+invalid:
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "invalid session cache \"%V\"", &value[i]);
+
+    return NGX_CONF_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_ssl_init(ngx_conf_t *cf)
+{
+    ngx_uint_t                   s;
+    ngx_http_ssl_srv_conf_t     *sscf;
+    ngx_http_core_loc_conf_t    *clcf;
+    ngx_http_core_srv_conf_t   **cscfp;
+    ngx_http_core_main_conf_t   *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+    cscfp = cmcf->servers.elts;
+
+    for (s = 0; s < cmcf->servers.nelts; s++) {
+
+        sscf = cscfp[s]->ctx->srv_conf[ngx_http_ssl_module.ctx_index];
+
+        if (sscf->ssl.ctx == NULL || !sscf->stapling) {
+            continue;
+        }
+
+        clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];
+
+        if (ngx_ssl_stapling_resolver(cf, &sscf->ssl, clcf->resolver,
+                                      clcf->resolver_timeout)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_ssl_module.h b/nginx/src/http/modules/ngx_http_ssl_module.h
new file mode 100644 (file)
index 0000000..57f5941
--- /dev/null
@@ -0,0 +1,66 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_SSL_H_INCLUDED_
+#define _NGX_HTTP_SSL_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_flag_t                      enable;
+
+    ngx_ssl_t                       ssl;
+
+    ngx_flag_t                      prefer_server_ciphers;
+
+    ngx_uint_t                      protocols;
+
+    ngx_uint_t                      verify;
+    ngx_uint_t                      verify_depth;
+
+    size_t                          buffer_size;
+
+    ssize_t                         builtin_session_cache;
+
+    time_t                          session_timeout;
+
+    ngx_array_t                    *certificates;
+    ngx_array_t                    *certificate_keys;
+
+    ngx_str_t                       dhparam;
+    ngx_str_t                       ecdh_curve;
+    ngx_str_t                       client_certificate;
+    ngx_str_t                       trusted_certificate;
+    ngx_str_t                       crl;
+
+    ngx_str_t                       ciphers;
+
+    ngx_array_t                    *passwords;
+
+    ngx_shm_zone_t                 *shm_zone;
+
+    ngx_flag_t                      session_tickets;
+    ngx_array_t                    *session_ticket_keys;
+
+    ngx_flag_t                      stapling;
+    ngx_flag_t                      stapling_verify;
+    ngx_str_t                       stapling_file;
+    ngx_str_t                       stapling_responder;
+
+    u_char                         *file;
+    ngx_uint_t                      line;
+} ngx_http_ssl_srv_conf_t;
+
+
+extern ngx_module_t  ngx_http_ssl_module;
+
+
+#endif /* _NGX_HTTP_SSL_H_INCLUDED_ */
diff --git a/nginx/src/http/modules/ngx_http_static_module.c b/nginx/src/http/modules/ngx_http_static_module.c
new file mode 100644 (file)
index 0000000..0e16c05
--- /dev/null
@@ -0,0 +1,288 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_static_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_static_init(ngx_conf_t *cf);
+
+
+static ngx_http_module_t  ngx_http_static_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_static_init,                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_static_module = {
+    NGX_MODULE_V1,
+    &ngx_http_static_module_ctx,           /* module context */
+    NULL,                                  /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_static_handler(ngx_http_request_t *r)
+{
+    u_char                    *last, *location;
+    size_t                     root, len;
+    ngx_str_t                  path;
+    ngx_int_t                  rc;
+    ngx_uint_t                 level;
+    ngx_log_t                 *log;
+    ngx_buf_t                 *b;
+    ngx_chain_t                out;
+    ngx_open_file_info_t       of;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_POST))) {
+        return NGX_HTTP_NOT_ALLOWED;
+    }
+
+    if (r->uri.data[r->uri.len - 1] == '/') {
+        return NGX_DECLINED;
+    }
+
+    log = r->connection->log;
+
+    /*
+     * ngx_http_map_uri_to_path() allocates memory for terminating '\0'
+     * so we do not need to reserve memory for '/' for possible redirect
+     */
+
+    last = ngx_http_map_uri_to_path(r, &path, &root, 0);
+    if (last == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    path.len = last - path.data;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+                   "http filename: \"%s\"", path.data);
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+    of.read_ahead = clcf->read_ahead;
+    of.directio = clcf->directio;
+    of.valid = clcf->open_file_cache_valid;
+    of.min_uses = clcf->open_file_cache_min_uses;
+    of.errors = clcf->open_file_cache_errors;
+    of.events = clcf->open_file_cache_events;
+
+    if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
+        != NGX_OK)
+    {
+        switch (of.err) {
+
+        case 0:
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+
+        case NGX_ENOENT:
+        case NGX_ENOTDIR:
+        case NGX_ENAMETOOLONG:
+
+            level = NGX_LOG_ERR;
+            rc = NGX_HTTP_NOT_FOUND;
+            break;
+
+        case NGX_EACCES:
+#if (NGX_HAVE_OPENAT)
+        case NGX_EMLINK:
+        case NGX_ELOOP:
+#endif
+
+            level = NGX_LOG_ERR;
+            rc = NGX_HTTP_FORBIDDEN;
+            break;
+
+        default:
+
+            level = NGX_LOG_CRIT;
+            rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+            break;
+        }
+
+        if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) {
+            ngx_log_error(level, log, of.err,
+                          "%s \"%s\" failed", of.failed, path.data);
+        }
+
+        return rc;
+    }
+
+    r->root_tested = !r->error_page;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http static fd: %d", of.fd);
+
+    if (of.is_dir) {
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http dir");
+
+        ngx_http_clear_location(r);
+
+        r->headers_out.location = ngx_list_push(&r->headers_out.headers);
+        if (r->headers_out.location == NULL) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        len = r->uri.len + 1;
+
+        if (!clcf->alias && clcf->root_lengths == NULL && r->args.len == 0) {
+            location = path.data + clcf->root.len;
+
+            *last = '/';
+
+        } else {
+            if (r->args.len) {
+                len += r->args.len + 1;
+            }
+
+            location = ngx_pnalloc(r->pool, len);
+            if (location == NULL) {
+                ngx_http_clear_location(r);
+                return NGX_HTTP_INTERNAL_SERVER_ERROR;
+            }
+
+            last = ngx_copy(location, r->uri.data, r->uri.len);
+
+            *last = '/';
+
+            if (r->args.len) {
+                *++last = '?';
+                ngx_memcpy(++last, r->args.data, r->args.len);
+            }
+        }
+
+        r->headers_out.location->hash = 1;
+        ngx_str_set(&r->headers_out.location->key, "Location");
+        r->headers_out.location->value.len = len;
+        r->headers_out.location->value.data = location;
+
+        return NGX_HTTP_MOVED_PERMANENTLY;
+    }
+
+#if !(NGX_WIN32) /* the not regular files are probably Unix specific */
+
+    if (!of.is_file) {
+        ngx_log_error(NGX_LOG_CRIT, log, 0,
+                      "\"%s\" is not a regular file", path.data);
+
+        return NGX_HTTP_NOT_FOUND;
+    }
+
+#endif
+
+    if (r->method == NGX_HTTP_POST) {
+        return NGX_HTTP_NOT_ALLOWED;
+    }
+
+    rc = ngx_http_discard_request_body(r);
+
+    if (rc != NGX_OK) {
+        return rc;
+    }
+
+    log->action = "sending response to client";
+
+    r->headers_out.status = NGX_HTTP_OK;
+    r->headers_out.content_length_n = of.size;
+    r->headers_out.last_modified_time = of.mtime;
+
+    if (ngx_http_set_etag(r) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (ngx_http_set_content_type(r) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (r != r->main && of.size == 0) {
+        return ngx_http_send_header(r);
+    }
+
+    r->allow_ranges = 1;
+
+    /* we need to allocate all before the header would be sent */
+
+    b = ngx_calloc_buf(r->pool);
+    if (b == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
+    if (b->file == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    rc = ngx_http_send_header(r);
+
+    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+        return rc;
+    }
+
+    b->file_pos = 0;
+    b->file_last = of.size;
+
+    b->in_file = b->file_last ? 1: 0;
+    b->last_buf = (r == r->main) ? 1: 0;
+    b->last_in_chain = 1;
+
+    b->file->fd = of.fd;
+    b->file->name = path;
+    b->file->log = log;
+    b->file->directio = of.is_directio;
+
+    out.buf = b;
+    out.next = NULL;
+
+    return ngx_http_output_filter(r, &out);
+}
+
+
+static ngx_int_t
+ngx_http_static_init(ngx_conf_t *cf)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_static_handler;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_stub_status_module.c b/nginx/src/http/modules/ngx_http_stub_status_module.c
new file mode 100644 (file)
index 0000000..9bdf881
--- /dev/null
@@ -0,0 +1,236 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_stub_status_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_stub_status_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_stub_status_add_variables(ngx_conf_t *cf);
+static char *ngx_http_set_stub_status(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+
+static ngx_command_t  ngx_http_status_commands[] = {
+
+    { ngx_string("stub_status"),
+      NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE1,
+      ngx_http_set_stub_status,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_stub_status_module_ctx = {
+    ngx_http_stub_status_add_variables,    /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_stub_status_module = {
+    NGX_MODULE_V1,
+    &ngx_http_stub_status_module_ctx,      /* module context */
+    ngx_http_status_commands,              /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_variable_t  ngx_http_stub_status_vars[] = {
+
+    { ngx_string("connections_active"), NULL, ngx_http_stub_status_variable,
+      0, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("connections_reading"), NULL, ngx_http_stub_status_variable,
+      1, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("connections_writing"), NULL, ngx_http_stub_status_variable,
+      2, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("connections_waiting"), NULL, ngx_http_stub_status_variable,
+      3, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+      ngx_http_null_variable
+};
+
+
+static ngx_int_t
+ngx_http_stub_status_handler(ngx_http_request_t *r)
+{
+    size_t             size;
+    ngx_int_t          rc;
+    ngx_buf_t         *b;
+    ngx_chain_t        out;
+    ngx_atomic_int_t   ap, hn, ac, rq, rd, wr, wa;
+
+    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
+        return NGX_HTTP_NOT_ALLOWED;
+    }
+
+    rc = ngx_http_discard_request_body(r);
+
+    if (rc != NGX_OK) {
+        return rc;
+    }
+
+    r->headers_out.content_type_len = sizeof("text/plain") - 1;
+    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
+           + sizeof("server accepts handled requests\n") - 1
+           + 6 + 3 * NGX_ATOMIC_T_LEN
+           + sizeof("Reading:  Writing:  Waiting:  \n") + 3 * NGX_ATOMIC_T_LEN;
+
+    b = ngx_create_temp_buf(r->pool, size);
+    if (b == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    out.buf = b;
+    out.next = NULL;
+
+    ap = *ngx_stat_accepted;
+    hn = *ngx_stat_handled;
+    ac = *ngx_stat_active;
+    rq = *ngx_stat_requests;
+    rd = *ngx_stat_reading;
+    wr = *ngx_stat_writing;
+    wa = *ngx_stat_waiting;
+
+    b->last = ngx_sprintf(b->last, "Active connections: %uA \n", ac);
+
+    b->last = ngx_cpymem(b->last, "server accepts handled requests\n",
+                         sizeof("server accepts handled requests\n") - 1);
+
+    b->last = ngx_sprintf(b->last, " %uA %uA %uA \n", ap, hn, rq);
+
+    b->last = ngx_sprintf(b->last, "Reading: %uA Writing: %uA Waiting: %uA \n",
+                          rd, wr, wa);
+
+    r->headers_out.status = NGX_HTTP_OK;
+    r->headers_out.content_length_n = b->last - b->pos;
+
+    b->last_buf = (r == r->main) ? 1 : 0;
+    b->last_in_chain = 1;
+
+    rc = ngx_http_send_header(r);
+
+    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+        return rc;
+    }
+
+    return ngx_http_output_filter(r, &out);
+}
+
+
+static ngx_int_t
+ngx_http_stub_status_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char            *p;
+    ngx_atomic_int_t   value;
+
+    p = ngx_pnalloc(r->pool, NGX_ATOMIC_T_LEN);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    switch (data) {
+    case 0:
+        value = *ngx_stat_active;
+        break;
+
+    case 1:
+        value = *ngx_stat_reading;
+        break;
+
+    case 2:
+        value = *ngx_stat_writing;
+        break;
+
+    case 3:
+        value = *ngx_stat_waiting;
+        break;
+
+    /* suppress warning */
+    default:
+        value = 0;
+        break;
+    }
+
+    v->len = ngx_sprintf(p, "%uA", value) - p;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = p;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_stub_status_add_variables(ngx_conf_t *cf)
+{
+    ngx_http_variable_t  *var, *v;
+
+    for (v = ngx_http_stub_status_vars; v->name.len; v++) {
+        var = ngx_http_add_variable(cf, &v->name, v->flags);
+        if (var == NULL) {
+            return NGX_ERROR;
+        }
+
+        var->get_handler = v->get_handler;
+        var->data = v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_http_set_stub_status(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_loc_conf_t  *clcf;
+
+    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+    clcf->handler = ngx_http_stub_status_handler;
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_sub_filter_module.c b/nginx/src/http/modules/ngx_http_sub_filter_module.c
new file mode 100644 (file)
index 0000000..6d3de59
--- /dev/null
@@ -0,0 +1,1018 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_http_complex_value_t   match;
+    ngx_http_complex_value_t   value;
+} ngx_http_sub_pair_t;
+
+
+typedef struct {
+    ngx_str_t                  match;
+    ngx_http_complex_value_t  *value;
+} ngx_http_sub_match_t;
+
+
+typedef struct {
+    ngx_uint_t                 min_match_len;
+    ngx_uint_t                 max_match_len;
+
+    u_char                     index[257];
+    u_char                     shift[256];
+} ngx_http_sub_tables_t;
+
+
+typedef struct {
+    ngx_uint_t                 dynamic; /* unsigned dynamic:1; */
+
+    ngx_array_t               *pairs;
+
+    ngx_http_sub_tables_t     *tables;
+
+    ngx_hash_t                 types;
+
+    ngx_flag_t                 once;
+    ngx_flag_t                 last_modified;
+
+    ngx_array_t               *types_keys;
+    ngx_array_t               *matches;
+} ngx_http_sub_loc_conf_t;
+
+
+typedef struct {
+    ngx_str_t                  saved;
+    ngx_str_t                  looked;
+
+    ngx_uint_t                 once;   /* unsigned  once:1 */
+
+    ngx_buf_t                 *buf;
+
+    u_char                    *pos;
+    u_char                    *copy_start;
+    u_char                    *copy_end;
+
+    ngx_chain_t               *in;
+    ngx_chain_t               *out;
+    ngx_chain_t              **last_out;
+    ngx_chain_t               *busy;
+    ngx_chain_t               *free;
+
+    ngx_str_t                 *sub;
+    ngx_uint_t                 applied;
+
+    ngx_int_t                  offset;
+    ngx_uint_t                 index;
+
+    ngx_http_sub_tables_t     *tables;
+    ngx_array_t               *matches;
+} ngx_http_sub_ctx_t;
+
+
+static ngx_uint_t ngx_http_sub_cmp_index;
+
+
+static ngx_int_t ngx_http_sub_output(ngx_http_request_t *r,
+    ngx_http_sub_ctx_t *ctx);
+static ngx_int_t ngx_http_sub_parse(ngx_http_request_t *r,
+    ngx_http_sub_ctx_t *ctx, ngx_uint_t flush);
+static ngx_int_t ngx_http_sub_match(ngx_http_sub_ctx_t *ctx, ngx_int_t start,
+    ngx_str_t *m);
+
+static char * ngx_http_sub_filter(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static void *ngx_http_sub_create_conf(ngx_conf_t *cf);
+static char *ngx_http_sub_merge_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+static void ngx_http_sub_init_tables(ngx_http_sub_tables_t *tables,
+    ngx_http_sub_match_t *match, ngx_uint_t n);
+static ngx_int_t ngx_http_sub_cmp_matches(const void *one, const void *two);
+static ngx_int_t ngx_http_sub_filter_init(ngx_conf_t *cf);
+
+
+static ngx_command_t  ngx_http_sub_filter_commands[] = {
+
+    { ngx_string("sub_filter"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+      ngx_http_sub_filter,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("sub_filter_types"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_types_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_sub_loc_conf_t, types_keys),
+      &ngx_http_html_default_types[0] },
+
+    { ngx_string("sub_filter_once"),
+      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_sub_loc_conf_t, once),
+      NULL },
+
+    { ngx_string("sub_filter_last_modified"),
+      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_sub_loc_conf_t, last_modified),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_sub_filter_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_sub_filter_init,              /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_sub_create_conf,              /* create location configuration */
+    ngx_http_sub_merge_conf                /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_sub_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_sub_filter_module_ctx,       /* module context */
+    ngx_http_sub_filter_commands,          /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_sub_header_filter(ngx_http_request_t *r)
+{
+    ngx_str_t                *m;
+    ngx_uint_t                i, j, n;
+    ngx_http_sub_ctx_t       *ctx;
+    ngx_http_sub_pair_t      *pairs;
+    ngx_http_sub_match_t     *matches;
+    ngx_http_sub_loc_conf_t  *slcf;
+
+    slcf = ngx_http_get_module_loc_conf(r, ngx_http_sub_filter_module);
+
+    if (slcf->pairs == NULL
+        || r->headers_out.content_length_n == 0
+        || ngx_http_test_content_type(r, &slcf->types) == NULL)
+    {
+        return ngx_http_next_header_filter(r);
+    }
+
+    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_sub_ctx_t));
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (slcf->dynamic == 0) {
+        ctx->tables = slcf->tables;
+        ctx->matches = slcf->matches;
+
+    } else {
+        pairs = slcf->pairs->elts;
+        n = slcf->pairs->nelts;
+
+        matches = ngx_pcalloc(r->pool, sizeof(ngx_http_sub_match_t) * n);
+        if (matches == NULL) {
+            return NGX_ERROR;
+        }
+
+        j = 0;
+        for (i = 0; i < n; i++) {
+            matches[j].value = &pairs[i].value;
+
+            if (pairs[i].match.lengths == NULL) {
+                matches[j].match = pairs[i].match.value;
+                j++;
+                continue;
+            }
+
+            m = &matches[j].match;
+            if (ngx_http_complex_value(r, &pairs[i].match, m) != NGX_OK) {
+                return NGX_ERROR;
+            }
+
+            if (m->len == 0) {
+                continue;
+            }
+
+            ngx_strlow(m->data, m->data, m->len);
+            j++;
+        }
+
+        if (j == 0) {
+            return ngx_http_next_header_filter(r);
+        }
+
+        ctx->matches = ngx_palloc(r->pool, sizeof(ngx_array_t));
+        if (ctx->matches == NULL) {
+            return NGX_ERROR;
+        }
+
+        ctx->matches->elts = matches;
+        ctx->matches->nelts = j;
+
+        ctx->tables = ngx_palloc(r->pool, sizeof(ngx_http_sub_tables_t));
+        if (ctx->tables == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_http_sub_init_tables(ctx->tables, ctx->matches->elts,
+                                 ctx->matches->nelts);
+    }
+
+    ctx->saved.data = ngx_pnalloc(r->pool, ctx->tables->max_match_len - 1);
+    if (ctx->saved.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ctx->looked.data = ngx_pnalloc(r->pool, ctx->tables->max_match_len - 1);
+    if (ctx->looked.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_http_set_ctx(r, ctx, ngx_http_sub_filter_module);
+
+    ctx->offset = ctx->tables->min_match_len - 1;
+    ctx->last_out = &ctx->out;
+
+    r->filter_need_in_memory = 1;
+
+    if (r == r->main) {
+        ngx_http_clear_content_length(r);
+
+        if (!slcf->last_modified) {
+            ngx_http_clear_last_modified(r);
+            ngx_http_clear_etag(r);
+
+        } else {
+            ngx_http_weak_etag(r);
+        }
+    }
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_sub_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    ngx_int_t                  rc;
+    ngx_buf_t                 *b;
+    ngx_str_t                 *sub;
+    ngx_uint_t                 flush, last;
+    ngx_chain_t               *cl;
+    ngx_http_sub_ctx_t        *ctx;
+    ngx_http_sub_match_t      *match;
+    ngx_http_sub_loc_conf_t   *slcf;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_sub_filter_module);
+
+    if (ctx == NULL) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    if ((in == NULL
+         && ctx->buf == NULL
+         && ctx->in == NULL
+         && ctx->busy == NULL))
+    {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    if (ctx->once && (ctx->buf == NULL || ctx->in == NULL)) {
+
+        if (ctx->busy) {
+            if (ngx_http_sub_output(r, ctx) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+        }
+
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    /* add the incoming chain to the chain ctx->in */
+
+    if (in) {
+        if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http sub filter \"%V\"", &r->uri);
+
+    flush = 0;
+    last = 0;
+
+    while (ctx->in || ctx->buf) {
+
+        if (ctx->buf == NULL) {
+            ctx->buf = ctx->in->buf;
+            ctx->in = ctx->in->next;
+            ctx->pos = ctx->buf->pos;
+        }
+
+        if (ctx->buf->flush || ctx->buf->recycled) {
+            flush = 1;
+        }
+
+        if (ctx->in == NULL) {
+            last = flush;
+        }
+
+        b = NULL;
+
+        while (ctx->pos < ctx->buf->last) {
+
+            rc = ngx_http_sub_parse(r, ctx, last);
+
+            ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "parse: %i, looked: \"%V\" %p-%p",
+                           rc, &ctx->looked, ctx->copy_start, ctx->copy_end);
+
+            if (rc == NGX_ERROR) {
+                return rc;
+            }
+
+            if (ctx->saved.len) {
+
+                ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                               "saved: \"%V\"", &ctx->saved);
+
+                cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+                if (cl == NULL) {
+                    return NGX_ERROR;
+                }
+
+                b = cl->buf;
+
+                ngx_memzero(b, sizeof(ngx_buf_t));
+
+                b->pos = ngx_pnalloc(r->pool, ctx->saved.len);
+                if (b->pos == NULL) {
+                    return NGX_ERROR;
+                }
+
+                ngx_memcpy(b->pos, ctx->saved.data, ctx->saved.len);
+                b->last = b->pos + ctx->saved.len;
+                b->memory = 1;
+
+                *ctx->last_out = cl;
+                ctx->last_out = &cl->next;
+
+                ctx->saved.len = 0;
+            }
+
+            if (ctx->copy_start != ctx->copy_end) {
+
+                cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+                if (cl == NULL) {
+                    return NGX_ERROR;
+                }
+
+                b = cl->buf;
+
+                ngx_memcpy(b, ctx->buf, sizeof(ngx_buf_t));
+
+                b->pos = ctx->copy_start;
+                b->last = ctx->copy_end;
+                b->shadow = NULL;
+                b->last_buf = 0;
+                b->last_in_chain = 0;
+                b->recycled = 0;
+
+                if (b->in_file) {
+                    b->file_last = b->file_pos + (b->last - ctx->buf->pos);
+                    b->file_pos += b->pos - ctx->buf->pos;
+                }
+
+                *ctx->last_out = cl;
+                ctx->last_out = &cl->next;
+            }
+
+            if (rc == NGX_AGAIN) {
+                continue;
+            }
+
+
+            /* rc == NGX_OK */
+
+            cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+            if (cl == NULL) {
+                return NGX_ERROR;
+            }
+
+            b = cl->buf;
+
+            ngx_memzero(b, sizeof(ngx_buf_t));
+
+            slcf = ngx_http_get_module_loc_conf(r, ngx_http_sub_filter_module);
+
+            if (ctx->sub == NULL) {
+                ctx->sub = ngx_pcalloc(r->pool, sizeof(ngx_str_t)
+                                                * ctx->matches->nelts);
+                if (ctx->sub == NULL) {
+                    return NGX_ERROR;
+                }
+            }
+
+            sub = &ctx->sub[ctx->index];
+
+            if (sub->data == NULL) {
+                match = ctx->matches->elts;
+
+                if (ngx_http_complex_value(r, match[ctx->index].value, sub)
+                    != NGX_OK)
+                {
+                    return NGX_ERROR;
+                }
+            }
+
+            if (sub->len) {
+                b->memory = 1;
+                b->pos = sub->data;
+                b->last = sub->data + sub->len;
+
+            } else {
+                b->sync = 1;
+            }
+
+            *ctx->last_out = cl;
+            ctx->last_out = &cl->next;
+
+            ctx->index = 0;
+            ctx->once = slcf->once && (++ctx->applied == ctx->matches->nelts);
+
+            continue;
+        }
+
+        if (ctx->looked.len
+            && (ctx->buf->last_buf || ctx->buf->last_in_chain))
+        {
+            cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+            if (cl == NULL) {
+                return NGX_ERROR;
+            }
+
+            b = cl->buf;
+
+            ngx_memzero(b, sizeof(ngx_buf_t));
+
+            b->pos = ctx->looked.data;
+            b->last = b->pos + ctx->looked.len;
+            b->memory = 1;
+
+            *ctx->last_out = cl;
+            ctx->last_out = &cl->next;
+
+            ctx->looked.len = 0;
+        }
+
+        if (ctx->buf->last_buf || ctx->buf->flush || ctx->buf->sync
+            || ngx_buf_in_memory(ctx->buf))
+        {
+            if (b == NULL) {
+                cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+                if (cl == NULL) {
+                    return NGX_ERROR;
+                }
+
+                b = cl->buf;
+
+                ngx_memzero(b, sizeof(ngx_buf_t));
+
+                b->sync = 1;
+
+                *ctx->last_out = cl;
+                ctx->last_out = &cl->next;
+            }
+
+            b->last_buf = ctx->buf->last_buf;
+            b->last_in_chain = ctx->buf->last_in_chain;
+            b->flush = ctx->buf->flush;
+            b->shadow = ctx->buf;
+
+            b->recycled = ctx->buf->recycled;
+        }
+
+        ctx->buf = NULL;
+    }
+
+    if (ctx->out == NULL && ctx->busy == NULL) {
+        return NGX_OK;
+    }
+
+    return ngx_http_sub_output(r, ctx);
+}
+
+
+static ngx_int_t
+ngx_http_sub_output(ngx_http_request_t *r, ngx_http_sub_ctx_t *ctx)
+{
+    ngx_int_t     rc;
+    ngx_buf_t    *b;
+    ngx_chain_t  *cl;
+
+#if 1
+    b = NULL;
+    for (cl = ctx->out; cl; cl = cl->next) {
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "sub out: %p %p", cl->buf, cl->buf->pos);
+        if (cl->buf == b) {
+            ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                          "the same buf was used in sub");
+            ngx_debug_point();
+            return NGX_ERROR;
+        }
+        b = cl->buf;
+    }
+#endif
+
+    rc = ngx_http_next_body_filter(r, ctx->out);
+
+    if (ctx->busy == NULL) {
+        ctx->busy = ctx->out;
+
+    } else {
+        for (cl = ctx->busy; cl->next; cl = cl->next) { /* void */ }
+        cl->next = ctx->out;
+    }
+
+    ctx->out = NULL;
+    ctx->last_out = &ctx->out;
+
+    while (ctx->busy) {
+
+        cl = ctx->busy;
+        b = cl->buf;
+
+        if (ngx_buf_size(b) != 0) {
+            break;
+        }
+
+        if (b->shadow) {
+            b->shadow->pos = b->shadow->last;
+        }
+
+        ctx->busy = cl->next;
+
+        if (ngx_buf_in_memory(b) || b->in_file) {
+            /* add data bufs only to the free buf chain */
+
+            cl->next = ctx->free;
+            ctx->free = cl;
+        }
+    }
+
+    if (ctx->in || ctx->buf) {
+        r->buffered |= NGX_HTTP_SUB_BUFFERED;
+
+    } else {
+        r->buffered &= ~NGX_HTTP_SUB_BUFFERED;
+    }
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_http_sub_parse(ngx_http_request_t *r, ngx_http_sub_ctx_t *ctx,
+    ngx_uint_t flush)
+{
+    u_char                   *p, c;
+    ngx_str_t                *m;
+    ngx_int_t                 offset, start, next, end, len, rc;
+    ngx_uint_t                shift, i, j;
+    ngx_http_sub_match_t     *match;
+    ngx_http_sub_tables_t    *tables;
+    ngx_http_sub_loc_conf_t  *slcf;
+
+    slcf = ngx_http_get_module_loc_conf(r, ngx_http_sub_filter_module);
+    tables = ctx->tables;
+    match = ctx->matches->elts;
+
+    offset = ctx->offset;
+    end = ctx->buf->last - ctx->pos;
+
+    if (ctx->once) {
+        /* sets start and next to end */
+        offset = end + (ngx_int_t) tables->min_match_len - 1;
+        goto again;
+    }
+
+    while (offset < end) {
+
+        c = offset < 0 ? ctx->looked.data[ctx->looked.len + offset]
+                       : ctx->pos[offset];
+
+        c = ngx_tolower(c);
+
+        shift = tables->shift[c];
+        if (shift > 0) {
+            offset += shift;
+            continue;
+        }
+
+        /* a potential match */
+
+        start = offset - (ngx_int_t) tables->min_match_len + 1;
+
+        i = ngx_max((ngx_uint_t) tables->index[c], ctx->index);
+        j = tables->index[c + 1];
+
+        while (i != j) {
+
+            if (slcf->once && ctx->sub && ctx->sub[i].data) {
+                goto next;
+            }
+
+            m = &match[i].match;
+
+            rc = ngx_http_sub_match(ctx, start, m);
+
+            if (rc == NGX_DECLINED) {
+                goto next;
+            }
+
+            ctx->index = i;
+
+            if (rc == NGX_AGAIN) {
+                goto again;
+            }
+
+            ctx->offset = offset + (ngx_int_t) m->len;
+            next = start + (ngx_int_t) m->len;
+            end = ngx_max(next, 0);
+            rc = NGX_OK;
+
+            goto done;
+
+        next:
+
+            i++;
+        }
+
+        offset++;
+        ctx->index = 0;
+    }
+
+    if (flush) {
+        for ( ;; ) {
+            start = offset - (ngx_int_t) tables->min_match_len + 1;
+
+            if (start >= end) {
+                break;
+            }
+
+            for (i = 0; i < ctx->matches->nelts; i++) {
+                m = &match[i].match;
+
+                if (ngx_http_sub_match(ctx, start, m) == NGX_AGAIN) {
+                    goto again;
+                }
+            }
+
+            offset++;
+        }
+    }
+
+again:
+
+    ctx->offset = offset;
+    start = offset - (ngx_int_t) tables->min_match_len + 1;
+    next = start;
+    rc = NGX_AGAIN;
+
+done:
+
+    /* send [ - looked.len, start ] to client */
+
+    ctx->saved.len = ctx->looked.len + ngx_min(start, 0);
+    ngx_memcpy(ctx->saved.data, ctx->looked.data, ctx->saved.len);
+
+    ctx->copy_start = ctx->pos;
+    ctx->copy_end = ctx->pos + ngx_max(start, 0);
+
+    /* save [ next, end ] in looked */
+
+    len = ngx_min(next, 0);
+    p = ctx->looked.data;
+    p = ngx_movemem(p, p + ctx->looked.len + len, - len);
+
+    len = ngx_max(next, 0);
+    p = ngx_cpymem(p, ctx->pos + len, end - len);
+    ctx->looked.len = p - ctx->looked.data;
+
+    /* update position */
+
+    ctx->pos += end;
+    ctx->offset -= end;
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_http_sub_match(ngx_http_sub_ctx_t *ctx, ngx_int_t start, ngx_str_t *m)
+{
+    u_char  *p, *last, *pat, *pat_end;
+
+    pat = m->data;
+    pat_end = m->data + m->len;
+
+    if (start >= 0) {
+        p = ctx->pos + start;
+
+    } else {
+        last = ctx->looked.data + ctx->looked.len;
+        p = last + start;
+
+        while (p < last && pat < pat_end) {
+            if (ngx_tolower(*p) != *pat) {
+                return NGX_DECLINED;
+            }
+
+            p++;
+            pat++;
+        }
+
+        p = ctx->pos;
+    }
+
+    while (p < ctx->buf->last && pat < pat_end) {
+        if (ngx_tolower(*p) != *pat) {
+            return NGX_DECLINED;
+        }
+
+        p++;
+        pat++;
+    }
+
+    if (pat != pat_end) {
+        /* partial match */
+        return NGX_AGAIN;
+    }
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_http_sub_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_sub_loc_conf_t *slcf = conf;
+
+    ngx_str_t                         *value;
+    ngx_http_sub_pair_t               *pair;
+    ngx_http_compile_complex_value_t   ccv;
+
+    value = cf->args->elts;
+
+    if (value[1].len == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "empty search pattern");
+        return NGX_CONF_ERROR;
+    }
+
+    if (slcf->pairs == NULL) {
+        slcf->pairs = ngx_array_create(cf->pool, 1,
+                                       sizeof(ngx_http_sub_pair_t));
+        if (slcf->pairs == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (slcf->pairs->nelts == 255) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "number of search patterns exceeds 255");
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_strlow(value[1].data, value[1].data, value[1].len);
+
+    pair = ngx_array_push(slcf->pairs);
+    if (pair == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[1];
+    ccv.complex_value = &pair->match;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (ccv.complex_value->lengths != NULL) {
+        slcf->dynamic = 1;
+
+    } else {
+        ngx_strlow(pair->match.value.data, pair->match.value.data,
+                   pair->match.value.len);
+    }
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[2];
+    ccv.complex_value = &pair->value;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_http_sub_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_sub_loc_conf_t  *slcf;
+
+    slcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_sub_loc_conf_t));
+    if (slcf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->dynamic = 0;
+     *     conf->pairs = NULL;
+     *     conf->tables = NULL;
+     *     conf->types = { NULL };
+     *     conf->types_keys = NULL;
+     *     conf->matches = NULL;
+     */
+
+    slcf->once = NGX_CONF_UNSET;
+    slcf->last_modified = NGX_CONF_UNSET;
+
+    return slcf;
+}
+
+
+static char *
+ngx_http_sub_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_uint_t                i, n;
+    ngx_http_sub_pair_t      *pairs;
+    ngx_http_sub_match_t     *matches;
+    ngx_http_sub_loc_conf_t  *prev = parent;
+    ngx_http_sub_loc_conf_t  *conf = child;
+
+    ngx_conf_merge_value(conf->once, prev->once, 1);
+    ngx_conf_merge_value(conf->last_modified, prev->last_modified, 0);
+
+    if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types,
+                             &prev->types_keys, &prev->types,
+                             ngx_http_html_default_types)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    if (conf->pairs == NULL) {
+        conf->dynamic = prev->dynamic;
+        conf->pairs = prev->pairs;
+        conf->matches = prev->matches;
+        conf->tables = prev->tables;
+    }
+
+    if (conf->pairs && conf->dynamic == 0 && conf->tables == NULL) {
+        pairs = conf->pairs->elts;
+        n = conf->pairs->nelts;
+
+        matches = ngx_palloc(cf->pool, sizeof(ngx_http_sub_match_t) * n);
+        if (matches == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        for (i = 0; i < n; i++) {
+            matches[i].match = pairs[i].match.value;
+            matches[i].value = &pairs[i].value;
+        }
+
+        conf->matches = ngx_palloc(cf->pool, sizeof(ngx_array_t));
+        if (conf->matches == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        conf->matches->elts = matches;
+        conf->matches->nelts = n;
+
+        conf->tables = ngx_palloc(cf->pool, sizeof(ngx_http_sub_tables_t));
+        if (conf->tables == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        ngx_http_sub_init_tables(conf->tables, conf->matches->elts,
+                                 conf->matches->nelts);
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static void
+ngx_http_sub_init_tables(ngx_http_sub_tables_t *tables,
+    ngx_http_sub_match_t *match, ngx_uint_t n)
+{
+    u_char      c;
+    ngx_uint_t  i, j, min, max, ch;
+
+    min = match[0].match.len;
+    max = match[0].match.len;
+
+    for (i = 1; i < n; i++) {
+        min = ngx_min(min, match[i].match.len);
+        max = ngx_max(max, match[i].match.len);
+    }
+
+    tables->min_match_len = min;
+    tables->max_match_len = max;
+
+    ngx_http_sub_cmp_index = tables->min_match_len - 1;
+    ngx_sort(match, n, sizeof(ngx_http_sub_match_t), ngx_http_sub_cmp_matches);
+
+    min = ngx_min(min, 255);
+    ngx_memset(tables->shift, min, 256);
+
+    ch = 0;
+
+    for (i = 0; i < n; i++) {
+
+        for (j = 0; j < min; j++) {
+            c = match[i].match.data[tables->min_match_len - 1 - j];
+            tables->shift[c] = ngx_min(tables->shift[c], (u_char) j);
+        }
+
+        c = match[i].match.data[tables->min_match_len - 1];
+        while (ch <= (ngx_uint_t) c) {
+            tables->index[ch++] = (u_char) i;
+        }
+    }
+
+    while (ch < 257) {
+        tables->index[ch++] = (u_char) n;
+    }
+}
+
+
+static ngx_int_t
+ngx_http_sub_cmp_matches(const void *one, const void *two)
+{
+    ngx_int_t              c1, c2;
+    ngx_http_sub_match_t  *first, *second;
+
+    first = (ngx_http_sub_match_t *) one;
+    second = (ngx_http_sub_match_t *) two;
+
+    c1 = first->match.data[ngx_http_sub_cmp_index];
+    c2 = second->match.data[ngx_http_sub_cmp_index];
+
+    return c1 - c2;
+}
+
+
+static ngx_int_t
+ngx_http_sub_filter_init(ngx_conf_t *cf)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_sub_header_filter;
+
+    ngx_http_next_body_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_sub_body_filter;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_try_files_module.c b/nginx/src/http/modules/ngx_http_try_files_module.c
new file mode 100644 (file)
index 0000000..ce783a2
--- /dev/null
@@ -0,0 +1,404 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_array_t           *lengths;
+    ngx_array_t           *values;
+    ngx_str_t              name;
+
+    unsigned               code:10;
+    unsigned               test_dir:1;
+} ngx_http_try_file_t;
+
+
+typedef struct {
+    ngx_http_try_file_t   *try_files;
+} ngx_http_try_files_loc_conf_t;
+
+
+static ngx_int_t ngx_http_try_files_handler(ngx_http_request_t *r);
+static char *ngx_http_try_files(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static void *ngx_http_try_files_create_loc_conf(ngx_conf_t *cf);
+static ngx_int_t ngx_http_try_files_init(ngx_conf_t *cf);
+
+
+static ngx_command_t  ngx_http_try_files_commands[] = {
+
+    { ngx_string("try_files"),
+      NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_2MORE,
+      ngx_http_try_files,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_try_files_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_try_files_init,               /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_try_files_create_loc_conf,    /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_try_files_module = {
+    NGX_MODULE_V1,
+    &ngx_http_try_files_module_ctx,        /* module context */
+    ngx_http_try_files_commands,           /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_try_files_handler(ngx_http_request_t *r)
+{
+    size_t                          len, root, alias, reserve, allocated;
+    u_char                         *p, *name;
+    ngx_str_t                       path, args;
+    ngx_uint_t                      test_dir;
+    ngx_http_try_file_t            *tf;
+    ngx_open_file_info_t            of;
+    ngx_http_script_code_pt         code;
+    ngx_http_script_engine_t        e;
+    ngx_http_core_loc_conf_t       *clcf;
+    ngx_http_script_len_code_pt     lcode;
+    ngx_http_try_files_loc_conf_t  *tlcf;
+
+    tlcf = ngx_http_get_module_loc_conf(r, ngx_http_try_files_module);
+
+    if (tlcf->try_files == NULL) {
+        return NGX_DECLINED;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "try files handler");
+
+    allocated = 0;
+    root = 0;
+    name = NULL;
+    /* suppress MSVC warning */
+    path.data = NULL;
+
+    tf = tlcf->try_files;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    alias = clcf->alias;
+
+    for ( ;; ) {
+
+        if (tf->lengths) {
+            ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
+
+            e.ip = tf->lengths->elts;
+            e.request = r;
+
+            /* 1 is for terminating '\0' as in static names */
+            len = 1;
+
+            while (*(uintptr_t *) e.ip) {
+                lcode = *(ngx_http_script_len_code_pt *) e.ip;
+                len += lcode(&e);
+            }
+
+        } else {
+            len = tf->name.len;
+        }
+
+        if (!alias) {
+            reserve = len > r->uri.len ? len - r->uri.len : 0;
+
+        } else if (alias == NGX_MAX_SIZE_T_VALUE) {
+            reserve = len;
+
+        } else {
+            reserve = len > r->uri.len - alias ? len - (r->uri.len - alias) : 0;
+        }
+
+        if (reserve > allocated || !allocated) {
+
+            /* 16 bytes are preallocation */
+            allocated = reserve + 16;
+
+            if (ngx_http_map_uri_to_path(r, &path, &root, allocated) == NULL) {
+                return NGX_HTTP_INTERNAL_SERVER_ERROR;
+            }
+
+            name = path.data + root;
+        }
+
+        if (tf->values == NULL) {
+
+            /* tf->name.len includes the terminating '\0' */
+
+            ngx_memcpy(name, tf->name.data, tf->name.len);
+
+            path.len = (name + tf->name.len - 1) - path.data;
+
+        } else {
+            e.ip = tf->values->elts;
+            e.pos = name;
+            e.flushed = 1;
+
+            while (*(uintptr_t *) e.ip) {
+                code = *(ngx_http_script_code_pt *) e.ip;
+                code((ngx_http_script_engine_t *) &e);
+            }
+
+            path.len = e.pos - path.data;
+
+            *e.pos = '\0';
+
+            if (alias && alias != NGX_MAX_SIZE_T_VALUE
+                && ngx_strncmp(name, r->uri.data, alias) == 0)
+            {
+                ngx_memmove(name, name + alias, len - alias);
+                path.len -= alias;
+            }
+        }
+
+        test_dir = tf->test_dir;
+
+        tf++;
+
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "trying to use %s: \"%s\" \"%s\"",
+                       test_dir ? "dir" : "file", name, path.data);
+
+        if (tf->lengths == NULL && tf->name.len == 0) {
+
+            if (tf->code) {
+                return tf->code;
+            }
+
+            path.len -= root;
+            path.data += root;
+
+            if (path.data[0] == '@') {
+                (void) ngx_http_named_location(r, &path);
+
+            } else {
+                ngx_http_split_args(r, &path, &args);
+
+                (void) ngx_http_internal_redirect(r, &path, &args);
+            }
+
+            ngx_http_finalize_request(r, NGX_DONE);
+            return NGX_DONE;
+        }
+
+        ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+        of.read_ahead = clcf->read_ahead;
+        of.directio = clcf->directio;
+        of.valid = clcf->open_file_cache_valid;
+        of.min_uses = clcf->open_file_cache_min_uses;
+        of.test_only = 1;
+        of.errors = clcf->open_file_cache_errors;
+        of.events = clcf->open_file_cache_events;
+
+        if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
+            != NGX_OK)
+        {
+            if (of.err == 0) {
+                return NGX_HTTP_INTERNAL_SERVER_ERROR;
+            }
+
+            if (of.err != NGX_ENOENT
+                && of.err != NGX_ENOTDIR
+                && of.err != NGX_ENAMETOOLONG)
+            {
+                ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err,
+                              "%s \"%s\" failed", of.failed, path.data);
+            }
+
+            continue;
+        }
+
+        if (of.is_dir != test_dir) {
+            continue;
+        }
+
+        path.len -= root;
+        path.data += root;
+
+        if (!alias) {
+            r->uri = path;
+
+        } else if (alias == NGX_MAX_SIZE_T_VALUE) {
+            if (!test_dir) {
+                r->uri = path;
+                r->add_uri_to_alias = 1;
+            }
+
+        } else {
+            name = r->uri.data;
+
+            r->uri.len = alias + path.len;
+            r->uri.data = ngx_pnalloc(r->pool, r->uri.len);
+            if (r->uri.data == NULL) {
+                r->uri.len = 0;
+                return NGX_HTTP_INTERNAL_SERVER_ERROR;
+            }
+
+            p = ngx_copy(r->uri.data, name, alias);
+            ngx_memcpy(p, path.data, path.len);
+        }
+
+        ngx_http_set_exten(r);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "try file uri: \"%V\"", &r->uri);
+
+        return NGX_DECLINED;
+    }
+
+    /* not reached */
+}
+
+
+static char *
+ngx_http_try_files(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_try_files_loc_conf_t *tlcf = conf;
+
+    ngx_str_t                  *value;
+    ngx_int_t                   code;
+    ngx_uint_t                  i, n;
+    ngx_http_try_file_t        *tf;
+    ngx_http_script_compile_t   sc;
+
+    if (tlcf->try_files) {
+        return "is duplicate";
+    }
+
+    tf = ngx_pcalloc(cf->pool, cf->args->nelts * sizeof(ngx_http_try_file_t));
+    if (tf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    tlcf->try_files = tf;
+
+    value = cf->args->elts;
+
+    for (i = 0; i < cf->args->nelts - 1; i++) {
+
+        tf[i].name = value[i + 1];
+
+        if (tf[i].name.len > 0
+            && tf[i].name.data[tf[i].name.len - 1] == '/'
+            && i + 2 < cf->args->nelts)
+        {
+            tf[i].test_dir = 1;
+            tf[i].name.len--;
+            tf[i].name.data[tf[i].name.len] = '\0';
+        }
+
+        n = ngx_http_script_variables_count(&tf[i].name);
+
+        if (n) {
+            ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+            sc.cf = cf;
+            sc.source = &tf[i].name;
+            sc.lengths = &tf[i].lengths;
+            sc.values = &tf[i].values;
+            sc.variables = n;
+            sc.complete_lengths = 1;
+            sc.complete_values = 1;
+
+            if (ngx_http_script_compile(&sc) != NGX_OK) {
+                return NGX_CONF_ERROR;
+            }
+
+        } else {
+            /* add trailing '\0' to length */
+            tf[i].name.len++;
+        }
+    }
+
+    if (tf[i - 1].name.data[0] == '=') {
+
+        code = ngx_atoi(tf[i - 1].name.data + 1, tf[i - 1].name.len - 2);
+
+        if (code == NGX_ERROR || code > 999) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid code \"%*s\"",
+                               tf[i - 1].name.len - 1, tf[i - 1].name.data);
+            return NGX_CONF_ERROR;
+        }
+
+        tf[i].code = code;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_http_try_files_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_try_files_loc_conf_t  *tlcf;
+
+    tlcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_try_files_loc_conf_t));
+    if (tlcf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     tlcf->try_files = NULL;
+     */
+
+    return tlcf;
+}
+
+
+static ngx_int_t
+ngx_http_try_files_init(ngx_conf_t *cf)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_PRECONTENT_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_try_files_handler;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_upstream_hash_module.c b/nginx/src/http/modules/ngx_http_upstream_hash_module.c
new file mode 100644 (file)
index 0000000..d67f34d
--- /dev/null
@@ -0,0 +1,680 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    uint32_t                            hash;
+    ngx_str_t                          *server;
+} ngx_http_upstream_chash_point_t;
+
+
+typedef struct {
+    ngx_uint_t                          number;
+    ngx_http_upstream_chash_point_t     point[1];
+} ngx_http_upstream_chash_points_t;
+
+
+typedef struct {
+    ngx_http_complex_value_t            key;
+    ngx_http_upstream_chash_points_t   *points;
+} ngx_http_upstream_hash_srv_conf_t;
+
+
+typedef struct {
+    /* the round robin data must be first */
+    ngx_http_upstream_rr_peer_data_t    rrp;
+    ngx_http_upstream_hash_srv_conf_t  *conf;
+    ngx_str_t                           key;
+    ngx_uint_t                          tries;
+    ngx_uint_t                          rehash;
+    uint32_t                            hash;
+    ngx_event_get_peer_pt               get_rr_peer;
+} ngx_http_upstream_hash_peer_data_t;
+
+
+static ngx_int_t ngx_http_upstream_init_hash(ngx_conf_t *cf,
+    ngx_http_upstream_srv_conf_t *us);
+static ngx_int_t ngx_http_upstream_init_hash_peer(ngx_http_request_t *r,
+    ngx_http_upstream_srv_conf_t *us);
+static ngx_int_t ngx_http_upstream_get_hash_peer(ngx_peer_connection_t *pc,
+    void *data);
+
+static ngx_int_t ngx_http_upstream_init_chash(ngx_conf_t *cf,
+    ngx_http_upstream_srv_conf_t *us);
+static int ngx_libc_cdecl
+    ngx_http_upstream_chash_cmp_points(const void *one, const void *two);
+static ngx_uint_t ngx_http_upstream_find_chash_point(
+    ngx_http_upstream_chash_points_t *points, uint32_t hash);
+static ngx_int_t ngx_http_upstream_init_chash_peer(ngx_http_request_t *r,
+    ngx_http_upstream_srv_conf_t *us);
+static ngx_int_t ngx_http_upstream_get_chash_peer(ngx_peer_connection_t *pc,
+    void *data);
+
+static void *ngx_http_upstream_hash_create_conf(ngx_conf_t *cf);
+static char *ngx_http_upstream_hash(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+
+static ngx_command_t  ngx_http_upstream_hash_commands[] = {
+
+    { ngx_string("hash"),
+      NGX_HTTP_UPS_CONF|NGX_CONF_TAKE12,
+      ngx_http_upstream_hash,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_upstream_hash_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_http_upstream_hash_create_conf,    /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_upstream_hash_module = {
+    NGX_MODULE_V1,
+    &ngx_http_upstream_hash_module_ctx,    /* module context */
+    ngx_http_upstream_hash_commands,       /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_upstream_init_hash(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us)
+{
+    if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    us->peer.init = ngx_http_upstream_init_hash_peer;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_init_hash_peer(ngx_http_request_t *r,
+    ngx_http_upstream_srv_conf_t *us)
+{
+    ngx_http_upstream_hash_srv_conf_t   *hcf;
+    ngx_http_upstream_hash_peer_data_t  *hp;
+
+    hp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_hash_peer_data_t));
+    if (hp == NULL) {
+        return NGX_ERROR;
+    }
+
+    r->upstream->peer.data = &hp->rrp;
+
+    if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    r->upstream->peer.get = ngx_http_upstream_get_hash_peer;
+
+    hcf = ngx_http_conf_upstream_srv_conf(us, ngx_http_upstream_hash_module);
+
+    if (ngx_http_complex_value(r, &hcf->key, &hp->key) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "upstream hash key:\"%V\"", &hp->key);
+
+    hp->conf = hcf;
+    hp->tries = 0;
+    hp->rehash = 0;
+    hp->hash = 0;
+    hp->get_rr_peer = ngx_http_upstream_get_round_robin_peer;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_get_hash_peer(ngx_peer_connection_t *pc, void *data)
+{
+    ngx_http_upstream_hash_peer_data_t  *hp = data;
+
+    time_t                        now;
+    u_char                        buf[NGX_INT_T_LEN];
+    size_t                        size;
+    uint32_t                      hash;
+    ngx_int_t                     w;
+    uintptr_t                     m;
+    ngx_uint_t                    n, p;
+    ngx_http_upstream_rr_peer_t  *peer;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                   "get hash peer, try: %ui", pc->tries);
+
+    ngx_http_upstream_rr_peers_wlock(hp->rrp.peers);
+
+    if (hp->tries > 20 || hp->rrp.peers->single) {
+        ngx_http_upstream_rr_peers_unlock(hp->rrp.peers);
+        return hp->get_rr_peer(pc, &hp->rrp);
+    }
+
+    now = ngx_time();
+
+    pc->cached = 0;
+    pc->connection = NULL;
+
+    for ( ;; ) {
+
+        /*
+         * Hash expression is compatible with Cache::Memcached:
+         * ((crc32([REHASH] KEY) >> 16) & 0x7fff) + PREV_HASH
+         * with REHASH omitted at the first iteration.
+         */
+
+        ngx_crc32_init(hash);
+
+        if (hp->rehash > 0) {
+            size = ngx_sprintf(buf, "%ui", hp->rehash) - buf;
+            ngx_crc32_update(&hash, buf, size);
+        }
+
+        ngx_crc32_update(&hash, hp->key.data, hp->key.len);
+        ngx_crc32_final(hash);
+
+        hash = (hash >> 16) & 0x7fff;
+
+        hp->hash += hash;
+        hp->rehash++;
+
+        w = hp->hash % hp->rrp.peers->total_weight;
+        peer = hp->rrp.peers->peer;
+        p = 0;
+
+        while (w >= peer->weight) {
+            w -= peer->weight;
+            peer = peer->next;
+            p++;
+        }
+
+        n = p / (8 * sizeof(uintptr_t));
+        m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));
+
+        if (hp->rrp.tried[n] & m) {
+            goto next;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                       "get hash peer, value:%uD, peer:%ui", hp->hash, p);
+
+        if (peer->down) {
+            goto next;
+        }
+
+        if (peer->max_fails
+            && peer->fails >= peer->max_fails
+            && now - peer->checked <= peer->fail_timeout)
+        {
+            goto next;
+        }
+
+        if (peer->max_conns && peer->conns >= peer->max_conns) {
+            goto next;
+        }
+
+        break;
+
+    next:
+
+        if (++hp->tries > 20) {
+            ngx_http_upstream_rr_peers_unlock(hp->rrp.peers);
+            return hp->get_rr_peer(pc, &hp->rrp);
+        }
+    }
+
+    hp->rrp.current = peer;
+
+    pc->sockaddr = peer->sockaddr;
+    pc->socklen = peer->socklen;
+    pc->name = &peer->name;
+
+    peer->conns++;
+
+    if (now - peer->checked > peer->fail_timeout) {
+        peer->checked = now;
+    }
+
+    ngx_http_upstream_rr_peers_unlock(hp->rrp.peers);
+
+    hp->rrp.tried[n] |= m;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_init_chash(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us)
+{
+    u_char                             *host, *port, c;
+    size_t                              host_len, port_len, size;
+    uint32_t                            hash, base_hash;
+    ngx_str_t                          *server;
+    ngx_uint_t                          npoints, i, j;
+    ngx_http_upstream_rr_peer_t        *peer;
+    ngx_http_upstream_rr_peers_t       *peers;
+    ngx_http_upstream_chash_points_t   *points;
+    ngx_http_upstream_hash_srv_conf_t  *hcf;
+    union {
+        uint32_t                        value;
+        u_char                          byte[4];
+    } prev_hash;
+
+    if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    us->peer.init = ngx_http_upstream_init_chash_peer;
+
+    peers = us->peer.data;
+    npoints = peers->total_weight * 160;
+
+    size = sizeof(ngx_http_upstream_chash_points_t)
+           + sizeof(ngx_http_upstream_chash_point_t) * (npoints - 1);
+
+    points = ngx_palloc(cf->pool, size);
+    if (points == NULL) {
+        return NGX_ERROR;
+    }
+
+    points->number = 0;
+
+    for (peer = peers->peer; peer; peer = peer->next) {
+        server = &peer->server;
+
+        /*
+         * Hash expression is compatible with Cache::Memcached::Fast:
+         * crc32(HOST \0 PORT PREV_HASH).
+         */
+
+        if (server->len >= 5
+            && ngx_strncasecmp(server->data, (u_char *) "unix:", 5) == 0)
+        {
+            host = server->data + 5;
+            host_len = server->len - 5;
+            port = NULL;
+            port_len = 0;
+            goto done;
+        }
+
+        for (j = 0; j < server->len; j++) {
+            c = server->data[server->len - j - 1];
+
+            if (c == ':') {
+                host = server->data;
+                host_len = server->len - j - 1;
+                port = server->data + server->len - j;
+                port_len = j;
+                goto done;
+            }
+
+            if (c < '0' || c > '9') {
+                break;
+            }
+        }
+
+        host = server->data;
+        host_len = server->len;
+        port = NULL;
+        port_len = 0;
+
+    done:
+
+        ngx_crc32_init(base_hash);
+        ngx_crc32_update(&base_hash, host, host_len);
+        ngx_crc32_update(&base_hash, (u_char *) "", 1);
+        ngx_crc32_update(&base_hash, port, port_len);
+
+        prev_hash.value = 0;
+        npoints = peer->weight * 160;
+
+        for (j = 0; j < npoints; j++) {
+            hash = base_hash;
+
+            ngx_crc32_update(&hash, prev_hash.byte, 4);
+            ngx_crc32_final(hash);
+
+            points->point[points->number].hash = hash;
+            points->point[points->number].server = server;
+            points->number++;
+
+#if (NGX_HAVE_LITTLE_ENDIAN)
+            prev_hash.value = hash;
+#else
+            prev_hash.byte[0] = (u_char) (hash & 0xff);
+            prev_hash.byte[1] = (u_char) ((hash >> 8) & 0xff);
+            prev_hash.byte[2] = (u_char) ((hash >> 16) & 0xff);
+            prev_hash.byte[3] = (u_char) ((hash >> 24) & 0xff);
+#endif
+        }
+    }
+
+    ngx_qsort(points->point,
+              points->number,
+              sizeof(ngx_http_upstream_chash_point_t),
+              ngx_http_upstream_chash_cmp_points);
+
+    for (i = 0, j = 1; j < points->number; j++) {
+        if (points->point[i].hash != points->point[j].hash) {
+            points->point[++i] = points->point[j];
+        }
+    }
+
+    points->number = i + 1;
+
+    hcf = ngx_http_conf_upstream_srv_conf(us, ngx_http_upstream_hash_module);
+    hcf->points = points;
+
+    return NGX_OK;
+}
+
+
+static int ngx_libc_cdecl
+ngx_http_upstream_chash_cmp_points(const void *one, const void *two)
+{
+    ngx_http_upstream_chash_point_t *first =
+                                       (ngx_http_upstream_chash_point_t *) one;
+    ngx_http_upstream_chash_point_t *second =
+                                       (ngx_http_upstream_chash_point_t *) two;
+
+    if (first->hash < second->hash) {
+        return -1;
+
+    } else if (first->hash > second->hash) {
+        return 1;
+
+    } else {
+        return 0;
+    }
+}
+
+
+static ngx_uint_t
+ngx_http_upstream_find_chash_point(ngx_http_upstream_chash_points_t *points,
+    uint32_t hash)
+{
+    ngx_uint_t                        i, j, k;
+    ngx_http_upstream_chash_point_t  *point;
+
+    /* find first point >= hash */
+
+    point = &points->point[0];
+
+    i = 0;
+    j = points->number;
+
+    while (i < j) {
+        k = (i + j) / 2;
+
+        if (hash > point[k].hash) {
+            i = k + 1;
+
+        } else if (hash < point[k].hash) {
+            j = k;
+
+        } else {
+            return k;
+        }
+    }
+
+    return i;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_init_chash_peer(ngx_http_request_t *r,
+    ngx_http_upstream_srv_conf_t *us)
+{
+    uint32_t                             hash;
+    ngx_http_upstream_hash_srv_conf_t   *hcf;
+    ngx_http_upstream_hash_peer_data_t  *hp;
+
+    if (ngx_http_upstream_init_hash_peer(r, us) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    r->upstream->peer.get = ngx_http_upstream_get_chash_peer;
+
+    hp = r->upstream->peer.data;
+    hcf = ngx_http_conf_upstream_srv_conf(us, ngx_http_upstream_hash_module);
+
+    hash = ngx_crc32_long(hp->key.data, hp->key.len);
+
+    ngx_http_upstream_rr_peers_rlock(hp->rrp.peers);
+
+    hp->hash = ngx_http_upstream_find_chash_point(hcf->points, hash);
+
+    ngx_http_upstream_rr_peers_unlock(hp->rrp.peers);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_get_chash_peer(ngx_peer_connection_t *pc, void *data)
+{
+    ngx_http_upstream_hash_peer_data_t  *hp = data;
+
+    time_t                              now;
+    intptr_t                            m;
+    ngx_str_t                          *server;
+    ngx_int_t                           total;
+    ngx_uint_t                          i, n, best_i;
+    ngx_http_upstream_rr_peer_t        *peer, *best;
+    ngx_http_upstream_chash_point_t    *point;
+    ngx_http_upstream_chash_points_t   *points;
+    ngx_http_upstream_hash_srv_conf_t  *hcf;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                   "get consistent hash peer, try: %ui", pc->tries);
+
+    ngx_http_upstream_rr_peers_wlock(hp->rrp.peers);
+
+    if (hp->tries > 20 || hp->rrp.peers->single) {
+        ngx_http_upstream_rr_peers_unlock(hp->rrp.peers);
+        return hp->get_rr_peer(pc, &hp->rrp);
+    }
+
+    pc->cached = 0;
+    pc->connection = NULL;
+
+    now = ngx_time();
+    hcf = hp->conf;
+
+    points = hcf->points;
+    point = &points->point[0];
+
+    for ( ;; ) {
+        server = point[hp->hash % points->number].server;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                       "consistent hash peer:%uD, server:\"%V\"",
+                       hp->hash, server);
+
+        best = NULL;
+        best_i = 0;
+        total = 0;
+
+        for (peer = hp->rrp.peers->peer, i = 0;
+             peer;
+             peer = peer->next, i++)
+        {
+            n = i / (8 * sizeof(uintptr_t));
+            m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));
+
+            if (hp->rrp.tried[n] & m) {
+                continue;
+            }
+
+            if (peer->down) {
+                continue;
+            }
+
+            if (peer->max_fails
+                && peer->fails >= peer->max_fails
+                && now - peer->checked <= peer->fail_timeout)
+            {
+                continue;
+            }
+
+            if (peer->max_conns && peer->conns >= peer->max_conns) {
+                continue;
+            }
+
+            if (peer->server.len != server->len
+                || ngx_strncmp(peer->server.data, server->data, server->len)
+                   != 0)
+            {
+                continue;
+            }
+
+            peer->current_weight += peer->effective_weight;
+            total += peer->effective_weight;
+
+            if (peer->effective_weight < peer->weight) {
+                peer->effective_weight++;
+            }
+
+            if (best == NULL || peer->current_weight > best->current_weight) {
+                best = peer;
+                best_i = i;
+            }
+        }
+
+        if (best) {
+            best->current_weight -= total;
+            goto found;
+        }
+
+        hp->hash++;
+        hp->tries++;
+
+        if (hp->tries > 20) {
+            ngx_http_upstream_rr_peers_unlock(hp->rrp.peers);
+            return hp->get_rr_peer(pc, &hp->rrp);
+        }
+    }
+
+found:
+
+    hp->rrp.current = best;
+
+    pc->sockaddr = best->sockaddr;
+    pc->socklen = best->socklen;
+    pc->name = &best->name;
+
+    best->conns++;
+
+    if (now - best->checked > best->fail_timeout) {
+        best->checked = now;
+    }
+
+    ngx_http_upstream_rr_peers_unlock(hp->rrp.peers);
+
+    n = best_i / (8 * sizeof(uintptr_t));
+    m = (uintptr_t) 1 << best_i % (8 * sizeof(uintptr_t));
+
+    hp->rrp.tried[n] |= m;
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_upstream_hash_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_upstream_hash_srv_conf_t  *conf;
+
+    conf = ngx_palloc(cf->pool, sizeof(ngx_http_upstream_hash_srv_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    conf->points = NULL;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_upstream_hash(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_upstream_hash_srv_conf_t  *hcf = conf;
+
+    ngx_str_t                         *value;
+    ngx_http_upstream_srv_conf_t      *uscf;
+    ngx_http_compile_complex_value_t   ccv;
+
+    value = cf->args->elts;
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[1];
+    ccv.complex_value = &hcf->key;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
+
+    if (uscf->peer.init_upstream) {
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                           "load balancing method redefined");
+    }
+
+    uscf->flags = NGX_HTTP_UPSTREAM_CREATE
+                  |NGX_HTTP_UPSTREAM_WEIGHT
+                  |NGX_HTTP_UPSTREAM_MAX_CONNS
+                  |NGX_HTTP_UPSTREAM_MAX_FAILS
+                  |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
+                  |NGX_HTTP_UPSTREAM_DOWN;
+
+    if (cf->args->nelts == 2) {
+        uscf->peer.init_upstream = ngx_http_upstream_init_hash;
+
+    } else if (ngx_strcmp(value[2].data, "consistent") == 0) {
+        uscf->peer.init_upstream = ngx_http_upstream_init_chash;
+
+    } else {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[2]);
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_upstream_ip_hash_module.c b/nginx/src/http/modules/ngx_http_upstream_ip_hash_module.c
new file mode 100644 (file)
index 0000000..296108f
--- /dev/null
@@ -0,0 +1,272 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    /* the round robin data must be first */
+    ngx_http_upstream_rr_peer_data_t   rrp;
+
+    ngx_uint_t                         hash;
+
+    u_char                             addrlen;
+    u_char                            *addr;
+
+    u_char                             tries;
+
+    ngx_event_get_peer_pt              get_rr_peer;
+} ngx_http_upstream_ip_hash_peer_data_t;
+
+
+static ngx_int_t ngx_http_upstream_init_ip_hash_peer(ngx_http_request_t *r,
+    ngx_http_upstream_srv_conf_t *us);
+static ngx_int_t ngx_http_upstream_get_ip_hash_peer(ngx_peer_connection_t *pc,
+    void *data);
+static char *ngx_http_upstream_ip_hash(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+
+static ngx_command_t  ngx_http_upstream_ip_hash_commands[] = {
+
+    { ngx_string("ip_hash"),
+      NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS,
+      ngx_http_upstream_ip_hash,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_upstream_ip_hash_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_upstream_ip_hash_module = {
+    NGX_MODULE_V1,
+    &ngx_http_upstream_ip_hash_module_ctx, /* module context */
+    ngx_http_upstream_ip_hash_commands,    /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static u_char ngx_http_upstream_ip_hash_pseudo_addr[3];
+
+
+static ngx_int_t
+ngx_http_upstream_init_ip_hash(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us)
+{
+    if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    us->peer.init = ngx_http_upstream_init_ip_hash_peer;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_init_ip_hash_peer(ngx_http_request_t *r,
+    ngx_http_upstream_srv_conf_t *us)
+{
+    struct sockaddr_in                     *sin;
+#if (NGX_HAVE_INET6)
+    struct sockaddr_in6                    *sin6;
+#endif
+    ngx_http_upstream_ip_hash_peer_data_t  *iphp;
+
+    iphp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_ip_hash_peer_data_t));
+    if (iphp == NULL) {
+        return NGX_ERROR;
+    }
+
+    r->upstream->peer.data = &iphp->rrp;
+
+    if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    r->upstream->peer.get = ngx_http_upstream_get_ip_hash_peer;
+
+    switch (r->connection->sockaddr->sa_family) {
+
+    case AF_INET:
+        sin = (struct sockaddr_in *) r->connection->sockaddr;
+        iphp->addr = (u_char *) &sin->sin_addr.s_addr;
+        iphp->addrlen = 3;
+        break;
+
+#if (NGX_HAVE_INET6)
+    case AF_INET6:
+        sin6 = (struct sockaddr_in6 *) r->connection->sockaddr;
+        iphp->addr = (u_char *) &sin6->sin6_addr.s6_addr;
+        iphp->addrlen = 16;
+        break;
+#endif
+
+    default:
+        iphp->addr = ngx_http_upstream_ip_hash_pseudo_addr;
+        iphp->addrlen = 3;
+    }
+
+    iphp->hash = 89;
+    iphp->tries = 0;
+    iphp->get_rr_peer = ngx_http_upstream_get_round_robin_peer;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_get_ip_hash_peer(ngx_peer_connection_t *pc, void *data)
+{
+    ngx_http_upstream_ip_hash_peer_data_t  *iphp = data;
+
+    time_t                        now;
+    ngx_int_t                     w;
+    uintptr_t                     m;
+    ngx_uint_t                    i, n, p, hash;
+    ngx_http_upstream_rr_peer_t  *peer;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                   "get ip hash peer, try: %ui", pc->tries);
+
+    /* TODO: cached */
+
+    ngx_http_upstream_rr_peers_wlock(iphp->rrp.peers);
+
+    if (iphp->tries > 20 || iphp->rrp.peers->single) {
+        ngx_http_upstream_rr_peers_unlock(iphp->rrp.peers);
+        return iphp->get_rr_peer(pc, &iphp->rrp);
+    }
+
+    now = ngx_time();
+
+    pc->cached = 0;
+    pc->connection = NULL;
+
+    hash = iphp->hash;
+
+    for ( ;; ) {
+
+        for (i = 0; i < (ngx_uint_t) iphp->addrlen; i++) {
+            hash = (hash * 113 + iphp->addr[i]) % 6271;
+        }
+
+        w = hash % iphp->rrp.peers->total_weight;
+        peer = iphp->rrp.peers->peer;
+        p = 0;
+
+        while (w >= peer->weight) {
+            w -= peer->weight;
+            peer = peer->next;
+            p++;
+        }
+
+        n = p / (8 * sizeof(uintptr_t));
+        m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));
+
+        if (iphp->rrp.tried[n] & m) {
+            goto next;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                       "get ip hash peer, hash: %ui %04XL", p, (uint64_t) m);
+
+        if (peer->down) {
+            goto next;
+        }
+
+        if (peer->max_fails
+            && peer->fails >= peer->max_fails
+            && now - peer->checked <= peer->fail_timeout)
+        {
+            goto next;
+        }
+
+        if (peer->max_conns && peer->conns >= peer->max_conns) {
+            goto next;
+        }
+
+        break;
+
+    next:
+
+        if (++iphp->tries > 20) {
+            ngx_http_upstream_rr_peers_unlock(iphp->rrp.peers);
+            return iphp->get_rr_peer(pc, &iphp->rrp);
+        }
+    }
+
+    iphp->rrp.current = peer;
+
+    pc->sockaddr = peer->sockaddr;
+    pc->socklen = peer->socklen;
+    pc->name = &peer->name;
+
+    peer->conns++;
+
+    if (now - peer->checked > peer->fail_timeout) {
+        peer->checked = now;
+    }
+
+    ngx_http_upstream_rr_peers_unlock(iphp->rrp.peers);
+
+    iphp->rrp.tried[n] |= m;
+    iphp->hash = hash;
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_http_upstream_ip_hash(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_upstream_srv_conf_t  *uscf;
+
+    uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
+
+    if (uscf->peer.init_upstream) {
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                           "load balancing method redefined");
+    }
+
+    uscf->peer.init_upstream = ngx_http_upstream_init_ip_hash;
+
+    uscf->flags = NGX_HTTP_UPSTREAM_CREATE
+                  |NGX_HTTP_UPSTREAM_WEIGHT
+                  |NGX_HTTP_UPSTREAM_MAX_CONNS
+                  |NGX_HTTP_UPSTREAM_MAX_FAILS
+                  |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
+                  |NGX_HTTP_UPSTREAM_DOWN;
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_upstream_keepalive_module.c b/nginx/src/http/modules/ngx_http_upstream_keepalive_module.c
new file mode 100644 (file)
index 0000000..2283023
--- /dev/null
@@ -0,0 +1,530 @@
+
+/*
+ * Copyright (C) Maxim Dounin
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_uint_t                         max_cached;
+
+    ngx_queue_t                        cache;
+    ngx_queue_t                        free;
+
+    ngx_http_upstream_init_pt          original_init_upstream;
+    ngx_http_upstream_init_peer_pt     original_init_peer;
+
+} ngx_http_upstream_keepalive_srv_conf_t;
+
+
+typedef struct {
+    ngx_http_upstream_keepalive_srv_conf_t  *conf;
+
+    ngx_queue_t                        queue;
+    ngx_connection_t                  *connection;
+
+    socklen_t                          socklen;
+    ngx_sockaddr_t                     sockaddr;
+
+} ngx_http_upstream_keepalive_cache_t;
+
+
+typedef struct {
+    ngx_http_upstream_keepalive_srv_conf_t  *conf;
+
+    ngx_http_upstream_t               *upstream;
+
+    void                              *data;
+
+    ngx_event_get_peer_pt              original_get_peer;
+    ngx_event_free_peer_pt             original_free_peer;
+
+#if (NGX_HTTP_SSL)
+    ngx_event_set_peer_session_pt      original_set_session;
+    ngx_event_save_peer_session_pt     original_save_session;
+#endif
+
+} ngx_http_upstream_keepalive_peer_data_t;
+
+
+static ngx_int_t ngx_http_upstream_init_keepalive_peer(ngx_http_request_t *r,
+    ngx_http_upstream_srv_conf_t *us);
+static ngx_int_t ngx_http_upstream_get_keepalive_peer(ngx_peer_connection_t *pc,
+    void *data);
+static void ngx_http_upstream_free_keepalive_peer(ngx_peer_connection_t *pc,
+    void *data, ngx_uint_t state);
+
+static void ngx_http_upstream_keepalive_dummy_handler(ngx_event_t *ev);
+static void ngx_http_upstream_keepalive_close_handler(ngx_event_t *ev);
+static void ngx_http_upstream_keepalive_close(ngx_connection_t *c);
+
+#if (NGX_HTTP_SSL)
+static ngx_int_t ngx_http_upstream_keepalive_set_session(
+    ngx_peer_connection_t *pc, void *data);
+static void ngx_http_upstream_keepalive_save_session(ngx_peer_connection_t *pc,
+    void *data);
+#endif
+
+static void *ngx_http_upstream_keepalive_create_conf(ngx_conf_t *cf);
+static char *ngx_http_upstream_keepalive(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+
+static ngx_command_t  ngx_http_upstream_keepalive_commands[] = {
+
+    { ngx_string("keepalive"),
+      NGX_HTTP_UPS_CONF|NGX_CONF_TAKE1,
+      ngx_http_upstream_keepalive,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_upstream_keepalive_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_http_upstream_keepalive_create_conf, /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_upstream_keepalive_module = {
+    NGX_MODULE_V1,
+    &ngx_http_upstream_keepalive_module_ctx, /* module context */
+    ngx_http_upstream_keepalive_commands,    /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_upstream_init_keepalive(ngx_conf_t *cf,
+    ngx_http_upstream_srv_conf_t *us)
+{
+    ngx_uint_t                               i;
+    ngx_http_upstream_keepalive_srv_conf_t  *kcf;
+    ngx_http_upstream_keepalive_cache_t     *cached;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0,
+                   "init keepalive");
+
+    kcf = ngx_http_conf_upstream_srv_conf(us,
+                                          ngx_http_upstream_keepalive_module);
+
+    if (kcf->original_init_upstream(cf, us) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    kcf->original_init_peer = us->peer.init;
+
+    us->peer.init = ngx_http_upstream_init_keepalive_peer;
+
+    /* allocate cache items and add to free queue */
+
+    cached = ngx_pcalloc(cf->pool,
+                sizeof(ngx_http_upstream_keepalive_cache_t) * kcf->max_cached);
+    if (cached == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_queue_init(&kcf->cache);
+    ngx_queue_init(&kcf->free);
+
+    for (i = 0; i < kcf->max_cached; i++) {
+        ngx_queue_insert_head(&kcf->free, &cached[i].queue);
+        cached[i].conf = kcf;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_init_keepalive_peer(ngx_http_request_t *r,
+    ngx_http_upstream_srv_conf_t *us)
+{
+    ngx_http_upstream_keepalive_peer_data_t  *kp;
+    ngx_http_upstream_keepalive_srv_conf_t   *kcf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "init keepalive peer");
+
+    kcf = ngx_http_conf_upstream_srv_conf(us,
+                                          ngx_http_upstream_keepalive_module);
+
+    kp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_keepalive_peer_data_t));
+    if (kp == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (kcf->original_init_peer(r, us) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    kp->conf = kcf;
+    kp->upstream = r->upstream;
+    kp->data = r->upstream->peer.data;
+    kp->original_get_peer = r->upstream->peer.get;
+    kp->original_free_peer = r->upstream->peer.free;
+
+    r->upstream->peer.data = kp;
+    r->upstream->peer.get = ngx_http_upstream_get_keepalive_peer;
+    r->upstream->peer.free = ngx_http_upstream_free_keepalive_peer;
+
+#if (NGX_HTTP_SSL)
+    kp->original_set_session = r->upstream->peer.set_session;
+    kp->original_save_session = r->upstream->peer.save_session;
+    r->upstream->peer.set_session = ngx_http_upstream_keepalive_set_session;
+    r->upstream->peer.save_session = ngx_http_upstream_keepalive_save_session;
+#endif
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_get_keepalive_peer(ngx_peer_connection_t *pc, void *data)
+{
+    ngx_http_upstream_keepalive_peer_data_t  *kp = data;
+    ngx_http_upstream_keepalive_cache_t      *item;
+
+    ngx_int_t          rc;
+    ngx_queue_t       *q, *cache;
+    ngx_connection_t  *c;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                   "get keepalive peer");
+
+    /* ask balancer */
+
+    rc = kp->original_get_peer(pc, kp->data);
+
+    if (rc != NGX_OK) {
+        return rc;
+    }
+
+    /* search cache for suitable connection */
+
+    cache = &kp->conf->cache;
+
+    for (q = ngx_queue_head(cache);
+         q != ngx_queue_sentinel(cache);
+         q = ngx_queue_next(q))
+    {
+        item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue);
+        c = item->connection;
+
+        if (ngx_memn2cmp((u_char *) &item->sockaddr, (u_char *) pc->sockaddr,
+                         item->socklen, pc->socklen)
+            == 0)
+        {
+            ngx_queue_remove(q);
+            ngx_queue_insert_head(&kp->conf->free, q);
+
+            goto found;
+        }
+    }
+
+    return NGX_OK;
+
+found:
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                   "get keepalive peer: using connection %p", c);
+
+    c->idle = 0;
+    c->sent = 0;
+    c->log = pc->log;
+    c->read->log = pc->log;
+    c->write->log = pc->log;
+    c->pool->log = pc->log;
+
+    pc->connection = c;
+    pc->cached = 1;
+
+    return NGX_DONE;
+}
+
+
+static void
+ngx_http_upstream_free_keepalive_peer(ngx_peer_connection_t *pc, void *data,
+    ngx_uint_t state)
+{
+    ngx_http_upstream_keepalive_peer_data_t  *kp = data;
+    ngx_http_upstream_keepalive_cache_t      *item;
+
+    ngx_queue_t          *q;
+    ngx_connection_t     *c;
+    ngx_http_upstream_t  *u;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                   "free keepalive peer");
+
+    /* cache valid connections */
+
+    u = kp->upstream;
+    c = pc->connection;
+
+    if (state & NGX_PEER_FAILED
+        || c == NULL
+        || c->read->eof
+        || c->read->error
+        || c->read->timedout
+        || c->write->error
+        || c->write->timedout)
+    {
+        goto invalid;
+    }
+
+    if (!u->keepalive) {
+        goto invalid;
+    }
+
+    if (!u->request_body_sent) {
+        goto invalid;
+    }
+
+    if (ngx_terminate || ngx_exiting) {
+        goto invalid;
+    }
+
+    if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+        goto invalid;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                   "free keepalive peer: saving connection %p", c);
+
+    if (ngx_queue_empty(&kp->conf->free)) {
+
+        q = ngx_queue_last(&kp->conf->cache);
+        ngx_queue_remove(q);
+
+        item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue);
+
+        ngx_http_upstream_keepalive_close(item->connection);
+
+    } else {
+        q = ngx_queue_head(&kp->conf->free);
+        ngx_queue_remove(q);
+
+        item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue);
+    }
+
+    ngx_queue_insert_head(&kp->conf->cache, q);
+
+    item->connection = c;
+
+    pc->connection = NULL;
+
+    if (c->read->timer_set) {
+        c->read->delayed = 0;
+        ngx_del_timer(c->read);
+    }
+    if (c->write->timer_set) {
+        ngx_del_timer(c->write);
+    }
+
+    c->write->handler = ngx_http_upstream_keepalive_dummy_handler;
+    c->read->handler = ngx_http_upstream_keepalive_close_handler;
+
+    c->data = item;
+    c->idle = 1;
+    c->log = ngx_cycle->log;
+    c->read->log = ngx_cycle->log;
+    c->write->log = ngx_cycle->log;
+    c->pool->log = ngx_cycle->log;
+
+    item->socklen = pc->socklen;
+    ngx_memcpy(&item->sockaddr, pc->sockaddr, pc->socklen);
+
+    if (c->read->ready) {
+        ngx_http_upstream_keepalive_close_handler(c->read);
+    }
+
+invalid:
+
+    kp->original_free_peer(pc, kp->data, state);
+}
+
+
+static void
+ngx_http_upstream_keepalive_dummy_handler(ngx_event_t *ev)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0,
+                   "keepalive dummy handler");
+}
+
+
+static void
+ngx_http_upstream_keepalive_close_handler(ngx_event_t *ev)
+{
+    ngx_http_upstream_keepalive_srv_conf_t  *conf;
+    ngx_http_upstream_keepalive_cache_t     *item;
+
+    int                n;
+    char               buf[1];
+    ngx_connection_t  *c;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0,
+                   "keepalive close handler");
+
+    c = ev->data;
+
+    if (c->close) {
+        goto close;
+    }
+
+    n = ngxvcl_recv(c->fd, buf, 1, MSG_PEEK);
+
+    if (n == -1 && ngx_socket_errno == NGX_EAGAIN) {
+        ev->ready = 0;
+
+        if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+            goto close;
+        }
+
+        return;
+    }
+
+close:
+
+    item = c->data;
+    conf = item->conf;
+
+    ngx_http_upstream_keepalive_close(c);
+
+    ngx_queue_remove(&item->queue);
+    ngx_queue_insert_head(&conf->free, &item->queue);
+}
+
+
+static void
+ngx_http_upstream_keepalive_close(ngx_connection_t *c)
+{
+
+#if (NGX_HTTP_SSL)
+
+    if (c->ssl) {
+        c->ssl->no_wait_shutdown = 1;
+        c->ssl->no_send_shutdown = 1;
+
+        if (ngx_ssl_shutdown(c) == NGX_AGAIN) {
+            c->ssl->handler = ngx_http_upstream_keepalive_close;
+            return;
+        }
+    }
+
+#endif
+
+    ngx_destroy_pool(c->pool);
+    ngx_close_connection(c);
+}
+
+
+#if (NGX_HTTP_SSL)
+
+static ngx_int_t
+ngx_http_upstream_keepalive_set_session(ngx_peer_connection_t *pc, void *data)
+{
+    ngx_http_upstream_keepalive_peer_data_t  *kp = data;
+
+    return kp->original_set_session(pc, kp->data);
+}
+
+
+static void
+ngx_http_upstream_keepalive_save_session(ngx_peer_connection_t *pc, void *data)
+{
+    ngx_http_upstream_keepalive_peer_data_t  *kp = data;
+
+    kp->original_save_session(pc, kp->data);
+    return;
+}
+
+#endif
+
+
+static void *
+ngx_http_upstream_keepalive_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_upstream_keepalive_srv_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool,
+                       sizeof(ngx_http_upstream_keepalive_srv_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->original_init_upstream = NULL;
+     *     conf->original_init_peer = NULL;
+     *     conf->max_cached = 0;
+     */
+
+    return conf;
+}
+
+
+static char *
+ngx_http_upstream_keepalive(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_upstream_srv_conf_t            *uscf;
+    ngx_http_upstream_keepalive_srv_conf_t  *kcf = conf;
+
+    ngx_int_t    n;
+    ngx_str_t   *value;
+
+    if (kcf->max_cached) {
+        return "is duplicate";
+    }
+
+    /* read options */
+
+    value = cf->args->elts;
+
+    n = ngx_atoi(value[1].data, value[1].len);
+
+    if (n == NGX_ERROR || n == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid value \"%V\" in \"%V\" directive",
+                           &value[1], &cmd->name);
+        return NGX_CONF_ERROR;
+    }
+
+    kcf->max_cached = n;
+
+    uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
+
+    kcf->original_init_upstream = uscf->peer.init_upstream
+                                  ? uscf->peer.init_upstream
+                                  : ngx_http_upstream_init_round_robin;
+
+    uscf->peer.init_upstream = ngx_http_upstream_init_keepalive;
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_upstream_least_conn_module.c b/nginx/src/http/modules/ngx_http_upstream_least_conn_module.c
new file mode 100644 (file)
index 0000000..ebe0627
--- /dev/null
@@ -0,0 +1,314 @@
+
+/*
+ * Copyright (C) Maxim Dounin
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_upstream_init_least_conn_peer(ngx_http_request_t *r,
+    ngx_http_upstream_srv_conf_t *us);
+static ngx_int_t ngx_http_upstream_get_least_conn_peer(
+    ngx_peer_connection_t *pc, void *data);
+static char *ngx_http_upstream_least_conn(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+
+static ngx_command_t  ngx_http_upstream_least_conn_commands[] = {
+
+    { ngx_string("least_conn"),
+      NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS,
+      ngx_http_upstream_least_conn,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_upstream_least_conn_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_upstream_least_conn_module = {
+    NGX_MODULE_V1,
+    &ngx_http_upstream_least_conn_module_ctx, /* module context */
+    ngx_http_upstream_least_conn_commands, /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_upstream_init_least_conn(ngx_conf_t *cf,
+    ngx_http_upstream_srv_conf_t *us)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0,
+                   "init least conn");
+
+    if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    us->peer.init = ngx_http_upstream_init_least_conn_peer;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_init_least_conn_peer(ngx_http_request_t *r,
+    ngx_http_upstream_srv_conf_t *us)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "init least conn peer");
+
+    if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    r->upstream->peer.get = ngx_http_upstream_get_least_conn_peer;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_get_least_conn_peer(ngx_peer_connection_t *pc, void *data)
+{
+    ngx_http_upstream_rr_peer_data_t  *rrp = data;
+
+    time_t                         now;
+    uintptr_t                      m;
+    ngx_int_t                      rc, total;
+    ngx_uint_t                     i, n, p, many;
+    ngx_http_upstream_rr_peer_t   *peer, *best;
+    ngx_http_upstream_rr_peers_t  *peers;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                   "get least conn peer, try: %ui", pc->tries);
+
+    if (rrp->peers->single) {
+        return ngx_http_upstream_get_round_robin_peer(pc, rrp);
+    }
+
+    pc->cached = 0;
+    pc->connection = NULL;
+
+    now = ngx_time();
+
+    peers = rrp->peers;
+
+    ngx_http_upstream_rr_peers_wlock(peers);
+
+    best = NULL;
+    total = 0;
+
+#if (NGX_SUPPRESS_WARN)
+    many = 0;
+    p = 0;
+#endif
+
+    for (peer = peers->peer, i = 0;
+         peer;
+         peer = peer->next, i++)
+    {
+        n = i / (8 * sizeof(uintptr_t));
+        m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));
+
+        if (rrp->tried[n] & m) {
+            continue;
+        }
+
+        if (peer->down) {
+            continue;
+        }
+
+        if (peer->max_fails
+            && peer->fails >= peer->max_fails
+            && now - peer->checked <= peer->fail_timeout)
+        {
+            continue;
+        }
+
+        if (peer->max_conns && peer->conns >= peer->max_conns) {
+            continue;
+        }
+
+        /*
+         * select peer with least number of connections; if there are
+         * multiple peers with the same number of connections, select
+         * based on round-robin
+         */
+
+        if (best == NULL
+            || peer->conns * best->weight < best->conns * peer->weight)
+        {
+            best = peer;
+            many = 0;
+            p = i;
+
+        } else if (peer->conns * best->weight == best->conns * peer->weight) {
+            many = 1;
+        }
+    }
+
+    if (best == NULL) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                       "get least conn peer, no peer found");
+
+        goto failed;
+    }
+
+    if (many) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                       "get least conn peer, many");
+
+        for (peer = best, i = p;
+             peer;
+             peer = peer->next, i++)
+        {
+            n = i / (8 * sizeof(uintptr_t));
+            m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));
+
+            if (rrp->tried[n] & m) {
+                continue;
+            }
+
+            if (peer->down) {
+                continue;
+            }
+
+            if (peer->conns * best->weight != best->conns * peer->weight) {
+                continue;
+            }
+
+            if (peer->max_fails
+                && peer->fails >= peer->max_fails
+                && now - peer->checked <= peer->fail_timeout)
+            {
+                continue;
+            }
+
+            if (peer->max_conns && peer->conns >= peer->max_conns) {
+                continue;
+            }
+
+            peer->current_weight += peer->effective_weight;
+            total += peer->effective_weight;
+
+            if (peer->effective_weight < peer->weight) {
+                peer->effective_weight++;
+            }
+
+            if (peer->current_weight > best->current_weight) {
+                best = peer;
+                p = i;
+            }
+        }
+    }
+
+    best->current_weight -= total;
+
+    if (now - best->checked > best->fail_timeout) {
+        best->checked = now;
+    }
+
+    pc->sockaddr = best->sockaddr;
+    pc->socklen = best->socklen;
+    pc->name = &best->name;
+
+    best->conns++;
+
+    rrp->current = best;
+
+    n = p / (8 * sizeof(uintptr_t));
+    m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));
+
+    rrp->tried[n] |= m;
+
+    ngx_http_upstream_rr_peers_unlock(peers);
+
+    return NGX_OK;
+
+failed:
+
+    if (peers->next) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                       "get least conn peer, backup servers");
+
+        rrp->peers = peers->next;
+
+        n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1))
+                / (8 * sizeof(uintptr_t));
+
+        for (i = 0; i < n; i++) {
+            rrp->tried[i] = 0;
+        }
+
+        ngx_http_upstream_rr_peers_unlock(peers);
+
+        rc = ngx_http_upstream_get_least_conn_peer(pc, rrp);
+
+        if (rc != NGX_BUSY) {
+            return rc;
+        }
+
+        ngx_http_upstream_rr_peers_wlock(peers);
+    }
+
+    ngx_http_upstream_rr_peers_unlock(peers);
+
+    pc->name = peers->name;
+
+    return NGX_BUSY;
+}
+
+
+static char *
+ngx_http_upstream_least_conn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_upstream_srv_conf_t  *uscf;
+
+    uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
+
+    if (uscf->peer.init_upstream) {
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                           "load balancing method redefined");
+    }
+
+    uscf->peer.init_upstream = ngx_http_upstream_init_least_conn;
+
+    uscf->flags = NGX_HTTP_UPSTREAM_CREATE
+                  |NGX_HTTP_UPSTREAM_WEIGHT
+                  |NGX_HTTP_UPSTREAM_MAX_CONNS
+                  |NGX_HTTP_UPSTREAM_MAX_FAILS
+                  |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
+                  |NGX_HTTP_UPSTREAM_DOWN
+                  |NGX_HTTP_UPSTREAM_BACKUP;
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_upstream_zone_module.c b/nginx/src/http/modules/ngx_http_upstream_zone_module.c
new file mode 100644 (file)
index 0000000..3229cfe
--- /dev/null
@@ -0,0 +1,325 @@
+
+/*
+ * Copyright (C) Ruslan Ermilov
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static char *ngx_http_upstream_zone(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static ngx_int_t ngx_http_upstream_init_zone(ngx_shm_zone_t *shm_zone,
+    void *data);
+static ngx_http_upstream_rr_peers_t *ngx_http_upstream_zone_copy_peers(
+    ngx_slab_pool_t *shpool, ngx_http_upstream_srv_conf_t *uscf);
+static ngx_http_upstream_rr_peer_t *ngx_http_upstream_zone_copy_peer(
+    ngx_http_upstream_rr_peers_t *peers, ngx_http_upstream_rr_peer_t *src);
+
+
+static ngx_command_t  ngx_http_upstream_zone_commands[] = {
+
+    { ngx_string("zone"),
+      NGX_HTTP_UPS_CONF|NGX_CONF_TAKE12,
+      ngx_http_upstream_zone,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_upstream_zone_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_upstream_zone_module = {
+    NGX_MODULE_V1,
+    &ngx_http_upstream_zone_module_ctx,    /* module context */
+    ngx_http_upstream_zone_commands,       /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static char *
+ngx_http_upstream_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ssize_t                         size;
+    ngx_str_t                      *value;
+    ngx_http_upstream_srv_conf_t   *uscf;
+    ngx_http_upstream_main_conf_t  *umcf;
+
+    uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
+    umcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_upstream_module);
+
+    value = cf->args->elts;
+
+    if (!value[1].len) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid zone name \"%V\"", &value[1]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (cf->args->nelts == 3) {
+        size = ngx_parse_size(&value[2]);
+
+        if (size == NGX_ERROR) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid zone size \"%V\"", &value[2]);
+            return NGX_CONF_ERROR;
+        }
+
+        if (size < (ssize_t) (8 * ngx_pagesize)) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "zone \"%V\" is too small", &value[1]);
+            return NGX_CONF_ERROR;
+        }
+
+    } else {
+        size = 0;
+    }
+
+    uscf->shm_zone = ngx_shared_memory_add(cf, &value[1], size,
+                                           &ngx_http_upstream_module);
+    if (uscf->shm_zone == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    uscf->shm_zone->init = ngx_http_upstream_init_zone;
+    uscf->shm_zone->data = umcf;
+
+    uscf->shm_zone->noreuse = 1;
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_init_zone(ngx_shm_zone_t *shm_zone, void *data)
+{
+    size_t                          len;
+    ngx_uint_t                      i;
+    ngx_slab_pool_t                *shpool;
+    ngx_http_upstream_rr_peers_t   *peers, **peersp;
+    ngx_http_upstream_srv_conf_t   *uscf, **uscfp;
+    ngx_http_upstream_main_conf_t  *umcf;
+
+    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+    umcf = shm_zone->data;
+    uscfp = umcf->upstreams.elts;
+
+    if (shm_zone->shm.exists) {
+        peers = shpool->data;
+
+        for (i = 0; i < umcf->upstreams.nelts; i++) {
+            uscf = uscfp[i];
+
+            if (uscf->shm_zone != shm_zone) {
+                continue;
+            }
+
+            uscf->peer.data = peers;
+            peers = peers->zone_next;
+        }
+
+        return NGX_OK;
+    }
+
+    len = sizeof(" in upstream zone \"\"") + shm_zone->shm.name.len;
+
+    shpool->log_ctx = ngx_slab_alloc(shpool, len);
+    if (shpool->log_ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_sprintf(shpool->log_ctx, " in upstream zone \"%V\"%Z",
+                &shm_zone->shm.name);
+
+
+    /* copy peers to shared memory */
+
+    peersp = (ngx_http_upstream_rr_peers_t **) (void *) &shpool->data;
+
+    for (i = 0; i < umcf->upstreams.nelts; i++) {
+        uscf = uscfp[i];
+
+        if (uscf->shm_zone != shm_zone) {
+            continue;
+        }
+
+        peers = ngx_http_upstream_zone_copy_peers(shpool, uscf);
+        if (peers == NULL) {
+            return NGX_ERROR;
+        }
+
+        *peersp = peers;
+        peersp = &peers->zone_next;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_http_upstream_rr_peers_t *
+ngx_http_upstream_zone_copy_peers(ngx_slab_pool_t *shpool,
+    ngx_http_upstream_srv_conf_t *uscf)
+{
+    ngx_str_t                     *name;
+    ngx_http_upstream_rr_peer_t   *peer, **peerp;
+    ngx_http_upstream_rr_peers_t  *peers, *backup;
+
+    peers = ngx_slab_alloc(shpool, sizeof(ngx_http_upstream_rr_peers_t));
+    if (peers == NULL) {
+        return NULL;
+    }
+
+    ngx_memcpy(peers, uscf->peer.data, sizeof(ngx_http_upstream_rr_peers_t));
+
+    name = ngx_slab_alloc(shpool, sizeof(ngx_str_t));
+    if (name == NULL) {
+        return NULL;
+    }
+
+    name->data = ngx_slab_alloc(shpool, peers->name->len);
+    if (name->data == NULL) {
+        return NULL;
+    }
+
+    ngx_memcpy(name->data, peers->name->data, peers->name->len);
+    name->len = peers->name->len;
+
+    peers->name = name;
+
+    peers->shpool = shpool;
+
+    for (peerp = &peers->peer; *peerp; peerp = &peer->next) {
+        /* pool is unlocked */
+        peer = ngx_http_upstream_zone_copy_peer(peers, *peerp);
+        if (peer == NULL) {
+            return NULL;
+        }
+
+        *peerp = peer;
+    }
+
+    if (peers->next == NULL) {
+        goto done;
+    }
+
+    backup = ngx_slab_alloc(shpool, sizeof(ngx_http_upstream_rr_peers_t));
+    if (backup == NULL) {
+        return NULL;
+    }
+
+    ngx_memcpy(backup, peers->next, sizeof(ngx_http_upstream_rr_peers_t));
+
+    backup->name = name;
+
+    backup->shpool = shpool;
+
+    for (peerp = &backup->peer; *peerp; peerp = &peer->next) {
+        /* pool is unlocked */
+        peer = ngx_http_upstream_zone_copy_peer(backup, *peerp);
+        if (peer == NULL) {
+            return NULL;
+        }
+
+        *peerp = peer;
+    }
+
+    peers->next = backup;
+
+done:
+
+    uscf->peer.data = peers;
+
+    return peers;
+}
+
+
+static ngx_http_upstream_rr_peer_t *
+ngx_http_upstream_zone_copy_peer(ngx_http_upstream_rr_peers_t *peers,
+    ngx_http_upstream_rr_peer_t *src)
+{
+    ngx_slab_pool_t              *pool;
+    ngx_http_upstream_rr_peer_t  *dst;
+
+    pool = peers->shpool;
+
+    dst = ngx_slab_calloc_locked(pool, sizeof(ngx_http_upstream_rr_peer_t));
+    if (dst == NULL) {
+        return NULL;
+    }
+
+    if (src) {
+        ngx_memcpy(dst, src, sizeof(ngx_http_upstream_rr_peer_t));
+        dst->sockaddr = NULL;
+        dst->name.data = NULL;
+        dst->server.data = NULL;
+    }
+
+    dst->sockaddr = ngx_slab_calloc_locked(pool, sizeof(ngx_sockaddr_t));
+    if (dst->sockaddr == NULL) {
+        goto failed;
+    }
+
+    dst->name.data = ngx_slab_calloc_locked(pool, NGX_SOCKADDR_STRLEN);
+    if (dst->name.data == NULL) {
+        goto failed;
+    }
+
+    if (src) {
+        ngx_memcpy(dst->sockaddr, src->sockaddr, src->socklen);
+        ngx_memcpy(dst->name.data, src->name.data, src->name.len);
+
+        dst->server.data = ngx_slab_alloc_locked(pool, src->server.len);
+        if (dst->server.data == NULL) {
+            goto failed;
+        }
+
+        ngx_memcpy(dst->server.data, src->server.data, src->server.len);
+    }
+
+    return dst;
+
+failed:
+
+    if (dst->server.data) {
+        ngx_slab_free_locked(pool, dst->server.data);
+    }
+
+    if (dst->name.data) {
+        ngx_slab_free_locked(pool, dst->name.data);
+    }
+
+    if (dst->sockaddr) {
+        ngx_slab_free_locked(pool, dst->sockaddr);
+    }
+
+    ngx_slab_free_locked(pool, dst);
+
+    return NULL;
+}
diff --git a/nginx/src/http/modules/ngx_http_userid_filter_module.c b/nginx/src/http/modules/ngx_http_userid_filter_module.c
new file mode 100644 (file)
index 0000000..a1a5493
--- /dev/null
@@ -0,0 +1,846 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_USERID_OFF   0
+#define NGX_HTTP_USERID_LOG   1
+#define NGX_HTTP_USERID_V1    2
+#define NGX_HTTP_USERID_ON    3
+
+/* 31 Dec 2037 23:55:55 GMT */
+#define NGX_HTTP_USERID_MAX_EXPIRES  2145916555
+
+
+typedef struct {
+    ngx_uint_t  enable;
+
+    ngx_int_t   service;
+
+    ngx_str_t   name;
+    ngx_str_t   domain;
+    ngx_str_t   path;
+    ngx_str_t   p3p;
+
+    time_t      expires;
+
+    u_char      mark;
+} ngx_http_userid_conf_t;
+
+
+typedef struct {
+    uint32_t    uid_got[4];
+    uint32_t    uid_set[4];
+    ngx_str_t   cookie;
+    ngx_uint_t  reset;
+} ngx_http_userid_ctx_t;
+
+
+static ngx_http_userid_ctx_t *ngx_http_userid_get_uid(ngx_http_request_t *r,
+    ngx_http_userid_conf_t *conf);
+static ngx_int_t ngx_http_userid_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, ngx_str_t *name, uint32_t *uid);
+static ngx_int_t ngx_http_userid_set_uid(ngx_http_request_t *r,
+    ngx_http_userid_ctx_t *ctx, ngx_http_userid_conf_t *conf);
+static ngx_int_t ngx_http_userid_create_uid(ngx_http_request_t *r,
+    ngx_http_userid_ctx_t *ctx, ngx_http_userid_conf_t *conf);
+
+static ngx_int_t ngx_http_userid_add_variables(ngx_conf_t *cf);
+static ngx_int_t ngx_http_userid_init(ngx_conf_t *cf);
+static void *ngx_http_userid_create_conf(ngx_conf_t *cf);
+static char *ngx_http_userid_merge_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static char *ngx_http_userid_domain(ngx_conf_t *cf, void *post, void *data);
+static char *ngx_http_userid_path(ngx_conf_t *cf, void *post, void *data);
+static char *ngx_http_userid_expires(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_userid_p3p(ngx_conf_t *cf, void *post, void *data);
+static char *ngx_http_userid_mark(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static ngx_int_t ngx_http_userid_init_worker(ngx_cycle_t *cycle);
+
+
+
+static uint32_t  start_value;
+static uint32_t  sequencer_v1 = 1;
+static uint32_t  sequencer_v2 = 0x03030302;
+
+
+static u_char expires[] = "; expires=Thu, 31-Dec-37 23:55:55 GMT";
+
+
+static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+
+
+static ngx_conf_enum_t  ngx_http_userid_state[] = {
+    { ngx_string("off"), NGX_HTTP_USERID_OFF },
+    { ngx_string("log"), NGX_HTTP_USERID_LOG },
+    { ngx_string("v1"), NGX_HTTP_USERID_V1 },
+    { ngx_string("on"), NGX_HTTP_USERID_ON },
+    { 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;
+static ngx_conf_post_handler_pt  ngx_http_userid_p3p_p = ngx_http_userid_p3p;
+
+
+static ngx_command_t  ngx_http_userid_commands[] = {
+
+    { ngx_string("userid"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_userid_conf_t, enable),
+      ngx_http_userid_state },
+
+    { ngx_string("userid_service"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_userid_conf_t, service),
+      NULL },
+
+    { ngx_string("userid_name"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_userid_conf_t, name),
+      NULL },
+
+    { ngx_string("userid_domain"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_userid_conf_t, domain),
+      &ngx_http_userid_domain_p },
+
+    { ngx_string("userid_path"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_userid_conf_t, path),
+      &ngx_http_userid_path_p },
+
+    { ngx_string("userid_expires"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_userid_expires,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("userid_p3p"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_userid_conf_t, p3p),
+      &ngx_http_userid_p3p_p },
+
+    { ngx_string("userid_mark"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_userid_mark,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_userid_filter_module_ctx = {
+    ngx_http_userid_add_variables,         /* preconfiguration */
+    ngx_http_userid_init,                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_userid_create_conf,           /* create location configuration */
+    ngx_http_userid_merge_conf             /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_userid_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_userid_filter_module_ctx,    /* module context */
+    ngx_http_userid_commands,              /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    ngx_http_userid_init_worker,           /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t   ngx_http_userid_got = ngx_string("uid_got");
+static ngx_str_t   ngx_http_userid_set = ngx_string("uid_set");
+static ngx_str_t   ngx_http_userid_reset = ngx_string("uid_reset");
+static ngx_uint_t  ngx_http_userid_reset_index;
+
+
+static ngx_int_t
+ngx_http_userid_filter(ngx_http_request_t *r)
+{
+    ngx_http_userid_ctx_t   *ctx;
+    ngx_http_userid_conf_t  *conf;
+
+    if (r != r->main) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_userid_filter_module);
+
+    if (conf->enable < NGX_HTTP_USERID_V1) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    ctx = ngx_http_userid_get_uid(r, conf);
+
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_http_userid_set_uid(r, ctx, conf) == NGX_OK) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_userid_got_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_http_userid_ctx_t   *ctx;
+    ngx_http_userid_conf_t  *conf;
+
+    conf = ngx_http_get_module_loc_conf(r->main, ngx_http_userid_filter_module);
+
+    if (conf->enable == NGX_HTTP_USERID_OFF) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    ctx = ngx_http_userid_get_uid(r->main, conf);
+
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (ctx->uid_got[3] != 0) {
+        return ngx_http_userid_variable(r->main, v, &conf->name, ctx->uid_got);
+    }
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_userid_set_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_http_userid_ctx_t   *ctx;
+    ngx_http_userid_conf_t  *conf;
+
+    conf = ngx_http_get_module_loc_conf(r->main, ngx_http_userid_filter_module);
+
+    if (conf->enable < NGX_HTTP_USERID_V1) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    ctx = ngx_http_userid_get_uid(r->main, conf);
+
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_http_userid_create_uid(r->main, ctx, conf) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (ctx->uid_set[3] == 0) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    return ngx_http_userid_variable(r->main, v, &conf->name, ctx->uid_set);
+}
+
+
+static ngx_http_userid_ctx_t *
+ngx_http_userid_get_uid(ngx_http_request_t *r, ngx_http_userid_conf_t *conf)
+{
+    ngx_int_t                n;
+    ngx_str_t                src, dst;
+    ngx_table_elt_t        **cookies;
+    ngx_http_userid_ctx_t   *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_userid_filter_module);
+
+    if (ctx) {
+        return ctx;
+    }
+
+    if (ctx == NULL) {
+        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_userid_ctx_t));
+        if (ctx == NULL) {
+            return NULL;
+        }
+
+        ngx_http_set_ctx(r, ctx, ngx_http_userid_filter_module);
+    }
+
+    n = ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &conf->name,
+                                          &ctx->cookie);
+    if (n == NGX_DECLINED) {
+        return ctx;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "uid cookie: \"%V\"", &ctx->cookie);
+
+    if (ctx->cookie.len < 22) {
+        cookies = r->headers_in.cookies.elts;
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "client sent too short userid cookie \"%V\"",
+                      &cookies[n]->value);
+        return ctx;
+    }
+
+    src = ctx->cookie;
+
+    /*
+     * we have to limit the encoded string to 22 characters because
+     *  1) cookie may be marked by "userid_mark",
+     *  2) and there are already the millions cookies with a garbage
+     *     instead of the correct base64 trail "=="
+     */
+
+    src.len = 22;
+
+    dst.data = (u_char *) ctx->uid_got;
+
+    if (ngx_decode_base64(&dst, &src) == NGX_ERROR) {
+        cookies = r->headers_in.cookies.elts;
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "client sent invalid userid cookie \"%V\"",
+                      &cookies[n]->value);
+        return ctx;
+    }
+
+    ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "uid: %08XD%08XD%08XD%08XD",
+                   ctx->uid_got[0], ctx->uid_got[1],
+                   ctx->uid_got[2], ctx->uid_got[3]);
+
+    return ctx;
+}
+
+
+static ngx_int_t
+ngx_http_userid_set_uid(ngx_http_request_t *r, ngx_http_userid_ctx_t *ctx,
+    ngx_http_userid_conf_t *conf)
+{
+    u_char           *cookie, *p;
+    size_t            len;
+    ngx_str_t         src, dst;
+    ngx_table_elt_t  *set_cookie, *p3p;
+
+    if (ngx_http_userid_create_uid(r, ctx, conf) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (ctx->uid_set[3] == 0) {
+        return NGX_OK;
+    }
+
+    len = conf->name.len + 1 + ngx_base64_encoded_length(16) + conf->path.len;
+
+    if (conf->expires) {
+        len += sizeof(expires) - 1 + 2;
+    }
+
+    if (conf->domain.len) {
+        len += conf->domain.len;
+    }
+
+    cookie = ngx_pnalloc(r->pool, len);
+    if (cookie == NULL) {
+        return NGX_ERROR;
+    }
+
+    p = ngx_copy(cookie, conf->name.data, conf->name.len);
+    *p++ = '=';
+
+    if (ctx->uid_got[3] == 0 || ctx->reset) {
+        src.len = 16;
+        src.data = (u_char *) ctx->uid_set;
+        dst.data = p;
+
+        ngx_encode_base64(&dst, &src);
+
+        p += dst.len;
+
+        if (conf->mark) {
+            *(p - 2) = conf->mark;
+        }
+
+    } else {
+        p = ngx_cpymem(p, ctx->cookie.data, 22);
+        *p++ = conf->mark;
+        *p++ = '=';
+    }
+
+    if (conf->expires == NGX_HTTP_USERID_MAX_EXPIRES) {
+        p = ngx_cpymem(p, expires, sizeof(expires) - 1);
+
+    } else if (conf->expires) {
+        p = ngx_cpymem(p, expires, sizeof("; expires=") - 1);
+        p = ngx_http_cookie_time(p, ngx_time() + conf->expires);
+    }
+
+    p = ngx_copy(p, conf->domain.data, conf->domain.len);
+
+    p = ngx_copy(p, conf->path.data, conf->path.len);
+
+    set_cookie = ngx_list_push(&r->headers_out.headers);
+    if (set_cookie == NULL) {
+        return NGX_ERROR;
+    }
+
+    set_cookie->hash = 1;
+    ngx_str_set(&set_cookie->key, "Set-Cookie");
+    set_cookie->value.len = p - cookie;
+    set_cookie->value.data = cookie;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "uid cookie: \"%V\"", &set_cookie->value);
+
+    if (conf->p3p.len == 0) {
+        return NGX_OK;
+    }
+
+    p3p = ngx_list_push(&r->headers_out.headers);
+    if (p3p == NULL) {
+        return NGX_ERROR;
+    }
+
+    p3p->hash = 1;
+    ngx_str_set(&p3p->key, "P3P");
+    p3p->value = conf->p3p;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_userid_create_uid(ngx_http_request_t *r, ngx_http_userid_ctx_t *ctx,
+    ngx_http_userid_conf_t *conf)
+{
+    ngx_connection_t           *c;
+    struct sockaddr_in         *sin;
+    ngx_http_variable_value_t  *vv;
+#if (NGX_HAVE_INET6)
+    u_char                     *p;
+    struct sockaddr_in6        *sin6;
+#endif
+
+    if (ctx->uid_set[3] != 0) {
+        return NGX_OK;
+    }
+
+    if (ctx->uid_got[3] != 0) {
+
+        vv = ngx_http_get_indexed_variable(r, ngx_http_userid_reset_index);
+
+        if (vv == NULL || vv->not_found) {
+            return NGX_ERROR;
+        }
+
+        if (vv->len == 0 || (vv->len == 1 && vv->data[0] == '0')) {
+
+            if (conf->mark == '\0'
+                || (ctx->cookie.len > 23
+                    && ctx->cookie.data[22] == conf->mark
+                    && ctx->cookie.data[23] == '='))
+            {
+                return NGX_OK;
+            }
+
+            ctx->uid_set[0] = ctx->uid_got[0];
+            ctx->uid_set[1] = ctx->uid_got[1];
+            ctx->uid_set[2] = ctx->uid_got[2];
+            ctx->uid_set[3] = ctx->uid_got[3];
+
+            return NGX_OK;
+
+        } else {
+            ctx->reset = 1;
+
+            if (vv->len == 3 && ngx_strncmp(vv->data, "log", 3) == 0) {
+                ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
+                        "userid cookie \"%V=%08XD%08XD%08XD%08XD\" was reset",
+                        &conf->name, ctx->uid_got[0], ctx->uid_got[1],
+                        ctx->uid_got[2], ctx->uid_got[3]);
+            }
+        }
+    }
+
+    /*
+     * TODO: in the threaded mode the sequencers should be in TLS and their
+     * ranges should be divided between threads
+     */
+
+    if (conf->enable == NGX_HTTP_USERID_V1) {
+        if (conf->service == NGX_CONF_UNSET) {
+            ctx->uid_set[0] = 0;
+        } else {
+            ctx->uid_set[0] = conf->service;
+        }
+        ctx->uid_set[1] = (uint32_t) ngx_time();
+        ctx->uid_set[2] = start_value;
+        ctx->uid_set[3] = sequencer_v1;
+        sequencer_v1 += 0x100;
+
+    } else {
+        if (conf->service == NGX_CONF_UNSET) {
+
+            c = r->connection;
+
+            if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
+                return NGX_ERROR;
+            }
+
+            switch (c->local_sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+            case AF_INET6:
+                sin6 = (struct sockaddr_in6 *) c->local_sockaddr;
+
+                p = (u_char *) &ctx->uid_set[0];
+
+                *p++ = sin6->sin6_addr.s6_addr[12];
+                *p++ = sin6->sin6_addr.s6_addr[13];
+                *p++ = sin6->sin6_addr.s6_addr[14];
+                *p = sin6->sin6_addr.s6_addr[15];
+
+                break;
+#endif
+            default: /* AF_INET */
+                sin = (struct sockaddr_in *) c->local_sockaddr;
+                ctx->uid_set[0] = sin->sin_addr.s_addr;
+                break;
+            }
+
+        } else {
+            ctx->uid_set[0] = htonl(conf->service);
+        }
+
+        ctx->uid_set[1] = htonl((uint32_t) ngx_time());
+        ctx->uid_set[2] = htonl(start_value);
+        ctx->uid_set[3] = htonl(sequencer_v2);
+        sequencer_v2 += 0x100;
+        if (sequencer_v2 < 0x03030302) {
+            sequencer_v2 = 0x03030302;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_userid_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+    ngx_str_t *name, uint32_t *uid)
+{
+    v->len = name->len + sizeof("=00001111222233334444555566667777") - 1;
+    v->data = ngx_pnalloc(r->pool, v->len);
+    if (v->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    ngx_sprintf(v->data, "%V=%08XD%08XD%08XD%08XD",
+                name, uid[0], uid[1], uid[2], uid[3]);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_userid_reset_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    *v = ngx_http_variable_null_value;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_userid_add_variables(ngx_conf_t *cf)
+{
+    ngx_int_t             n;
+    ngx_http_variable_t  *var;
+
+    var = ngx_http_add_variable(cf, &ngx_http_userid_got, 0);
+    if (var == NULL) {
+        return NGX_ERROR;
+    }
+
+    var->get_handler = ngx_http_userid_got_variable;
+
+    var = ngx_http_add_variable(cf, &ngx_http_userid_set, 0);
+    if (var == NULL) {
+        return NGX_ERROR;
+    }
+
+    var->get_handler = ngx_http_userid_set_variable;
+
+    var = ngx_http_add_variable(cf, &ngx_http_userid_reset,
+                                NGX_HTTP_VAR_CHANGEABLE);
+    if (var == NULL) {
+        return NGX_ERROR;
+    }
+
+    var->get_handler = ngx_http_userid_reset_variable;
+
+    n = ngx_http_get_variable_index(cf, &ngx_http_userid_reset);
+    if (n == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    ngx_http_userid_reset_index = n;
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_userid_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_userid_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_userid_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->name = { 0, NULL };
+     *     conf->domain = { 0, NULL };
+     *     conf->path = { 0, NULL };
+     *     conf->p3p = { 0, NULL };
+     */
+
+    conf->enable = NGX_CONF_UNSET_UINT;
+    conf->service = NGX_CONF_UNSET;
+    conf->expires = NGX_CONF_UNSET;
+    conf->mark = (u_char) '\xFF';
+
+    return conf;
+}
+
+
+static char *
+ngx_http_userid_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_userid_conf_t *prev = parent;
+    ngx_http_userid_conf_t *conf = child;
+
+    ngx_conf_merge_uint_value(conf->enable, prev->enable,
+                              NGX_HTTP_USERID_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=/");
+    ngx_conf_merge_str_value(conf->p3p, prev->p3p, "");
+
+    ngx_conf_merge_value(conf->service, prev->service, NGX_CONF_UNSET);
+    ngx_conf_merge_sec_value(conf->expires, prev->expires, 0);
+
+    if (conf->mark == (u_char) '\xFF') {
+        if (prev->mark == (u_char) '\xFF') {
+            conf->mark = '\0';
+        } else {
+            conf->mark = prev->mark;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_userid_init(ngx_conf_t *cf)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_userid_filter;
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_http_userid_domain(ngx_conf_t *cf, void *post, void *data)
+{
+    ngx_str_t  *domain = data;
+
+    u_char  *p, *new;
+
+    if (ngx_strcmp(domain->data, "none") == 0) {
+        ngx_str_set(domain, "");
+        return NGX_CONF_OK;
+    }
+
+    new = ngx_pnalloc(cf->pool, sizeof("; domain=") - 1 + domain->len);
+    if (new == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    p = ngx_cpymem(new, "; domain=", sizeof("; domain=") - 1);
+    ngx_memcpy(p, domain->data, domain->len);
+
+    domain->len += sizeof("; domain=") - 1;
+    domain->data = new;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_userid_path(ngx_conf_t *cf, void *post, void *data)
+{
+    ngx_str_t  *path = data;
+
+    u_char  *p, *new;
+
+    new = ngx_pnalloc(cf->pool, sizeof("; path=") - 1 + path->len);
+    if (new == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    p = ngx_cpymem(new, "; path=", sizeof("; path=") - 1);
+    ngx_memcpy(p, path->data, path->len);
+
+    path->len += sizeof("; path=") - 1;
+    path->data = new;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_userid_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_userid_conf_t *ucf = conf;
+
+    ngx_str_t  *value;
+
+    if (ucf->expires != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    if (ngx_strcmp(value[1].data, "max") == 0) {
+        ucf->expires = NGX_HTTP_USERID_MAX_EXPIRES;
+        return NGX_CONF_OK;
+    }
+
+    if (ngx_strcmp(value[1].data, "off") == 0) {
+        ucf->expires = 0;
+        return NGX_CONF_OK;
+    }
+
+    ucf->expires = ngx_parse_time(&value[1], 1);
+    if (ucf->expires == (time_t) NGX_ERROR) {
+        return "invalid value";
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_userid_p3p(ngx_conf_t *cf, void *post, void *data)
+{
+    ngx_str_t  *p3p = data;
+
+    if (ngx_strcmp(p3p->data, "none") == 0) {
+        ngx_str_set(p3p, "");
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_userid_mark(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_userid_conf_t *ucf = conf;
+
+    ngx_str_t  *value;
+
+    if (ucf->mark != (u_char) '\xFF') {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    if (ngx_strcmp(value[1].data, "off") == 0) {
+        ucf->mark = '\0';
+        return NGX_CONF_OK;
+    }
+
+    if (value[1].len != 1
+        || !((value[1].data[0] >= '0' && value[1].data[0] <= '9')
+              || (value[1].data[0] >= 'A' && value[1].data[0] <= 'Z')
+              || (value[1].data[0] >= 'a' && value[1].data[0] <= 'z')
+              || value[1].data[0] == '='))
+    {
+        return "value must be \"off\" or a single letter, digit or \"=\"";
+    }
+
+    ucf->mark = value[1].data[0];
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_userid_init_worker(ngx_cycle_t *cycle)
+{
+    struct timeval  tp;
+
+    ngx_gettimeofday(&tp);
+
+    /* use the most significant usec part that fits to 16 bits */
+    start_value = (((uint32_t) tp.tv_usec / 20) << 16) | ngx_pid;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/modules/ngx_http_uwsgi_module.c b/nginx/src/http/modules/ngx_http_uwsgi_module.c
new file mode 100644 (file)
index 0000000..238bcf8
--- /dev/null
@@ -0,0 +1,2397 @@
+
+/*
+ * Copyright (C) Unbit S.a.s. 2009-2010
+ * Copyright (C) 2008 Manlio Perillo ([email protected])
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_array_t                caches;  /* ngx_http_file_cache_t * */
+} ngx_http_uwsgi_main_conf_t;
+
+
+typedef struct {
+    ngx_array_t               *flushes;
+    ngx_array_t               *lengths;
+    ngx_array_t               *values;
+    ngx_uint_t                 number;
+    ngx_hash_t                 hash;
+} ngx_http_uwsgi_params_t;
+
+
+typedef struct {
+    ngx_http_upstream_conf_t   upstream;
+
+    ngx_http_uwsgi_params_t    params;
+#if (NGX_HTTP_CACHE)
+    ngx_http_uwsgi_params_t    params_cache;
+#endif
+    ngx_array_t               *params_source;
+
+    ngx_array_t               *uwsgi_lengths;
+    ngx_array_t               *uwsgi_values;
+
+#if (NGX_HTTP_CACHE)
+    ngx_http_complex_value_t   cache_key;
+#endif
+
+    ngx_str_t                  uwsgi_string;
+
+    ngx_uint_t                 modifier1;
+    ngx_uint_t                 modifier2;
+
+#if (NGX_HTTP_SSL)
+    ngx_uint_t                 ssl;
+    ngx_uint_t                 ssl_protocols;
+    ngx_str_t                  ssl_ciphers;
+    ngx_uint_t                 ssl_verify_depth;
+    ngx_str_t                  ssl_trusted_certificate;
+    ngx_str_t                  ssl_crl;
+    ngx_str_t                  ssl_certificate;
+    ngx_str_t                  ssl_certificate_key;
+    ngx_array_t               *ssl_passwords;
+#endif
+} ngx_http_uwsgi_loc_conf_t;
+
+
+static ngx_int_t ngx_http_uwsgi_eval(ngx_http_request_t *r,
+    ngx_http_uwsgi_loc_conf_t *uwcf);
+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 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);
+
+static void *ngx_http_uwsgi_create_main_conf(ngx_conf_t *cf);
+static void *ngx_http_uwsgi_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_uwsgi_merge_loc_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static ngx_int_t ngx_http_uwsgi_init_params(ngx_conf_t *cf,
+    ngx_http_uwsgi_loc_conf_t *conf, ngx_http_uwsgi_params_t *params,
+    ngx_keyval_t *default_params);
+
+static char *ngx_http_uwsgi_pass(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_uwsgi_store(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+#if (NGX_HTTP_CACHE)
+static ngx_int_t ngx_http_uwsgi_create_key(ngx_http_request_t *r);
+static char *ngx_http_uwsgi_cache(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_uwsgi_cache_key(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+#endif
+
+#if (NGX_HTTP_SSL)
+static char *ngx_http_uwsgi_ssl_password_file(ngx_conf_t *cf,
+    ngx_command_t *cmd, void *conf);
+static ngx_int_t ngx_http_uwsgi_set_ssl(ngx_conf_t *cf,
+    ngx_http_uwsgi_loc_conf_t *uwcf);
+#endif
+
+
+static ngx_conf_num_bounds_t  ngx_http_uwsgi_modifier_bounds = {
+    ngx_conf_check_num_bounds, 0, 255
+};
+
+
+static ngx_conf_bitmask_t ngx_http_uwsgi_next_upstream_masks[] = {
+    { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR },
+    { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT },
+    { ngx_string("invalid_header"), NGX_HTTP_UPSTREAM_FT_INVALID_HEADER },
+    { ngx_string("non_idempotent"), NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT },
+    { ngx_string("http_500"), NGX_HTTP_UPSTREAM_FT_HTTP_500 },
+    { ngx_string("http_503"), NGX_HTTP_UPSTREAM_FT_HTTP_503 },
+    { ngx_string("http_403"), NGX_HTTP_UPSTREAM_FT_HTTP_403 },
+    { ngx_string("http_404"), NGX_HTTP_UPSTREAM_FT_HTTP_404 },
+    { ngx_string("http_429"), NGX_HTTP_UPSTREAM_FT_HTTP_429 },
+    { ngx_string("updating"), NGX_HTTP_UPSTREAM_FT_UPDATING },
+    { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF },
+    { ngx_null_string, 0 }
+};
+
+
+#if (NGX_HTTP_SSL)
+
+static ngx_conf_bitmask_t  ngx_http_uwsgi_ssl_protocols[] = {
+    { ngx_string("SSLv2"), NGX_SSL_SSLv2 },
+    { ngx_string("SSLv3"), NGX_SSL_SSLv3 },
+    { ngx_string("TLSv1"), NGX_SSL_TLSv1 },
+    { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 },
+    { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 },
+    { ngx_string("TLSv1.3"), NGX_SSL_TLSv1_3 },
+    { ngx_null_string, 0 }
+};
+
+#endif
+
+
+ngx_module_t  ngx_http_uwsgi_module;
+
+
+static ngx_command_t ngx_http_uwsgi_commands[] = {
+
+    { ngx_string("uwsgi_pass"),
+      NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
+      ngx_http_uwsgi_pass,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("uwsgi_modifier1"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, modifier1),
+      &ngx_http_uwsgi_modifier_bounds },
+
+    { ngx_string("uwsgi_modifier2"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, modifier2),
+      &ngx_http_uwsgi_modifier_bounds },
+
+    { ngx_string("uwsgi_store"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_uwsgi_store,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("uwsgi_store_access"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
+      ngx_conf_set_access_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.store_access),
+      NULL },
+
+    { ngx_string("uwsgi_buffering"),
+      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_uwsgi_loc_conf_t, upstream.buffering),
+      NULL },
+
+    { ngx_string("uwsgi_request_buffering"),
+      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_uwsgi_loc_conf_t, upstream.request_buffering),
+      NULL },
+
+    { ngx_string("uwsgi_ignore_client_abort"),
+      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_uwsgi_loc_conf_t, upstream.ignore_client_abort),
+      NULL },
+
+    { ngx_string("uwsgi_bind"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+      ngx_http_upstream_bind_set_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.local),
+      NULL },
+
+    { ngx_string("uwsgi_connect_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.connect_timeout),
+      NULL },
+
+    { ngx_string("uwsgi_send_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.send_timeout),
+      NULL },
+
+    { ngx_string("uwsgi_buffer_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.buffer_size),
+      NULL },
+
+    { ngx_string("uwsgi_pass_request_headers"),
+      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_uwsgi_loc_conf_t, upstream.pass_request_headers),
+      NULL },
+
+    { ngx_string("uwsgi_pass_request_body"),
+      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_uwsgi_loc_conf_t, upstream.pass_request_body),
+      NULL },
+
+    { ngx_string("uwsgi_intercept_errors"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.intercept_errors),
+      NULL },
+
+    { ngx_string("uwsgi_read_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.read_timeout),
+      NULL },
+
+    { ngx_string("uwsgi_buffers"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+      ngx_conf_set_bufs_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.bufs),
+      NULL },
+
+    { ngx_string("uwsgi_busy_buffers_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.busy_buffers_size_conf),
+      NULL },
+
+    { ngx_string("uwsgi_force_ranges"),
+      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_uwsgi_loc_conf_t, upstream.force_ranges),
+      NULL },
+
+    { ngx_string("uwsgi_limit_rate"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.limit_rate),
+      NULL },
+
+#if (NGX_HTTP_CACHE)
+
+    { ngx_string("uwsgi_cache"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_uwsgi_cache,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("uwsgi_cache_key"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_uwsgi_cache_key,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("uwsgi_cache_path"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_2MORE,
+      ngx_http_file_cache_set_slot,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_main_conf_t, caches),
+      &ngx_http_uwsgi_module },
+
+    { ngx_string("uwsgi_cache_bypass"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_set_predicate_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_bypass),
+      NULL },
+
+    { ngx_string("uwsgi_no_cache"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_set_predicate_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.no_cache),
+      NULL },
+
+    { ngx_string("uwsgi_cache_valid"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_file_cache_valid_set_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_valid),
+      NULL },
+
+    { ngx_string("uwsgi_cache_min_uses"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_min_uses),
+      NULL },
+
+    { ngx_string("uwsgi_cache_max_range_offset"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_off_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_max_range_offset),
+      NULL },
+
+    { ngx_string("uwsgi_cache_use_stale"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_use_stale),
+      &ngx_http_uwsgi_next_upstream_masks },
+
+    { ngx_string("uwsgi_cache_methods"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_methods),
+      &ngx_http_upstream_cache_method_mask },
+
+    { ngx_string("uwsgi_cache_lock"),
+      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_uwsgi_loc_conf_t, upstream.cache_lock),
+      NULL },
+
+    { ngx_string("uwsgi_cache_lock_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_lock_timeout),
+      NULL },
+
+    { ngx_string("uwsgi_cache_lock_age"),
+      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_uwsgi_loc_conf_t, upstream.cache_lock_age),
+      NULL },
+
+    { ngx_string("uwsgi_cache_revalidate"),
+      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_uwsgi_loc_conf_t, upstream.cache_revalidate),
+      NULL },
+
+    { ngx_string("uwsgi_cache_background_update"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_background_update),
+      NULL },
+
+#endif
+
+    { ngx_string("uwsgi_temp_path"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234,
+      ngx_conf_set_path_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.temp_path),
+      NULL },
+
+    { ngx_string("uwsgi_max_temp_file_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.max_temp_file_size_conf),
+      NULL },
+
+    { ngx_string("uwsgi_temp_file_write_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.temp_file_write_size_conf),
+      NULL },
+
+    { ngx_string("uwsgi_next_upstream"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.next_upstream),
+      &ngx_http_uwsgi_next_upstream_masks },
+
+    { ngx_string("uwsgi_next_upstream_tries"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.next_upstream_tries),
+      NULL },
+
+    { ngx_string("uwsgi_next_upstream_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.next_upstream_timeout),
+      NULL },
+
+    { ngx_string("uwsgi_param"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE23,
+      ngx_http_upstream_param_set_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, params_source),
+      NULL },
+
+    { ngx_string("uwsgi_string"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, uwsgi_string),
+      NULL },
+
+    { ngx_string("uwsgi_pass_header"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.pass_headers),
+      NULL },
+
+    { ngx_string("uwsgi_hide_header"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.hide_headers),
+      NULL },
+
+    { ngx_string("uwsgi_ignore_headers"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.ignore_headers),
+      &ngx_http_upstream_ignore_headers_masks },
+
+#if (NGX_HTTP_SSL)
+
+    { ngx_string("uwsgi_ssl_session_reuse"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.ssl_session_reuse),
+      NULL },
+
+    { ngx_string("uwsgi_ssl_protocols"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, ssl_protocols),
+      &ngx_http_uwsgi_ssl_protocols },
+
+    { ngx_string("uwsgi_ssl_ciphers"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, ssl_ciphers),
+      NULL },
+
+    { ngx_string("uwsgi_ssl_name"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_set_complex_value_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.ssl_name),
+      NULL },
+
+    { ngx_string("uwsgi_ssl_server_name"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.ssl_server_name),
+      NULL },
+
+    { ngx_string("uwsgi_ssl_verify"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.ssl_verify),
+      NULL },
+
+    { ngx_string("uwsgi_ssl_verify_depth"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, ssl_verify_depth),
+      NULL },
+
+    { ngx_string("uwsgi_ssl_trusted_certificate"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, ssl_trusted_certificate),
+      NULL },
+
+    { ngx_string("uwsgi_ssl_crl"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, ssl_crl),
+      NULL },
+
+    { 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_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, 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_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, ssl_certificate_key),
+      NULL },
+
+    { ngx_string("uwsgi_ssl_password_file"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_uwsgi_ssl_password_file,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+#endif
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_uwsgi_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    ngx_http_uwsgi_create_main_conf,       /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_uwsgi_create_loc_conf,        /* create location configuration */
+    ngx_http_uwsgi_merge_loc_conf          /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_uwsgi_module = {
+    NGX_MODULE_V1,
+    &ngx_http_uwsgi_module_ctx,            /* module context */
+    ngx_http_uwsgi_commands,               /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t ngx_http_uwsgi_hide_headers[] = {
+    ngx_string("X-Accel-Expires"),
+    ngx_string("X-Accel-Redirect"),
+    ngx_string("X-Accel-Limit-Rate"),
+    ngx_string("X-Accel-Buffering"),
+    ngx_string("X-Accel-Charset"),
+    ngx_null_string
+};
+
+
+#if (NGX_HTTP_CACHE)
+
+static ngx_keyval_t  ngx_http_uwsgi_cache_headers[] = {
+    { ngx_string("HTTP_IF_MODIFIED_SINCE"),
+      ngx_string("$upstream_cache_last_modified") },
+    { ngx_string("HTTP_IF_UNMODIFIED_SINCE"), ngx_string("") },
+    { ngx_string("HTTP_IF_NONE_MATCH"), ngx_string("$upstream_cache_etag") },
+    { ngx_string("HTTP_IF_MATCH"), ngx_string("") },
+    { ngx_string("HTTP_RANGE"), ngx_string("") },
+    { ngx_string("HTTP_IF_RANGE"), ngx_string("") },
+    { ngx_null_string, ngx_null_string }
+};
+
+#endif
+
+
+static ngx_path_init_t ngx_http_uwsgi_temp_path = {
+    ngx_string(NGX_HTTP_UWSGI_TEMP_PATH), { 1, 2, 0 }
+};
+
+
+static ngx_int_t
+ngx_http_uwsgi_handler(ngx_http_request_t *r)
+{
+    ngx_int_t                    rc;
+    ngx_http_status_t           *status;
+    ngx_http_upstream_t         *u;
+    ngx_http_uwsgi_loc_conf_t   *uwcf;
+#if (NGX_HTTP_CACHE)
+    ngx_http_uwsgi_main_conf_t  *uwmcf;
+#endif
+
+    if (ngx_http_upstream_create(r) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    status = ngx_pcalloc(r->pool, sizeof(ngx_http_status_t));
+    if (status == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    ngx_http_set_ctx(r, status, ngx_http_uwsgi_module);
+
+    uwcf = ngx_http_get_module_loc_conf(r, ngx_http_uwsgi_module);
+
+    u = r->upstream;
+
+    if (uwcf->uwsgi_lengths == NULL) {
+
+#if (NGX_HTTP_SSL)
+        u->ssl = (uwcf->upstream.ssl != NULL);
+
+        if (u->ssl) {
+            ngx_str_set(&u->schema, "suwsgi://");
+
+        } else {
+            ngx_str_set(&u->schema, "uwsgi://");
+        }
+#else
+        ngx_str_set(&u->schema, "uwsgi://");
+#endif
+
+    } else {
+        if (ngx_http_uwsgi_eval(r, uwcf) != NGX_OK) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+    }
+
+    u->output.tag = (ngx_buf_tag_t) &ngx_http_uwsgi_module;
+
+    u->conf = &uwcf->upstream;
+
+#if (NGX_HTTP_CACHE)
+    uwmcf = ngx_http_get_module_main_conf(r, ngx_http_uwsgi_module);
+
+    u->caches = &uwmcf->caches;
+    u->create_key = ngx_http_uwsgi_create_key;
+#endif
+
+    u->create_request = ngx_http_uwsgi_create_request;
+    u->reinit_request = ngx_http_uwsgi_reinit_request;
+    u->process_header = ngx_http_uwsgi_process_status_line;
+    u->abort_request = ngx_http_uwsgi_abort_request;
+    u->finalize_request = ngx_http_uwsgi_finalize_request;
+    r->state = 0;
+
+    u->buffering = uwcf->upstream.buffering;
+
+    u->pipe = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t));
+    if (u->pipe == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    u->pipe->input_filter = ngx_event_pipe_copy_input_filter;
+    u->pipe->input_ctx = r;
+
+    if (!uwcf->upstream.request_buffering
+        && uwcf->upstream.pass_request_body
+        && !r->headers_in.chunked)
+    {
+        r->request_body_no_buffering = 1;
+    }
+
+    rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);
+
+    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+        return rc;
+    }
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_uwsgi_eval(ngx_http_request_t *r, ngx_http_uwsgi_loc_conf_t * uwcf)
+{
+    size_t                add;
+    ngx_url_t             url;
+    ngx_http_upstream_t  *u;
+
+    ngx_memzero(&url, sizeof(ngx_url_t));
+
+    if (ngx_http_script_run(r, &url.url, uwcf->uwsgi_lengths->elts, 0,
+                            uwcf->uwsgi_values->elts)
+        == NULL)
+    {
+        return NGX_ERROR;
+    }
+
+    if (url.url.len > 8
+        && ngx_strncasecmp(url.url.data, (u_char *) "uwsgi://", 8) == 0)
+    {
+        add = 8;
+
+    } else if (url.url.len > 9
+               && ngx_strncasecmp(url.url.data, (u_char *) "suwsgi://", 9) == 0)
+    {
+
+#if (NGX_HTTP_SSL)
+        add = 9;
+        r->upstream->ssl = 1;
+#else
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "suwsgi protocol requires SSL support");
+        return NGX_ERROR;
+#endif
+
+    } else {
+        add = 0;
+    }
+
+    u = r->upstream;
+
+    if (add) {
+        u->schema.len = add;
+        u->schema.data = url.url.data;
+
+        url.url.data += add;
+        url.url.len -= add;
+
+    } else {
+        ngx_str_set(&u->schema, "uwsgi://");
+    }
+
+    url.no_resolve = 1;
+
+    if (ngx_parse_url(r->pool, &url) != NGX_OK) {
+        if (url.err) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "%s in upstream \"%V\"", url.err, &url.url);
+        }
+
+        return NGX_ERROR;
+    }
+
+    u->resolved = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t));
+    if (u->resolved == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (url.addrs) {
+        u->resolved->sockaddr = url.addrs[0].sockaddr;
+        u->resolved->socklen = url.addrs[0].socklen;
+        u->resolved->name = url.addrs[0].name;
+        u->resolved->naddrs = 1;
+    }
+
+    u->resolved->host = url.host;
+    u->resolved->port = url.port;
+    u->resolved->no_port = url.no_port;
+
+    return NGX_OK;
+}
+
+
+#if (NGX_HTTP_CACHE)
+
+static ngx_int_t
+ngx_http_uwsgi_create_key(ngx_http_request_t *r)
+{
+    ngx_str_t                  *key;
+    ngx_http_uwsgi_loc_conf_t  *uwcf;
+
+    key = ngx_array_push(&r->cache->keys);
+    if (key == NULL) {
+        return NGX_ERROR;
+    }
+
+    uwcf = ngx_http_get_module_loc_conf(r, ngx_http_uwsgi_module);
+
+    if (ngx_http_complex_value(r, &uwcf->cache_key, key) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_uwsgi_create_request(ngx_http_request_t *r)
+{
+    u_char                        ch, *lowcase_key;
+    size_t                        key_len, val_len, len, allocated;
+    ngx_uint_t                    i, n, hash, skip_empty, header_params;
+    ngx_buf_t                    *b;
+    ngx_chain_t                  *cl, *body;
+    ngx_list_part_t              *part;
+    ngx_table_elt_t              *header, **ignored;
+    ngx_http_uwsgi_params_t      *params;
+    ngx_http_script_code_pt       code;
+    ngx_http_script_engine_t      e, le;
+    ngx_http_uwsgi_loc_conf_t    *uwcf;
+    ngx_http_script_len_code_pt   lcode;
+
+    len = 0;
+    header_params = 0;
+    ignored = NULL;
+
+    uwcf = ngx_http_get_module_loc_conf(r, ngx_http_uwsgi_module);
+
+#if (NGX_HTTP_CACHE)
+    params = r->upstream->cacheable ? &uwcf->params_cache : &uwcf->params;
+#else
+    params = &uwcf->params;
+#endif
+
+    if (params->lengths) {
+        ngx_memzero(&le, sizeof(ngx_http_script_engine_t));
+
+        ngx_http_script_flush_no_cacheable_variables(r, params->flushes);
+        le.flushed = 1;
+
+        le.ip = params->lengths->elts;
+        le.request = r;
+
+        while (*(uintptr_t *) le.ip) {
+
+            lcode = *(ngx_http_script_len_code_pt *) le.ip;
+            key_len = lcode(&le);
+
+            lcode = *(ngx_http_script_len_code_pt *) le.ip;
+            skip_empty = lcode(&le);
+
+            for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) {
+                lcode = *(ngx_http_script_len_code_pt *) le.ip;
+            }
+            le.ip += sizeof(uintptr_t);
+
+            if (skip_empty && val_len == 0) {
+                continue;
+            }
+
+            len += 2 + key_len + 2 + val_len;
+        }
+    }
+
+    if (uwcf->upstream.pass_request_headers) {
+
+        allocated = 0;
+        lowcase_key = NULL;
+
+        if (params->number) {
+            n = 0;
+            part = &r->headers_in.headers.part;
+
+            while (part) {
+                n += part->nelts;
+                part = part->next;
+            }
+
+            ignored = ngx_palloc(r->pool, n * sizeof(void *));
+            if (ignored == NULL) {
+                return NGX_ERROR;
+            }
+        }
+
+        part = &r->headers_in.headers.part;
+        header = part->elts;
+
+        for (i = 0; /* void */ ; i++) {
+
+            if (i >= part->nelts) {
+                if (part->next == NULL) {
+                    break;
+                }
+
+                part = part->next;
+                header = part->elts;
+                i = 0;
+            }
+
+            if (params->number) {
+                if (allocated < header[i].key.len) {
+                    allocated = header[i].key.len + 16;
+                    lowcase_key = ngx_pnalloc(r->pool, allocated);
+                    if (lowcase_key == NULL) {
+                        return NGX_ERROR;
+                    }
+                }
+
+                hash = 0;
+
+                for (n = 0; n < header[i].key.len; n++) {
+                    ch = header[i].key.data[n];
+
+                    if (ch >= 'A' && ch <= 'Z') {
+                        ch |= 0x20;
+
+                    } else if (ch == '-') {
+                        ch = '_';
+                    }
+
+                    hash = ngx_hash(hash, ch);
+                    lowcase_key[n] = ch;
+                }
+
+                if (ngx_hash_find(&params->hash, hash, lowcase_key, n)) {
+                    ignored[header_params++] = &header[i];
+                    continue;
+                }
+            }
+
+            len += 2 + sizeof("HTTP_") - 1 + header[i].key.len
+                 + 2 + header[i].value.len;
+        }
+    }
+
+    len += uwcf->uwsgi_string.len;
+
+#if 0
+    /* allow custom uwsgi packet */
+    if (len > 0 && len < 2) {
+        ngx_log_error (NGX_LOG_ALERT, r->connection->log, 0,
+                       "uwsgi request is too little: %uz", len);
+        return NGX_ERROR;
+    }
+#endif
+
+    b = ngx_create_temp_buf(r->pool, len + 4);
+    if (b == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl = ngx_alloc_chain_link(r->pool);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl->buf = b;
+
+    *b->last++ = (u_char) uwcf->modifier1;
+    *b->last++ = (u_char) (len & 0xff);
+    *b->last++ = (u_char) ((len >> 8) & 0xff);
+    *b->last++ = (u_char) uwcf->modifier2;
+
+    if (params->lengths) {
+        ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
+
+        e.ip = params->values->elts;
+        e.pos = b->last;
+        e.request = r;
+        e.flushed = 1;
+
+        le.ip = params->lengths->elts;
+
+        while (*(uintptr_t *) le.ip) {
+
+            lcode = *(ngx_http_script_len_code_pt *) le.ip;
+            key_len = (u_char) lcode(&le);
+
+            lcode = *(ngx_http_script_len_code_pt *) le.ip;
+            skip_empty = lcode(&le);
+
+            for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) {
+                lcode = *(ngx_http_script_len_code_pt *) le.ip;
+            }
+            le.ip += sizeof(uintptr_t);
+
+            if (skip_empty && val_len == 0) {
+                e.skip = 1;
+
+                while (*(uintptr_t *) e.ip) {
+                    code = *(ngx_http_script_code_pt *) e.ip;
+                    code((ngx_http_script_engine_t *) &e);
+                }
+                e.ip += sizeof(uintptr_t);
+
+                e.skip = 0;
+
+                continue;
+            }
+
+            *e.pos++ = (u_char) (key_len & 0xff);
+            *e.pos++ = (u_char) ((key_len >> 8) & 0xff);
+
+            code = *(ngx_http_script_code_pt *) e.ip;
+            code((ngx_http_script_engine_t *) &e);
+
+            *e.pos++ = (u_char) (val_len & 0xff);
+            *e.pos++ = (u_char) ((val_len >> 8) & 0xff);
+
+            while (*(uintptr_t *) e.ip) {
+                code = *(ngx_http_script_code_pt *) e.ip;
+                code((ngx_http_script_engine_t *) &e);
+            }
+
+            e.ip += sizeof(uintptr_t);
+
+            ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "uwsgi param: \"%*s: %*s\"",
+                           key_len, e.pos - (key_len + 2 + val_len),
+                           val_len, e.pos - val_len);
+        }
+
+        b->last = e.pos;
+    }
+
+    if (uwcf->upstream.pass_request_headers) {
+
+        part = &r->headers_in.headers.part;
+        header = part->elts;
+
+        for (i = 0; /* void */ ; i++) {
+
+            if (i >= part->nelts) {
+                if (part->next == NULL) {
+                    break;
+                }
+
+                part = part->next;
+                header = part->elts;
+                i = 0;
+            }
+
+            for (n = 0; n < header_params; n++) {
+                if (&header[i] == ignored[n]) {
+                    goto next;
+                }
+            }
+
+            key_len = sizeof("HTTP_") - 1 + header[i].key.len;
+            *b->last++ = (u_char) (key_len & 0xff);
+            *b->last++ = (u_char) ((key_len >> 8) & 0xff);
+
+            b->last = ngx_cpymem(b->last, "HTTP_", sizeof("HTTP_") - 1);
+            for (n = 0; n < header[i].key.len; n++) {
+                ch = header[i].key.data[n];
+
+                if (ch >= 'a' && ch <= 'z') {
+                    ch &= ~0x20;
+
+                } else if (ch == '-') {
+                    ch = '_';
+                }
+
+                *b->last++ = ch;
+            }
+
+            val_len = header[i].value.len;
+            *b->last++ = (u_char) (val_len & 0xff);
+            *b->last++ = (u_char) ((val_len >> 8) & 0xff);
+            b->last = ngx_copy(b->last, header[i].value.data, val_len);
+
+            ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "uwsgi param: \"%*s: %*s\"",
+                           key_len, b->last - (key_len + 2 + val_len),
+                           val_len, b->last - val_len);
+        next:
+
+            continue;
+        }
+    }
+
+    b->last = ngx_copy(b->last, uwcf->uwsgi_string.data,
+                       uwcf->uwsgi_string.len);
+
+    if (r->request_body_no_buffering) {
+        r->upstream->request_bufs = cl;
+
+    } else if (uwcf->upstream.pass_request_body) {
+        body = r->upstream->request_bufs;
+        r->upstream->request_bufs = cl;
+
+        while (body) {
+            b = ngx_alloc_buf(r->pool);
+            if (b == NULL) {
+                return NGX_ERROR;
+            }
+
+            ngx_memcpy(b, body->buf, sizeof(ngx_buf_t));
+
+            cl->next = ngx_alloc_chain_link(r->pool);
+            if (cl->next == NULL) {
+                return NGX_ERROR;
+            }
+
+            cl = cl->next;
+            cl->buf = b;
+
+            body = body->next;
+        }
+
+    } else {
+        r->upstream->request_bufs = cl;
+    }
+
+    cl->next = NULL;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_uwsgi_reinit_request(ngx_http_request_t *r)
+{
+    ngx_http_status_t  *status;
+
+    status = ngx_http_get_module_ctx(r, ngx_http_uwsgi_module);
+
+    if (status == NULL) {
+        return NGX_OK;
+    }
+
+    status->code = 0;
+    status->count = 0;
+    status->start = NULL;
+    status->end = NULL;
+
+    r->upstream->process_header = ngx_http_uwsgi_process_status_line;
+    r->state = 0;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_uwsgi_process_status_line(ngx_http_request_t *r)
+{
+    size_t                 len;
+    ngx_int_t              rc;
+    ngx_http_status_t     *status;
+    ngx_http_upstream_t   *u;
+
+    status = ngx_http_get_module_ctx(r, ngx_http_uwsgi_module);
+
+    if (status == NULL) {
+        return NGX_ERROR;
+    }
+
+    u = r->upstream;
+
+    rc = ngx_http_parse_status_line(r, &u->buffer, status);
+
+    if (rc == NGX_AGAIN) {
+        return rc;
+    }
+
+    if (rc == NGX_ERROR) {
+        u->process_header = ngx_http_uwsgi_process_header;
+        return ngx_http_uwsgi_process_header(r);
+    }
+
+    if (u->state && u->state->status == 0) {
+        u->state->status = status->code;
+    }
+
+    u->headers_in.status_n = status->code;
+
+    len = status->end - status->start;
+    u->headers_in.status_line.len = len;
+
+    u->headers_in.status_line.data = ngx_pnalloc(r->pool, len);
+    if (u->headers_in.status_line.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(u->headers_in.status_line.data, status->start, len);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http uwsgi status %ui \"%V\"",
+                   u->headers_in.status_n, &u->headers_in.status_line);
+
+    u->process_header = ngx_http_uwsgi_process_header;
+
+    return ngx_http_uwsgi_process_header(r);
+}
+
+
+static ngx_int_t
+ngx_http_uwsgi_process_header(ngx_http_request_t *r)
+{
+    ngx_str_t                      *status_line;
+    ngx_int_t                       rc, status;
+    ngx_table_elt_t                *h;
+    ngx_http_upstream_t            *u;
+    ngx_http_upstream_header_t     *hh;
+    ngx_http_upstream_main_conf_t  *umcf;
+
+    umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);
+
+    for ( ;; ) {
+
+        rc = ngx_http_parse_header_line(r, &r->upstream->buffer, 1);
+
+        if (rc == NGX_OK) {
+
+            /* a header line has been parsed successfully */
+
+            h = ngx_list_push(&r->upstream->headers_in.headers);
+            if (h == NULL) {
+                return NGX_ERROR;
+            }
+
+            h->hash = r->header_hash;
+
+            h->key.len = r->header_name_end - r->header_name_start;
+            h->value.len = r->header_end - r->header_start;
+
+            h->key.data = ngx_pnalloc(r->pool,
+                                      h->key.len + 1 + h->value.len + 1
+                                      + h->key.len);
+            if (h->key.data == NULL) {
+                h->hash = 0;
+                return NGX_ERROR;
+            }
+
+            h->value.data = h->key.data + h->key.len + 1;
+            h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1;
+
+            ngx_memcpy(h->key.data, r->header_name_start, h->key.len);
+            h->key.data[h->key.len] = '\0';
+            ngx_memcpy(h->value.data, r->header_start, h->value.len);
+            h->value.data[h->value.len] = '\0';
+
+            if (h->key.len == r->lowcase_index) {
+                ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);
+
+            } else {
+                ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
+            }
+
+            hh = ngx_hash_find(&umcf->headers_in_hash, h->hash,
+                               h->lowcase_key, h->key.len);
+
+            if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
+                return NGX_ERROR;
+            }
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http uwsgi header: \"%V: %V\"", &h->key, &h->value);
+
+            continue;
+        }
+
+        if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
+
+            /* a whole header has been parsed successfully */
+
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http uwsgi header done");
+
+            u = r->upstream;
+
+            if (u->headers_in.status_n) {
+                goto done;
+            }
+
+            if (u->headers_in.status) {
+                status_line = &u->headers_in.status->value;
+
+                status = ngx_atoi(status_line->data, 3);
+                if (status == NGX_ERROR) {
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                  "upstream sent invalid status \"%V\"",
+                                  status_line);
+                    return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+                }
+
+                u->headers_in.status_n = status;
+                u->headers_in.status_line = *status_line;
+
+            } else if (u->headers_in.location) {
+                u->headers_in.status_n = 302;
+                ngx_str_set(&u->headers_in.status_line,
+                            "302 Moved Temporarily");
+
+            } else {
+                u->headers_in.status_n = 200;
+                ngx_str_set(&u->headers_in.status_line, "200 OK");
+            }
+
+            if (u->state && u->state->status == 0) {
+                u->state->status = u->headers_in.status_n;
+            }
+
+        done:
+
+            if (u->headers_in.status_n == NGX_HTTP_SWITCHING_PROTOCOLS
+                && r->headers_in.upgrade)
+            {
+                u->upgrade = 1;
+            }
+
+            return NGX_OK;
+        }
+
+        if (rc == NGX_AGAIN) {
+            return NGX_AGAIN;
+        }
+
+        /* there was error while a header line parsing */
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "upstream sent invalid header");
+
+        return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+    }
+}
+
+
+static void
+ngx_http_uwsgi_abort_request(ngx_http_request_t *r)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "abort http uwsgi request");
+
+    return;
+}
+
+
+static void
+ngx_http_uwsgi_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "finalize http uwsgi request");
+
+    return;
+}
+
+
+static void *
+ngx_http_uwsgi_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_http_uwsgi_main_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_uwsgi_main_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+#if (NGX_HTTP_CACHE)
+    if (ngx_array_init(&conf->caches, cf->pool, 4,
+                       sizeof(ngx_http_file_cache_t *))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+#endif
+
+    return conf;
+}
+
+
+static void *
+ngx_http_uwsgi_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_uwsgi_loc_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_uwsgi_loc_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    conf->modifier1 = NGX_CONF_UNSET_UINT;
+    conf->modifier2 = NGX_CONF_UNSET_UINT;
+
+    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;
+    conf->upstream.force_ranges = NGX_CONF_UNSET;
+
+    conf->upstream.local = NGX_CONF_UNSET_PTR;
+
+    conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.next_upstream_timeout = NGX_CONF_UNSET_MSEC;
+
+    conf->upstream.send_lowat = NGX_CONF_UNSET_SIZE;
+    conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE;
+    conf->upstream.limit_rate = NGX_CONF_UNSET_SIZE;
+
+    conf->upstream.busy_buffers_size_conf = NGX_CONF_UNSET_SIZE;
+    conf->upstream.max_temp_file_size_conf = NGX_CONF_UNSET_SIZE;
+    conf->upstream.temp_file_write_size_conf = NGX_CONF_UNSET_SIZE;
+
+    conf->upstream.pass_request_headers = NGX_CONF_UNSET;
+    conf->upstream.pass_request_body = NGX_CONF_UNSET;
+
+#if (NGX_HTTP_CACHE)
+    conf->upstream.cache = NGX_CONF_UNSET;
+    conf->upstream.cache_min_uses = NGX_CONF_UNSET_UINT;
+    conf->upstream.cache_max_range_offset = NGX_CONF_UNSET;
+    conf->upstream.cache_bypass = NGX_CONF_UNSET_PTR;
+    conf->upstream.no_cache = NGX_CONF_UNSET_PTR;
+    conf->upstream.cache_valid = NGX_CONF_UNSET_PTR;
+    conf->upstream.cache_lock = NGX_CONF_UNSET;
+    conf->upstream.cache_lock_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.cache_lock_age = NGX_CONF_UNSET_MSEC;
+    conf->upstream.cache_revalidate = NGX_CONF_UNSET;
+    conf->upstream.cache_background_update = NGX_CONF_UNSET;
+#endif
+
+    conf->upstream.hide_headers = NGX_CONF_UNSET_PTR;
+    conf->upstream.pass_headers = NGX_CONF_UNSET_PTR;
+
+    conf->upstream.intercept_errors = NGX_CONF_UNSET;
+
+#if (NGX_HTTP_SSL)
+    conf->upstream.ssl_session_reuse = NGX_CONF_UNSET;
+    conf->upstream.ssl_server_name = NGX_CONF_UNSET;
+    conf->upstream.ssl_verify = NGX_CONF_UNSET;
+    conf->ssl_verify_depth = NGX_CONF_UNSET_UINT;
+    conf->ssl_passwords = NGX_CONF_UNSET_PTR;
+#endif
+
+    /* "uwsgi_cyclic_temp_file" is disabled */
+    conf->upstream.cyclic_temp_file = 0;
+
+    conf->upstream.change_buffering = 1;
+
+    ngx_str_set(&conf->upstream.module, "uwsgi");
+
+    return conf;
+}
+
+
+static char *
+ngx_http_uwsgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_uwsgi_loc_conf_t *prev = parent;
+    ngx_http_uwsgi_loc_conf_t *conf = child;
+
+    size_t                        size;
+    ngx_int_t                     rc;
+    ngx_hash_init_t               hash;
+    ngx_http_core_loc_conf_t     *clcf;
+
+#if (NGX_HTTP_CACHE)
+
+    if (conf->upstream.store > 0) {
+        conf->upstream.cache = 0;
+    }
+
+    if (conf->upstream.cache > 0) {
+        conf->upstream.store = 0;
+    }
+
+#endif
+
+    if (conf->upstream.store == NGX_CONF_UNSET) {
+        ngx_conf_merge_value(conf->upstream.store, prev->upstream.store, 0);
+
+        conf->upstream.store_lengths = prev->upstream.store_lengths;
+        conf->upstream.store_values = prev->upstream.store_values;
+    }
+
+    ngx_conf_merge_uint_value(conf->upstream.store_access,
+                              prev->upstream.store_access, 0600);
+
+    ngx_conf_merge_uint_value(conf->upstream.next_upstream_tries,
+                              prev->upstream.next_upstream_tries, 0);
+
+    ngx_conf_merge_value(conf->upstream.buffering,
+                              prev->upstream.buffering, 1);
+
+    ngx_conf_merge_value(conf->upstream.request_buffering,
+                              prev->upstream.request_buffering, 1);
+
+    ngx_conf_merge_value(conf->upstream.ignore_client_abort,
+                              prev->upstream.ignore_client_abort, 0);
+
+    ngx_conf_merge_value(conf->upstream.force_ranges,
+                              prev->upstream.force_ranges, 0);
+
+    ngx_conf_merge_ptr_value(conf->upstream.local,
+                              prev->upstream.local, NULL);
+
+    ngx_conf_merge_msec_value(conf->upstream.connect_timeout,
+                              prev->upstream.connect_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->upstream.send_timeout,
+                              prev->upstream.send_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->upstream.read_timeout,
+                              prev->upstream.read_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->upstream.next_upstream_timeout,
+                              prev->upstream.next_upstream_timeout, 0);
+
+    ngx_conf_merge_size_value(conf->upstream.send_lowat,
+                              prev->upstream.send_lowat, 0);
+
+    ngx_conf_merge_size_value(conf->upstream.buffer_size,
+                              prev->upstream.buffer_size,
+                              (size_t) ngx_pagesize);
+
+    ngx_conf_merge_size_value(conf->upstream.limit_rate,
+                              prev->upstream.limit_rate, 0);
+
+
+    ngx_conf_merge_bufs_value(conf->upstream.bufs, prev->upstream.bufs,
+                              8, ngx_pagesize);
+
+    if (conf->upstream.bufs.num < 2) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "there must be at least 2 \"uwsgi_buffers\"");
+        return NGX_CONF_ERROR;
+    }
+
+
+    size = conf->upstream.buffer_size;
+    if (size < conf->upstream.bufs.size) {
+        size = conf->upstream.bufs.size;
+    }
+
+
+    ngx_conf_merge_size_value(conf->upstream.busy_buffers_size_conf,
+                              prev->upstream.busy_buffers_size_conf,
+                              NGX_CONF_UNSET_SIZE);
+
+    if (conf->upstream.busy_buffers_size_conf == NGX_CONF_UNSET_SIZE) {
+        conf->upstream.busy_buffers_size = 2 * size;
+    } else {
+        conf->upstream.busy_buffers_size =
+            conf->upstream.busy_buffers_size_conf;
+    }
+
+    if (conf->upstream.busy_buffers_size < size) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+            "\"uwsgi_busy_buffers_size\" must be equal to or greater "
+            "than the maximum of the value of \"uwsgi_buffer_size\" and "
+            "one of the \"uwsgi_buffers\"");
+
+        return NGX_CONF_ERROR;
+    }
+
+    if (conf->upstream.busy_buffers_size
+        > (conf->upstream.bufs.num - 1) * conf->upstream.bufs.size)
+    {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+            "\"uwsgi_busy_buffers_size\" must be less than "
+            "the size of all \"uwsgi_buffers\" minus one buffer");
+
+        return NGX_CONF_ERROR;
+    }
+
+
+    ngx_conf_merge_size_value(conf->upstream.temp_file_write_size_conf,
+                              prev->upstream.temp_file_write_size_conf,
+                              NGX_CONF_UNSET_SIZE);
+
+    if (conf->upstream.temp_file_write_size_conf == NGX_CONF_UNSET_SIZE) {
+        conf->upstream.temp_file_write_size = 2 * size;
+    } else {
+        conf->upstream.temp_file_write_size =
+            conf->upstream.temp_file_write_size_conf;
+    }
+
+    if (conf->upstream.temp_file_write_size < size) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+            "\"uwsgi_temp_file_write_size\" must be equal to or greater than "
+            "the maximum of the value of \"uwsgi_buffer_size\" and "
+            "one of the \"uwsgi_buffers\"");
+
+        return NGX_CONF_ERROR;
+    }
+
+
+    ngx_conf_merge_size_value(conf->upstream.max_temp_file_size_conf,
+                              prev->upstream.max_temp_file_size_conf,
+                              NGX_CONF_UNSET_SIZE);
+
+    if (conf->upstream.max_temp_file_size_conf == NGX_CONF_UNSET_SIZE) {
+        conf->upstream.max_temp_file_size = 1024 * 1024 * 1024;
+    } else {
+        conf->upstream.max_temp_file_size =
+            conf->upstream.max_temp_file_size_conf;
+    }
+
+    if (conf->upstream.max_temp_file_size != 0
+        && conf->upstream.max_temp_file_size < size)
+    {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+            "\"uwsgi_max_temp_file_size\" must be equal to zero to disable "
+            "temporary files usage or must be equal to or greater than "
+            "the maximum of the value of \"uwsgi_buffer_size\" and "
+            "one of the \"uwsgi_buffers\"");
+
+        return NGX_CONF_ERROR;
+    }
+
+
+    ngx_conf_merge_bitmask_value(conf->upstream.ignore_headers,
+                                 prev->upstream.ignore_headers,
+                                 NGX_CONF_BITMASK_SET);
+
+
+    ngx_conf_merge_bitmask_value(conf->upstream.next_upstream,
+                                 prev->upstream.next_upstream,
+                                 (NGX_CONF_BITMASK_SET
+                                  |NGX_HTTP_UPSTREAM_FT_ERROR
+                                  |NGX_HTTP_UPSTREAM_FT_TIMEOUT));
+
+    if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) {
+        conf->upstream.next_upstream = NGX_CONF_BITMASK_SET
+                                       |NGX_HTTP_UPSTREAM_FT_OFF;
+    }
+
+    if (ngx_conf_merge_path_value(cf, &conf->upstream.temp_path,
+                                  prev->upstream.temp_path,
+                                  &ngx_http_uwsgi_temp_path)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+#if (NGX_HTTP_CACHE)
+
+    if (conf->upstream.cache == NGX_CONF_UNSET) {
+        ngx_conf_merge_value(conf->upstream.cache,
+                              prev->upstream.cache, 0);
+
+        conf->upstream.cache_zone = prev->upstream.cache_zone;
+        conf->upstream.cache_value = prev->upstream.cache_value;
+    }
+
+    if (conf->upstream.cache_zone && conf->upstream.cache_zone->data == NULL) {
+        ngx_shm_zone_t  *shm_zone;
+
+        shm_zone = conf->upstream.cache_zone;
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"uwsgi_cache\" zone \"%V\" is unknown",
+                           &shm_zone->shm.name);
+
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_conf_merge_uint_value(conf->upstream.cache_min_uses,
+                              prev->upstream.cache_min_uses, 1);
+
+    ngx_conf_merge_off_value(conf->upstream.cache_max_range_offset,
+                              prev->upstream.cache_max_range_offset,
+                              NGX_MAX_OFF_T_VALUE);
+
+    ngx_conf_merge_bitmask_value(conf->upstream.cache_use_stale,
+                              prev->upstream.cache_use_stale,
+                              (NGX_CONF_BITMASK_SET
+                               |NGX_HTTP_UPSTREAM_FT_OFF));
+
+    if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_OFF) {
+        conf->upstream.cache_use_stale = NGX_CONF_BITMASK_SET
+                                         |NGX_HTTP_UPSTREAM_FT_OFF;
+    }
+
+    if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_ERROR) {
+        conf->upstream.cache_use_stale |= NGX_HTTP_UPSTREAM_FT_NOLIVE;
+    }
+
+    if (conf->upstream.cache_methods == 0) {
+        conf->upstream.cache_methods = prev->upstream.cache_methods;
+    }
+
+    conf->upstream.cache_methods |= NGX_HTTP_GET|NGX_HTTP_HEAD;
+
+    ngx_conf_merge_ptr_value(conf->upstream.cache_bypass,
+                             prev->upstream.cache_bypass, NULL);
+
+    ngx_conf_merge_ptr_value(conf->upstream.no_cache,
+                             prev->upstream.no_cache, NULL);
+
+    ngx_conf_merge_ptr_value(conf->upstream.cache_valid,
+                             prev->upstream.cache_valid, NULL);
+
+    if (conf->cache_key.value.data == NULL) {
+        conf->cache_key = prev->cache_key;
+    }
+
+    if (conf->upstream.cache && conf->cache_key.value.data == NULL) {
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                           "no \"uwsgi_cache_key\" for \"uwsgi_cache\"");
+    }
+
+    ngx_conf_merge_value(conf->upstream.cache_lock,
+                              prev->upstream.cache_lock, 0);
+
+    ngx_conf_merge_msec_value(conf->upstream.cache_lock_timeout,
+                              prev->upstream.cache_lock_timeout, 5000);
+
+    ngx_conf_merge_msec_value(conf->upstream.cache_lock_age,
+                              prev->upstream.cache_lock_age, 5000);
+
+    ngx_conf_merge_value(conf->upstream.cache_revalidate,
+                              prev->upstream.cache_revalidate, 0);
+
+    ngx_conf_merge_value(conf->upstream.cache_background_update,
+                              prev->upstream.cache_background_update, 0);
+
+#endif
+
+    ngx_conf_merge_value(conf->upstream.pass_request_headers,
+                         prev->upstream.pass_request_headers, 1);
+    ngx_conf_merge_value(conf->upstream.pass_request_body,
+                         prev->upstream.pass_request_body, 1);
+
+    ngx_conf_merge_value(conf->upstream.intercept_errors,
+                         prev->upstream.intercept_errors, 0);
+
+#if (NGX_HTTP_SSL)
+
+    ngx_conf_merge_value(conf->upstream.ssl_session_reuse,
+                              prev->upstream.ssl_session_reuse, 1);
+
+    ngx_conf_merge_bitmask_value(conf->ssl_protocols, prev->ssl_protocols,
+                                 (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1
+                                  |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2));
+
+    ngx_conf_merge_str_value(conf->ssl_ciphers, prev->ssl_ciphers,
+                             "DEFAULT");
+
+    if (conf->upstream.ssl_name == NULL) {
+        conf->upstream.ssl_name = prev->upstream.ssl_name;
+    }
+
+    ngx_conf_merge_value(conf->upstream.ssl_server_name,
+                              prev->upstream.ssl_server_name, 0);
+    ngx_conf_merge_value(conf->upstream.ssl_verify,
+                              prev->upstream.ssl_verify, 0);
+    ngx_conf_merge_uint_value(conf->ssl_verify_depth,
+                              prev->ssl_verify_depth, 1);
+    ngx_conf_merge_str_value(conf->ssl_trusted_certificate,
+                              prev->ssl_trusted_certificate, "");
+    ngx_conf_merge_str_value(conf->ssl_crl, prev->ssl_crl, "");
+
+    ngx_conf_merge_str_value(conf->ssl_certificate,
+                              prev->ssl_certificate, "");
+    ngx_conf_merge_str_value(conf->ssl_certificate_key,
+                              prev->ssl_certificate_key, "");
+    ngx_conf_merge_ptr_value(conf->ssl_passwords, prev->ssl_passwords, NULL);
+
+    if (conf->ssl && ngx_http_uwsgi_set_ssl(cf, conf) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+#endif
+
+    ngx_conf_merge_str_value(conf->uwsgi_string, prev->uwsgi_string, "");
+
+    hash.max_size = 512;
+    hash.bucket_size = ngx_align(64, ngx_cacheline_size);
+    hash.name = "uwsgi_hide_headers_hash";
+
+    if (ngx_http_upstream_hide_headers_hash(cf, &conf->upstream,
+            &prev->upstream, ngx_http_uwsgi_hide_headers, &hash)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+    if (clcf->noname
+        && conf->upstream.upstream == NULL && conf->uwsgi_lengths == NULL)
+    {
+        conf->upstream.upstream = prev->upstream.upstream;
+
+        conf->uwsgi_lengths = prev->uwsgi_lengths;
+        conf->uwsgi_values = prev->uwsgi_values;
+
+#if (NGX_HTTP_SSL)
+        conf->upstream.ssl = prev->upstream.ssl;
+#endif
+    }
+
+    if (clcf->lmt_excpt && clcf->handler == NULL
+        && (conf->upstream.upstream || conf->uwsgi_lengths))
+    {
+        clcf->handler = ngx_http_uwsgi_handler;
+    }
+
+    ngx_conf_merge_uint_value(conf->modifier1, prev->modifier1, 0);
+    ngx_conf_merge_uint_value(conf->modifier2, prev->modifier2, 0);
+
+    if (conf->params_source == NULL) {
+        conf->params = prev->params;
+#if (NGX_HTTP_CACHE)
+        conf->params_cache = prev->params_cache;
+#endif
+        conf->params_source = prev->params_source;
+    }
+
+    rc = ngx_http_uwsgi_init_params(cf, conf, &conf->params, NULL);
+    if (rc != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+#if (NGX_HTTP_CACHE)
+
+    if (conf->upstream.cache) {
+        rc = ngx_http_uwsgi_init_params(cf, conf, &conf->params_cache,
+                                        ngx_http_uwsgi_cache_headers);
+        if (rc != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+#endif
+
+    /*
+     * special handling to preserve conf->params in the "http" section
+     * to inherit it to all servers
+     */
+
+    if (prev->params.hash.buckets == NULL
+        && conf->params_source == prev->params_source)
+    {
+        prev->params = conf->params;
+#if (NGX_HTTP_CACHE)
+        prev->params_cache = conf->params_cache;
+#endif
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_uwsgi_init_params(ngx_conf_t *cf, ngx_http_uwsgi_loc_conf_t *conf,
+    ngx_http_uwsgi_params_t *params, ngx_keyval_t *default_params)
+{
+    u_char                       *p;
+    size_t                        size;
+    uintptr_t                    *code;
+    ngx_uint_t                    i, nsrc;
+    ngx_array_t                   headers_names, params_merged;
+    ngx_keyval_t                 *h;
+    ngx_hash_key_t               *hk;
+    ngx_hash_init_t               hash;
+    ngx_http_upstream_param_t    *src, *s;
+    ngx_http_script_compile_t     sc;
+    ngx_http_script_copy_code_t  *copy;
+
+    if (params->hash.buckets) {
+        return NGX_OK;
+    }
+
+    if (conf->params_source == NULL && default_params == NULL) {
+        params->hash.buckets = (void *) 1;
+        return NGX_OK;
+    }
+
+    params->lengths = ngx_array_create(cf->pool, 64, 1);
+    if (params->lengths == NULL) {
+        return NGX_ERROR;
+    }
+
+    params->values = ngx_array_create(cf->pool, 512, 1);
+    if (params->values == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (conf->params_source) {
+        src = conf->params_source->elts;
+        nsrc = conf->params_source->nelts;
+
+    } else {
+        src = NULL;
+        nsrc = 0;
+    }
+
+    if (default_params) {
+        if (ngx_array_init(&params_merged, cf->temp_pool, 4,
+                           sizeof(ngx_http_upstream_param_t))
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+
+        for (i = 0; i < nsrc; i++) {
+
+            s = ngx_array_push(&params_merged);
+            if (s == NULL) {
+                return NGX_ERROR;
+            }
+
+            *s = src[i];
+        }
+
+        h = default_params;
+
+        while (h->key.len) {
+
+            src = params_merged.elts;
+            nsrc = params_merged.nelts;
+
+            for (i = 0; i < nsrc; i++) {
+                if (ngx_strcasecmp(h->key.data, src[i].key.data) == 0) {
+                    goto next;
+                }
+            }
+
+            s = ngx_array_push(&params_merged);
+            if (s == NULL) {
+                return NGX_ERROR;
+            }
+
+            s->key = h->key;
+            s->value = h->value;
+            s->skip_empty = 1;
+
+        next:
+
+            h++;
+        }
+
+        src = params_merged.elts;
+        nsrc = params_merged.nelts;
+    }
+
+    for (i = 0; i < nsrc; i++) {
+
+        if (src[i].key.len > sizeof("HTTP_") - 1
+            && ngx_strncmp(src[i].key.data, "HTTP_", sizeof("HTTP_") - 1) == 0)
+        {
+            hk = ngx_array_push(&headers_names);
+            if (hk == NULL) {
+                return NGX_ERROR;
+            }
+
+            hk->key.len = src[i].key.len - 5;
+            hk->key.data = src[i].key.data + 5;
+            hk->key_hash = ngx_hash_key_lc(hk->key.data, hk->key.len);
+            hk->value = (void *) 1;
+
+            if (src[i].value.len == 0) {
+                continue;
+            }
+        }
+
+        copy = ngx_array_push_n(params->lengths,
+                                sizeof(ngx_http_script_copy_code_t));
+        if (copy == NULL) {
+            return NGX_ERROR;
+        }
+
+        copy->code = (ngx_http_script_code_pt) (void *)
+                                                 ngx_http_script_copy_len_code;
+        copy->len = src[i].key.len;
+
+        copy = ngx_array_push_n(params->lengths,
+                                sizeof(ngx_http_script_copy_code_t));
+        if (copy == NULL) {
+            return NGX_ERROR;
+        }
+
+        copy->code = (ngx_http_script_code_pt) (void *)
+                                                 ngx_http_script_copy_len_code;
+        copy->len = src[i].skip_empty;
+
+
+        size = (sizeof(ngx_http_script_copy_code_t)
+                + src[i].key.len + sizeof(uintptr_t) - 1)
+               & ~(sizeof(uintptr_t) - 1);
+
+        copy = ngx_array_push_n(params->values, size);
+        if (copy == NULL) {
+            return NGX_ERROR;
+        }
+
+        copy->code = ngx_http_script_copy_code;
+        copy->len = src[i].key.len;
+
+        p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t);
+        ngx_memcpy(p, src[i].key.data, src[i].key.len);
+
+
+        ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+        sc.cf = cf;
+        sc.source = &src[i].value;
+        sc.flushes = &params->flushes;
+        sc.lengths = &params->lengths;
+        sc.values = &params->values;
+
+        if (ngx_http_script_compile(&sc) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        code = ngx_array_push_n(params->lengths, sizeof(uintptr_t));
+        if (code == NULL) {
+            return NGX_ERROR;
+        }
+
+        *code = (uintptr_t) NULL;
+
+
+        code = ngx_array_push_n(params->values, sizeof(uintptr_t));
+        if (code == NULL) {
+            return NGX_ERROR;
+        }
+
+        *code = (uintptr_t) NULL;
+    }
+
+    code = ngx_array_push_n(params->lengths, sizeof(uintptr_t));
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    *code = (uintptr_t) NULL;
+
+    params->number = headers_names.nelts;
+
+    hash.hash = &params->hash;
+    hash.key = ngx_hash_key_lc;
+    hash.max_size = 512;
+    hash.bucket_size = 64;
+    hash.name = "uwsgi_params_hash";
+    hash.pool = cf->pool;
+    hash.temp_pool = NULL;
+
+    return ngx_hash_init(&hash, headers_names.elts, headers_names.nelts);
+}
+
+
+static char *
+ngx_http_uwsgi_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_uwsgi_loc_conf_t *uwcf = conf;
+
+    size_t                      add;
+    ngx_url_t                   u;
+    ngx_str_t                  *value, *url;
+    ngx_uint_t                  n;
+    ngx_http_core_loc_conf_t   *clcf;
+    ngx_http_script_compile_t   sc;
+
+    if (uwcf->upstream.upstream || uwcf->uwsgi_lengths) {
+        return "is duplicate";
+    }
+
+    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+    clcf->handler = ngx_http_uwsgi_handler;
+
+    value = cf->args->elts;
+
+    url = &value[1];
+
+    n = ngx_http_script_variables_count(url);
+
+    if (n) {
+
+        ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+        sc.cf = cf;
+        sc.source = url;
+        sc.lengths = &uwcf->uwsgi_lengths;
+        sc.values = &uwcf->uwsgi_values;
+        sc.variables = n;
+        sc.complete_lengths = 1;
+        sc.complete_values = 1;
+
+        if (ngx_http_script_compile(&sc) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+#if (NGX_HTTP_SSL)
+        uwcf->ssl = 1;
+#endif
+
+        return NGX_CONF_OK;
+    }
+
+    if (ngx_strncasecmp(url->data, (u_char *) "uwsgi://", 8) == 0) {
+        add = 8;
+
+    } else if (ngx_strncasecmp(url->data, (u_char *) "suwsgi://", 9) == 0) {
+
+#if (NGX_HTTP_SSL)
+        add = 9;
+        uwcf->ssl = 1;
+#else
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "suwsgi protocol requires SSL support");
+        return NGX_CONF_ERROR;
+#endif
+
+    } else {
+        add = 0;
+    }
+
+    ngx_memzero(&u, sizeof(ngx_url_t));
+
+    u.url.len = url->len - add;
+    u.url.data = url->data + add;
+    u.no_resolve = 1;
+
+    uwcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);
+    if (uwcf->upstream.upstream == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (clcf->name.data[clcf->name.len - 1] == '/') {
+        clcf->auto_redirect = 1;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_uwsgi_store(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_uwsgi_loc_conf_t *uwcf = conf;
+
+    ngx_str_t                  *value;
+    ngx_http_script_compile_t   sc;
+
+    if (uwcf->upstream.store != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    if (ngx_strcmp(value[1].data, "off") == 0) {
+        uwcf->upstream.store = 0;
+        return NGX_CONF_OK;
+    }
+
+#if (NGX_HTTP_CACHE)
+
+    if (uwcf->upstream.cache > 0) {
+        return "is incompatible with \"uwsgi_cache\"";
+    }
+
+#endif
+
+    uwcf->upstream.store = 1;
+
+    if (ngx_strcmp(value[1].data, "on") == 0) {
+        return NGX_CONF_OK;
+    }
+
+    /* include the terminating '\0' into script */
+    value[1].len++;
+
+    ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+    sc.cf = cf;
+    sc.source = &value[1];
+    sc.lengths = &uwcf->upstream.store_lengths;
+    sc.values = &uwcf->upstream.store_values;
+    sc.variables = ngx_http_script_variables_count(&value[1]);
+    sc.complete_lengths = 1;
+    sc.complete_values = 1;
+
+    if (ngx_http_script_compile(&sc) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+#if (NGX_HTTP_CACHE)
+
+static char *
+ngx_http_uwsgi_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_uwsgi_loc_conf_t *uwcf = conf;
+
+    ngx_str_t                         *value;
+    ngx_http_complex_value_t           cv;
+    ngx_http_compile_complex_value_t   ccv;
+
+    value = cf->args->elts;
+
+    if (uwcf->upstream.cache != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    if (ngx_strcmp(value[1].data, "off") == 0) {
+        uwcf->upstream.cache = 0;
+        return NGX_CONF_OK;
+    }
+
+    if (uwcf->upstream.store > 0) {
+        return "is incompatible with \"uwsgi_store\"";
+    }
+
+    uwcf->upstream.cache = 1;
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[1];
+    ccv.complex_value = &cv;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (cv.lengths != NULL) {
+
+        uwcf->upstream.cache_value = ngx_palloc(cf->pool,
+                                             sizeof(ngx_http_complex_value_t));
+        if (uwcf->upstream.cache_value == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        *uwcf->upstream.cache_value = cv;
+
+        return NGX_CONF_OK;
+    }
+
+    uwcf->upstream.cache_zone = ngx_shared_memory_add(cf, &value[1], 0,
+                                                      &ngx_http_uwsgi_module);
+    if (uwcf->upstream.cache_zone == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_uwsgi_cache_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_uwsgi_loc_conf_t *uwcf = conf;
+
+    ngx_str_t                         *value;
+    ngx_http_compile_complex_value_t   ccv;
+
+    value = cf->args->elts;
+
+    if (uwcf->cache_key.value.data) {
+        return "is duplicate";
+    }
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[1];
+    ccv.complex_value = &uwcf->cache_key;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+#endif
+
+
+#if (NGX_HTTP_SSL)
+
+static char *
+ngx_http_uwsgi_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_uwsgi_loc_conf_t *uwcf = conf;
+
+    ngx_str_t  *value;
+
+    if (uwcf->ssl_passwords != NGX_CONF_UNSET_PTR) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    uwcf->ssl_passwords = ngx_ssl_read_password_file(cf, &value[1]);
+
+    if (uwcf->ssl_passwords == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_uwsgi_set_ssl(ngx_conf_t *cf, ngx_http_uwsgi_loc_conf_t *uwcf)
+{
+    ngx_pool_cleanup_t  *cln;
+
+    uwcf->upstream.ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t));
+    if (uwcf->upstream.ssl == NULL) {
+        return NGX_ERROR;
+    }
+
+    uwcf->upstream.ssl->log = cf->log;
+
+    if (ngx_ssl_create(uwcf->upstream.ssl, uwcf->ssl_protocols, NULL)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    cln = ngx_pool_cleanup_add(cf->pool, 0);
+    if (cln == NULL) {
+        return NGX_ERROR;
+    }
+
+    cln->handler = ngx_ssl_cleanup_ctx;
+    cln->data = uwcf->upstream.ssl;
+
+    if (uwcf->ssl_certificate.len) {
+
+        if (uwcf->ssl_certificate_key.len == 0) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "no \"uwsgi_ssl_certificate_key\" is defined "
+                          "for certificate \"%V\"", &uwcf->ssl_certificate);
+            return NGX_ERROR;
+        }
+
+        if (ngx_ssl_certificate(cf, uwcf->upstream.ssl, &uwcf->ssl_certificate,
+                                &uwcf->ssl_certificate_key, uwcf->ssl_passwords)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    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,
+                      "no uwsgi_ssl_trusted_certificate for uwsgi_ssl_verify");
+            return NGX_ERROR;
+        }
+
+        if (ngx_ssl_trusted_certificate(cf, uwcf->upstream.ssl,
+                                        &uwcf->ssl_trusted_certificate,
+                                        uwcf->ssl_verify_depth)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+
+        if (ngx_ssl_crl(cf, uwcf->upstream.ssl, &uwcf->ssl_crl) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+#endif
diff --git a/nginx/src/http/modules/ngx_http_xslt_filter_module.c b/nginx/src/http/modules/ngx_http_xslt_filter_module.c
new file mode 100644 (file)
index 0000000..ea7ce2a
--- /dev/null
@@ -0,0 +1,1158 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxslt/xslt.h>
+#include <libxslt/xsltInternals.h>
+#include <libxslt/transform.h>
+#include <libxslt/variables.h>
+#include <libxslt/xsltutils.h>
+
+#if (NGX_HAVE_EXSLT)
+#include <libexslt/exslt.h>
+#endif
+
+
+#ifndef NGX_HTTP_XSLT_REUSE_DTD
+#define NGX_HTTP_XSLT_REUSE_DTD  1
+#endif
+
+
+typedef struct {
+    u_char                    *name;
+    void                      *data;
+} ngx_http_xslt_file_t;
+
+
+typedef struct {
+    ngx_array_t                dtd_files;    /* ngx_http_xslt_file_t */
+    ngx_array_t                sheet_files;  /* ngx_http_xslt_file_t */
+} ngx_http_xslt_filter_main_conf_t;
+
+
+typedef struct {
+    u_char                    *name;
+    ngx_http_complex_value_t   value;
+    ngx_uint_t                 quote;        /* unsigned  quote:1; */
+} ngx_http_xslt_param_t;
+
+
+typedef struct {
+    xsltStylesheetPtr          stylesheet;
+    ngx_array_t                params;       /* ngx_http_xslt_param_t */
+} ngx_http_xslt_sheet_t;
+
+
+typedef struct {
+    xmlDtdPtr                  dtd;
+    ngx_array_t                sheets;       /* ngx_http_xslt_sheet_t */
+    ngx_hash_t                 types;
+    ngx_array_t               *types_keys;
+    ngx_array_t               *params;       /* ngx_http_xslt_param_t */
+    ngx_flag_t                 last_modified;
+} ngx_http_xslt_filter_loc_conf_t;
+
+
+typedef struct {
+    xmlDocPtr                  doc;
+    xmlParserCtxtPtr           ctxt;
+    xsltTransformContextPtr    transform;
+    ngx_http_request_t        *request;
+    ngx_array_t                params;
+
+    ngx_uint_t                 done;         /* unsigned  done:1; */
+} ngx_http_xslt_filter_ctx_t;
+
+
+static ngx_int_t ngx_http_xslt_send(ngx_http_request_t *r,
+    ngx_http_xslt_filter_ctx_t *ctx, ngx_buf_t *b);
+static ngx_int_t ngx_http_xslt_add_chunk(ngx_http_request_t *r,
+    ngx_http_xslt_filter_ctx_t *ctx, ngx_buf_t *b);
+
+
+static void ngx_http_xslt_sax_external_subset(void *data, const xmlChar *name,
+    const xmlChar *externalId, const xmlChar *systemId);
+static void ngx_cdecl ngx_http_xslt_sax_error(void *data, const char *msg, ...);
+
+
+static ngx_buf_t *ngx_http_xslt_apply_stylesheet(ngx_http_request_t *r,
+    ngx_http_xslt_filter_ctx_t *ctx);
+static ngx_int_t ngx_http_xslt_params(ngx_http_request_t *r,
+    ngx_http_xslt_filter_ctx_t *ctx, ngx_array_t *params, ngx_uint_t final);
+static u_char *ngx_http_xslt_content_type(xsltStylesheetPtr s);
+static u_char *ngx_http_xslt_encoding(xsltStylesheetPtr s);
+static void ngx_http_xslt_cleanup(void *data);
+
+static char *ngx_http_xslt_entities(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_xslt_stylesheet(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_xslt_param(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static void ngx_http_xslt_cleanup_dtd(void *data);
+static void ngx_http_xslt_cleanup_stylesheet(void *data);
+static void *ngx_http_xslt_filter_create_main_conf(ngx_conf_t *cf);
+static void *ngx_http_xslt_filter_create_conf(ngx_conf_t *cf);
+static char *ngx_http_xslt_filter_merge_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static ngx_int_t ngx_http_xslt_filter_preconfiguration(ngx_conf_t *cf);
+static ngx_int_t ngx_http_xslt_filter_init(ngx_conf_t *cf);
+static void ngx_http_xslt_filter_exit(ngx_cycle_t *cycle);
+
+
+static ngx_str_t  ngx_http_xslt_default_types[] = {
+    ngx_string("text/xml"),
+    ngx_null_string
+};
+
+
+static ngx_command_t  ngx_http_xslt_filter_commands[] = {
+
+    { ngx_string("xml_entities"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_xslt_entities,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("xslt_stylesheet"),
+      NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_xslt_stylesheet,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("xslt_param"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+      ngx_http_xslt_param,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("xslt_string_param"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+      ngx_http_xslt_param,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      (void *) 1 },
+
+    { ngx_string("xslt_types"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_types_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_xslt_filter_loc_conf_t, types_keys),
+      &ngx_http_xslt_default_types[0] },
+
+    { ngx_string("xslt_last_modified"),
+      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_xslt_filter_loc_conf_t, last_modified),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_xslt_filter_module_ctx = {
+    ngx_http_xslt_filter_preconfiguration, /* preconfiguration */
+    ngx_http_xslt_filter_init,             /* postconfiguration */
+
+    ngx_http_xslt_filter_create_main_conf, /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_xslt_filter_create_conf,      /* create location configuration */
+    ngx_http_xslt_filter_merge_conf        /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_xslt_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_xslt_filter_module_ctx,      /* module context */
+    ngx_http_xslt_filter_commands,         /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    ngx_http_xslt_filter_exit,             /* exit process */
+    ngx_http_xslt_filter_exit,             /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_xslt_header_filter(ngx_http_request_t *r)
+{
+    ngx_http_xslt_filter_ctx_t       *ctx;
+    ngx_http_xslt_filter_loc_conf_t  *conf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "xslt filter header");
+
+    if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_xslt_filter_module);
+
+    if (conf->sheets.nelts == 0
+        || ngx_http_test_content_type(r, &conf->types) == NULL)
+    {
+        return ngx_http_next_header_filter(r);
+    }
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_xslt_filter_module);
+
+    if (ctx) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_xslt_filter_ctx_t));
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_http_set_ctx(r, ctx, ngx_http_xslt_filter_module);
+
+    r->main_filter_need_in_memory = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_xslt_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    int                          wellFormed;
+    ngx_chain_t                 *cl;
+    ngx_http_xslt_filter_ctx_t  *ctx;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "xslt filter body");
+
+    if (in == NULL) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_xslt_filter_module);
+
+    if (ctx == NULL || ctx->done) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    for (cl = in; cl; cl = cl->next) {
+
+        if (ngx_http_xslt_add_chunk(r, ctx, cl->buf) != NGX_OK) {
+
+            if (ctx->ctxt->myDoc) {
+
+#if (NGX_HTTP_XSLT_REUSE_DTD)
+                ctx->ctxt->myDoc->extSubset = NULL;
+#endif
+                xmlFreeDoc(ctx->ctxt->myDoc);
+            }
+
+            xmlFreeParserCtxt(ctx->ctxt);
+
+            return ngx_http_xslt_send(r, ctx, NULL);
+        }
+
+        if (cl->buf->last_buf || cl->buf->last_in_chain) {
+
+            ctx->doc = ctx->ctxt->myDoc;
+
+#if (NGX_HTTP_XSLT_REUSE_DTD)
+            ctx->doc->extSubset = NULL;
+#endif
+
+            wellFormed = ctx->ctxt->wellFormed;
+
+            xmlFreeParserCtxt(ctx->ctxt);
+
+            if (wellFormed) {
+                return ngx_http_xslt_send(r, ctx,
+                                       ngx_http_xslt_apply_stylesheet(r, ctx));
+            }
+
+            xmlFreeDoc(ctx->doc);
+
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "not well formed XML document");
+
+            return ngx_http_xslt_send(r, ctx, NULL);
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_xslt_send(ngx_http_request_t *r, ngx_http_xslt_filter_ctx_t *ctx,
+    ngx_buf_t *b)
+{
+    ngx_int_t                         rc;
+    ngx_chain_t                       out;
+    ngx_pool_cleanup_t               *cln;
+    ngx_http_xslt_filter_loc_conf_t  *conf;
+
+    ctx->done = 1;
+
+    if (b == NULL) {
+        return ngx_http_filter_finalize_request(r, &ngx_http_xslt_filter_module,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+    }
+
+    cln = ngx_pool_cleanup_add(r->pool, 0);
+
+    if (cln == NULL) {
+        ngx_free(b->pos);
+        return ngx_http_filter_finalize_request(r, &ngx_http_xslt_filter_module,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+    }
+
+    if (r == r->main) {
+        r->headers_out.content_length_n = b->last - b->pos;
+
+        if (r->headers_out.content_length) {
+            r->headers_out.content_length->hash = 0;
+            r->headers_out.content_length = NULL;
+        }
+
+        conf = ngx_http_get_module_loc_conf(r, ngx_http_xslt_filter_module);
+
+        if (!conf->last_modified) {
+            ngx_http_clear_last_modified(r);
+            ngx_http_clear_etag(r);
+
+        } else {
+            ngx_http_weak_etag(r);
+        }
+    }
+
+    rc = ngx_http_next_header_filter(r);
+
+    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+        ngx_free(b->pos);
+        return rc;
+    }
+
+    cln->handler = ngx_http_xslt_cleanup;
+    cln->data = b->pos;
+
+    out.buf = b;
+    out.next = NULL;
+
+    return ngx_http_next_body_filter(r, &out);
+}
+
+
+static ngx_int_t
+ngx_http_xslt_add_chunk(ngx_http_request_t *r, ngx_http_xslt_filter_ctx_t *ctx,
+    ngx_buf_t *b)
+{
+    int               err;
+    xmlParserCtxtPtr  ctxt;
+
+    if (ctx->ctxt == NULL) {
+
+        ctxt = xmlCreatePushParserCtxt(NULL, NULL, NULL, 0, NULL);
+        if (ctxt == NULL) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "xmlCreatePushParserCtxt() failed");
+            return NGX_ERROR;
+        }
+        xmlCtxtUseOptions(ctxt, XML_PARSE_NOENT|XML_PARSE_DTDLOAD
+                                               |XML_PARSE_NOWARNING);
+
+        ctxt->sax->externalSubset = ngx_http_xslt_sax_external_subset;
+        ctxt->sax->setDocumentLocator = NULL;
+        ctxt->sax->error = ngx_http_xslt_sax_error;
+        ctxt->sax->fatalError = ngx_http_xslt_sax_error;
+        ctxt->sax->_private = ctx;
+
+        ctx->ctxt = ctxt;
+        ctx->request = r;
+    }
+
+    err = xmlParseChunk(ctx->ctxt, (char *) b->pos, (int) (b->last - b->pos),
+                        (b->last_buf) || (b->last_in_chain));
+
+    if (err == 0) {
+        b->pos = b->last;
+        return NGX_OK;
+    }
+
+    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                  "xmlParseChunk() failed, error:%d", err);
+
+    return NGX_ERROR;
+}
+
+
+static void
+ngx_http_xslt_sax_external_subset(void *data, const xmlChar *name,
+    const xmlChar *externalId, const xmlChar *systemId)
+{
+    xmlParserCtxtPtr ctxt = data;
+
+    xmlDocPtr                         doc;
+    xmlDtdPtr                         dtd;
+    ngx_http_request_t               *r;
+    ngx_http_xslt_filter_ctx_t       *ctx;
+    ngx_http_xslt_filter_loc_conf_t  *conf;
+
+    ctx = ctxt->sax->_private;
+    r = ctx->request;
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_xslt_filter_module);
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "xslt filter extSubset: \"%s\" \"%s\" \"%s\"",
+                   name ? name : (xmlChar *) "",
+                   externalId ? externalId : (xmlChar *) "",
+                   systemId ? systemId : (xmlChar *) "");
+
+    doc = ctxt->myDoc;
+
+#if (NGX_HTTP_XSLT_REUSE_DTD)
+
+    dtd = conf->dtd;
+
+#else
+
+    dtd = xmlCopyDtd(conf->dtd);
+    if (dtd == NULL) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "xmlCopyDtd() failed");
+        return;
+    }
+
+    if (doc->children == NULL) {
+        xmlAddChild((xmlNodePtr) doc, (xmlNodePtr) dtd);
+
+    } else {
+        xmlAddPrevSibling(doc->children, (xmlNodePtr) dtd);
+    }
+
+#endif
+
+    doc->extSubset = dtd;
+}
+
+
+static void ngx_cdecl
+ngx_http_xslt_sax_error(void *data, const char *msg, ...)
+{
+    xmlParserCtxtPtr ctxt = data;
+
+    size_t                       n;
+    va_list                      args;
+    ngx_http_xslt_filter_ctx_t  *ctx;
+    u_char                       buf[NGX_MAX_ERROR_STR];
+
+    ctx = ctxt->sax->_private;
+
+    buf[0] = '\0';
+
+    va_start(args, msg);
+    n = (size_t) vsnprintf((char *) buf, NGX_MAX_ERROR_STR, msg, args);
+    va_end(args);
+
+    while (--n && (buf[n] == CR || buf[n] == LF)) { /* void */ }
+
+    ngx_log_error(NGX_LOG_ERR, ctx->request->connection->log, 0,
+                  "libxml2 error: \"%*s\"", n + 1, buf);
+}
+
+
+static ngx_buf_t *
+ngx_http_xslt_apply_stylesheet(ngx_http_request_t *r,
+    ngx_http_xslt_filter_ctx_t *ctx)
+{
+    int                               len, rc, doc_type;
+    u_char                           *type, *encoding;
+    ngx_buf_t                        *b;
+    ngx_uint_t                        i;
+    xmlChar                          *buf;
+    xmlDocPtr                         doc, res;
+    ngx_http_xslt_sheet_t            *sheet;
+    ngx_http_xslt_filter_loc_conf_t  *conf;
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_xslt_filter_module);
+    sheet = conf->sheets.elts;
+    doc = ctx->doc;
+
+    /* preallocate array for 4 params */
+
+    if (ngx_array_init(&ctx->params, r->pool, 4 * 2 + 1, sizeof(char *))
+        != NGX_OK)
+    {
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+
+    for (i = 0; i < conf->sheets.nelts; i++) {
+
+        ctx->transform = xsltNewTransformContext(sheet[i].stylesheet, doc);
+        if (ctx->transform == NULL) {
+            xmlFreeDoc(doc);
+            return NULL;
+        }
+
+        if (conf->params
+            && ngx_http_xslt_params(r, ctx, conf->params, 0) != NGX_OK)
+        {
+            xsltFreeTransformContext(ctx->transform);
+            xmlFreeDoc(doc);
+            return NULL;
+        }
+
+        if (ngx_http_xslt_params(r, ctx, &sheet[i].params, 1) != NGX_OK) {
+            xsltFreeTransformContext(ctx->transform);
+            xmlFreeDoc(doc);
+            return NULL;
+        }
+
+        res = xsltApplyStylesheetUser(sheet[i].stylesheet, doc,
+                                      ctx->params.elts, NULL, NULL,
+                                      ctx->transform);
+
+        xsltFreeTransformContext(ctx->transform);
+        xmlFreeDoc(doc);
+
+        if (res == NULL) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "xsltApplyStylesheet() failed");
+            return NULL;
+        }
+
+        doc = res;
+
+        /* reset array elements */
+        ctx->params.nelts = 0;
+    }
+
+    /* there must be at least one stylesheet */
+
+    if (r == r->main) {
+        type = ngx_http_xslt_content_type(sheet[i - 1].stylesheet);
+
+    } else {
+        type = NULL;
+    }
+
+    encoding = ngx_http_xslt_encoding(sheet[i - 1].stylesheet);
+    doc_type = doc->type;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "xslt filter type: %d t:%s e:%s",
+                   doc_type, type ? type : (u_char *) "(null)",
+                   encoding ? encoding : (u_char *) "(null)");
+
+    rc = xsltSaveResultToString(&buf, &len, doc, sheet[i - 1].stylesheet);
+
+    xmlFreeDoc(doc);
+
+    if (rc != 0) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "xsltSaveResultToString() failed");
+        return NULL;
+    }
+
+    if (len == 0) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "xsltSaveResultToString() returned zero-length result");
+        return NULL;
+    }
+
+    b = ngx_calloc_buf(r->pool);
+    if (b == NULL) {
+        ngx_free(buf);
+        return NULL;
+    }
+
+    b->pos = buf;
+    b->last = buf + len;
+    b->memory = 1;
+
+    if (encoding) {
+        r->headers_out.charset.len = ngx_strlen(encoding);
+        r->headers_out.charset.data = encoding;
+    }
+
+    if (r != r->main) {
+        return b;
+    }
+
+    b->last_buf = 1;
+
+    if (type) {
+        len = ngx_strlen(type);
+
+        r->headers_out.content_type_len = len;
+        r->headers_out.content_type.len = len;
+        r->headers_out.content_type.data = type;
+
+    } else if (doc_type == XML_HTML_DOCUMENT_NODE) {
+
+        r->headers_out.content_type_len = sizeof("text/html") - 1;
+        ngx_str_set(&r->headers_out.content_type, "text/html");
+    }
+
+    r->headers_out.content_type_lowcase = NULL;
+
+    return b;
+}
+
+
+static ngx_int_t
+ngx_http_xslt_params(ngx_http_request_t *r, ngx_http_xslt_filter_ctx_t *ctx,
+    ngx_array_t *params, ngx_uint_t final)
+{
+    u_char                 *p, *last, *value, *dst, *src, **s;
+    size_t                  len;
+    ngx_uint_t              i;
+    ngx_str_t               string;
+    ngx_http_xslt_param_t  *param;
+
+    param = params->elts;
+
+    for (i = 0; i < params->nelts; i++) {
+
+        if (ngx_http_complex_value(r, &param[i].value, &string) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "xslt filter param: \"%s\"", string.data);
+
+        if (param[i].name) {
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "xslt filter param name: \"%s\"", param[i].name);
+
+            if (param[i].quote) {
+                if (xsltQuoteOneUserParam(ctx->transform, param[i].name,
+                                          string.data)
+                    != 0)
+                {
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                "xsltQuoteOneUserParam(\"%s\", \"%s\") failed",
+                                param[i].name, string.data);
+                    return NGX_ERROR;
+                }
+
+                continue;
+            }
+
+            s = ngx_array_push(&ctx->params);
+            if (s == NULL) {
+                return NGX_ERROR;
+            }
+
+            *s = param[i].name;
+
+            s = ngx_array_push(&ctx->params);
+            if (s == NULL) {
+                return NGX_ERROR;
+            }
+
+            *s = string.data;
+
+            continue;
+        }
+
+        /*
+         * parse param1=value1:param2=value2 syntax as used by parameters
+         * specified in xslt_stylesheet directives
+         */
+
+        if (param[i].value.lengths) {
+            p = string.data;
+
+        } else {
+            p = ngx_pnalloc(r->pool, string.len + 1);
+            if (p == NULL) {
+                return NGX_ERROR;
+            }
+
+            ngx_memcpy(p, string.data, string.len + 1);
+        }
+
+        last = p + string.len;
+
+        while (p && *p) {
+
+            value = p;
+            p = (u_char *) ngx_strchr(p, '=');
+            if (p == NULL) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                "invalid libxslt parameter \"%s\"", value);
+                return NGX_ERROR;
+            }
+            *p++ = '\0';
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "xslt filter param name: \"%s\"", value);
+
+            s = ngx_array_push(&ctx->params);
+            if (s == NULL) {
+                return NGX_ERROR;
+            }
+
+            *s = value;
+
+            value = p;
+            p = (u_char *) ngx_strchr(p, ':');
+
+            if (p) {
+                len = p - value;
+                *p++ = '\0';
+
+            } else {
+                len = last - value;
+            }
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "xslt filter param value: \"%s\"", value);
+
+            dst = value;
+            src = value;
+
+            ngx_unescape_uri(&dst, &src, len, 0);
+
+            *dst = '\0';
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "xslt filter param unescaped: \"%s\"", value);
+
+            s = ngx_array_push(&ctx->params);
+            if (s == NULL) {
+                return NGX_ERROR;
+            }
+
+            *s = value;
+        }
+    }
+
+    if (final) {
+        s = ngx_array_push(&ctx->params);
+        if (s == NULL) {
+            return NGX_ERROR;
+        }
+
+        *s = NULL;
+    }
+
+    return NGX_OK;
+}
+
+
+static u_char *
+ngx_http_xslt_content_type(xsltStylesheetPtr s)
+{
+    u_char  *type;
+
+    if (s->mediaType) {
+        return s->mediaType;
+    }
+
+    for (s = s->imports; s; s = s->next) {
+
+        type = ngx_http_xslt_content_type(s);
+
+        if (type) {
+            return type;
+        }
+    }
+
+    return NULL;
+}
+
+
+static u_char *
+ngx_http_xslt_encoding(xsltStylesheetPtr s)
+{
+    u_char  *encoding;
+
+    if (s->encoding) {
+        return s->encoding;
+    }
+
+    for (s = s->imports; s; s = s->next) {
+
+        encoding = ngx_http_xslt_encoding(s);
+
+        if (encoding) {
+            return encoding;
+        }
+    }
+
+    return NULL;
+}
+
+
+static void
+ngx_http_xslt_cleanup(void *data)
+{
+    ngx_free(data);
+}
+
+
+static char *
+ngx_http_xslt_entities(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_xslt_filter_loc_conf_t *xlcf = conf;
+
+    ngx_str_t                         *value;
+    ngx_uint_t                         i;
+    ngx_pool_cleanup_t                *cln;
+    ngx_http_xslt_file_t              *file;
+    ngx_http_xslt_filter_main_conf_t  *xmcf;
+
+    if (xlcf->dtd) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    xmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_xslt_filter_module);
+
+    file = xmcf->dtd_files.elts;
+    for (i = 0; i < xmcf->dtd_files.nelts; i++) {
+        if (ngx_strcmp(file[i].name, value[1].data) == 0) {
+            xlcf->dtd = file[i].data;
+            return NGX_CONF_OK;
+        }
+    }
+
+    cln = ngx_pool_cleanup_add(cf->pool, 0);
+    if (cln == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    xlcf->dtd = xmlParseDTD(NULL, (xmlChar *) value[1].data);
+
+    if (xlcf->dtd == NULL) {
+        ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "xmlParseDTD() failed");
+        return NGX_CONF_ERROR;
+    }
+
+    cln->handler = ngx_http_xslt_cleanup_dtd;
+    cln->data = xlcf->dtd;
+
+    file = ngx_array_push(&xmcf->dtd_files);
+    if (file == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    file->name = value[1].data;
+    file->data = xlcf->dtd;
+
+    return NGX_CONF_OK;
+}
+
+
+
+static char *
+ngx_http_xslt_stylesheet(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_xslt_filter_loc_conf_t *xlcf = conf;
+
+    ngx_str_t                         *value;
+    ngx_uint_t                         i, n;
+    ngx_pool_cleanup_t                *cln;
+    ngx_http_xslt_file_t              *file;
+    ngx_http_xslt_sheet_t             *sheet;
+    ngx_http_xslt_param_t             *param;
+    ngx_http_compile_complex_value_t   ccv;
+    ngx_http_xslt_filter_main_conf_t  *xmcf;
+
+    value = cf->args->elts;
+
+    if (xlcf->sheets.elts == NULL) {
+        if (ngx_array_init(&xlcf->sheets, cf->pool, 1,
+                           sizeof(ngx_http_xslt_sheet_t))
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    sheet = ngx_array_push(&xlcf->sheets);
+    if (sheet == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memzero(sheet, sizeof(ngx_http_xslt_sheet_t));
+
+    if (ngx_conf_full_name(cf->cycle, &value[1], 0) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    xmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_xslt_filter_module);
+
+    file = xmcf->sheet_files.elts;
+    for (i = 0; i < xmcf->sheet_files.nelts; i++) {
+        if (ngx_strcmp(file[i].name, value[1].data) == 0) {
+            sheet->stylesheet = file[i].data;
+            goto found;
+        }
+    }
+
+    cln = ngx_pool_cleanup_add(cf->pool, 0);
+    if (cln == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    sheet->stylesheet = xsltParseStylesheetFile(value[1].data);
+    if (sheet->stylesheet == NULL) {
+        ngx_conf_log_error(NGX_LOG_ERR, cf, 0,
+                           "xsltParseStylesheetFile(\"%s\") failed",
+                           value[1].data);
+        return NGX_CONF_ERROR;
+    }
+
+    cln->handler = ngx_http_xslt_cleanup_stylesheet;
+    cln->data = sheet->stylesheet;
+
+    file = ngx_array_push(&xmcf->sheet_files);
+    if (file == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    file->name = value[1].data;
+    file->data = sheet->stylesheet;
+
+found:
+
+    n = cf->args->nelts;
+
+    if (n == 2) {
+        return NGX_CONF_OK;
+    }
+
+    if (ngx_array_init(&sheet->params, cf->pool, n - 2,
+                       sizeof(ngx_http_xslt_param_t))
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    for (i = 2; i < n; i++) {
+
+        param = ngx_array_push(&sheet->params);
+        if (param == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        ngx_memzero(param, sizeof(ngx_http_xslt_param_t));
+        ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+        ccv.cf = cf;
+        ccv.value = &value[i];
+        ccv.complex_value = &param->value;
+        ccv.zero = 1;
+
+        if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_xslt_param(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_xslt_filter_loc_conf_t  *xlcf = conf;
+
+    ngx_http_xslt_param_t            *param;
+    ngx_http_compile_complex_value_t  ccv;
+    ngx_str_t                        *value;
+
+    value = cf->args->elts;
+
+    if (xlcf->params == NULL) {
+        xlcf->params = ngx_array_create(cf->pool, 2,
+                                        sizeof(ngx_http_xslt_param_t));
+        if (xlcf->params == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    param = ngx_array_push(xlcf->params);
+    if (param == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    param->name = value[1].data;
+    param->quote = (cmd->post == NULL) ? 0 : 1;
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[2];
+    ccv.complex_value = &param->value;
+    ccv.zero = 1;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static void
+ngx_http_xslt_cleanup_dtd(void *data)
+{
+    xmlFreeDtd(data);
+}
+
+
+static void
+ngx_http_xslt_cleanup_stylesheet(void *data)
+{
+    xsltFreeStylesheet(data);
+}
+
+
+static void *
+ngx_http_xslt_filter_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_http_xslt_filter_main_conf_t  *conf;
+
+    conf = ngx_palloc(cf->pool, sizeof(ngx_http_xslt_filter_main_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    if (ngx_array_init(&conf->dtd_files, cf->pool, 1,
+                       sizeof(ngx_http_xslt_file_t))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    if (ngx_array_init(&conf->sheet_files, cf->pool, 1,
+                       sizeof(ngx_http_xslt_file_t))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    return conf;
+}
+
+
+static void *
+ngx_http_xslt_filter_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_xslt_filter_loc_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_xslt_filter_loc_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->dtd = NULL;
+     *     conf->sheets = { NULL };
+     *     conf->types = { NULL };
+     *     conf->types_keys = NULL;
+     *     conf->params = NULL;
+     */
+
+    conf->last_modified = NGX_CONF_UNSET;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_xslt_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_xslt_filter_loc_conf_t *prev = parent;
+    ngx_http_xslt_filter_loc_conf_t *conf = child;
+
+    if (conf->dtd == NULL) {
+        conf->dtd = prev->dtd;
+    }
+
+    if (conf->sheets.nelts == 0) {
+        conf->sheets = prev->sheets;
+    }
+
+    if (conf->params == NULL) {
+        conf->params = prev->params;
+    }
+
+    if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types,
+                             &prev->types_keys, &prev->types,
+                             ngx_http_xslt_default_types)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_conf_merge_value(conf->last_modified, prev->last_modified, 0);
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_xslt_filter_preconfiguration(ngx_conf_t *cf)
+{
+    xmlInitParser();
+
+#if (NGX_HAVE_EXSLT)
+    exsltRegisterAll();
+#endif
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_xslt_filter_init(ngx_conf_t *cf)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_xslt_header_filter;
+
+    ngx_http_next_body_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_xslt_body_filter;
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_xslt_filter_exit(ngx_cycle_t *cycle)
+{
+    xsltCleanupGlobals();
+    xmlCleanupParser();
+}
diff --git a/nginx/src/http/modules/perl/Makefile.PL b/nginx/src/http/modules/perl/Makefile.PL
new file mode 100644 (file)
index 0000000..7edadcb
--- /dev/null
@@ -0,0 +1,35 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+use 5.006001;
+use ExtUtils::MakeMaker;
+
+WriteMakefile(
+    NAME              => 'nginx',
+    VERSION_FROM      => 'nginx.pm',     # finds $VERSION
+    PREREQ_PM         => {},             # e.g., Module::Name => 1.1
+
+    ABSTRACT_FROM     => 'nginx.pm',     # retrieve abstract from module
+    AUTHOR            => 'Igor Sysoev',
+
+    CCFLAGS           => "$ENV{NGX_PM_CFLAGS}",
+    OPTIMIZE          => '-O',
+
+    LDDLFLAGS         => "$ENV{NGX_PM_LDFLAGS}",
+
+    INC               => join(" ", map {
+                             m#^/# ? "-I $_" : "-I ../../../../../$_"
+                         } (split /\s+/, $ENV{NGX_INCS})),
+
+    depend => {
+        'nginx.c'     => join(" ", map {
+                             m#^/# ? $_ : "../../../../../$_"
+                         } (split(/\s+/, $ENV{NGX_DEPS}),
+                            "src/http/modules/perl/ngx_http_perl_module.h"))
+    },
+
+    PM => {
+        'nginx.pm'    => '$(INST_LIBDIR)/nginx.pm'
+    }
+);
diff --git a/nginx/src/http/modules/perl/nginx.pm b/nginx/src/http/modules/perl/nginx.pm
new file mode 100644 (file)
index 0000000..d4663dc
--- /dev/null
@@ -0,0 +1,140 @@
+package nginx;
+
+use 5.006001;
+use strict;
+use warnings;
+
+require Exporter;
+
+our @ISA = qw(Exporter);
+
+our @EXPORT = qw(
+    OK
+    DECLINED
+
+    HTTP_OK
+    HTTP_CREATED
+    HTTP_ACCEPTED
+    HTTP_NO_CONTENT
+    HTTP_PARTIAL_CONTENT
+
+    HTTP_MOVED_PERMANENTLY
+    HTTP_MOVED_TEMPORARILY
+    HTTP_REDIRECT
+    HTTP_SEE_OTHER
+    HTTP_NOT_MODIFIED
+    HTTP_TEMPORARY_REDIRECT
+    HTTP_PERMANENT_REDIRECT
+
+    HTTP_BAD_REQUEST
+    HTTP_UNAUTHORIZED
+    HTTP_PAYMENT_REQUIRED
+    HTTP_FORBIDDEN
+    HTTP_NOT_FOUND
+    HTTP_NOT_ALLOWED
+    HTTP_NOT_ACCEPTABLE
+    HTTP_REQUEST_TIME_OUT
+    HTTP_CONFLICT
+    HTTP_GONE
+    HTTP_LENGTH_REQUIRED
+    HTTP_REQUEST_ENTITY_TOO_LARGE
+    HTTP_REQUEST_URI_TOO_LARGE
+    HTTP_UNSUPPORTED_MEDIA_TYPE
+    HTTP_RANGE_NOT_SATISFIABLE
+
+    HTTP_INTERNAL_SERVER_ERROR
+    HTTP_SERVER_ERROR
+    HTTP_NOT_IMPLEMENTED
+    HTTP_BAD_GATEWAY
+    HTTP_SERVICE_UNAVAILABLE
+    HTTP_GATEWAY_TIME_OUT
+    HTTP_INSUFFICIENT_STORAGE
+);
+
+our $VERSION = '%%VERSION%%';
+
+require XSLoader;
+XSLoader::load('nginx', $VERSION);
+
+# Preloaded methods go here.
+
+use constant OK                             => 0;
+use constant DECLINED                       => -5;
+
+use constant HTTP_OK                        => 200;
+use constant HTTP_CREATED                   => 201;
+use constant HTTP_ACCEPTED                  => 202;
+use constant HTTP_NO_CONTENT                => 204;
+use constant HTTP_PARTIAL_CONTENT           => 206;
+
+use constant HTTP_MOVED_PERMANENTLY         => 301;
+use constant HTTP_MOVED_TEMPORARILY         => 302;
+use constant HTTP_REDIRECT                  => 302;
+use constant HTTP_SEE_OTHER                 => 303;
+use constant HTTP_NOT_MODIFIED              => 304;
+use constant HTTP_TEMPORARY_REDIRECT        => 307;
+use constant HTTP_PERMANENT_REDIRECT        => 308;
+
+use constant HTTP_BAD_REQUEST               => 400;
+use constant HTTP_UNAUTHORIZED              => 401;
+use constant HTTP_PAYMENT_REQUIRED          => 402;
+use constant HTTP_FORBIDDEN                 => 403;
+use constant HTTP_NOT_FOUND                 => 404;
+use constant HTTP_NOT_ALLOWED               => 405;
+use constant HTTP_NOT_ACCEPTABLE            => 406;
+use constant HTTP_REQUEST_TIME_OUT          => 408;
+use constant HTTP_CONFLICT                  => 409;
+use constant HTTP_GONE                      => 410;
+use constant HTTP_LENGTH_REQUIRED           => 411;
+use constant HTTP_REQUEST_ENTITY_TOO_LARGE  => 413;
+use constant HTTP_REQUEST_URI_TOO_LARGE     => 414;
+use constant HTTP_UNSUPPORTED_MEDIA_TYPE    => 415;
+use constant HTTP_RANGE_NOT_SATISFIABLE     => 416;
+
+use constant HTTP_INTERNAL_SERVER_ERROR     => 500;
+use constant HTTP_SERVER_ERROR              => 500;
+use constant HTTP_NOT_IMPLEMENTED           => 501;
+use constant HTTP_BAD_GATEWAY               => 502;
+use constant HTTP_SERVICE_UNAVAILABLE       => 503;
+use constant HTTP_GATEWAY_TIME_OUT          => 504;
+use constant HTTP_INSUFFICIENT_STORAGE      => 507;
+
+
+sub rflush {
+    my $r = shift;
+
+    $r->flush;
+}
+
+
+1;
+__END__
+
+=head1 NAME
+
+nginx - Perl interface to the nginx HTTP server API
+
+=head1 SYNOPSIS
+
+  use nginx;
+
+=head1 DESCRIPTION
+
+This module provides a Perl interface to the nginx HTTP server API.
+
+
+=head1 SEE ALSO
+
+http://nginx.org/en/docs/http/ngx_http_perl_module.html
+
+=head1 AUTHOR
+
+Igor Sysoev
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) Igor Sysoev
+Copyright (C) Nginx, Inc.
+
+
+=cut
diff --git a/nginx/src/http/modules/perl/nginx.xs b/nginx/src/http/modules/perl/nginx.xs
new file mode 100644 (file)
index 0000000..ad12632
--- /dev/null
@@ -0,0 +1,1041 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#define PERL_NO_GET_CONTEXT
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_http_perl_module.h>
+
+#include "XSUB.h"
+
+
+#define ngx_http_perl_set_request(r)                                          \
+    r = INT2PTR(ngx_http_request_t *, SvIV((SV *) SvRV(ST(0))))
+
+
+#define ngx_http_perl_set_targ(p, len)                                        \
+                                                                              \
+    SvUPGRADE(TARG, SVt_PV);                                                  \
+    SvPOK_on(TARG);                                                           \
+    sv_setpvn(TARG, (char *) p, len)
+
+
+static ngx_int_t
+ngx_http_perl_sv2str(pTHX_ ngx_http_request_t *r, ngx_str_t *s, SV *sv)
+{
+    u_char  *p;
+    STRLEN   len;
+
+    if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PV) {
+        sv = SvRV(sv);
+    }
+
+    p = (u_char *) SvPV(sv, len);
+
+    s->len = len;
+
+    if (SvREADONLY(sv) && SvPOK(sv)) {
+        s->data = p;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "perl sv2str: %08XD \"%V\"", sv->sv_flags, s);
+
+        return NGX_OK;
+    }
+
+    s->data = ngx_pnalloc(r->pool, len);
+    if (s->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(s->data, p, len);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "perl sv2str: %08XD \"%V\"", sv->sv_flags, s);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_perl_output(ngx_http_request_t *r, ngx_buf_t *b)
+{
+    ngx_chain_t           out;
+#if (NGX_HTTP_SSI)
+    ngx_chain_t          *cl;
+    ngx_http_perl_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+
+    if (ctx->ssi) {
+        cl = ngx_alloc_chain_link(r->pool);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+        cl->buf = b;
+        cl->next = NULL;
+        *ctx->ssi->last_out = cl;
+        ctx->ssi->last_out = &cl->next;
+
+        return NGX_OK;
+    }
+#endif
+
+    out.buf = b;
+    out.next = NULL;
+
+    return ngx_http_output_filter(r, &out);
+}
+
+
+MODULE = nginx    PACKAGE = nginx
+
+
+PROTOTYPES: DISABLE
+
+
+void
+status(r, code)
+    CODE:
+
+    ngx_http_request_t  *r;
+
+    ngx_http_perl_set_request(r);
+
+    r->headers_out.status = SvIV(ST(1));
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "perl status: %d", r->headers_out.status);
+
+    XSRETURN_UNDEF;
+
+
+void
+send_http_header(r, ...)
+    CODE:
+
+    ngx_http_request_t  *r;
+    SV                  *sv;
+
+    ngx_http_perl_set_request(r);
+
+    if (r->headers_out.status == 0) {
+        r->headers_out.status = NGX_HTTP_OK;
+    }
+
+    if (items != 1) {
+        sv = ST(1);
+
+        if (ngx_http_perl_sv2str(aTHX_ r, &r->headers_out.content_type, sv)
+            != NGX_OK)
+        {
+            XSRETURN_EMPTY;
+        }
+
+        r->headers_out.content_type_len = r->headers_out.content_type.len;
+
+    } else {
+        if (ngx_http_set_content_type(r) != NGX_OK) {
+            XSRETURN_EMPTY;
+        }
+    }
+
+    (void) ngx_http_send_header(r);
+
+
+void
+header_only(r)
+    CODE:
+
+    dXSTARG;
+    ngx_http_request_t  *r;
+
+    ngx_http_perl_set_request(r);
+
+    sv_upgrade(TARG, SVt_IV);
+    sv_setiv(TARG, r->header_only);
+
+    ST(0) = TARG;
+
+
+void
+uri(r)
+    CODE:
+
+    dXSTARG;
+    ngx_http_request_t  *r;
+
+    ngx_http_perl_set_request(r);
+    ngx_http_perl_set_targ(r->uri.data, r->uri.len);
+
+    ST(0) = TARG;
+
+
+void
+args(r)
+    CODE:
+
+    dXSTARG;
+    ngx_http_request_t  *r;
+
+    ngx_http_perl_set_request(r);
+    ngx_http_perl_set_targ(r->args.data, r->args.len);
+
+    ST(0) = TARG;
+
+
+void
+request_method(r)
+    CODE:
+
+    dXSTARG;
+    ngx_http_request_t  *r;
+
+    ngx_http_perl_set_request(r);
+    ngx_http_perl_set_targ(r->method_name.data, r->method_name.len);
+
+    ST(0) = TARG;
+
+
+void
+remote_addr(r)
+    CODE:
+
+    dXSTARG;
+    ngx_http_request_t  *r;
+
+    ngx_http_perl_set_request(r);
+    ngx_http_perl_set_targ(r->connection->addr_text.data,
+                           r->connection->addr_text.len);
+
+    ST(0) = TARG;
+
+
+void
+header_in(r, key)
+    CODE:
+
+    dXSTARG;
+    ngx_http_request_t         *r;
+    SV                         *key;
+    u_char                     *p, *lowcase_key, *value, sep;
+    STRLEN                      len;
+    ssize_t                     size;
+    ngx_uint_t                  i, n, hash;
+    ngx_array_t                *a;
+    ngx_list_part_t            *part;
+    ngx_table_elt_t            *h, **ph;
+    ngx_http_header_t          *hh;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    ngx_http_perl_set_request(r);
+
+    key = ST(1);
+
+    if (SvROK(key) && SvTYPE(SvRV(key)) == SVt_PV) {
+        key = SvRV(key);
+    }
+
+    p = (u_char *) SvPV(key, len);
+
+    /* look up hashed headers */
+
+    lowcase_key = ngx_pnalloc(r->pool, len);
+    if (lowcase_key == NULL) {
+        XSRETURN_UNDEF;
+    }
+
+    hash = ngx_hash_strlow(lowcase_key, p, len);
+
+    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+    hh = ngx_hash_find(&cmcf->headers_in_hash, hash, lowcase_key, len);
+
+    if (hh) {
+
+        if (hh->offset == offsetof(ngx_http_headers_in_t, cookies)) {
+            sep = ';';
+            goto multi;
+        }
+#if (NGX_HTTP_X_FORWARDED_FOR)
+        if (hh->offset == offsetof(ngx_http_headers_in_t, x_forwarded_for)) {
+            sep = ',';
+            goto multi;
+        }
+#endif
+
+        ph = (ngx_table_elt_t **) ((char *) &r->headers_in + hh->offset);
+
+        if (*ph) {
+            ngx_http_perl_set_targ((*ph)->value.data, (*ph)->value.len);
+
+            goto done;
+        }
+
+        XSRETURN_UNDEF;
+
+    multi:
+
+        /* Cookie, X-Forwarded-For */
+
+        a = (ngx_array_t *) ((char *) &r->headers_in + hh->offset);
+
+        n = a->nelts;
+
+        if (n == 0) {
+            XSRETURN_UNDEF;
+        }
+
+        ph = a->elts;
+
+        if (n == 1) {
+            ngx_http_perl_set_targ((*ph)->value.data, (*ph)->value.len);
+
+            goto done;
+        }
+
+        size = - (ssize_t) (sizeof("; ") - 1);
+
+        for (i = 0; i < n; i++) {
+            size += ph[i]->value.len + sizeof("; ") - 1;
+        }
+
+        value = ngx_pnalloc(r->pool, size);
+        if (value == NULL) {
+            XSRETURN_UNDEF;
+        }
+
+        p = value;
+
+        for (i = 0; /* void */ ; i++) {
+            p = ngx_copy(p, ph[i]->value.data, ph[i]->value.len);
+
+            if (i == n - 1) {
+                break;
+            }
+
+            *p++ = sep; *p++ = ' ';
+        }
+
+        ngx_http_perl_set_targ(value, size);
+
+        goto done;
+    }
+
+    /* iterate over all headers */
+
+    part = &r->headers_in.headers.part;
+    h = part->elts;
+
+    for (i = 0; /* void */ ; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            h = part->elts;
+            i = 0;
+        }
+
+        if (len != h[i].key.len
+            || ngx_strcasecmp(p, h[i].key.data) != 0)
+        {
+            continue;
+        }
+
+        ngx_http_perl_set_targ(h[i].value.data, h[i].value.len);
+
+        goto done;
+    }
+
+    XSRETURN_UNDEF;
+
+    done:
+
+    ST(0) = TARG;
+
+
+void
+has_request_body(r, next)
+    CODE:
+
+    dXSTARG;
+    ngx_http_request_t   *r;
+    ngx_http_perl_ctx_t  *ctx;
+
+    ngx_http_perl_set_request(r);
+
+    if (r->headers_in.content_length_n <= 0 && !r->headers_in.chunked) {
+        XSRETURN_UNDEF;
+    }
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+    ctx->next = SvRV(ST(1));
+
+    r->request_body_in_single_buf = 1;
+    r->request_body_in_persistent_file = 1;
+    r->request_body_in_clean_file = 1;
+
+    if (r->request_body_in_file_only) {
+        r->request_body_file_log_level = 0;
+    }
+
+    ngx_http_read_client_request_body(r, ngx_http_perl_handle_request);
+
+    sv_upgrade(TARG, SVt_IV);
+    sv_setiv(TARG, 1);
+
+    ST(0) = TARG;
+
+
+void
+request_body(r)
+    CODE:
+
+    dXSTARG;
+    ngx_http_request_t  *r;
+    u_char              *p, *data;
+    size_t               len;
+    ngx_buf_t           *buf;
+    ngx_chain_t         *cl;
+
+    ngx_http_perl_set_request(r);
+
+    if (r->request_body == NULL
+        || r->request_body->temp_file
+        || r->request_body->bufs == NULL)
+    {
+        XSRETURN_UNDEF;
+    }
+
+    cl = r->request_body->bufs;
+    buf = cl->buf;
+
+    if (cl->next == NULL) {
+        len = buf->last - buf->pos;
+        data = buf->pos;
+        goto done;
+    }
+
+    len = buf->last - buf->pos;
+    cl = cl->next;
+
+    for ( /* void */ ; cl; cl = cl->next) {
+        buf = cl->buf;
+        len += buf->last - buf->pos;
+    }
+
+    p = ngx_pnalloc(r->pool, len);
+    if (p == NULL) {
+        XSRETURN_UNDEF;
+    }
+
+    data = p;
+    cl = r->request_body->bufs;
+
+    for ( /* void */ ; cl; cl = cl->next) {
+        buf = cl->buf;
+        p = ngx_cpymem(p, buf->pos, buf->last - buf->pos);
+    }
+
+    done:
+
+    if (len == 0) {
+        XSRETURN_UNDEF;
+    }
+
+    ngx_http_perl_set_targ(data, len);
+
+    ST(0) = TARG;
+
+
+void
+request_body_file(r)
+    CODE:
+
+    dXSTARG;
+    ngx_http_request_t  *r;
+
+    ngx_http_perl_set_request(r);
+
+    if (r->request_body == NULL || r->request_body->temp_file == NULL) {
+        XSRETURN_UNDEF;
+    }
+
+    ngx_http_perl_set_targ(r->request_body->temp_file->file.name.data,
+                           r->request_body->temp_file->file.name.len);
+
+    ST(0) = TARG;
+
+
+void
+discard_request_body(r)
+    CODE:
+
+    ngx_http_request_t  *r;
+
+    ngx_http_perl_set_request(r);
+
+    ngx_http_discard_request_body(r);
+
+
+void
+header_out(r, key, value)
+    CODE:
+
+    ngx_http_request_t  *r;
+    SV                  *key;
+    SV                  *value;
+    ngx_table_elt_t     *header;
+
+    ngx_http_perl_set_request(r);
+
+    key = ST(1);
+    value = ST(2);
+
+    header = ngx_list_push(&r->headers_out.headers);
+    if (header == NULL) {
+        XSRETURN_EMPTY;
+    }
+
+    header->hash = 1;
+
+    if (ngx_http_perl_sv2str(aTHX_ r, &header->key, key) != NGX_OK) {
+        header->hash = 0;
+        XSRETURN_EMPTY;
+    }
+
+    if (ngx_http_perl_sv2str(aTHX_ r, &header->value, value) != NGX_OK) {
+        header->hash = 0;
+        XSRETURN_EMPTY;
+    }
+
+    if (header->key.len == sizeof("Content-Length") - 1
+        && ngx_strncasecmp(header->key.data, (u_char *) "Content-Length",
+                           sizeof("Content-Length") - 1) == 0)
+    {
+        r->headers_out.content_length_n = (off_t) SvIV(value);
+        r->headers_out.content_length = header;
+    }
+
+    if (header->key.len == sizeof("Content-Encoding") - 1
+        && ngx_strncasecmp(header->key.data, (u_char *) "Content-Encoding",
+                           sizeof("Content-Encoding") - 1) == 0)
+    {
+        r->headers_out.content_encoding = header;
+    }
+
+
+void
+filename(r)
+    CODE:
+
+    dXSTARG;
+    size_t                root;
+    ngx_http_request_t   *r;
+    ngx_http_perl_ctx_t  *ctx;
+
+    ngx_http_perl_set_request(r);
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+    if (ctx->filename.data) {
+        goto done;
+    }
+
+    if (ngx_http_map_uri_to_path(r, &ctx->filename, &root, 0) == NULL) {
+        XSRETURN_UNDEF;
+    }
+
+    ctx->filename.len--;
+    sv_setpv(PL_statname, (char *) ctx->filename.data);
+
+    done:
+
+    ngx_http_perl_set_targ(ctx->filename.data, ctx->filename.len);
+
+    ST(0) = TARG;
+
+
+void
+print(r, ...)
+    CODE:
+
+    ngx_http_request_t  *r;
+    SV                  *sv;
+    int                  i;
+    u_char              *p;
+    size_t               size;
+    STRLEN               len;
+    ngx_buf_t           *b;
+
+    ngx_http_perl_set_request(r);
+
+    if (items == 2) {
+
+        /*
+         * do zero copy for prolate single read-only SV:
+         *     $r->print("some text\n");
+         */
+
+        sv = ST(1);
+
+        if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PV) {
+            sv = SvRV(sv);
+        }
+
+        if (SvREADONLY(sv) && SvPOK(sv)) {
+
+            p = (u_char *) SvPV(sv, len);
+
+            if (len == 0) {
+                XSRETURN_EMPTY;
+            }
+
+            b = ngx_calloc_buf(r->pool);
+            if (b == NULL) {
+                XSRETURN_EMPTY;
+            }
+
+            b->memory = 1;
+            b->pos = p;
+            b->last = p + len;
+            b->start = p;
+            b->end = b->last;
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "$r->print: read-only SV: %z", len);
+
+            goto out;
+        }
+    }
+
+    size = 0;
+
+    for (i = 1; i < items; i++) {
+
+        sv = ST(i);
+
+        if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PV) {
+            sv = SvRV(sv);
+        }
+
+        (void) SvPV(sv, len);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "$r->print: copy SV: %z", len);
+
+        size += len;
+    }
+
+    if (size == 0) {
+        XSRETURN_EMPTY;
+    }
+
+    b = ngx_create_temp_buf(r->pool, size);
+    if (b == NULL) {
+        XSRETURN_EMPTY;
+    }
+
+    for (i = 1; i < items; i++) {
+        sv = ST(i);
+
+        if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PV) {
+            sv = SvRV(sv);
+        }
+
+        p = (u_char *) SvPV(sv, len);
+        b->last = ngx_cpymem(b->last, p, len);
+    }
+
+    out:
+
+    (void) ngx_http_perl_output(r, b);
+
+
+void
+sendfile(r, filename, offset = -1, bytes = 0)
+    CODE:
+
+    ngx_http_request_t        *r;
+    char                      *filename;
+    off_t                      offset;
+    size_t                     bytes;
+    ngx_str_t                  path;
+    ngx_buf_t                 *b;
+    ngx_open_file_info_t       of;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    ngx_http_perl_set_request(r);
+
+    filename = SvPV_nolen(ST(1));
+
+    if (filename == NULL) {
+        croak("sendfile(): NULL filename");
+    }
+
+    offset = items < 3 ? -1 : SvIV(ST(2));
+    bytes = items < 4 ? 0 : SvIV(ST(3));
+
+    b = ngx_calloc_buf(r->pool);
+    if (b == NULL) {
+        XSRETURN_EMPTY;
+    }
+
+    b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
+    if (b->file == NULL) {
+        XSRETURN_EMPTY;
+    }
+
+    path.len = ngx_strlen(filename);
+
+    path.data = ngx_pnalloc(r->pool, path.len + 1);
+    if (path.data == NULL) {
+        XSRETURN_EMPTY;
+    }
+
+    (void) ngx_cpystrn(path.data, (u_char *) filename, path.len + 1);
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+    of.read_ahead = clcf->read_ahead;
+    of.directio = clcf->directio;
+    of.valid = clcf->open_file_cache_valid;
+    of.min_uses = clcf->open_file_cache_min_uses;
+    of.errors = clcf->open_file_cache_errors;
+    of.events = clcf->open_file_cache_events;
+
+    if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
+        XSRETURN_EMPTY;
+    }
+
+    if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
+        != NGX_OK)
+    {
+        if (of.err == 0) {
+            XSRETURN_EMPTY;
+        }
+
+        ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+                      "%s \"%s\" failed", of.failed, filename);
+        XSRETURN_EMPTY;
+    }
+
+    if (offset == -1) {
+        offset = 0;
+    }
+
+    if (bytes == 0) {
+        bytes = of.size - offset;
+    }
+
+    b->in_file = 1;
+
+    b->file_pos = offset;
+    b->file_last = offset + bytes;
+
+    b->file->fd = of.fd;
+    b->file->log = r->connection->log;
+    b->file->directio = of.is_directio;
+
+    (void) ngx_http_perl_output(r, b);
+
+
+void
+flush(r)
+    CODE:
+
+    ngx_http_request_t  *r;
+    ngx_buf_t           *b;
+
+    ngx_http_perl_set_request(r);
+
+    b = ngx_calloc_buf(r->pool);
+    if (b == NULL) {
+        XSRETURN_EMPTY;
+    }
+
+    b->flush = 1;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "$r->flush");
+
+    (void) ngx_http_perl_output(r, b);
+
+    XSRETURN_EMPTY;
+
+
+void
+internal_redirect(r, uri)
+    CODE:
+
+    ngx_http_request_t   *r;
+    SV                   *uri;
+    ngx_uint_t            i;
+    ngx_http_perl_ctx_t  *ctx;
+
+    ngx_http_perl_set_request(r);
+
+    uri = ST(1);
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+
+    if (ngx_http_perl_sv2str(aTHX_ r, &ctx->redirect_uri, uri) != NGX_OK) {
+        XSRETURN_EMPTY;
+    }
+
+    for (i = 0; i < ctx->redirect_uri.len; i++) {
+        if (ctx->redirect_uri.data[i] == '?') {
+
+            ctx->redirect_args.len = ctx->redirect_uri.len - (i + 1);
+            ctx->redirect_args.data = &ctx->redirect_uri.data[i + 1];
+            ctx->redirect_uri.len = i;
+
+            XSRETURN_EMPTY;
+        }
+    }
+
+
+void
+allow_ranges(r)
+    CODE:
+
+    ngx_http_request_t  *r;
+
+    ngx_http_perl_set_request(r);
+
+    r->allow_ranges = 1;
+
+
+void
+unescape(r, text, type = 0)
+    CODE:
+
+    dXSTARG;
+    ngx_http_request_t  *r;
+    SV                  *text;
+    int                  type;
+    u_char              *p, *dst, *src;
+    STRLEN               len;
+
+    ngx_http_perl_set_request(r);
+
+    text = ST(1);
+
+    src = (u_char *) SvPV(text, len);
+
+    p = ngx_pnalloc(r->pool, len + 1);
+    if (p == NULL) {
+        XSRETURN_UNDEF;
+    }
+
+    dst = p;
+
+    type = items < 3 ? 0 : SvIV(ST(2));
+
+    ngx_unescape_uri(&dst, &src, len, (ngx_uint_t) type);
+    *dst = '\0';
+
+    ngx_http_perl_set_targ(p, dst - p);
+
+    ST(0) = TARG;
+
+
+void
+variable(r, name, value = NULL)
+    CODE:
+
+    dXSTARG;
+    ngx_http_request_t         *r;
+    SV                         *name, *value;
+    u_char                     *p, *lowcase;
+    STRLEN                      len;
+    ngx_str_t                   var, val;
+    ngx_uint_t                  i, hash;
+    ngx_http_perl_var_t        *v;
+    ngx_http_perl_ctx_t        *ctx;
+    ngx_http_variable_value_t  *vv;
+
+    ngx_http_perl_set_request(r);
+
+    name = ST(1);
+
+    if (SvROK(name) && SvTYPE(SvRV(name)) == SVt_PV) {
+        name = SvRV(name);
+    }
+
+    if (items == 2) {
+        value = NULL;
+
+    } else {
+        value = ST(2);
+
+        if (SvROK(value) && SvTYPE(SvRV(value)) == SVt_PV) {
+            value = SvRV(value);
+        }
+
+        if (ngx_http_perl_sv2str(aTHX_ r, &val, value) != NGX_OK) {
+            XSRETURN_UNDEF;
+        }
+    }
+
+    p = (u_char *) SvPV(name, len);
+
+    lowcase = ngx_pnalloc(r->pool, len);
+    if (lowcase == NULL) {
+        XSRETURN_UNDEF;
+    }
+
+    hash = ngx_hash_strlow(lowcase, p, len);
+
+    var.len = len;
+    var.data = lowcase;
+#if (NGX_DEBUG)
+
+    if (value) {
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "perl variable: \"%V\"=\"%V\"", &var, &val);
+    } else {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "perl variable: \"%V\"", &var);
+    }
+#endif
+
+    vv = ngx_http_get_variable(r, &var, hash);
+    if (vv == NULL) {
+        XSRETURN_UNDEF;
+    }
+
+    if (vv->not_found) {
+
+        ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+
+        if (ctx->variables) {
+
+            v = ctx->variables->elts;
+            for (i = 0; i < ctx->variables->nelts; i++) {
+
+                if (hash != v[i].hash
+                    || len != v[i].name.len
+                    || ngx_strncmp(lowcase, v[i].name.data, len) != 0)
+                {
+                    continue;
+                }
+
+                if (value) {
+                    v[i].value = val;
+                    XSRETURN_UNDEF;
+                }
+
+                ngx_http_perl_set_targ(v[i].value.data, v[i].value.len);
+
+                goto done;
+            }
+        }
+
+        if (value) {
+            if (ctx->variables == NULL) {
+                ctx->variables = ngx_array_create(r->pool, 1,
+                                                  sizeof(ngx_http_perl_var_t));
+                if (ctx->variables == NULL) {
+                    XSRETURN_UNDEF;
+                }
+            }
+
+            v = ngx_array_push(ctx->variables);
+            if (v == NULL) {
+                XSRETURN_UNDEF;
+            }
+
+            v->hash = hash;
+            v->name.len = len;
+            v->name.data = lowcase;
+            v->value = val;
+
+            XSRETURN_UNDEF;
+        }
+
+        XSRETURN_UNDEF;
+    }
+
+    if (value) {
+        vv->len = val.len;
+        vv->valid = 1;
+        vv->no_cacheable = 0;
+        vv->not_found = 0;
+        vv->data = val.data;
+
+        XSRETURN_UNDEF;
+    }
+
+    ngx_http_perl_set_targ(vv->data, vv->len);
+
+    done:
+
+    ST(0) = TARG;
+
+
+void
+sleep(r, sleep, next)
+    CODE:
+
+    ngx_http_request_t   *r;
+    ngx_msec_t            sleep;
+    ngx_http_perl_ctx_t  *ctx;
+
+    ngx_http_perl_set_request(r);
+
+    sleep = (ngx_msec_t) SvIV(ST(1));
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "perl sleep: %M", sleep);
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+
+    ctx->next = SvRV(ST(2));
+
+    r->connection->write->delayed = 1;
+    ngx_add_timer(r->connection->write, sleep);
+
+    r->write_event_handler = ngx_http_perl_sleep_handler;
+    r->main->count++;
+
+
+void
+log_error(r, err, msg)
+    CODE:
+
+    ngx_http_request_t  *r;
+    SV                  *err, *msg;
+    u_char              *p;
+    STRLEN               len;
+    ngx_err_t            e;
+
+    ngx_http_perl_set_request(r);
+
+    err = ST(1);
+
+    if (SvROK(err) && SvTYPE(SvRV(err)) == SVt_PV) {
+        err = SvRV(err);
+    }
+
+    e = SvIV(err);
+
+    msg = ST(2);
+
+    if (SvROK(msg) && SvTYPE(SvRV(msg)) == SVt_PV) {
+        msg = SvRV(msg);
+    }
+
+    p = (u_char *) SvPV(msg, len);
+
+    ngx_log_error(NGX_LOG_ERR, r->connection->log, e, "perl: %s", p);
diff --git a/nginx/src/http/modules/perl/ngx_http_perl_module.c b/nginx/src/http/modules/perl/ngx_http_perl_module.c
new file mode 100644 (file)
index 0000000..6d3be91
--- /dev/null
@@ -0,0 +1,1086 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_http_perl_module.h>
+
+
+typedef struct {
+    PerlInterpreter   *perl;
+    HV                *nginx;
+    ngx_array_t       *modules;
+    ngx_array_t       *requires;
+} ngx_http_perl_main_conf_t;
+
+
+typedef struct {
+    SV                *sub;
+    ngx_str_t          handler;
+} ngx_http_perl_loc_conf_t;
+
+
+typedef struct {
+    SV                *sub;
+    ngx_str_t          handler;
+} ngx_http_perl_variable_t;
+
+
+#if (NGX_HTTP_SSI)
+static ngx_int_t ngx_http_perl_ssi(ngx_http_request_t *r,
+    ngx_http_ssi_ctx_t *ssi_ctx, ngx_str_t **params);
+#endif
+
+static char *ngx_http_perl_init_interpreter(ngx_conf_t *cf,
+    ngx_http_perl_main_conf_t *pmcf);
+static PerlInterpreter *ngx_http_perl_create_interpreter(ngx_conf_t *cf,
+    ngx_http_perl_main_conf_t *pmcf);
+static ngx_int_t ngx_http_perl_run_requires(pTHX_ ngx_array_t *requires,
+    ngx_log_t *log);
+static ngx_int_t ngx_http_perl_call_handler(pTHX_ ngx_http_request_t *r,
+    HV *nginx, SV *sub, SV **args, ngx_str_t *handler, ngx_str_t *rv);
+static void ngx_http_perl_eval_anon_sub(pTHX_ ngx_str_t *handler, SV **sv);
+
+static ngx_int_t ngx_http_perl_preconfiguration(ngx_conf_t *cf);
+static void *ngx_http_perl_create_main_conf(ngx_conf_t *cf);
+static char *ngx_http_perl_init_main_conf(ngx_conf_t *cf, void *conf);
+static void *ngx_http_perl_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_perl_merge_loc_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static char *ngx_http_perl(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_http_perl_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+#if (NGX_HAVE_PERL_MULTIPLICITY)
+static void ngx_http_perl_cleanup_perl(void *data);
+#endif
+
+static ngx_int_t ngx_http_perl_init_worker(ngx_cycle_t *cycle);
+static void ngx_http_perl_exit(ngx_cycle_t *cycle);
+
+
+static ngx_command_t  ngx_http_perl_commands[] = {
+
+    { ngx_string("perl_modules"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      offsetof(ngx_http_perl_main_conf_t, modules),
+      NULL },
+
+    { ngx_string("perl_require"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      offsetof(ngx_http_perl_main_conf_t, requires),
+      NULL },
+
+    { ngx_string("perl"),
+      NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1,
+      ngx_http_perl,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("perl_set"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE2,
+      ngx_http_perl_set,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_perl_module_ctx = {
+    ngx_http_perl_preconfiguration,        /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    ngx_http_perl_create_main_conf,        /* create main configuration */
+    ngx_http_perl_init_main_conf,          /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_perl_create_loc_conf,         /* create location configuration */
+    ngx_http_perl_merge_loc_conf           /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_perl_module = {
+    NGX_MODULE_V1,
+    &ngx_http_perl_module_ctx,             /* module context */
+    ngx_http_perl_commands,                /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    ngx_http_perl_init_worker,             /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    ngx_http_perl_exit,                    /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+#if (NGX_HTTP_SSI)
+
+#define NGX_HTTP_PERL_SSI_SUB  0
+#define NGX_HTTP_PERL_SSI_ARG  1
+
+
+static ngx_http_ssi_param_t  ngx_http_perl_ssi_params[] = {
+    { ngx_string("sub"), NGX_HTTP_PERL_SSI_SUB, 1, 0 },
+    { ngx_string("arg"), NGX_HTTP_PERL_SSI_ARG, 0, 1 },
+    { ngx_null_string, 0, 0, 0 }
+};
+
+static ngx_http_ssi_command_t  ngx_http_perl_ssi_command = {
+    ngx_string("perl"), ngx_http_perl_ssi, ngx_http_perl_ssi_params, 0, 0, 1
+};
+
+#endif
+
+
+static ngx_str_t         ngx_null_name = ngx_null_string;
+static HV               *nginx_stash;
+
+#if (NGX_HAVE_PERL_MULTIPLICITY)
+static ngx_uint_t        ngx_perl_term;
+#else
+static PerlInterpreter  *perl;
+#endif
+
+
+static void
+ngx_http_perl_xs_init(pTHX)
+{
+    newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, __FILE__);
+
+    nginx_stash = gv_stashpv("nginx", TRUE);
+}
+
+
+static ngx_int_t
+ngx_http_perl_handler(ngx_http_request_t *r)
+{
+    r->main->count++;
+
+    ngx_http_perl_handle_request(r);
+
+    return NGX_DONE;
+}
+
+
+void
+ngx_http_perl_handle_request(ngx_http_request_t *r)
+{
+    SV                         *sub;
+    ngx_int_t                   rc;
+    ngx_str_t                   uri, args, *handler;
+    ngx_http_perl_ctx_t        *ctx;
+    ngx_http_perl_loc_conf_t   *plcf;
+    ngx_http_perl_main_conf_t  *pmcf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "perl handler");
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+
+    if (ctx == NULL) {
+        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_perl_ctx_t));
+        if (ctx == NULL) {
+            ngx_http_finalize_request(r, NGX_ERROR);
+            return;
+        }
+
+        ngx_http_set_ctx(r, ctx, ngx_http_perl_module);
+    }
+
+    pmcf = ngx_http_get_module_main_conf(r, ngx_http_perl_module);
+
+    {
+
+    dTHXa(pmcf->perl);
+    PERL_SET_CONTEXT(pmcf->perl);
+    PERL_SET_INTERP(pmcf->perl);
+
+    if (ctx->next == NULL) {
+        plcf = ngx_http_get_module_loc_conf(r, ngx_http_perl_module);
+        sub = plcf->sub;
+        handler = &plcf->handler;
+
+    } else {
+        sub = ctx->next;
+        handler = &ngx_null_name;
+        ctx->next = NULL;
+    }
+
+    rc = ngx_http_perl_call_handler(aTHX_ r, pmcf->nginx, sub, NULL, handler,
+                                    NULL);
+
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "perl handler done: %i", rc);
+
+    if (rc == NGX_DONE) {
+        ngx_http_finalize_request(r, rc);
+        return;
+    }
+
+    if (rc > 600) {
+        rc = NGX_OK;
+    }
+
+    if (ctx->redirect_uri.len) {
+        uri = ctx->redirect_uri;
+        args = ctx->redirect_args;
+
+    } else {
+        uri.len = 0;
+    }
+
+    ctx->filename.data = NULL;
+    ctx->redirect_uri.len = 0;
+
+    if (ctx->done || ctx->next) {
+        ngx_http_finalize_request(r, NGX_DONE);
+        return;
+    }
+
+    if (uri.len) {
+        ngx_http_internal_redirect(r, &uri, &args);
+        ngx_http_finalize_request(r, NGX_DONE);
+        return;
+    }
+
+    if (rc == NGX_OK || rc == NGX_HTTP_OK) {
+        ngx_http_send_special(r, NGX_HTTP_LAST);
+        ctx->done = 1;
+    }
+
+    ngx_http_finalize_request(r, rc);
+}
+
+
+void
+ngx_http_perl_sleep_handler(ngx_http_request_t *r)
+{
+    ngx_event_t  *wev;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "perl sleep handler");
+
+    wev = r->connection->write;
+
+    if (wev->delayed) {
+
+        if (ngx_handle_write_event(wev, 0) != NGX_OK) {
+            ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        }
+
+        return;
+    }
+
+    ngx_http_perl_handle_request(r);
+}
+
+
+static ngx_int_t
+ngx_http_perl_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+    uintptr_t data)
+{
+    ngx_http_perl_variable_t *pv = (ngx_http_perl_variable_t *) data;
+
+    ngx_int_t                   rc;
+    ngx_str_t                   value;
+    ngx_http_perl_ctx_t        *ctx;
+    ngx_http_perl_main_conf_t  *pmcf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "perl variable handler");
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+
+    if (ctx == NULL) {
+        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_perl_ctx_t));
+        if (ctx == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_http_set_ctx(r, ctx, ngx_http_perl_module);
+    }
+
+    pmcf = ngx_http_get_module_main_conf(r, ngx_http_perl_module);
+
+    value.data = NULL;
+
+    {
+
+    dTHXa(pmcf->perl);
+    PERL_SET_CONTEXT(pmcf->perl);
+    PERL_SET_INTERP(pmcf->perl);
+
+    rc = ngx_http_perl_call_handler(aTHX_ r, pmcf->nginx, pv->sub, NULL,
+                                    &pv->handler, &value);
+
+    }
+
+    if (value.data) {
+        v->len = value.len;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = value.data;
+
+    } else {
+        v->not_found = 1;
+    }
+
+    ctx->filename.data = NULL;
+    ctx->redirect_uri.len = 0;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "perl variable done");
+
+    return rc;
+}
+
+
+#if (NGX_HTTP_SSI)
+
+static ngx_int_t
+ngx_http_perl_ssi(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ssi_ctx,
+    ngx_str_t **params)
+{
+    SV                         *sv, **asv;
+    ngx_int_t                   rc;
+    ngx_str_t                  *handler, **args;
+    ngx_uint_t                  i;
+    ngx_http_perl_ctx_t        *ctx;
+    ngx_http_perl_main_conf_t  *pmcf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "perl ssi handler");
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+
+    if (ctx == NULL) {
+        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_perl_ctx_t));
+        if (ctx == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_http_set_ctx(r, ctx, ngx_http_perl_module);
+    }
+
+    pmcf = ngx_http_get_module_main_conf(r, ngx_http_perl_module);
+
+    ctx->ssi = ssi_ctx;
+
+    handler = params[NGX_HTTP_PERL_SSI_SUB];
+    handler->data[handler->len] = '\0';
+
+    {
+
+    dTHXa(pmcf->perl);
+    PERL_SET_CONTEXT(pmcf->perl);
+    PERL_SET_INTERP(pmcf->perl);
+
+#if 0
+
+    /* the code is disabled to force the precompiled perl code using only */
+
+    ngx_http_perl_eval_anon_sub(aTHX_ handler, &sv);
+
+    if (sv == &PL_sv_undef) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "eval_pv(\"%V\") failed", handler);
+        return NGX_ERROR;
+    }
+
+    if (sv == NULL) {
+        sv = newSVpvn((char *) handler->data, handler->len);
+    }
+
+#endif
+
+    sv = newSVpvn((char *) handler->data, handler->len);
+
+    args = &params[NGX_HTTP_PERL_SSI_ARG];
+
+    if (args[0]) {
+
+        for (i = 0; args[i]; i++) { /* void */ }
+
+        asv = ngx_pcalloc(r->pool, (i + 1) * sizeof(SV *));
+
+        if (asv == NULL) {
+            SvREFCNT_dec(sv);
+            return NGX_ERROR;
+        }
+
+        asv[0] = (SV *) (uintptr_t) i;
+
+        for (i = 0; args[i]; i++) {
+            asv[i + 1] = newSVpvn((char *) args[i]->data, args[i]->len);
+        }
+
+    } else {
+        asv = NULL;
+    }
+
+    rc = ngx_http_perl_call_handler(aTHX_ r, pmcf->nginx, sv, asv, handler,
+                                    NULL);
+
+    SvREFCNT_dec(sv);
+
+    }
+
+    ctx->filename.data = NULL;
+    ctx->redirect_uri.len = 0;
+    ctx->ssi = NULL;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "perl ssi done");
+
+    return rc;
+}
+
+#endif
+
+
+static char *
+ngx_http_perl_init_interpreter(ngx_conf_t *cf, ngx_http_perl_main_conf_t *pmcf)
+{
+    ngx_str_t           *m;
+    ngx_uint_t           i;
+#if (NGX_HAVE_PERL_MULTIPLICITY)
+    ngx_pool_cleanup_t  *cln;
+
+    cln = ngx_pool_cleanup_add(cf->pool, 0);
+    if (cln == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+#endif
+
+#ifdef NGX_PERL_MODULES
+    if (pmcf->modules == NGX_CONF_UNSET_PTR) {
+
+        pmcf->modules = ngx_array_create(cf->pool, 1, sizeof(ngx_str_t));
+        if (pmcf->modules == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        m = ngx_array_push(pmcf->modules);
+        if (m == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        ngx_str_set(m, NGX_PERL_MODULES);
+    }
+#endif
+
+    if (pmcf->modules != NGX_CONF_UNSET_PTR) {
+        m = pmcf->modules->elts;
+        for (i = 0; i < pmcf->modules->nelts; i++) {
+            if (ngx_conf_full_name(cf->cycle, &m[i], 0) != NGX_OK) {
+                return NGX_CONF_ERROR;
+            }
+        }
+    }
+
+#if !(NGX_HAVE_PERL_MULTIPLICITY)
+
+    if (perl) {
+
+        if (ngx_set_environment(cf->cycle, NULL) == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_http_perl_run_requires(aTHX_ pmcf->requires, cf->log)
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+
+        pmcf->perl = perl;
+        pmcf->nginx = nginx_stash;
+
+        return NGX_CONF_OK;
+    }
+
+#endif
+
+    if (nginx_stash == NULL) {
+        PERL_SYS_INIT(&ngx_argc, &ngx_argv);
+    }
+
+    pmcf->perl = ngx_http_perl_create_interpreter(cf, pmcf);
+
+    if (pmcf->perl == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    pmcf->nginx = nginx_stash;
+
+#if (NGX_HAVE_PERL_MULTIPLICITY)
+
+    cln->handler = ngx_http_perl_cleanup_perl;
+    cln->data = pmcf->perl;
+
+#else
+
+    perl = pmcf->perl;
+
+#endif
+
+    return NGX_CONF_OK;
+}
+
+
+static PerlInterpreter *
+ngx_http_perl_create_interpreter(ngx_conf_t *cf,
+    ngx_http_perl_main_conf_t *pmcf)
+{
+    int                n;
+    STRLEN             len;
+    SV                *sv;
+    char              *ver, **embedding;
+    ngx_str_t         *m;
+    ngx_uint_t         i;
+    PerlInterpreter   *perl;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0, "create perl interpreter");
+
+    if (ngx_set_environment(cf->cycle, NULL) == NULL) {
+        return NULL;
+    }
+
+    perl = perl_alloc();
+    if (perl == NULL) {
+        ngx_log_error(NGX_LOG_ALERT, cf->log, 0, "perl_alloc() failed");
+        return NULL;
+    }
+
+    {
+
+    dTHXa(perl);
+    PERL_SET_CONTEXT(perl);
+    PERL_SET_INTERP(perl);
+
+    perl_construct(perl);
+
+#ifdef PERL_EXIT_DESTRUCT_END
+    PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
+#endif
+
+    n = (pmcf->modules != NGX_CONF_UNSET_PTR) ? pmcf->modules->nelts * 2 : 0;
+
+    embedding = ngx_palloc(cf->pool, (5 + n) * sizeof(char *));
+    if (embedding == NULL) {
+        goto fail;
+    }
+
+    embedding[0] = "";
+
+    if (n++) {
+        m = pmcf->modules->elts;
+        for (i = 0; i < pmcf->modules->nelts; i++) {
+            embedding[2 * i + 1] = "-I";
+            embedding[2 * i + 2] = (char *) m[i].data;
+        }
+    }
+
+    embedding[n++] = "-Mnginx";
+    embedding[n++] = "-e";
+    embedding[n++] = "0";
+    embedding[n] = NULL;
+
+    n = perl_parse(perl, ngx_http_perl_xs_init, n, embedding, NULL);
+
+    if (n != 0) {
+        ngx_log_error(NGX_LOG_ALERT, cf->log, 0, "perl_parse() failed: %d", n);
+        goto fail;
+    }
+
+    sv = get_sv("nginx::VERSION", FALSE);
+    ver = SvPV(sv, len);
+
+    if (ngx_strcmp(ver, NGINX_VERSION) != 0) {
+        ngx_log_error(NGX_LOG_ALERT, cf->log, 0,
+                      "version " NGINX_VERSION " of nginx.pm is required, "
+                      "but %s was found", ver);
+        goto fail;
+    }
+
+    if (ngx_http_perl_run_requires(aTHX_ pmcf->requires, cf->log) != NGX_OK) {
+        goto fail;
+    }
+
+    }
+
+    return perl;
+
+fail:
+
+    (void) perl_destruct(perl);
+
+    perl_free(perl);
+
+    return NULL;
+}
+
+
+static ngx_int_t
+ngx_http_perl_run_requires(pTHX_ ngx_array_t *requires, ngx_log_t *log)
+{
+    u_char      *err;
+    STRLEN       len;
+    ngx_str_t   *script;
+    ngx_uint_t   i;
+
+    if (requires == NGX_CONF_UNSET_PTR) {
+        return NGX_OK;
+    }
+
+    script = requires->elts;
+    for (i = 0; i < requires->nelts; i++) {
+
+        require_pv((char *) script[i].data);
+
+        if (SvTRUE(ERRSV)) {
+
+            err = (u_char *) SvPV(ERRSV, len);
+            while (--len && (err[len] == CR || err[len] == LF)) { /* void */ }
+
+            ngx_log_error(NGX_LOG_EMERG, log, 0,
+                          "require_pv(\"%s\") failed: \"%*s\"",
+                          script[i].data, len + 1, err);
+
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_perl_call_handler(pTHX_ ngx_http_request_t *r, HV *nginx, SV *sub,
+    SV **args, ngx_str_t *handler, ngx_str_t *rv)
+{
+    SV                *sv;
+    int                n, status;
+    char              *line;
+    u_char            *err;
+    STRLEN             len, n_a;
+    ngx_uint_t         i;
+    ngx_connection_t  *c;
+
+    dSP;
+
+    status = 0;
+
+    ENTER;
+    SAVETMPS;
+
+    PUSHMARK(sp);
+
+    sv = sv_2mortal(sv_bless(newRV_noinc(newSViv(PTR2IV(r))), nginx));
+    XPUSHs(sv);
+
+    if (args) {
+        EXTEND(sp, (intptr_t) args[0]);
+
+        for (i = 1; i <= (uintptr_t) args[0]; i++) {
+            PUSHs(sv_2mortal(args[i]));
+        }
+    }
+
+    PUTBACK;
+
+    c = r->connection;
+
+    n = call_sv(sub, G_EVAL);
+
+    SPAGAIN;
+
+    if (n) {
+        if (rv == NULL) {
+            status = POPi;
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                           "call_sv: %d", status);
+
+        } else {
+            line = SvPVx(POPs, n_a);
+            rv->len = n_a;
+
+            rv->data = ngx_pnalloc(r->pool, n_a);
+            if (rv->data == NULL) {
+                return NGX_ERROR;
+            }
+
+            ngx_memcpy(rv->data, line, n_a);
+        }
+    }
+
+    PUTBACK;
+
+    FREETMPS;
+    LEAVE;
+
+    /* check $@ */
+
+    if (SvTRUE(ERRSV)) {
+
+        err = (u_char *) SvPV(ERRSV, len);
+        while (--len && (err[len] == CR || err[len] == LF)) { /* void */ }
+
+        ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                      "call_sv(\"%V\") failed: \"%*s\"", handler, len + 1, err);
+
+        if (rv) {
+            return NGX_ERROR;
+        }
+
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (n != 1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                      "call_sv(\"%V\") returned %d results", handler, n);
+        status = NGX_OK;
+    }
+
+    if (rv) {
+        return NGX_OK;
+    }
+
+    return (ngx_int_t) status;
+}
+
+
+static void
+ngx_http_perl_eval_anon_sub(pTHX_ ngx_str_t *handler, SV **sv)
+{
+    u_char  *p;
+
+    for (p = handler->data; *p; p++) {
+        if (*p != ' ' && *p != '\t' && *p != CR && *p != LF) {
+            break;
+        }
+    }
+
+    if (ngx_strncmp(p, "sub ", 4) == 0
+        || ngx_strncmp(p, "sub{", 4) == 0
+        || ngx_strncmp(p, "use ", 4) == 0)
+    {
+        *sv = eval_pv((char *) p, FALSE);
+
+        /* eval_pv() does not set ERRSV on failure */
+
+        return;
+    }
+
+    *sv = NULL;
+}
+
+
+static void *
+ngx_http_perl_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_http_perl_main_conf_t  *pmcf;
+
+    pmcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_perl_main_conf_t));
+    if (pmcf == NULL) {
+        return NULL;
+    }
+
+    pmcf->modules = NGX_CONF_UNSET_PTR;
+    pmcf->requires = NGX_CONF_UNSET_PTR;
+
+    return pmcf;
+}
+
+
+static char *
+ngx_http_perl_init_main_conf(ngx_conf_t *cf, void *conf)
+{
+    ngx_http_perl_main_conf_t *pmcf = conf;
+
+    if (pmcf->perl == NULL) {
+        if (ngx_http_perl_init_interpreter(cf, pmcf) != NGX_CONF_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+#if (NGX_HAVE_PERL_MULTIPLICITY)
+
+static void
+ngx_http_perl_cleanup_perl(void *data)
+{
+    PerlInterpreter  *perl = data;
+
+    PERL_SET_CONTEXT(perl);
+    PERL_SET_INTERP(perl);
+
+    (void) perl_destruct(perl);
+
+    perl_free(perl);
+
+    if (ngx_perl_term) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, "perl term");
+
+        PERL_SYS_TERM();
+    }
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_perl_preconfiguration(ngx_conf_t *cf)
+{
+#if (NGX_HTTP_SSI)
+    ngx_int_t                  rc;
+    ngx_http_ssi_main_conf_t  *smcf;
+
+    smcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_ssi_filter_module);
+
+    rc = ngx_hash_add_key(&smcf->commands, &ngx_http_perl_ssi_command.name,
+                          &ngx_http_perl_ssi_command, NGX_HASH_READONLY_KEY);
+
+    if (rc != NGX_OK) {
+        if (rc == NGX_BUSY) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "conflicting SSI command \"%V\"",
+                               &ngx_http_perl_ssi_command.name);
+        }
+
+        return NGX_ERROR;
+    }
+#endif
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_perl_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_perl_loc_conf_t *plcf;
+
+    plcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_perl_loc_conf_t));
+    if (plcf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     plcf->handler = { 0, NULL };
+     */
+
+    return plcf;
+}
+
+
+static char *
+ngx_http_perl_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_perl_loc_conf_t *prev = parent;
+    ngx_http_perl_loc_conf_t *conf = child;
+
+    if (conf->sub == NULL) {
+        conf->sub = prev->sub;
+        conf->handler = prev->handler;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_perl(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_perl_loc_conf_t *plcf = conf;
+
+    ngx_str_t                  *value;
+    ngx_http_core_loc_conf_t   *clcf;
+    ngx_http_perl_main_conf_t  *pmcf;
+
+    value = cf->args->elts;
+
+    if (plcf->handler.data) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "duplicate perl handler \"%V\"", &value[1]);
+        return NGX_CONF_ERROR;
+    }
+
+    pmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_perl_module);
+
+    if (pmcf->perl == NULL) {
+        if (ngx_http_perl_init_interpreter(cf, pmcf) != NGX_CONF_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    plcf->handler = value[1];
+
+    {
+
+    dTHXa(pmcf->perl);
+    PERL_SET_CONTEXT(pmcf->perl);
+    PERL_SET_INTERP(pmcf->perl);
+
+    ngx_http_perl_eval_anon_sub(aTHX_ &value[1], &plcf->sub);
+
+    if (plcf->sub == &PL_sv_undef) {
+        ngx_conf_log_error(NGX_LOG_ERR, cf, 0,
+                           "eval_pv(\"%V\") failed", &value[1]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (plcf->sub == NULL) {
+        plcf->sub = newSVpvn((char *) value[1].data, value[1].len);
+    }
+
+    }
+
+    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+    clcf->handler = ngx_http_perl_handler;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_perl_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_int_t                   index;
+    ngx_str_t                  *value;
+    ngx_http_variable_t        *v;
+    ngx_http_perl_variable_t   *pv;
+    ngx_http_perl_main_conf_t  *pmcf;
+
+    value = cf->args->elts;
+
+    if (value[1].data[0] != '$') {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid variable name \"%V\"", &value[1]);
+        return NGX_CONF_ERROR;
+    }
+
+    value[1].len--;
+    value[1].data++;
+
+    v = ngx_http_add_variable(cf, &value[1], NGX_HTTP_VAR_CHANGEABLE);
+    if (v == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    pv = ngx_palloc(cf->pool, sizeof(ngx_http_perl_variable_t));
+    if (pv == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    index = ngx_http_get_variable_index(cf, &value[1]);
+    if (index == NGX_ERROR) {
+        return NGX_CONF_ERROR;
+    }
+
+    pmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_perl_module);
+
+    if (pmcf->perl == NULL) {
+        if (ngx_http_perl_init_interpreter(cf, pmcf) != NGX_CONF_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    pv->handler = value[2];
+
+    {
+
+    dTHXa(pmcf->perl);
+    PERL_SET_CONTEXT(pmcf->perl);
+    PERL_SET_INTERP(pmcf->perl);
+
+    ngx_http_perl_eval_anon_sub(aTHX_ &value[2], &pv->sub);
+
+    if (pv->sub == &PL_sv_undef) {
+        ngx_conf_log_error(NGX_LOG_ERR, cf, 0,
+                           "eval_pv(\"%V\") failed", &value[2]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (pv->sub == NULL) {
+        pv->sub = newSVpvn((char *) value[2].data, value[2].len);
+    }
+
+    }
+
+    v->get_handler = ngx_http_perl_variable;
+    v->data = (uintptr_t) pv;
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_perl_init_worker(ngx_cycle_t *cycle)
+{
+    ngx_http_perl_main_conf_t  *pmcf;
+
+    pmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_perl_module);
+
+    if (pmcf) {
+        dTHXa(pmcf->perl);
+        PERL_SET_CONTEXT(pmcf->perl);
+        PERL_SET_INTERP(pmcf->perl);
+
+        /* set worker's $$ */
+
+        sv_setiv(GvSV(gv_fetchpv("$", TRUE, SVt_PV)), (I32) ngx_pid);
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_perl_exit(ngx_cycle_t *cycle)
+{
+#if (NGX_HAVE_PERL_MULTIPLICITY)
+
+    /*
+     * the master exit hook is run before global pool cleanup,
+     * therefore just set flag here
+     */
+
+    ngx_perl_term = 1;
+
+#else
+
+    if (nginx_stash) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cycle->log, 0, "perl term");
+
+        (void) perl_destruct(perl);
+
+        perl_free(perl);
+
+        PERL_SYS_TERM();
+    }
+
+#endif
+}
diff --git a/nginx/src/http/modules/perl/ngx_http_perl_module.h b/nginx/src/http/modules/perl/ngx_http_perl_module.h
new file mode 100644 (file)
index 0000000..5e60b03
--- /dev/null
@@ -0,0 +1,67 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_PERL_MODULE_H_INCLUDED_
+#define _NGX_HTTP_PERL_MODULE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <nginx.h>
+
+#include <EXTERN.h>
+#include <perl.h>
+
+
+typedef ngx_http_request_t   *nginx;
+
+typedef struct {
+    ngx_str_t                 filename;
+    ngx_str_t                 redirect_uri;
+    ngx_str_t                 redirect_args;
+
+    SV                       *next;
+
+    ngx_uint_t                done;       /* unsigned  done:1; */
+
+    ngx_array_t              *variables;  /* array of ngx_http_perl_var_t */
+
+#if (NGX_HTTP_SSI)
+    ngx_http_ssi_ctx_t       *ssi;
+#endif
+} ngx_http_perl_ctx_t;
+
+
+typedef struct {
+    ngx_uint_t    hash;
+    ngx_str_t     name;
+    ngx_str_t     value;
+} ngx_http_perl_var_t;
+
+
+extern ngx_module_t  ngx_http_perl_module;
+
+
+/*
+ * workaround for "unused variable `Perl___notused'" warning
+ * when building with perl 5.6.1
+ */
+#ifndef PERL_IMPLICIT_CONTEXT
+#undef  dTHXa
+#define dTHXa(a)
+#endif
+
+
+extern void boot_DynaLoader(pTHX_ CV* cv);
+
+
+void ngx_http_perl_handle_request(ngx_http_request_t *r);
+void ngx_http_perl_sleep_handler(ngx_http_request_t *r);
+
+
+#endif /* _NGX_HTTP_PERL_MODULE_H_INCLUDED_ */
diff --git a/nginx/src/http/modules/perl/typemap b/nginx/src/http/modules/perl/typemap
new file mode 100644 (file)
index 0000000..e2f1a4c
--- /dev/null
@@ -0,0 +1,3 @@
+TYPEMAP
+
+nginx  T_PTROBJ
diff --git a/nginx/src/http/ngx_http.c b/nginx/src/http/ngx_http.c
new file mode 100644 (file)
index 0000000..9d8b6d7
--- /dev/null
@@ -0,0 +1,2078 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static char *ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static ngx_int_t ngx_http_init_phases(ngx_conf_t *cf,
+    ngx_http_core_main_conf_t *cmcf);
+static ngx_int_t ngx_http_init_headers_in_hash(ngx_conf_t *cf,
+    ngx_http_core_main_conf_t *cmcf);
+static ngx_int_t ngx_http_init_phase_handlers(ngx_conf_t *cf,
+    ngx_http_core_main_conf_t *cmcf);
+
+static ngx_int_t ngx_http_add_addresses(ngx_conf_t *cf,
+    ngx_http_core_srv_conf_t *cscf, ngx_http_conf_port_t *port,
+    ngx_http_listen_opt_t *lsopt);
+static ngx_int_t ngx_http_add_address(ngx_conf_t *cf,
+    ngx_http_core_srv_conf_t *cscf, ngx_http_conf_port_t *port,
+    ngx_http_listen_opt_t *lsopt);
+static ngx_int_t ngx_http_add_server(ngx_conf_t *cf,
+    ngx_http_core_srv_conf_t *cscf, ngx_http_conf_addr_t *addr);
+
+static char *ngx_http_merge_servers(ngx_conf_t *cf,
+    ngx_http_core_main_conf_t *cmcf, ngx_http_module_t *module,
+    ngx_uint_t ctx_index);
+static char *ngx_http_merge_locations(ngx_conf_t *cf,
+    ngx_queue_t *locations, void **loc_conf, ngx_http_module_t *module,
+    ngx_uint_t ctx_index);
+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_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,
+    ngx_queue_t *locations);
+static void ngx_http_create_locations_list(ngx_queue_t *locations,
+    ngx_queue_t *q);
+static ngx_http_location_tree_node_t *
+    ngx_http_create_locations_tree(ngx_conf_t *cf, ngx_queue_t *locations,
+    size_t prefix);
+
+static ngx_int_t ngx_http_optimize_servers(ngx_conf_t *cf,
+    ngx_http_core_main_conf_t *cmcf, ngx_array_t *ports);
+static ngx_int_t ngx_http_server_names(ngx_conf_t *cf,
+    ngx_http_core_main_conf_t *cmcf, ngx_http_conf_addr_t *addr);
+static ngx_int_t ngx_http_cmp_conf_addrs(const void *one, const void *two);
+static int ngx_libc_cdecl ngx_http_cmp_dns_wildcards(const void *one,
+    const void *two);
+
+static ngx_int_t ngx_http_init_listening(ngx_conf_t *cf,
+    ngx_http_conf_port_t *port);
+static ngx_listening_t *ngx_http_add_listening(ngx_conf_t *cf,
+    ngx_http_conf_addr_t *addr);
+static ngx_int_t ngx_http_add_addrs(ngx_conf_t *cf, ngx_http_port_t *hport,
+    ngx_http_conf_addr_t *addr);
+#if (NGX_HAVE_INET6)
+static ngx_int_t ngx_http_add_addrs6(ngx_conf_t *cf, ngx_http_port_t *hport,
+    ngx_http_conf_addr_t *addr);
+#endif
+
+ngx_uint_t   ngx_http_max_module;
+
+
+ngx_http_output_header_filter_pt  ngx_http_top_header_filter;
+ngx_http_output_body_filter_pt    ngx_http_top_body_filter;
+ngx_http_request_body_filter_pt   ngx_http_top_request_body_filter;
+
+
+ngx_str_t  ngx_http_html_default_types[] = {
+    ngx_string("text/html"),
+    ngx_null_string
+};
+
+
+static ngx_command_t  ngx_http_commands[] = {
+
+    { ngx_string("http"),
+      NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+      ngx_http_block,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_core_module_t  ngx_http_module_ctx = {
+    ngx_string("http"),
+    NULL,
+    NULL
+};
+
+
+ngx_module_t  ngx_http_module = {
+    NGX_MODULE_V1,
+    &ngx_http_module_ctx,                  /* module context */
+    ngx_http_commands,                     /* module directives */
+    NGX_CORE_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 char *
+ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char                        *rv;
+    ngx_uint_t                   mi, m, s;
+    ngx_conf_t                   pcf;
+    ngx_http_module_t           *module;
+    ngx_http_conf_ctx_t         *ctx;
+    ngx_http_core_loc_conf_t    *clcf;
+    ngx_http_core_srv_conf_t   **cscfp;
+    ngx_http_core_main_conf_t   *cmcf;
+
+    if (*(ngx_http_conf_ctx_t **) conf) {
+        return "is duplicate";
+    }
+
+    /* the main http context */
+
+    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
+    if (ctx == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    *(ngx_http_conf_ctx_t **) conf = ctx;
+
+
+    /* count the number of the http modules and set up their indices */
+
+    ngx_http_max_module = ngx_count_modules(cf->cycle, NGX_HTTP_MODULE);
+
+
+    /* the http main_conf context, it is the same in the all http contexts */
+
+    ctx->main_conf = ngx_pcalloc(cf->pool,
+                                 sizeof(void *) * ngx_http_max_module);
+    if (ctx->main_conf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+
+    /*
+     * the http null srv_conf context, it is used to merge
+     * the server{}s' srv_conf's
+     */
+
+    ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
+    if (ctx->srv_conf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+
+    /*
+     * the http null loc_conf context, it is used to merge
+     * the server{}s' loc_conf's
+     */
+
+    ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
+    if (ctx->loc_conf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+
+    /*
+     * create the main_conf's, the null srv_conf's, and the null loc_conf's
+     * of the all http modules
+     */
+
+    for (m = 0; cf->cycle->modules[m]; m++) {
+        if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
+            continue;
+        }
+
+        module = cf->cycle->modules[m]->ctx;
+        mi = cf->cycle->modules[m]->ctx_index;
+
+        if (module->create_main_conf) {
+            ctx->main_conf[mi] = module->create_main_conf(cf);
+            if (ctx->main_conf[mi] == NULL) {
+                return NGX_CONF_ERROR;
+            }
+        }
+
+        if (module->create_srv_conf) {
+            ctx->srv_conf[mi] = module->create_srv_conf(cf);
+            if (ctx->srv_conf[mi] == NULL) {
+                return NGX_CONF_ERROR;
+            }
+        }
+
+        if (module->create_loc_conf) {
+            ctx->loc_conf[mi] = module->create_loc_conf(cf);
+            if (ctx->loc_conf[mi] == NULL) {
+                return NGX_CONF_ERROR;
+            }
+        }
+    }
+
+    pcf = *cf;
+    cf->ctx = ctx;
+
+    for (m = 0; cf->cycle->modules[m]; m++) {
+        if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
+            continue;
+        }
+
+        module = cf->cycle->modules[m]->ctx;
+
+        if (module->preconfiguration) {
+            if (module->preconfiguration(cf) != NGX_OK) {
+                return NGX_CONF_ERROR;
+            }
+        }
+    }
+
+    /* parse inside the http{} block */
+
+    cf->module_type = NGX_HTTP_MODULE;
+    cf->cmd_type = NGX_HTTP_MAIN_CONF;
+    rv = ngx_conf_parse(cf, NULL);
+
+    if (rv != NGX_CONF_OK) {
+        goto failed;
+    }
+
+    /*
+     * init http{} main_conf's, merge the server{}s' srv_conf's
+     * and its location{}s' loc_conf's
+     */
+
+    cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
+    cscfp = cmcf->servers.elts;
+
+    for (m = 0; cf->cycle->modules[m]; m++) {
+        if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
+            continue;
+        }
+
+        module = cf->cycle->modules[m]->ctx;
+        mi = cf->cycle->modules[m]->ctx_index;
+
+        /* init http{} main_conf's */
+
+        if (module->init_main_conf) {
+            rv = module->init_main_conf(cf, ctx->main_conf[mi]);
+            if (rv != NGX_CONF_OK) {
+                goto failed;
+            }
+        }
+
+        rv = ngx_http_merge_servers(cf, cmcf, module, mi);
+        if (rv != NGX_CONF_OK) {
+            goto failed;
+        }
+    }
+
+
+    /* create location trees */
+
+    for (s = 0; s < cmcf->servers.nelts; s++) {
+
+        clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];
+
+        if (ngx_http_init_locations(cf, cscfp[s], clcf) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+
+    if (ngx_http_init_phases(cf, cmcf) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (ngx_http_init_headers_in_hash(cf, cmcf) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+
+    for (m = 0; cf->cycle->modules[m]; m++) {
+        if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
+            continue;
+        }
+
+        module = cf->cycle->modules[m]->ctx;
+
+        if (module->postconfiguration) {
+            if (module->postconfiguration(cf) != NGX_OK) {
+                return NGX_CONF_ERROR;
+            }
+        }
+    }
+
+    if (ngx_http_variables_init_vars(cf) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    /*
+     * http{}'s cf->ctx was needed while the configuration merging
+     * and in postconfiguration process
+     */
+
+    *cf = pcf;
+
+
+    if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+
+    /* optimize the lists of ports, addresses and server names */
+
+    if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+
+failed:
+
+    *cf = pcf;
+
+    return rv;
+}
+
+
+static ngx_int_t
+ngx_http_init_phases(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)
+{
+    if (ngx_array_init(&cmcf->phases[NGX_HTTP_POST_READ_PHASE].handlers,
+                       cf->pool, 1, sizeof(ngx_http_handler_pt))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&cmcf->phases[NGX_HTTP_SERVER_REWRITE_PHASE].handlers,
+                       cf->pool, 1, sizeof(ngx_http_handler_pt))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers,
+                       cf->pool, 1, sizeof(ngx_http_handler_pt))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers,
+                       cf->pool, 1, sizeof(ngx_http_handler_pt))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers,
+                       cf->pool, 2, sizeof(ngx_http_handler_pt))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&cmcf->phases[NGX_HTTP_PRECONTENT_PHASE].handlers,
+                       cf->pool, 2, sizeof(ngx_http_handler_pt))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers,
+                       cf->pool, 4, sizeof(ngx_http_handler_pt))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&cmcf->phases[NGX_HTTP_LOG_PHASE].handlers,
+                       cf->pool, 1, sizeof(ngx_http_handler_pt))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_init_headers_in_hash(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)
+{
+    ngx_array_t         headers_in;
+    ngx_hash_key_t     *hk;
+    ngx_hash_init_t     hash;
+    ngx_http_header_t  *header;
+
+    if (ngx_array_init(&headers_in, cf->temp_pool, 32, sizeof(ngx_hash_key_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    for (header = ngx_http_headers_in; header->name.len; header++) {
+        hk = ngx_array_push(&headers_in);
+        if (hk == NULL) {
+            return NGX_ERROR;
+        }
+
+        hk->key = header->name;
+        hk->key_hash = ngx_hash_key_lc(header->name.data, header->name.len);
+        hk->value = header;
+    }
+
+    hash.hash = &cmcf->headers_in_hash;
+    hash.key = ngx_hash_key_lc;
+    hash.max_size = 512;
+    hash.bucket_size = ngx_align(64, ngx_cacheline_size);
+    hash.name = "headers_in_hash";
+    hash.pool = cf->pool;
+    hash.temp_pool = NULL;
+
+    if (ngx_hash_init(&hash, headers_in.elts, headers_in.nelts) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_init_phase_handlers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)
+{
+    ngx_int_t                   j;
+    ngx_uint_t                  i, n;
+    ngx_uint_t                  find_config_index, use_rewrite, use_access;
+    ngx_http_handler_pt        *h;
+    ngx_http_phase_handler_t   *ph;
+    ngx_http_phase_handler_pt   checker;
+
+    cmcf->phase_engine.server_rewrite_index = (ngx_uint_t) -1;
+    cmcf->phase_engine.location_rewrite_index = (ngx_uint_t) -1;
+    find_config_index = 0;
+    use_rewrite = cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers.nelts ? 1 : 0;
+    use_access = cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers.nelts ? 1 : 0;
+
+    n = 1                  /* find config phase */
+        + use_rewrite      /* post rewrite phase */
+        + use_access;      /* post access phase */
+
+    for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) {
+        n += cmcf->phases[i].handlers.nelts;
+    }
+
+    ph = ngx_pcalloc(cf->pool,
+                     n * sizeof(ngx_http_phase_handler_t) + sizeof(void *));
+    if (ph == NULL) {
+        return NGX_ERROR;
+    }
+
+    cmcf->phase_engine.handlers = ph;
+    n = 0;
+
+    for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) {
+        h = cmcf->phases[i].handlers.elts;
+
+        switch (i) {
+
+        case NGX_HTTP_SERVER_REWRITE_PHASE:
+            if (cmcf->phase_engine.server_rewrite_index == (ngx_uint_t) -1) {
+                cmcf->phase_engine.server_rewrite_index = n;
+            }
+            checker = ngx_http_core_rewrite_phase;
+
+            break;
+
+        case NGX_HTTP_FIND_CONFIG_PHASE:
+            find_config_index = n;
+
+            ph->checker = ngx_http_core_find_config_phase;
+            n++;
+            ph++;
+
+            continue;
+
+        case NGX_HTTP_REWRITE_PHASE:
+            if (cmcf->phase_engine.location_rewrite_index == (ngx_uint_t) -1) {
+                cmcf->phase_engine.location_rewrite_index = n;
+            }
+            checker = ngx_http_core_rewrite_phase;
+
+            break;
+
+        case NGX_HTTP_POST_REWRITE_PHASE:
+            if (use_rewrite) {
+                ph->checker = ngx_http_core_post_rewrite_phase;
+                ph->next = find_config_index;
+                n++;
+                ph++;
+            }
+
+            continue;
+
+        case NGX_HTTP_ACCESS_PHASE:
+            checker = ngx_http_core_access_phase;
+            n++;
+            break;
+
+        case NGX_HTTP_POST_ACCESS_PHASE:
+            if (use_access) {
+                ph->checker = ngx_http_core_post_access_phase;
+                ph->next = n;
+                ph++;
+            }
+
+            continue;
+
+        case NGX_HTTP_CONTENT_PHASE:
+            checker = ngx_http_core_content_phase;
+            break;
+
+        default:
+            checker = ngx_http_core_generic_phase;
+        }
+
+        n += cmcf->phases[i].handlers.nelts;
+
+        for (j = cmcf->phases[i].handlers.nelts - 1; j >= 0; j--) {
+            ph->checker = checker;
+            ph->handler = h[j];
+            ph->next = n;
+            ph++;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_http_merge_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
+    ngx_http_module_t *module, ngx_uint_t ctx_index)
+{
+    char                        *rv;
+    ngx_uint_t                   s;
+    ngx_http_conf_ctx_t         *ctx, saved;
+    ngx_http_core_loc_conf_t    *clcf;
+    ngx_http_core_srv_conf_t   **cscfp;
+
+    cscfp = cmcf->servers.elts;
+    ctx = (ngx_http_conf_ctx_t *) cf->ctx;
+    saved = *ctx;
+    rv = NGX_CONF_OK;
+
+    for (s = 0; s < cmcf->servers.nelts; s++) {
+
+        /* merge the server{}s' srv_conf's */
+
+        ctx->srv_conf = cscfp[s]->ctx->srv_conf;
+
+        if (module->merge_srv_conf) {
+            rv = module->merge_srv_conf(cf, saved.srv_conf[ctx_index],
+                                        cscfp[s]->ctx->srv_conf[ctx_index]);
+            if (rv != NGX_CONF_OK) {
+                goto failed;
+            }
+        }
+
+        if (module->merge_loc_conf) {
+
+            /* merge the server{}'s loc_conf */
+
+            ctx->loc_conf = cscfp[s]->ctx->loc_conf;
+
+            rv = module->merge_loc_conf(cf, saved.loc_conf[ctx_index],
+                                        cscfp[s]->ctx->loc_conf[ctx_index]);
+            if (rv != NGX_CONF_OK) {
+                goto failed;
+            }
+
+            /* merge the locations{}' loc_conf's */
+
+            clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];
+
+            rv = ngx_http_merge_locations(cf, clcf->locations,
+                                          cscfp[s]->ctx->loc_conf,
+                                          module, ctx_index);
+            if (rv != NGX_CONF_OK) {
+                goto failed;
+            }
+        }
+    }
+
+failed:
+
+    *ctx = saved;
+
+    return rv;
+}
+
+
+static char *
+ngx_http_merge_locations(ngx_conf_t *cf, ngx_queue_t *locations,
+    void **loc_conf, ngx_http_module_t *module, ngx_uint_t ctx_index)
+{
+    char                       *rv;
+    ngx_queue_t                *q;
+    ngx_http_conf_ctx_t        *ctx, saved;
+    ngx_http_core_loc_conf_t   *clcf;
+    ngx_http_location_queue_t  *lq;
+
+    if (locations == NULL) {
+        return NGX_CONF_OK;
+    }
+
+    ctx = (ngx_http_conf_ctx_t *) cf->ctx;
+    saved = *ctx;
+
+    for (q = ngx_queue_head(locations);
+         q != ngx_queue_sentinel(locations);
+         q = ngx_queue_next(q))
+    {
+        lq = (ngx_http_location_queue_t *) q;
+
+        clcf = lq->exact ? lq->exact : lq->inclusive;
+        ctx->loc_conf = clcf->loc_conf;
+
+        rv = module->merge_loc_conf(cf, loc_conf[ctx_index],
+                                    clcf->loc_conf[ctx_index]);
+        if (rv != NGX_CONF_OK) {
+            return rv;
+        }
+
+        rv = ngx_http_merge_locations(cf, clcf->locations, clcf->loc_conf,
+                                      module, ctx_index);
+        if (rv != NGX_CONF_OK) {
+            return rv;
+        }
+    }
+
+    *ctx = saved;
+
+    return NGX_CONF_OK;
+}
+
+
+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)
+{
+    ngx_uint_t                   n;
+    ngx_queue_t                 *q, *locations, *named, tail;
+    ngx_http_core_loc_conf_t    *clcf;
+    ngx_http_location_queue_t   *lq;
+    ngx_http_core_loc_conf_t   **clcfp;
+#if (NGX_PCRE)
+    ngx_uint_t                   r;
+    ngx_queue_t                 *regex;
+#endif
+
+    locations = pclcf->locations;
+
+    if (locations == NULL) {
+        return NGX_OK;
+    }
+
+    ngx_queue_sort(locations, ngx_http_cmp_locations);
+
+    named = NULL;
+    n = 0;
+#if (NGX_PCRE)
+    regex = NULL;
+    r = 0;
+#endif
+
+    for (q = ngx_queue_head(locations);
+         q != ngx_queue_sentinel(locations);
+         q = ngx_queue_next(q))
+    {
+        lq = (ngx_http_location_queue_t *) q;
+
+        clcf = lq->exact ? lq->exact : lq->inclusive;
+
+        if (ngx_http_init_locations(cf, NULL, clcf) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+#if (NGX_PCRE)
+
+        if (clcf->regex) {
+            r++;
+
+            if (regex == NULL) {
+                regex = q;
+            }
+
+            continue;
+        }
+
+#endif
+
+        if (clcf->named) {
+            n++;
+
+            if (named == NULL) {
+                named = q;
+            }
+
+            continue;
+        }
+
+        if (clcf->noname) {
+            break;
+        }
+    }
+
+    if (q != ngx_queue_sentinel(locations)) {
+        ngx_queue_split(locations, q, &tail);
+    }
+
+    if (named) {
+        clcfp = ngx_palloc(cf->pool,
+                           (n + 1) * sizeof(ngx_http_core_loc_conf_t *));
+        if (clcfp == NULL) {
+            return NGX_ERROR;
+        }
+
+        cscf->named_locations = clcfp;
+
+        for (q = named;
+             q != ngx_queue_sentinel(locations);
+             q = ngx_queue_next(q))
+        {
+            lq = (ngx_http_location_queue_t *) q;
+
+            *(clcfp++) = lq->exact;
+        }
+
+        *clcfp = NULL;
+
+        ngx_queue_split(locations, named, &tail);
+    }
+
+#if (NGX_PCRE)
+
+    if (regex) {
+
+        clcfp = ngx_palloc(cf->pool,
+                           (r + 1) * sizeof(ngx_http_core_loc_conf_t *));
+        if (clcfp == NULL) {
+            return NGX_ERROR;
+        }
+
+        pclcf->regex_locations = clcfp;
+
+        for (q = regex;
+             q != ngx_queue_sentinel(locations);
+             q = ngx_queue_next(q))
+        {
+            lq = (ngx_http_location_queue_t *) q;
+
+            *(clcfp++) = lq->exact;
+        }
+
+        *clcfp = NULL;
+
+        ngx_queue_split(locations, regex, &tail);
+    }
+
+#endif
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_init_static_location_trees(ngx_conf_t *cf,
+    ngx_http_core_loc_conf_t *pclcf)
+{
+    ngx_queue_t                *q, *locations;
+    ngx_http_core_loc_conf_t   *clcf;
+    ngx_http_location_queue_t  *lq;
+
+    locations = pclcf->locations;
+
+    if (locations == NULL) {
+        return NGX_OK;
+    }
+
+    if (ngx_queue_empty(locations)) {
+        return NGX_OK;
+    }
+
+    for (q = ngx_queue_head(locations);
+         q != ngx_queue_sentinel(locations);
+         q = ngx_queue_next(q))
+    {
+        lq = (ngx_http_location_queue_t *) q;
+
+        clcf = lq->exact ? lq->exact : lq->inclusive;
+
+        if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    if (ngx_http_join_exact_locations(cf, locations) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    ngx_http_create_locations_list(locations, ngx_queue_head(locations));
+
+    pclcf->static_locations = ngx_http_create_locations_tree(cf, locations, 0);
+    if (pclcf->static_locations == NULL) {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_add_location(ngx_conf_t *cf, ngx_queue_t **locations,
+    ngx_http_core_loc_conf_t *clcf)
+{
+    ngx_http_location_queue_t  *lq;
+
+    if (*locations == NULL) {
+        *locations = ngx_palloc(cf->temp_pool,
+                                sizeof(ngx_http_location_queue_t));
+        if (*locations == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_queue_init(*locations);
+    }
+
+    lq = ngx_palloc(cf->temp_pool, sizeof(ngx_http_location_queue_t));
+    if (lq == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (clcf->exact_match
+#if (NGX_PCRE)
+        || clcf->regex
+#endif
+        || clcf->named || clcf->noname)
+    {
+        lq->exact = clcf;
+        lq->inclusive = NULL;
+
+    } else {
+        lq->exact = NULL;
+        lq->inclusive = clcf;
+    }
+
+    lq->name = &clcf->name;
+    lq->file_name = cf->conf_file->file.name.data;
+    lq->line = cf->conf_file->line;
+
+    ngx_queue_init(&lq->list);
+
+    ngx_queue_insert_tail(*locations, &lq->queue);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_cmp_locations(const ngx_queue_t *one, const ngx_queue_t *two)
+{
+    ngx_int_t                   rc;
+    ngx_http_core_loc_conf_t   *first, *second;
+    ngx_http_location_queue_t  *lq1, *lq2;
+
+    lq1 = (ngx_http_location_queue_t *) one;
+    lq2 = (ngx_http_location_queue_t *) two;
+
+    first = lq1->exact ? lq1->exact : lq1->inclusive;
+    second = lq2->exact ? lq2->exact : lq2->inclusive;
+
+    if (first->noname && !second->noname) {
+        /* shift no named locations to the end */
+        return 1;
+    }
+
+    if (!first->noname && second->noname) {
+        /* shift no named locations to the end */
+        return -1;
+    }
+
+    if (first->noname || second->noname) {
+        /* do not sort no named locations */
+        return 0;
+    }
+
+    if (first->named && !second->named) {
+        /* shift named locations to the end */
+        return 1;
+    }
+
+    if (!first->named && second->named) {
+        /* shift named locations to the end */
+        return -1;
+    }
+
+    if (first->named && second->named) {
+        return ngx_strcmp(first->name.data, second->name.data);
+    }
+
+#if (NGX_PCRE)
+
+    if (first->regex && !second->regex) {
+        /* shift the regex matches to the end */
+        return 1;
+    }
+
+    if (!first->regex && second->regex) {
+        /* shift the regex matches to the end */
+        return -1;
+    }
+
+    if (first->regex || second->regex) {
+        /* do not sort the regex matches */
+        return 0;
+    }
+
+#endif
+
+    rc = ngx_filename_cmp(first->name.data, second->name.data,
+                          ngx_min(first->name.len, second->name.len) + 1);
+
+    if (rc == 0 && !first->exact_match && second->exact_match) {
+        /* an exact match must be before the same inclusive one */
+        return 1;
+    }
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_http_join_exact_locations(ngx_conf_t *cf, ngx_queue_t *locations)
+{
+    ngx_queue_t                *q, *x;
+    ngx_http_location_queue_t  *lq, *lx;
+
+    q = ngx_queue_head(locations);
+
+    while (q != ngx_queue_last(locations)) {
+
+        x = ngx_queue_next(q);
+
+        lq = (ngx_http_location_queue_t *) q;
+        lx = (ngx_http_location_queue_t *) x;
+
+        if (lq->name->len == lx->name->len
+            && ngx_filename_cmp(lq->name->data, lx->name->data, lx->name->len)
+               == 0)
+        {
+            if ((lq->exact && lx->exact) || (lq->inclusive && lx->inclusive)) {
+                ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                              "duplicate location \"%V\" in %s:%ui",
+                              lx->name, lx->file_name, lx->line);
+
+                return NGX_ERROR;
+            }
+
+            lq->inclusive = lx->inclusive;
+
+            ngx_queue_remove(x);
+
+            continue;
+        }
+
+        q = ngx_queue_next(q);
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_create_locations_list(ngx_queue_t *locations, ngx_queue_t *q)
+{
+    u_char                     *name;
+    size_t                      len;
+    ngx_queue_t                *x, tail;
+    ngx_http_location_queue_t  *lq, *lx;
+
+    if (q == ngx_queue_last(locations)) {
+        return;
+    }
+
+    lq = (ngx_http_location_queue_t *) q;
+
+    if (lq->inclusive == NULL) {
+        ngx_http_create_locations_list(locations, ngx_queue_next(q));
+        return;
+    }
+
+    len = lq->name->len;
+    name = lq->name->data;
+
+    for (x = ngx_queue_next(q);
+         x != ngx_queue_sentinel(locations);
+         x = ngx_queue_next(x))
+    {
+        lx = (ngx_http_location_queue_t *) x;
+
+        if (len > lx->name->len
+            || ngx_filename_cmp(name, lx->name->data, len) != 0)
+        {
+            break;
+        }
+    }
+
+    q = ngx_queue_next(q);
+
+    if (q == x) {
+        ngx_http_create_locations_list(locations, x);
+        return;
+    }
+
+    ngx_queue_split(locations, q, &tail);
+    ngx_queue_add(&lq->list, &tail);
+
+    if (x == ngx_queue_sentinel(locations)) {
+        ngx_http_create_locations_list(&lq->list, ngx_queue_head(&lq->list));
+        return;
+    }
+
+    ngx_queue_split(&lq->list, x, &tail);
+    ngx_queue_add(locations, &tail);
+
+    ngx_http_create_locations_list(&lq->list, ngx_queue_head(&lq->list));
+
+    ngx_http_create_locations_list(locations, x);
+}
+
+
+/*
+ * to keep cache locality for left leaf nodes, allocate nodes in following
+ * order: node, left subtree, right subtree, inclusive subtree
+ */
+
+static ngx_http_location_tree_node_t *
+ngx_http_create_locations_tree(ngx_conf_t *cf, ngx_queue_t *locations,
+    size_t prefix)
+{
+    size_t                          len;
+    ngx_queue_t                    *q, tail;
+    ngx_http_location_queue_t      *lq;
+    ngx_http_location_tree_node_t  *node;
+
+    q = ngx_queue_middle(locations);
+
+    lq = (ngx_http_location_queue_t *) q;
+    len = lq->name->len - prefix;
+
+    node = ngx_palloc(cf->pool,
+                      offsetof(ngx_http_location_tree_node_t, name) + len);
+    if (node == NULL) {
+        return NULL;
+    }
+
+    node->left = NULL;
+    node->right = NULL;
+    node->tree = NULL;
+    node->exact = lq->exact;
+    node->inclusive = lq->inclusive;
+
+    node->auto_redirect = (u_char) ((lq->exact && lq->exact->auto_redirect)
+                           || (lq->inclusive && lq->inclusive->auto_redirect));
+
+    node->len = (u_char) len;
+    ngx_memcpy(node->name, &lq->name->data[prefix], len);
+
+    ngx_queue_split(locations, q, &tail);
+
+    if (ngx_queue_empty(locations)) {
+        /*
+         * ngx_queue_split() insures that if left part is empty,
+         * then right one is empty too
+         */
+        goto inclusive;
+    }
+
+    node->left = ngx_http_create_locations_tree(cf, locations, prefix);
+    if (node->left == NULL) {
+        return NULL;
+    }
+
+    ngx_queue_remove(q);
+
+    if (ngx_queue_empty(&tail)) {
+        goto inclusive;
+    }
+
+    node->right = ngx_http_create_locations_tree(cf, &tail, prefix);
+    if (node->right == NULL) {
+        return NULL;
+    }
+
+inclusive:
+
+    if (ngx_queue_empty(&lq->list)) {
+        return node;
+    }
+
+    node->tree = ngx_http_create_locations_tree(cf, &lq->list, prefix + len);
+    if (node->tree == NULL) {
+        return NULL;
+    }
+
+    return node;
+}
+
+
+ngx_int_t
+ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
+    ngx_http_listen_opt_t *lsopt)
+{
+    in_port_t                   p;
+    ngx_uint_t                  i;
+    struct sockaddr            *sa;
+    ngx_http_conf_port_t       *port;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    if (cmcf->ports == NULL) {
+        cmcf->ports = ngx_array_create(cf->temp_pool, 2,
+                                       sizeof(ngx_http_conf_port_t));
+        if (cmcf->ports == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    sa = &lsopt->sockaddr.sockaddr;
+    p = ngx_inet_get_port(sa);
+
+    port = cmcf->ports->elts;
+    for (i = 0; i < cmcf->ports->nelts; i++) {
+
+        if (p != port[i].port || sa->sa_family != port[i].family) {
+            continue;
+        }
+
+        /* a port is already in the port list */
+
+        return ngx_http_add_addresses(cf, cscf, &port[i], lsopt);
+    }
+
+    /* add a port to the port list */
+
+    port = ngx_array_push(cmcf->ports);
+    if (port == NULL) {
+        return NGX_ERROR;
+    }
+
+    port->family = sa->sa_family;
+    port->port = p;
+    port->addrs.elts = NULL;
+
+    return ngx_http_add_address(cf, cscf, port, lsopt);
+}
+
+
+static ngx_int_t
+ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
+    ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)
+{
+    ngx_uint_t             i, default_server, proxy_protocol;
+    ngx_http_conf_addr_t  *addr;
+#if (NGX_HTTP_SSL)
+    ngx_uint_t             ssl;
+#endif
+#if (NGX_HTTP_V2)
+    ngx_uint_t             http2;
+#endif
+
+    /*
+     * we cannot compare whole sockaddr struct's as kernel
+     * may fill some fields in inherited sockaddr struct's
+     */
+
+    addr = port->addrs.elts;
+
+    for (i = 0; i < port->addrs.nelts; i++) {
+
+        if (ngx_cmp_sockaddr(&lsopt->sockaddr.sockaddr, lsopt->socklen,
+                             &addr[i].opt.sockaddr.sockaddr,
+                             addr[i].opt.socklen, 0)
+            != NGX_OK)
+        {
+            continue;
+        }
+
+        /* the address is already in the address list */
+
+        if (ngx_http_add_server(cf, cscf, &addr[i]) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        /* preserve default_server bit during listen options overwriting */
+        default_server = addr[i].opt.default_server;
+
+        proxy_protocol = lsopt->proxy_protocol || addr[i].opt.proxy_protocol;
+
+#if (NGX_HTTP_SSL)
+        ssl = lsopt->ssl || addr[i].opt.ssl;
+#endif
+#if (NGX_HTTP_V2)
+        http2 = lsopt->http2 || addr[i].opt.http2;
+#endif
+
+        if (lsopt->set) {
+
+            if (addr[i].opt.set) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                        "duplicate listen options for %s", addr[i].opt.addr);
+                return NGX_ERROR;
+            }
+
+            addr[i].opt = *lsopt;
+        }
+
+        /* check the duplicate "default" server for this address:port */
+
+        if (lsopt->default_server) {
+
+            if (default_server) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                        "a duplicate default server for %s", addr[i].opt.addr);
+                return NGX_ERROR;
+            }
+
+            default_server = 1;
+            addr[i].default_server = cscf;
+        }
+
+        addr[i].opt.default_server = default_server;
+        addr[i].opt.proxy_protocol = proxy_protocol;
+#if (NGX_HTTP_SSL)
+        addr[i].opt.ssl = ssl;
+#endif
+#if (NGX_HTTP_V2)
+        addr[i].opt.http2 = http2;
+#endif
+
+        return NGX_OK;
+    }
+
+    /* add the address to the addresses list that bound to this port */
+
+    return ngx_http_add_address(cf, cscf, port, lsopt);
+}
+
+
+/*
+ * add the server address, the server names and the server core module
+ * configurations to the port list
+ */
+
+static ngx_int_t
+ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
+    ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)
+{
+    ngx_http_conf_addr_t  *addr;
+
+    if (port->addrs.elts == NULL) {
+        if (ngx_array_init(&port->addrs, cf->temp_pool, 4,
+                           sizeof(ngx_http_conf_addr_t))
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+#if (NGX_HTTP_V2 && NGX_HTTP_SSL                                              \
+     && !defined TLSEXT_TYPE_application_layer_protocol_negotiation           \
+     && !defined TLSEXT_TYPE_next_proto_neg)
+
+    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 %s",
+                           lsopt->addr);
+    }
+
+#endif
+
+    addr = ngx_array_push(&port->addrs);
+    if (addr == NULL) {
+        return NGX_ERROR;
+    }
+
+    addr->opt = *lsopt;
+    addr->hash.buckets = NULL;
+    addr->hash.size = 0;
+    addr->wc_head = NULL;
+    addr->wc_tail = NULL;
+#if (NGX_PCRE)
+    addr->nregex = 0;
+    addr->regex = NULL;
+#endif
+    addr->default_server = cscf;
+    addr->servers.elts = NULL;
+
+    return ngx_http_add_server(cf, cscf, addr);
+}
+
+
+/* add the server core module configuration to the address:port */
+
+static ngx_int_t
+ngx_http_add_server(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
+    ngx_http_conf_addr_t *addr)
+{
+    ngx_uint_t                  i;
+    ngx_http_core_srv_conf_t  **server;
+
+    if (addr->servers.elts == NULL) {
+        if (ngx_array_init(&addr->servers, cf->temp_pool, 4,
+                           sizeof(ngx_http_core_srv_conf_t *))
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+
+    } else {
+        server = addr->servers.elts;
+        for (i = 0; i < addr->servers.nelts; i++) {
+            if (server[i] == cscf) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "a duplicate listen %s", addr->opt.addr);
+                return NGX_ERROR;
+            }
+        }
+    }
+
+    server = ngx_array_push(&addr->servers);
+    if (server == NULL) {
+        return NGX_ERROR;
+    }
+
+    *server = cscf;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_optimize_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
+    ngx_array_t *ports)
+{
+    ngx_uint_t             p, a;
+    ngx_http_conf_port_t  *port;
+    ngx_http_conf_addr_t  *addr;
+
+    if (ports == NULL) {
+        return NGX_OK;
+    }
+
+    port = ports->elts;
+    for (p = 0; p < ports->nelts; p++) {
+
+        ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,
+                 sizeof(ngx_http_conf_addr_t), ngx_http_cmp_conf_addrs);
+
+        /*
+         * check whether all name-based servers have the same
+         * configuration as a default server for given address:port
+         */
+
+        addr = port[p].addrs.elts;
+        for (a = 0; a < port[p].addrs.nelts; a++) {
+
+            if (addr[a].servers.nelts > 1
+#if (NGX_PCRE)
+                || addr[a].default_server->captures
+#endif
+               )
+            {
+                if (ngx_http_server_names(cf, cmcf, &addr[a]) != NGX_OK) {
+                    return NGX_ERROR;
+                }
+            }
+        }
+
+        if (ngx_http_init_listening(cf, &port[p]) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_server_names(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
+    ngx_http_conf_addr_t *addr)
+{
+    ngx_int_t                   rc;
+    ngx_uint_t                  n, s;
+    ngx_hash_init_t             hash;
+    ngx_hash_keys_arrays_t      ha;
+    ngx_http_server_name_t     *name;
+    ngx_http_core_srv_conf_t  **cscfp;
+#if (NGX_PCRE)
+    ngx_uint_t                  regex, i;
+
+    regex = 0;
+#endif
+
+    ngx_memzero(&ha, sizeof(ngx_hash_keys_arrays_t));
+
+    ha.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
+    if (ha.temp_pool == NULL) {
+        return NGX_ERROR;
+    }
+
+    ha.pool = cf->pool;
+
+    if (ngx_hash_keys_array_init(&ha, NGX_HASH_LARGE) != NGX_OK) {
+        goto failed;
+    }
+
+    cscfp = addr->servers.elts;
+
+    for (s = 0; s < addr->servers.nelts; s++) {
+
+        name = cscfp[s]->server_names.elts;
+
+        for (n = 0; n < cscfp[s]->server_names.nelts; n++) {
+
+#if (NGX_PCRE)
+            if (name[n].regex) {
+                regex++;
+                continue;
+            }
+#endif
+
+            rc = ngx_hash_add_key(&ha, &name[n].name, name[n].server,
+                                  NGX_HASH_WILDCARD_KEY);
+
+            if (rc == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            if (rc == NGX_DECLINED) {
+                ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                              "invalid server name or wildcard \"%V\" on %s",
+                              &name[n].name, addr->opt.addr);
+                return NGX_ERROR;
+            }
+
+            if (rc == NGX_BUSY) {
+                ngx_log_error(NGX_LOG_WARN, cf->log, 0,
+                              "conflicting server name \"%V\" on %s, ignored",
+                              &name[n].name, addr->opt.addr);
+            }
+        }
+    }
+
+    hash.key = ngx_hash_key_lc;
+    hash.max_size = cmcf->server_names_hash_max_size;
+    hash.bucket_size = cmcf->server_names_hash_bucket_size;
+    hash.name = "server_names_hash";
+    hash.pool = cf->pool;
+
+    if (ha.keys.nelts) {
+        hash.hash = &addr->hash;
+        hash.temp_pool = NULL;
+
+        if (ngx_hash_init(&hash, ha.keys.elts, ha.keys.nelts) != NGX_OK) {
+            goto failed;
+        }
+    }
+
+    if (ha.dns_wc_head.nelts) {
+
+        ngx_qsort(ha.dns_wc_head.elts, (size_t) ha.dns_wc_head.nelts,
+                  sizeof(ngx_hash_key_t), ngx_http_cmp_dns_wildcards);
+
+        hash.hash = NULL;
+        hash.temp_pool = ha.temp_pool;
+
+        if (ngx_hash_wildcard_init(&hash, ha.dns_wc_head.elts,
+                                   ha.dns_wc_head.nelts)
+            != NGX_OK)
+        {
+            goto failed;
+        }
+
+        addr->wc_head = (ngx_hash_wildcard_t *) hash.hash;
+    }
+
+    if (ha.dns_wc_tail.nelts) {
+
+        ngx_qsort(ha.dns_wc_tail.elts, (size_t) ha.dns_wc_tail.nelts,
+                  sizeof(ngx_hash_key_t), ngx_http_cmp_dns_wildcards);
+
+        hash.hash = NULL;
+        hash.temp_pool = ha.temp_pool;
+
+        if (ngx_hash_wildcard_init(&hash, ha.dns_wc_tail.elts,
+                                   ha.dns_wc_tail.nelts)
+            != NGX_OK)
+        {
+            goto failed;
+        }
+
+        addr->wc_tail = (ngx_hash_wildcard_t *) hash.hash;
+    }
+
+    ngx_destroy_pool(ha.temp_pool);
+
+#if (NGX_PCRE)
+
+    if (regex == 0) {
+        return NGX_OK;
+    }
+
+    addr->nregex = regex;
+    addr->regex = ngx_palloc(cf->pool, regex * sizeof(ngx_http_server_name_t));
+    if (addr->regex == NULL) {
+        return NGX_ERROR;
+    }
+
+    i = 0;
+
+    for (s = 0; s < addr->servers.nelts; s++) {
+
+        name = cscfp[s]->server_names.elts;
+
+        for (n = 0; n < cscfp[s]->server_names.nelts; n++) {
+            if (name[n].regex) {
+                addr->regex[i++] = name[n];
+            }
+        }
+    }
+
+#endif
+
+    return NGX_OK;
+
+failed:
+
+    ngx_destroy_pool(ha.temp_pool);
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_cmp_conf_addrs(const void *one, const void *two)
+{
+    ngx_http_conf_addr_t  *first, *second;
+
+    first = (ngx_http_conf_addr_t *) one;
+    second = (ngx_http_conf_addr_t *) two;
+
+    if (first->opt.wildcard) {
+        /* a wildcard address must be the last resort, shift it to the end */
+        return 1;
+    }
+
+    if (second->opt.wildcard) {
+        /* a wildcard address must be the last resort, shift it to the end */
+        return -1;
+    }
+
+    if (first->opt.bind && !second->opt.bind) {
+        /* shift explicit bind()ed addresses to the start */
+        return -1;
+    }
+
+    if (!first->opt.bind && second->opt.bind) {
+        /* shift explicit bind()ed addresses to the start */
+        return 1;
+    }
+
+    /* do not sort by default */
+
+    return 0;
+}
+
+
+static int ngx_libc_cdecl
+ngx_http_cmp_dns_wildcards(const void *one, const void *two)
+{
+    ngx_hash_key_t  *first, *second;
+
+    first = (ngx_hash_key_t *) one;
+    second = (ngx_hash_key_t *) two;
+
+    return ngx_dns_strcmp(first->key.data, second->key.data);
+}
+
+
+static ngx_int_t
+ngx_http_init_listening(ngx_conf_t *cf, ngx_http_conf_port_t *port)
+{
+    ngx_uint_t                 i, last, bind_wildcard;
+    ngx_listening_t           *ls;
+    ngx_http_port_t           *hport;
+    ngx_http_conf_addr_t      *addr;
+
+    addr = port->addrs.elts;
+    last = port->addrs.nelts;
+
+    /*
+     * If there is a binding to an "*:port" then we need to bind() to
+     * the "*:port" only and ignore other implicit bindings.  The bindings
+     * have been already sorted: explicit bindings are on the start, then
+     * implicit bindings go, and wildcard binding is in the end.
+     */
+
+    if (addr[last - 1].opt.wildcard) {
+        addr[last - 1].opt.bind = 1;
+        bind_wildcard = 1;
+
+    } else {
+        bind_wildcard = 0;
+    }
+
+    i = 0;
+
+    while (i < last) {
+
+        if (bind_wildcard && !addr[i].opt.bind) {
+            i++;
+            continue;
+        }
+
+        ls = ngx_http_add_listening(cf, &addr[i]);
+        if (ls == NULL) {
+            return NGX_ERROR;
+        }
+
+        hport = ngx_pcalloc(cf->pool, sizeof(ngx_http_port_t));
+        if (hport == NULL) {
+            return NGX_ERROR;
+        }
+
+        ls->servers = hport;
+
+        hport->naddrs = i + 1;
+
+        switch (ls->sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+        case AF_INET6:
+            if (ngx_http_add_addrs6(cf, hport, addr) != NGX_OK) {
+                return NGX_ERROR;
+            }
+            break;
+#endif
+        default: /* AF_INET */
+            if (ngx_http_add_addrs(cf, hport, addr) != NGX_OK) {
+                return NGX_ERROR;
+            }
+            break;
+        }
+
+        if (ngx_clone_listening(cf, ls) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        addr++;
+        last--;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_listening_t *
+ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
+{
+    ngx_listening_t           *ls;
+    ngx_http_core_loc_conf_t  *clcf;
+    ngx_http_core_srv_conf_t  *cscf;
+
+    ls = ngx_create_listening(cf, &addr->opt.sockaddr.sockaddr,
+                              addr->opt.socklen);
+    if (ls == NULL) {
+        return NULL;
+    }
+
+    ls->addr_ntop = 1;
+
+    ls->handler = ngx_http_init_connection;
+
+    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];
+
+    ls->logp = clcf->error_log;
+    ls->log.data = &ls->addr_text;
+    ls->log.handler = ngx_accept_log_error;
+
+#if (NGX_WIN32)
+    {
+    ngx_iocp_conf_t  *iocpcf = NULL;
+
+    if (ngx_get_conf(cf->cycle->conf_ctx, ngx_events_module)) {
+        iocpcf = ngx_event_get_conf(cf->cycle->conf_ctx, ngx_iocp_module);
+    }
+    if (iocpcf && iocpcf->acceptex_read) {
+        ls->post_accept_buffer_size = cscf->client_header_buffer_size;
+    }
+    }
+#endif
+
+    ls->backlog = addr->opt.backlog;
+    ls->rcvbuf = addr->opt.rcvbuf;
+    ls->sndbuf = addr->opt.sndbuf;
+
+    ls->keepalive = addr->opt.so_keepalive;
+#if (NGX_HAVE_KEEPALIVE_TUNABLE)
+    ls->keepidle = addr->opt.tcp_keepidle;
+    ls->keepintvl = addr->opt.tcp_keepintvl;
+    ls->keepcnt = addr->opt.tcp_keepcnt;
+#endif
+
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
+    ls->accept_filter = addr->opt.accept_filter;
+#endif
+
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
+    ls->deferred_accept = addr->opt.deferred_accept;
+#endif
+
+#if (NGX_HAVE_INET6)
+    ls->ipv6only = addr->opt.ipv6only;
+#endif
+
+#if (NGX_HAVE_SETFIB)
+    ls->setfib = addr->opt.setfib;
+#endif
+
+#if (NGX_HAVE_TCP_FASTOPEN)
+    ls->fastopen = addr->opt.fastopen;
+#endif
+
+#if (NGX_HAVE_REUSEPORT)
+    ls->reuseport = addr->opt.reuseport;
+#endif
+
+    return ls;
+}
+
+
+static ngx_int_t
+ngx_http_add_addrs(ngx_conf_t *cf, ngx_http_port_t *hport,
+    ngx_http_conf_addr_t *addr)
+{
+    ngx_uint_t                 i;
+    ngx_http_in_addr_t        *addrs;
+    struct sockaddr_in        *sin;
+    ngx_http_virtual_names_t  *vn;
+
+    hport->addrs = ngx_pcalloc(cf->pool,
+                               hport->naddrs * sizeof(ngx_http_in_addr_t));
+    if (hport->addrs == NULL) {
+        return NGX_ERROR;
+    }
+
+    addrs = hport->addrs;
+
+    for (i = 0; i < hport->naddrs; i++) {
+
+        sin = &addr[i].opt.sockaddr.sockaddr_in;
+        addrs[i].addr = sin->sin_addr.s_addr;
+        addrs[i].conf.default_server = addr[i].default_server;
+#if (NGX_HTTP_SSL)
+        addrs[i].conf.ssl = addr[i].opt.ssl;
+#endif
+#if (NGX_HTTP_V2)
+        addrs[i].conf.http2 = addr[i].opt.http2;
+#endif
+        addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
+
+        if (addr[i].hash.buckets == NULL
+            && (addr[i].wc_head == NULL
+                || addr[i].wc_head->hash.buckets == NULL)
+            && (addr[i].wc_tail == NULL
+                || addr[i].wc_tail->hash.buckets == NULL)
+#if (NGX_PCRE)
+            && addr[i].nregex == 0
+#endif
+            )
+        {
+            continue;
+        }
+
+        vn = ngx_palloc(cf->pool, sizeof(ngx_http_virtual_names_t));
+        if (vn == NULL) {
+            return NGX_ERROR;
+        }
+
+        addrs[i].conf.virtual_names = vn;
+
+        vn->names.hash = addr[i].hash;
+        vn->names.wc_head = addr[i].wc_head;
+        vn->names.wc_tail = addr[i].wc_tail;
+#if (NGX_PCRE)
+        vn->nregex = addr[i].nregex;
+        vn->regex = addr[i].regex;
+#endif
+    }
+
+    return NGX_OK;
+}
+
+
+#if (NGX_HAVE_INET6)
+
+static ngx_int_t
+ngx_http_add_addrs6(ngx_conf_t *cf, ngx_http_port_t *hport,
+    ngx_http_conf_addr_t *addr)
+{
+    ngx_uint_t                 i;
+    ngx_http_in6_addr_t       *addrs6;
+    struct sockaddr_in6       *sin6;
+    ngx_http_virtual_names_t  *vn;
+
+    hport->addrs = ngx_pcalloc(cf->pool,
+                               hport->naddrs * sizeof(ngx_http_in6_addr_t));
+    if (hport->addrs == NULL) {
+        return NGX_ERROR;
+    }
+
+    addrs6 = hport->addrs;
+
+    for (i = 0; i < hport->naddrs; i++) {
+
+        sin6 = &addr[i].opt.sockaddr.sockaddr_in6;
+        addrs6[i].addr6 = sin6->sin6_addr;
+        addrs6[i].conf.default_server = addr[i].default_server;
+#if (NGX_HTTP_SSL)
+        addrs6[i].conf.ssl = addr[i].opt.ssl;
+#endif
+#if (NGX_HTTP_V2)
+        addrs6[i].conf.http2 = addr[i].opt.http2;
+#endif
+        addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
+
+        if (addr[i].hash.buckets == NULL
+            && (addr[i].wc_head == NULL
+                || addr[i].wc_head->hash.buckets == NULL)
+            && (addr[i].wc_tail == NULL
+                || addr[i].wc_tail->hash.buckets == NULL)
+#if (NGX_PCRE)
+            && addr[i].nregex == 0
+#endif
+            )
+        {
+            continue;
+        }
+
+        vn = ngx_palloc(cf->pool, sizeof(ngx_http_virtual_names_t));
+        if (vn == NULL) {
+            return NGX_ERROR;
+        }
+
+        addrs6[i].conf.virtual_names = vn;
+
+        vn->names.hash = addr[i].hash;
+        vn->names.wc_head = addr[i].wc_head;
+        vn->names.wc_tail = addr[i].wc_tail;
+#if (NGX_PCRE)
+        vn->nregex = addr[i].nregex;
+        vn->regex = addr[i].regex;
+#endif
+    }
+
+    return NGX_OK;
+}
+
+#endif
+
+
+char *
+ngx_http_types_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ngx_array_t     **types;
+    ngx_str_t        *value, *default_type;
+    ngx_uint_t        i, n, hash;
+    ngx_hash_key_t   *type;
+
+    types = (ngx_array_t **) (p + cmd->offset);
+
+    if (*types == (void *) -1) {
+        return NGX_CONF_OK;
+    }
+
+    default_type = cmd->post;
+
+    if (*types == NULL) {
+        *types = ngx_array_create(cf->temp_pool, 1, sizeof(ngx_hash_key_t));
+        if (*types == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        if (default_type) {
+            type = ngx_array_push(*types);
+            if (type == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            type->key = *default_type;
+            type->key_hash = ngx_hash_key(default_type->data,
+                                          default_type->len);
+            type->value = (void *) 4;
+        }
+    }
+
+    value = cf->args->elts;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+
+        if (value[i].len == 1 && value[i].data[0] == '*') {
+            *types = (void *) -1;
+            return NGX_CONF_OK;
+        }
+
+        hash = ngx_hash_strlow(value[i].data, value[i].data, value[i].len);
+        value[i].data[value[i].len] = '\0';
+
+        type = (*types)->elts;
+        for (n = 0; n < (*types)->nelts; n++) {
+
+            if (ngx_strcmp(value[i].data, type[n].key.data) == 0) {
+                ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                                   "duplicate MIME type \"%V\"", &value[i]);
+                goto next;
+            }
+        }
+
+        type = ngx_array_push(*types);
+        if (type == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        type->key = value[i];
+        type->key_hash = hash;
+        type->value = (void *) 4;
+
+    next:
+
+        continue;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *
+ngx_http_merge_types(ngx_conf_t *cf, ngx_array_t **keys, ngx_hash_t *types_hash,
+    ngx_array_t **prev_keys, ngx_hash_t *prev_types_hash,
+    ngx_str_t *default_types)
+{
+    ngx_hash_init_t  hash;
+
+    if (*keys) {
+
+        if (*keys == (void *) -1) {
+            return NGX_CONF_OK;
+        }
+
+        hash.hash = types_hash;
+        hash.key = NULL;
+        hash.max_size = 2048;
+        hash.bucket_size = 64;
+        hash.name = "test_types_hash";
+        hash.pool = cf->pool;
+        hash.temp_pool = NULL;
+
+        if (ngx_hash_init(&hash, (*keys)->elts, (*keys)->nelts) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+        return NGX_CONF_OK;
+    }
+
+    if (prev_types_hash->buckets == NULL) {
+
+        if (*prev_keys == NULL) {
+
+            if (ngx_http_set_default_types(cf, prev_keys, default_types)
+                != NGX_OK)
+            {
+                return NGX_CONF_ERROR;
+            }
+
+        } else if (*prev_keys == (void *) -1) {
+            *keys = *prev_keys;
+            return NGX_CONF_OK;
+        }
+
+        hash.hash = prev_types_hash;
+        hash.key = NULL;
+        hash.max_size = 2048;
+        hash.bucket_size = 64;
+        hash.name = "test_types_hash";
+        hash.pool = cf->pool;
+        hash.temp_pool = NULL;
+
+        if (ngx_hash_init(&hash, (*prev_keys)->elts, (*prev_keys)->nelts)
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    *types_hash = *prev_types_hash;
+
+    return NGX_CONF_OK;
+
+}
+
+
+ngx_int_t
+ngx_http_set_default_types(ngx_conf_t *cf, ngx_array_t **types,
+    ngx_str_t *default_type)
+{
+    ngx_hash_key_t  *type;
+
+    *types = ngx_array_create(cf->temp_pool, 1, sizeof(ngx_hash_key_t));
+    if (*types == NULL) {
+        return NGX_ERROR;
+    }
+
+    while (default_type->len) {
+
+        type = ngx_array_push(*types);
+        if (type == NULL) {
+            return NGX_ERROR;
+        }
+
+        type->key = *default_type;
+        type->key_hash = ngx_hash_key(default_type->data,
+                                      default_type->len);
+        type->value = (void *) 4;
+
+        default_type++;
+    }
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/ngx_http.h b/nginx/src/http/ngx_http.h
new file mode 100644 (file)
index 0000000..afab4f6
--- /dev/null
@@ -0,0 +1,176 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_H_INCLUDED_
+#define _NGX_HTTP_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef struct ngx_http_request_s     ngx_http_request_t;
+typedef struct ngx_http_upstream_s    ngx_http_upstream_t;
+typedef struct ngx_http_cache_s       ngx_http_cache_t;
+typedef struct ngx_http_file_cache_s  ngx_http_file_cache_t;
+typedef struct ngx_http_log_ctx_s     ngx_http_log_ctx_t;
+typedef struct ngx_http_chunked_s     ngx_http_chunked_t;
+typedef struct ngx_http_v2_stream_s   ngx_http_v2_stream_t;
+
+typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+typedef u_char *(*ngx_http_log_handler_pt)(ngx_http_request_t *r,
+    ngx_http_request_t *sr, u_char *buf, size_t len);
+
+
+#include <ngx_http_variables.h>
+#include <ngx_http_config.h>
+#include <ngx_http_request.h>
+#include <ngx_http_script.h>
+#include <ngx_http_upstream.h>
+#include <ngx_http_upstream_round_robin.h>
+#include <ngx_http_core_module.h>
+
+#if (NGX_HTTP_V2)
+#include <ngx_http_v2.h>
+#endif
+#if (NGX_HTTP_CACHE)
+#include <ngx_http_cache.h>
+#endif
+#if (NGX_HTTP_SSI)
+#include <ngx_http_ssi_filter_module.h>
+#endif
+#if (NGX_HTTP_SSL)
+#include <ngx_http_ssl_module.h>
+#endif
+
+
+struct ngx_http_log_ctx_s {
+    ngx_connection_t    *connection;
+    ngx_http_request_t  *request;
+    ngx_http_request_t  *current_request;
+};
+
+
+struct ngx_http_chunked_s {
+    ngx_uint_t           state;
+    off_t                size;
+    off_t                length;
+};
+
+
+typedef struct {
+    ngx_uint_t           http_version;
+    ngx_uint_t           code;
+    ngx_uint_t           count;
+    u_char              *start;
+    u_char              *end;
+} ngx_http_status_t;
+
+
+#define ngx_http_get_module_ctx(r, module)  (r)->ctx[module.ctx_index]
+#define ngx_http_set_ctx(r, c, module)      r->ctx[module.ctx_index] = c;
+
+
+ngx_int_t ngx_http_add_location(ngx_conf_t *cf, ngx_queue_t **locations,
+    ngx_http_core_loc_conf_t *clcf);
+ngx_int_t ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
+    ngx_http_listen_opt_t *lsopt);
+
+
+void ngx_http_init_connection(ngx_connection_t *c);
+void ngx_http_close_connection(ngx_connection_t *c);
+
+#if (NGX_HTTP_SSL && defined SSL_CTRL_SET_TLSEXT_HOSTNAME)
+int ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg);
+#endif
+
+ngx_int_t ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b);
+ngx_int_t ngx_http_parse_uri(ngx_http_request_t *r);
+ngx_int_t ngx_http_parse_complex_uri(ngx_http_request_t *r,
+    ngx_uint_t merge_slashes);
+ngx_int_t ngx_http_parse_status_line(ngx_http_request_t *r, ngx_buf_t *b,
+    ngx_http_status_t *status);
+ngx_int_t ngx_http_parse_unsafe_uri(ngx_http_request_t *r, ngx_str_t *uri,
+    ngx_str_t *args, ngx_uint_t *flags);
+ngx_int_t ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b,
+    ngx_uint_t allow_underscores);
+ngx_int_t ngx_http_parse_multi_header_lines(ngx_array_t *headers,
+    ngx_str_t *name, ngx_str_t *value);
+ngx_int_t ngx_http_parse_set_cookie_lines(ngx_array_t *headers,
+    ngx_str_t *name, ngx_str_t *value);
+ngx_int_t ngx_http_arg(ngx_http_request_t *r, u_char *name, size_t len,
+    ngx_str_t *value);
+void ngx_http_split_args(ngx_http_request_t *r, ngx_str_t *uri,
+    ngx_str_t *args);
+ngx_int_t ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b,
+    ngx_http_chunked_t *ctx);
+
+
+ngx_http_request_t *ngx_http_create_request(ngx_connection_t *c);
+ngx_int_t ngx_http_process_request_uri(ngx_http_request_t *r);
+ngx_int_t ngx_http_process_request_header(ngx_http_request_t *r);
+void ngx_http_process_request(ngx_http_request_t *r);
+void ngx_http_update_location_config(ngx_http_request_t *r);
+void ngx_http_handler(ngx_http_request_t *r);
+void ngx_http_run_posted_requests(ngx_connection_t *c);
+ngx_int_t ngx_http_post_request(ngx_http_request_t *r,
+    ngx_http_posted_request_t *pr);
+void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc);
+void ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc);
+
+void ngx_http_empty_handler(ngx_event_t *wev);
+void ngx_http_request_empty_handler(ngx_http_request_t *r);
+
+
+#define NGX_HTTP_LAST   1
+#define NGX_HTTP_FLUSH  2
+
+ngx_int_t ngx_http_send_special(ngx_http_request_t *r, ngx_uint_t flags);
+
+
+ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r,
+    ngx_http_client_body_handler_pt post_handler);
+ngx_int_t ngx_http_read_unbuffered_request_body(ngx_http_request_t *r);
+
+ngx_int_t ngx_http_send_header(ngx_http_request_t *r);
+ngx_int_t ngx_http_special_response_handler(ngx_http_request_t *r,
+    ngx_int_t error);
+ngx_int_t ngx_http_filter_finalize_request(ngx_http_request_t *r,
+    ngx_module_t *m, ngx_int_t error);
+void ngx_http_clean_header(ngx_http_request_t *r);
+
+
+ngx_int_t ngx_http_discard_request_body(ngx_http_request_t *r);
+void ngx_http_discarded_request_body_handler(ngx_http_request_t *r);
+void ngx_http_block_reading(ngx_http_request_t *r);
+void ngx_http_test_reading(ngx_http_request_t *r);
+
+
+char *ngx_http_types_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+char *ngx_http_merge_types(ngx_conf_t *cf, ngx_array_t **keys,
+    ngx_hash_t *types_hash, ngx_array_t **prev_keys,
+    ngx_hash_t *prev_types_hash, ngx_str_t *default_types);
+ngx_int_t ngx_http_set_default_types(ngx_conf_t *cf, ngx_array_t **types,
+    ngx_str_t *default_type);
+
+#if (NGX_HTTP_DEGRADATION)
+ngx_uint_t  ngx_http_degraded(ngx_http_request_t *);
+#endif
+
+
+extern ngx_module_t  ngx_http_module;
+
+extern ngx_str_t  ngx_http_html_default_types[];
+
+
+extern ngx_http_output_header_filter_pt  ngx_http_top_header_filter;
+extern ngx_http_output_body_filter_pt    ngx_http_top_body_filter;
+extern ngx_http_request_body_filter_pt   ngx_http_top_request_body_filter;
+
+
+#endif /* _NGX_HTTP_H_INCLUDED_ */
diff --git a/nginx/src/http/ngx_http_cache.h b/nginx/src/http/ngx_http_cache.h
new file mode 100644 (file)
index 0000000..f9e9664
--- /dev/null
@@ -0,0 +1,207 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_CACHE_H_INCLUDED_
+#define _NGX_HTTP_CACHE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_CACHE_MISS          1
+#define NGX_HTTP_CACHE_BYPASS        2
+#define NGX_HTTP_CACHE_EXPIRED       3
+#define NGX_HTTP_CACHE_STALE         4
+#define NGX_HTTP_CACHE_UPDATING      5
+#define NGX_HTTP_CACHE_REVALIDATED   6
+#define NGX_HTTP_CACHE_HIT           7
+#define NGX_HTTP_CACHE_SCARCE        8
+
+#define NGX_HTTP_CACHE_KEY_LEN       16
+#define NGX_HTTP_CACHE_ETAG_LEN      128
+#define NGX_HTTP_CACHE_VARY_LEN      128
+
+#define NGX_HTTP_CACHE_VERSION       5
+
+
+typedef struct {
+    ngx_uint_t                       status;
+    time_t                           valid;
+} ngx_http_cache_valid_t;
+
+
+typedef struct {
+    ngx_rbtree_node_t                node;
+    ngx_queue_t                      queue;
+
+    u_char                           key[NGX_HTTP_CACHE_KEY_LEN
+                                         - sizeof(ngx_rbtree_key_t)];
+
+    unsigned                         count:20;
+    unsigned                         uses:10;
+    unsigned                         valid_msec:10;
+    unsigned                         error:10;
+    unsigned                         exists:1;
+    unsigned                         updating:1;
+    unsigned                         deleting:1;
+    unsigned                         purged:1;
+                                     /* 10 unused bits */
+
+    ngx_file_uniq_t                  uniq;
+    time_t                           expire;
+    time_t                           valid_sec;
+    size_t                           body_start;
+    off_t                            fs_size;
+    ngx_msec_t                       lock_time;
+} ngx_http_file_cache_node_t;
+
+
+struct ngx_http_cache_s {
+    ngx_file_t                       file;
+    ngx_array_t                      keys;
+    uint32_t                         crc32;
+    u_char                           key[NGX_HTTP_CACHE_KEY_LEN];
+    u_char                           main[NGX_HTTP_CACHE_KEY_LEN];
+
+    ngx_file_uniq_t                  uniq;
+    time_t                           valid_sec;
+    time_t                           updating_sec;
+    time_t                           error_sec;
+    time_t                           last_modified;
+    time_t                           date;
+
+    ngx_str_t                        etag;
+    ngx_str_t                        vary;
+    u_char                           variant[NGX_HTTP_CACHE_KEY_LEN];
+
+    size_t                           header_start;
+    size_t                           body_start;
+    off_t                            length;
+    off_t                            fs_size;
+
+    ngx_uint_t                       min_uses;
+    ngx_uint_t                       error;
+    ngx_uint_t                       valid_msec;
+    ngx_uint_t                       vary_tag;
+
+    ngx_buf_t                       *buf;
+
+    ngx_http_file_cache_t           *file_cache;
+    ngx_http_file_cache_node_t      *node;
+
+#if (NGX_THREADS || NGX_COMPAT)
+    ngx_thread_task_t               *thread_task;
+#endif
+
+    ngx_msec_t                       lock_timeout;
+    ngx_msec_t                       lock_age;
+    ngx_msec_t                       lock_time;
+    ngx_msec_t                       wait_time;
+
+    ngx_event_t                      wait_event;
+
+    unsigned                         lock:1;
+    unsigned                         waiting:1;
+
+    unsigned                         updated:1;
+    unsigned                         updating:1;
+    unsigned                         exists:1;
+    unsigned                         temp_file:1;
+    unsigned                         purged:1;
+    unsigned                         reading:1;
+    unsigned                         secondary:1;
+    unsigned                         background:1;
+
+    unsigned                         stale_updating:1;
+    unsigned                         stale_error:1;
+};
+
+
+typedef struct {
+    ngx_uint_t                       version;
+    time_t                           valid_sec;
+    time_t                           updating_sec;
+    time_t                           error_sec;
+    time_t                           last_modified;
+    time_t                           date;
+    uint32_t                         crc32;
+    u_short                          valid_msec;
+    u_short                          header_start;
+    u_short                          body_start;
+    u_char                           etag_len;
+    u_char                           etag[NGX_HTTP_CACHE_ETAG_LEN];
+    u_char                           vary_len;
+    u_char                           vary[NGX_HTTP_CACHE_VARY_LEN];
+    u_char                           variant[NGX_HTTP_CACHE_KEY_LEN];
+} ngx_http_file_cache_header_t;
+
+
+typedef struct {
+    ngx_rbtree_t                     rbtree;
+    ngx_rbtree_node_t                sentinel;
+    ngx_queue_t                      queue;
+    ngx_atomic_t                     cold;
+    ngx_atomic_t                     loading;
+    off_t                            size;
+    ngx_uint_t                       count;
+    ngx_uint_t                       watermark;
+} ngx_http_file_cache_sh_t;
+
+
+struct ngx_http_file_cache_s {
+    ngx_http_file_cache_sh_t        *sh;
+    ngx_slab_pool_t                 *shpool;
+
+    ngx_path_t                      *path;
+
+    off_t                            max_size;
+    size_t                           bsize;
+
+    time_t                           inactive;
+
+    time_t                           fail_time;
+
+    ngx_uint_t                       files;
+    ngx_uint_t                       loader_files;
+    ngx_msec_t                       last;
+    ngx_msec_t                       loader_sleep;
+    ngx_msec_t                       loader_threshold;
+
+    ngx_uint_t                       manager_files;
+    ngx_msec_t                       manager_sleep;
+    ngx_msec_t                       manager_threshold;
+
+    ngx_shm_zone_t                  *shm_zone;
+
+    ngx_uint_t                       use_temp_path;
+                                     /* unsigned use_temp_path:1 */
+};
+
+
+ngx_int_t ngx_http_file_cache_new(ngx_http_request_t *r);
+ngx_int_t ngx_http_file_cache_create(ngx_http_request_t *r);
+void ngx_http_file_cache_create_key(ngx_http_request_t *r);
+ngx_int_t ngx_http_file_cache_open(ngx_http_request_t *r);
+ngx_int_t ngx_http_file_cache_set_header(ngx_http_request_t *r, u_char *buf);
+void ngx_http_file_cache_update(ngx_http_request_t *r, ngx_temp_file_t *tf);
+void ngx_http_file_cache_update_header(ngx_http_request_t *r);
+ngx_int_t ngx_http_cache_send(ngx_http_request_t *);
+void ngx_http_file_cache_free(ngx_http_cache_t *c, ngx_temp_file_t *tf);
+time_t ngx_http_file_cache_valid(ngx_array_t *cache_valid, ngx_uint_t status);
+
+char *ngx_http_file_cache_set_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+char *ngx_http_file_cache_valid_set_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+
+extern ngx_str_t  ngx_http_cache_status[];
+
+
+#endif /* _NGX_HTTP_CACHE_H_INCLUDED_ */
diff --git a/nginx/src/http/ngx_http_config.h b/nginx/src/http/ngx_http_config.h
new file mode 100644 (file)
index 0000000..2208c60
--- /dev/null
@@ -0,0 +1,75 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_CONFIG_H_INCLUDED_
+#define _NGX_HTTP_CONFIG_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    void        **main_conf;
+    void        **srv_conf;
+    void        **loc_conf;
+} ngx_http_conf_ctx_t;
+
+
+typedef struct {
+    ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);
+    ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);
+
+    void       *(*create_main_conf)(ngx_conf_t *cf);
+    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);
+
+    void       *(*create_srv_conf)(ngx_conf_t *cf);
+    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
+
+    void       *(*create_loc_conf)(ngx_conf_t *cf);
+    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
+} ngx_http_module_t;
+
+
+#define NGX_HTTP_MODULE           0x50545448   /* "HTTP" */
+
+#define NGX_HTTP_MAIN_CONF        0x02000000
+#define NGX_HTTP_SRV_CONF         0x04000000
+#define NGX_HTTP_LOC_CONF         0x08000000
+#define NGX_HTTP_UPS_CONF         0x10000000
+#define NGX_HTTP_SIF_CONF         0x20000000
+#define NGX_HTTP_LIF_CONF         0x40000000
+#define NGX_HTTP_LMT_CONF         0x80000000
+
+
+#define NGX_HTTP_MAIN_CONF_OFFSET  offsetof(ngx_http_conf_ctx_t, main_conf)
+#define NGX_HTTP_SRV_CONF_OFFSET   offsetof(ngx_http_conf_ctx_t, srv_conf)
+#define NGX_HTTP_LOC_CONF_OFFSET   offsetof(ngx_http_conf_ctx_t, loc_conf)
+
+
+#define ngx_http_get_module_main_conf(r, module)                             \
+    (r)->main_conf[module.ctx_index]
+#define ngx_http_get_module_srv_conf(r, module)  (r)->srv_conf[module.ctx_index]
+#define ngx_http_get_module_loc_conf(r, module)  (r)->loc_conf[module.ctx_index]
+
+
+#define ngx_http_conf_get_module_main_conf(cf, module)                        \
+    ((ngx_http_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index]
+#define ngx_http_conf_get_module_srv_conf(cf, module)                         \
+    ((ngx_http_conf_ctx_t *) cf->ctx)->srv_conf[module.ctx_index]
+#define ngx_http_conf_get_module_loc_conf(cf, module)                         \
+    ((ngx_http_conf_ctx_t *) cf->ctx)->loc_conf[module.ctx_index]
+
+#define ngx_http_cycle_get_module_main_conf(cycle, module)                    \
+    (cycle->conf_ctx[ngx_http_module.index] ?                                 \
+        ((ngx_http_conf_ctx_t *) cycle->conf_ctx[ngx_http_module.index])      \
+            ->main_conf[module.ctx_index]:                                    \
+        NULL)
+
+
+#endif /* _NGX_HTTP_CONFIG_H_INCLUDED_ */
diff --git a/nginx/src/http/ngx_http_copy_filter_module.c b/nginx/src/http/ngx_http_copy_filter_module.c
new file mode 100644 (file)
index 0000000..c8ad5da
--- /dev/null
@@ -0,0 +1,380 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_bufs_t  bufs;
+} ngx_http_copy_filter_conf_t;
+
+
+#if (NGX_HAVE_FILE_AIO)
+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,
+    ngx_file_t *file);
+static void ngx_http_copy_thread_event_handler(ngx_event_t *ev);
+#endif
+
+static void *ngx_http_copy_filter_create_conf(ngx_conf_t *cf);
+static char *ngx_http_copy_filter_merge_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+static ngx_int_t ngx_http_copy_filter_init(ngx_conf_t *cf);
+
+
+static ngx_command_t  ngx_http_copy_filter_commands[] = {
+
+    { ngx_string("output_buffers"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+      ngx_conf_set_bufs_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_copy_filter_conf_t, bufs),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_copy_filter_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_copy_filter_init,             /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_copy_filter_create_conf,      /* create location configuration */
+    ngx_http_copy_filter_merge_conf        /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_copy_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_copy_filter_module_ctx,      /* module context */
+    ngx_http_copy_filter_commands,         /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_copy_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    ngx_int_t                     rc;
+    ngx_connection_t             *c;
+    ngx_output_chain_ctx_t       *ctx;
+    ngx_http_core_loc_conf_t     *clcf;
+    ngx_http_copy_filter_conf_t  *conf;
+
+    c = r->connection;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http copy filter: \"%V?%V\"", &r->uri, &r->args);
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_copy_filter_module);
+
+    if (ctx == NULL) {
+        ctx = ngx_pcalloc(r->pool, sizeof(ngx_output_chain_ctx_t));
+        if (ctx == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_http_set_ctx(r, ctx, ngx_http_copy_filter_module);
+
+        conf = ngx_http_get_module_loc_conf(r, ngx_http_copy_filter_module);
+        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+        ctx->sendfile = c->sendfile;
+        ctx->need_in_memory = r->main_filter_need_in_memory
+                              || r->filter_need_in_memory;
+        ctx->need_in_temp = r->filter_need_temporary;
+
+        ctx->alignment = clcf->directio_alignment;
+
+        ctx->pool = r->pool;
+        ctx->bufs = conf->bufs;
+        ctx->tag = (ngx_buf_tag_t) &ngx_http_copy_filter_module;
+
+        ctx->output_filter = (ngx_output_chain_filter_pt)
+                                  ngx_http_next_body_filter;
+        ctx->filter_ctx = r;
+
+#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
+
+#if (NGX_THREADS)
+        if (clcf->aio == NGX_HTTP_AIO_THREADS) {
+            ctx->thread_handler = ngx_http_copy_thread_handler;
+        }
+#endif
+
+        if (in && in->buf && ngx_buf_size(in->buf)) {
+            r->request_output = 1;
+        }
+    }
+
+#if (NGX_HAVE_FILE_AIO || NGX_THREADS)
+    ctx->aio = r->aio;
+#endif
+
+    rc = ngx_output_chain(ctx, in);
+
+    if (ctx->in == NULL) {
+        r->buffered &= ~NGX_HTTP_COPY_BUFFERED;
+
+    } else {
+        r->buffered |= NGX_HTTP_COPY_BUFFERED;
+    }
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http copy filter: %i \"%V?%V\"", rc, &r->uri, &r->args);
+
+    return rc;
+}
+
+
+#if (NGX_HAVE_FILE_AIO)
+
+static void
+ngx_http_copy_aio_handler(ngx_output_chain_ctx_t *ctx, ngx_file_t *file)
+{
+    ngx_http_request_t *r;
+
+    r = ctx->filter_ctx;
+
+    file->aio->data = r;
+    file->aio->handler = ngx_http_copy_aio_event_handler;
+
+    r->main->blocked++;
+    r->aio = 1;
+    ctx->aio = 1;
+}
+
+
+static void
+ngx_http_copy_aio_event_handler(ngx_event_t *ev)
+{
+    ngx_event_aio_t     *aio;
+    ngx_connection_t    *c;
+    ngx_http_request_t  *r;
+
+    aio = ev->data;
+    r = aio->data;
+    c = r->connection;
+
+    ngx_http_set_log_request(c->log, r);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http aio: \"%V?%V\"", &r->uri, &r->args);
+
+    r->main->blocked--;
+    r->aio = 0;
+
+    r->write_event_handler(r);
+
+    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
+
+
+#if (NGX_THREADS)
+
+static ngx_int_t
+ngx_http_copy_thread_handler(ngx_thread_task_t *task, ngx_file_t *file)
+{
+    ngx_str_t                  name;
+    ngx_thread_pool_t         *tp;
+    ngx_http_request_t        *r;
+    ngx_output_chain_ctx_t    *ctx;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    r = file->thread_ctx;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+    tp = clcf->thread_pool;
+
+    if (tp == NULL) {
+        if (ngx_http_complex_value(r, clcf->thread_pool_value, &name)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+
+        tp = ngx_thread_pool_get((ngx_cycle_t *) ngx_cycle, &name);
+
+        if (tp == NULL) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "thread pool \"%V\" not found", &name);
+            return NGX_ERROR;
+        }
+    }
+
+    task->event.data = r;
+    task->event.handler = ngx_http_copy_thread_event_handler;
+
+    if (ngx_thread_task_post(tp, task) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    r->main->blocked++;
+    r->aio = 1;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_copy_filter_module);
+    ctx->aio = 1;
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_copy_thread_event_handler(ngx_event_t *ev)
+{
+    ngx_connection_t    *c;
+    ngx_http_request_t  *r;
+
+    r = ev->data;
+    c = r->connection;
+
+    ngx_http_set_log_request(c->log, r);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http thread: \"%V?%V\"", &r->uri, &r->args);
+
+    r->main->blocked--;
+    r->aio = 0;
+
+    if (r->done) {
+        /*
+         * trigger connection event handler if the subrequest was
+         * already finalized; this can happen if the handler is used
+         * for sendfile() in threads
+         */
+
+        c->write->handler(c->write);
+
+    } else {
+        r->write_event_handler(r);
+        ngx_http_run_posted_requests(c);
+    }
+}
+
+#endif
+
+
+static void *
+ngx_http_copy_filter_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_copy_filter_conf_t *conf;
+
+    conf = ngx_palloc(cf->pool, sizeof(ngx_http_copy_filter_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    conf->bufs.num = 0;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_copy_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_copy_filter_conf_t *prev = parent;
+    ngx_http_copy_filter_conf_t *conf = child;
+
+    ngx_conf_merge_bufs_value(conf->bufs, prev->bufs, 2, 32768);
+
+    return NULL;
+}
+
+
+static ngx_int_t
+ngx_http_copy_filter_init(ngx_conf_t *cf)
+{
+    ngx_http_next_body_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_copy_filter;
+
+    return NGX_OK;
+}
+
diff --git a/nginx/src/http/ngx_http_core_module.c b/nginx/src/http/ngx_http_core_module.c
new file mode 100644 (file)
index 0000000..6b318dd
--- /dev/null
@@ -0,0 +1,5084 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    u_char    *name;
+    uint32_t   method;
+} ngx_http_method_name_t;
+
+
+#define NGX_HTTP_REQUEST_BODY_FILE_OFF    0
+#define NGX_HTTP_REQUEST_BODY_FILE_ON     1
+#define NGX_HTTP_REQUEST_BODY_FILE_CLEAN  2
+
+
+static ngx_int_t ngx_http_core_find_location(ngx_http_request_t *r);
+static ngx_int_t ngx_http_core_find_static_location(ngx_http_request_t *r,
+    ngx_http_location_tree_node_t *node);
+
+static ngx_int_t ngx_http_core_preconfiguration(ngx_conf_t *cf);
+static ngx_int_t ngx_http_core_postconfiguration(ngx_conf_t *cf);
+static void *ngx_http_core_create_main_conf(ngx_conf_t *cf);
+static char *ngx_http_core_init_main_conf(ngx_conf_t *cf, void *conf);
+static void *ngx_http_core_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_http_core_merge_srv_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+static void *ngx_http_core_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_core_merge_loc_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+
+static char *ngx_http_core_server(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *dummy);
+static char *ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *dummy);
+static ngx_int_t ngx_http_core_regex_location(ngx_conf_t *cf,
+    ngx_http_core_loc_conf_t *clcf, ngx_str_t *regex, ngx_uint_t caseless);
+
+static char *ngx_http_core_types(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_core_type(ngx_conf_t *cf, ngx_command_t *dummy,
+    void *conf);
+
+static char *ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_core_server_name(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_core_root(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_http_core_limit_except(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_core_set_aio(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_core_directio(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_core_error_page(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_core_open_file_cache(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_core_error_log(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_core_keepalive(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_core_internal(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+#if (NGX_HTTP_GZIP)
+static ngx_int_t ngx_http_gzip_accept_encoding(ngx_str_t *ae);
+static ngx_uint_t ngx_http_gzip_quantity(u_char *p, u_char *last);
+static char *ngx_http_gzip_disable(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+#endif
+static ngx_int_t ngx_http_get_forwarded_addr_internal(ngx_http_request_t *r,
+    ngx_addr_t *addr, u_char *xff, size_t xfflen, ngx_array_t *proxies,
+    int recursive);
+#if (NGX_HAVE_OPENAT)
+static char *ngx_http_disable_symlinks(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+#endif
+
+static char *ngx_http_core_lowat_check(ngx_conf_t *cf, void *post, void *data);
+static char *ngx_http_core_pool_size(ngx_conf_t *cf, void *post, void *data);
+
+static ngx_conf_post_t  ngx_http_core_lowat_post =
+    { ngx_http_core_lowat_check };
+
+static ngx_conf_post_handler_pt  ngx_http_core_pool_size_p =
+    ngx_http_core_pool_size;
+
+
+static ngx_conf_enum_t  ngx_http_core_request_body_in_file[] = {
+    { ngx_string("off"), NGX_HTTP_REQUEST_BODY_FILE_OFF },
+    { ngx_string("on"), NGX_HTTP_REQUEST_BODY_FILE_ON },
+    { ngx_string("clean"), NGX_HTTP_REQUEST_BODY_FILE_CLEAN },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_enum_t  ngx_http_core_satisfy[] = {
+    { ngx_string("all"), NGX_HTTP_SATISFY_ALL },
+    { ngx_string("any"), NGX_HTTP_SATISFY_ANY },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_enum_t  ngx_http_core_lingering_close[] = {
+    { ngx_string("off"), NGX_HTTP_LINGERING_OFF },
+    { ngx_string("on"), NGX_HTTP_LINGERING_ON },
+    { ngx_string("always"), NGX_HTTP_LINGERING_ALWAYS },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_enum_t  ngx_http_core_server_tokens[] = {
+    { ngx_string("off"), NGX_HTTP_SERVER_TOKENS_OFF },
+    { ngx_string("on"), NGX_HTTP_SERVER_TOKENS_ON },
+    { ngx_string("build"), NGX_HTTP_SERVER_TOKENS_BUILD },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_enum_t  ngx_http_core_if_modified_since[] = {
+    { ngx_string("off"), NGX_HTTP_IMS_OFF },
+    { ngx_string("exact"), NGX_HTTP_IMS_EXACT },
+    { ngx_string("before"), NGX_HTTP_IMS_BEFORE },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_bitmask_t  ngx_http_core_keepalive_disable[] = {
+    { ngx_string("none"), NGX_HTTP_KEEPALIVE_DISABLE_NONE },
+    { ngx_string("msie6"), NGX_HTTP_KEEPALIVE_DISABLE_MSIE6 },
+    { ngx_string("safari"), NGX_HTTP_KEEPALIVE_DISABLE_SAFARI },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_path_init_t  ngx_http_client_temp_path = {
+    ngx_string(NGX_HTTP_CLIENT_TEMP_PATH), { 0, 0, 0 }
+};
+
+
+#if (NGX_HTTP_GZIP)
+
+static ngx_conf_enum_t  ngx_http_gzip_http_version[] = {
+    { ngx_string("1.0"), NGX_HTTP_VERSION_10 },
+    { ngx_string("1.1"), NGX_HTTP_VERSION_11 },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_bitmask_t  ngx_http_gzip_proxied_mask[] = {
+    { ngx_string("off"), NGX_HTTP_GZIP_PROXIED_OFF },
+    { ngx_string("expired"), NGX_HTTP_GZIP_PROXIED_EXPIRED },
+    { ngx_string("no-cache"), NGX_HTTP_GZIP_PROXIED_NO_CACHE },
+    { ngx_string("no-store"), NGX_HTTP_GZIP_PROXIED_NO_STORE },
+    { ngx_string("private"), NGX_HTTP_GZIP_PROXIED_PRIVATE },
+    { ngx_string("no_last_modified"), NGX_HTTP_GZIP_PROXIED_NO_LM },
+    { ngx_string("no_etag"), NGX_HTTP_GZIP_PROXIED_NO_ETAG },
+    { ngx_string("auth"), NGX_HTTP_GZIP_PROXIED_AUTH },
+    { ngx_string("any"), NGX_HTTP_GZIP_PROXIED_ANY },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_str_t  ngx_http_gzip_no_cache = ngx_string("no-cache");
+static ngx_str_t  ngx_http_gzip_no_store = ngx_string("no-store");
+static ngx_str_t  ngx_http_gzip_private = ngx_string("private");
+
+#endif
+
+
+static ngx_command_t  ngx_http_core_commands[] = {
+
+    { ngx_string("variables_hash_max_size"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      offsetof(ngx_http_core_main_conf_t, variables_hash_max_size),
+      NULL },
+
+    { ngx_string("variables_hash_bucket_size"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      offsetof(ngx_http_core_main_conf_t, variables_hash_bucket_size),
+      NULL },
+
+    { ngx_string("server_names_hash_max_size"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      offsetof(ngx_http_core_main_conf_t, server_names_hash_max_size),
+      NULL },
+
+    { ngx_string("server_names_hash_bucket_size"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      offsetof(ngx_http_core_main_conf_t, server_names_hash_bucket_size),
+      NULL },
+
+    { ngx_string("server"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+      ngx_http_core_server,
+      0,
+      0,
+      NULL },
+
+    { ngx_string("connection_pool_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_core_srv_conf_t, connection_pool_size),
+      &ngx_http_core_pool_size_p },
+
+    { ngx_string("request_pool_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_core_srv_conf_t, request_pool_size),
+      &ngx_http_core_pool_size_p },
+
+    { ngx_string("client_header_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_core_srv_conf_t, client_header_timeout),
+      NULL },
+
+    { ngx_string("client_header_buffer_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_core_srv_conf_t, client_header_buffer_size),
+      NULL },
+
+    { ngx_string("large_client_header_buffers"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE2,
+      ngx_conf_set_bufs_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_core_srv_conf_t, large_client_header_buffers),
+      NULL },
+
+    { ngx_string("ignore_invalid_headers"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_core_srv_conf_t, ignore_invalid_headers),
+      NULL },
+
+    { ngx_string("merge_slashes"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_core_srv_conf_t, merge_slashes),
+      NULL },
+
+    { ngx_string("underscores_in_headers"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_core_srv_conf_t, underscores_in_headers),
+      NULL },
+
+    { ngx_string("location"),
+      NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12,
+      ngx_http_core_location,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("listen"),
+      NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
+      ngx_http_core_listen,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("server_name"),
+      NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
+      ngx_http_core_server_name,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("types_hash_max_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, types_hash_max_size),
+      NULL },
+
+    { ngx_string("types_hash_bucket_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, types_hash_bucket_size),
+      NULL },
+
+    { ngx_string("types"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF
+                                          |NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+      ngx_http_core_types,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("default_type"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, default_type),
+      NULL },
+
+    { ngx_string("root"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+                        |NGX_CONF_TAKE1,
+      ngx_http_core_root,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("alias"),
+      NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_core_root,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("limit_except"),
+      NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_1MORE,
+      ngx_http_core_limit_except,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("client_max_body_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_off_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, client_max_body_size),
+      NULL },
+
+    { ngx_string("client_body_buffer_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, client_body_buffer_size),
+      NULL },
+
+    { ngx_string("client_body_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, client_body_timeout),
+      NULL },
+
+    { ngx_string("client_body_temp_path"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234,
+      ngx_conf_set_path_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, client_body_temp_path),
+      NULL },
+
+    { ngx_string("client_body_in_file_only"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, client_body_in_file_only),
+      &ngx_http_core_request_body_in_file },
+
+    { ngx_string("client_body_in_single_buffer"),
+      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_core_loc_conf_t, client_body_in_single_buffer),
+      NULL },
+
+    { ngx_string("sendfile"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+                        |NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, sendfile),
+      NULL },
+
+    { ngx_string("sendfile_max_chunk"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, sendfile_max_chunk),
+      NULL },
+
+    { ngx_string("subrequest_output_buffer_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, subrequest_output_buffer_size),
+      NULL },
+
+    { ngx_string("aio"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_core_set_aio,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("aio_write"),
+      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_core_loc_conf_t, aio_write),
+      NULL },
+
+    { ngx_string("read_ahead"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, read_ahead),
+      NULL },
+
+    { ngx_string("directio"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_core_directio,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("directio_alignment"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_off_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, directio_alignment),
+      NULL },
+
+    { ngx_string("tcp_nopush"),
+      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_core_loc_conf_t, tcp_nopush),
+      NULL },
+
+    { ngx_string("tcp_nodelay"),
+      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_core_loc_conf_t, tcp_nodelay),
+      NULL },
+
+    { ngx_string("send_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, send_timeout),
+      NULL },
+
+    { ngx_string("send_lowat"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, send_lowat),
+      &ngx_http_core_lowat_post },
+
+    { ngx_string("postpone_output"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, postpone_output),
+      NULL },
+
+    { ngx_string("limit_rate"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+                        |NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, limit_rate),
+      NULL },
+
+    { ngx_string("limit_rate_after"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+                        |NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, limit_rate_after),
+      NULL },
+
+    { ngx_string("keepalive_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+      ngx_http_core_keepalive,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("keepalive_requests"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, keepalive_requests),
+      NULL },
+
+    { ngx_string("keepalive_disable"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, keepalive_disable),
+      &ngx_http_core_keepalive_disable },
+
+    { ngx_string("satisfy"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, satisfy),
+      &ngx_http_core_satisfy },
+
+    { ngx_string("internal"),
+      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
+      ngx_http_core_internal,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("lingering_close"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, lingering_close),
+      &ngx_http_core_lingering_close },
+
+    { ngx_string("lingering_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, lingering_time),
+      NULL },
+
+    { ngx_string("lingering_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, lingering_timeout),
+      NULL },
+
+    { ngx_string("reset_timedout_connection"),
+      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_core_loc_conf_t, reset_timedout_connection),
+      NULL },
+
+    { ngx_string("absolute_redirect"),
+      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_core_loc_conf_t, absolute_redirect),
+      NULL },
+
+    { ngx_string("server_name_in_redirect"),
+      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_core_loc_conf_t, server_name_in_redirect),
+      NULL },
+
+    { ngx_string("port_in_redirect"),
+      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_core_loc_conf_t, port_in_redirect),
+      NULL },
+
+    { ngx_string("msie_padding"),
+      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_core_loc_conf_t, msie_padding),
+      NULL },
+
+    { ngx_string("msie_refresh"),
+      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_core_loc_conf_t, msie_refresh),
+      NULL },
+
+    { ngx_string("log_not_found"),
+      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_core_loc_conf_t, log_not_found),
+      NULL },
+
+    { ngx_string("log_subrequest"),
+      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_core_loc_conf_t, log_subrequest),
+      NULL },
+
+    { ngx_string("recursive_error_pages"),
+      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_core_loc_conf_t, recursive_error_pages),
+      NULL },
+
+    { ngx_string("server_tokens"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, server_tokens),
+      &ngx_http_core_server_tokens },
+
+    { ngx_string("if_modified_since"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, if_modified_since),
+      &ngx_http_core_if_modified_since },
+
+    { ngx_string("max_ranges"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, max_ranges),
+      NULL },
+
+    { ngx_string("chunked_transfer_encoding"),
+      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_core_loc_conf_t, chunked_transfer_encoding),
+      NULL },
+
+    { ngx_string("etag"),
+      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_core_loc_conf_t, etag),
+      NULL },
+
+    { ngx_string("error_page"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+                        |NGX_CONF_2MORE,
+      ngx_http_core_error_page,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("post_action"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+                        |NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, post_action),
+      NULL },
+
+    { ngx_string("error_log"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_core_error_log,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("open_file_cache"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+      ngx_http_core_open_file_cache,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, open_file_cache),
+      NULL },
+
+    { ngx_string("open_file_cache_valid"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_sec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, open_file_cache_valid),
+      NULL },
+
+    { ngx_string("open_file_cache_min_uses"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, open_file_cache_min_uses),
+      NULL },
+
+    { ngx_string("open_file_cache_errors"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, open_file_cache_errors),
+      NULL },
+
+    { ngx_string("open_file_cache_events"),
+      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_core_loc_conf_t, open_file_cache_events),
+      NULL },
+
+    { ngx_string("resolver"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_core_resolver,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("resolver_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, resolver_timeout),
+      NULL },
+
+#if (NGX_HTTP_GZIP)
+
+    { ngx_string("gzip_vary"),
+      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_core_loc_conf_t, gzip_vary),
+      NULL },
+
+    { ngx_string("gzip_http_version"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, gzip_http_version),
+      &ngx_http_gzip_http_version },
+
+    { ngx_string("gzip_proxied"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, gzip_proxied),
+      &ngx_http_gzip_proxied_mask },
+
+    { ngx_string("gzip_disable"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_gzip_disable,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+#endif
+
+#if (NGX_HAVE_OPENAT)
+
+    { ngx_string("disable_symlinks"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+      ngx_http_disable_symlinks,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+#endif
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_core_module_ctx = {
+    ngx_http_core_preconfiguration,        /* preconfiguration */
+    ngx_http_core_postconfiguration,       /* postconfiguration */
+
+    ngx_http_core_create_main_conf,        /* create main configuration */
+    ngx_http_core_init_main_conf,          /* init main configuration */
+
+    ngx_http_core_create_srv_conf,         /* create server configuration */
+    ngx_http_core_merge_srv_conf,          /* merge server configuration */
+
+    ngx_http_core_create_loc_conf,         /* create location configuration */
+    ngx_http_core_merge_loc_conf           /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_core_module = {
+    NGX_MODULE_V1,
+    &ngx_http_core_module_ctx,             /* module context */
+    ngx_http_core_commands,                /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+ngx_str_t  ngx_http_core_get_method = { 3, (u_char *) "GET" };
+
+
+void
+ngx_http_handler(ngx_http_request_t *r)
+{
+    ngx_http_core_main_conf_t  *cmcf;
+
+    r->connection->log->action = NULL;
+
+    if (!r->internal) {
+        switch (r->headers_in.connection_type) {
+        case 0:
+            r->keepalive = (r->http_version > NGX_HTTP_VERSION_10);
+            break;
+
+        case NGX_HTTP_CONNECTION_CLOSE:
+            r->keepalive = 0;
+            break;
+
+        case NGX_HTTP_CONNECTION_KEEP_ALIVE:
+            r->keepalive = 1;
+            break;
+        }
+
+        r->lingering_close = (r->headers_in.content_length_n > 0
+                              || r->headers_in.chunked);
+        r->phase_handler = 0;
+
+    } else {
+        cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+        r->phase_handler = cmcf->phase_engine.server_rewrite_index;
+    }
+
+    r->valid_location = 1;
+#if (NGX_HTTP_GZIP)
+    r->gzip_tested = 0;
+    r->gzip_ok = 0;
+    r->gzip_vary = 0;
+#endif
+
+    r->write_event_handler = ngx_http_core_run_phases;
+    ngx_http_core_run_phases(r);
+}
+
+
+void
+ngx_http_core_run_phases(ngx_http_request_t *r)
+{
+    ngx_int_t                   rc;
+    ngx_http_phase_handler_t   *ph;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+    ph = cmcf->phase_engine.handlers;
+
+    while (ph[r->phase_handler].checker) {
+
+        rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
+
+        if (rc == NGX_OK) {
+            return;
+        }
+    }
+}
+
+
+ngx_int_t
+ngx_http_core_generic_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph)
+{
+    ngx_int_t  rc;
+
+    /*
+     * generic phase checker,
+     * used by the post read and pre-access phases
+     */
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "generic phase: %ui", r->phase_handler);
+
+    rc = ph->handler(r);
+
+    if (rc == NGX_OK) {
+        r->phase_handler = ph->next;
+        return NGX_AGAIN;
+    }
+
+    if (rc == NGX_DECLINED) {
+        r->phase_handler++;
+        return NGX_AGAIN;
+    }
+
+    if (rc == NGX_AGAIN || rc == NGX_DONE) {
+        return NGX_OK;
+    }
+
+    /* rc == NGX_ERROR || rc == NGX_HTTP_...  */
+
+    ngx_http_finalize_request(r, rc);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_core_rewrite_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph)
+{
+    ngx_int_t  rc;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "rewrite phase: %ui", r->phase_handler);
+
+    rc = ph->handler(r);
+
+    if (rc == NGX_DECLINED) {
+        r->phase_handler++;
+        return NGX_AGAIN;
+    }
+
+    if (rc == NGX_DONE) {
+        return NGX_OK;
+    }
+
+    /* NGX_OK, NGX_AGAIN, NGX_ERROR, NGX_HTTP_...  */
+
+    ngx_http_finalize_request(r, rc);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_core_find_config_phase(ngx_http_request_t *r,
+    ngx_http_phase_handler_t *ph)
+{
+    u_char                    *p;
+    size_t                     len;
+    ngx_int_t                  rc;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    r->content_handler = NULL;
+    r->uri_changed = 0;
+
+    rc = ngx_http_core_find_location(r);
+
+    if (rc == NGX_ERROR) {
+        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NGX_OK;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (!r->internal && clcf->internal) {
+        ngx_http_finalize_request(r, NGX_HTTP_NOT_FOUND);
+        return NGX_OK;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "using configuration \"%s%V\"",
+                   (clcf->noname ? "*" : (clcf->exact_match ? "=" : "")),
+                   &clcf->name);
+
+    ngx_http_update_location_config(r);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http cl:%O max:%O",
+                   r->headers_in.content_length_n, clcf->client_max_body_size);
+
+    if (r->headers_in.content_length_n != -1
+        && !r->discard_body
+        && clcf->client_max_body_size
+        && clcf->client_max_body_size < r->headers_in.content_length_n)
+    {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "client intended to send too large body: %O bytes",
+                      r->headers_in.content_length_n);
+
+        r->expect_tested = 1;
+        (void) ngx_http_discard_request_body(r);
+        ngx_http_finalize_request(r, NGX_HTTP_REQUEST_ENTITY_TOO_LARGE);
+        return NGX_OK;
+    }
+
+    if (rc == NGX_DONE) {
+        ngx_http_clear_location(r);
+
+        r->headers_out.location = ngx_list_push(&r->headers_out.headers);
+        if (r->headers_out.location == NULL) {
+            ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return NGX_OK;
+        }
+
+        r->headers_out.location->hash = 1;
+        ngx_str_set(&r->headers_out.location->key, "Location");
+
+        if (r->args.len == 0) {
+            r->headers_out.location->value = clcf->name;
+
+        } else {
+            len = clcf->name.len + 1 + r->args.len;
+            p = ngx_pnalloc(r->pool, len);
+
+            if (p == NULL) {
+                ngx_http_clear_location(r);
+                ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+                return NGX_OK;
+            }
+
+            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_memcpy(p, r->args.data, r->args.len);
+        }
+
+        ngx_http_finalize_request(r, NGX_HTTP_MOVED_PERMANENTLY);
+        return NGX_OK;
+    }
+
+    r->phase_handler++;
+    return NGX_AGAIN;
+}
+
+
+ngx_int_t
+ngx_http_core_post_rewrite_phase(ngx_http_request_t *r,
+    ngx_http_phase_handler_t *ph)
+{
+    ngx_http_core_srv_conf_t  *cscf;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "post rewrite phase: %ui", r->phase_handler);
+
+    if (!r->uri_changed) {
+        r->phase_handler++;
+        return NGX_AGAIN;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "uri changes: %d", r->uri_changes);
+
+    /*
+     * gcc before 3.3 compiles the broken code for
+     *     if (r->uri_changes-- == 0)
+     * if the r->uri_changes is defined as
+     *     unsigned  uri_changes:4
+     */
+
+    r->uri_changes--;
+
+    if (r->uri_changes == 0) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "rewrite or internal redirection cycle "
+                      "while processing \"%V\"", &r->uri);
+
+        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NGX_OK;
+    }
+
+    r->phase_handler = ph->next;
+
+    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+    r->loc_conf = cscf->ctx->loc_conf;
+
+    return NGX_AGAIN;
+}
+
+
+ngx_int_t
+ngx_http_core_access_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph)
+{
+    ngx_int_t                  rc;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    if (r != r->main) {
+        r->phase_handler = ph->next;
+        return NGX_AGAIN;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "access phase: %ui", r->phase_handler);
+
+    rc = ph->handler(r);
+
+    if (rc == NGX_DECLINED) {
+        r->phase_handler++;
+        return NGX_AGAIN;
+    }
+
+    if (rc == NGX_AGAIN || rc == NGX_DONE) {
+        return NGX_OK;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (clcf->satisfy == NGX_HTTP_SATISFY_ALL) {
+
+        if (rc == NGX_OK) {
+            r->phase_handler++;
+            return NGX_AGAIN;
+        }
+
+    } else {
+        if (rc == NGX_OK) {
+            r->access_code = 0;
+
+            if (r->headers_out.www_authenticate) {
+                r->headers_out.www_authenticate->hash = 0;
+            }
+
+            r->phase_handler = ph->next;
+            return NGX_AGAIN;
+        }
+
+        if (rc == NGX_HTTP_FORBIDDEN || rc == NGX_HTTP_UNAUTHORIZED) {
+            if (r->access_code != NGX_HTTP_UNAUTHORIZED) {
+                r->access_code = rc;
+            }
+
+            r->phase_handler++;
+            return NGX_AGAIN;
+        }
+    }
+
+    /* rc == NGX_ERROR || rc == NGX_HTTP_...  */
+
+    ngx_http_finalize_request(r, rc);
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_core_post_access_phase(ngx_http_request_t *r,
+    ngx_http_phase_handler_t *ph)
+{
+    ngx_int_t  access_code;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "post access phase: %ui", r->phase_handler);
+
+    access_code = r->access_code;
+
+    if (access_code) {
+        if (access_code == NGX_HTTP_FORBIDDEN) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "access forbidden by rule");
+        }
+
+        r->access_code = 0;
+        ngx_http_finalize_request(r, access_code);
+        return NGX_OK;
+    }
+
+    r->phase_handler++;
+    return NGX_AGAIN;
+}
+
+
+ngx_int_t
+ngx_http_core_content_phase(ngx_http_request_t *r,
+    ngx_http_phase_handler_t *ph)
+{
+    size_t     root;
+    ngx_int_t  rc;
+    ngx_str_t  path;
+
+    if (r->content_handler) {
+        r->write_event_handler = ngx_http_request_empty_handler;
+        ngx_http_finalize_request(r, r->content_handler(r));
+        return NGX_OK;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "content phase: %ui", r->phase_handler);
+
+    rc = ph->handler(r);
+
+    if (rc != NGX_DECLINED) {
+        ngx_http_finalize_request(r, rc);
+        return NGX_OK;
+    }
+
+    /* rc == NGX_DECLINED */
+
+    ph++;
+
+    if (ph->checker) {
+        r->phase_handler++;
+        return NGX_AGAIN;
+    }
+
+    /* no content handler was found */
+
+    if (r->uri.data[r->uri.len - 1] == '/') {
+
+        if (ngx_http_map_uri_to_path(r, &path, &root, 0) != NULL) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "directory index of \"%s\" is forbidden", path.data);
+        }
+
+        ngx_http_finalize_request(r, NGX_HTTP_FORBIDDEN);
+        return NGX_OK;
+    }
+
+    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no handler found");
+
+    ngx_http_finalize_request(r, NGX_HTTP_NOT_FOUND);
+    return NGX_OK;
+}
+
+
+void
+ngx_http_update_location_config(ngx_http_request_t *r)
+{
+    ngx_http_core_loc_conf_t  *clcf;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (r->method & clcf->limit_except) {
+        r->loc_conf = clcf->limit_except_loc_conf;
+        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+    }
+
+    if (r == r->main) {
+        ngx_set_connection_log(r->connection, clcf->error_log);
+    }
+
+    if ((ngx_io.flags & NGX_IO_SENDFILE) && clcf->sendfile) {
+        r->connection->sendfile = 1;
+
+    } else {
+        r->connection->sendfile = 0;
+    }
+
+    if (clcf->client_body_in_file_only) {
+        r->request_body_in_file_only = 1;
+        r->request_body_in_persistent_file = 1;
+        r->request_body_in_clean_file =
+            clcf->client_body_in_file_only == NGX_HTTP_REQUEST_BODY_FILE_CLEAN;
+        r->request_body_file_log_level = NGX_LOG_NOTICE;
+
+    } else {
+        r->request_body_file_log_level = NGX_LOG_WARN;
+    }
+
+    r->request_body_in_single_buf = clcf->client_body_in_single_buffer;
+
+    if (r->keepalive) {
+        if (clcf->keepalive_timeout == 0) {
+            r->keepalive = 0;
+
+        } else if (r->connection->requests >= clcf->keepalive_requests) {
+            r->keepalive = 0;
+
+        } else if (r->headers_in.msie6
+                   && r->method == NGX_HTTP_POST
+                   && (clcf->keepalive_disable
+                       & NGX_HTTP_KEEPALIVE_DISABLE_MSIE6))
+        {
+            /*
+             * MSIE may wait for some time if an response for
+             * a POST request was sent over a keepalive connection
+             */
+            r->keepalive = 0;
+
+        } else if (r->headers_in.safari
+                   && (clcf->keepalive_disable
+                       & NGX_HTTP_KEEPALIVE_DISABLE_SAFARI))
+        {
+            /*
+             * Safari may send a POST request to a closed keepalive
+             * connection and may stall for some time, see
+             *     https://bugs.webkit.org/show_bug.cgi?id=5760
+             */
+            r->keepalive = 0;
+        }
+    }
+
+    if (!clcf->tcp_nopush) {
+        /* disable TCP_NOPUSH/TCP_CORK use */
+        r->connection->tcp_nopush = NGX_TCP_NOPUSH_DISABLED;
+    }
+
+    if (r->limit_rate == 0) {
+        r->limit_rate = clcf->limit_rate;
+    }
+
+    if (clcf->handler) {
+        r->content_handler = clcf->handler;
+    }
+}
+
+
+/*
+ * NGX_OK       - exact or regex match
+ * NGX_DONE     - auto redirect
+ * NGX_AGAIN    - inclusive match
+ * NGX_ERROR    - regex error
+ * NGX_DECLINED - no match
+ */
+
+static ngx_int_t
+ngx_http_core_find_location(ngx_http_request_t *r)
+{
+    ngx_int_t                  rc;
+    ngx_http_core_loc_conf_t  *pclcf;
+#if (NGX_PCRE)
+    ngx_int_t                  n;
+    ngx_uint_t                 noregex;
+    ngx_http_core_loc_conf_t  *clcf, **clcfp;
+
+    noregex = 0;
+#endif
+
+    pclcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    rc = ngx_http_core_find_static_location(r, pclcf->static_locations);
+
+    if (rc == NGX_AGAIN) {
+
+#if (NGX_PCRE)
+        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+        noregex = clcf->noregex;
+#endif
+
+        /* look up nested locations */
+
+        rc = ngx_http_core_find_location(r);
+    }
+
+    if (rc == NGX_OK || rc == NGX_DONE) {
+        return rc;
+    }
+
+    /* rc == NGX_DECLINED or rc == NGX_AGAIN in nested location */
+
+#if (NGX_PCRE)
+
+    if (noregex == 0 && pclcf->regex_locations) {
+
+        for (clcfp = pclcf->regex_locations; *clcfp; clcfp++) {
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "test location: ~ \"%V\"", &(*clcfp)->name);
+
+            n = ngx_http_regex_exec(r, (*clcfp)->regex, &r->uri);
+
+            if (n == NGX_OK) {
+                r->loc_conf = (*clcfp)->loc_conf;
+
+                /* look up nested locations */
+
+                rc = ngx_http_core_find_location(r);
+
+                return (rc == NGX_ERROR) ? rc : NGX_OK;
+            }
+
+            if (n == NGX_DECLINED) {
+                continue;
+            }
+
+            return NGX_ERROR;
+        }
+    }
+#endif
+
+    return rc;
+}
+
+
+/*
+ * NGX_OK       - exact match
+ * NGX_DONE     - auto redirect
+ * NGX_AGAIN    - inclusive match
+ * NGX_DECLINED - no match
+ */
+
+static ngx_int_t
+ngx_http_core_find_static_location(ngx_http_request_t *r,
+    ngx_http_location_tree_node_t *node)
+{
+    u_char     *uri;
+    size_t      len, n;
+    ngx_int_t   rc, rv;
+
+    len = r->uri.len;
+    uri = r->uri.data;
+
+    rv = NGX_DECLINED;
+
+    for ( ;; ) {
+
+        if (node == NULL) {
+            return rv;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "test location: \"%*s\"",
+                       (size_t) node->len, node->name);
+
+        n = (len <= (size_t) node->len) ? len : node->len;
+
+        rc = ngx_filename_cmp(uri, node->name, n);
+
+        if (rc != 0) {
+            node = (rc < 0) ? node->left : node->right;
+
+            continue;
+        }
+
+        if (len > (size_t) node->len) {
+
+            if (node->inclusive) {
+
+                r->loc_conf = node->inclusive->loc_conf;
+                rv = NGX_AGAIN;
+
+                node = node->tree;
+                uri += n;
+                len -= n;
+
+                continue;
+            }
+
+            /* exact only */
+
+            node = node->right;
+
+            continue;
+        }
+
+        if (len == (size_t) node->len) {
+
+            if (node->exact) {
+                r->loc_conf = node->exact->loc_conf;
+                return NGX_OK;
+
+            } else {
+                r->loc_conf = node->inclusive->loc_conf;
+                return NGX_AGAIN;
+            }
+        }
+
+        /* len < node->len */
+
+        if (len + 1 == (size_t) node->len && node->auto_redirect) {
+
+            r->loc_conf = (node->exact) ? node->exact->loc_conf:
+                                          node->inclusive->loc_conf;
+            rv = NGX_DONE;
+        }
+
+        node = node->left;
+    }
+}
+
+
+void *
+ngx_http_test_content_type(ngx_http_request_t *r, ngx_hash_t *types_hash)
+{
+    u_char      c, *lowcase;
+    size_t      len;
+    ngx_uint_t  i, hash;
+
+    if (types_hash->size == 0) {
+        return (void *) 4;
+    }
+
+    if (r->headers_out.content_type.len == 0) {
+        return NULL;
+    }
+
+    len = r->headers_out.content_type_len;
+
+    if (r->headers_out.content_type_lowcase == NULL) {
+
+        lowcase = ngx_pnalloc(r->pool, len);
+        if (lowcase == NULL) {
+            return NULL;
+        }
+
+        r->headers_out.content_type_lowcase = lowcase;
+
+        hash = 0;
+
+        for (i = 0; i < len; i++) {
+            c = ngx_tolower(r->headers_out.content_type.data[i]);
+            hash = ngx_hash(hash, c);
+            lowcase[i] = c;
+        }
+
+        r->headers_out.content_type_hash = hash;
+    }
+
+    return ngx_hash_find(types_hash, r->headers_out.content_type_hash,
+                         r->headers_out.content_type_lowcase, len);
+}
+
+
+ngx_int_t
+ngx_http_set_content_type(ngx_http_request_t *r)
+{
+    u_char                     c, *exten;
+    ngx_str_t                 *type;
+    ngx_uint_t                 i, hash;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    if (r->headers_out.content_type.len) {
+        return NGX_OK;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (r->exten.len) {
+
+        hash = 0;
+
+        for (i = 0; i < r->exten.len; i++) {
+            c = r->exten.data[i];
+
+            if (c >= 'A' && c <= 'Z') {
+
+                exten = ngx_pnalloc(r->pool, r->exten.len);
+                if (exten == NULL) {
+                    return NGX_ERROR;
+                }
+
+                hash = ngx_hash_strlow(exten, r->exten.data, r->exten.len);
+
+                r->exten.data = exten;
+
+                break;
+            }
+
+            hash = ngx_hash(hash, c);
+        }
+
+        type = ngx_hash_find(&clcf->types_hash, hash,
+                             r->exten.data, r->exten.len);
+
+        if (type) {
+            r->headers_out.content_type_len = type->len;
+            r->headers_out.content_type = *type;
+
+            return NGX_OK;
+        }
+    }
+
+    r->headers_out.content_type_len = clcf->default_type.len;
+    r->headers_out.content_type = clcf->default_type;
+
+    return NGX_OK;
+}
+
+
+void
+ngx_http_set_exten(ngx_http_request_t *r)
+{
+    ngx_int_t  i;
+
+    ngx_str_null(&r->exten);
+
+    for (i = r->uri.len - 1; i > 1; i--) {
+        if (r->uri.data[i] == '.' && r->uri.data[i - 1] != '/') {
+
+            r->exten.len = r->uri.len - i - 1;
+            r->exten.data = &r->uri.data[i + 1];
+
+            return;
+
+        } else if (r->uri.data[i] == '/') {
+            return;
+        }
+    }
+
+    return;
+}
+
+
+ngx_int_t
+ngx_http_set_etag(ngx_http_request_t *r)
+{
+    ngx_table_elt_t           *etag;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (!clcf->etag) {
+        return NGX_OK;
+    }
+
+    etag = ngx_list_push(&r->headers_out.headers);
+    if (etag == NULL) {
+        return NGX_ERROR;
+    }
+
+    etag->hash = 1;
+    ngx_str_set(&etag->key, "ETag");
+
+    etag->value.data = ngx_pnalloc(r->pool, NGX_OFF_T_LEN + NGX_TIME_T_LEN + 3);
+    if (etag->value.data == NULL) {
+        etag->hash = 0;
+        return NGX_ERROR;
+    }
+
+    etag->value.len = ngx_sprintf(etag->value.data, "\"%xT-%xO\"",
+                                  r->headers_out.last_modified_time,
+                                  r->headers_out.content_length_n)
+                      - etag->value.data;
+
+    r->headers_out.etag = etag;
+
+    return NGX_OK;
+}
+
+
+void
+ngx_http_weak_etag(ngx_http_request_t *r)
+{
+    size_t            len;
+    u_char           *p;
+    ngx_table_elt_t  *etag;
+
+    etag = r->headers_out.etag;
+
+    if (etag == NULL) {
+        return;
+    }
+
+    if (etag->value.len > 2
+        && etag->value.data[0] == 'W'
+        && etag->value.data[1] == '/')
+    {
+        return;
+    }
+
+    if (etag->value.len < 1 || etag->value.data[0] != '"') {
+        r->headers_out.etag->hash = 0;
+        r->headers_out.etag = NULL;
+        return;
+    }
+
+    p = ngx_pnalloc(r->pool, etag->value.len + 2);
+    if (p == NULL) {
+        r->headers_out.etag->hash = 0;
+        r->headers_out.etag = NULL;
+        return;
+    }
+
+    len = ngx_sprintf(p, "W/%V", &etag->value) - p;
+
+    etag->value.data = p;
+    etag->value.len = len;
+}
+
+
+ngx_int_t
+ngx_http_send_response(ngx_http_request_t *r, ngx_uint_t status,
+    ngx_str_t *ct, ngx_http_complex_value_t *cv)
+{
+    ngx_int_t     rc;
+    ngx_str_t     val;
+    ngx_buf_t    *b;
+    ngx_chain_t   out;
+
+    if (ngx_http_discard_request_body(r) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    r->headers_out.status = status;
+
+    if (ngx_http_complex_value(r, cv, &val) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (status == NGX_HTTP_MOVED_PERMANENTLY
+        || status == NGX_HTTP_MOVED_TEMPORARILY
+        || status == NGX_HTTP_SEE_OTHER
+        || status == NGX_HTTP_TEMPORARY_REDIRECT
+        || status == NGX_HTTP_PERMANENT_REDIRECT)
+    {
+        ngx_http_clear_location(r);
+
+        r->headers_out.location = ngx_list_push(&r->headers_out.headers);
+        if (r->headers_out.location == NULL) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        r->headers_out.location->hash = 1;
+        ngx_str_set(&r->headers_out.location->key, "Location");
+        r->headers_out.location->value = val;
+
+        return status;
+    }
+
+    r->headers_out.content_length_n = val.len;
+
+    if (ct) {
+        r->headers_out.content_type_len = ct->len;
+        r->headers_out.content_type = *ct;
+
+    } else {
+        if (ngx_http_set_content_type(r) != NGX_OK) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+    }
+
+    if (r->method == NGX_HTTP_HEAD || (r != r->main && val.len == 0)) {
+        return ngx_http_send_header(r);
+    }
+
+    b = ngx_calloc_buf(r->pool);
+    if (b == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    b->pos = val.data;
+    b->last = val.data + val.len;
+    b->memory = val.len ? 1 : 0;
+    b->last_buf = (r == r->main) ? 1 : 0;
+    b->last_in_chain = 1;
+
+    out.buf = b;
+    out.next = NULL;
+
+    rc = ngx_http_send_header(r);
+
+    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+        return rc;
+    }
+
+    return ngx_http_output_filter(r, &out);
+}
+
+
+ngx_int_t
+ngx_http_send_header(ngx_http_request_t *r)
+{
+    if (r->post_action) {
+        return NGX_OK;
+    }
+
+    if (r->header_sent) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                      "header already sent");
+        return NGX_ERROR;
+    }
+
+    if (r->err_status) {
+        r->headers_out.status = r->err_status;
+        r->headers_out.status_line.len = 0;
+    }
+
+    return ngx_http_top_header_filter(r);
+}
+
+
+ngx_int_t
+ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    ngx_int_t          rc;
+    ngx_connection_t  *c;
+
+    c = r->connection;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http output filter \"%V?%V\"", &r->uri, &r->args);
+
+    rc = ngx_http_top_body_filter(r, in);
+
+    if (rc == NGX_ERROR) {
+        /* NGX_ERROR may be returned by any filter */
+        c->error = 1;
+    }
+
+    return rc;
+}
+
+
+u_char *
+ngx_http_map_uri_to_path(ngx_http_request_t *r, ngx_str_t *path,
+    size_t *root_length, size_t reserved)
+{
+    u_char                    *last;
+    size_t                     alias;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    alias = clcf->alias;
+
+    if (alias && !r->valid_location) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                      "\"alias\" cannot be used in location \"%V\" "
+                      "where URI was rewritten", &clcf->name);
+        return NULL;
+    }
+
+    if (clcf->root_lengths == NULL) {
+
+        *root_length = clcf->root.len;
+
+        path->len = clcf->root.len + reserved + r->uri.len - alias + 1;
+
+        path->data = ngx_pnalloc(r->pool, path->len);
+        if (path->data == NULL) {
+            return NULL;
+        }
+
+        last = ngx_copy(path->data, clcf->root.data, clcf->root.len);
+
+    } else {
+
+        if (alias == NGX_MAX_SIZE_T_VALUE) {
+            reserved += r->add_uri_to_alias ? r->uri.len + 1 : 1;
+
+        } else {
+            reserved += r->uri.len - alias + 1;
+        }
+
+        if (ngx_http_script_run(r, path, clcf->root_lengths->elts, reserved,
+                                clcf->root_values->elts)
+            == NULL)
+        {
+            return NULL;
+        }
+
+        if (ngx_get_full_name(r->pool, (ngx_str_t *) &ngx_cycle->prefix, path)
+            != NGX_OK)
+        {
+            return NULL;
+        }
+
+        *root_length = path->len - reserved;
+        last = path->data + *root_length;
+
+        if (alias == NGX_MAX_SIZE_T_VALUE) {
+            if (!r->add_uri_to_alias) {
+                *last = '\0';
+                return last;
+            }
+
+            alias = 0;
+        }
+    }
+
+    last = ngx_cpystrn(last, r->uri.data + alias, r->uri.len - alias + 1);
+
+    return last;
+}
+
+
+ngx_int_t
+ngx_http_auth_basic_user(ngx_http_request_t *r)
+{
+    ngx_str_t   auth, encoded;
+    ngx_uint_t  len;
+
+    if (r->headers_in.user.len == 0 && r->headers_in.user.data != NULL) {
+        return NGX_DECLINED;
+    }
+
+    if (r->headers_in.authorization == NULL) {
+        r->headers_in.user.data = (u_char *) "";
+        return NGX_DECLINED;
+    }
+
+    encoded = r->headers_in.authorization->value;
+
+    if (encoded.len < sizeof("Basic ") - 1
+        || ngx_strncasecmp(encoded.data, (u_char *) "Basic ",
+                           sizeof("Basic ") - 1)
+           != 0)
+    {
+        r->headers_in.user.data = (u_char *) "";
+        return NGX_DECLINED;
+    }
+
+    encoded.len -= sizeof("Basic ") - 1;
+    encoded.data += sizeof("Basic ") - 1;
+
+    while (encoded.len && encoded.data[0] == ' ') {
+        encoded.len--;
+        encoded.data++;
+    }
+
+    if (encoded.len == 0) {
+        r->headers_in.user.data = (u_char *) "";
+        return NGX_DECLINED;
+    }
+
+    auth.len = ngx_base64_decoded_length(encoded.len);
+    auth.data = ngx_pnalloc(r->pool, auth.len + 1);
+    if (auth.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_decode_base64(&auth, &encoded) != NGX_OK) {
+        r->headers_in.user.data = (u_char *) "";
+        return NGX_DECLINED;
+    }
+
+    auth.data[auth.len] = '\0';
+
+    for (len = 0; len < auth.len; len++) {
+        if (auth.data[len] == ':') {
+            break;
+        }
+    }
+
+    if (len == 0 || len == auth.len) {
+        r->headers_in.user.data = (u_char *) "";
+        return NGX_DECLINED;
+    }
+
+    r->headers_in.user.len = len;
+    r->headers_in.user.data = auth.data;
+    r->headers_in.passwd.len = auth.len - len - 1;
+    r->headers_in.passwd.data = &auth.data[len + 1];
+
+    return NGX_OK;
+}
+
+
+#if (NGX_HTTP_GZIP)
+
+ngx_int_t
+ngx_http_gzip_ok(ngx_http_request_t *r)
+{
+    time_t                     date, expires;
+    ngx_uint_t                 p;
+    ngx_array_t               *cc;
+    ngx_table_elt_t           *e, *d, *ae;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    r->gzip_tested = 1;
+
+    if (r != r->main) {
+        return NGX_DECLINED;
+    }
+
+    ae = r->headers_in.accept_encoding;
+    if (ae == NULL) {
+        return NGX_DECLINED;
+    }
+
+    if (ae->value.len < sizeof("gzip") - 1) {
+        return NGX_DECLINED;
+    }
+
+    /*
+     * test first for the most common case "gzip,...":
+     *   MSIE:    "gzip, deflate"
+     *   Firefox: "gzip,deflate"
+     *   Chrome:  "gzip,deflate,sdch"
+     *   Safari:  "gzip, deflate"
+     *   Opera:   "gzip, deflate"
+     */
+
+    if (ngx_memcmp(ae->value.data, "gzip,", 5) != 0
+        && ngx_http_gzip_accept_encoding(&ae->value) != NGX_OK)
+    {
+        return NGX_DECLINED;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (r->headers_in.msie6 && clcf->gzip_disable_msie6) {
+        return NGX_DECLINED;
+    }
+
+    if (r->http_version < clcf->gzip_http_version) {
+        return NGX_DECLINED;
+    }
+
+    if (r->headers_in.via == NULL) {
+        goto ok;
+    }
+
+    p = clcf->gzip_proxied;
+
+    if (p & NGX_HTTP_GZIP_PROXIED_OFF) {
+        return NGX_DECLINED;
+    }
+
+    if (p & NGX_HTTP_GZIP_PROXIED_ANY) {
+        goto ok;
+    }
+
+    if (r->headers_in.authorization && (p & NGX_HTTP_GZIP_PROXIED_AUTH)) {
+        goto ok;
+    }
+
+    e = r->headers_out.expires;
+
+    if (e) {
+
+        if (!(p & NGX_HTTP_GZIP_PROXIED_EXPIRED)) {
+            return NGX_DECLINED;
+        }
+
+        expires = ngx_parse_http_time(e->value.data, e->value.len);
+        if (expires == NGX_ERROR) {
+            return NGX_DECLINED;
+        }
+
+        d = r->headers_out.date;
+
+        if (d) {
+            date = ngx_parse_http_time(d->value.data, d->value.len);
+            if (date == NGX_ERROR) {
+                return NGX_DECLINED;
+            }
+
+        } else {
+            date = ngx_time();
+        }
+
+        if (expires < date) {
+            goto ok;
+        }
+
+        return NGX_DECLINED;
+    }
+
+    cc = &r->headers_out.cache_control;
+
+    if (cc->elts) {
+
+        if ((p & NGX_HTTP_GZIP_PROXIED_NO_CACHE)
+            && ngx_http_parse_multi_header_lines(cc, &ngx_http_gzip_no_cache,
+                                                 NULL)
+               >= 0)
+        {
+            goto ok;
+        }
+
+        if ((p & NGX_HTTP_GZIP_PROXIED_NO_STORE)
+            && ngx_http_parse_multi_header_lines(cc, &ngx_http_gzip_no_store,
+                                                 NULL)
+               >= 0)
+        {
+            goto ok;
+        }
+
+        if ((p & NGX_HTTP_GZIP_PROXIED_PRIVATE)
+            && ngx_http_parse_multi_header_lines(cc, &ngx_http_gzip_private,
+                                                 NULL)
+               >= 0)
+        {
+            goto ok;
+        }
+
+        return NGX_DECLINED;
+    }
+
+    if ((p & NGX_HTTP_GZIP_PROXIED_NO_LM) && r->headers_out.last_modified) {
+        return NGX_DECLINED;
+    }
+
+    if ((p & NGX_HTTP_GZIP_PROXIED_NO_ETAG) && r->headers_out.etag) {
+        return NGX_DECLINED;
+    }
+
+ok:
+
+#if (NGX_PCRE)
+
+    if (clcf->gzip_disable && r->headers_in.user_agent) {
+
+        if (ngx_regex_exec_array(clcf->gzip_disable,
+                                 &r->headers_in.user_agent->value,
+                                 r->connection->log)
+            != NGX_DECLINED)
+        {
+            return NGX_DECLINED;
+        }
+    }
+
+#endif
+
+    r->gzip_ok = 1;
+
+    return NGX_OK;
+}
+
+
+/*
+ * gzip is enabled for the following quantities:
+ *     "gzip; q=0.001" ... "gzip; q=1.000"
+ * gzip is disabled for the following quantities:
+ *     "gzip; q=0" ... "gzip; q=0.000", and for any invalid cases
+ */
+
+static ngx_int_t
+ngx_http_gzip_accept_encoding(ngx_str_t *ae)
+{
+    u_char  *p, *start, *last;
+
+    start = ae->data;
+    last = start + ae->len;
+
+    for ( ;; ) {
+        p = ngx_strcasestrn(start, "gzip", 4 - 1);
+        if (p == NULL) {
+            return NGX_DECLINED;
+        }
+
+        if (p == start || (*(p - 1) == ',' || *(p - 1) == ' ')) {
+            break;
+        }
+
+        start = p + 4;
+    }
+
+    p += 4;
+
+    while (p < last) {
+        switch (*p++) {
+        case ',':
+            return NGX_OK;
+        case ';':
+            goto quantity;
+        case ' ':
+            continue;
+        default:
+            return NGX_DECLINED;
+        }
+    }
+
+    return NGX_OK;
+
+quantity:
+
+    while (p < last) {
+        switch (*p++) {
+        case 'q':
+        case 'Q':
+            goto equal;
+        case ' ':
+            continue;
+        default:
+            return NGX_DECLINED;
+        }
+    }
+
+    return NGX_OK;
+
+equal:
+
+    if (p + 2 > last || *p++ != '=') {
+        return NGX_DECLINED;
+    }
+
+    if (ngx_http_gzip_quantity(p, last) == 0) {
+        return NGX_DECLINED;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_uint_t
+ngx_http_gzip_quantity(u_char *p, u_char *last)
+{
+    u_char      c;
+    ngx_uint_t  n, q;
+
+    c = *p++;
+
+    if (c != '0' && c != '1') {
+        return 0;
+    }
+
+    q = (c - '0') * 100;
+
+    if (p == last) {
+        return q;
+    }
+
+    c = *p++;
+
+    if (c == ',' || c == ' ') {
+        return q;
+    }
+
+    if (c != '.') {
+        return 0;
+    }
+
+    n = 0;
+
+    while (p < last) {
+        c = *p++;
+
+        if (c == ',' || c == ' ') {
+            break;
+        }
+
+        if (c >= '0' && c <= '9') {
+            q += c - '0';
+            n++;
+            continue;
+        }
+
+        return 0;
+    }
+
+    if (q > 100 || n > 3) {
+        return 0;
+    }
+
+    return q;
+}
+
+#endif
+
+
+ngx_int_t
+ngx_http_subrequest(ngx_http_request_t *r,
+    ngx_str_t *uri, ngx_str_t *args, ngx_http_request_t **psr,
+    ngx_http_post_subrequest_t *ps, ngx_uint_t flags)
+{
+    ngx_time_t                    *tp;
+    ngx_connection_t              *c;
+    ngx_http_request_t            *sr;
+    ngx_http_core_srv_conf_t      *cscf;
+    ngx_http_postponed_request_t  *pr, *p;
+
+    if (r->subrequests == 0) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "subrequests cycle while processing \"%V\"", uri);
+        return NGX_ERROR;
+    }
+
+    /*
+     * 1000 is reserved for other purposes.
+     */
+    if (r->main->count >= 65535 - 1000) {
+        ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
+                      "request reference counter overflow "
+                      "while processing \"%V\"", uri);
+        return NGX_ERROR;
+    }
+
+    if (r->subrequest_in_memory) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "nested in-memory subrequest \"%V\"", uri);
+        return NGX_ERROR;
+    }
+
+    sr = ngx_pcalloc(r->pool, sizeof(ngx_http_request_t));
+    if (sr == NULL) {
+        return NGX_ERROR;
+    }
+
+    sr->signature = NGX_HTTP_MODULE;
+
+    c = r->connection;
+    sr->connection = c;
+
+    sr->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module);
+    if (sr->ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_list_init(&sr->headers_out.headers, r->pool, 20,
+                      sizeof(ngx_table_elt_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_list_init(&sr->headers_out.trailers, r->pool, 4,
+                      sizeof(ngx_table_elt_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+    sr->main_conf = cscf->ctx->main_conf;
+    sr->srv_conf = cscf->ctx->srv_conf;
+    sr->loc_conf = cscf->ctx->loc_conf;
+
+    sr->pool = r->pool;
+
+    sr->headers_in = r->headers_in;
+
+    ngx_http_clear_content_length(sr);
+    ngx_http_clear_accept_ranges(sr);
+    ngx_http_clear_last_modified(sr);
+
+    sr->request_body = r->request_body;
+
+#if (NGX_HTTP_V2)
+    sr->stream = r->stream;
+#endif
+
+    sr->method = NGX_HTTP_GET;
+    sr->http_version = r->http_version;
+
+    sr->request_line = r->request_line;
+    sr->uri = *uri;
+
+    if (args) {
+        sr->args = *args;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http subrequest \"%V?%V\"", uri, &sr->args);
+
+    sr->subrequest_in_memory = (flags & NGX_HTTP_SUBREQUEST_IN_MEMORY) != 0;
+    sr->waited = (flags & NGX_HTTP_SUBREQUEST_WAITED) != 0;
+    sr->background = (flags & NGX_HTTP_SUBREQUEST_BACKGROUND) != 0;
+
+    sr->unparsed_uri = r->unparsed_uri;
+    sr->method_name = ngx_http_core_get_method;
+    sr->http_protocol = r->http_protocol;
+
+    ngx_http_set_exten(sr);
+
+    sr->main = r->main;
+    sr->parent = r;
+    sr->post_subrequest = ps;
+    sr->read_event_handler = ngx_http_request_empty_handler;
+    sr->write_event_handler = ngx_http_handler;
+
+    sr->variables = r->variables;
+
+    sr->log_handler = r->log_handler;
+
+    if (sr->subrequest_in_memory) {
+        sr->filter_need_in_memory = 1;
+    }
+
+    if (!sr->background) {
+        if (c->data == r && r->postponed == NULL) {
+            c->data = sr;
+        }
+
+        pr = ngx_palloc(r->pool, sizeof(ngx_http_postponed_request_t));
+        if (pr == NULL) {
+            return NGX_ERROR;
+        }
+
+        pr->request = sr;
+        pr->out = NULL;
+        pr->next = NULL;
+
+        if (r->postponed) {
+            for (p = r->postponed; p->next; p = p->next) { /* void */ }
+            p->next = pr;
+
+        } else {
+            r->postponed = pr;
+        }
+    }
+
+    sr->internal = 1;
+
+    sr->discard_body = r->discard_body;
+    sr->expect_tested = 1;
+    sr->main_filter_need_in_memory = r->main_filter_need_in_memory;
+
+    sr->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1;
+    sr->subrequests = r->subrequests - 1;
+
+    tp = ngx_timeofday();
+    sr->start_sec = tp->sec;
+    sr->start_msec = tp->msec;
+
+    r->main->count++;
+
+    *psr = sr;
+
+    if (flags & NGX_HTTP_SUBREQUEST_CLONE) {
+        sr->method = r->method;
+        sr->method_name = r->method_name;
+        sr->loc_conf = r->loc_conf;
+        sr->valid_location = r->valid_location;
+        sr->valid_unparsed_uri = r->valid_unparsed_uri;
+        sr->content_handler = r->content_handler;
+        sr->phase_handler = r->phase_handler;
+        sr->write_event_handler = ngx_http_core_run_phases;
+
+        ngx_http_update_location_config(sr);
+    }
+
+    return ngx_http_post_request(sr, NULL);
+}
+
+
+ngx_int_t
+ngx_http_internal_redirect(ngx_http_request_t *r,
+    ngx_str_t *uri, ngx_str_t *args)
+{
+    ngx_http_core_srv_conf_t  *cscf;
+
+    r->uri_changes--;
+
+    if (r->uri_changes == 0) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "rewrite or internal redirection cycle "
+                      "while internally redirecting to \"%V\"", uri);
+
+        r->main->count++;
+        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NGX_DONE;
+    }
+
+    r->uri = *uri;
+
+    if (args) {
+        r->args = *args;
+
+    } else {
+        ngx_str_null(&r->args);
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "internal redirect: \"%V?%V\"", uri, &r->args);
+
+    ngx_http_set_exten(r);
+
+    /* clear the modules contexts */
+    ngx_memzero(r->ctx, sizeof(void *) * ngx_http_max_module);
+
+    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+    r->loc_conf = cscf->ctx->loc_conf;
+
+    ngx_http_update_location_config(r);
+
+#if (NGX_HTTP_CACHE)
+    r->cache = NULL;
+#endif
+
+    r->internal = 1;
+    r->valid_unparsed_uri = 0;
+    r->add_uri_to_alias = 0;
+    r->main->count++;
+
+    ngx_http_handler(r);
+
+    return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_named_location(ngx_http_request_t *r, ngx_str_t *name)
+{
+    ngx_http_core_srv_conf_t    *cscf;
+    ngx_http_core_loc_conf_t   **clcfp;
+    ngx_http_core_main_conf_t   *cmcf;
+
+    r->main->count++;
+    r->uri_changes--;
+
+    if (r->uri_changes == 0) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "rewrite or internal redirection cycle "
+                      "while redirect to named location \"%V\"", name);
+
+        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NGX_DONE;
+    }
+
+    if (r->uri.len == 0) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "empty URI in redirect to named location \"%V\"", name);
+
+        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NGX_DONE;
+    }
+
+    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+    if (cscf->named_locations) {
+
+        for (clcfp = cscf->named_locations; *clcfp; clcfp++) {
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "test location: \"%V\"", &(*clcfp)->name);
+
+            if (name->len != (*clcfp)->name.len
+                || ngx_strncmp(name->data, (*clcfp)->name.data, name->len) != 0)
+            {
+                continue;
+            }
+
+            ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "using location: %V \"%V?%V\"",
+                           name, &r->uri, &r->args);
+
+            r->internal = 1;
+            r->content_handler = NULL;
+            r->uri_changed = 0;
+            r->loc_conf = (*clcfp)->loc_conf;
+
+            /* clear the modules contexts */
+            ngx_memzero(r->ctx, sizeof(void *) * ngx_http_max_module);
+
+            ngx_http_update_location_config(r);
+
+            cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+            r->phase_handler = cmcf->phase_engine.location_rewrite_index;
+
+            r->write_event_handler = ngx_http_core_run_phases;
+            ngx_http_core_run_phases(r);
+
+            return NGX_DONE;
+        }
+    }
+
+    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                  "could not find named location \"%V\"", name);
+
+    ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+
+    return NGX_DONE;
+}
+
+
+ngx_http_cleanup_t *
+ngx_http_cleanup_add(ngx_http_request_t *r, size_t size)
+{
+    ngx_http_cleanup_t  *cln;
+
+    r = r->main;
+
+    cln = ngx_palloc(r->pool, sizeof(ngx_http_cleanup_t));
+    if (cln == NULL) {
+        return NULL;
+    }
+
+    if (size) {
+        cln->data = ngx_palloc(r->pool, size);
+        if (cln->data == NULL) {
+            return NULL;
+        }
+
+    } else {
+        cln->data = NULL;
+    }
+
+    cln->handler = NULL;
+    cln->next = r->cleanup;
+
+    r->cleanup = cln;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http cleanup add: %p", cln);
+
+    return cln;
+}
+
+
+ngx_int_t
+ngx_http_set_disable_symlinks(ngx_http_request_t *r,
+    ngx_http_core_loc_conf_t *clcf, ngx_str_t *path, ngx_open_file_info_t *of)
+{
+#if (NGX_HAVE_OPENAT)
+    u_char     *p;
+    ngx_str_t   from;
+
+    of->disable_symlinks = clcf->disable_symlinks;
+
+    if (clcf->disable_symlinks_from == NULL) {
+        return NGX_OK;
+    }
+
+    if (ngx_http_complex_value(r, clcf->disable_symlinks_from, &from)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (from.len == 0
+        || from.len > path->len
+        || ngx_memcmp(path->data, from.data, from.len) != 0)
+    {
+        return NGX_OK;
+    }
+
+    if (from.len == path->len) {
+        of->disable_symlinks = NGX_DISABLE_SYMLINKS_OFF;
+        return NGX_OK;
+    }
+
+    p = path->data + from.len;
+
+    if (*p == '/') {
+        of->disable_symlinks_from = from.len;
+        return NGX_OK;
+    }
+
+    p--;
+
+    if (*p == '/') {
+        of->disable_symlinks_from = from.len - 1;
+    }
+#endif
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_get_forwarded_addr(ngx_http_request_t *r, ngx_addr_t *addr,
+    ngx_array_t *headers, ngx_str_t *value, ngx_array_t *proxies,
+    int recursive)
+{
+    ngx_int_t          rc;
+    ngx_uint_t         i, found;
+    ngx_table_elt_t  **h;
+
+    if (headers == NULL) {
+        return ngx_http_get_forwarded_addr_internal(r, addr, value->data,
+                                                    value->len, proxies,
+                                                    recursive);
+    }
+
+    i = headers->nelts;
+    h = headers->elts;
+
+    rc = NGX_DECLINED;
+
+    found = 0;
+
+    while (i-- > 0) {
+        rc = ngx_http_get_forwarded_addr_internal(r, addr, h[i]->value.data,
+                                                  h[i]->value.len, proxies,
+                                                  recursive);
+
+        if (!recursive) {
+            break;
+        }
+
+        if (rc == NGX_DECLINED && found) {
+            rc = NGX_DONE;
+            break;
+        }
+
+        if (rc != NGX_OK) {
+            break;
+        }
+
+        found = 1;
+    }
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_http_get_forwarded_addr_internal(ngx_http_request_t *r, ngx_addr_t *addr,
+    u_char *xff, size_t xfflen, ngx_array_t *proxies, int recursive)
+{
+    u_char      *p;
+    ngx_int_t    rc;
+    ngx_addr_t   paddr;
+
+    if (ngx_cidr_match(addr->sockaddr, proxies) != NGX_OK) {
+        return NGX_DECLINED;
+    }
+
+    for (p = xff + xfflen - 1; p > xff; p--, xfflen--) {
+        if (*p != ' ' && *p != ',') {
+            break;
+        }
+    }
+
+    for ( /* void */ ; p > xff; p--) {
+        if (*p == ' ' || *p == ',') {
+            p++;
+            break;
+        }
+    }
+
+    if (ngx_parse_addr_port(r->pool, &paddr, p, xfflen - (p - xff)) != NGX_OK) {
+        return NGX_DECLINED;
+    }
+
+    *addr = paddr;
+
+    if (recursive && p > xff) {
+        rc = ngx_http_get_forwarded_addr_internal(r, addr, xff, p - 1 - xff,
+                                                  proxies, 1);
+
+        if (rc == NGX_DECLINED) {
+            return NGX_DONE;
+        }
+
+        /* rc == NGX_OK || rc == NGX_DONE  */
+        return rc;
+    }
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_http_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
+{
+    char                        *rv;
+    void                        *mconf;
+    ngx_uint_t                   i;
+    ngx_conf_t                   pcf;
+    ngx_http_module_t           *module;
+    struct sockaddr_in          *sin;
+    ngx_http_conf_ctx_t         *ctx, *http_ctx;
+    ngx_http_listen_opt_t        lsopt;
+    ngx_http_core_srv_conf_t    *cscf, **cscfp;
+    ngx_http_core_main_conf_t   *cmcf;
+
+    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
+    if (ctx == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    http_ctx = cf->ctx;
+    ctx->main_conf = http_ctx->main_conf;
+
+    /* the server{}'s srv_conf */
+
+    ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
+    if (ctx->srv_conf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    /* the server{}'s loc_conf */
+
+    ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
+    if (ctx->loc_conf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    for (i = 0; cf->cycle->modules[i]; i++) {
+        if (cf->cycle->modules[i]->type != NGX_HTTP_MODULE) {
+            continue;
+        }
+
+        module = cf->cycle->modules[i]->ctx;
+
+        if (module->create_srv_conf) {
+            mconf = module->create_srv_conf(cf);
+            if (mconf == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            ctx->srv_conf[cf->cycle->modules[i]->ctx_index] = mconf;
+        }
+
+        if (module->create_loc_conf) {
+            mconf = module->create_loc_conf(cf);
+            if (mconf == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            ctx->loc_conf[cf->cycle->modules[i]->ctx_index] = mconf;
+        }
+    }
+
+
+    /* the server configuration context */
+
+    cscf = ctx->srv_conf[ngx_http_core_module.ctx_index];
+    cscf->ctx = ctx;
+
+
+    cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
+
+    cscfp = ngx_array_push(&cmcf->servers);
+    if (cscfp == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    *cscfp = cscf;
+
+
+    /* parse inside server{} */
+
+    pcf = *cf;
+    cf->ctx = ctx;
+    cf->cmd_type = NGX_HTTP_SRV_CONF;
+
+    rv = ngx_conf_parse(cf, NULL);
+
+    *cf = pcf;
+
+    if (rv == NGX_CONF_OK && !cscf->listen) {
+        ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));
+
+        sin = &lsopt.sockaddr.sockaddr_in;
+
+        sin->sin_family = AF_INET;
+#if (NGX_WIN32)
+        sin->sin_port = htons(80);
+#else
+        sin->sin_port = htons((getuid() == 0) ? 80 : 8000);
+#endif
+        sin->sin_addr.s_addr = INADDR_ANY;
+
+        lsopt.socklen = sizeof(struct sockaddr_in);
+
+        lsopt.backlog = NGX_LISTEN_BACKLOG;
+        lsopt.rcvbuf = -1;
+        lsopt.sndbuf = -1;
+#if (NGX_HAVE_SETFIB)
+        lsopt.setfib = -1;
+#endif
+#if (NGX_HAVE_TCP_FASTOPEN)
+        lsopt.fastopen = -1;
+#endif
+        lsopt.wildcard = 1;
+
+        (void) ngx_sock_ntop(&lsopt.sockaddr.sockaddr, lsopt.socklen,
+                             lsopt.addr, NGX_SOCKADDR_STRLEN, 1);
+
+        if (ngx_http_add_listen(cf, cscf, &lsopt) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    return rv;
+}
+
+
+static char *
+ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
+{
+    char                      *rv;
+    u_char                    *mod;
+    size_t                     len;
+    ngx_str_t                 *value, *name;
+    ngx_uint_t                 i;
+    ngx_conf_t                 save;
+    ngx_http_module_t         *module;
+    ngx_http_conf_ctx_t       *ctx, *pctx;
+    ngx_http_core_loc_conf_t  *clcf, *pclcf;
+
+    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
+    if (ctx == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    pctx = cf->ctx;
+    ctx->main_conf = pctx->main_conf;
+    ctx->srv_conf = pctx->srv_conf;
+
+    ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
+    if (ctx->loc_conf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    for (i = 0; cf->cycle->modules[i]; i++) {
+        if (cf->cycle->modules[i]->type != NGX_HTTP_MODULE) {
+            continue;
+        }
+
+        module = cf->cycle->modules[i]->ctx;
+
+        if (module->create_loc_conf) {
+            ctx->loc_conf[cf->cycle->modules[i]->ctx_index] =
+                                                   module->create_loc_conf(cf);
+            if (ctx->loc_conf[cf->cycle->modules[i]->ctx_index] == NULL) {
+                return NGX_CONF_ERROR;
+            }
+        }
+    }
+
+    clcf = ctx->loc_conf[ngx_http_core_module.ctx_index];
+    clcf->loc_conf = ctx->loc_conf;
+
+    value = cf->args->elts;
+
+    if (cf->args->nelts == 3) {
+
+        len = value[1].len;
+        mod = value[1].data;
+        name = &value[2];
+
+        if (len == 1 && mod[0] == '=') {
+
+            clcf->name = *name;
+            clcf->exact_match = 1;
+
+        } else if (len == 2 && mod[0] == '^' && mod[1] == '~') {
+
+            clcf->name = *name;
+            clcf->noregex = 1;
+
+        } else if (len == 1 && mod[0] == '~') {
+
+            if (ngx_http_core_regex_location(cf, clcf, name, 0) != NGX_OK) {
+                return NGX_CONF_ERROR;
+            }
+
+        } else if (len == 2 && mod[0] == '~' && mod[1] == '*') {
+
+            if (ngx_http_core_regex_location(cf, clcf, name, 1) != NGX_OK) {
+                return NGX_CONF_ERROR;
+            }
+
+        } else {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid location modifier \"%V\"", &value[1]);
+            return NGX_CONF_ERROR;
+        }
+
+    } else {
+
+        name = &value[1];
+
+        if (name->data[0] == '=') {
+
+            clcf->name.len = name->len - 1;
+            clcf->name.data = name->data + 1;
+            clcf->exact_match = 1;
+
+        } else if (name->data[0] == '^' && name->data[1] == '~') {
+
+            clcf->name.len = name->len - 2;
+            clcf->name.data = name->data + 2;
+            clcf->noregex = 1;
+
+        } else if (name->data[0] == '~') {
+
+            name->len--;
+            name->data++;
+
+            if (name->data[0] == '*') {
+
+                name->len--;
+                name->data++;
+
+                if (ngx_http_core_regex_location(cf, clcf, name, 1) != NGX_OK) {
+                    return NGX_CONF_ERROR;
+                }
+
+            } else {
+                if (ngx_http_core_regex_location(cf, clcf, name, 0) != NGX_OK) {
+                    return NGX_CONF_ERROR;
+                }
+            }
+
+        } else {
+
+            clcf->name = *name;
+
+            if (name->data[0] == '@') {
+                clcf->named = 1;
+            }
+        }
+    }
+
+    pclcf = pctx->loc_conf[ngx_http_core_module.ctx_index];
+
+    if (cf->cmd_type == NGX_HTTP_LOC_CONF) {
+
+        /* nested location */
+
+#if 0
+        clcf->prev_location = pclcf;
+#endif
+
+        if (pclcf->exact_match) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "location \"%V\" cannot be inside "
+                               "the exact location \"%V\"",
+                               &clcf->name, &pclcf->name);
+            return NGX_CONF_ERROR;
+        }
+
+        if (pclcf->named) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "location \"%V\" cannot be inside "
+                               "the named location \"%V\"",
+                               &clcf->name, &pclcf->name);
+            return NGX_CONF_ERROR;
+        }
+
+        if (clcf->named) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "named location \"%V\" can be "
+                               "on the server level only",
+                               &clcf->name);
+            return NGX_CONF_ERROR;
+        }
+
+        len = pclcf->name.len;
+
+#if (NGX_PCRE)
+        if (clcf->regex == NULL
+            && ngx_filename_cmp(clcf->name.data, pclcf->name.data, len) != 0)
+#else
+        if (ngx_filename_cmp(clcf->name.data, pclcf->name.data, len) != 0)
+#endif
+        {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "location \"%V\" is outside location \"%V\"",
+                               &clcf->name, &pclcf->name);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (ngx_http_add_location(cf, &pclcf->locations, clcf) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    save = *cf;
+    cf->ctx = ctx;
+    cf->cmd_type = NGX_HTTP_LOC_CONF;
+
+    rv = ngx_conf_parse(cf, NULL);
+
+    *cf = save;
+
+    return rv;
+}
+
+
+static ngx_int_t
+ngx_http_core_regex_location(ngx_conf_t *cf, ngx_http_core_loc_conf_t *clcf,
+    ngx_str_t *regex, ngx_uint_t caseless)
+{
+#if (NGX_PCRE)
+    ngx_regex_compile_t  rc;
+    u_char               errstr[NGX_MAX_CONF_ERRSTR];
+
+    ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
+
+    rc.pattern = *regex;
+    rc.err.len = NGX_MAX_CONF_ERRSTR;
+    rc.err.data = errstr;
+
+#if (NGX_HAVE_CASELESS_FILESYSTEM)
+    rc.options = NGX_REGEX_CASELESS;
+#else
+    rc.options = caseless ? NGX_REGEX_CASELESS : 0;
+#endif
+
+    clcf->regex = ngx_http_regex_compile(cf, &rc);
+    if (clcf->regex == NULL) {
+        return NGX_ERROR;
+    }
+
+    clcf->name = *regex;
+
+    return NGX_OK;
+
+#else
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "using regex \"%V\" requires PCRE library",
+                       regex);
+    return NGX_ERROR;
+
+#endif
+}
+
+
+static char *
+ngx_http_core_types(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_loc_conf_t *clcf = conf;
+
+    char        *rv;
+    ngx_conf_t   save;
+
+    if (clcf->types == NULL) {
+        clcf->types = ngx_array_create(cf->pool, 64, sizeof(ngx_hash_key_t));
+        if (clcf->types == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    save = *cf;
+    cf->handler = ngx_http_core_type;
+    cf->handler_conf = conf;
+
+    rv = ngx_conf_parse(cf, NULL);
+
+    *cf = save;
+
+    return rv;
+}
+
+
+static char *
+ngx_http_core_type(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
+{
+    ngx_http_core_loc_conf_t *clcf = conf;
+
+    ngx_str_t       *value, *content_type, *old;
+    ngx_uint_t       i, n, hash;
+    ngx_hash_key_t  *type;
+
+    value = cf->args->elts;
+
+    if (ngx_strcmp(value[0].data, "include") == 0) {
+        if (cf->args->nelts != 2) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid number of arguments"
+                               " in \"include\" directive");
+            return NGX_CONF_ERROR;
+        }
+
+        return ngx_conf_include(cf, dummy, conf);
+    }
+
+    content_type = ngx_palloc(cf->pool, sizeof(ngx_str_t));
+    if (content_type == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    *content_type = value[0];
+
+    for (i = 1; i < cf->args->nelts; i++) {
+
+        hash = ngx_hash_strlow(value[i].data, value[i].data, value[i].len);
+
+        type = clcf->types->elts;
+        for (n = 0; n < clcf->types->nelts; n++) {
+            if (ngx_strcmp(value[i].data, type[n].key.data) == 0) {
+                old = type[n].value;
+                type[n].value = content_type;
+
+                ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                                   "duplicate extension \"%V\", "
+                                   "content type: \"%V\", "
+                                   "previous content type: \"%V\"",
+                                   &value[i], content_type, old);
+                goto next;
+            }
+        }
+
+
+        type = ngx_array_push(clcf->types);
+        if (type == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        type->key = value[i];
+        type->key_hash = hash;
+        type->value = content_type;
+
+    next:
+        continue;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_core_preconfiguration(ngx_conf_t *cf)
+{
+    return ngx_http_variables_add_core_vars(cf);
+}
+
+
+static ngx_int_t
+ngx_http_core_postconfiguration(ngx_conf_t *cf)
+{
+    ngx_http_top_request_body_filter = ngx_http_request_body_save_filter;
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_core_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_core_main_conf_t));
+    if (cmcf == NULL) {
+        return NULL;
+    }
+
+    if (ngx_array_init(&cmcf->servers, cf->pool, 4,
+                       sizeof(ngx_http_core_srv_conf_t *))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    cmcf->server_names_hash_max_size = NGX_CONF_UNSET_UINT;
+    cmcf->server_names_hash_bucket_size = NGX_CONF_UNSET_UINT;
+
+    cmcf->variables_hash_max_size = NGX_CONF_UNSET_UINT;
+    cmcf->variables_hash_bucket_size = NGX_CONF_UNSET_UINT;
+
+    return cmcf;
+}
+
+
+static char *
+ngx_http_core_init_main_conf(ngx_conf_t *cf, void *conf)
+{
+    ngx_http_core_main_conf_t *cmcf = conf;
+
+    ngx_conf_init_uint_value(cmcf->server_names_hash_max_size, 512);
+    ngx_conf_init_uint_value(cmcf->server_names_hash_bucket_size,
+                             ngx_cacheline_size);
+
+    cmcf->server_names_hash_bucket_size =
+            ngx_align(cmcf->server_names_hash_bucket_size, ngx_cacheline_size);
+
+
+    ngx_conf_init_uint_value(cmcf->variables_hash_max_size, 1024);
+    ngx_conf_init_uint_value(cmcf->variables_hash_bucket_size, 64);
+
+    cmcf->variables_hash_bucket_size =
+               ngx_align(cmcf->variables_hash_bucket_size, ngx_cacheline_size);
+
+    if (cmcf->ncaptures) {
+        cmcf->ncaptures = (cmcf->ncaptures + 1) * 3;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_http_core_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_http_core_srv_conf_t  *cscf;
+
+    cscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_core_srv_conf_t));
+    if (cscf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->client_large_buffers.num = 0;
+     */
+
+    if (ngx_array_init(&cscf->server_names, cf->temp_pool, 4,
+                       sizeof(ngx_http_server_name_t))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    cscf->connection_pool_size = NGX_CONF_UNSET_SIZE;
+    cscf->request_pool_size = NGX_CONF_UNSET_SIZE;
+    cscf->client_header_timeout = NGX_CONF_UNSET_MSEC;
+    cscf->client_header_buffer_size = NGX_CONF_UNSET_SIZE;
+    cscf->ignore_invalid_headers = NGX_CONF_UNSET;
+    cscf->merge_slashes = NGX_CONF_UNSET;
+    cscf->underscores_in_headers = NGX_CONF_UNSET;
+
+    return cscf;
+}
+
+
+static char *
+ngx_http_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_core_srv_conf_t *prev = parent;
+    ngx_http_core_srv_conf_t *conf = child;
+
+    ngx_str_t                name;
+    ngx_http_server_name_t  *sn;
+
+    /* TODO: it does not merge, it inits only */
+
+    ngx_conf_merge_size_value(conf->connection_pool_size,
+                              prev->connection_pool_size, 64 * sizeof(void *));
+    ngx_conf_merge_size_value(conf->request_pool_size,
+                              prev->request_pool_size, 4096);
+    ngx_conf_merge_msec_value(conf->client_header_timeout,
+                              prev->client_header_timeout, 60000);
+    ngx_conf_merge_size_value(conf->client_header_buffer_size,
+                              prev->client_header_buffer_size, 1024);
+    ngx_conf_merge_bufs_value(conf->large_client_header_buffers,
+                              prev->large_client_header_buffers,
+                              4, 8192);
+
+    if (conf->large_client_header_buffers.size < conf->connection_pool_size) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "the \"large_client_header_buffers\" size must be "
+                           "equal to or greater than \"connection_pool_size\"");
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_conf_merge_value(conf->ignore_invalid_headers,
+                              prev->ignore_invalid_headers, 1);
+
+    ngx_conf_merge_value(conf->merge_slashes, prev->merge_slashes, 1);
+
+    ngx_conf_merge_value(conf->underscores_in_headers,
+                              prev->underscores_in_headers, 0);
+
+    if (conf->server_names.nelts == 0) {
+        /* the array has 4 empty preallocated elements, so push cannot fail */
+        sn = ngx_array_push(&conf->server_names);
+#if (NGX_PCRE)
+        sn->regex = NULL;
+#endif
+        sn->server = conf;
+        ngx_str_set(&sn->name, "");
+    }
+
+    sn = conf->server_names.elts;
+    name = sn[0].name;
+
+#if (NGX_PCRE)
+    if (sn->regex) {
+        name.len++;
+        name.data--;
+    } else
+#endif
+
+    if (name.data[0] == '.') {
+        name.len--;
+        name.data++;
+    }
+
+    conf->server_name.len = name.len;
+    conf->server_name.data = ngx_pstrdup(cf->pool, &name);
+    if (conf->server_name.data == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_http_core_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_core_loc_conf_t  *clcf;
+
+    clcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_core_loc_conf_t));
+    if (clcf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     clcf->root = { 0, NULL };
+     *     clcf->limit_except = 0;
+     *     clcf->post_action = { 0, NULL };
+     *     clcf->types = NULL;
+     *     clcf->default_type = { 0, NULL };
+     *     clcf->error_log = NULL;
+     *     clcf->error_pages = NULL;
+     *     clcf->client_body_path = NULL;
+     *     clcf->regex = NULL;
+     *     clcf->exact_match = 0;
+     *     clcf->auto_redirect = 0;
+     *     clcf->alias = 0;
+     *     clcf->gzip_proxied = 0;
+     *     clcf->keepalive_disable = 0;
+     */
+
+    clcf->client_max_body_size = NGX_CONF_UNSET;
+    clcf->client_body_buffer_size = NGX_CONF_UNSET_SIZE;
+    clcf->client_body_timeout = NGX_CONF_UNSET_MSEC;
+    clcf->satisfy = NGX_CONF_UNSET_UINT;
+    clcf->if_modified_since = NGX_CONF_UNSET_UINT;
+    clcf->max_ranges = NGX_CONF_UNSET_UINT;
+    clcf->client_body_in_file_only = NGX_CONF_UNSET_UINT;
+    clcf->client_body_in_single_buffer = NGX_CONF_UNSET;
+    clcf->internal = NGX_CONF_UNSET;
+    clcf->sendfile = NGX_CONF_UNSET;
+    clcf->sendfile_max_chunk = NGX_CONF_UNSET_SIZE;
+    clcf->subrequest_output_buffer_size = NGX_CONF_UNSET_SIZE;
+    clcf->aio = NGX_CONF_UNSET;
+    clcf->aio_write = NGX_CONF_UNSET;
+#if (NGX_THREADS)
+    clcf->thread_pool = NGX_CONF_UNSET_PTR;
+    clcf->thread_pool_value = NGX_CONF_UNSET_PTR;
+#endif
+    clcf->read_ahead = NGX_CONF_UNSET_SIZE;
+    clcf->directio = NGX_CONF_UNSET;
+    clcf->directio_alignment = NGX_CONF_UNSET;
+    clcf->tcp_nopush = NGX_CONF_UNSET;
+    clcf->tcp_nodelay = NGX_CONF_UNSET;
+    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_SIZE;
+    clcf->limit_rate_after = NGX_CONF_UNSET_SIZE;
+    clcf->keepalive_timeout = NGX_CONF_UNSET_MSEC;
+    clcf->keepalive_header = NGX_CONF_UNSET;
+    clcf->keepalive_requests = NGX_CONF_UNSET_UINT;
+    clcf->lingering_close = NGX_CONF_UNSET_UINT;
+    clcf->lingering_time = NGX_CONF_UNSET_MSEC;
+    clcf->lingering_timeout = NGX_CONF_UNSET_MSEC;
+    clcf->resolver_timeout = NGX_CONF_UNSET_MSEC;
+    clcf->reset_timedout_connection = NGX_CONF_UNSET;
+    clcf->absolute_redirect = NGX_CONF_UNSET;
+    clcf->server_name_in_redirect = NGX_CONF_UNSET;
+    clcf->port_in_redirect = NGX_CONF_UNSET;
+    clcf->msie_padding = NGX_CONF_UNSET;
+    clcf->msie_refresh = NGX_CONF_UNSET;
+    clcf->log_not_found = NGX_CONF_UNSET;
+    clcf->log_subrequest = NGX_CONF_UNSET;
+    clcf->recursive_error_pages = NGX_CONF_UNSET;
+    clcf->chunked_transfer_encoding = NGX_CONF_UNSET;
+    clcf->etag = NGX_CONF_UNSET;
+    clcf->server_tokens = NGX_CONF_UNSET_UINT;
+    clcf->types_hash_max_size = NGX_CONF_UNSET_UINT;
+    clcf->types_hash_bucket_size = NGX_CONF_UNSET_UINT;
+
+    clcf->open_file_cache = NGX_CONF_UNSET_PTR;
+    clcf->open_file_cache_valid = NGX_CONF_UNSET;
+    clcf->open_file_cache_min_uses = NGX_CONF_UNSET_UINT;
+    clcf->open_file_cache_errors = NGX_CONF_UNSET;
+    clcf->open_file_cache_events = NGX_CONF_UNSET;
+
+#if (NGX_HTTP_GZIP)
+    clcf->gzip_vary = NGX_CONF_UNSET;
+    clcf->gzip_http_version = NGX_CONF_UNSET_UINT;
+#if (NGX_PCRE)
+    clcf->gzip_disable = NGX_CONF_UNSET_PTR;
+#endif
+    clcf->gzip_disable_msie6 = 3;
+#if (NGX_HTTP_DEGRADATION)
+    clcf->gzip_disable_degradation = 3;
+#endif
+#endif
+
+#if (NGX_HAVE_OPENAT)
+    clcf->disable_symlinks = NGX_CONF_UNSET_UINT;
+    clcf->disable_symlinks_from = NGX_CONF_UNSET_PTR;
+#endif
+
+    return clcf;
+}
+
+
+static ngx_str_t  ngx_http_core_text_html_type = ngx_string("text/html");
+static ngx_str_t  ngx_http_core_image_gif_type = ngx_string("image/gif");
+static ngx_str_t  ngx_http_core_image_jpeg_type = ngx_string("image/jpeg");
+
+static ngx_hash_key_t  ngx_http_core_default_types[] = {
+    { ngx_string("html"), 0, &ngx_http_core_text_html_type },
+    { ngx_string("gif"), 0, &ngx_http_core_image_gif_type },
+    { ngx_string("jpg"), 0, &ngx_http_core_image_jpeg_type },
+    { ngx_null_string, 0, NULL }
+};
+
+
+static char *
+ngx_http_core_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_core_loc_conf_t *prev = parent;
+    ngx_http_core_loc_conf_t *conf = child;
+
+    ngx_uint_t        i;
+    ngx_hash_key_t   *type;
+    ngx_hash_init_t   types_hash;
+
+    if (conf->root.data == NULL) {
+
+        conf->alias = prev->alias;
+        conf->root = prev->root;
+        conf->root_lengths = prev->root_lengths;
+        conf->root_values = prev->root_values;
+
+        if (prev->root.data == NULL) {
+            ngx_str_set(&conf->root, "html");
+
+            if (ngx_conf_full_name(cf->cycle, &conf->root, 0) != NGX_OK) {
+                return NGX_CONF_ERROR;
+            }
+        }
+    }
+
+    if (conf->post_action.data == NULL) {
+        conf->post_action = prev->post_action;
+    }
+
+    ngx_conf_merge_uint_value(conf->types_hash_max_size,
+                              prev->types_hash_max_size, 1024);
+
+    ngx_conf_merge_uint_value(conf->types_hash_bucket_size,
+                              prev->types_hash_bucket_size, 64);
+
+    conf->types_hash_bucket_size = ngx_align(conf->types_hash_bucket_size,
+                                             ngx_cacheline_size);
+
+    /*
+     * the special handling of the "types" directive in the "http" section
+     * to inherit the http's conf->types_hash to all servers
+     */
+
+    if (prev->types && prev->types_hash.buckets == NULL) {
+
+        types_hash.hash = &prev->types_hash;
+        types_hash.key = ngx_hash_key_lc;
+        types_hash.max_size = conf->types_hash_max_size;
+        types_hash.bucket_size = conf->types_hash_bucket_size;
+        types_hash.name = "types_hash";
+        types_hash.pool = cf->pool;
+        types_hash.temp_pool = NULL;
+
+        if (ngx_hash_init(&types_hash, prev->types->elts, prev->types->nelts)
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (conf->types == NULL) {
+        conf->types = prev->types;
+        conf->types_hash = prev->types_hash;
+    }
+
+    if (conf->types == NULL) {
+        conf->types = ngx_array_create(cf->pool, 3, sizeof(ngx_hash_key_t));
+        if (conf->types == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        for (i = 0; ngx_http_core_default_types[i].key.len; i++) {
+            type = ngx_array_push(conf->types);
+            if (type == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            type->key = ngx_http_core_default_types[i].key;
+            type->key_hash =
+                       ngx_hash_key_lc(ngx_http_core_default_types[i].key.data,
+                                       ngx_http_core_default_types[i].key.len);
+            type->value = ngx_http_core_default_types[i].value;
+        }
+    }
+
+    if (conf->types_hash.buckets == NULL) {
+
+        types_hash.hash = &conf->types_hash;
+        types_hash.key = ngx_hash_key_lc;
+        types_hash.max_size = conf->types_hash_max_size;
+        types_hash.bucket_size = conf->types_hash_bucket_size;
+        types_hash.name = "types_hash";
+        types_hash.pool = cf->pool;
+        types_hash.temp_pool = NULL;
+
+        if (ngx_hash_init(&types_hash, conf->types->elts, conf->types->nelts)
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (conf->error_log == NULL) {
+        if (prev->error_log) {
+            conf->error_log = prev->error_log;
+        } else {
+            conf->error_log = &cf->cycle->new_log;
+        }
+    }
+
+    if (conf->error_pages == NULL && prev->error_pages) {
+        conf->error_pages = prev->error_pages;
+    }
+
+    ngx_conf_merge_str_value(conf->default_type,
+                              prev->default_type, "text/plain");
+
+    ngx_conf_merge_off_value(conf->client_max_body_size,
+                              prev->client_max_body_size, 1 * 1024 * 1024);
+    ngx_conf_merge_size_value(conf->client_body_buffer_size,
+                              prev->client_body_buffer_size,
+                              (size_t) 2 * ngx_pagesize);
+    ngx_conf_merge_msec_value(conf->client_body_timeout,
+                              prev->client_body_timeout, 60000);
+
+    ngx_conf_merge_bitmask_value(conf->keepalive_disable,
+                              prev->keepalive_disable,
+                              (NGX_CONF_BITMASK_SET
+                               |NGX_HTTP_KEEPALIVE_DISABLE_MSIE6));
+    ngx_conf_merge_uint_value(conf->satisfy, prev->satisfy,
+                              NGX_HTTP_SATISFY_ALL);
+    ngx_conf_merge_uint_value(conf->if_modified_since, prev->if_modified_since,
+                              NGX_HTTP_IMS_EXACT);
+    ngx_conf_merge_uint_value(conf->max_ranges, prev->max_ranges,
+                              NGX_MAX_INT32_VALUE);
+    ngx_conf_merge_uint_value(conf->client_body_in_file_only,
+                              prev->client_body_in_file_only,
+                              NGX_HTTP_REQUEST_BODY_FILE_OFF);
+    ngx_conf_merge_value(conf->client_body_in_single_buffer,
+                              prev->client_body_in_single_buffer, 0);
+    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);
+    ngx_conf_merge_size_value(conf->subrequest_output_buffer_size,
+                              prev->subrequest_output_buffer_size,
+                              (size_t) ngx_pagesize);
+    ngx_conf_merge_value(conf->aio, prev->aio, NGX_HTTP_AIO_OFF);
+    ngx_conf_merge_value(conf->aio_write, prev->aio_write, 0);
+#if (NGX_THREADS)
+    ngx_conf_merge_ptr_value(conf->thread_pool, prev->thread_pool, NULL);
+    ngx_conf_merge_ptr_value(conf->thread_pool_value, prev->thread_pool_value,
+                             NULL);
+#endif
+    ngx_conf_merge_size_value(conf->read_ahead, prev->read_ahead, 0);
+    ngx_conf_merge_off_value(conf->directio, prev->directio,
+                              NGX_OPEN_FILE_DIRECTIO_OFF);
+    ngx_conf_merge_off_value(conf->directio_alignment, prev->directio_alignment,
+                              512);
+    ngx_conf_merge_value(conf->tcp_nopush, prev->tcp_nopush, 0);
+    ngx_conf_merge_value(conf->tcp_nodelay, prev->tcp_nodelay, 1);
+
+    ngx_conf_merge_msec_value(conf->send_timeout, prev->send_timeout, 60000);
+    ngx_conf_merge_size_value(conf->send_lowat, prev->send_lowat, 0);
+    ngx_conf_merge_size_value(conf->postpone_output, prev->postpone_output,
+                              1460);
+    ngx_conf_merge_size_value(conf->limit_rate, prev->limit_rate, 0);
+    ngx_conf_merge_size_value(conf->limit_rate_after, prev->limit_rate_after,
+                              0);
+    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);
+    ngx_conf_merge_uint_value(conf->lingering_close,
+                              prev->lingering_close, NGX_HTTP_LINGERING_ON);
+    ngx_conf_merge_msec_value(conf->lingering_time,
+                              prev->lingering_time, 30000);
+    ngx_conf_merge_msec_value(conf->lingering_timeout,
+                              prev->lingering_timeout, 5000);
+    ngx_conf_merge_msec_value(conf->resolver_timeout,
+                              prev->resolver_timeout, 30000);
+
+    if (conf->resolver == NULL) {
+
+        if (prev->resolver == NULL) {
+
+            /*
+             * create dummy resolver in http {} context
+             * to inherit it in all servers
+             */
+
+            prev->resolver = ngx_resolver_create(cf, NULL, 0);
+            if (prev->resolver == NULL) {
+                return NGX_CONF_ERROR;
+            }
+        }
+
+        conf->resolver = prev->resolver;
+    }
+
+    if (ngx_conf_merge_path_value(cf, &conf->client_body_temp_path,
+                              prev->client_body_temp_path,
+                              &ngx_http_client_temp_path)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_conf_merge_value(conf->reset_timedout_connection,
+                              prev->reset_timedout_connection, 0);
+    ngx_conf_merge_value(conf->absolute_redirect,
+                              prev->absolute_redirect, 1);
+    ngx_conf_merge_value(conf->server_name_in_redirect,
+                              prev->server_name_in_redirect, 0);
+    ngx_conf_merge_value(conf->port_in_redirect, prev->port_in_redirect, 1);
+    ngx_conf_merge_value(conf->msie_padding, prev->msie_padding, 1);
+    ngx_conf_merge_value(conf->msie_refresh, prev->msie_refresh, 0);
+    ngx_conf_merge_value(conf->log_not_found, prev->log_not_found, 1);
+    ngx_conf_merge_value(conf->log_subrequest, prev->log_subrequest, 0);
+    ngx_conf_merge_value(conf->recursive_error_pages,
+                              prev->recursive_error_pages, 0);
+    ngx_conf_merge_value(conf->chunked_transfer_encoding,
+                              prev->chunked_transfer_encoding, 1);
+    ngx_conf_merge_value(conf->etag, prev->etag, 1);
+
+    ngx_conf_merge_uint_value(conf->server_tokens, prev->server_tokens,
+                              NGX_HTTP_SERVER_TOKENS_ON);
+
+    ngx_conf_merge_ptr_value(conf->open_file_cache,
+                              prev->open_file_cache, NULL);
+
+    ngx_conf_merge_sec_value(conf->open_file_cache_valid,
+                              prev->open_file_cache_valid, 60);
+
+    ngx_conf_merge_uint_value(conf->open_file_cache_min_uses,
+                              prev->open_file_cache_min_uses, 1);
+
+    ngx_conf_merge_sec_value(conf->open_file_cache_errors,
+                              prev->open_file_cache_errors, 0);
+
+    ngx_conf_merge_sec_value(conf->open_file_cache_events,
+                              prev->open_file_cache_events, 0);
+#if (NGX_HTTP_GZIP)
+
+    ngx_conf_merge_value(conf->gzip_vary, prev->gzip_vary, 0);
+    ngx_conf_merge_uint_value(conf->gzip_http_version, prev->gzip_http_version,
+                              NGX_HTTP_VERSION_11);
+    ngx_conf_merge_bitmask_value(conf->gzip_proxied, prev->gzip_proxied,
+                              (NGX_CONF_BITMASK_SET|NGX_HTTP_GZIP_PROXIED_OFF));
+
+#if (NGX_PCRE)
+    ngx_conf_merge_ptr_value(conf->gzip_disable, prev->gzip_disable, NULL);
+#endif
+
+    if (conf->gzip_disable_msie6 == 3) {
+        conf->gzip_disable_msie6 =
+            (prev->gzip_disable_msie6 == 3) ? 0 : prev->gzip_disable_msie6;
+    }
+
+#if (NGX_HTTP_DEGRADATION)
+
+    if (conf->gzip_disable_degradation == 3) {
+        conf->gzip_disable_degradation =
+            (prev->gzip_disable_degradation == 3) ?
+                 0 : prev->gzip_disable_degradation;
+    }
+
+#endif
+#endif
+
+#if (NGX_HAVE_OPENAT)
+    ngx_conf_merge_uint_value(conf->disable_symlinks, prev->disable_symlinks,
+                              NGX_DISABLE_SYMLINKS_OFF);
+    ngx_conf_merge_ptr_value(conf->disable_symlinks_from,
+                             prev->disable_symlinks_from, NULL);
+#endif
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_srv_conf_t *cscf = conf;
+
+    ngx_str_t              *value, size;
+    ngx_url_t               u;
+    ngx_uint_t              n;
+    ngx_http_listen_opt_t   lsopt;
+
+    cscf->listen = 1;
+
+    value = cf->args->elts;
+
+    ngx_memzero(&u, sizeof(ngx_url_t));
+
+    u.url = value[1];
+    u.listen = 1;
+    u.default_port = 80;
+
+    if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
+        if (u.err) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "%s in \"%V\" of the \"listen\" directive",
+                               u.err, &u.url);
+        }
+
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));
+
+    ngx_memcpy(&lsopt.sockaddr.sockaddr, &u.sockaddr, u.socklen);
+
+    lsopt.socklen = u.socklen;
+    lsopt.backlog = NGX_LISTEN_BACKLOG;
+    lsopt.rcvbuf = -1;
+    lsopt.sndbuf = -1;
+#if (NGX_HAVE_SETFIB)
+    lsopt.setfib = -1;
+#endif
+#if (NGX_HAVE_TCP_FASTOPEN)
+    lsopt.fastopen = -1;
+#endif
+    lsopt.wildcard = u.wildcard;
+#if (NGX_HAVE_INET6)
+    lsopt.ipv6only = 1;
+#endif
+
+    (void) ngx_sock_ntop(&lsopt.sockaddr.sockaddr, lsopt.socklen, lsopt.addr,
+                         NGX_SOCKADDR_STRLEN, 1);
+
+    for (n = 2; n < cf->args->nelts; n++) {
+
+        if (ngx_strcmp(value[n].data, "default_server") == 0
+            || ngx_strcmp(value[n].data, "default") == 0)
+        {
+            lsopt.default_server = 1;
+            continue;
+        }
+
+        if (ngx_strcmp(value[n].data, "bind") == 0) {
+            lsopt.set = 1;
+            lsopt.bind = 1;
+            continue;
+        }
+
+#if (NGX_HAVE_SETFIB)
+        if (ngx_strncmp(value[n].data, "setfib=", 7) == 0) {
+            lsopt.setfib = ngx_atoi(value[n].data + 7, value[n].len - 7);
+            lsopt.set = 1;
+            lsopt.bind = 1;
+
+            if (lsopt.setfib == NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid setfib \"%V\"", &value[n]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+#endif
+
+#if (NGX_HAVE_TCP_FASTOPEN)
+        if (ngx_strncmp(value[n].data, "fastopen=", 9) == 0) {
+            lsopt.fastopen = ngx_atoi(value[n].data + 9, value[n].len - 9);
+            lsopt.set = 1;
+            lsopt.bind = 1;
+
+            if (lsopt.fastopen == NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid fastopen \"%V\"", &value[n]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+#endif
+
+        if (ngx_strncmp(value[n].data, "backlog=", 8) == 0) {
+            lsopt.backlog = ngx_atoi(value[n].data + 8, value[n].len - 8);
+            lsopt.set = 1;
+            lsopt.bind = 1;
+
+            if (lsopt.backlog == NGX_ERROR || lsopt.backlog == 0) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid backlog \"%V\"", &value[n]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[n].data, "rcvbuf=", 7) == 0) {
+            size.len = value[n].len - 7;
+            size.data = value[n].data + 7;
+
+            lsopt.rcvbuf = ngx_parse_size(&size);
+            lsopt.set = 1;
+            lsopt.bind = 1;
+
+            if (lsopt.rcvbuf == NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid rcvbuf \"%V\"", &value[n]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[n].data, "sndbuf=", 7) == 0) {
+            size.len = value[n].len - 7;
+            size.data = value[n].data + 7;
+
+            lsopt.sndbuf = ngx_parse_size(&size);
+            lsopt.set = 1;
+            lsopt.bind = 1;
+
+            if (lsopt.sndbuf == NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid sndbuf \"%V\"", &value[n]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[n].data, "accept_filter=", 14) == 0) {
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
+            lsopt.accept_filter = (char *) &value[n].data[14];
+            lsopt.set = 1;
+            lsopt.bind = 1;
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "accept filters \"%V\" are not supported "
+                               "on this platform, ignored",
+                               &value[n]);
+#endif
+            continue;
+        }
+
+        if (ngx_strcmp(value[n].data, "deferred") == 0) {
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
+            lsopt.deferred_accept = 1;
+            lsopt.set = 1;
+            lsopt.bind = 1;
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "the deferred accept is not supported "
+                               "on this platform, ignored");
+#endif
+            continue;
+        }
+
+        if (ngx_strncmp(value[n].data, "ipv6only=o", 10) == 0) {
+#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
+            struct sockaddr  *sa;
+
+            sa = &lsopt.sockaddr.sockaddr;
+
+            if (sa->sa_family == AF_INET6) {
+
+                if (ngx_strcmp(&value[n].data[10], "n") == 0) {
+                    lsopt.ipv6only = 1;
+
+                } else if (ngx_strcmp(&value[n].data[10], "ff") == 0) {
+                    lsopt.ipv6only = 0;
+
+                } else {
+                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                       "invalid ipv6only flags \"%s\"",
+                                       &value[n].data[9]);
+                    return NGX_CONF_ERROR;
+                }
+
+                lsopt.set = 1;
+                lsopt.bind = 1;
+
+            } else {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "ipv6only is not supported "
+                                   "on addr \"%s\", ignored", lsopt.addr);
+            }
+
+            continue;
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "ipv6only is not supported "
+                               "on this platform");
+            return NGX_CONF_ERROR;
+#endif
+        }
+
+        if (ngx_strcmp(value[n].data, "reuseport") == 0) {
+#if (NGX_HAVE_REUSEPORT)
+            lsopt.reuseport = 1;
+            lsopt.set = 1;
+            lsopt.bind = 1;
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "reuseport is not supported "
+                               "on this platform, ignored");
+#endif
+            continue;
+        }
+
+        if (ngx_strcmp(value[n].data, "ssl") == 0) {
+#if (NGX_HTTP_SSL)
+            lsopt.ssl = 1;
+            continue;
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "the \"ssl\" parameter requires "
+                               "ngx_http_ssl_module");
+            return NGX_CONF_ERROR;
+#endif
+        }
+
+        if (ngx_strcmp(value[n].data, "http2") == 0) {
+#if (NGX_HTTP_V2)
+            lsopt.http2 = 1;
+            continue;
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "the \"http2\" parameter requires "
+                               "ngx_http_v2_module");
+            return NGX_CONF_ERROR;
+#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) {
+                lsopt.so_keepalive = 1;
+
+            } else if (ngx_strcmp(&value[n].data[13], "off") == 0) {
+                lsopt.so_keepalive = 2;
+
+            } else {
+
+#if (NGX_HAVE_KEEPALIVE_TUNABLE)
+                u_char     *p, *end;
+                ngx_str_t   s;
+
+                end = value[n].data + value[n].len;
+                s.data = value[n].data + 13;
+
+                p = ngx_strlchr(s.data, end, ':');
+                if (p == NULL) {
+                    p = end;
+                }
+
+                if (p > s.data) {
+                    s.len = p - s.data;
+
+                    lsopt.tcp_keepidle = ngx_parse_time(&s, 1);
+                    if (lsopt.tcp_keepidle == (time_t) NGX_ERROR) {
+                        goto invalid_so_keepalive;
+                    }
+                }
+
+                s.data = (p < end) ? (p + 1) : end;
+
+                p = ngx_strlchr(s.data, end, ':');
+                if (p == NULL) {
+                    p = end;
+                }
+
+                if (p > s.data) {
+                    s.len = p - s.data;
+
+                    lsopt.tcp_keepintvl = ngx_parse_time(&s, 1);
+                    if (lsopt.tcp_keepintvl == (time_t) NGX_ERROR) {
+                        goto invalid_so_keepalive;
+                    }
+                }
+
+                s.data = (p < end) ? (p + 1) : end;
+
+                if (s.data < end) {
+                    s.len = end - s.data;
+
+                    lsopt.tcp_keepcnt = ngx_atoi(s.data, s.len);
+                    if (lsopt.tcp_keepcnt == NGX_ERROR) {
+                        goto invalid_so_keepalive;
+                    }
+                }
+
+                if (lsopt.tcp_keepidle == 0 && lsopt.tcp_keepintvl == 0
+                    && lsopt.tcp_keepcnt == 0)
+                {
+                    goto invalid_so_keepalive;
+                }
+
+                lsopt.so_keepalive = 1;
+
+#else
+
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "the \"so_keepalive\" parameter accepts "
+                                   "only \"on\" or \"off\" on this platform");
+                return NGX_CONF_ERROR;
+
+#endif
+            }
+
+            lsopt.set = 1;
+            lsopt.bind = 1;
+
+            continue;
+
+#if (NGX_HAVE_KEEPALIVE_TUNABLE)
+        invalid_so_keepalive:
+
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid so_keepalive value: \"%s\"",
+                               &value[n].data[13]);
+            return NGX_CONF_ERROR;
+#endif
+        }
+
+        if (ngx_strcmp(value[n].data, "proxy_protocol") == 0) {
+            lsopt.proxy_protocol = 1;
+            continue;
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[n]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (ngx_http_add_listen(cf, cscf, &lsopt) == NGX_OK) {
+        return NGX_CONF_OK;
+    }
+
+    return NGX_CONF_ERROR;
+}
+
+
+static char *
+ngx_http_core_server_name(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_srv_conf_t *cscf = conf;
+
+    u_char                   ch;
+    ngx_str_t               *value;
+    ngx_uint_t               i;
+    ngx_http_server_name_t  *sn;
+
+    value = cf->args->elts;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+
+        ch = value[i].data[0];
+
+        if ((ch == '*' && (value[i].len < 3 || value[i].data[1] != '.'))
+            || (ch == '.' && value[i].len < 2))
+        {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "server name \"%V\" is invalid", &value[i]);
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_strchr(value[i].data, '/')) {
+            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                               "server name \"%V\" has suspicious symbols",
+                               &value[i]);
+        }
+
+        sn = ngx_array_push(&cscf->server_names);
+        if (sn == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+#if (NGX_PCRE)
+        sn->regex = NULL;
+#endif
+        sn->server = cscf;
+
+        if (ngx_strcasecmp(value[i].data, (u_char *) "$hostname") == 0) {
+            sn->name = cf->cycle->hostname;
+
+        } else {
+            sn->name = value[i];
+        }
+
+        if (value[i].data[0] != '~') {
+            ngx_strlow(sn->name.data, sn->name.data, sn->name.len);
+            continue;
+        }
+
+#if (NGX_PCRE)
+        {
+        u_char               *p;
+        ngx_regex_compile_t   rc;
+        u_char                errstr[NGX_MAX_CONF_ERRSTR];
+
+        if (value[i].len == 1) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "empty regex in server name \"%V\"", &value[i]);
+            return NGX_CONF_ERROR;
+        }
+
+        value[i].len--;
+        value[i].data++;
+
+        ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
+
+        rc.pattern = value[i];
+        rc.err.len = NGX_MAX_CONF_ERRSTR;
+        rc.err.data = errstr;
+
+        for (p = value[i].data; p < value[i].data + value[i].len; p++) {
+            if (*p >= 'A' && *p <= 'Z') {
+                rc.options = NGX_REGEX_CASELESS;
+                break;
+            }
+        }
+
+        sn->regex = ngx_http_regex_compile(cf, &rc);
+        if (sn->regex == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        sn->name = value[i];
+        cscf->captures = (rc.captures > 0);
+        }
+#else
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "using regex \"%V\" "
+                           "requires PCRE library", &value[i]);
+
+        return NGX_CONF_ERROR;
+#endif
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_core_root(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_loc_conf_t *clcf = conf;
+
+    ngx_str_t                  *value;
+    ngx_int_t                   alias;
+    ngx_uint_t                  n;
+    ngx_http_script_compile_t   sc;
+
+    alias = (cmd->name.len == sizeof("alias") - 1) ? 1 : 0;
+
+    if (clcf->root.data) {
+
+        if ((clcf->alias != 0) == alias) {
+            return "is duplicate";
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"%V\" directive is duplicate, "
+                           "\"%s\" directive was specified earlier",
+                           &cmd->name, clcf->alias ? "alias" : "root");
+
+        return NGX_CONF_ERROR;
+    }
+
+    if (clcf->named && alias) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "the \"alias\" directive cannot be used "
+                           "inside the named location");
+
+        return NGX_CONF_ERROR;
+    }
+
+    value = cf->args->elts;
+
+    if (ngx_strstr(value[1].data, "$document_root")
+        || ngx_strstr(value[1].data, "${document_root}"))
+    {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "the $document_root variable cannot be used "
+                           "in the \"%V\" directive",
+                           &cmd->name);
+
+        return NGX_CONF_ERROR;
+    }
+
+    if (ngx_strstr(value[1].data, "$realpath_root")
+        || ngx_strstr(value[1].data, "${realpath_root}"))
+    {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "the $realpath_root variable cannot be used "
+                           "in the \"%V\" directive",
+                           &cmd->name);
+
+        return NGX_CONF_ERROR;
+    }
+
+    clcf->alias = alias ? clcf->name.len : 0;
+    clcf->root = value[1];
+
+    if (!alias && clcf->root.len > 0
+        && clcf->root.data[clcf->root.len - 1] == '/')
+    {
+        clcf->root.len--;
+    }
+
+    if (clcf->root.data[0] != '$') {
+        if (ngx_conf_full_name(cf->cycle, &clcf->root, 0) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    n = ngx_http_script_variables_count(&clcf->root);
+
+    ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+    sc.variables = n;
+
+#if (NGX_PCRE)
+    if (alias && clcf->regex) {
+        clcf->alias = NGX_MAX_SIZE_T_VALUE;
+        n = 1;
+    }
+#endif
+
+    if (n) {
+        sc.cf = cf;
+        sc.source = &clcf->root;
+        sc.lengths = &clcf->root_lengths;
+        sc.values = &clcf->root_values;
+        sc.complete_lengths = 1;
+        sc.complete_values = 1;
+
+        if (ngx_http_script_compile(&sc) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_http_method_name_t  ngx_methods_names[] = {
+    { (u_char *) "GET",       (uint32_t) ~NGX_HTTP_GET },
+    { (u_char *) "HEAD",      (uint32_t) ~NGX_HTTP_HEAD },
+    { (u_char *) "POST",      (uint32_t) ~NGX_HTTP_POST },
+    { (u_char *) "PUT",       (uint32_t) ~NGX_HTTP_PUT },
+    { (u_char *) "DELETE",    (uint32_t) ~NGX_HTTP_DELETE },
+    { (u_char *) "MKCOL",     (uint32_t) ~NGX_HTTP_MKCOL },
+    { (u_char *) "COPY",      (uint32_t) ~NGX_HTTP_COPY },
+    { (u_char *) "MOVE",      (uint32_t) ~NGX_HTTP_MOVE },
+    { (u_char *) "OPTIONS",   (uint32_t) ~NGX_HTTP_OPTIONS },
+    { (u_char *) "PROPFIND",  (uint32_t) ~NGX_HTTP_PROPFIND },
+    { (u_char *) "PROPPATCH", (uint32_t) ~NGX_HTTP_PROPPATCH },
+    { (u_char *) "LOCK",      (uint32_t) ~NGX_HTTP_LOCK },
+    { (u_char *) "UNLOCK",    (uint32_t) ~NGX_HTTP_UNLOCK },
+    { (u_char *) "PATCH",     (uint32_t) ~NGX_HTTP_PATCH },
+    { NULL, 0 }
+};
+
+
+static char *
+ngx_http_core_limit_except(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_loc_conf_t *pclcf = conf;
+
+    char                      *rv;
+    void                      *mconf;
+    ngx_str_t                 *value;
+    ngx_uint_t                 i;
+    ngx_conf_t                 save;
+    ngx_http_module_t         *module;
+    ngx_http_conf_ctx_t       *ctx, *pctx;
+    ngx_http_method_name_t    *name;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    if (pclcf->limit_except) {
+        return "is duplicate";
+    }
+
+    pclcf->limit_except = 0xffffffff;
+
+    value = cf->args->elts;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+        for (name = ngx_methods_names; name->name; name++) {
+
+            if (ngx_strcasecmp(value[i].data, name->name) == 0) {
+                pclcf->limit_except &= name->method;
+                goto next;
+            }
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid method \"%V\"", &value[i]);
+        return NGX_CONF_ERROR;
+
+    next:
+        continue;
+    }
+
+    if (!(pclcf->limit_except & NGX_HTTP_GET)) {
+        pclcf->limit_except &= (uint32_t) ~NGX_HTTP_HEAD;
+    }
+
+    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
+    if (ctx == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    pctx = cf->ctx;
+    ctx->main_conf = pctx->main_conf;
+    ctx->srv_conf = pctx->srv_conf;
+
+    ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
+    if (ctx->loc_conf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    for (i = 0; cf->cycle->modules[i]; i++) {
+        if (cf->cycle->modules[i]->type != NGX_HTTP_MODULE) {
+            continue;
+        }
+
+        module = cf->cycle->modules[i]->ctx;
+
+        if (module->create_loc_conf) {
+
+            mconf = module->create_loc_conf(cf);
+            if (mconf == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            ctx->loc_conf[cf->cycle->modules[i]->ctx_index] = mconf;
+        }
+    }
+
+
+    clcf = ctx->loc_conf[ngx_http_core_module.ctx_index];
+    pclcf->limit_except_loc_conf = ctx->loc_conf;
+    clcf->loc_conf = ctx->loc_conf;
+    clcf->name = pclcf->name;
+    clcf->noname = 1;
+    clcf->lmt_excpt = 1;
+
+    if (ngx_http_add_location(cf, &pclcf->locations, clcf) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    save = *cf;
+    cf->ctx = ctx;
+    cf->cmd_type = NGX_HTTP_LMT_CONF;
+
+    rv = ngx_conf_parse(cf, NULL);
+
+    *cf = save;
+
+    return rv;
+}
+
+
+static char *
+ngx_http_core_set_aio(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_loc_conf_t *clcf = conf;
+
+    ngx_str_t  *value;
+
+    if (clcf->aio != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+#if (NGX_THREADS)
+    clcf->thread_pool = NULL;
+    clcf->thread_pool_value = NULL;
+#endif
+
+    value = cf->args->elts;
+
+    if (ngx_strcmp(value[1].data, "off") == 0) {
+        clcf->aio = NGX_HTTP_AIO_OFF;
+        return NGX_CONF_OK;
+    }
+
+    if (ngx_strcmp(value[1].data, "on") == 0) {
+#if (NGX_HAVE_FILE_AIO)
+        clcf->aio = NGX_HTTP_AIO_ON;
+        return NGX_CONF_OK;
+#else
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"aio on\" "
+                           "is unsupported on this platform");
+        return NGX_CONF_ERROR;
+#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] == '='))
+    {
+#if (NGX_THREADS)
+        ngx_str_t                          name;
+        ngx_thread_pool_t                 *tp;
+        ngx_http_complex_value_t           cv;
+        ngx_http_compile_complex_value_t   ccv;
+
+        clcf->aio = NGX_HTTP_AIO_THREADS;
+
+        if (value[1].len >= 8) {
+            name.len = value[1].len - 8;
+            name.data = value[1].data + 8;
+
+            ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+            ccv.cf = cf;
+            ccv.value = &name;
+            ccv.complex_value = &cv;
+
+            if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+                return NGX_CONF_ERROR;
+            }
+
+            if (cv.lengths != NULL) {
+                clcf->thread_pool_value = ngx_palloc(cf->pool,
+                                    sizeof(ngx_http_complex_value_t));
+                if (clcf->thread_pool_value == NULL) {
+                    return NGX_CONF_ERROR;
+                }
+
+                *clcf->thread_pool_value = cv;
+
+                return NGX_CONF_OK;
+            }
+
+            tp = ngx_thread_pool_add(cf, &name);
+
+        } else {
+            tp = ngx_thread_pool_add(cf, NULL);
+        }
+
+        if (tp == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        clcf->thread_pool = tp;
+
+        return NGX_CONF_OK;
+#else
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"aio threads\" "
+                           "is unsupported on this platform");
+        return NGX_CONF_ERROR;
+#endif
+    }
+
+    return "invalid value";
+}
+
+
+static char *
+ngx_http_core_directio(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_loc_conf_t *clcf = conf;
+
+    ngx_str_t  *value;
+
+    if (clcf->directio != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    if (ngx_strcmp(value[1].data, "off") == 0) {
+        clcf->directio = NGX_OPEN_FILE_DIRECTIO_OFF;
+        return NGX_CONF_OK;
+    }
+
+    clcf->directio = ngx_parse_offset(&value[1]);
+    if (clcf->directio == (off_t) NGX_ERROR) {
+        return "invalid value";
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_core_error_page(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_loc_conf_t *clcf = conf;
+
+    u_char                            *p;
+    ngx_int_t                          overwrite;
+    ngx_str_t                         *value, uri, args;
+    ngx_uint_t                         i, n;
+    ngx_http_err_page_t               *err;
+    ngx_http_complex_value_t           cv;
+    ngx_http_compile_complex_value_t   ccv;
+
+    if (clcf->error_pages == NULL) {
+        clcf->error_pages = ngx_array_create(cf->pool, 4,
+                                             sizeof(ngx_http_err_page_t));
+        if (clcf->error_pages == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    value = cf->args->elts;
+
+    i = cf->args->nelts - 2;
+
+    if (value[i].data[0] == '=') {
+        if (i == 1) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid value \"%V\"", &value[i]);
+            return NGX_CONF_ERROR;
+        }
+
+        if (value[i].len > 1) {
+            overwrite = ngx_atoi(&value[i].data[1], value[i].len - 1);
+
+            if (overwrite == NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid value \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+        } else {
+            overwrite = 0;
+        }
+
+        n = 2;
+
+    } else {
+        overwrite = -1;
+        n = 1;
+    }
+
+    uri = value[cf->args->nelts - 1];
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &uri;
+    ccv.complex_value = &cv;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_str_null(&args);
+
+    if (cv.lengths == NULL && uri.len && uri.data[0] == '/') {
+        p = (u_char *) ngx_strchr(uri.data, '?');
+
+        if (p) {
+            cv.value.len = p - uri.data;
+            cv.value.data = uri.data;
+            p++;
+            args.len = (uri.data + uri.len) - p;
+            args.data = p;
+        }
+    }
+
+    for (i = 1; i < cf->args->nelts - n; i++) {
+        err = ngx_array_push(clcf->error_pages);
+        if (err == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        err->status = ngx_atoi(value[i].data, value[i].len);
+
+        if (err->status == NGX_ERROR || err->status == 499) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid value \"%V\"", &value[i]);
+            return NGX_CONF_ERROR;
+        }
+
+        if (err->status < 300 || err->status > 599) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "value \"%V\" must be between 300 and 599",
+                               &value[i]);
+            return NGX_CONF_ERROR;
+        }
+
+        err->overwrite = overwrite;
+
+        if (overwrite == -1) {
+            switch (err->status) {
+                case NGX_HTTP_TO_HTTPS:
+                case NGX_HTTPS_CERT_ERROR:
+                case NGX_HTTPS_NO_CERT:
+                    err->overwrite = NGX_HTTP_BAD_REQUEST;
+            }
+        }
+
+        err->value = cv;
+        err->args = args;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_core_open_file_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_loc_conf_t *clcf = conf;
+
+    time_t       inactive;
+    ngx_str_t   *value, s;
+    ngx_int_t    max;
+    ngx_uint_t   i;
+
+    if (clcf->open_file_cache != NGX_CONF_UNSET_PTR) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    max = 0;
+    inactive = 60;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+
+        if (ngx_strncmp(value[i].data, "max=", 4) == 0) {
+
+            max = ngx_atoi(value[i].data + 4, value[i].len - 4);
+            if (max <= 0) {
+                goto failed;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "inactive=", 9) == 0) {
+
+            s.len = value[i].len - 9;
+            s.data = value[i].data + 9;
+
+            inactive = ngx_parse_time(&s, 1);
+            if (inactive == (time_t) NGX_ERROR) {
+                goto failed;
+            }
+
+            continue;
+        }
+
+        if (ngx_strcmp(value[i].data, "off") == 0) {
+
+            clcf->open_file_cache = NULL;
+
+            continue;
+        }
+
+    failed:
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid \"open_file_cache\" parameter \"%V\"",
+                           &value[i]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (clcf->open_file_cache == NULL) {
+        return NGX_CONF_OK;
+    }
+
+    if (max == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                        "\"open_file_cache\" must have the \"max\" parameter");
+        return NGX_CONF_ERROR;
+    }
+
+    clcf->open_file_cache = ngx_open_file_cache_init(cf->pool, max, inactive);
+    if (clcf->open_file_cache) {
+        return NGX_CONF_OK;
+    }
+
+    return NGX_CONF_ERROR;
+}
+
+
+static char *
+ngx_http_core_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_loc_conf_t *clcf = conf;
+
+    return ngx_log_set_log(cf, &clcf->error_log);
+}
+
+
+static char *
+ngx_http_core_keepalive(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_loc_conf_t *clcf = conf;
+
+    ngx_str_t  *value;
+
+    if (clcf->keepalive_timeout != NGX_CONF_UNSET_MSEC) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    clcf->keepalive_timeout = ngx_parse_time(&value[1], 0);
+
+    if (clcf->keepalive_timeout == (ngx_msec_t) NGX_ERROR) {
+        return "invalid value";
+    }
+
+    if (cf->args->nelts == 2) {
+        return NGX_CONF_OK;
+    }
+
+    clcf->keepalive_header = ngx_parse_time(&value[2], 1);
+
+    if (clcf->keepalive_header == (time_t) NGX_ERROR) {
+        return "invalid value";
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_core_internal(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_loc_conf_t *clcf = conf;
+
+    if (clcf->internal != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    clcf->internal = 1;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_loc_conf_t  *clcf = conf;
+
+    ngx_str_t  *value;
+
+    if (clcf->resolver) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    clcf->resolver = ngx_resolver_create(cf, &value[1], cf->args->nelts - 1);
+    if (clcf->resolver == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+#if (NGX_HTTP_GZIP)
+
+static char *
+ngx_http_gzip_disable(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_loc_conf_t  *clcf = conf;
+
+#if (NGX_PCRE)
+
+    ngx_str_t            *value;
+    ngx_uint_t            i;
+    ngx_regex_elt_t      *re;
+    ngx_regex_compile_t   rc;
+    u_char                errstr[NGX_MAX_CONF_ERRSTR];
+
+    if (clcf->gzip_disable == NGX_CONF_UNSET_PTR) {
+        clcf->gzip_disable = ngx_array_create(cf->pool, 2,
+                                              sizeof(ngx_regex_elt_t));
+        if (clcf->gzip_disable == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    value = cf->args->elts;
+
+    ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
+
+    rc.pool = cf->pool;
+    rc.err.len = NGX_MAX_CONF_ERRSTR;
+    rc.err.data = errstr;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+
+        if (ngx_strcmp(value[i].data, "msie6") == 0) {
+            clcf->gzip_disable_msie6 = 1;
+            continue;
+        }
+
+#if (NGX_HTTP_DEGRADATION)
+
+        if (ngx_strcmp(value[i].data, "degradation") == 0) {
+            clcf->gzip_disable_degradation = 1;
+            continue;
+        }
+
+#endif
+
+        re = ngx_array_push(clcf->gzip_disable);
+        if (re == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        rc.pattern = value[i];
+        rc.options = NGX_REGEX_CASELESS;
+
+        if (ngx_regex_compile(&rc) != NGX_OK) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err);
+            return NGX_CONF_ERROR;
+        }
+
+        re->regex = rc.regex;
+        re->name = value[i].data;
+    }
+
+    return NGX_CONF_OK;
+
+#else
+    ngx_str_t   *value;
+    ngx_uint_t   i;
+
+    value = cf->args->elts;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+        if (ngx_strcmp(value[i].data, "msie6") == 0) {
+            clcf->gzip_disable_msie6 = 1;
+            continue;
+        }
+
+#if (NGX_HTTP_DEGRADATION)
+
+        if (ngx_strcmp(value[i].data, "degradation") == 0) {
+            clcf->gzip_disable_degradation = 1;
+            continue;
+        }
+
+#endif
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "without PCRE library \"gzip_disable\" supports "
+                           "builtin \"msie6\" and \"degradation\" mask only");
+
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+
+#endif
+}
+
+#endif
+
+
+#if (NGX_HAVE_OPENAT)
+
+static char *
+ngx_http_disable_symlinks(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_loc_conf_t *clcf = conf;
+
+    ngx_str_t                         *value;
+    ngx_uint_t                         i;
+    ngx_http_compile_complex_value_t   ccv;
+
+    if (clcf->disable_symlinks != NGX_CONF_UNSET_UINT) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+
+        if (ngx_strcmp(value[i].data, "off") == 0) {
+            clcf->disable_symlinks = NGX_DISABLE_SYMLINKS_OFF;
+            continue;
+        }
+
+        if (ngx_strcmp(value[i].data, "if_not_owner") == 0) {
+            clcf->disable_symlinks = NGX_DISABLE_SYMLINKS_NOTOWNER;
+            continue;
+        }
+
+        if (ngx_strcmp(value[i].data, "on") == 0) {
+            clcf->disable_symlinks = NGX_DISABLE_SYMLINKS_ON;
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "from=", 5) == 0) {
+            value[i].len -= 5;
+            value[i].data += 5;
+
+            ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+            ccv.cf = cf;
+            ccv.value = &value[i];
+            ccv.complex_value = ngx_palloc(cf->pool,
+                                           sizeof(ngx_http_complex_value_t));
+            if (ccv.complex_value == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+                return NGX_CONF_ERROR;
+            }
+
+            clcf->disable_symlinks_from = ccv.complex_value;
+
+            continue;
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[i]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (clcf->disable_symlinks == NGX_CONF_UNSET_UINT) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"%V\" must have \"off\", \"on\" "
+                           "or \"if_not_owner\" parameter",
+                           &cmd->name);
+        return NGX_CONF_ERROR;
+    }
+
+    if (cf->args->nelts == 2) {
+        clcf->disable_symlinks_from = NULL;
+        return NGX_CONF_OK;
+    }
+
+    if (clcf->disable_symlinks_from == NGX_CONF_UNSET_PTR) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "duplicate parameters \"%V %V\"",
+                           &value[1], &value[2]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (clcf->disable_symlinks == NGX_DISABLE_SYMLINKS_OFF) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"from=\" cannot be used with \"off\" parameter");
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+#endif
+
+
+static char *
+ngx_http_core_lowat_check(ngx_conf_t *cf, void *post, void *data)
+{
+#if (NGX_FREEBSD)
+    ssize_t *np = data;
+
+    if ((u_long) *np >= ngx_freebsd_net_inet_tcp_sendspace) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"send_lowat\" must be less than %d "
+                           "(sysctl net.inet.tcp.sendspace)",
+                           ngx_freebsd_net_inet_tcp_sendspace);
+
+        return NGX_CONF_ERROR;
+    }
+
+#elif !(NGX_HAVE_SO_SNDLOWAT)
+    ssize_t *np = data;
+
+    ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                       "\"send_lowat\" is not supported, ignored");
+
+    *np = 0;
+
+#endif
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_core_pool_size(ngx_conf_t *cf, void *post, void *data)
+{
+    size_t *sp = data;
+
+    if (*sp < NGX_MIN_POOL_SIZE) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "the pool size must be no less than %uz",
+                           NGX_MIN_POOL_SIZE);
+        return NGX_CONF_ERROR;
+    }
+
+    if (*sp % NGX_POOL_ALIGNMENT) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "the pool size must be a multiple of %uz",
+                           NGX_POOL_ALIGNMENT);
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/http/ngx_http_core_module.h b/nginx/src/http/ngx_http_core_module.h
new file mode 100644 (file)
index 0000000..d798504
--- /dev/null
@@ -0,0 +1,576 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_CORE_H_INCLUDED_
+#define _NGX_HTTP_CORE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#if (NGX_THREADS)
+#include <ngx_thread_pool.h>
+#elif (NGX_COMPAT)
+typedef struct ngx_thread_pool_s  ngx_thread_pool_t;
+#endif
+
+
+#define NGX_HTTP_GZIP_PROXIED_OFF       0x0002
+#define NGX_HTTP_GZIP_PROXIED_EXPIRED   0x0004
+#define NGX_HTTP_GZIP_PROXIED_NO_CACHE  0x0008
+#define NGX_HTTP_GZIP_PROXIED_NO_STORE  0x0010
+#define NGX_HTTP_GZIP_PROXIED_PRIVATE   0x0020
+#define NGX_HTTP_GZIP_PROXIED_NO_LM     0x0040
+#define NGX_HTTP_GZIP_PROXIED_NO_ETAG   0x0080
+#define NGX_HTTP_GZIP_PROXIED_AUTH      0x0100
+#define NGX_HTTP_GZIP_PROXIED_ANY       0x0200
+
+
+#define NGX_HTTP_AIO_OFF                0
+#define NGX_HTTP_AIO_ON                 1
+#define NGX_HTTP_AIO_THREADS            2
+
+
+#define NGX_HTTP_SATISFY_ALL            0
+#define NGX_HTTP_SATISFY_ANY            1
+
+
+#define NGX_HTTP_LINGERING_OFF          0
+#define NGX_HTTP_LINGERING_ON           1
+#define NGX_HTTP_LINGERING_ALWAYS       2
+
+
+#define NGX_HTTP_IMS_OFF                0
+#define NGX_HTTP_IMS_EXACT              1
+#define NGX_HTTP_IMS_BEFORE             2
+
+
+#define NGX_HTTP_KEEPALIVE_DISABLE_NONE    0x0002
+#define NGX_HTTP_KEEPALIVE_DISABLE_MSIE6   0x0004
+#define NGX_HTTP_KEEPALIVE_DISABLE_SAFARI  0x0008
+
+
+#define NGX_HTTP_SERVER_TOKENS_OFF      0
+#define NGX_HTTP_SERVER_TOKENS_ON       1
+#define NGX_HTTP_SERVER_TOKENS_BUILD    2
+
+
+typedef struct ngx_http_location_tree_node_s  ngx_http_location_tree_node_t;
+typedef struct ngx_http_core_loc_conf_s  ngx_http_core_loc_conf_t;
+
+
+typedef struct {
+    ngx_sockaddr_t             sockaddr;
+    socklen_t                  socklen;
+
+    unsigned                   set:1;
+    unsigned                   default_server:1;
+    unsigned                   bind:1;
+    unsigned                   wildcard:1;
+    unsigned                   ssl:1;
+    unsigned                   http2:1;
+#if (NGX_HAVE_INET6)
+    unsigned                   ipv6only:1;
+#endif
+    unsigned                   deferred_accept:1;
+    unsigned                   reuseport:1;
+    unsigned                   so_keepalive:2;
+    unsigned                   proxy_protocol:1;
+
+    int                        backlog;
+    int                        rcvbuf;
+    int                        sndbuf;
+#if (NGX_HAVE_SETFIB)
+    int                        setfib;
+#endif
+#if (NGX_HAVE_TCP_FASTOPEN)
+    int                        fastopen;
+#endif
+#if (NGX_HAVE_KEEPALIVE_TUNABLE)
+    int                        tcp_keepidle;
+    int                        tcp_keepintvl;
+    int                        tcp_keepcnt;
+#endif
+
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
+    char                      *accept_filter;
+#endif
+
+    u_char                     addr[NGX_SOCKADDR_STRLEN + 1];
+} ngx_http_listen_opt_t;
+
+
+typedef enum {
+    NGX_HTTP_POST_READ_PHASE = 0,
+
+    NGX_HTTP_SERVER_REWRITE_PHASE,
+
+    NGX_HTTP_FIND_CONFIG_PHASE,
+    NGX_HTTP_REWRITE_PHASE,
+    NGX_HTTP_POST_REWRITE_PHASE,
+
+    NGX_HTTP_PREACCESS_PHASE,
+
+    NGX_HTTP_ACCESS_PHASE,
+    NGX_HTTP_POST_ACCESS_PHASE,
+
+    NGX_HTTP_PRECONTENT_PHASE,
+
+    NGX_HTTP_CONTENT_PHASE,
+
+    NGX_HTTP_LOG_PHASE
+} ngx_http_phases;
+
+typedef struct ngx_http_phase_handler_s  ngx_http_phase_handler_t;
+
+typedef ngx_int_t (*ngx_http_phase_handler_pt)(ngx_http_request_t *r,
+    ngx_http_phase_handler_t *ph);
+
+struct ngx_http_phase_handler_s {
+    ngx_http_phase_handler_pt  checker;
+    ngx_http_handler_pt        handler;
+    ngx_uint_t                 next;
+};
+
+
+typedef struct {
+    ngx_http_phase_handler_t  *handlers;
+    ngx_uint_t                 server_rewrite_index;
+    ngx_uint_t                 location_rewrite_index;
+} ngx_http_phase_engine_t;
+
+
+typedef struct {
+    ngx_array_t                handlers;
+} ngx_http_phase_t;
+
+
+typedef struct {
+    ngx_array_t                servers;         /* ngx_http_core_srv_conf_t */
+
+    ngx_http_phase_engine_t    phase_engine;
+
+    ngx_hash_t                 headers_in_hash;
+
+    ngx_hash_t                 variables_hash;
+
+    ngx_array_t                variables;         /* ngx_http_variable_t */
+    ngx_array_t                prefix_variables;  /* ngx_http_variable_t */
+    ngx_uint_t                 ncaptures;
+
+    ngx_uint_t                 server_names_hash_max_size;
+    ngx_uint_t                 server_names_hash_bucket_size;
+
+    ngx_uint_t                 variables_hash_max_size;
+    ngx_uint_t                 variables_hash_bucket_size;
+
+    ngx_hash_keys_arrays_t    *variables_keys;
+
+    ngx_array_t               *ports;
+
+    ngx_http_phase_t           phases[NGX_HTTP_LOG_PHASE + 1];
+} ngx_http_core_main_conf_t;
+
+
+typedef struct {
+    /* array of the ngx_http_server_name_t, "server_name" directive */
+    ngx_array_t                 server_names;
+
+    /* server ctx */
+    ngx_http_conf_ctx_t        *ctx;
+
+    ngx_str_t                   server_name;
+
+    size_t                      connection_pool_size;
+    size_t                      request_pool_size;
+    size_t                      client_header_buffer_size;
+
+    ngx_bufs_t                  large_client_header_buffers;
+
+    ngx_msec_t                  client_header_timeout;
+
+    ngx_flag_t                  ignore_invalid_headers;
+    ngx_flag_t                  merge_slashes;
+    ngx_flag_t                  underscores_in_headers;
+
+    unsigned                    listen:1;
+#if (NGX_PCRE)
+    unsigned                    captures:1;
+#endif
+
+    ngx_http_core_loc_conf_t  **named_locations;
+} ngx_http_core_srv_conf_t;
+
+
+/* list of structures to find core_srv_conf quickly at run time */
+
+
+typedef struct {
+#if (NGX_PCRE)
+    ngx_http_regex_t          *regex;
+#endif
+    ngx_http_core_srv_conf_t  *server;   /* virtual name server conf */
+    ngx_str_t                  name;
+} ngx_http_server_name_t;
+
+
+typedef struct {
+    ngx_hash_combined_t        names;
+
+    ngx_uint_t                 nregex;
+    ngx_http_server_name_t    *regex;
+} ngx_http_virtual_names_t;
+
+
+struct ngx_http_addr_conf_s {
+    /* the default server configuration for this address:port */
+    ngx_http_core_srv_conf_t  *default_server;
+
+    ngx_http_virtual_names_t  *virtual_names;
+
+    unsigned                   ssl:1;
+    unsigned                   http2:1;
+    unsigned                   proxy_protocol:1;
+};
+
+
+typedef struct {
+    in_addr_t                  addr;
+    ngx_http_addr_conf_t       conf;
+} ngx_http_in_addr_t;
+
+
+#if (NGX_HAVE_INET6)
+
+typedef struct {
+    struct in6_addr            addr6;
+    ngx_http_addr_conf_t       conf;
+} ngx_http_in6_addr_t;
+
+#endif
+
+
+typedef struct {
+    /* ngx_http_in_addr_t or ngx_http_in6_addr_t */
+    void                      *addrs;
+    ngx_uint_t                 naddrs;
+} ngx_http_port_t;
+
+
+typedef struct {
+    ngx_int_t                  family;
+    in_port_t                  port;
+    ngx_array_t                addrs;     /* array of ngx_http_conf_addr_t */
+} ngx_http_conf_port_t;
+
+
+typedef struct {
+    ngx_http_listen_opt_t      opt;
+
+    ngx_hash_t                 hash;
+    ngx_hash_wildcard_t       *wc_head;
+    ngx_hash_wildcard_t       *wc_tail;
+
+#if (NGX_PCRE)
+    ngx_uint_t                 nregex;
+    ngx_http_server_name_t    *regex;
+#endif
+
+    /* the default server configuration for this address:port */
+    ngx_http_core_srv_conf_t  *default_server;
+    ngx_array_t                servers;  /* array of ngx_http_core_srv_conf_t */
+} ngx_http_conf_addr_t;
+
+
+typedef struct {
+    ngx_int_t                  status;
+    ngx_int_t                  overwrite;
+    ngx_http_complex_value_t   value;
+    ngx_str_t                  args;
+} ngx_http_err_page_t;
+
+
+struct ngx_http_core_loc_conf_s {
+    ngx_str_t     name;          /* location name */
+
+#if (NGX_PCRE)
+    ngx_http_regex_t  *regex;
+#endif
+
+    unsigned      noname:1;   /* "if () {}" block or limit_except */
+    unsigned      lmt_excpt:1;
+    unsigned      named:1;
+
+    unsigned      exact_match:1;
+    unsigned      noregex:1;
+
+    unsigned      auto_redirect:1;
+#if (NGX_HTTP_GZIP)
+    unsigned      gzip_disable_msie6:2;
+    unsigned      gzip_disable_degradation:2;
+#endif
+
+    ngx_http_location_tree_node_t   *static_locations;
+#if (NGX_PCRE)
+    ngx_http_core_loc_conf_t       **regex_locations;
+#endif
+
+    /* pointer to the modules' loc_conf */
+    void        **loc_conf;
+
+    uint32_t      limit_except;
+    void        **limit_except_loc_conf;
+
+    ngx_http_handler_pt  handler;
+
+    /* location name length for inclusive location with inherited alias */
+    size_t        alias;
+    ngx_str_t     root;                    /* root, alias */
+    ngx_str_t     post_action;
+
+    ngx_array_t  *root_lengths;
+    ngx_array_t  *root_values;
+
+    ngx_array_t  *types;
+    ngx_hash_t    types_hash;
+    ngx_str_t     default_type;
+
+    off_t         client_max_body_size;    /* client_max_body_size */
+    off_t         directio;                /* directio */
+    off_t         directio_alignment;      /* directio_alignment */
+
+    size_t        client_body_buffer_size; /* client_body_buffer_size */
+    size_t        send_lowat;              /* send_lowat */
+    size_t        postpone_output;         /* postpone_output */
+    size_t        limit_rate;              /* limit_rate */
+    size_t        limit_rate_after;        /* limit_rate_after */
+    size_t        sendfile_max_chunk;      /* sendfile_max_chunk */
+    size_t        read_ahead;              /* read_ahead */
+    size_t        subrequest_output_buffer_size;
+                                           /* subrequest_output_buffer_size */
+
+    ngx_msec_t    client_body_timeout;     /* client_body_timeout */
+    ngx_msec_t    send_timeout;            /* send_timeout */
+    ngx_msec_t    keepalive_timeout;       /* keepalive_timeout */
+    ngx_msec_t    lingering_time;          /* lingering_time */
+    ngx_msec_t    lingering_timeout;       /* lingering_timeout */
+    ngx_msec_t    resolver_timeout;        /* resolver_timeout */
+
+    ngx_resolver_t  *resolver;             /* resolver */
+
+    time_t        keepalive_header;        /* keepalive_timeout */
+
+    ngx_uint_t    keepalive_requests;      /* keepalive_requests */
+    ngx_uint_t    keepalive_disable;       /* keepalive_disable */
+    ngx_uint_t    satisfy;                 /* satisfy */
+    ngx_uint_t    lingering_close;         /* lingering_close */
+    ngx_uint_t    if_modified_since;       /* if_modified_since */
+    ngx_uint_t    max_ranges;              /* max_ranges */
+    ngx_uint_t    client_body_in_file_only; /* client_body_in_file_only */
+
+    ngx_flag_t    client_body_in_single_buffer;
+                                           /* client_body_in_singe_buffer */
+    ngx_flag_t    internal;                /* internal */
+    ngx_flag_t    sendfile;                /* sendfile */
+    ngx_flag_t    aio;                     /* aio */
+    ngx_flag_t    aio_write;               /* aio_write */
+    ngx_flag_t    tcp_nopush;              /* tcp_nopush */
+    ngx_flag_t    tcp_nodelay;             /* tcp_nodelay */
+    ngx_flag_t    reset_timedout_connection; /* reset_timedout_connection */
+    ngx_flag_t    absolute_redirect;       /* absolute_redirect */
+    ngx_flag_t    server_name_in_redirect; /* server_name_in_redirect */
+    ngx_flag_t    port_in_redirect;        /* port_in_redirect */
+    ngx_flag_t    msie_padding;            /* msie_padding */
+    ngx_flag_t    msie_refresh;            /* msie_refresh */
+    ngx_flag_t    log_not_found;           /* log_not_found */
+    ngx_flag_t    log_subrequest;          /* log_subrequest */
+    ngx_flag_t    recursive_error_pages;   /* recursive_error_pages */
+    ngx_uint_t    server_tokens;           /* server_tokens */
+    ngx_flag_t    chunked_transfer_encoding; /* chunked_transfer_encoding */
+    ngx_flag_t    etag;                    /* etag */
+
+#if (NGX_HTTP_GZIP)
+    ngx_flag_t    gzip_vary;               /* gzip_vary */
+
+    ngx_uint_t    gzip_http_version;       /* gzip_http_version */
+    ngx_uint_t    gzip_proxied;            /* gzip_proxied */
+
+#if (NGX_PCRE)
+    ngx_array_t  *gzip_disable;            /* gzip_disable */
+#endif
+#endif
+
+#if (NGX_THREADS || NGX_COMPAT)
+    ngx_thread_pool_t         *thread_pool;
+    ngx_http_complex_value_t  *thread_pool_value;
+#endif
+
+#if (NGX_HAVE_OPENAT)
+    ngx_uint_t    disable_symlinks;        /* disable_symlinks */
+    ngx_http_complex_value_t  *disable_symlinks_from;
+#endif
+
+    ngx_array_t  *error_pages;             /* error_page */
+
+    ngx_path_t   *client_body_temp_path;   /* client_body_temp_path */
+
+    ngx_open_file_cache_t  *open_file_cache;
+    time_t        open_file_cache_valid;
+    ngx_uint_t    open_file_cache_min_uses;
+    ngx_flag_t    open_file_cache_errors;
+    ngx_flag_t    open_file_cache_events;
+
+    ngx_log_t    *error_log;
+
+    ngx_uint_t    types_hash_max_size;
+    ngx_uint_t    types_hash_bucket_size;
+
+    ngx_queue_t  *locations;
+
+#if 0
+    ngx_http_core_loc_conf_t  *prev_location;
+#endif
+};
+
+
+typedef struct {
+    ngx_queue_t                      queue;
+    ngx_http_core_loc_conf_t        *exact;
+    ngx_http_core_loc_conf_t        *inclusive;
+    ngx_str_t                       *name;
+    u_char                          *file_name;
+    ngx_uint_t                       line;
+    ngx_queue_t                      list;
+} ngx_http_location_queue_t;
+
+
+struct ngx_http_location_tree_node_s {
+    ngx_http_location_tree_node_t   *left;
+    ngx_http_location_tree_node_t   *right;
+    ngx_http_location_tree_node_t   *tree;
+
+    ngx_http_core_loc_conf_t        *exact;
+    ngx_http_core_loc_conf_t        *inclusive;
+
+    u_char                           auto_redirect;
+    u_char                           len;
+    u_char                           name[1];
+};
+
+
+void ngx_http_core_run_phases(ngx_http_request_t *r);
+ngx_int_t ngx_http_core_generic_phase(ngx_http_request_t *r,
+    ngx_http_phase_handler_t *ph);
+ngx_int_t ngx_http_core_rewrite_phase(ngx_http_request_t *r,
+    ngx_http_phase_handler_t *ph);
+ngx_int_t ngx_http_core_find_config_phase(ngx_http_request_t *r,
+    ngx_http_phase_handler_t *ph);
+ngx_int_t ngx_http_core_post_rewrite_phase(ngx_http_request_t *r,
+    ngx_http_phase_handler_t *ph);
+ngx_int_t ngx_http_core_access_phase(ngx_http_request_t *r,
+    ngx_http_phase_handler_t *ph);
+ngx_int_t ngx_http_core_post_access_phase(ngx_http_request_t *r,
+    ngx_http_phase_handler_t *ph);
+ngx_int_t ngx_http_core_content_phase(ngx_http_request_t *r,
+    ngx_http_phase_handler_t *ph);
+
+
+void *ngx_http_test_content_type(ngx_http_request_t *r, ngx_hash_t *types_hash);
+ngx_int_t ngx_http_set_content_type(ngx_http_request_t *r);
+void ngx_http_set_exten(ngx_http_request_t *r);
+ngx_int_t ngx_http_set_etag(ngx_http_request_t *r);
+void ngx_http_weak_etag(ngx_http_request_t *r);
+ngx_int_t ngx_http_send_response(ngx_http_request_t *r, ngx_uint_t status,
+    ngx_str_t *ct, ngx_http_complex_value_t *cv);
+u_char *ngx_http_map_uri_to_path(ngx_http_request_t *r, ngx_str_t *name,
+    size_t *root_length, size_t reserved);
+ngx_int_t ngx_http_auth_basic_user(ngx_http_request_t *r);
+#if (NGX_HTTP_GZIP)
+ngx_int_t ngx_http_gzip_ok(ngx_http_request_t *r);
+#endif
+
+
+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_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);
+
+
+ngx_http_cleanup_t *ngx_http_cleanup_add(ngx_http_request_t *r, size_t size);
+
+
+typedef ngx_int_t (*ngx_http_output_header_filter_pt)(ngx_http_request_t *r);
+typedef ngx_int_t (*ngx_http_output_body_filter_pt)
+    (ngx_http_request_t *r, ngx_chain_t *chain);
+typedef ngx_int_t (*ngx_http_request_body_filter_pt)
+    (ngx_http_request_t *r, ngx_chain_t *chain);
+
+
+ngx_int_t ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *chain);
+ngx_int_t ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *chain);
+ngx_int_t ngx_http_request_body_save_filter(ngx_http_request_t *r,
+    ngx_chain_t *chain);
+
+
+ngx_int_t ngx_http_set_disable_symlinks(ngx_http_request_t *r,
+    ngx_http_core_loc_conf_t *clcf, ngx_str_t *path, ngx_open_file_info_t *of);
+
+ngx_int_t ngx_http_get_forwarded_addr(ngx_http_request_t *r, ngx_addr_t *addr,
+    ngx_array_t *headers, ngx_str_t *value, ngx_array_t *proxies,
+    int recursive);
+
+
+extern ngx_module_t  ngx_http_core_module;
+
+extern ngx_uint_t ngx_http_max_module;
+
+extern ngx_str_t  ngx_http_core_get_method;
+
+
+#define ngx_http_clear_content_length(r)                                      \
+                                                                              \
+    r->headers_out.content_length_n = -1;                                     \
+    if (r->headers_out.content_length) {                                      \
+        r->headers_out.content_length->hash = 0;                              \
+        r->headers_out.content_length = NULL;                                 \
+    }
+
+#define ngx_http_clear_accept_ranges(r)                                       \
+                                                                              \
+    r->allow_ranges = 0;                                                      \
+    if (r->headers_out.accept_ranges) {                                       \
+        r->headers_out.accept_ranges->hash = 0;                               \
+        r->headers_out.accept_ranges = NULL;                                  \
+    }
+
+#define ngx_http_clear_last_modified(r)                                       \
+                                                                              \
+    r->headers_out.last_modified_time = -1;                                   \
+    if (r->headers_out.last_modified) {                                       \
+        r->headers_out.last_modified->hash = 0;                               \
+        r->headers_out.last_modified = NULL;                                  \
+    }
+
+#define ngx_http_clear_location(r)                                            \
+                                                                              \
+    if (r->headers_out.location) {                                            \
+        r->headers_out.location->hash = 0;                                    \
+        r->headers_out.location = NULL;                                       \
+    }
+
+#define ngx_http_clear_etag(r)                                                \
+                                                                              \
+    if (r->headers_out.etag) {                                                \
+        r->headers_out.etag->hash = 0;                                        \
+        r->headers_out.etag = NULL;                                           \
+    }
+
+
+#endif /* _NGX_HTTP_CORE_H_INCLUDED_ */
diff --git a/nginx/src/http/ngx_http_file_cache.c b/nginx/src/http/ngx_http_file_cache.c
new file mode 100644 (file)
index 0000000..3b2b68a
--- /dev/null
@@ -0,0 +1,2688 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_md5.h>
+
+
+static ngx_int_t ngx_http_file_cache_lock(ngx_http_request_t *r,
+    ngx_http_cache_t *c);
+static void ngx_http_file_cache_lock_wait_handler(ngx_event_t *ev);
+static void ngx_http_file_cache_lock_wait(ngx_http_request_t *r,
+    ngx_http_cache_t *c);
+static ngx_int_t ngx_http_file_cache_read(ngx_http_request_t *r,
+    ngx_http_cache_t *c);
+static ssize_t ngx_http_file_cache_aio_read(ngx_http_request_t *r,
+    ngx_http_cache_t *c);
+#if (NGX_HAVE_FILE_AIO)
+static void ngx_http_cache_aio_event_handler(ngx_event_t *ev);
+#endif
+#if (NGX_THREADS)
+static ngx_int_t ngx_http_cache_thread_handler(ngx_thread_task_t *task,
+    ngx_file_t *file);
+static void ngx_http_cache_thread_event_handler(ngx_event_t *ev);
+#endif
+static ngx_int_t ngx_http_file_cache_exists(ngx_http_file_cache_t *cache,
+    ngx_http_cache_t *c);
+static ngx_int_t ngx_http_file_cache_name(ngx_http_request_t *r,
+    ngx_path_t *path);
+static ngx_http_file_cache_node_t *
+    ngx_http_file_cache_lookup(ngx_http_file_cache_t *cache, u_char *key);
+static void ngx_http_file_cache_rbtree_insert_value(ngx_rbtree_node_t *temp,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
+static void ngx_http_file_cache_vary(ngx_http_request_t *r, u_char *vary,
+    size_t len, u_char *hash);
+static void ngx_http_file_cache_vary_header(ngx_http_request_t *r,
+    ngx_md5_t *md5, ngx_str_t *name);
+static ngx_int_t ngx_http_file_cache_reopen(ngx_http_request_t *r,
+    ngx_http_cache_t *c);
+static ngx_int_t ngx_http_file_cache_update_variant(ngx_http_request_t *r,
+    ngx_http_cache_t *c);
+static void ngx_http_file_cache_cleanup(void *data);
+static time_t ngx_http_file_cache_forced_expire(ngx_http_file_cache_t *cache);
+static time_t ngx_http_file_cache_expire(ngx_http_file_cache_t *cache);
+static void ngx_http_file_cache_delete(ngx_http_file_cache_t *cache,
+    ngx_queue_t *q, u_char *name);
+static void ngx_http_file_cache_loader_sleep(ngx_http_file_cache_t *cache);
+static ngx_int_t ngx_http_file_cache_noop(ngx_tree_ctx_t *ctx,
+    ngx_str_t *path);
+static ngx_int_t ngx_http_file_cache_manage_file(ngx_tree_ctx_t *ctx,
+    ngx_str_t *path);
+static ngx_int_t ngx_http_file_cache_manage_directory(ngx_tree_ctx_t *ctx,
+    ngx_str_t *path);
+static ngx_int_t ngx_http_file_cache_add_file(ngx_tree_ctx_t *ctx,
+    ngx_str_t *path);
+static ngx_int_t ngx_http_file_cache_add(ngx_http_file_cache_t *cache,
+    ngx_http_cache_t *c);
+static ngx_int_t ngx_http_file_cache_delete_file(ngx_tree_ctx_t *ctx,
+    ngx_str_t *path);
+static void ngx_http_file_cache_set_watermark(ngx_http_file_cache_t *cache);
+
+
+ngx_str_t  ngx_http_cache_status[] = {
+    ngx_string("MISS"),
+    ngx_string("BYPASS"),
+    ngx_string("EXPIRED"),
+    ngx_string("STALE"),
+    ngx_string("UPDATING"),
+    ngx_string("REVALIDATED"),
+    ngx_string("HIT")
+};
+
+
+static u_char  ngx_http_file_cache_key[] = { LF, 'K', 'E', 'Y', ':', ' ' };
+
+
+static ngx_int_t
+ngx_http_file_cache_init(ngx_shm_zone_t *shm_zone, void *data)
+{
+    ngx_http_file_cache_t  *ocache = data;
+
+    size_t                  len;
+    ngx_uint_t              n;
+    ngx_http_file_cache_t  *cache;
+
+    cache = shm_zone->data;
+
+    if (ocache) {
+        if (ngx_strcmp(cache->path->name.data, ocache->path->name.data) != 0) {
+            ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
+                          "cache \"%V\" uses the \"%V\" cache path "
+                          "while previously it used the \"%V\" cache path",
+                          &shm_zone->shm.name, &cache->path->name,
+                          &ocache->path->name);
+
+            return NGX_ERROR;
+        }
+
+        for (n = 0; n < NGX_MAX_PATH_LEVEL; n++) {
+            if (cache->path->level[n] != ocache->path->level[n]) {
+                ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
+                              "cache \"%V\" had previously different levels",
+                              &shm_zone->shm.name);
+                return NGX_ERROR;
+            }
+        }
+
+        cache->sh = ocache->sh;
+
+        cache->shpool = ocache->shpool;
+        cache->bsize = ocache->bsize;
+
+        cache->max_size /= cache->bsize;
+
+        if (!cache->sh->cold || cache->sh->loading) {
+            cache->path->loader = NULL;
+        }
+
+        return NGX_OK;
+    }
+
+    cache->shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+    if (shm_zone->shm.exists) {
+        cache->sh = cache->shpool->data;
+        cache->bsize = ngx_fs_bsize(cache->path->name.data);
+        cache->max_size /= cache->bsize;
+
+        return NGX_OK;
+    }
+
+    cache->sh = ngx_slab_alloc(cache->shpool, sizeof(ngx_http_file_cache_sh_t));
+    if (cache->sh == NULL) {
+        return NGX_ERROR;
+    }
+
+    cache->shpool->data = cache->sh;
+
+    ngx_rbtree_init(&cache->sh->rbtree, &cache->sh->sentinel,
+                    ngx_http_file_cache_rbtree_insert_value);
+
+    ngx_queue_init(&cache->sh->queue);
+
+    cache->sh->cold = 1;
+    cache->sh->loading = 0;
+    cache->sh->size = 0;
+    cache->sh->count = 0;
+    cache->sh->watermark = (ngx_uint_t) -1;
+
+    cache->bsize = ngx_fs_bsize(cache->path->name.data);
+
+    cache->max_size /= cache->bsize;
+
+    len = sizeof(" in cache keys zone \"\"") + shm_zone->shm.name.len;
+
+    cache->shpool->log_ctx = ngx_slab_alloc(cache->shpool, len);
+    if (cache->shpool->log_ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_sprintf(cache->shpool->log_ctx, " in cache keys zone \"%V\"%Z",
+                &shm_zone->shm.name);
+
+    cache->shpool->log_nomem = 0;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_file_cache_new(ngx_http_request_t *r)
+{
+    ngx_http_cache_t  *c;
+
+    c = ngx_pcalloc(r->pool, sizeof(ngx_http_cache_t));
+    if (c == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&c->keys, r->pool, 4, sizeof(ngx_str_t)) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    r->cache = c;
+    c->file.log = r->connection->log;
+    c->file.fd = NGX_INVALID_FILE;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_file_cache_create(ngx_http_request_t *r)
+{
+    ngx_http_cache_t       *c;
+    ngx_pool_cleanup_t     *cln;
+    ngx_http_file_cache_t  *cache;
+
+    c = r->cache;
+    cache = c->file_cache;
+
+    cln = ngx_pool_cleanup_add(r->pool, 0);
+    if (cln == NULL) {
+        return NGX_ERROR;
+    }
+
+    cln->handler = ngx_http_file_cache_cleanup;
+    cln->data = c;
+
+    if (ngx_http_file_cache_exists(cache, c) == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_http_file_cache_name(r, cache->path) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+void
+ngx_http_file_cache_create_key(ngx_http_request_t *r)
+{
+    size_t             len;
+    ngx_str_t         *key;
+    ngx_uint_t         i;
+    ngx_md5_t          md5;
+    ngx_http_cache_t  *c;
+
+    c = r->cache;
+
+    len = 0;
+
+    ngx_crc32_init(c->crc32);
+    ngx_md5_init(&md5);
+
+    key = c->keys.elts;
+    for (i = 0; i < c->keys.nelts; i++) {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http cache key: \"%V\"", &key[i]);
+
+        len += key[i].len;
+
+        ngx_crc32_update(&c->crc32, key[i].data, key[i].len);
+        ngx_md5_update(&md5, key[i].data, key[i].len);
+    }
+
+    c->header_start = sizeof(ngx_http_file_cache_header_t)
+                      + sizeof(ngx_http_file_cache_key) + len + 1;
+
+    ngx_crc32_final(c->crc32);
+    ngx_md5_final(c->key, &md5);
+
+    ngx_memcpy(c->main, c->key, NGX_HTTP_CACHE_KEY_LEN);
+}
+
+
+ngx_int_t
+ngx_http_file_cache_open(ngx_http_request_t *r)
+{
+    ngx_int_t                  rc, rv;
+    ngx_uint_t                 test;
+    ngx_http_cache_t          *c;
+    ngx_pool_cleanup_t        *cln;
+    ngx_open_file_info_t       of;
+    ngx_http_file_cache_t     *cache;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    c = r->cache;
+
+    if (c->waiting) {
+        return NGX_AGAIN;
+    }
+
+    if (c->reading) {
+        return ngx_http_file_cache_read(r, c);
+    }
+
+    cache = c->file_cache;
+
+    if (c->node == NULL) {
+        cln = ngx_pool_cleanup_add(r->pool, 0);
+        if (cln == NULL) {
+            return NGX_ERROR;
+        }
+
+        cln->handler = ngx_http_file_cache_cleanup;
+        cln->data = c;
+    }
+
+    rc = ngx_http_file_cache_exists(cache, c);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http file cache exists: %i e:%d", rc, c->exists);
+
+    if (rc == NGX_ERROR) {
+        return rc;
+    }
+
+    if (rc == NGX_AGAIN) {
+        return NGX_HTTP_CACHE_SCARCE;
+    }
+
+    if (rc == NGX_OK) {
+
+        if (c->error) {
+            return c->error;
+        }
+
+        c->temp_file = 1;
+        test = c->exists ? 1 : 0;
+        rv = NGX_DECLINED;
+
+    } else { /* rc == NGX_DECLINED */
+
+        test = cache->sh->cold ? 1 : 0;
+
+        if (c->min_uses > 1) {
+
+            if (!test) {
+                return NGX_HTTP_CACHE_SCARCE;
+            }
+
+            rv = NGX_HTTP_CACHE_SCARCE;
+
+        } else {
+            c->temp_file = 1;
+            rv = NGX_DECLINED;
+        }
+    }
+
+    if (ngx_http_file_cache_name(r, cache->path) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (!test) {
+        goto done;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+    of.uniq = c->uniq;
+    of.valid = clcf->open_file_cache_valid;
+    of.min_uses = clcf->open_file_cache_min_uses;
+    of.events = clcf->open_file_cache_events;
+    of.directio = NGX_OPEN_FILE_DIRECTIO_OFF;
+    of.read_ahead = clcf->read_ahead;
+
+    if (ngx_open_cached_file(clcf->open_file_cache, &c->file.name, &of, r->pool)
+        != NGX_OK)
+    {
+        switch (of.err) {
+
+        case 0:
+            return NGX_ERROR;
+
+        case NGX_ENOENT:
+        case NGX_ENOTDIR:
+            goto done;
+
+        default:
+            ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err,
+                          ngx_open_file_n " \"%s\" failed", c->file.name.data);
+            return NGX_ERROR;
+        }
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http file cache fd: %d", of.fd);
+
+    c->file.fd = of.fd;
+    c->file.log = r->connection->log;
+    c->uniq = of.uniq;
+    c->length = of.size;
+    c->fs_size = (of.fs_size + cache->bsize - 1) / cache->bsize;
+
+    c->buf = ngx_create_temp_buf(r->pool, c->body_start);
+    if (c->buf == NULL) {
+        return NGX_ERROR;
+    }
+
+    return ngx_http_file_cache_read(r, c);
+
+done:
+
+    if (rv == NGX_DECLINED) {
+        return ngx_http_file_cache_lock(r, c);
+    }
+
+    return rv;
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_lock(ngx_http_request_t *r, ngx_http_cache_t *c)
+{
+    ngx_msec_t                 now, timer;
+    ngx_http_file_cache_t     *cache;
+
+    if (!c->lock) {
+        return NGX_DECLINED;
+    }
+
+    now = ngx_current_msec;
+
+    cache = c->file_cache;
+
+    ngx_shmtx_lock(&cache->shpool->mutex);
+
+    timer = c->node->lock_time - now;
+
+    if (!c->node->updating || (ngx_msec_int_t) timer <= 0) {
+        c->node->updating = 1;
+        c->node->lock_time = now + c->lock_age;
+        c->updating = 1;
+        c->lock_time = c->node->lock_time;
+    }
+
+    ngx_shmtx_unlock(&cache->shpool->mutex);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http file cache lock u:%d wt:%M",
+                   c->updating, c->wait_time);
+
+    if (c->updating) {
+        return NGX_DECLINED;
+    }
+
+    if (c->lock_timeout == 0) {
+        return NGX_HTTP_CACHE_SCARCE;
+    }
+
+    c->waiting = 1;
+
+    if (c->wait_time == 0) {
+        c->wait_time = now + c->lock_timeout;
+
+        c->wait_event.handler = ngx_http_file_cache_lock_wait_handler;
+        c->wait_event.data = r;
+        c->wait_event.log = r->connection->log;
+    }
+
+    timer = c->wait_time - now;
+
+    ngx_add_timer(&c->wait_event, (timer > 500) ? 500 : timer);
+
+    r->main->blocked++;
+
+    return NGX_AGAIN;
+}
+
+
+static void
+ngx_http_file_cache_lock_wait_handler(ngx_event_t *ev)
+{
+    ngx_connection_t    *c;
+    ngx_http_request_t  *r;
+
+    r = ev->data;
+    c = r->connection;
+
+    ngx_http_set_log_request(c->log, r);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http file cache wait: \"%V?%V\"", &r->uri, &r->args);
+
+    ngx_http_file_cache_lock_wait(r, r->cache);
+
+    ngx_http_run_posted_requests(c);
+}
+
+
+static void
+ngx_http_file_cache_lock_wait(ngx_http_request_t *r, ngx_http_cache_t *c)
+{
+    ngx_uint_t              wait;
+    ngx_msec_t              now, timer;
+    ngx_http_file_cache_t  *cache;
+
+    now = ngx_current_msec;
+
+    timer = c->wait_time - now;
+
+    if ((ngx_msec_int_t) timer <= 0) {
+        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                      "cache lock timeout");
+        c->lock_timeout = 0;
+        goto wakeup;
+    }
+
+    cache = c->file_cache;
+    wait = 0;
+
+    ngx_shmtx_lock(&cache->shpool->mutex);
+
+    timer = c->node->lock_time - now;
+
+    if (c->node->updating && (ngx_msec_int_t) timer > 0) {
+        wait = 1;
+    }
+
+    ngx_shmtx_unlock(&cache->shpool->mutex);
+
+    if (wait) {
+        ngx_add_timer(&c->wait_event, (timer > 500) ? 500 : timer);
+        return;
+    }
+
+wakeup:
+
+    c->waiting = 0;
+    r->main->blocked--;
+    r->write_event_handler(r);
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_read(ngx_http_request_t *r, ngx_http_cache_t *c)
+{
+    u_char                        *p;
+    time_t                         now;
+    ssize_t                        n;
+    ngx_str_t                     *key;
+    ngx_int_t                      rc;
+    ngx_uint_t                     i;
+    ngx_http_file_cache_t         *cache;
+    ngx_http_file_cache_header_t  *h;
+
+    n = ngx_http_file_cache_aio_read(r, c);
+
+    if (n < 0) {
+        return n;
+    }
+
+    if ((size_t) n < c->header_start) {
+        ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
+                      "cache file \"%s\" is too small", c->file.name.data);
+        return NGX_DECLINED;
+    }
+
+    h = (ngx_http_file_cache_header_t *) c->buf->pos;
+
+    if (h->version != NGX_HTTP_CACHE_VERSION) {
+        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                      "cache file \"%s\" version mismatch", c->file.name.data);
+        return NGX_DECLINED;
+    }
+
+    if (h->crc32 != c->crc32 || (size_t) h->header_start != c->header_start) {
+        ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
+                      "cache file \"%s\" has md5 collision", c->file.name.data);
+        return NGX_DECLINED;
+    }
+
+    p = c->buf->pos + sizeof(ngx_http_file_cache_header_t)
+        + sizeof(ngx_http_file_cache_key);
+
+    key = c->keys.elts;
+    for (i = 0; i < c->keys.nelts; i++) {
+        if (ngx_memcmp(p, key[i].data, key[i].len) != 0) {
+            ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
+                          "cache file \"%s\" has md5 collision",
+                          c->file.name.data);
+            return NGX_DECLINED;
+        }
+
+        p += key[i].len;
+    }
+
+    if ((size_t) h->body_start > c->body_start) {
+        ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
+                      "cache file \"%s\" has too long header",
+                      c->file.name.data);
+        return NGX_DECLINED;
+    }
+
+    if (h->vary_len > NGX_HTTP_CACHE_VARY_LEN) {
+        ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
+                      "cache file \"%s\" has incorrect vary length",
+                      c->file.name.data);
+        return NGX_DECLINED;
+    }
+
+    if (h->vary_len) {
+        ngx_http_file_cache_vary(r, h->vary, h->vary_len, c->variant);
+
+        if (ngx_memcmp(c->variant, h->variant, NGX_HTTP_CACHE_KEY_LEN) != 0) {
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http file cache vary mismatch");
+            return ngx_http_file_cache_reopen(r, c);
+        }
+    }
+
+    c->buf->last += n;
+
+    c->valid_sec = h->valid_sec;
+    c->updating_sec = h->updating_sec;
+    c->error_sec = h->error_sec;
+    c->last_modified = h->last_modified;
+    c->date = h->date;
+    c->valid_msec = h->valid_msec;
+    c->body_start = h->body_start;
+    c->etag.len = h->etag_len;
+    c->etag.data = h->etag;
+
+    r->cached = 1;
+
+    cache = c->file_cache;
+
+    if (cache->sh->cold) {
+
+        ngx_shmtx_lock(&cache->shpool->mutex);
+
+        if (!c->node->exists) {
+            c->node->uses = 1;
+            c->node->body_start = c->body_start;
+            c->node->exists = 1;
+            c->node->uniq = c->uniq;
+            c->node->fs_size = c->fs_size;
+
+            cache->sh->size += c->fs_size;
+        }
+
+        ngx_shmtx_unlock(&cache->shpool->mutex);
+    }
+
+    now = ngx_time();
+
+    if (c->valid_sec < now) {
+        c->stale_updating = c->valid_sec + c->updating_sec >= now;
+        c->stale_error = c->valid_sec + c->error_sec >= now;
+
+        ngx_shmtx_lock(&cache->shpool->mutex);
+
+        if (c->node->updating) {
+            rc = NGX_HTTP_CACHE_UPDATING;
+
+        } else {
+            c->node->updating = 1;
+            c->updating = 1;
+            c->lock_time = c->node->lock_time;
+            rc = NGX_HTTP_CACHE_STALE;
+        }
+
+        ngx_shmtx_unlock(&cache->shpool->mutex);
+
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http file cache expired: %i %T %T",
+                       rc, c->valid_sec, now);
+
+        return rc;
+    }
+
+    return NGX_OK;
+}
+
+
+static ssize_t
+ngx_http_file_cache_aio_read(ngx_http_request_t *r, ngx_http_cache_t *c)
+{
+#if (NGX_HAVE_FILE_AIO || NGX_THREADS)
+    ssize_t                    n;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+#endif
+
+#if (NGX_HAVE_FILE_AIO)
+
+    if (clcf->aio == NGX_HTTP_AIO_ON && ngx_file_aio) {
+        n = ngx_file_aio_read(&c->file, c->buf->pos, c->body_start, 0, r->pool);
+
+        if (n != NGX_AGAIN) {
+            c->reading = 0;
+            return n;
+        }
+
+        c->reading = 1;
+
+        c->file.aio->data = r;
+        c->file.aio->handler = ngx_http_cache_aio_event_handler;
+
+        r->main->blocked++;
+        r->aio = 1;
+
+        return NGX_AGAIN;
+    }
+
+#endif
+
+#if (NGX_THREADS)
+
+    if (clcf->aio == NGX_HTTP_AIO_THREADS) {
+        c->file.thread_task = c->thread_task;
+        c->file.thread_handler = ngx_http_cache_thread_handler;
+        c->file.thread_ctx = r;
+
+        n = ngx_thread_read(&c->file, c->buf->pos, c->body_start, 0, r->pool);
+
+        c->thread_task = c->file.thread_task;
+        c->reading = (n == NGX_AGAIN);
+
+        return n;
+    }
+
+#endif
+
+    return ngx_read_file(&c->file, c->buf->pos, c->body_start, 0);
+}
+
+
+#if (NGX_HAVE_FILE_AIO)
+
+static void
+ngx_http_cache_aio_event_handler(ngx_event_t *ev)
+{
+    ngx_event_aio_t     *aio;
+    ngx_connection_t    *c;
+    ngx_http_request_t  *r;
+
+    aio = ev->data;
+    r = aio->data;
+    c = r->connection;
+
+    ngx_http_set_log_request(c->log, r);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http file cache aio: \"%V?%V\"", &r->uri, &r->args);
+
+    r->main->blocked--;
+    r->aio = 0;
+
+    r->write_event_handler(r);
+
+    ngx_http_run_posted_requests(c);
+}
+
+#endif
+
+
+#if (NGX_THREADS)
+
+static ngx_int_t
+ngx_http_cache_thread_handler(ngx_thread_task_t *task, ngx_file_t *file)
+{
+    ngx_str_t                  name;
+    ngx_thread_pool_t         *tp;
+    ngx_http_request_t        *r;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    r = file->thread_ctx;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+    tp = clcf->thread_pool;
+
+    if (tp == NULL) {
+        if (ngx_http_complex_value(r, clcf->thread_pool_value, &name)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+
+        tp = ngx_thread_pool_get((ngx_cycle_t *) ngx_cycle, &name);
+
+        if (tp == NULL) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "thread pool \"%V\" not found", &name);
+            return NGX_ERROR;
+        }
+    }
+
+    task->event.data = r;
+    task->event.handler = ngx_http_cache_thread_event_handler;
+
+    if (ngx_thread_task_post(tp, task) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    r->main->blocked++;
+    r->aio = 1;
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_cache_thread_event_handler(ngx_event_t *ev)
+{
+    ngx_connection_t    *c;
+    ngx_http_request_t  *r;
+
+    r = ev->data;
+    c = r->connection;
+
+    ngx_http_set_log_request(c->log, r);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http file cache thread: \"%V?%V\"", &r->uri, &r->args);
+
+    r->main->blocked--;
+    r->aio = 0;
+
+    r->write_event_handler(r);
+
+    ngx_http_run_posted_requests(c);
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_file_cache_exists(ngx_http_file_cache_t *cache, ngx_http_cache_t *c)
+{
+    ngx_int_t                    rc;
+    ngx_http_file_cache_node_t  *fcn;
+
+    ngx_shmtx_lock(&cache->shpool->mutex);
+
+    fcn = c->node;
+
+    if (fcn == NULL) {
+        fcn = ngx_http_file_cache_lookup(cache, c->key);
+    }
+
+    if (fcn) {
+        ngx_queue_remove(&fcn->queue);
+
+        if (c->node == NULL) {
+            fcn->uses++;
+            fcn->count++;
+        }
+
+        if (fcn->error) {
+
+            if (fcn->valid_sec < ngx_time()) {
+                goto renew;
+            }
+
+            rc = NGX_OK;
+
+            goto done;
+        }
+
+        if (fcn->exists || fcn->uses >= c->min_uses) {
+
+            c->exists = fcn->exists;
+            if (fcn->body_start) {
+                c->body_start = fcn->body_start;
+            }
+
+            rc = NGX_OK;
+
+            goto done;
+        }
+
+        rc = NGX_AGAIN;
+
+        goto done;
+    }
+
+    fcn = ngx_slab_calloc_locked(cache->shpool,
+                                 sizeof(ngx_http_file_cache_node_t));
+    if (fcn == NULL) {
+        ngx_http_file_cache_set_watermark(cache);
+
+        ngx_shmtx_unlock(&cache->shpool->mutex);
+
+        (void) ngx_http_file_cache_forced_expire(cache);
+
+        ngx_shmtx_lock(&cache->shpool->mutex);
+
+        fcn = ngx_slab_calloc_locked(cache->shpool,
+                                     sizeof(ngx_http_file_cache_node_t));
+        if (fcn == NULL) {
+            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+                          "could not allocate node%s", cache->shpool->log_ctx);
+            rc = NGX_ERROR;
+            goto failed;
+        }
+    }
+
+    cache->sh->count++;
+
+    ngx_memcpy((u_char *) &fcn->node.key, c->key, sizeof(ngx_rbtree_key_t));
+
+    ngx_memcpy(fcn->key, &c->key[sizeof(ngx_rbtree_key_t)],
+               NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t));
+
+    ngx_rbtree_insert(&cache->sh->rbtree, &fcn->node);
+
+    fcn->uses = 1;
+    fcn->count = 1;
+
+renew:
+
+    rc = NGX_DECLINED;
+
+    fcn->valid_msec = 0;
+    fcn->error = 0;
+    fcn->exists = 0;
+    fcn->valid_sec = 0;
+    fcn->uniq = 0;
+    fcn->body_start = 0;
+    fcn->fs_size = 0;
+
+done:
+
+    fcn->expire = ngx_time() + cache->inactive;
+
+    ngx_queue_insert_head(&cache->sh->queue, &fcn->queue);
+
+    c->uniq = fcn->uniq;
+    c->error = fcn->error;
+    c->node = fcn;
+
+failed:
+
+    ngx_shmtx_unlock(&cache->shpool->mutex);
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_name(ngx_http_request_t *r, ngx_path_t *path)
+{
+    u_char            *p;
+    ngx_http_cache_t  *c;
+
+    c = r->cache;
+
+    if (c->file.name.len) {
+        return NGX_OK;
+    }
+
+    c->file.name.len = path->name.len + 1 + path->len
+                       + 2 * NGX_HTTP_CACHE_KEY_LEN;
+
+    c->file.name.data = ngx_pnalloc(r->pool, c->file.name.len + 1);
+    if (c->file.name.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(c->file.name.data, path->name.data, path->name.len);
+
+    p = c->file.name.data + path->name.len + 1 + path->len;
+    p = ngx_hex_dump(p, c->key, NGX_HTTP_CACHE_KEY_LEN);
+    *p = '\0';
+
+    ngx_create_hashed_filename(path, c->file.name.data, c->file.name.len);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "cache file: \"%s\"", c->file.name.data);
+
+    return NGX_OK;
+}
+
+
+static ngx_http_file_cache_node_t *
+ngx_http_file_cache_lookup(ngx_http_file_cache_t *cache, u_char *key)
+{
+    ngx_int_t                    rc;
+    ngx_rbtree_key_t             node_key;
+    ngx_rbtree_node_t           *node, *sentinel;
+    ngx_http_file_cache_node_t  *fcn;
+
+    ngx_memcpy((u_char *) &node_key, key, sizeof(ngx_rbtree_key_t));
+
+    node = cache->sh->rbtree.root;
+    sentinel = cache->sh->rbtree.sentinel;
+
+    while (node != sentinel) {
+
+        if (node_key < node->key) {
+            node = node->left;
+            continue;
+        }
+
+        if (node_key > node->key) {
+            node = node->right;
+            continue;
+        }
+
+        /* node_key == node->key */
+
+        fcn = (ngx_http_file_cache_node_t *) node;
+
+        rc = ngx_memcmp(&key[sizeof(ngx_rbtree_key_t)], fcn->key,
+                        NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t));
+
+        if (rc == 0) {
+            return fcn;
+        }
+
+        node = (rc < 0) ? node->left : node->right;
+    }
+
+    /* not found */
+
+    return NULL;
+}
+
+
+static void
+ngx_http_file_cache_rbtree_insert_value(ngx_rbtree_node_t *temp,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
+{
+    ngx_rbtree_node_t           **p;
+    ngx_http_file_cache_node_t   *cn, *cnt;
+
+    for ( ;; ) {
+
+        if (node->key < temp->key) {
+
+            p = &temp->left;
+
+        } else if (node->key > temp->key) {
+
+            p = &temp->right;
+
+        } else { /* node->key == temp->key */
+
+            cn = (ngx_http_file_cache_node_t *) node;
+            cnt = (ngx_http_file_cache_node_t *) temp;
+
+            p = (ngx_memcmp(cn->key, cnt->key,
+                            NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t))
+                 < 0)
+                    ? &temp->left : &temp->right;
+        }
+
+        if (*p == sentinel) {
+            break;
+        }
+
+        temp = *p;
+    }
+
+    *p = node;
+    node->parent = temp;
+    node->left = sentinel;
+    node->right = sentinel;
+    ngx_rbt_red(node);
+}
+
+
+static void
+ngx_http_file_cache_vary(ngx_http_request_t *r, u_char *vary, size_t len,
+    u_char *hash)
+{
+    u_char     *p, *last;
+    ngx_str_t   name;
+    ngx_md5_t   md5;
+    u_char      buf[NGX_HTTP_CACHE_VARY_LEN];
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http file cache vary: \"%*s\"", len, vary);
+
+    ngx_md5_init(&md5);
+    ngx_md5_update(&md5, r->cache->main, NGX_HTTP_CACHE_KEY_LEN);
+
+    ngx_strlow(buf, vary, len);
+
+    p = buf;
+    last = buf + len;
+
+    while (p < last) {
+
+        while (p < last && (*p == ' ' || *p == ',')) { p++; }
+
+        name.data = p;
+
+        while (p < last && *p != ',' && *p != ' ') { p++; }
+
+        name.len = p - name.data;
+
+        if (name.len == 0) {
+            break;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http file cache vary: %V", &name);
+
+        ngx_md5_update(&md5, name.data, name.len);
+        ngx_md5_update(&md5, (u_char *) ":", sizeof(":") - 1);
+
+        ngx_http_file_cache_vary_header(r, &md5, &name);
+
+        ngx_md5_update(&md5, (u_char *) CRLF, sizeof(CRLF) - 1);
+    }
+
+    ngx_md5_final(hash, &md5);
+}
+
+
+static void
+ngx_http_file_cache_vary_header(ngx_http_request_t *r, ngx_md5_t *md5,
+    ngx_str_t *name)
+{
+    size_t            len;
+    u_char           *p, *start, *last;
+    ngx_uint_t        i, multiple, normalize;
+    ngx_list_part_t  *part;
+    ngx_table_elt_t  *header;
+
+    multiple = 0;
+    normalize = 0;
+
+    if (name->len == sizeof("Accept-Charset") - 1
+        && ngx_strncasecmp(name->data, (u_char *) "Accept-Charset",
+                           sizeof("Accept-Charset") - 1) == 0)
+    {
+        normalize = 1;
+
+    } else if (name->len == sizeof("Accept-Encoding") - 1
+        && ngx_strncasecmp(name->data, (u_char *) "Accept-Encoding",
+                           sizeof("Accept-Encoding") - 1) == 0)
+    {
+        normalize = 1;
+
+    } else if (name->len == sizeof("Accept-Language") - 1
+        && ngx_strncasecmp(name->data, (u_char *) "Accept-Language",
+                           sizeof("Accept-Language") - 1) == 0)
+    {
+        normalize = 1;
+    }
+
+    part = &r->headers_in.headers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */ ; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (header[i].hash == 0) {
+            continue;
+        }
+
+        if (header[i].key.len != name->len) {
+            continue;
+        }
+
+        if (ngx_strncasecmp(header[i].key.data, name->data, name->len) != 0) {
+            continue;
+        }
+
+        if (!normalize) {
+
+            if (multiple) {
+                ngx_md5_update(md5, (u_char *) ",", sizeof(",") - 1);
+            }
+
+            ngx_md5_update(md5, header[i].value.data, header[i].value.len);
+
+            multiple = 1;
+
+            continue;
+        }
+
+        /* normalize spaces */
+
+        p = header[i].value.data;
+        last = p + header[i].value.len;
+
+        while (p < last) {
+
+            while (p < last && (*p == ' ' || *p == ',')) { p++; }
+
+            start = p;
+
+            while (p < last && *p != ',' && *p != ' ') { p++; }
+
+            len = p - start;
+
+            if (len == 0) {
+                break;
+            }
+
+            if (multiple) {
+                ngx_md5_update(md5, (u_char *) ",", sizeof(",") - 1);
+            }
+
+            ngx_md5_update(md5, start, len);
+
+            multiple = 1;
+        }
+    }
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_reopen(ngx_http_request_t *r, ngx_http_cache_t *c)
+{
+    ngx_http_file_cache_t  *cache;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->file.log, 0,
+                   "http file cache reopen");
+
+    if (c->secondary) {
+        ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
+                      "cache file \"%s\" has incorrect vary hash",
+                      c->file.name.data);
+        return NGX_DECLINED;
+    }
+
+    cache = c->file_cache;
+
+    ngx_shmtx_lock(&cache->shpool->mutex);
+
+    c->node->count--;
+    c->node = NULL;
+
+    ngx_shmtx_unlock(&cache->shpool->mutex);
+
+    c->secondary = 1;
+    c->file.name.len = 0;
+    c->body_start = c->buf->end - c->buf->start;
+
+    ngx_memcpy(c->key, c->variant, NGX_HTTP_CACHE_KEY_LEN);
+
+    return ngx_http_file_cache_open(r);
+}
+
+
+ngx_int_t
+ngx_http_file_cache_set_header(ngx_http_request_t *r, u_char *buf)
+{
+    ngx_http_file_cache_header_t  *h = (ngx_http_file_cache_header_t *) buf;
+
+    u_char            *p;
+    ngx_str_t         *key;
+    ngx_uint_t         i;
+    ngx_http_cache_t  *c;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http file cache set header");
+
+    c = r->cache;
+
+    ngx_memzero(h, sizeof(ngx_http_file_cache_header_t));
+
+    h->version = NGX_HTTP_CACHE_VERSION;
+    h->valid_sec = c->valid_sec;
+    h->updating_sec = c->updating_sec;
+    h->error_sec = c->error_sec;
+    h->last_modified = c->last_modified;
+    h->date = c->date;
+    h->crc32 = c->crc32;
+    h->valid_msec = (u_short) c->valid_msec;
+    h->header_start = (u_short) c->header_start;
+    h->body_start = (u_short) c->body_start;
+
+    if (c->etag.len <= NGX_HTTP_CACHE_ETAG_LEN) {
+        h->etag_len = (u_char) c->etag.len;
+        ngx_memcpy(h->etag, c->etag.data, c->etag.len);
+    }
+
+    if (c->vary.len) {
+        if (c->vary.len > NGX_HTTP_CACHE_VARY_LEN) {
+            /* should not happen */
+            c->vary.len = NGX_HTTP_CACHE_VARY_LEN;
+        }
+
+        h->vary_len = (u_char) c->vary.len;
+        ngx_memcpy(h->vary, c->vary.data, c->vary.len);
+
+        ngx_http_file_cache_vary(r, c->vary.data, c->vary.len, c->variant);
+        ngx_memcpy(h->variant, c->variant, NGX_HTTP_CACHE_KEY_LEN);
+    }
+
+    if (ngx_http_file_cache_update_variant(r, c) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    p = buf + sizeof(ngx_http_file_cache_header_t);
+
+    p = ngx_cpymem(p, ngx_http_file_cache_key, sizeof(ngx_http_file_cache_key));
+
+    key = c->keys.elts;
+    for (i = 0; i < c->keys.nelts; i++) {
+        p = ngx_copy(p, key[i].data, key[i].len);
+    }
+
+    *p = LF;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_update_variant(ngx_http_request_t *r, ngx_http_cache_t *c)
+{
+    ngx_http_file_cache_t  *cache;
+
+    if (!c->secondary) {
+        return NGX_OK;
+    }
+
+    if (c->vary.len
+        && ngx_memcmp(c->variant, c->key, NGX_HTTP_CACHE_KEY_LEN) == 0)
+    {
+        return NGX_OK;
+    }
+
+    /*
+     * if the variant hash doesn't match one we used as a secondary
+     * cache key, switch back to the original key
+     */
+
+    cache = c->file_cache;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http file cache main key");
+
+    ngx_shmtx_lock(&cache->shpool->mutex);
+
+    c->node->count--;
+    c->node->updating = 0;
+    c->node = NULL;
+
+    ngx_shmtx_unlock(&cache->shpool->mutex);
+
+    c->file.name.len = 0;
+
+    ngx_memcpy(c->key, c->main, NGX_HTTP_CACHE_KEY_LEN);
+
+    if (ngx_http_file_cache_exists(cache, c) == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_http_file_cache_name(r, cache->path) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+void
+ngx_http_file_cache_update(ngx_http_request_t *r, ngx_temp_file_t *tf)
+{
+    off_t                   fs_size;
+    ngx_int_t               rc;
+    ngx_file_uniq_t         uniq;
+    ngx_file_info_t         fi;
+    ngx_http_cache_t        *c;
+    ngx_ext_rename_file_t   ext;
+    ngx_http_file_cache_t  *cache;
+
+    c = r->cache;
+
+    if (c->updated) {
+        return;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http file cache update");
+
+    cache = c->file_cache;
+
+    c->updated = 1;
+    c->updating = 0;
+
+    uniq = 0;
+    fs_size = 0;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http file cache rename: \"%s\" to \"%s\"",
+                   tf->file.name.data, c->file.name.data);
+
+    ext.access = NGX_FILE_OWNER_ACCESS;
+    ext.path_access = NGX_FILE_OWNER_ACCESS;
+    ext.time = -1;
+    ext.create_path = 1;
+    ext.delete_file = 1;
+    ext.log = r->connection->log;
+
+    rc = ngx_ext_rename_file(&tf->file.name, &c->file.name, &ext);
+
+    if (rc == NGX_OK) {
+
+        if (ngx_fd_info(tf->file.fd, &fi) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+                          ngx_fd_info_n " \"%s\" failed", tf->file.name.data);
+
+            rc = NGX_ERROR;
+
+        } else {
+            uniq = ngx_file_uniq(&fi);
+            fs_size = (ngx_file_fs_size(&fi) + cache->bsize - 1) / cache->bsize;
+        }
+    }
+
+    ngx_shmtx_lock(&cache->shpool->mutex);
+
+    c->node->count--;
+    c->node->error = 0;
+    c->node->uniq = uniq;
+    c->node->body_start = c->body_start;
+
+    cache->sh->size += fs_size - c->node->fs_size;
+    c->node->fs_size = fs_size;
+
+    if (rc == NGX_OK) {
+        c->node->exists = 1;
+    }
+
+    c->node->updating = 0;
+
+    ngx_shmtx_unlock(&cache->shpool->mutex);
+}
+
+
+void
+ngx_http_file_cache_update_header(ngx_http_request_t *r)
+{
+    ssize_t                        n;
+    ngx_err_t                      err;
+    ngx_file_t                     file;
+    ngx_file_info_t                fi;
+    ngx_http_cache_t              *c;
+    ngx_http_file_cache_header_t   h;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http file cache update header");
+
+    c = r->cache;
+
+    ngx_memzero(&file, sizeof(ngx_file_t));
+
+    file.name = c->file.name;
+    file.log = r->connection->log;
+    file.fd = ngx_open_file(file.name.data, NGX_FILE_RDWR, NGX_FILE_OPEN, 0);
+
+    if (file.fd == NGX_INVALID_FILE) {
+        err = ngx_errno;
+
+        /* cache file may have been deleted */
+
+        if (err == NGX_ENOENT) {
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http file cache \"%s\" not found",
+                           file.name.data);
+            return;
+        }
+
+        ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
+                      ngx_open_file_n " \"%s\" failed", file.name.data);
+        return;
+    }
+
+    /*
+     * make sure cache file wasn't replaced;
+     * if it was, do nothing
+     */
+
+    if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+                      ngx_fd_info_n " \"%s\" failed", file.name.data);
+        goto done;
+    }
+
+    if (c->uniq != ngx_file_uniq(&fi)
+        || c->length != ngx_file_size(&fi))
+    {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http file cache \"%s\" changed",
+                       file.name.data);
+        goto done;
+    }
+
+    n = ngx_read_file(&file, (u_char *) &h,
+                      sizeof(ngx_http_file_cache_header_t), 0);
+
+    if (n == NGX_ERROR) {
+        goto done;
+    }
+
+    if ((size_t) n != sizeof(ngx_http_file_cache_header_t)) {
+        ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
+                      ngx_read_file_n " read only %z of %z from \"%s\"",
+                      n, sizeof(ngx_http_file_cache_header_t), file.name.data);
+        goto done;
+    }
+
+    if (h.version != NGX_HTTP_CACHE_VERSION
+        || h.last_modified != c->last_modified
+        || h.crc32 != c->crc32
+        || (size_t) h.header_start != c->header_start
+        || (size_t) h.body_start != c->body_start)
+    {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http file cache \"%s\" content changed",
+                       file.name.data);
+        goto done;
+    }
+
+    /*
+     * update cache file header with new data,
+     * notably h.valid_sec and h.date
+     */
+
+    ngx_memzero(&h, sizeof(ngx_http_file_cache_header_t));
+
+    h.version = NGX_HTTP_CACHE_VERSION;
+    h.valid_sec = c->valid_sec;
+    h.updating_sec = c->updating_sec;
+    h.error_sec = c->error_sec;
+    h.last_modified = c->last_modified;
+    h.date = c->date;
+    h.crc32 = c->crc32;
+    h.valid_msec = (u_short) c->valid_msec;
+    h.header_start = (u_short) c->header_start;
+    h.body_start = (u_short) c->body_start;
+
+    if (c->etag.len <= NGX_HTTP_CACHE_ETAG_LEN) {
+        h.etag_len = (u_char) c->etag.len;
+        ngx_memcpy(h.etag, c->etag.data, c->etag.len);
+    }
+
+    if (c->vary.len) {
+        if (c->vary.len > NGX_HTTP_CACHE_VARY_LEN) {
+            /* should not happen */
+            c->vary.len = NGX_HTTP_CACHE_VARY_LEN;
+        }
+
+        h.vary_len = (u_char) c->vary.len;
+        ngx_memcpy(h.vary, c->vary.data, c->vary.len);
+
+        ngx_http_file_cache_vary(r, c->vary.data, c->vary.len, c->variant);
+        ngx_memcpy(h.variant, c->variant, NGX_HTTP_CACHE_KEY_LEN);
+    }
+
+    (void) ngx_write_file(&file, (u_char *) &h,
+                          sizeof(ngx_http_file_cache_header_t), 0);
+
+done:
+
+    if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
+                      ngx_close_file_n " \"%s\" failed", file.name.data);
+    }
+}
+
+
+ngx_int_t
+ngx_http_cache_send(ngx_http_request_t *r)
+{
+    ngx_int_t          rc;
+    ngx_buf_t         *b;
+    ngx_chain_t        out;
+    ngx_http_cache_t  *c;
+
+    c = r->cache;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http file cache send: %s", c->file.name.data);
+
+    if (r != r->main && c->length - c->body_start == 0) {
+        return ngx_http_send_header(r);
+    }
+
+    /* we need to allocate all before the header would be sent */
+
+    b = ngx_calloc_buf(r->pool);
+    if (b == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
+    if (b->file == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    rc = ngx_http_send_header(r);
+
+    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+        return rc;
+    }
+
+    b->file_pos = c->body_start;
+    b->file_last = c->length;
+
+    b->in_file = (c->length - c->body_start) ? 1: 0;
+    b->last_buf = (r == r->main) ? 1: 0;
+    b->last_in_chain = 1;
+
+    b->file->fd = c->file.fd;
+    b->file->name = c->file.name;
+    b->file->log = r->connection->log;
+
+    out.buf = b;
+    out.next = NULL;
+
+    return ngx_http_output_filter(r, &out);
+}
+
+
+void
+ngx_http_file_cache_free(ngx_http_cache_t *c, ngx_temp_file_t *tf)
+{
+    ngx_http_file_cache_t       *cache;
+    ngx_http_file_cache_node_t  *fcn;
+
+    if (c->updated || c->node == NULL) {
+        return;
+    }
+
+    cache = c->file_cache;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->file.log, 0,
+                   "http file cache free, fd: %d", c->file.fd);
+
+    ngx_shmtx_lock(&cache->shpool->mutex);
+
+    fcn = c->node;
+    fcn->count--;
+
+    if (c->updating && fcn->lock_time == c->lock_time) {
+        fcn->updating = 0;
+    }
+
+    if (c->error) {
+        fcn->error = c->error;
+
+        if (c->valid_sec) {
+            fcn->valid_sec = c->valid_sec;
+            fcn->valid_msec = c->valid_msec;
+        }
+
+    } else if (!fcn->exists && fcn->count == 0 && c->min_uses == 1) {
+        ngx_queue_remove(&fcn->queue);
+        ngx_rbtree_delete(&cache->sh->rbtree, &fcn->node);
+        ngx_slab_free_locked(cache->shpool, fcn);
+        cache->sh->count--;
+        c->node = NULL;
+    }
+
+    ngx_shmtx_unlock(&cache->shpool->mutex);
+
+    c->updated = 1;
+    c->updating = 0;
+
+    if (c->temp_file) {
+        if (tf && tf->file.fd != NGX_INVALID_FILE) {
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->file.log, 0,
+                           "http file cache incomplete: \"%s\"",
+                           tf->file.name.data);
+
+            if (ngx_delete_file(tf->file.name.data) == NGX_FILE_ERROR) {
+                ngx_log_error(NGX_LOG_CRIT, c->file.log, ngx_errno,
+                              ngx_delete_file_n " \"%s\" failed",
+                              tf->file.name.data);
+            }
+        }
+    }
+
+    if (c->wait_event.timer_set) {
+        ngx_del_timer(&c->wait_event);
+    }
+}
+
+
+static void
+ngx_http_file_cache_cleanup(void *data)
+{
+    ngx_http_cache_t  *c = data;
+
+    if (c->updated) {
+        return;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->file.log, 0,
+                   "http file cache cleanup");
+
+    if (c->updating && !c->background) {
+        ngx_log_error(NGX_LOG_ALERT, c->file.log, 0,
+                      "stalled cache updating, error:%ui", c->error);
+    }
+
+    ngx_http_file_cache_free(c, NULL);
+}
+
+
+static time_t
+ngx_http_file_cache_forced_expire(ngx_http_file_cache_t *cache)
+{
+    u_char                      *name, *p;
+    size_t                       len;
+    time_t                       wait;
+    ngx_uint_t                   tries;
+    ngx_path_t                  *path;
+    ngx_queue_t                 *q, *sentinel;
+    ngx_http_file_cache_node_t  *fcn;
+    u_char                       key[2 * NGX_HTTP_CACHE_KEY_LEN];
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+                   "http file cache forced expire");
+
+    path = cache->path;
+    len = path->name.len + 1 + path->len + 2 * NGX_HTTP_CACHE_KEY_LEN;
+
+    name = ngx_alloc(len + 1, ngx_cycle->log);
+    if (name == NULL) {
+        return 10;
+    }
+
+    ngx_memcpy(name, path->name.data, path->name.len);
+
+    wait = 10;
+    tries = 20;
+    sentinel = NULL;
+
+    ngx_shmtx_lock(&cache->shpool->mutex);
+
+    for ( ;; ) {
+        if (ngx_queue_empty(&cache->sh->queue)) {
+            break;
+        }
+
+        q = ngx_queue_last(&cache->sh->queue);
+
+        if (q == sentinel) {
+            break;
+        }
+
+        fcn = ngx_queue_data(q, ngx_http_file_cache_node_t, queue);
+
+        ngx_log_debug6(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+                  "http file cache forced expire: #%d %d %02xd%02xd%02xd%02xd",
+                  fcn->count, fcn->exists,
+                  fcn->key[0], fcn->key[1], fcn->key[2], fcn->key[3]);
+
+        if (fcn->count == 0) {
+            ngx_http_file_cache_delete(cache, q, name);
+            wait = 0;
+            break;
+        }
+
+        p = ngx_hex_dump(key, (u_char *) &fcn->node.key,
+                         sizeof(ngx_rbtree_key_t));
+        len = NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t);
+        (void) ngx_hex_dump(p, fcn->key, len);
+
+        /*
+         * abnormally exited workers may leave locked cache entries,
+         * and although it may be safe to remove them completely,
+         * we prefer to just move them to the top of the inactive queue
+         */
+
+        ngx_queue_remove(q);
+        fcn->expire = ngx_time() + cache->inactive;
+        ngx_queue_insert_head(&cache->sh->queue, &fcn->queue);
+
+        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+                      "ignore long locked inactive cache entry %*s, count:%d",
+                      (size_t) 2 * NGX_HTTP_CACHE_KEY_LEN, key, fcn->count);
+
+        if (sentinel == NULL) {
+            sentinel = q;
+        }
+
+        if (--tries) {
+            continue;
+        }
+
+        wait = 1;
+        break;
+    }
+
+    ngx_shmtx_unlock(&cache->shpool->mutex);
+
+    ngx_free(name);
+
+    return wait;
+}
+
+
+static time_t
+ngx_http_file_cache_expire(ngx_http_file_cache_t *cache)
+{
+    u_char                      *name, *p;
+    size_t                       len;
+    time_t                       now, wait;
+    ngx_path_t                  *path;
+    ngx_msec_t                   elapsed;
+    ngx_queue_t                 *q;
+    ngx_http_file_cache_node_t  *fcn;
+    u_char                       key[2 * NGX_HTTP_CACHE_KEY_LEN];
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+                   "http file cache expire");
+
+    path = cache->path;
+    len = path->name.len + 1 + path->len + 2 * NGX_HTTP_CACHE_KEY_LEN;
+
+    name = ngx_alloc(len + 1, ngx_cycle->log);
+    if (name == NULL) {
+        return 10;
+    }
+
+    ngx_memcpy(name, path->name.data, path->name.len);
+
+    now = ngx_time();
+
+    ngx_shmtx_lock(&cache->shpool->mutex);
+
+    for ( ;; ) {
+
+        if (ngx_quit || ngx_terminate) {
+            wait = 1;
+            break;
+        }
+
+        if (ngx_queue_empty(&cache->sh->queue)) {
+            wait = 10;
+            break;
+        }
+
+        q = ngx_queue_last(&cache->sh->queue);
+
+        fcn = ngx_queue_data(q, ngx_http_file_cache_node_t, queue);
+
+        wait = fcn->expire - now;
+
+        if (wait > 0) {
+            wait = wait > 10 ? 10 : wait;
+            break;
+        }
+
+        ngx_log_debug6(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+                       "http file cache expire: #%d %d %02xd%02xd%02xd%02xd",
+                       fcn->count, fcn->exists,
+                       fcn->key[0], fcn->key[1], fcn->key[2], fcn->key[3]);
+
+        if (fcn->count == 0) {
+            ngx_http_file_cache_delete(cache, q, name);
+            goto next;
+        }
+
+        if (fcn->deleting) {
+            wait = 1;
+            break;
+        }
+
+        p = ngx_hex_dump(key, (u_char *) &fcn->node.key,
+                         sizeof(ngx_rbtree_key_t));
+        len = NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t);
+        (void) ngx_hex_dump(p, fcn->key, len);
+
+        /*
+         * abnormally exited workers may leave locked cache entries,
+         * and although it may be safe to remove them completely,
+         * we prefer to just move them to the top of the inactive queue
+         */
+
+        ngx_queue_remove(q);
+        fcn->expire = ngx_time() + cache->inactive;
+        ngx_queue_insert_head(&cache->sh->queue, &fcn->queue);
+
+        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+                      "ignore long locked inactive cache entry %*s, count:%d",
+                      (size_t) 2 * NGX_HTTP_CACHE_KEY_LEN, key, fcn->count);
+
+next:
+
+        if (++cache->files >= cache->manager_files) {
+            wait = 0;
+            break;
+        }
+
+        ngx_time_update();
+
+        elapsed = ngx_abs((ngx_msec_int_t) (ngx_current_msec - cache->last));
+
+        if (elapsed >= cache->manager_threshold) {
+            wait = 0;
+            break;
+        }
+    }
+
+    ngx_shmtx_unlock(&cache->shpool->mutex);
+
+    ngx_free(name);
+
+    return wait;
+}
+
+
+static void
+ngx_http_file_cache_delete(ngx_http_file_cache_t *cache, ngx_queue_t *q,
+    u_char *name)
+{
+    u_char                      *p;
+    size_t                       len;
+    ngx_path_t                  *path;
+    ngx_http_file_cache_node_t  *fcn;
+
+    fcn = ngx_queue_data(q, ngx_http_file_cache_node_t, queue);
+
+    if (fcn->exists) {
+        cache->sh->size -= fcn->fs_size;
+
+        path = cache->path;
+        p = name + path->name.len + 1 + path->len;
+        p = ngx_hex_dump(p, (u_char *) &fcn->node.key,
+                         sizeof(ngx_rbtree_key_t));
+        len = NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t);
+        p = ngx_hex_dump(p, fcn->key, len);
+        *p = '\0';
+
+        fcn->count++;
+        fcn->deleting = 1;
+        ngx_shmtx_unlock(&cache->shpool->mutex);
+
+        len = path->name.len + 1 + path->len + 2 * NGX_HTTP_CACHE_KEY_LEN;
+        ngx_create_hashed_filename(path, name, len);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+                       "http file cache expire: \"%s\"", name);
+
+        if (ngx_delete_file(name) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno,
+                          ngx_delete_file_n " \"%s\" failed", name);
+        }
+
+        ngx_shmtx_lock(&cache->shpool->mutex);
+        fcn->count--;
+        fcn->deleting = 0;
+    }
+
+    if (fcn->count == 0) {
+        ngx_queue_remove(q);
+        ngx_rbtree_delete(&cache->sh->rbtree, &fcn->node);
+        ngx_slab_free_locked(cache->shpool, fcn);
+        cache->sh->count--;
+    }
+}
+
+
+static ngx_msec_t
+ngx_http_file_cache_manager(void *data)
+{
+    ngx_http_file_cache_t  *cache = data;
+
+    off_t       size;
+    time_t      wait;
+    ngx_msec_t  elapsed, next;
+    ngx_uint_t  count, watermark;
+
+    cache->last = ngx_current_msec;
+    cache->files = 0;
+
+    next = (ngx_msec_t) ngx_http_file_cache_expire(cache) * 1000;
+
+    if (next == 0) {
+        next = cache->manager_sleep;
+        goto done;
+    }
+
+    for ( ;; ) {
+        ngx_shmtx_lock(&cache->shpool->mutex);
+
+        size = cache->sh->size;
+        count = cache->sh->count;
+        watermark = cache->sh->watermark;
+
+        ngx_shmtx_unlock(&cache->shpool->mutex);
+
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+                       "http file cache size: %O c:%ui w:%i",
+                       size, count, (ngx_int_t) watermark);
+
+        if (size < cache->max_size && count < watermark) {
+            break;
+        }
+
+        wait = ngx_http_file_cache_forced_expire(cache);
+
+        if (wait > 0) {
+            next = (ngx_msec_t) wait * 1000;
+            break;
+        }
+
+        if (ngx_quit || ngx_terminate) {
+            break;
+        }
+
+        if (++cache->files >= cache->manager_files) {
+            next = cache->manager_sleep;
+            break;
+        }
+
+        ngx_time_update();
+
+        elapsed = ngx_abs((ngx_msec_int_t) (ngx_current_msec - cache->last));
+
+        if (elapsed >= cache->manager_threshold) {
+            next = cache->manager_sleep;
+            break;
+        }
+    }
+
+done:
+
+    elapsed = ngx_abs((ngx_msec_int_t) (ngx_current_msec - cache->last));
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+                   "http file cache manager: %ui e:%M n:%M",
+                   cache->files, elapsed, next);
+
+    return next;
+}
+
+
+static void
+ngx_http_file_cache_loader(void *data)
+{
+    ngx_http_file_cache_t  *cache = data;
+
+    ngx_tree_ctx_t  tree;
+
+    if (!cache->sh->cold || cache->sh->loading) {
+        return;
+    }
+
+    if (!ngx_atomic_cmp_set(&cache->sh->loading, 0, ngx_pid)) {
+        return;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+                   "http file cache loader");
+
+    tree.init_handler = NULL;
+    tree.file_handler = ngx_http_file_cache_manage_file;
+    tree.pre_tree_handler = ngx_http_file_cache_manage_directory;
+    tree.post_tree_handler = ngx_http_file_cache_noop;
+    tree.spec_handler = ngx_http_file_cache_delete_file;
+    tree.data = cache;
+    tree.alloc = 0;
+    tree.log = ngx_cycle->log;
+
+    cache->last = ngx_current_msec;
+    cache->files = 0;
+
+    if (ngx_walk_tree(&tree, &cache->path->name) == NGX_ABORT) {
+        cache->sh->loading = 0;
+        return;
+    }
+
+    cache->sh->cold = 0;
+    cache->sh->loading = 0;
+
+    ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
+                  "http file cache: %V %.3fM, bsize: %uz",
+                  &cache->path->name,
+                  ((double) cache->sh->size * cache->bsize) / (1024 * 1024),
+                  cache->bsize);
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_noop(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_manage_file(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+    ngx_msec_t              elapsed;
+    ngx_http_file_cache_t  *cache;
+
+    cache = ctx->data;
+
+    if (ngx_http_file_cache_add_file(ctx, path) != NGX_OK) {
+        (void) ngx_http_file_cache_delete_file(ctx, path);
+    }
+
+    if (++cache->files >= cache->loader_files) {
+        ngx_http_file_cache_loader_sleep(cache);
+
+    } else {
+        ngx_time_update();
+
+        elapsed = ngx_abs((ngx_msec_int_t) (ngx_current_msec - cache->last));
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+                       "http file cache loader time elapsed: %M", elapsed);
+
+        if (elapsed >= cache->loader_threshold) {
+            ngx_http_file_cache_loader_sleep(cache);
+        }
+    }
+
+    return (ngx_quit || ngx_terminate) ? NGX_ABORT : NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_manage_directory(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+    if (path->len >= 5
+        && ngx_strncmp(path->data + path->len - 5, "/temp", 5) == 0)
+    {
+        return NGX_DECLINED;
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_file_cache_loader_sleep(ngx_http_file_cache_t *cache)
+{
+    ngx_msleep(cache->loader_sleep);
+
+    ngx_time_update();
+
+    cache->last = ngx_current_msec;
+    cache->files = 0;
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_add_file(ngx_tree_ctx_t *ctx, ngx_str_t *name)
+{
+    u_char                 *p;
+    ngx_int_t               n;
+    ngx_uint_t              i;
+    ngx_http_cache_t        c;
+    ngx_http_file_cache_t  *cache;
+
+    if (name->len < 2 * NGX_HTTP_CACHE_KEY_LEN) {
+        return NGX_ERROR;
+    }
+
+    /*
+     * Temporary files in cache have a suffix consisting of a dot
+     * followed by 10 digits.
+     */
+
+    if (name->len >= 2 * NGX_HTTP_CACHE_KEY_LEN + 1 + 10
+        && name->data[name->len - 10 - 1] == '.')
+    {
+        return NGX_OK;
+    }
+
+    if (ctx->size < (off_t) sizeof(ngx_http_file_cache_header_t)) {
+        ngx_log_error(NGX_LOG_CRIT, ctx->log, 0,
+                      "cache file \"%s\" is too small", name->data);
+        return NGX_ERROR;
+    }
+
+    ngx_memzero(&c, sizeof(ngx_http_cache_t));
+    cache = ctx->data;
+
+    c.length = ctx->size;
+    c.fs_size = (ctx->fs_size + cache->bsize - 1) / cache->bsize;
+
+    p = &name->data[name->len - 2 * NGX_HTTP_CACHE_KEY_LEN];
+
+    for (i = 0; i < NGX_HTTP_CACHE_KEY_LEN; i++) {
+        n = ngx_hextoi(p, 2);
+
+        if (n == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+
+        p += 2;
+
+        c.key[i] = (u_char) n;
+    }
+
+    return ngx_http_file_cache_add(cache, &c);
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_add(ngx_http_file_cache_t *cache, ngx_http_cache_t *c)
+{
+    ngx_http_file_cache_node_t  *fcn;
+
+    ngx_shmtx_lock(&cache->shpool->mutex);
+
+    fcn = ngx_http_file_cache_lookup(cache, c->key);
+
+    if (fcn == NULL) {
+
+        fcn = ngx_slab_calloc_locked(cache->shpool,
+                                     sizeof(ngx_http_file_cache_node_t));
+        if (fcn == NULL) {
+            ngx_http_file_cache_set_watermark(cache);
+
+            if (cache->fail_time != ngx_time()) {
+                cache->fail_time = ngx_time();
+                ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+                           "could not allocate node%s", cache->shpool->log_ctx);
+            }
+
+            ngx_shmtx_unlock(&cache->shpool->mutex);
+            return NGX_ERROR;
+        }
+
+        cache->sh->count++;
+
+        ngx_memcpy((u_char *) &fcn->node.key, c->key, sizeof(ngx_rbtree_key_t));
+
+        ngx_memcpy(fcn->key, &c->key[sizeof(ngx_rbtree_key_t)],
+                   NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t));
+
+        ngx_rbtree_insert(&cache->sh->rbtree, &fcn->node);
+
+        fcn->uses = 1;
+        fcn->exists = 1;
+        fcn->fs_size = c->fs_size;
+
+        cache->sh->size += c->fs_size;
+
+    } else {
+        ngx_queue_remove(&fcn->queue);
+    }
+
+    fcn->expire = ngx_time() + cache->inactive;
+
+    ngx_queue_insert_head(&cache->sh->queue, &fcn->queue);
+
+    ngx_shmtx_unlock(&cache->shpool->mutex);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+                   "http file cache delete: \"%s\"", path->data);
+
+    if (ngx_delete_file(path->data) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
+                      ngx_delete_file_n " \"%s\" failed", path->data);
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_file_cache_set_watermark(ngx_http_file_cache_t *cache)
+{
+    cache->sh->watermark = cache->sh->count - cache->sh->count / 8;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+                   "http file cache watermark: %ui", cache->sh->watermark);
+}
+
+
+time_t
+ngx_http_file_cache_valid(ngx_array_t *cache_valid, ngx_uint_t status)
+{
+    ngx_uint_t               i;
+    ngx_http_cache_valid_t  *valid;
+
+    if (cache_valid == NULL) {
+        return 0;
+    }
+
+    valid = cache_valid->elts;
+    for (i = 0; i < cache_valid->nelts; i++) {
+
+        if (valid[i].status == 0) {
+            return valid[i].valid;
+        }
+
+        if (valid[i].status == status) {
+            return valid[i].valid;
+        }
+    }
+
+    return 0;
+}
+
+
+char *
+ngx_http_file_cache_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *confp = conf;
+
+    off_t                   max_size;
+    u_char                 *last, *p;
+    time_t                  inactive;
+    ssize_t                 size;
+    ngx_str_t               s, name, *value;
+    ngx_int_t               loader_files, manager_files;
+    ngx_msec_t              loader_sleep, manager_sleep, loader_threshold,
+                            manager_threshold;
+    ngx_uint_t              i, n, use_temp_path;
+    ngx_array_t            *caches;
+    ngx_http_file_cache_t  *cache, **ce;
+
+    cache = ngx_pcalloc(cf->pool, sizeof(ngx_http_file_cache_t));
+    if (cache == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    cache->path = ngx_pcalloc(cf->pool, sizeof(ngx_path_t));
+    if (cache->path == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    use_temp_path = 1;
+
+    inactive = 600;
+
+    loader_files = 100;
+    loader_sleep = 50;
+    loader_threshold = 200;
+
+    manager_files = 100;
+    manager_sleep = 50;
+    manager_threshold = 200;
+
+    name.len = 0;
+    size = 0;
+    max_size = NGX_MAX_OFF_T_VALUE;
+
+    value = cf->args->elts;
+
+    cache->path->name = value[1];
+
+    if (cache->path->name.data[cache->path->name.len - 1] == '/') {
+        cache->path->name.len--;
+    }
+
+    if (ngx_conf_full_name(cf->cycle, &cache->path->name, 0) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    for (i = 2; i < cf->args->nelts; i++) {
+
+        if (ngx_strncmp(value[i].data, "levels=", 7) == 0) {
+
+            p = value[i].data + 7;
+            last = value[i].data + value[i].len;
+
+            for (n = 0; n < NGX_MAX_PATH_LEVEL && p < last; n++) {
+
+                if (*p > '0' && *p < '3') {
+
+                    cache->path->level[n] = *p++ - '0';
+                    cache->path->len += cache->path->level[n] + 1;
+
+                    if (p == last) {
+                        break;
+                    }
+
+                    if (*p++ == ':' && n < NGX_MAX_PATH_LEVEL - 1 && p < last) {
+                        continue;
+                    }
+
+                    goto invalid_levels;
+                }
+
+                goto invalid_levels;
+            }
+
+            if (cache->path->len < 10 + NGX_MAX_PATH_LEVEL) {
+                continue;
+            }
+
+        invalid_levels:
+
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid \"levels\" \"%V\"", &value[i]);
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_strncmp(value[i].data, "use_temp_path=", 14) == 0) {
+
+            if (ngx_strcmp(&value[i].data[14], "on") == 0) {
+                use_temp_path = 1;
+
+            } else if (ngx_strcmp(&value[i].data[14], "off") == 0) {
+                use_temp_path = 0;
+
+            } else {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid use_temp_path value \"%V\", "
+                                   "it must be \"on\" or \"off\"",
+                                   &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "keys_zone=", 10) == 0) {
+
+            name.data = value[i].data + 10;
+
+            p = (u_char *) ngx_strchr(name.data, ':');
+
+            if (p) {
+                name.len = p - name.data;
+
+                p++;
+
+                s.len = value[i].data + value[i].len - p;
+                s.data = p;
+
+                size = ngx_parse_size(&s);
+                if (size > 8191) {
+                    continue;
+                }
+            }
+
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid keys zone size \"%V\"", &value[i]);
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_strncmp(value[i].data, "inactive=", 9) == 0) {
+
+            s.len = value[i].len - 9;
+            s.data = value[i].data + 9;
+
+            inactive = ngx_parse_time(&s, 1);
+            if (inactive == (time_t) NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid inactive value \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "max_size=", 9) == 0) {
+
+            s.len = value[i].len - 9;
+            s.data = value[i].data + 9;
+
+            max_size = ngx_parse_offset(&s);
+            if (max_size < 0) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid max_size value \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "loader_files=", 13) == 0) {
+
+            loader_files = ngx_atoi(value[i].data + 13, value[i].len - 13);
+            if (loader_files == NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid loader_files value \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "loader_sleep=", 13) == 0) {
+
+            s.len = value[i].len - 13;
+            s.data = value[i].data + 13;
+
+            loader_sleep = ngx_parse_time(&s, 0);
+            if (loader_sleep == (ngx_msec_t) NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid loader_sleep value \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "loader_threshold=", 17) == 0) {
+
+            s.len = value[i].len - 17;
+            s.data = value[i].data + 17;
+
+            loader_threshold = ngx_parse_time(&s, 0);
+            if (loader_threshold == (ngx_msec_t) NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid loader_threshold value \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "manager_files=", 14) == 0) {
+
+            manager_files = ngx_atoi(value[i].data + 14, value[i].len - 14);
+            if (manager_files == NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid manager_files value \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "manager_sleep=", 14) == 0) {
+
+            s.len = value[i].len - 14;
+            s.data = value[i].data + 14;
+
+            manager_sleep = ngx_parse_time(&s, 0);
+            if (manager_sleep == (ngx_msec_t) NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid manager_sleep value \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "manager_threshold=", 18) == 0) {
+
+            s.len = value[i].len - 18;
+            s.data = value[i].data + 18;
+
+            manager_threshold = ngx_parse_time(&s, 0);
+            if (manager_threshold == (ngx_msec_t) NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid manager_threshold value \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[i]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (name.len == 0 || size == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"%V\" must have \"keys_zone\" parameter",
+                           &cmd->name);
+        return NGX_CONF_ERROR;
+    }
+
+    cache->path->manager = ngx_http_file_cache_manager;
+    cache->path->loader = ngx_http_file_cache_loader;
+    cache->path->data = cache;
+    cache->path->conf_file = cf->conf_file->file.name.data;
+    cache->path->line = cf->conf_file->line;
+    cache->loader_files = loader_files;
+    cache->loader_sleep = loader_sleep;
+    cache->loader_threshold = loader_threshold;
+    cache->manager_files = manager_files;
+    cache->manager_sleep = manager_sleep;
+    cache->manager_threshold = manager_threshold;
+
+    if (ngx_add_path(cf, &cache->path) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    cache->shm_zone = ngx_shared_memory_add(cf, &name, size, cmd->post);
+    if (cache->shm_zone == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (cache->shm_zone->data) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "duplicate zone \"%V\"", &name);
+        return NGX_CONF_ERROR;
+    }
+
+
+    cache->shm_zone->init = ngx_http_file_cache_init;
+    cache->shm_zone->data = cache;
+
+    cache->use_temp_path = use_temp_path;
+
+    cache->inactive = inactive;
+    cache->max_size = max_size;
+
+    caches = (ngx_array_t *) (confp + cmd->offset);
+
+    ce = ngx_array_push(caches);
+    if (ce == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    *ce = cache;
+
+    return NGX_CONF_OK;
+}
+
+
+char *
+ngx_http_file_cache_valid_set_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf)
+{
+    char  *p = conf;
+
+    time_t                    valid;
+    ngx_str_t                *value;
+    ngx_uint_t                i, n, status;
+    ngx_array_t             **a;
+    ngx_http_cache_valid_t   *v;
+    static ngx_uint_t         statuses[] = { 200, 301, 302 };
+
+    a = (ngx_array_t **) (p + cmd->offset);
+
+    if (*a == NGX_CONF_UNSET_PTR) {
+        *a = ngx_array_create(cf->pool, 1, sizeof(ngx_http_cache_valid_t));
+        if (*a == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    value = cf->args->elts;
+    n = cf->args->nelts - 1;
+
+    valid = ngx_parse_time(&value[n], 1);
+    if (valid == (time_t) NGX_ERROR) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid time value \"%V\"", &value[n]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (n == 1) {
+
+        for (i = 0; i < 3; i++) {
+            v = ngx_array_push(*a);
+            if (v == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            v->status = statuses[i];
+            v->valid = valid;
+        }
+
+        return NGX_CONF_OK;
+    }
+
+    for (i = 1; i < n; i++) {
+
+        if (ngx_strcmp(value[i].data, "any") == 0) {
+
+            status = 0;
+
+        } else {
+
+            status = ngx_atoi(value[i].data, value[i].len);
+            if (status < 100) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid status \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+        }
+
+        v = ngx_array_push(*a);
+        if (v == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        v->status = status;
+        v->valid = valid;
+    }
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/http/ngx_http_header_filter_module.c b/nginx/src/http/ngx_http_header_filter_module.c
new file mode 100644 (file)
index 0000000..9b89405
--- /dev/null
@@ -0,0 +1,630 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <nginx.h>
+
+
+static ngx_int_t ngx_http_header_filter_init(ngx_conf_t *cf);
+static ngx_int_t ngx_http_header_filter(ngx_http_request_t *r);
+
+
+static ngx_http_module_t  ngx_http_header_filter_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_header_filter_init,           /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL,                                  /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_header_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_header_filter_module_ctx,    /* module context */
+    NULL,                                  /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static u_char ngx_http_server_string[] = "Server: nginx" CRLF;
+static u_char ngx_http_server_full_string[] = "Server: " NGINX_VER CRLF;
+static u_char ngx_http_server_build_string[] = "Server: " NGINX_VER_BUILD CRLF;
+
+
+static ngx_str_t ngx_http_status_lines[] = {
+
+    ngx_string("200 OK"),
+    ngx_string("201 Created"),
+    ngx_string("202 Accepted"),
+    ngx_null_string,  /* "203 Non-Authoritative Information" */
+    ngx_string("204 No Content"),
+    ngx_null_string,  /* "205 Reset Content" */
+    ngx_string("206 Partial Content"),
+
+    /* ngx_null_string, */  /* "207 Multi-Status" */
+
+#define NGX_HTTP_LAST_2XX  207
+#define NGX_HTTP_OFF_3XX   (NGX_HTTP_LAST_2XX - 200)
+
+    /* ngx_null_string, */  /* "300 Multiple Choices" */
+
+    ngx_string("301 Moved Permanently"),
+    ngx_string("302 Moved Temporarily"),
+    ngx_string("303 See Other"),
+    ngx_string("304 Not Modified"),
+    ngx_null_string,  /* "305 Use Proxy" */
+    ngx_null_string,  /* "306 unused" */
+    ngx_string("307 Temporary Redirect"),
+    ngx_string("308 Permanent Redirect"),
+
+#define NGX_HTTP_LAST_3XX  309
+#define NGX_HTTP_OFF_4XX   (NGX_HTTP_LAST_3XX - 301 + NGX_HTTP_OFF_3XX)
+
+    ngx_string("400 Bad Request"),
+    ngx_string("401 Unauthorized"),
+    ngx_string("402 Payment Required"),
+    ngx_string("403 Forbidden"),
+    ngx_string("404 Not Found"),
+    ngx_string("405 Not Allowed"),
+    ngx_string("406 Not Acceptable"),
+    ngx_null_string,  /* "407 Proxy Authentication Required" */
+    ngx_string("408 Request Time-out"),
+    ngx_string("409 Conflict"),
+    ngx_string("410 Gone"),
+    ngx_string("411 Length Required"),
+    ngx_string("412 Precondition Failed"),
+    ngx_string("413 Request Entity Too Large"),
+    ngx_string("414 Request-URI Too Large"),
+    ngx_string("415 Unsupported Media Type"),
+    ngx_string("416 Requested Range Not Satisfiable"),
+    ngx_null_string,  /* "417 Expectation Failed" */
+    ngx_null_string,  /* "418 unused" */
+    ngx_null_string,  /* "419 unused" */
+    ngx_null_string,  /* "420 unused" */
+    ngx_string("421 Misdirected Request"),
+    ngx_null_string,  /* "422 Unprocessable Entity" */
+    ngx_null_string,  /* "423 Locked" */
+    ngx_null_string,  /* "424 Failed Dependency" */
+    ngx_null_string,  /* "425 unused" */
+    ngx_null_string,  /* "426 Upgrade Required" */
+    ngx_null_string,  /* "427 unused" */
+    ngx_null_string,  /* "428 Precondition Required" */
+    ngx_string("429 Too Many Requests"),
+
+#define NGX_HTTP_LAST_4XX  430
+#define NGX_HTTP_OFF_5XX   (NGX_HTTP_LAST_4XX - 400 + NGX_HTTP_OFF_4XX)
+
+    ngx_string("500 Internal Server Error"),
+    ngx_string("501 Not Implemented"),
+    ngx_string("502 Bad Gateway"),
+    ngx_string("503 Service Temporarily Unavailable"),
+    ngx_string("504 Gateway Time-out"),
+    ngx_string("505 HTTP Version Not Supported"),
+    ngx_null_string,        /* "506 Variant Also Negotiates" */
+    ngx_string("507 Insufficient Storage"),
+
+    /* ngx_null_string, */  /* "508 unused" */
+    /* ngx_null_string, */  /* "509 unused" */
+    /* ngx_null_string, */  /* "510 Not Extended" */
+
+#define NGX_HTTP_LAST_5XX  508
+
+};
+
+
+ngx_http_header_out_t  ngx_http_headers_out[] = {
+    { ngx_string("Server"), offsetof(ngx_http_headers_out_t, server) },
+    { ngx_string("Date"), offsetof(ngx_http_headers_out_t, date) },
+    { ngx_string("Content-Length"),
+                 offsetof(ngx_http_headers_out_t, content_length) },
+    { ngx_string("Content-Encoding"),
+                 offsetof(ngx_http_headers_out_t, content_encoding) },
+    { ngx_string("Location"), offsetof(ngx_http_headers_out_t, location) },
+    { ngx_string("Last-Modified"),
+                 offsetof(ngx_http_headers_out_t, last_modified) },
+    { ngx_string("Accept-Ranges"),
+                 offsetof(ngx_http_headers_out_t, accept_ranges) },
+    { ngx_string("Expires"), offsetof(ngx_http_headers_out_t, expires) },
+    { ngx_string("Cache-Control"),
+                 offsetof(ngx_http_headers_out_t, cache_control) },
+    { ngx_string("ETag"), offsetof(ngx_http_headers_out_t, etag) },
+
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_int_t
+ngx_http_header_filter(ngx_http_request_t *r)
+{
+    u_char                    *p;
+    size_t                     len;
+    ngx_str_t                  host, *status_line;
+    ngx_buf_t                 *b;
+    ngx_uint_t                 status, i, port;
+    ngx_chain_t                out;
+    ngx_list_part_t           *part;
+    ngx_table_elt_t           *header;
+    ngx_connection_t          *c;
+    ngx_http_core_loc_conf_t  *clcf;
+    ngx_http_core_srv_conf_t  *cscf;
+    u_char                     addr[NGX_SOCKADDR_STRLEN];
+
+    if (r->header_sent) {
+        return NGX_OK;
+    }
+
+    r->header_sent = 1;
+
+    if (r != r->main) {
+        return NGX_OK;
+    }
+
+    if (r->http_version < NGX_HTTP_VERSION_10) {
+        return NGX_OK;
+    }
+
+    if (r->method == NGX_HTTP_HEAD) {
+        r->header_only = 1;
+    }
+
+    if (r->headers_out.last_modified_time != -1) {
+        if (r->headers_out.status != NGX_HTTP_OK
+            && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT
+            && r->headers_out.status != NGX_HTTP_NOT_MODIFIED)
+        {
+            r->headers_out.last_modified_time = -1;
+            r->headers_out.last_modified = NULL;
+        }
+    }
+
+    len = sizeof("HTTP/1.x ") - 1 + sizeof(CRLF) - 1
+          /* the end of the header */
+          + sizeof(CRLF) - 1;
+
+    /* status line */
+
+    if (r->headers_out.status_line.len) {
+        len += r->headers_out.status_line.len;
+        status_line = &r->headers_out.status_line;
+#if (NGX_SUPPRESS_WARN)
+        status = 0;
+#endif
+
+    } else {
+
+        status = r->headers_out.status;
+
+        if (status >= NGX_HTTP_OK
+            && status < NGX_HTTP_LAST_2XX)
+        {
+            /* 2XX */
+
+            if (status == NGX_HTTP_NO_CONTENT) {
+                r->header_only = 1;
+                ngx_str_null(&r->headers_out.content_type);
+                r->headers_out.last_modified_time = -1;
+                r->headers_out.last_modified = NULL;
+                r->headers_out.content_length = NULL;
+                r->headers_out.content_length_n = -1;
+            }
+
+            status -= NGX_HTTP_OK;
+            status_line = &ngx_http_status_lines[status];
+            len += ngx_http_status_lines[status].len;
+
+        } else if (status >= NGX_HTTP_MOVED_PERMANENTLY
+                   && status < NGX_HTTP_LAST_3XX)
+        {
+            /* 3XX */
+
+            if (status == NGX_HTTP_NOT_MODIFIED) {
+                r->header_only = 1;
+            }
+
+            status = status - NGX_HTTP_MOVED_PERMANENTLY + NGX_HTTP_OFF_3XX;
+            status_line = &ngx_http_status_lines[status];
+            len += ngx_http_status_lines[status].len;
+
+        } else if (status >= NGX_HTTP_BAD_REQUEST
+                   && status < NGX_HTTP_LAST_4XX)
+        {
+            /* 4XX */
+            status = status - NGX_HTTP_BAD_REQUEST
+                            + NGX_HTTP_OFF_4XX;
+
+            status_line = &ngx_http_status_lines[status];
+            len += ngx_http_status_lines[status].len;
+
+        } else if (status >= NGX_HTTP_INTERNAL_SERVER_ERROR
+                   && status < NGX_HTTP_LAST_5XX)
+        {
+            /* 5XX */
+            status = status - NGX_HTTP_INTERNAL_SERVER_ERROR
+                            + NGX_HTTP_OFF_5XX;
+
+            status_line = &ngx_http_status_lines[status];
+            len += ngx_http_status_lines[status].len;
+
+        } else {
+            len += NGX_INT_T_LEN + 1 /* SP */;
+            status_line = NULL;
+        }
+
+        if (status_line && status_line->len == 0) {
+            status = r->headers_out.status;
+            len += NGX_INT_T_LEN + 1 /* SP */;
+            status_line = NULL;
+        }
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (r->headers_out.server == NULL) {
+        if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
+            len += sizeof(ngx_http_server_full_string) - 1;
+
+        } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
+            len += sizeof(ngx_http_server_build_string) - 1;
+
+        } else {
+            len += sizeof(ngx_http_server_string) - 1;
+        }
+    }
+
+    if (r->headers_out.date == NULL) {
+        len += sizeof("Date: Mon, 28 Sep 1970 06:00:00 GMT" CRLF) - 1;
+    }
+
+    if (r->headers_out.content_type.len) {
+        len += sizeof("Content-Type: ") - 1
+               + r->headers_out.content_type.len + 2;
+
+        if (r->headers_out.content_type_len == r->headers_out.content_type.len
+            && r->headers_out.charset.len)
+        {
+            len += sizeof("; charset=") - 1 + r->headers_out.charset.len;
+        }
+    }
+
+    if (r->headers_out.content_length == NULL
+        && r->headers_out.content_length_n >= 0)
+    {
+        len += sizeof("Content-Length: ") - 1 + NGX_OFF_T_LEN + 2;
+    }
+
+    if (r->headers_out.last_modified == NULL
+        && r->headers_out.last_modified_time != -1)
+    {
+        len += sizeof("Last-Modified: Mon, 28 Sep 1970 06:00:00 GMT" CRLF) - 1;
+    }
+
+    c = r->connection;
+
+    if (r->headers_out.location
+        && r->headers_out.location->value.len
+        && r->headers_out.location->value.data[0] == '/'
+        && clcf->absolute_redirect)
+    {
+        r->headers_out.location->hash = 0;
+
+        if (clcf->server_name_in_redirect) {
+            cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+            host = cscf->server_name;
+
+        } else if (r->headers_in.server.len) {
+            host = r->headers_in.server;
+
+        } else {
+            host.len = NGX_SOCKADDR_STRLEN;
+            host.data = addr;
+
+            if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) {
+                return NGX_ERROR;
+            }
+        }
+
+        port = ngx_inet_get_port(c->local_sockaddr);
+
+        len += sizeof("Location: https://") - 1
+               + host.len
+               + r->headers_out.location->value.len + 2;
+
+        if (clcf->port_in_redirect) {
+
+#if (NGX_HTTP_SSL)
+            if (c->ssl)
+                port = (port == 443) ? 0 : port;
+            else
+#endif
+                port = (port == 80) ? 0 : port;
+
+        } else {
+            port = 0;
+        }
+
+        if (port) {
+            len += sizeof(":65535") - 1;
+        }
+
+    } else {
+        ngx_str_null(&host);
+        port = 0;
+    }
+
+    if (r->chunked) {
+        len += sizeof("Transfer-Encoding: chunked" CRLF) - 1;
+    }
+
+    if (r->headers_out.status == NGX_HTTP_SWITCHING_PROTOCOLS) {
+        len += sizeof("Connection: upgrade" CRLF) - 1;
+
+    } else if (r->keepalive) {
+        len += sizeof("Connection: keep-alive" CRLF) - 1;
+
+        /*
+         * MSIE and Opera ignore the "Keep-Alive: timeout=<N>" header.
+         * MSIE keeps the connection alive for about 60-65 seconds.
+         * Opera keeps the connection alive very long.
+         * Mozilla keeps the connection alive for N plus about 1-10 seconds.
+         * Konqueror keeps the connection alive for about N seconds.
+         */
+
+        if (clcf->keepalive_header) {
+            len += sizeof("Keep-Alive: timeout=") - 1 + NGX_TIME_T_LEN + 2;
+        }
+
+    } else {
+        len += sizeof("Connection: close" CRLF) - 1;
+    }
+
+#if (NGX_HTTP_GZIP)
+    if (r->gzip_vary) {
+        if (clcf->gzip_vary) {
+            len += sizeof("Vary: Accept-Encoding" CRLF) - 1;
+
+        } else {
+            r->gzip_vary = 0;
+        }
+    }
+#endif
+
+    part = &r->headers_out.headers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (header[i].hash == 0) {
+            continue;
+        }
+
+        len += header[i].key.len + sizeof(": ") - 1 + header[i].value.len
+               + sizeof(CRLF) - 1;
+    }
+
+    b = ngx_create_temp_buf(r->pool, len);
+    if (b == NULL) {
+        return NGX_ERROR;
+    }
+
+    /* "HTTP/1.x " */
+    b->last = ngx_cpymem(b->last, "HTTP/1.1 ", sizeof("HTTP/1.x ") - 1);
+
+    /* status line */
+    if (status_line) {
+        b->last = ngx_copy(b->last, status_line->data, status_line->len);
+
+    } else {
+        b->last = ngx_sprintf(b->last, "%03ui ", status);
+    }
+    *b->last++ = CR; *b->last++ = LF;
+
+    if (r->headers_out.server == NULL) {
+        if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
+            p = ngx_http_server_full_string;
+            len = sizeof(ngx_http_server_full_string) - 1;
+
+        } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
+            p = ngx_http_server_build_string;
+            len = sizeof(ngx_http_server_build_string) - 1;
+
+        } else {
+            p = ngx_http_server_string;
+            len = sizeof(ngx_http_server_string) - 1;
+        }
+
+        b->last = ngx_cpymem(b->last, p, len);
+    }
+
+    if (r->headers_out.date == NULL) {
+        b->last = ngx_cpymem(b->last, "Date: ", sizeof("Date: ") - 1);
+        b->last = ngx_cpymem(b->last, ngx_cached_http_time.data,
+                             ngx_cached_http_time.len);
+
+        *b->last++ = CR; *b->last++ = LF;
+    }
+
+    if (r->headers_out.content_type.len) {
+        b->last = ngx_cpymem(b->last, "Content-Type: ",
+                             sizeof("Content-Type: ") - 1);
+        p = b->last;
+        b->last = ngx_copy(b->last, r->headers_out.content_type.data,
+                           r->headers_out.content_type.len);
+
+        if (r->headers_out.content_type_len == r->headers_out.content_type.len
+            && r->headers_out.charset.len)
+        {
+            b->last = ngx_cpymem(b->last, "; charset=",
+                                 sizeof("; charset=") - 1);
+            b->last = ngx_copy(b->last, r->headers_out.charset.data,
+                               r->headers_out.charset.len);
+
+            /* update r->headers_out.content_type for possible logging */
+
+            r->headers_out.content_type.len = b->last - p;
+            r->headers_out.content_type.data = p;
+        }
+
+        *b->last++ = CR; *b->last++ = LF;
+    }
+
+    if (r->headers_out.content_length == NULL
+        && r->headers_out.content_length_n >= 0)
+    {
+        b->last = ngx_sprintf(b->last, "Content-Length: %O" CRLF,
+                              r->headers_out.content_length_n);
+    }
+
+    if (r->headers_out.last_modified == NULL
+        && r->headers_out.last_modified_time != -1)
+    {
+        b->last = ngx_cpymem(b->last, "Last-Modified: ",
+                             sizeof("Last-Modified: ") - 1);
+        b->last = ngx_http_time(b->last, r->headers_out.last_modified_time);
+
+        *b->last++ = CR; *b->last++ = LF;
+    }
+
+    if (host.data) {
+
+        p = b->last + sizeof("Location: ") - 1;
+
+        b->last = ngx_cpymem(b->last, "Location: http",
+                             sizeof("Location: http") - 1);
+
+#if (NGX_HTTP_SSL)
+        if (c->ssl) {
+            *b->last++ ='s';
+        }
+#endif
+
+        *b->last++ = ':'; *b->last++ = '/'; *b->last++ = '/';
+        b->last = ngx_copy(b->last, host.data, host.len);
+
+        if (port) {
+            b->last = ngx_sprintf(b->last, ":%ui", port);
+        }
+
+        b->last = ngx_copy(b->last, r->headers_out.location->value.data,
+                           r->headers_out.location->value.len);
+
+        /* update r->headers_out.location->value for possible logging */
+
+        r->headers_out.location->value.len = b->last - p;
+        r->headers_out.location->value.data = p;
+        ngx_str_set(&r->headers_out.location->key, "Location");
+
+        *b->last++ = CR; *b->last++ = LF;
+    }
+
+    if (r->chunked) {
+        b->last = ngx_cpymem(b->last, "Transfer-Encoding: chunked" CRLF,
+                             sizeof("Transfer-Encoding: chunked" CRLF) - 1);
+    }
+
+    if (r->headers_out.status == NGX_HTTP_SWITCHING_PROTOCOLS) {
+        b->last = ngx_cpymem(b->last, "Connection: upgrade" CRLF,
+                             sizeof("Connection: upgrade" CRLF) - 1);
+
+    } else if (r->keepalive) {
+        b->last = ngx_cpymem(b->last, "Connection: keep-alive" CRLF,
+                             sizeof("Connection: keep-alive" CRLF) - 1);
+
+        if (clcf->keepalive_header) {
+            b->last = ngx_sprintf(b->last, "Keep-Alive: timeout=%T" CRLF,
+                                  clcf->keepalive_header);
+        }
+
+    } else {
+        b->last = ngx_cpymem(b->last, "Connection: close" CRLF,
+                             sizeof("Connection: close" CRLF) - 1);
+    }
+
+#if (NGX_HTTP_GZIP)
+    if (r->gzip_vary) {
+        b->last = ngx_cpymem(b->last, "Vary: Accept-Encoding" CRLF,
+                             sizeof("Vary: Accept-Encoding" CRLF) - 1);
+    }
+#endif
+
+    part = &r->headers_out.headers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (header[i].hash == 0) {
+            continue;
+        }
+
+        b->last = ngx_copy(b->last, header[i].key.data, header[i].key.len);
+        *b->last++ = ':'; *b->last++ = ' ';
+
+        b->last = ngx_copy(b->last, header[i].value.data, header[i].value.len);
+        *b->last++ = CR; *b->last++ = LF;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "%*s", (size_t) (b->last - b->pos), b->pos);
+
+    /* the end of HTTP header */
+    *b->last++ = CR; *b->last++ = LF;
+
+    r->header_size = b->last - b->pos;
+
+    if (r->header_only) {
+        b->last_buf = 1;
+    }
+
+    out.buf = b;
+    out.next = NULL;
+
+    return ngx_http_write_filter(r, &out);
+}
+
+
+static ngx_int_t
+ngx_http_header_filter_init(ngx_conf_t *cf)
+{
+    ngx_http_top_header_filter = ngx_http_header_filter;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/ngx_http_parse.c b/nginx/src/http/ngx_http_parse.c
new file mode 100644 (file)
index 0000000..844054c
--- /dev/null
@@ -0,0 +1,2388 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static uint32_t  usual[] = {
+    0xffffdbfe, /* 1111 1111 1111 1111  1101 1011 1111 1110 */
+
+                /* ?>=< ;:98 7654 3210  /.-, +*)( '&%$ #"!  */
+    0x7fff37d6, /* 0111 1111 1111 1111  0011 0111 1101 0110 */
+
+                /* _^]\ [ZYX WVUT SRQP  ONML KJIH GFED CBA@ */
+#if (NGX_WIN32)
+    0xefffffff, /* 1110 1111 1111 1111  1111 1111 1111 1111 */
+#else
+    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
+#endif
+
+                /*  ~}| {zyx wvut srqp  onml kjih gfed cba` */
+    0xffffffff, /* 1111 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 */
+    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
+    0xffffffff  /* 1111 1111 1111 1111  1111 1111 1111 1111 */
+};
+
+
+#if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED)
+
+#define ngx_str3_cmp(m, c0, c1, c2, c3)                                       \
+    *(uint32_t *) m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0)
+
+#define ngx_str3Ocmp(m, c0, c1, c2, c3)                                       \
+    *(uint32_t *) m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0)
+
+#define ngx_str4cmp(m, c0, c1, c2, c3)                                        \
+    *(uint32_t *) m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0)
+
+#define ngx_str5cmp(m, c0, c1, c2, c3, c4)                                    \
+    *(uint32_t *) m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0)             \
+        && m[4] == c4
+
+#define ngx_str6cmp(m, c0, c1, c2, c3, c4, c5)                                \
+    *(uint32_t *) m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0)             \
+        && (((uint32_t *) m)[1] & 0xffff) == ((c5 << 8) | c4)
+
+#define ngx_str7_cmp(m, c0, c1, c2, c3, c4, c5, c6, c7)                       \
+    *(uint32_t *) m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0)             \
+        && ((uint32_t *) m)[1] == ((c7 << 24) | (c6 << 16) | (c5 << 8) | c4)
+
+#define ngx_str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7)                        \
+    *(uint32_t *) m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0)             \
+        && ((uint32_t *) m)[1] == ((c7 << 24) | (c6 << 16) | (c5 << 8) | c4)
+
+#define ngx_str9cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8)                    \
+    *(uint32_t *) m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0)             \
+        && ((uint32_t *) m)[1] == ((c7 << 24) | (c6 << 16) | (c5 << 8) | c4)  \
+        && m[8] == c8
+
+#else /* !(NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED) */
+
+#define ngx_str3_cmp(m, c0, c1, c2, c3)                                       \
+    m[0] == c0 && m[1] == c1 && m[2] == c2
+
+#define ngx_str3Ocmp(m, c0, c1, c2, c3)                                       \
+    m[0] == c0 && m[2] == c2 && m[3] == c3
+
+#define ngx_str4cmp(m, c0, c1, c2, c3)                                        \
+    m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3
+
+#define ngx_str5cmp(m, c0, c1, c2, c3, c4)                                    \
+    m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 && m[4] == c4
+
+#define ngx_str6cmp(m, c0, c1, c2, c3, c4, c5)                                \
+    m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3                      \
+        && m[4] == c4 && m[5] == c5
+
+#define ngx_str7_cmp(m, c0, c1, c2, c3, c4, c5, c6, c7)                       \
+    m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3                      \
+        && m[4] == c4 && m[5] == c5 && m[6] == c6
+
+#define ngx_str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7)                        \
+    m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3                      \
+        && m[4] == c4 && m[5] == c5 && m[6] == c6 && m[7] == c7
+
+#define ngx_str9cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8)                    \
+    m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3                      \
+        && m[4] == c4 && m[5] == c5 && m[6] == c6 && m[7] == c7 && m[8] == c8
+
+#endif
+
+
+/* gcc, icc, msvc and others compile these switches as an jump table */
+
+ngx_int_t
+ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
+{
+    u_char  c, ch, *p, *m;
+    enum {
+        sw_start = 0,
+        sw_method,
+        sw_spaces_before_uri,
+        sw_schema,
+        sw_schema_slash,
+        sw_schema_slash_slash,
+        sw_host_start,
+        sw_host,
+        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,
+        sw_http_HT,
+        sw_http_HTT,
+        sw_http_HTTP,
+        sw_first_major_digit,
+        sw_major_digit,
+        sw_first_minor_digit,
+        sw_minor_digit,
+        sw_spaces_after_digit,
+        sw_almost_done
+    } state;
+
+    state = r->state;
+
+    for (p = b->pos; p < b->last; p++) {
+        ch = *p;
+
+        switch (state) {
+
+        /* HTTP methods: GET, HEAD, POST */
+        case sw_start:
+            r->request_start = p;
+
+            if (ch == CR || ch == LF) {
+                break;
+            }
+
+            if ((ch < 'A' || ch > 'Z') && ch != '_' && ch != '-') {
+                return NGX_HTTP_PARSE_INVALID_METHOD;
+            }
+
+            state = sw_method;
+            break;
+
+        case sw_method:
+            if (ch == ' ') {
+                r->method_end = p - 1;
+                m = r->request_start;
+
+                switch (p - m) {
+
+                case 3:
+                    if (ngx_str3_cmp(m, 'G', 'E', 'T', ' ')) {
+                        r->method = NGX_HTTP_GET;
+                        break;
+                    }
+
+                    if (ngx_str3_cmp(m, 'P', 'U', 'T', ' ')) {
+                        r->method = NGX_HTTP_PUT;
+                        break;
+                    }
+
+                    break;
+
+                case 4:
+                    if (m[1] == 'O') {
+
+                        if (ngx_str3Ocmp(m, 'P', 'O', 'S', 'T')) {
+                            r->method = NGX_HTTP_POST;
+                            break;
+                        }
+
+                        if (ngx_str3Ocmp(m, 'C', 'O', 'P', 'Y')) {
+                            r->method = NGX_HTTP_COPY;
+                            break;
+                        }
+
+                        if (ngx_str3Ocmp(m, 'M', 'O', 'V', 'E')) {
+                            r->method = NGX_HTTP_MOVE;
+                            break;
+                        }
+
+                        if (ngx_str3Ocmp(m, 'L', 'O', 'C', 'K')) {
+                            r->method = NGX_HTTP_LOCK;
+                            break;
+                        }
+
+                    } else {
+
+                        if (ngx_str4cmp(m, 'H', 'E', 'A', 'D')) {
+                            r->method = NGX_HTTP_HEAD;
+                            break;
+                        }
+                    }
+
+                    break;
+
+                case 5:
+                    if (ngx_str5cmp(m, 'M', 'K', 'C', 'O', 'L')) {
+                        r->method = NGX_HTTP_MKCOL;
+                        break;
+                    }
+
+                    if (ngx_str5cmp(m, 'P', 'A', 'T', 'C', 'H')) {
+                        r->method = NGX_HTTP_PATCH;
+                        break;
+                    }
+
+                    if (ngx_str5cmp(m, 'T', 'R', 'A', 'C', 'E')) {
+                        r->method = NGX_HTTP_TRACE;
+                        break;
+                    }
+
+                    break;
+
+                case 6:
+                    if (ngx_str6cmp(m, 'D', 'E', 'L', 'E', 'T', 'E')) {
+                        r->method = NGX_HTTP_DELETE;
+                        break;
+                    }
+
+                    if (ngx_str6cmp(m, 'U', 'N', 'L', 'O', 'C', 'K')) {
+                        r->method = NGX_HTTP_UNLOCK;
+                        break;
+                    }
+
+                    break;
+
+                case 7:
+                    if (ngx_str7_cmp(m, 'O', 'P', 'T', 'I', 'O', 'N', 'S', ' '))
+                    {
+                        r->method = NGX_HTTP_OPTIONS;
+                    }
+
+                    break;
+
+                case 8:
+                    if (ngx_str8cmp(m, 'P', 'R', 'O', 'P', 'F', 'I', 'N', 'D'))
+                    {
+                        r->method = NGX_HTTP_PROPFIND;
+                    }
+
+                    break;
+
+                case 9:
+                    if (ngx_str9cmp(m,
+                            'P', 'R', 'O', 'P', 'P', 'A', 'T', 'C', 'H'))
+                    {
+                        r->method = NGX_HTTP_PROPPATCH;
+                    }
+
+                    break;
+                }
+
+                state = sw_spaces_before_uri;
+                break;
+            }
+
+            if ((ch < 'A' || ch > 'Z') && ch != '_' && ch != '-') {
+                return NGX_HTTP_PARSE_INVALID_METHOD;
+            }
+
+            break;
+
+        /* space* before URI */
+        case sw_spaces_before_uri:
+
+            if (ch == '/') {
+                r->uri_start = p;
+                state = sw_after_slash_in_uri;
+                break;
+            }
+
+            c = (u_char) (ch | 0x20);
+            if (c >= 'a' && c <= 'z') {
+                r->schema_start = p;
+                state = sw_schema;
+                break;
+            }
+
+            switch (ch) {
+            case ' ':
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        case sw_schema:
+
+            c = (u_char) (ch | 0x20);
+            if (c >= 'a' && c <= 'z') {
+                break;
+            }
+
+            switch (ch) {
+            case ':':
+                r->schema_end = p;
+                state = sw_schema_slash;
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        case sw_schema_slash:
+            switch (ch) {
+            case '/':
+                state = sw_schema_slash_slash;
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        case sw_schema_slash_slash:
+            switch (ch) {
+            case '/':
+                state = sw_host_start;
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        case sw_host_start:
+
+            r->host_start = p;
+
+            if (ch == '[') {
+                state = sw_host_ip_literal;
+                break;
+            }
+
+            state = sw_host;
+
+            /* fall through */
+
+        case sw_host:
+
+            c = (u_char) (ch | 0x20);
+            if (c >= 'a' && c <= 'z') {
+                break;
+            }
+
+            if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-') {
+                break;
+            }
+
+            /* fall through */
+
+        case sw_host_end:
+
+            r->host_end = p;
+
+            switch (ch) {
+            case ':':
+                state = sw_port;
+                break;
+            case '/':
+                r->uri_start = p;
+                state = sw_after_slash_in_uri;
+                break;
+            case ' ':
+                /*
+                 * use single "/" from request line to preserve pointers,
+                 * if request line will be copied to large client buffer
+                 */
+                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;
+
+        case sw_host_ip_literal:
+
+            if (ch >= '0' && ch <= '9') {
+                break;
+            }
+
+            c = (u_char) (ch | 0x20);
+            if (c >= 'a' && c <= 'z') {
+                break;
+            }
+
+            switch (ch) {
+            case ':':
+                break;
+            case ']':
+                state = sw_host_end;
+                break;
+            case '-':
+            case '.':
+            case '_':
+            case '~':
+                /* unreserved */
+                break;
+            case '!':
+            case '$':
+            case '&':
+            case '\'':
+            case '(':
+            case ')':
+            case '*':
+            case '+':
+            case ',':
+            case ';':
+            case '=':
+                /* sub-delims */
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        case sw_port:
+            if (ch >= '0' && ch <= '9') {
+                break;
+            }
+
+            switch (ch) {
+            case '/':
+                r->port_end = p;
+                r->uri_start = p;
+                state = sw_after_slash_in_uri;
+                break;
+            case ' ':
+                r->port_end = p;
+                /*
+                 * use single "/" from request line to preserve pointers,
+                 * if request line will be copied to large client buffer
+                 */
+                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;
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+
+        /* check "/.", "//", "%", and "\" (Win32) in URI */
+        case sw_after_slash_in_uri:
+
+            if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
+                state = sw_check_uri;
+                break;
+            }
+
+            switch (ch) {
+            case ' ':
+                r->uri_end = p;
+                state = sw_check_uri_http_09;
+                break;
+            case CR:
+                r->uri_end = p;
+                r->http_minor = 9;
+                state = sw_almost_done;
+                break;
+            case LF:
+                r->uri_end = p;
+                r->http_minor = 9;
+                goto done;
+            case '.':
+                r->complex_uri = 1;
+                state = sw_uri;
+                break;
+            case '%':
+                r->quoted_uri = 1;
+                state = sw_uri;
+                break;
+            case '/':
+                r->complex_uri = 1;
+                state = sw_uri;
+                break;
+#if (NGX_WIN32)
+            case '\\':
+                r->complex_uri = 1;
+                state = sw_uri;
+                break;
+#endif
+            case '?':
+                r->args_start = p + 1;
+                state = sw_uri;
+                break;
+            case '#':
+                r->complex_uri = 1;
+                state = sw_uri;
+                break;
+            case '+':
+                r->plus_in_uri = 1;
+                break;
+            case '\0':
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            default:
+                state = sw_check_uri;
+                break;
+            }
+            break;
+
+        /* check "/", "%" and "\" (Win32) in URI */
+        case sw_check_uri:
+
+            if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
+                break;
+            }
+
+            switch (ch) {
+            case '/':
+#if (NGX_WIN32)
+                if (r->uri_ext == p) {
+                    r->complex_uri = 1;
+                    state = sw_uri;
+                    break;
+                }
+#endif
+                r->uri_ext = NULL;
+                state = sw_after_slash_in_uri;
+                break;
+            case '.':
+                r->uri_ext = p + 1;
+                break;
+            case ' ':
+                r->uri_end = p;
+                state = sw_check_uri_http_09;
+                break;
+            case CR:
+                r->uri_end = p;
+                r->http_minor = 9;
+                state = sw_almost_done;
+                break;
+            case LF:
+                r->uri_end = p;
+                r->http_minor = 9;
+                goto done;
+#if (NGX_WIN32)
+            case '\\':
+                r->complex_uri = 1;
+                state = sw_after_slash_in_uri;
+                break;
+#endif
+            case '%':
+                r->quoted_uri = 1;
+                state = sw_uri;
+                break;
+            case '?':
+                r->args_start = p + 1;
+                state = sw_uri;
+                break;
+            case '#':
+                r->complex_uri = 1;
+                state = sw_uri;
+                break;
+            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--;
+                break;
+            }
+            break;
+
+
+        /* URI */
+        case sw_uri:
+
+            if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
+                break;
+            }
+
+            switch (ch) {
+            case ' ':
+                r->uri_end = p;
+                state = sw_http_09;
+                break;
+            case CR:
+                r->uri_end = p;
+                r->http_minor = 9;
+                state = sw_almost_done;
+                break;
+            case LF:
+                r->uri_end = p;
+                r->http_minor = 9;
+                goto done;
+            case '#':
+                r->complex_uri = 1;
+                break;
+            case '\0':
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        /* space+ after URI */
+        case sw_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_uri;
+                p--;
+                break;
+            }
+            break;
+
+        case sw_http_H:
+            switch (ch) {
+            case 'T':
+                state = sw_http_HT;
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        case sw_http_HT:
+            switch (ch) {
+            case 'T':
+                state = sw_http_HTT;
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        case sw_http_HTT:
+            switch (ch) {
+            case 'P':
+                state = sw_http_HTTP;
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        case sw_http_HTTP:
+            switch (ch) {
+            case '/':
+                state = sw_first_major_digit;
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        /* first digit of major HTTP version */
+        case sw_first_major_digit:
+            if (ch < '1' || ch > '9') {
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+
+            r->http_major = ch - '0';
+
+            if (r->http_major > 1) {
+                return NGX_HTTP_PARSE_INVALID_VERSION;
+            }
+
+            state = sw_major_digit;
+            break;
+
+        /* major HTTP version or dot */
+        case sw_major_digit:
+            if (ch == '.') {
+                state = sw_first_minor_digit;
+                break;
+            }
+
+            if (ch < '0' || ch > '9') {
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+
+            r->http_major = r->http_major * 10 + (ch - '0');
+
+            if (r->http_major > 1) {
+                return NGX_HTTP_PARSE_INVALID_VERSION;
+            }
+
+            break;
+
+        /* first digit of minor HTTP version */
+        case sw_first_minor_digit:
+            if (ch < '0' || ch > '9') {
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+
+            r->http_minor = ch - '0';
+            state = sw_minor_digit;
+            break;
+
+        /* minor HTTP version or end of request line */
+        case sw_minor_digit:
+            if (ch == CR) {
+                state = sw_almost_done;
+                break;
+            }
+
+            if (ch == LF) {
+                goto done;
+            }
+
+            if (ch == ' ') {
+                state = sw_spaces_after_digit;
+                break;
+            }
+
+            if (ch < '0' || ch > '9') {
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+
+            if (r->http_minor > 99) {
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+
+            r->http_minor = r->http_minor * 10 + (ch - '0');
+            break;
+
+        case sw_spaces_after_digit:
+            switch (ch) {
+            case ' ':
+                break;
+            case CR:
+                state = sw_almost_done;
+                break;
+            case LF:
+                goto done;
+            default:
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        /* end of request line */
+        case sw_almost_done:
+            r->request_end = p - 1;
+            switch (ch) {
+            case LF:
+                goto done;
+            default:
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+        }
+    }
+
+    b->pos = p;
+    r->state = state;
+
+    return NGX_AGAIN;
+
+done:
+
+    b->pos = p + 1;
+
+    if (r->request_end == NULL) {
+        r->request_end = p;
+    }
+
+    r->http_version = r->http_major * 1000 + r->http_minor;
+    r->state = sw_start;
+
+    if (r->http_version == 9 && r->method != NGX_HTTP_GET) {
+        return NGX_HTTP_PARSE_INVALID_09_METHOD;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b,
+    ngx_uint_t allow_underscores)
+{
+    u_char      c, ch, *p;
+    ngx_uint_t  hash, i;
+    enum {
+        sw_start = 0,
+        sw_name,
+        sw_space_before_value,
+        sw_value,
+        sw_space_after_value,
+        sw_ignore_line,
+        sw_almost_done,
+        sw_header_almost_done
+    } state;
+
+    /* the last '\0' is not needed because string is zero terminated */
+
+    static u_char  lowcase[] =
+        "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+        "\0\0\0\0\0\0\0\0\0\0\0\0\0-\0\0" "0123456789\0\0\0\0\0\0"
+        "\0abcdefghijklmnopqrstuvwxyz\0\0\0\0\0"
+        "\0abcdefghijklmnopqrstuvwxyz\0\0\0\0\0"
+        "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+        "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+        "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+        "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
+
+    state = r->state;
+    hash = r->header_hash;
+    i = r->lowcase_index;
+
+    for (p = b->pos; p < b->last; p++) {
+        ch = *p;
+
+        switch (state) {
+
+        /* first char */
+        case sw_start:
+            r->header_name_start = p;
+            r->invalid_header = 0;
+
+            switch (ch) {
+            case CR:
+                r->header_end = p;
+                state = sw_header_almost_done;
+                break;
+            case LF:
+                r->header_end = p;
+                goto header_done;
+            default:
+                state = sw_name;
+
+                c = lowcase[ch];
+
+                if (c) {
+                    hash = ngx_hash(0, c);
+                    r->lowcase_header[0] = c;
+                    i = 1;
+                    break;
+                }
+
+                if (ch == '_') {
+                    if (allow_underscores) {
+                        hash = ngx_hash(0, ch);
+                        r->lowcase_header[0] = ch;
+                        i = 1;
+
+                    } else {
+                        r->invalid_header = 1;
+                    }
+
+                    break;
+                }
+
+                if (ch == '\0') {
+                    return NGX_HTTP_PARSE_INVALID_HEADER;
+                }
+
+                r->invalid_header = 1;
+
+                break;
+
+            }
+            break;
+
+        /* header name */
+        case sw_name:
+            c = lowcase[ch];
+
+            if (c) {
+                hash = ngx_hash(hash, c);
+                r->lowcase_header[i++] = c;
+                i &= (NGX_HTTP_LC_HEADER_LEN - 1);
+                break;
+            }
+
+            if (ch == '_') {
+                if (allow_underscores) {
+                    hash = ngx_hash(hash, ch);
+                    r->lowcase_header[i++] = ch;
+                    i &= (NGX_HTTP_LC_HEADER_LEN - 1);
+
+                } else {
+                    r->invalid_header = 1;
+                }
+
+                break;
+            }
+
+            if (ch == ':') {
+                r->header_name_end = p;
+                state = sw_space_before_value;
+                break;
+            }
+
+            if (ch == CR) {
+                r->header_name_end = p;
+                r->header_start = p;
+                r->header_end = p;
+                state = sw_almost_done;
+                break;
+            }
+
+            if (ch == LF) {
+                r->header_name_end = p;
+                r->header_start = p;
+                r->header_end = p;
+                goto done;
+            }
+
+            /* IIS may send the duplicate "HTTP/1.1 ..." lines */
+            if (ch == '/'
+                && r->upstream
+                && p - r->header_name_start == 4
+                && ngx_strncmp(r->header_name_start, "HTTP", 4) == 0)
+            {
+                state = sw_ignore_line;
+                break;
+            }
+
+            if (ch == '\0') {
+                return NGX_HTTP_PARSE_INVALID_HEADER;
+            }
+
+            r->invalid_header = 1;
+
+            break;
+
+        /* space* before header value */
+        case sw_space_before_value:
+            switch (ch) {
+            case ' ':
+                break;
+            case CR:
+                r->header_start = p;
+                r->header_end = p;
+                state = sw_almost_done;
+                break;
+            case LF:
+                r->header_start = p;
+                r->header_end = p;
+                goto done;
+            case '\0':
+                return NGX_HTTP_PARSE_INVALID_HEADER;
+            default:
+                r->header_start = p;
+                state = sw_value;
+                break;
+            }
+            break;
+
+        /* header value */
+        case sw_value:
+            switch (ch) {
+            case ' ':
+                r->header_end = p;
+                state = sw_space_after_value;
+                break;
+            case CR:
+                r->header_end = p;
+                state = sw_almost_done;
+                break;
+            case LF:
+                r->header_end = p;
+                goto done;
+            case '\0':
+                return NGX_HTTP_PARSE_INVALID_HEADER;
+            }
+            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;
+            case '\0':
+                return NGX_HTTP_PARSE_INVALID_HEADER;
+            default:
+                state = sw_value;
+                break;
+            }
+            break;
+
+        /* ignore header line */
+        case sw_ignore_line:
+            switch (ch) {
+            case LF:
+                state = sw_start;
+                break;
+            default:
+                break;
+            }
+            break;
+
+        /* end of header line */
+        case sw_almost_done:
+            switch (ch) {
+            case LF:
+                goto done;
+            case CR:
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_HEADER;
+            }
+            break;
+
+        /* end of header */
+        case sw_header_almost_done:
+            switch (ch) {
+            case LF:
+                goto header_done;
+            default:
+                return NGX_HTTP_PARSE_INVALID_HEADER;
+            }
+        }
+    }
+
+    b->pos = p;
+    r->state = state;
+    r->header_hash = hash;
+    r->lowcase_index = i;
+
+    return NGX_AGAIN;
+
+done:
+
+    b->pos = p + 1;
+    r->state = sw_start;
+    r->header_hash = hash;
+    r->lowcase_index = i;
+
+    return NGX_OK;
+
+header_done:
+
+    b->pos = p + 1;
+    r->state = sw_start;
+
+    return NGX_HTTP_PARSE_HEADER_DONE;
+}
+
+
+ngx_int_t
+ngx_http_parse_uri(ngx_http_request_t *r)
+{
+    u_char  *p, ch;
+    enum {
+        sw_start = 0,
+        sw_after_slash_in_uri,
+        sw_check_uri,
+        sw_uri
+    } state;
+
+    state = sw_start;
+
+    for (p = r->uri_start; p != r->uri_end; p++) {
+
+        ch = *p;
+
+        switch (state) {
+
+        case sw_start:
+
+            if (ch != '/') {
+                return NGX_ERROR;
+            }
+
+            state = sw_after_slash_in_uri;
+            break;
+
+        /* check "/.", "//", "%", and "\" (Win32) in URI */
+        case sw_after_slash_in_uri:
+
+            if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
+                state = sw_check_uri;
+                break;
+            }
+
+            switch (ch) {
+            case ' ':
+                r->space_in_uri = 1;
+                state = sw_check_uri;
+                break;
+            case '.':
+                r->complex_uri = 1;
+                state = sw_uri;
+                break;
+            case '%':
+                r->quoted_uri = 1;
+                state = sw_uri;
+                break;
+            case '/':
+                r->complex_uri = 1;
+                state = sw_uri;
+                break;
+#if (NGX_WIN32)
+            case '\\':
+                r->complex_uri = 1;
+                state = sw_uri;
+                break;
+#endif
+            case '?':
+                r->args_start = p + 1;
+                state = sw_uri;
+                break;
+            case '#':
+                r->complex_uri = 1;
+                state = sw_uri;
+                break;
+            case '+':
+                r->plus_in_uri = 1;
+                break;
+            default:
+                state = sw_check_uri;
+                break;
+            }
+            break;
+
+        /* check "/", "%" and "\" (Win32) in URI */
+        case sw_check_uri:
+
+            if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
+                break;
+            }
+
+            switch (ch) {
+            case '/':
+#if (NGX_WIN32)
+                if (r->uri_ext == p) {
+                    r->complex_uri = 1;
+                    state = sw_uri;
+                    break;
+                }
+#endif
+                r->uri_ext = NULL;
+                state = sw_after_slash_in_uri;
+                break;
+            case '.':
+                r->uri_ext = p + 1;
+                break;
+            case ' ':
+                r->space_in_uri = 1;
+                break;
+#if (NGX_WIN32)
+            case '\\':
+                r->complex_uri = 1;
+                state = sw_after_slash_in_uri;
+                break;
+#endif
+            case '%':
+                r->quoted_uri = 1;
+                state = sw_uri;
+                break;
+            case '?':
+                r->args_start = p + 1;
+                state = sw_uri;
+                break;
+            case '#':
+                r->complex_uri = 1;
+                state = sw_uri;
+                break;
+            case '+':
+                r->plus_in_uri = 1;
+                break;
+            }
+            break;
+
+        /* URI */
+        case sw_uri:
+
+            if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
+                break;
+            }
+
+            switch (ch) {
+            case ' ':
+                r->space_in_uri = 1;
+                break;
+            case '#':
+                r->complex_uri = 1;
+                break;
+            }
+            break;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_parse_complex_uri(ngx_http_request_t *r, ngx_uint_t merge_slashes)
+{
+    u_char  c, ch, decoded, *p, *u;
+    enum {
+        sw_usual = 0,
+        sw_slash,
+        sw_dot,
+        sw_dot_dot,
+        sw_quoted,
+        sw_quoted_second
+    } state, quoted_state;
+
+#if (NGX_SUPPRESS_WARN)
+    decoded = '\0';
+    quoted_state = sw_usual;
+#endif
+
+    state = sw_usual;
+    p = r->uri_start;
+    u = r->uri.data;
+    r->uri_ext = NULL;
+    r->args_start = NULL;
+
+    ch = *p++;
+
+    while (p <= r->uri_end) {
+
+        /*
+         * we use "ch = *p++" inside the cycle, but this operation is safe,
+         * because after the URI there is always at least one character:
+         * the line feed
+         */
+
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "s:%d in:'%Xd:%c'", state, ch, ch);
+
+        switch (state) {
+
+        case sw_usual:
+
+            if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
+                *u++ = ch;
+                ch = *p++;
+                break;
+            }
+
+            switch (ch) {
+#if (NGX_WIN32)
+            case '\\':
+                if (u - 2 >= r->uri.data
+                    && *(u - 1) == '.' && *(u - 2) != '.')
+                {
+                    u--;
+                }
+
+                r->uri_ext = NULL;
+
+                if (p == r->uri_start + r->uri.len) {
+
+                    /*
+                     * we omit the last "\" to cause redirect because
+                     * the browsers do not treat "\" as "/" in relative URL path
+                     */
+
+                    break;
+                }
+
+                state = sw_slash;
+                *u++ = '/';
+                break;
+#endif
+            case '/':
+#if (NGX_WIN32)
+                if (u - 2 >= r->uri.data
+                    && *(u - 1) == '.' && *(u - 2) != '.')
+                {
+                    u--;
+                }
+#endif
+                r->uri_ext = NULL;
+                state = sw_slash;
+                *u++ = ch;
+                break;
+            case '%':
+                quoted_state = state;
+                state = sw_quoted;
+                break;
+            case '?':
+                r->args_start = p;
+                goto args;
+            case '#':
+                goto done;
+            case '.':
+                r->uri_ext = u + 1;
+                *u++ = ch;
+                break;
+            case '+':
+                r->plus_in_uri = 1;
+                /* fall through */
+            default:
+                *u++ = ch;
+                break;
+            }
+
+            ch = *p++;
+            break;
+
+        case sw_slash:
+
+            if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
+                state = sw_usual;
+                *u++ = ch;
+                ch = *p++;
+                break;
+            }
+
+            switch (ch) {
+#if (NGX_WIN32)
+            case '\\':
+                break;
+#endif
+            case '/':
+                if (!merge_slashes) {
+                    *u++ = ch;
+                }
+                break;
+            case '.':
+                state = sw_dot;
+                *u++ = ch;
+                break;
+            case '%':
+                quoted_state = state;
+                state = sw_quoted;
+                break;
+            case '?':
+                r->args_start = p;
+                goto args;
+            case '#':
+                goto done;
+            case '+':
+                r->plus_in_uri = 1;
+                /* fall through */
+            default:
+                state = sw_usual;
+                *u++ = ch;
+                break;
+            }
+
+            ch = *p++;
+            break;
+
+        case sw_dot:
+
+            if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
+                state = sw_usual;
+                *u++ = ch;
+                ch = *p++;
+                break;
+            }
+
+            switch (ch) {
+#if (NGX_WIN32)
+            case '\\':
+#endif
+            case '/':
+                state = sw_slash;
+                u--;
+                break;
+            case '.':
+                state = sw_dot_dot;
+                *u++ = ch;
+                break;
+            case '%':
+                quoted_state = state;
+                state = sw_quoted;
+                break;
+            case '?':
+                r->args_start = p;
+                goto args;
+            case '#':
+                goto done;
+            case '+':
+                r->plus_in_uri = 1;
+                /* fall through */
+            default:
+                state = sw_usual;
+                *u++ = ch;
+                break;
+            }
+
+            ch = *p++;
+            break;
+
+        case sw_dot_dot:
+
+            if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
+                state = sw_usual;
+                *u++ = ch;
+                ch = *p++;
+                break;
+            }
+
+            switch (ch) {
+#if (NGX_WIN32)
+            case '\\':
+#endif
+            case '/':
+                state = sw_slash;
+                u -= 5;
+                for ( ;; ) {
+                    if (u < r->uri.data) {
+                        return NGX_HTTP_PARSE_INVALID_REQUEST;
+                    }
+                    if (*u == '/') {
+                        u++;
+                        break;
+                    }
+                    u--;
+                }
+                break;
+            case '%':
+                quoted_state = state;
+                state = sw_quoted;
+                break;
+            case '?':
+                r->args_start = p;
+                goto args;
+            case '#':
+                goto done;
+            case '+':
+                r->plus_in_uri = 1;
+                /* fall through */
+            default:
+                state = sw_usual;
+                *u++ = ch;
+                break;
+            }
+
+            ch = *p++;
+            break;
+
+        case sw_quoted:
+            r->quoted_uri = 1;
+
+            if (ch >= '0' && ch <= '9') {
+                decoded = (u_char) (ch - '0');
+                state = sw_quoted_second;
+                ch = *p++;
+                break;
+            }
+
+            c = (u_char) (ch | 0x20);
+            if (c >= 'a' && c <= 'f') {
+                decoded = (u_char) (c - 'a' + 10);
+                state = sw_quoted_second;
+                ch = *p++;
+                break;
+            }
+
+            return NGX_HTTP_PARSE_INVALID_REQUEST;
+
+        case sw_quoted_second:
+            if (ch >= '0' && ch <= '9') {
+                ch = (u_char) ((decoded << 4) + (ch - '0'));
+
+                if (ch == '%' || ch == '#') {
+                    state = sw_usual;
+                    *u++ = ch;
+                    ch = *p++;
+                    break;
+
+                } else if (ch == '\0') {
+                    return NGX_HTTP_PARSE_INVALID_REQUEST;
+                }
+
+                state = quoted_state;
+                break;
+            }
+
+            c = (u_char) (ch | 0x20);
+            if (c >= 'a' && c <= 'f') {
+                ch = (u_char) ((decoded << 4) + (c - 'a') + 10);
+
+                if (ch == '?') {
+                    state = sw_usual;
+                    *u++ = ch;
+                    ch = *p++;
+                    break;
+
+                } else if (ch == '+') {
+                    r->plus_in_uri = 1;
+                }
+
+                state = quoted_state;
+                break;
+            }
+
+            return NGX_HTTP_PARSE_INVALID_REQUEST;
+        }
+    }
+
+done:
+
+    r->uri.len = u - r->uri.data;
+
+    if (r->uri_ext) {
+        r->exten.len = u - r->uri_ext;
+        r->exten.data = r->uri_ext;
+    }
+
+    r->uri_ext = NULL;
+
+    return NGX_OK;
+
+args:
+
+    while (p < r->uri_end) {
+        if (*p++ != '#') {
+            continue;
+        }
+
+        r->args.len = p - 1 - r->args_start;
+        r->args.data = r->args_start;
+        r->args_start = NULL;
+
+        break;
+    }
+
+    r->uri.len = u - r->uri.data;
+
+    if (r->uri_ext) {
+        r->exten.len = u - r->uri_ext;
+        r->exten.data = r->uri_ext;
+    }
+
+    r->uri_ext = NULL;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_parse_status_line(ngx_http_request_t *r, ngx_buf_t *b,
+    ngx_http_status_t *status)
+{
+    u_char   ch;
+    u_char  *p;
+    enum {
+        sw_start = 0,
+        sw_H,
+        sw_HT,
+        sw_HTT,
+        sw_HTTP,
+        sw_first_major_digit,
+        sw_major_digit,
+        sw_first_minor_digit,
+        sw_minor_digit,
+        sw_status,
+        sw_space_after_status,
+        sw_status_text,
+        sw_almost_done
+    } state;
+
+    state = r->state;
+
+    for (p = b->pos; p < b->last; p++) {
+        ch = *p;
+
+        switch (state) {
+
+        /* "HTTP/" */
+        case sw_start:
+            switch (ch) {
+            case 'H':
+                state = sw_H;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        case sw_H:
+            switch (ch) {
+            case 'T':
+                state = sw_HT;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        case sw_HT:
+            switch (ch) {
+            case 'T':
+                state = sw_HTT;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        case sw_HTT:
+            switch (ch) {
+            case 'P':
+                state = sw_HTTP;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        case sw_HTTP:
+            switch (ch) {
+            case '/':
+                state = sw_first_major_digit;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        /* the first digit of major HTTP version */
+        case sw_first_major_digit:
+            if (ch < '1' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            r->http_major = ch - '0';
+            state = sw_major_digit;
+            break;
+
+        /* the major HTTP version or dot */
+        case sw_major_digit:
+            if (ch == '.') {
+                state = sw_first_minor_digit;
+                break;
+            }
+
+            if (ch < '0' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            if (r->http_major > 99) {
+                return NGX_ERROR;
+            }
+
+            r->http_major = r->http_major * 10 + (ch - '0');
+            break;
+
+        /* the first digit of minor HTTP version */
+        case sw_first_minor_digit:
+            if (ch < '0' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            r->http_minor = ch - '0';
+            state = sw_minor_digit;
+            break;
+
+        /* the minor HTTP version or the end of the request line */
+        case sw_minor_digit:
+            if (ch == ' ') {
+                state = sw_status;
+                break;
+            }
+
+            if (ch < '0' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            if (r->http_minor > 99) {
+                return NGX_ERROR;
+            }
+
+            r->http_minor = r->http_minor * 10 + (ch - '0');
+            break;
+
+        /* HTTP status code */
+        case sw_status:
+            if (ch == ' ') {
+                break;
+            }
+
+            if (ch < '0' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            status->code = status->code * 10 + (ch - '0');
+
+            if (++status->count == 3) {
+                state = sw_space_after_status;
+                status->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:
+                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:
+                goto done;
+            }
+            break;
+
+        /* end of status line */
+        case sw_almost_done:
+            status->end = p - 1;
+            switch (ch) {
+            case LF:
+                goto done;
+            default:
+                return NGX_ERROR;
+            }
+        }
+    }
+
+    b->pos = p;
+    r->state = state;
+
+    return NGX_AGAIN;
+
+done:
+
+    b->pos = p + 1;
+
+    if (status->end == NULL) {
+        status->end = p;
+    }
+
+    status->http_version = r->http_major * 1000 + r->http_minor;
+    r->state = sw_start;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_parse_unsafe_uri(ngx_http_request_t *r, ngx_str_t *uri,
+    ngx_str_t *args, ngx_uint_t *flags)
+{
+    u_char      ch, *p, *src, *dst;
+    size_t      len;
+    ngx_uint_t  quoted;
+
+    len = uri->len;
+    p = uri->data;
+    quoted = 0;
+
+    if (len == 0 || p[0] == '?') {
+        goto unsafe;
+    }
+
+    if (p[0] == '.' && len > 1 && p[1] == '.'
+        && (len == 2 || ngx_path_separator(p[2])))
+    {
+        goto unsafe;
+    }
+
+    for ( /* void */ ; len; len--) {
+
+        ch = *p++;
+
+        if (ch == '%') {
+            quoted = 1;
+            continue;
+        }
+
+        if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
+            continue;
+        }
+
+        if (ch == '?') {
+            args->len = len - 1;
+            args->data = p;
+            uri->len -= len;
+
+            break;
+        }
+
+        if (ch == '\0') {
+            goto unsafe;
+        }
+
+        if (ngx_path_separator(ch) && len > 2) {
+
+            /* detect "/../" and "/.." */
+
+            if (p[0] == '.' && p[1] == '.'
+                && (len == 3 || ngx_path_separator(p[2])))
+            {
+                goto unsafe;
+            }
+        }
+    }
+
+    if (quoted) {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "escaped URI: \"%V\"", uri);
+
+        src = uri->data;
+
+        dst = ngx_pnalloc(r->pool, uri->len);
+        if (dst == NULL) {
+            return NGX_ERROR;
+        }
+
+        uri->data = dst;
+
+        ngx_unescape_uri(&dst, &src, uri->len, 0);
+
+        uri->len = dst - uri->data;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "unescaped URI: \"%V\"", uri);
+
+        len = uri->len;
+        p = uri->data;
+
+        if (p[0] == '.' && len > 1 && p[1] == '.'
+            && (len == 2 || ngx_path_separator(p[2])))
+        {
+            goto unsafe;
+        }
+
+        for ( /* void */ ; len; len--) {
+
+            ch = *p++;
+
+            if (ch == '\0') {
+                goto unsafe;
+            }
+
+            if (ngx_path_separator(ch) && len > 2) {
+
+                /* detect "/../" and "/.." */
+
+                if (p[0] == '.' && p[1] == '.'
+                    && (len == 3 || ngx_path_separator(p[2])))
+                {
+                    goto unsafe;
+                }
+            }
+        }
+    }
+
+    return NGX_OK;
+
+unsafe:
+
+    if (*flags & NGX_HTTP_LOG_UNSAFE) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "unsafe URI \"%V\" was detected", uri);
+    }
+
+    return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_http_parse_multi_header_lines(ngx_array_t *headers, ngx_str_t *name,
+    ngx_str_t *value)
+{
+    ngx_uint_t         i;
+    u_char            *start, *last, *end, ch;
+    ngx_table_elt_t  **h;
+
+    h = headers->elts;
+
+    for (i = 0; i < headers->nelts; i++) {
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, headers->pool->log, 0,
+                       "parse header: \"%V: %V\"", &h[i]->key, &h[i]->value);
+
+        if (name->len > h[i]->value.len) {
+            continue;
+        }
+
+        start = h[i]->value.data;
+        end = h[i]->value.data + h[i]->value.len;
+
+        while (start < end) {
+
+            if (ngx_strncasecmp(start, name->data, name->len) != 0) {
+                goto skip;
+            }
+
+            for (start += name->len; start < end && *start == ' '; start++) {
+                /* void */
+            }
+
+            if (value == NULL) {
+                if (start == end || *start == ',') {
+                    return i;
+                }
+
+                goto skip;
+            }
+
+            if (start == end || *start++ != '=') {
+                /* the invalid header value */
+                goto skip;
+            }
+
+            while (start < end && *start == ' ') { start++; }
+
+            for (last = start; last < end && *last != ';'; last++) {
+                /* void */
+            }
+
+            value->len = last - start;
+            value->data = start;
+
+            return i;
+
+        skip:
+
+            while (start < end) {
+                ch = *start++;
+                if (ch == ';' || ch == ',') {
+                    break;
+                }
+            }
+
+            while (start < end && *start == ' ') { start++; }
+        }
+    }
+
+    return NGX_DECLINED;
+}
+
+
+ngx_int_t
+ngx_http_parse_set_cookie_lines(ngx_array_t *headers, ngx_str_t *name,
+    ngx_str_t *value)
+{
+    ngx_uint_t         i;
+    u_char            *start, *last, *end;
+    ngx_table_elt_t  **h;
+
+    h = headers->elts;
+
+    for (i = 0; i < headers->nelts; i++) {
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, headers->pool->log, 0,
+                       "parse header: \"%V: %V\"", &h[i]->key, &h[i]->value);
+
+        if (name->len >= h[i]->value.len) {
+            continue;
+        }
+
+        start = h[i]->value.data;
+        end = h[i]->value.data + h[i]->value.len;
+
+        if (ngx_strncasecmp(start, name->data, name->len) != 0) {
+            continue;
+        }
+
+        for (start += name->len; start < end && *start == ' '; start++) {
+            /* void */
+        }
+
+        if (start == end || *start++ != '=') {
+            /* the invalid header value */
+            continue;
+        }
+
+        while (start < end && *start == ' ') { start++; }
+
+        for (last = start; last < end && *last != ';'; last++) {
+            /* void */
+        }
+
+        value->len = last - start;
+        value->data = start;
+
+        return i;
+    }
+
+    return NGX_DECLINED;
+}
+
+
+ngx_int_t
+ngx_http_arg(ngx_http_request_t *r, u_char *name, size_t len, ngx_str_t *value)
+{
+    u_char  *p, *last;
+
+    if (r->args.len == 0) {
+        return NGX_DECLINED;
+    }
+
+    p = r->args.data;
+    last = p + r->args.len;
+
+    for ( /* void */ ; p < last; p++) {
+
+        /* we need '=' after name, so drop one char from last */
+
+        p = ngx_strlcasestrn(p, last - 1, name, len - 1);
+
+        if (p == NULL) {
+            return NGX_DECLINED;
+        }
+
+        if ((p == r->args.data || *(p - 1) == '&') && *(p + len) == '=') {
+
+            value->data = p + len + 1;
+
+            p = ngx_strlchr(p, last, '&');
+
+            if (p == NULL) {
+                p = r->args.data + r->args.len;
+            }
+
+            value->len = p - value->data;
+
+            return NGX_OK;
+        }
+    }
+
+    return NGX_DECLINED;
+}
+
+
+void
+ngx_http_split_args(ngx_http_request_t *r, ngx_str_t *uri, ngx_str_t *args)
+{
+    u_char  *p, *last;
+
+    last = uri->data + uri->len;
+
+    p = ngx_strlchr(uri->data, last, '?');
+
+    if (p) {
+        uri->len = p - uri->data;
+        p++;
+        args->len = last - p;
+        args->data = p;
+
+    } else {
+        args->len = 0;
+    }
+}
+
+
+ngx_int_t
+ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b,
+    ngx_http_chunked_t *ctx)
+{
+    u_char     *pos, ch, c;
+    ngx_int_t   rc;
+    enum {
+        sw_chunk_start = 0,
+        sw_chunk_size,
+        sw_chunk_extension,
+        sw_chunk_extension_almost_done,
+        sw_chunk_data,
+        sw_after_data,
+        sw_after_data_almost_done,
+        sw_last_chunk_extension,
+        sw_last_chunk_extension_almost_done,
+        sw_trailer,
+        sw_trailer_almost_done,
+        sw_trailer_header,
+        sw_trailer_header_almost_done
+    } state;
+
+    state = ctx->state;
+
+    if (state == sw_chunk_data && ctx->size == 0) {
+        state = sw_after_data;
+    }
+
+    rc = NGX_AGAIN;
+
+    for (pos = b->pos; pos < b->last; pos++) {
+
+        ch = *pos;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http chunked byte: %02Xd s:%d", ch, state);
+
+        switch (state) {
+
+        case sw_chunk_start:
+            if (ch >= '0' && ch <= '9') {
+                state = sw_chunk_size;
+                ctx->size = ch - '0';
+                break;
+            }
+
+            c = (u_char) (ch | 0x20);
+
+            if (c >= 'a' && c <= 'f') {
+                state = sw_chunk_size;
+                ctx->size = c - 'a' + 10;
+                break;
+            }
+
+            goto invalid;
+
+        case sw_chunk_size:
+            if (ctx->size > NGX_MAX_OFF_T_VALUE / 16) {
+                goto invalid;
+            }
+
+            if (ch >= '0' && ch <= '9') {
+                ctx->size = ctx->size * 16 + (ch - '0');
+                break;
+            }
+
+            c = (u_char) (ch | 0x20);
+
+            if (c >= 'a' && c <= 'f') {
+                ctx->size = ctx->size * 16 + (c - 'a' + 10);
+                break;
+            }
+
+            if (ctx->size == 0) {
+
+                switch (ch) {
+                case CR:
+                    state = sw_last_chunk_extension_almost_done;
+                    break;
+                case LF:
+                    state = sw_trailer;
+                    break;
+                case ';':
+                case ' ':
+                case '\t':
+                    state = sw_last_chunk_extension;
+                    break;
+                default:
+                    goto invalid;
+                }
+
+                break;
+            }
+
+            switch (ch) {
+            case CR:
+                state = sw_chunk_extension_almost_done;
+                break;
+            case LF:
+                state = sw_chunk_data;
+                break;
+            case ';':
+            case ' ':
+            case '\t':
+                state = sw_chunk_extension;
+                break;
+            default:
+                goto invalid;
+            }
+
+            break;
+
+        case sw_chunk_extension:
+            switch (ch) {
+            case CR:
+                state = sw_chunk_extension_almost_done;
+                break;
+            case LF:
+                state = sw_chunk_data;
+            }
+            break;
+
+        case sw_chunk_extension_almost_done:
+            if (ch == LF) {
+                state = sw_chunk_data;
+                break;
+            }
+            goto invalid;
+
+        case sw_chunk_data:
+            rc = NGX_OK;
+            goto data;
+
+        case sw_after_data:
+            switch (ch) {
+            case CR:
+                state = sw_after_data_almost_done;
+                break;
+            case LF:
+                state = sw_chunk_start;
+            }
+            break;
+
+        case sw_after_data_almost_done:
+            if (ch == LF) {
+                state = sw_chunk_start;
+                break;
+            }
+            goto invalid;
+
+        case sw_last_chunk_extension:
+            switch (ch) {
+            case CR:
+                state = sw_last_chunk_extension_almost_done;
+                break;
+            case LF:
+                state = sw_trailer;
+            }
+            break;
+
+        case sw_last_chunk_extension_almost_done:
+            if (ch == LF) {
+                state = sw_trailer;
+                break;
+            }
+            goto invalid;
+
+        case sw_trailer:
+            switch (ch) {
+            case CR:
+                state = sw_trailer_almost_done;
+                break;
+            case LF:
+                goto done;
+            default:
+                state = sw_trailer_header;
+            }
+            break;
+
+        case sw_trailer_almost_done:
+            if (ch == LF) {
+                goto done;
+            }
+            goto invalid;
+
+        case sw_trailer_header:
+            switch (ch) {
+            case CR:
+                state = sw_trailer_header_almost_done;
+                break;
+            case LF:
+                state = sw_trailer;
+            }
+            break;
+
+        case sw_trailer_header_almost_done:
+            if (ch == LF) {
+                state = sw_trailer;
+                break;
+            }
+            goto invalid;
+
+        }
+    }
+
+data:
+
+    ctx->state = state;
+    b->pos = pos;
+
+    if (ctx->size > NGX_MAX_OFF_T_VALUE - 5) {
+        goto invalid;
+    }
+
+    switch (state) {
+
+    case sw_chunk_start:
+        ctx->length = 3 /* "0" LF LF */;
+        break;
+    case sw_chunk_size:
+        ctx->length = 1 /* LF */
+                      + (ctx->size ? ctx->size + 4 /* LF "0" LF LF */
+                                   : 1 /* LF */);
+        break;
+    case sw_chunk_extension:
+    case sw_chunk_extension_almost_done:
+        ctx->length = 1 /* LF */ + ctx->size + 4 /* LF "0" LF LF */;
+        break;
+    case sw_chunk_data:
+        ctx->length = ctx->size + 4 /* LF "0" LF LF */;
+        break;
+    case sw_after_data:
+    case sw_after_data_almost_done:
+        ctx->length = 4 /* LF "0" LF LF */;
+        break;
+    case sw_last_chunk_extension:
+    case sw_last_chunk_extension_almost_done:
+        ctx->length = 2 /* LF LF */;
+        break;
+    case sw_trailer:
+    case sw_trailer_almost_done:
+        ctx->length = 1 /* LF */;
+        break;
+    case sw_trailer_header:
+    case sw_trailer_header_almost_done:
+        ctx->length = 2 /* LF LF */;
+        break;
+
+    }
+
+    return rc;
+
+done:
+
+    ctx->state = 0;
+    b->pos = pos + 1;
+
+    return NGX_DONE;
+
+invalid:
+
+    return NGX_ERROR;
+}
diff --git a/nginx/src/http/ngx_http_postpone_filter_module.c b/nginx/src/http/ngx_http_postpone_filter_module.c
new file mode 100644 (file)
index 0000000..599d263
--- /dev/null
@@ -0,0 +1,259 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_postpone_filter_add(ngx_http_request_t *r,
+    ngx_chain_t *in);
+static ngx_int_t ngx_http_postpone_filter_in_memory(ngx_http_request_t *r,
+    ngx_chain_t *in);
+static ngx_int_t ngx_http_postpone_filter_init(ngx_conf_t *cf);
+
+
+static ngx_http_module_t  ngx_http_postpone_filter_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_postpone_filter_init,         /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_postpone_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_postpone_filter_module_ctx,  /* module context */
+    NULL,                                  /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_postpone_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    ngx_connection_t              *c;
+    ngx_http_postponed_request_t  *pr;
+
+    c = r->connection;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http postpone filter \"%V?%V\" %p", &r->uri, &r->args, in);
+
+    if (r->subrequest_in_memory) {
+        return ngx_http_postpone_filter_in_memory(r, in);
+    }
+
+    if (r != c->data) {
+
+        if (in) {
+            if (ngx_http_postpone_filter_add(r, in) != NGX_OK) {
+                return NGX_ERROR;
+            }
+
+            return NGX_OK;
+        }
+
+#if 0
+        /* TODO: SSI may pass NULL */
+        ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                      "http postpone filter NULL inactive request");
+#endif
+
+        return NGX_OK;
+    }
+
+    if (r->postponed == NULL) {
+
+        if (in || c->buffered) {
+            return ngx_http_next_body_filter(r->main, in);
+        }
+
+        return NGX_OK;
+    }
+
+    if (in) {
+        if (ngx_http_postpone_filter_add(r, in) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    do {
+        pr = r->postponed;
+
+        if (pr->request) {
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                           "http postpone filter wake \"%V?%V\"",
+                           &pr->request->uri, &pr->request->args);
+
+            r->postponed = pr->next;
+
+            c->data = pr->request;
+
+            return ngx_http_post_request(pr->request, NULL);
+        }
+
+        if (pr->out == NULL) {
+            ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                          "http postpone filter NULL output");
+
+        } else {
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                           "http postpone filter output \"%V?%V\"",
+                           &r->uri, &r->args);
+
+            if (ngx_http_next_body_filter(r->main, pr->out) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+        }
+
+        r->postponed = pr->next;
+
+    } while (r->postponed);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_postpone_filter_add(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    ngx_http_postponed_request_t  *pr, **ppr;
+
+    if (r->postponed) {
+        for (pr = r->postponed; pr->next; pr = pr->next) { /* void */ }
+
+        if (pr->request == NULL) {
+            goto found;
+        }
+
+        ppr = &pr->next;
+
+    } else {
+        ppr = &r->postponed;
+    }
+
+    pr = ngx_palloc(r->pool, sizeof(ngx_http_postponed_request_t));
+    if (pr == NULL) {
+        return NGX_ERROR;
+    }
+
+    *ppr = pr;
+
+    pr->request = NULL;
+    pr->out = NULL;
+    pr->next = NULL;
+
+found:
+
+    if (ngx_chain_add_copy(r->pool, &pr->out, in) == NGX_OK) {
+        return NGX_OK;
+    }
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_postpone_filter_in_memory(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    size_t                     len;
+    ngx_buf_t                 *b;
+    ngx_connection_t          *c;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    c = r->connection;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http postpone filter in memory");
+
+    if (r->out == NULL) {
+        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+        if (r->headers_out.content_length_n != -1) {
+            len = r->headers_out.content_length_n;
+
+            if (len > clcf->subrequest_output_buffer_size) {
+                ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                              "too big subrequest response: %uz", len);
+                return NGX_ERROR;
+            }
+
+        } else {
+            len = clcf->subrequest_output_buffer_size;
+        }
+
+        b = ngx_create_temp_buf(r->pool, len);
+        if (b == NULL) {
+            return NGX_ERROR;
+        }
+
+        b->last_buf = 1;
+
+        r->out = ngx_alloc_chain_link(r->pool);
+        if (r->out == NULL) {
+            return NGX_ERROR;
+        }
+
+        r->out->buf = b;
+        r->out->next = NULL;
+    }
+
+    b = r->out->buf;
+
+    for ( /* void */ ; in; in = in->next) {
+
+        if (ngx_buf_special(in->buf)) {
+            continue;
+        }
+
+        len = in->buf->last - in->buf->pos;
+
+        if (len > (size_t) (b->end - b->last)) {
+            ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                          "too big subrequest response");
+            return NGX_ERROR;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http postpone filter in memory %uz bytes", len);
+
+        b->last = ngx_cpymem(b->last, in->buf->pos, len);
+        in->buf->pos = in->buf->last;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_postpone_filter_init(ngx_conf_t *cf)
+{
+    ngx_http_next_body_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_postpone_filter;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/ngx_http_request.c b/nginx/src/http/ngx_http_request.c
new file mode 100644 (file)
index 0000000..968ff84
--- /dev/null
@@ -0,0 +1,3707 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static void ngx_http_wait_request_handler(ngx_event_t *ev);
+static void ngx_http_process_request_line(ngx_event_t *rev);
+static void ngx_http_process_request_headers(ngx_event_t *rev);
+static ssize_t ngx_http_read_request_header(ngx_http_request_t *r);
+static ngx_int_t ngx_http_alloc_large_header_buffer(ngx_http_request_t *r,
+    ngx_uint_t request_line);
+
+static ngx_int_t ngx_http_process_header_line(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_process_unique_header_line(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_process_multi_header_lines(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_process_host(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_process_connection(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_process_user_agent(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+
+static ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool,
+    ngx_uint_t alloc);
+static ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r,
+    ngx_str_t *host);
+static ngx_int_t ngx_http_find_virtual_server(ngx_connection_t *c,
+    ngx_http_virtual_names_t *virtual_names, ngx_str_t *host,
+    ngx_http_request_t *r, ngx_http_core_srv_conf_t **cscfp);
+
+static void ngx_http_request_handler(ngx_event_t *ev);
+static void ngx_http_terminate_request(ngx_http_request_t *r, ngx_int_t rc);
+static void ngx_http_terminate_handler(ngx_http_request_t *r);
+static void ngx_http_finalize_connection(ngx_http_request_t *r);
+static ngx_int_t ngx_http_set_write_handler(ngx_http_request_t *r);
+static void ngx_http_writer(ngx_http_request_t *r);
+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_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);
+static void ngx_http_log_request(ngx_http_request_t *r);
+
+static u_char *ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len);
+static u_char *ngx_http_log_error_handler(ngx_http_request_t *r,
+    ngx_http_request_t *sr, u_char *buf, size_t len);
+
+#if (NGX_HTTP_SSL)
+static void ngx_http_ssl_handshake(ngx_event_t *rev);
+static void ngx_http_ssl_handshake_handler(ngx_connection_t *c);
+#endif
+
+
+static char *ngx_http_client_errors[] = {
+
+    /* NGX_HTTP_PARSE_INVALID_METHOD */
+    "client sent invalid method",
+
+    /* NGX_HTTP_PARSE_INVALID_REQUEST */
+    "client sent invalid request",
+
+    /* NGX_HTTP_PARSE_INVALID_VERSION */
+    "client sent invalid version",
+
+    /* NGX_HTTP_PARSE_INVALID_09_METHOD */
+    "client sent invalid method in HTTP/0.9 request"
+};
+
+
+ngx_http_header_t  ngx_http_headers_in[] = {
+    { ngx_string("Host"), offsetof(ngx_http_headers_in_t, host),
+                 ngx_http_process_host },
+
+    { ngx_string("Connection"), offsetof(ngx_http_headers_in_t, connection),
+                 ngx_http_process_connection },
+
+    { ngx_string("If-Modified-Since"),
+                 offsetof(ngx_http_headers_in_t, if_modified_since),
+                 ngx_http_process_unique_header_line },
+
+    { ngx_string("If-Unmodified-Since"),
+                 offsetof(ngx_http_headers_in_t, if_unmodified_since),
+                 ngx_http_process_unique_header_line },
+
+    { ngx_string("If-Match"),
+                 offsetof(ngx_http_headers_in_t, if_match),
+                 ngx_http_process_unique_header_line },
+
+    { ngx_string("If-None-Match"),
+                 offsetof(ngx_http_headers_in_t, if_none_match),
+                 ngx_http_process_unique_header_line },
+
+    { ngx_string("User-Agent"), offsetof(ngx_http_headers_in_t, user_agent),
+                 ngx_http_process_user_agent },
+
+    { ngx_string("Referer"), offsetof(ngx_http_headers_in_t, referer),
+                 ngx_http_process_header_line },
+
+    { ngx_string("Content-Length"),
+                 offsetof(ngx_http_headers_in_t, content_length),
+                 ngx_http_process_unique_header_line },
+
+    { ngx_string("Content-Range"),
+                 offsetof(ngx_http_headers_in_t, content_range),
+                 ngx_http_process_unique_header_line },
+
+    { ngx_string("Content-Type"),
+                 offsetof(ngx_http_headers_in_t, content_type),
+                 ngx_http_process_header_line },
+
+    { ngx_string("Range"), offsetof(ngx_http_headers_in_t, range),
+                 ngx_http_process_header_line },
+
+    { ngx_string("If-Range"),
+                 offsetof(ngx_http_headers_in_t, if_range),
+                 ngx_http_process_unique_header_line },
+
+    { ngx_string("Transfer-Encoding"),
+                 offsetof(ngx_http_headers_in_t, transfer_encoding),
+                 ngx_http_process_header_line },
+
+    { ngx_string("TE"),
+                 offsetof(ngx_http_headers_in_t, te),
+                 ngx_http_process_header_line },
+
+    { ngx_string("Expect"),
+                 offsetof(ngx_http_headers_in_t, expect),
+                 ngx_http_process_unique_header_line },
+
+    { ngx_string("Upgrade"),
+                 offsetof(ngx_http_headers_in_t, upgrade),
+                 ngx_http_process_header_line },
+
+#if (NGX_HTTP_GZIP || NGX_HTTP_HEADERS)
+    { ngx_string("Accept-Encoding"),
+                 offsetof(ngx_http_headers_in_t, accept_encoding),
+                 ngx_http_process_header_line },
+
+    { ngx_string("Via"), offsetof(ngx_http_headers_in_t, via),
+                 ngx_http_process_header_line },
+#endif
+
+    { ngx_string("Authorization"),
+                 offsetof(ngx_http_headers_in_t, authorization),
+                 ngx_http_process_unique_header_line },
+
+    { ngx_string("Keep-Alive"), offsetof(ngx_http_headers_in_t, keep_alive),
+                 ngx_http_process_header_line },
+
+#if (NGX_HTTP_X_FORWARDED_FOR)
+    { ngx_string("X-Forwarded-For"),
+                 offsetof(ngx_http_headers_in_t, x_forwarded_for),
+                 ngx_http_process_multi_header_lines },
+#endif
+
+#if (NGX_HTTP_REALIP)
+    { ngx_string("X-Real-IP"),
+                 offsetof(ngx_http_headers_in_t, x_real_ip),
+                 ngx_http_process_header_line },
+#endif
+
+#if (NGX_HTTP_HEADERS)
+    { ngx_string("Accept"), offsetof(ngx_http_headers_in_t, accept),
+                 ngx_http_process_header_line },
+
+    { ngx_string("Accept-Language"),
+                 offsetof(ngx_http_headers_in_t, accept_language),
+                 ngx_http_process_header_line },
+#endif
+
+#if (NGX_HTTP_DAV)
+    { ngx_string("Depth"), offsetof(ngx_http_headers_in_t, depth),
+                 ngx_http_process_header_line },
+
+    { ngx_string("Destination"), offsetof(ngx_http_headers_in_t, destination),
+                 ngx_http_process_header_line },
+
+    { ngx_string("Overwrite"), offsetof(ngx_http_headers_in_t, overwrite),
+                 ngx_http_process_header_line },
+
+    { ngx_string("Date"), offsetof(ngx_http_headers_in_t, date),
+                 ngx_http_process_header_line },
+#endif
+
+    { ngx_string("Cookie"), offsetof(ngx_http_headers_in_t, cookies),
+                 ngx_http_process_multi_header_lines },
+
+    { ngx_null_string, 0, NULL }
+};
+
+
+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;
+#if (NGX_HAVE_INET6)
+    struct sockaddr_in6    *sin6;
+    ngx_http_in6_addr_t    *addr6;
+#endif
+
+    hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
+    if (hc == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    c->data = hc;
+
+    /* find the server configuration for the address:port */
+
+    port = c->listening->servers;
+
+    if (port->naddrs > 1) {
+
+        /*
+         * there are several addresses on this port and one of them
+         * is an "*:port" wildcard so getsockname() in ngx_http_server_addr()
+         * is required to determine a server address
+         */
+
+        if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        switch (c->local_sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+        case AF_INET6:
+            sin6 = (struct sockaddr_in6 *) c->local_sockaddr;
+
+            addr6 = port->addrs;
+
+            /* the last address is "*" */
+
+            for (i = 0; i < port->naddrs - 1; i++) {
+                if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) {
+                    break;
+                }
+            }
+
+            hc->addr_conf = &addr6[i].conf;
+
+            break;
+#endif
+
+        default: /* AF_INET */
+            sin = (struct sockaddr_in *) c->local_sockaddr;
+
+            addr = port->addrs;
+
+            /* the last address is "*" */
+
+            for (i = 0; i < port->naddrs - 1; i++) {
+                if (addr[i].addr == sin->sin_addr.s_addr) {
+                    break;
+                }
+            }
+
+            hc->addr_conf = &addr[i].conf;
+
+            break;
+        }
+
+    } else {
+
+        switch (c->local_sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+        case AF_INET6:
+            addr6 = port->addrs;
+            hc->addr_conf = &addr6[0].conf;
+            break;
+#endif
+
+        default: /* AF_INET */
+            addr = port->addrs;
+            hc->addr_conf = &addr[0].conf;
+            break;
+        }
+    }
+
+    /* the default server configuration for the address:port */
+    hc->conf_ctx = hc->addr_conf->default_server->ctx;
+
+    ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t));
+    if (ctx == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    ctx->connection = c;
+    ctx->request = NULL;
+    ctx->current_request = NULL;
+
+    c->log->connection = c->number;
+    c->log->handler = ngx_http_log_error;
+    c->log->data = ctx;
+    c->log->action = "waiting for request";
+
+    c->log_error = NGX_ERROR_INFO;
+
+    rev = c->read;
+    rev->handler = ngx_http_wait_request_handler;
+    c->write->handler = ngx_http_empty_handler;
+
+#if (NGX_HTTP_V2)
+    if (hc->addr_conf->http2) {
+        rev->handler = ngx_http_v2_init;
+    }
+#endif
+
+#if (NGX_HTTP_SSL)
+    {
+    ngx_http_ssl_srv_conf_t  *sscf;
+
+    sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module);
+
+    if (sscf->enable || hc->addr_conf->ssl) {
+
+        c->log->action = "SSL handshaking";
+
+        if (hc->addr_conf->ssl && sscf->ssl.ctx == NULL) {
+            ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                          "no \"ssl_certificate\" is defined "
+                          "in server listening on SSL port");
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        hc->ssl = 1;
+
+        rev->handler = ngx_http_ssl_handshake;
+    }
+    }
+#endif
+
+    if (hc->addr_conf->proxy_protocol) {
+        hc->proxy_protocol = 1;
+        c->log->action = "reading PROXY protocol";
+    }
+
+    if (rev->ready) {
+        /* the deferred accept(), iocp */
+
+        if (ngx_use_accept_mutex) {
+            ngx_post_event(rev, &ngx_posted_events);
+            return;
+        }
+
+        rev->handler(rev);
+        return;
+    }
+
+    ngx_add_timer(rev, c->listening->post_accept_timeout);
+    ngx_reusable_connection(c, 1);
+
+    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+        ngx_http_close_connection(c);
+        return;
+    }
+}
+
+
+static void
+ngx_http_wait_request_handler(ngx_event_t *rev)
+{
+    u_char                    *p;
+    size_t                     size;
+    ssize_t                    n;
+    ngx_buf_t                 *b;
+    ngx_connection_t          *c;
+    ngx_http_connection_t     *hc;
+    ngx_http_core_srv_conf_t  *cscf;
+
+    c = rev->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http wait request handler");
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    if (c->close) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    hc = c->data;
+    cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);
+
+    size = cscf->client_header_buffer_size;
+
+    b = c->buffer;
+
+    if (b == NULL) {
+        b = ngx_create_temp_buf(c->pool, size);
+        if (b == NULL) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        c->buffer = b;
+
+    } else if (b->start == NULL) {
+
+        b->start = ngx_palloc(c->pool, size);
+        if (b->start == NULL) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        b->pos = b->start;
+        b->last = b->start;
+        b->end = b->last + size;
+    }
+
+    n = c->recv(c, b->last, size);
+
+    if (n == NGX_AGAIN) {
+
+        if (!rev->timer_set) {
+            ngx_add_timer(rev, c->listening->post_accept_timeout);
+            ngx_reusable_connection(c, 1);
+        }
+
+        if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        /*
+         * We are trying to not hold c->buffer's memory for an idle connection.
+         */
+
+        if (ngx_pfree(c->pool, b->start) == NGX_OK) {
+            b->start = NULL;
+        }
+
+        return;
+    }
+
+    if (n == NGX_ERROR) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    if (n == 0) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "client closed connection");
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    b->last += n;
+
+    if (hc->proxy_protocol) {
+        hc->proxy_protocol = 0;
+
+        p = ngx_proxy_protocol_read(c, b->pos, b->last);
+
+        if (p == NULL) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        b->pos = p;
+
+        if (b->pos == b->last) {
+            c->log->action = "waiting for request";
+            b->pos = b->start;
+            b->last = b->start;
+            ngx_post_event(rev, &ngx_posted_events);
+            return;
+        }
+    }
+
+    c->log->action = "reading client request line";
+
+    ngx_reusable_connection(c, 0);
+
+    c->data = ngx_http_create_request(c);
+    if (c->data == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    rev->handler = ngx_http_process_request_line;
+    ngx_http_process_request_line(rev);
+}
+
+
+ngx_http_request_t *
+ngx_http_create_request(ngx_connection_t *c)
+{
+    ngx_pool_t                 *pool;
+    ngx_time_t                 *tp;
+    ngx_http_request_t         *r;
+    ngx_http_log_ctx_t         *ctx;
+    ngx_http_connection_t      *hc;
+    ngx_http_core_srv_conf_t   *cscf;
+    ngx_http_core_loc_conf_t   *clcf;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    c->requests++;
+
+    hc = c->data;
+
+    cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);
+
+    pool = ngx_create_pool(cscf->request_pool_size, c->log);
+    if (pool == NULL) {
+        return NULL;
+    }
+
+    r = ngx_pcalloc(pool, sizeof(ngx_http_request_t));
+    if (r == NULL) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+    r->pool = pool;
+
+    r->http_connection = hc;
+    r->signature = NGX_HTTP_MODULE;
+    r->connection = c;
+
+    r->main_conf = hc->conf_ctx->main_conf;
+    r->srv_conf = hc->conf_ctx->srv_conf;
+    r->loc_conf = hc->conf_ctx->loc_conf;
+
+    r->read_event_handler = ngx_http_block_reading;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    ngx_set_connection_log(r->connection, clcf->error_log);
+
+    r->header_in = hc->busy ? hc->busy->buf : c->buffer;
+
+    if (ngx_list_init(&r->headers_out.headers, r->pool, 20,
+                      sizeof(ngx_table_elt_t))
+        != NGX_OK)
+    {
+        ngx_destroy_pool(r->pool);
+        return NULL;
+    }
+
+    if (ngx_list_init(&r->headers_out.trailers, r->pool, 4,
+                      sizeof(ngx_table_elt_t))
+        != NGX_OK)
+    {
+        ngx_destroy_pool(r->pool);
+        return NULL;
+    }
+
+    r->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module);
+    if (r->ctx == NULL) {
+        ngx_destroy_pool(r->pool);
+        return NULL;
+    }
+
+    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+    r->variables = ngx_pcalloc(r->pool, cmcf->variables.nelts
+                                        * sizeof(ngx_http_variable_value_t));
+    if (r->variables == NULL) {
+        ngx_destroy_pool(r->pool);
+        return NULL;
+    }
+
+#if (NGX_HTTP_SSL)
+    if (c->ssl) {
+        r->main_filter_need_in_memory = 1;
+    }
+#endif
+
+    r->main = r;
+    r->count = 1;
+
+    tp = ngx_timeofday();
+    r->start_sec = tp->sec;
+    r->start_msec = tp->msec;
+
+    r->method = NGX_HTTP_UNKNOWN;
+    r->http_version = NGX_HTTP_VERSION_10;
+
+    r->headers_in.content_length_n = -1;
+    r->headers_in.keep_alive_n = -1;
+    r->headers_out.content_length_n = -1;
+    r->headers_out.last_modified_time = -1;
+
+    r->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1;
+    r->subrequests = NGX_HTTP_MAX_SUBREQUESTS + 1;
+
+    r->http_state = NGX_HTTP_READING_REQUEST_STATE;
+
+    ctx = c->log->data;
+    ctx->request = r;
+    ctx->current_request = r;
+    r->log_handler = ngx_http_log_error_handler;
+
+#if (NGX_STAT_STUB)
+    (void) ngx_atomic_fetch_add(ngx_stat_reading, 1);
+    r->stat_reading = 1;
+    (void) ngx_atomic_fetch_add(ngx_stat_requests, 1);
+#endif
+
+    return r;
+}
+
+
+#if (NGX_HTTP_SSL)
+
+static void
+ngx_http_ssl_handshake(ngx_event_t *rev)
+{
+    u_char                    *p, buf[NGX_PROXY_PROTOCOL_MAX_HEADER + 1];
+    size_t                     size;
+    ssize_t                    n;
+    ngx_err_t                  err;
+    ngx_int_t                  rc;
+    ngx_connection_t          *c;
+    ngx_http_connection_t     *hc;
+    ngx_http_ssl_srv_conf_t   *sscf;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    c = rev->data;
+    hc = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                   "http check ssl handshake");
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    if (c->close) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    size = hc->proxy_protocol ? sizeof(buf) : 1;
+
+    n = ngxvcl_recv(c->fd, (char *) buf, size, MSG_PEEK);
+
+    err = ngx_socket_errno;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http recv(): %z", n);
+
+    if (n == -1) {
+        if (err == NGX_EAGAIN) {
+            rev->ready = 0;
+
+            if (!rev->timer_set) {
+                ngx_add_timer(rev, c->listening->post_accept_timeout);
+                ngx_reusable_connection(c, 1);
+            }
+
+            if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+                ngx_http_close_connection(c);
+            }
+
+            return;
+        }
+
+        ngx_connection_error(c, err, "recv() failed");
+        ngx_http_close_connection(c);
+
+        return;
+    }
+
+    if (hc->proxy_protocol) {
+        hc->proxy_protocol = 0;
+
+        p = ngx_proxy_protocol_read(c, buf, buf + n);
+
+        if (p == NULL) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        size = p - buf;
+
+        if (c->recv(c, buf, size) != (ssize_t) size) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        c->log->action = "SSL handshaking";
+
+        if (n == (ssize_t) size) {
+            ngx_post_event(rev, &ngx_posted_events);
+            return;
+        }
+
+        n = 1;
+        buf[0] = *p;
+    }
+
+    if (n == 1) {
+        if (buf[0] & 0x80 /* SSLv2 */ || buf[0] == 0x16 /* SSLv3/TLSv1 */) {
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                           "https ssl handshake: 0x%02Xd", buf[0]);
+
+            clcf = ngx_http_get_module_loc_conf(hc->conf_ctx,
+                                                ngx_http_core_module);
+
+            if (clcf->tcp_nodelay && ngx_tcp_nodelay(c) != NGX_OK) {
+                ngx_http_close_connection(c);
+                return;
+            }
+
+            sscf = ngx_http_get_module_srv_conf(hc->conf_ctx,
+                                                ngx_http_ssl_module);
+
+            if (ngx_ssl_create_connection(&sscf->ssl, c, NGX_SSL_BUFFER)
+                != NGX_OK)
+            {
+                ngx_http_close_connection(c);
+                return;
+            }
+
+            rc = ngx_ssl_handshake(c);
+
+            if (rc == NGX_AGAIN) {
+
+                if (!rev->timer_set) {
+                    ngx_add_timer(rev, c->listening->post_accept_timeout);
+                }
+
+                ngx_reusable_connection(c, 0);
+
+                c->ssl->handler = ngx_http_ssl_handshake_handler;
+                return;
+            }
+
+            ngx_http_ssl_handshake_handler(c);
+
+            return;
+        }
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "plain http");
+
+        c->log->action = "waiting for request";
+
+        rev->handler = ngx_http_wait_request_handler;
+        ngx_http_wait_request_handler(rev);
+
+        return;
+    }
+
+    ngx_log_error(NGX_LOG_INFO, c->log, 0, "client closed connection");
+    ngx_http_close_connection(c);
+}
+
+
+static void
+ngx_http_ssl_handshake_handler(ngx_connection_t *c)
+{
+    if (c->ssl->handshaked) {
+
+        /*
+         * The majority of browsers do not send the "close notify" alert.
+         * Among them are MSIE, old Mozilla, Netscape 4, Konqueror,
+         * and Links.  And what is more, MSIE ignores the server's alert.
+         *
+         * Opera and recent Mozilla send the alert.
+         */
+
+        c->ssl->no_wait_shutdown = 1;
+
+#if (NGX_HTTP_V2                                                              \
+     && (defined TLSEXT_TYPE_application_layer_protocol_negotiation           \
+         || defined TLSEXT_TYPE_next_proto_neg))
+        {
+        unsigned int            len;
+        const unsigned char    *data;
+        ngx_http_connection_t  *hc;
+
+        hc = c->data;
+
+        if (hc->addr_conf->http2) {
+
+#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
+            SSL_get0_alpn_selected(c->ssl->connection, &data, &len);
+
+#ifdef TLSEXT_TYPE_next_proto_neg
+            if (len == 0) {
+                SSL_get0_next_proto_negotiated(c->ssl->connection, &data, &len);
+            }
+#endif
+
+#else /* TLSEXT_TYPE_next_proto_neg */
+            SSL_get0_next_proto_negotiated(c->ssl->connection, &data, &len);
+#endif
+
+            if (len == 2 && data[0] == 'h' && data[1] == '2') {
+                ngx_http_v2_init(c->read);
+                return;
+            }
+        }
+        }
+#endif
+
+        c->log->action = "waiting for request";
+
+        c->read->handler = ngx_http_wait_request_handler;
+        /* STUB: epoll edge */ c->write->handler = ngx_http_empty_handler;
+
+        ngx_reusable_connection(c, 1);
+
+        ngx_http_wait_request_handler(c->read);
+
+        return;
+    }
+
+    if (c->read->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+    }
+
+    ngx_http_close_connection(c);
+}
+
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+
+int
+ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg)
+{
+    ngx_str_t                  host;
+    const char                *servername;
+    ngx_connection_t          *c;
+    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;
+
+    servername = SSL_get_servername(ssl_conn, TLSEXT_NAMETYPE_host_name);
+
+    if (servername == NULL) {
+        return SSL_TLSEXT_ERR_NOACK;
+    }
+
+    c = ngx_ssl_get_connection(ssl_conn);
+
+    if (c->ssl->renegotiation) {
+        return SSL_TLSEXT_ERR_NOACK;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "SSL server name: \"%s\"", servername);
+
+    host.len = ngx_strlen(servername);
+
+    if (host.len == 0) {
+        return SSL_TLSEXT_ERR_NOACK;
+    }
+
+    host.data = (u_char *) servername;
+
+    if (ngx_http_validate_host(&host, c->pool, 1) != NGX_OK) {
+        return SSL_TLSEXT_ERR_NOACK;
+    }
+
+    hc = c->data;
+
+    if (ngx_http_find_virtual_server(c, hc->addr_conf->virtual_names, &host,
+                                     NULL, &cscf)
+        != NGX_OK)
+    {
+        return SSL_TLSEXT_ERR_NOACK;
+    }
+
+    hc->ssl_servername = ngx_palloc(c->pool, sizeof(ngx_str_t));
+    if (hc->ssl_servername == NULL) {
+        return SSL_TLSEXT_ERR_NOACK;
+    }
+
+    *hc->ssl_servername = host;
+
+    hc->conf_ctx = cscf->ctx;
+
+    clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module);
+
+    ngx_set_connection_log(c, clcf->error_log);
+
+    sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module);
+
+    c->ssl->buffer_size = sscf->buffer_size;
+
+    if (sscf->ssl.ctx) {
+        SSL_set_SSL_CTX(ssl_conn, sscf->ssl.ctx);
+
+        /*
+         * SSL_set_SSL_CTX() only changes certs as of 1.0.0d
+         * adjust other things we care about
+         */
+
+        SSL_set_verify(ssl_conn, SSL_CTX_get_verify_mode(sscf->ssl.ctx),
+                       SSL_CTX_get_verify_callback(sscf->ssl.ctx));
+
+        SSL_set_verify_depth(ssl_conn, SSL_CTX_get_verify_depth(sscf->ssl.ctx));
+
+#if OPENSSL_VERSION_NUMBER >= 0x009080dfL
+        /* only in 0.9.8m+ */
+        SSL_clear_options(ssl_conn, SSL_get_options(ssl_conn) &
+                                    ~SSL_CTX_get_options(sscf->ssl.ctx));
+#endif
+
+        SSL_set_options(ssl_conn, SSL_CTX_get_options(sscf->ssl.ctx));
+    }
+
+    return SSL_TLSEXT_ERR_OK;
+}
+
+#endif
+
+#endif
+
+
+static void
+ngx_http_process_request_line(ngx_event_t *rev)
+{
+    ssize_t              n;
+    ngx_int_t            rc, rv;
+    ngx_str_t            host;
+    ngx_connection_t    *c;
+    ngx_http_request_t  *r;
+
+    c = rev->data;
+    r = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                   "http process request line");
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+        c->timedout = 1;
+        ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
+        return;
+    }
+
+    rc = NGX_AGAIN;
+
+    for ( ;; ) {
+
+        if (rc == NGX_AGAIN) {
+            n = ngx_http_read_request_header(r);
+
+            if (n == NGX_AGAIN || n == NGX_ERROR) {
+                return;
+            }
+        }
+
+        rc = ngx_http_parse_request_line(r, r->header_in);
+
+        if (rc == NGX_OK) {
+
+            /* the request line has been parsed successfully */
+
+            r->request_line.len = r->request_end - r->request_start;
+            r->request_line.data = r->request_start;
+            r->request_length = r->header_in->pos - r->request_start;
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                           "http request line: \"%V\"", &r->request_line);
+
+            r->method_name.len = r->method_end - r->request_start + 1;
+            r->method_name.data = r->request_line.data;
+
+            if (r->http_protocol.data) {
+                r->http_protocol.len = r->request_end - r->http_protocol.data;
+            }
+
+            if (ngx_http_process_request_uri(r) != NGX_OK) {
+                return;
+            }
+
+            if (r->host_start && r->host_end) {
+
+                host.len = r->host_end - r->host_start;
+                host.data = r->host_start;
+
+                rc = ngx_http_validate_host(&host, r->pool, 0);
+
+                if (rc == NGX_DECLINED) {
+                    ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                                  "client sent invalid host in request line");
+                    ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+                    return;
+                }
+
+                if (rc == NGX_ERROR) {
+                    ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+                    return;
+                }
+
+                if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) {
+                    return;
+                }
+
+                r->headers_in.server = host;
+            }
+
+            if (r->http_version < NGX_HTTP_VERSION_10) {
+
+                if (r->headers_in.server.len == 0
+                    && ngx_http_set_virtual_server(r, &r->headers_in.server)
+                       == NGX_ERROR)
+                {
+                    return;
+                }
+
+                ngx_http_process_request(r);
+                return;
+            }
+
+
+            if (ngx_list_init(&r->headers_in.headers, r->pool, 20,
+                              sizeof(ngx_table_elt_t))
+                != NGX_OK)
+            {
+                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+                return;
+            }
+
+            c->log->action = "reading client request headers";
+
+            rev->handler = ngx_http_process_request_headers;
+            ngx_http_process_request_headers(rev);
+
+            return;
+        }
+
+        if (rc != NGX_AGAIN) {
+
+            /* there was error while a request line parsing */
+
+            ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                          ngx_http_client_errors[rc - NGX_HTTP_CLIENT_ERROR]);
+
+            if (rc == NGX_HTTP_PARSE_INVALID_VERSION) {
+                ngx_http_finalize_request(r, NGX_HTTP_VERSION_NOT_SUPPORTED);
+
+            } else {
+                ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+            }
+
+            return;
+        }
+
+        /* NGX_AGAIN: a request line parsing is still incomplete */
+
+        if (r->header_in->pos == r->header_in->end) {
+
+            rv = ngx_http_alloc_large_header_buffer(r, 1);
+
+            if (rv == NGX_ERROR) {
+                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+                return;
+            }
+
+            if (rv == NGX_DECLINED) {
+                r->request_line.len = r->header_in->end - r->request_start;
+                r->request_line.data = r->request_start;
+
+                ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                              "client sent too long URI");
+                ngx_http_finalize_request(r, NGX_HTTP_REQUEST_URI_TOO_LARGE);
+                return;
+            }
+        }
+    }
+}
+
+
+ngx_int_t
+ngx_http_process_request_uri(ngx_http_request_t *r)
+{
+    ngx_http_core_srv_conf_t  *cscf;
+
+    if (r->args_start) {
+        r->uri.len = r->args_start - 1 - r->uri_start;
+    } else {
+        r->uri.len = r->uri_end - r->uri_start;
+    }
+
+    if (r->complex_uri || r->quoted_uri) {
+
+        r->uri.data = ngx_pnalloc(r->pool, r->uri.len + 1);
+        if (r->uri.data == NULL) {
+            ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return NGX_ERROR;
+        }
+
+        cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+        if (ngx_http_parse_complex_uri(r, cscf->merge_slashes) != NGX_OK) {
+            r->uri.len = 0;
+
+            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                          "client sent invalid request");
+            ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+            return NGX_ERROR;
+        }
+
+    } else {
+        r->uri.data = r->uri_start;
+    }
+
+    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;
+
+    if (r->uri_ext) {
+        if (r->args_start) {
+            r->exten.len = r->args_start - 1 - r->uri_ext;
+        } else {
+            r->exten.len = r->uri_end - r->uri_ext;
+        }
+
+        r->exten.data = r->uri_ext;
+    }
+
+    if (r->args_start && r->uri_end > r->args_start) {
+        r->args.len = r->uri_end - r->args_start;
+        r->args.data = r->args_start;
+    }
+
+#if (NGX_WIN32)
+    {
+    u_char  *p, *last;
+
+    p = r->uri.data;
+    last = r->uri.data + r->uri.len;
+
+    while (p < last) {
+
+        if (*p++ == ':') {
+
+            /*
+             * this check covers "::$data", "::$index_allocation" and
+             * ":$i30:$index_allocation"
+             */
+
+            if (p < last && *p == '$') {
+                ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                              "client sent unsafe win32 URI");
+                ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+                return NGX_ERROR;
+            }
+        }
+    }
+
+    p = r->uri.data + r->uri.len - 1;
+
+    while (p > r->uri.data) {
+
+        if (*p == ' ') {
+            p--;
+            continue;
+        }
+
+        if (*p == '.') {
+            p--;
+            continue;
+        }
+
+        break;
+    }
+
+    if (p != r->uri.data + r->uri.len - 1) {
+        r->uri.len = p + 1 - r->uri.data;
+        ngx_http_set_exten(r);
+    }
+
+    }
+#endif
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http uri: \"%V\"", &r->uri);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http args: \"%V\"", &r->args);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http exten: \"%V\"", &r->exten);
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_process_request_headers(ngx_event_t *rev)
+{
+    u_char                     *p;
+    size_t                      len;
+    ssize_t                     n;
+    ngx_int_t                   rc, rv;
+    ngx_table_elt_t            *h;
+    ngx_connection_t           *c;
+    ngx_http_header_t          *hh;
+    ngx_http_request_t         *r;
+    ngx_http_core_srv_conf_t   *cscf;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    c = rev->data;
+    r = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                   "http process request header line");
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+        c->timedout = 1;
+        ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
+        return;
+    }
+
+    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+    rc = NGX_AGAIN;
+
+    for ( ;; ) {
+
+        if (rc == NGX_AGAIN) {
+
+            if (r->header_in->pos == r->header_in->end) {
+
+                rv = ngx_http_alloc_large_header_buffer(r, 0);
+
+                if (rv == NGX_ERROR) {
+                    ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+                    return;
+                }
+
+                if (rv == NGX_DECLINED) {
+                    p = r->header_name_start;
+
+                    r->lingering_close = 1;
+
+                    if (p == NULL) {
+                        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                                      "client sent too large request");
+                        ngx_http_finalize_request(r,
+                                            NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
+                        return;
+                    }
+
+                    len = r->header_in->end - p;
+
+                    if (len > NGX_MAX_ERROR_STR - 300) {
+                        len = NGX_MAX_ERROR_STR - 300;
+                    }
+
+                    ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                                "client sent too long header line: \"%*s...\"",
+                                len, r->header_name_start);
+
+                    ngx_http_finalize_request(r,
+                                            NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
+                    return;
+                }
+            }
+
+            n = ngx_http_read_request_header(r);
+
+            if (n == NGX_AGAIN || n == NGX_ERROR) {
+                return;
+            }
+        }
+
+        /* the host header could change the server configuration context */
+        cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+        rc = ngx_http_parse_header_line(r, r->header_in,
+                                        cscf->underscores_in_headers);
+
+        if (rc == NGX_OK) {
+
+            r->request_length += r->header_in->pos - r->header_name_start;
+
+            if (r->invalid_header && cscf->ignore_invalid_headers) {
+
+                /* there was error while a header line parsing */
+
+                ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                              "client sent invalid header line: \"%*s\"",
+                              r->header_end - r->header_name_start,
+                              r->header_name_start);
+                continue;
+            }
+
+            /* a header line has been parsed successfully */
+
+            h = ngx_list_push(&r->headers_in.headers);
+            if (h == NULL) {
+                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+                return;
+            }
+
+            h->hash = r->header_hash;
+
+            h->key.len = r->header_name_end - r->header_name_start;
+            h->key.data = r->header_name_start;
+            h->key.data[h->key.len] = '\0';
+
+            h->value.len = r->header_end - r->header_start;
+            h->value.data = r->header_start;
+            h->value.data[h->value.len] = '\0';
+
+            h->lowcase_key = ngx_pnalloc(r->pool, h->key.len);
+            if (h->lowcase_key == NULL) {
+                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+                return;
+            }
+
+            if (h->key.len == r->lowcase_index) {
+                ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);
+
+            } else {
+                ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
+            }
+
+            hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
+                               h->lowcase_key, h->key.len);
+
+            if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
+                return;
+            }
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http header: \"%V: %V\"",
+                           &h->key, &h->value);
+
+            continue;
+        }
+
+        if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
+
+            /* a whole header has been parsed successfully */
+
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http header done");
+
+            r->request_length += r->header_in->pos - r->header_name_start;
+
+            r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;
+
+            rc = ngx_http_process_request_header(r);
+
+            if (rc != NGX_OK) {
+                return;
+            }
+
+            ngx_http_process_request(r);
+
+            return;
+        }
+
+        if (rc == NGX_AGAIN) {
+
+            /* a header line parsing is still not complete */
+
+            continue;
+        }
+
+        /* rc == NGX_HTTP_PARSE_INVALID_HEADER */
+
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "client sent invalid header line");
+
+        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+        return;
+    }
+}
+
+
+static ssize_t
+ngx_http_read_request_header(ngx_http_request_t *r)
+{
+    ssize_t                    n;
+    ngx_event_t               *rev;
+    ngx_connection_t          *c;
+    ngx_http_core_srv_conf_t  *cscf;
+
+    c = r->connection;
+    rev = c->read;
+
+    n = r->header_in->last - r->header_in->pos;
+
+    if (n > 0) {
+        return n;
+    }
+
+    if (rev->ready) {
+        n = c->recv(c, r->header_in->last,
+                    r->header_in->end - r->header_in->last);
+    } else {
+        n = NGX_AGAIN;
+    }
+
+    if (n == NGX_AGAIN) {
+        if (!rev->timer_set) {
+            cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+            ngx_add_timer(rev, cscf->client_header_timeout);
+        }
+
+        if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+            ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return NGX_ERROR;
+        }
+
+        return NGX_AGAIN;
+    }
+
+    if (n == 0) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "client prematurely closed connection");
+    }
+
+    if (n == 0 || n == NGX_ERROR) {
+        c->error = 1;
+        c->log->action = "reading client request headers";
+
+        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+        return NGX_ERROR;
+    }
+
+    r->header_in->last += n;
+
+    return n;
+}
+
+
+static ngx_int_t
+ngx_http_alloc_large_header_buffer(ngx_http_request_t *r,
+    ngx_uint_t request_line)
+{
+    u_char                    *old, *new;
+    ngx_buf_t                 *b;
+    ngx_chain_t               *cl;
+    ngx_http_connection_t     *hc;
+    ngx_http_core_srv_conf_t  *cscf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http alloc large header buffer");
+
+    if (request_line && r->state == 0) {
+
+        /* the client fills up the buffer with "\r\n" */
+
+        r->header_in->pos = r->header_in->start;
+        r->header_in->last = r->header_in->start;
+
+        return NGX_OK;
+    }
+
+    old = request_line ? r->request_start : r->header_name_start;
+
+    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+    if (r->state != 0
+        && (size_t) (r->header_in->pos - old)
+                                     >= cscf->large_client_header_buffers.size)
+    {
+        return NGX_DECLINED;
+    }
+
+    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 if (hc->nbusy < cscf->large_client_header_buffers.num) {
+
+        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);
+
+    } else {
+        return NGX_DECLINED;
+    }
+
+    cl->next = hc->busy;
+    hc->busy = cl;
+    hc->nbusy++;
+
+    if (r->state == 0) {
+        /*
+         * r->state == 0 means that a header line was parsed successfully
+         * and we do not need to copy incomplete header line and
+         * to relocate the parser header pointers
+         */
+
+        r->header_in = b;
+
+        return NGX_OK;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http large header copy: %uz", r->header_in->pos - old);
+
+    new = b->start;
+
+    ngx_memcpy(new, old, r->header_in->pos - old);
+
+    b->pos = new + (r->header_in->pos - old);
+    b->last = new + (r->header_in->pos - old);
+
+    if (request_line) {
+        r->request_start = new;
+
+        if (r->request_end) {
+            r->request_end = new + (r->request_end - old);
+        }
+
+        r->method_end = new + (r->method_end - old);
+
+        r->uri_start = new + (r->uri_start - old);
+        r->uri_end = new + (r->uri_end - old);
+
+        if (r->schema_start) {
+            r->schema_start = new + (r->schema_start - old);
+            r->schema_end = new + (r->schema_end - old);
+        }
+
+        if (r->host_start) {
+            r->host_start = new + (r->host_start - old);
+            if (r->host_end) {
+                r->host_end = new + (r->host_end - old);
+            }
+        }
+
+        if (r->port_start) {
+            r->port_start = new + (r->port_start - old);
+            r->port_end = new + (r->port_end - old);
+        }
+
+        if (r->uri_ext) {
+            r->uri_ext = new + (r->uri_ext - old);
+        }
+
+        if (r->args_start) {
+            r->args_start = new + (r->args_start - old);
+        }
+
+        if (r->http_protocol.data) {
+            r->http_protocol.data = new + (r->http_protocol.data - old);
+        }
+
+    } else {
+        r->header_name_start = new;
+        r->header_name_end = new + (r->header_name_end - old);
+        r->header_start = new + (r->header_start - old);
+        r->header_end = new + (r->header_end - old);
+    }
+
+    r->header_in = b;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_process_header_line(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    ngx_table_elt_t  **ph;
+
+    ph = (ngx_table_elt_t **) ((char *) &r->headers_in + offset);
+
+    if (*ph == NULL) {
+        *ph = h;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_process_unique_header_line(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    ngx_table_elt_t  **ph;
+
+    ph = (ngx_table_elt_t **) ((char *) &r->headers_in + offset);
+
+    if (*ph == NULL) {
+        *ph = h;
+        return NGX_OK;
+    }
+
+    ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                  "client sent duplicate header line: \"%V: %V\", "
+                  "previous value: \"%V: %V\"",
+                  &h->key, &h->value, &(*ph)->key, &(*ph)->value);
+
+    ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_process_host(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    ngx_int_t  rc;
+    ngx_str_t  host;
+
+    if (r->headers_in.host == NULL) {
+        r->headers_in.host = h;
+    }
+
+    host = h->value;
+
+    rc = ngx_http_validate_host(&host, r->pool, 0);
+
+    if (rc == NGX_DECLINED) {
+        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                      "client sent invalid host header");
+        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+        return NGX_ERROR;
+    }
+
+    if (rc == NGX_ERROR) {
+        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NGX_ERROR;
+    }
+
+    if (r->headers_in.server.len) {
+        return NGX_OK;
+    }
+
+    if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    r->headers_in.server = host;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_process_connection(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    if (ngx_strcasestrn(h->value.data, "close", 5 - 1)) {
+        r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE;
+
+    } else if (ngx_strcasestrn(h->value.data, "keep-alive", 10 - 1)) {
+        r->headers_in.connection_type = NGX_HTTP_CONNECTION_KEEP_ALIVE;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_process_user_agent(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    u_char  *user_agent, *msie;
+
+    if (r->headers_in.user_agent) {
+        return NGX_OK;
+    }
+
+    r->headers_in.user_agent = h;
+
+    /* check some widespread browsers while the header is in CPU cache */
+
+    user_agent = h->value.data;
+
+    msie = ngx_strstrn(user_agent, "MSIE ", 5 - 1);
+
+    if (msie && msie + 7 < user_agent + h->value.len) {
+
+        r->headers_in.msie = 1;
+
+        if (msie[6] == '.') {
+
+            switch (msie[5]) {
+            case '4':
+            case '5':
+                r->headers_in.msie6 = 1;
+                break;
+            case '6':
+                if (ngx_strstrn(msie + 8, "SV1", 3 - 1) == NULL) {
+                    r->headers_in.msie6 = 1;
+                }
+                break;
+            }
+        }
+
+#if 0
+        /* MSIE ignores the SSL "close notify" alert */
+        if (c->ssl) {
+            c->ssl->no_send_shutdown = 1;
+        }
+#endif
+    }
+
+    if (ngx_strstrn(user_agent, "Opera", 5 - 1)) {
+        r->headers_in.opera = 1;
+        r->headers_in.msie = 0;
+        r->headers_in.msie6 = 0;
+    }
+
+    if (!r->headers_in.msie && !r->headers_in.opera) {
+
+        if (ngx_strstrn(user_agent, "Gecko/", 6 - 1)) {
+            r->headers_in.gecko = 1;
+
+        } else if (ngx_strstrn(user_agent, "Chrome/", 7 - 1)) {
+            r->headers_in.chrome = 1;
+
+        } else if (ngx_strstrn(user_agent, "Safari/", 7 - 1)
+                   && ngx_strstrn(user_agent, "Mac OS X", 8 - 1))
+        {
+            r->headers_in.safari = 1;
+
+        } else if (ngx_strstrn(user_agent, "Konqueror", 9 - 1)) {
+            r->headers_in.konqueror = 1;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_process_multi_header_lines(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    ngx_array_t       *headers;
+    ngx_table_elt_t  **ph;
+
+    headers = (ngx_array_t *) ((char *) &r->headers_in + offset);
+
+    if (headers->elts == NULL) {
+        if (ngx_array_init(headers, r->pool, 1, sizeof(ngx_table_elt_t *))
+            != NGX_OK)
+        {
+            ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return NGX_ERROR;
+        }
+    }
+
+    ph = ngx_array_push(headers);
+    if (ph == NULL) {
+        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NGX_ERROR;
+    }
+
+    *ph = h;
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_process_request_header(ngx_http_request_t *r)
+{
+    if (r->headers_in.server.len == 0
+        && ngx_http_set_virtual_server(r, &r->headers_in.server)
+           == NGX_ERROR)
+    {
+        return NGX_ERROR;
+    }
+
+    if (r->headers_in.host == NULL && r->http_version > NGX_HTTP_VERSION_10) {
+        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                   "client sent HTTP/1.1 request without \"Host\" header");
+        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+        return NGX_ERROR;
+    }
+
+    if (r->headers_in.content_length) {
+        r->headers_in.content_length_n =
+                            ngx_atoof(r->headers_in.content_length->value.data,
+                                      r->headers_in.content_length->value.len);
+
+        if (r->headers_in.content_length_n == NGX_ERROR) {
+            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                          "client sent invalid \"Content-Length\" header");
+            ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+            return NGX_ERROR;
+        }
+    }
+
+    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->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;
+            r->headers_in.chunked = 1;
+
+        } else if (r->headers_in.transfer_encoding->value.len != 8
+            || ngx_strncasecmp(r->headers_in.transfer_encoding->value.data,
+                               (u_char *) "identity", 8) != 0)
+        {
+            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                          "client sent unknown \"Transfer-Encoding\": \"%V\"",
+                          &r->headers_in.transfer_encoding->value);
+            ngx_http_finalize_request(r, NGX_HTTP_NOT_IMPLEMENTED);
+            return NGX_ERROR;
+        }
+    }
+
+    if (r->headers_in.connection_type == NGX_HTTP_CONNECTION_KEEP_ALIVE) {
+        if (r->headers_in.keep_alive) {
+            r->headers_in.keep_alive_n =
+                            ngx_atotm(r->headers_in.keep_alive->value.data,
+                                      r->headers_in.keep_alive->value.len);
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+void
+ngx_http_process_request(ngx_http_request_t *r)
+{
+    ngx_connection_t  *c;
+
+    c = r->connection;
+
+#if (NGX_HTTP_SSL)
+
+    if (r->http_connection->ssl) {
+        long                      rc;
+        X509                     *cert;
+        ngx_http_ssl_srv_conf_t  *sscf;
+
+        if (c->ssl == NULL) {
+            ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                          "client sent plain HTTP request to HTTPS port");
+            ngx_http_finalize_request(r, NGX_HTTP_TO_HTTPS);
+            return;
+        }
+
+        sscf = ngx_http_get_module_srv_conf(r, ngx_http_ssl_module);
+
+        if (sscf->verify) {
+            rc = SSL_get_verify_result(c->ssl->connection);
+
+            if (rc != X509_V_OK
+                && (sscf->verify != 3 || !ngx_ssl_verify_error_optional(rc)))
+            {
+                ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                              "client SSL certificate verify error: (%l:%s)",
+                              rc, X509_verify_cert_error_string(rc));
+
+                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;
+            }
+
+            if (sscf->verify == 1) {
+                cert = SSL_get_peer_certificate(c->ssl->connection);
+
+                if (cert == NULL) {
+                    ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                                  "client sent no required SSL certificate");
+
+                    ngx_ssl_remove_cached_session(c->ssl->session_ctx,
+                                       (SSL_get0_session(c->ssl->connection)));
+
+                    ngx_http_finalize_request(r, NGX_HTTPS_NO_CERT);
+                    return;
+                }
+
+                X509_free(cert);
+            }
+        }
+    }
+
+#endif
+
+    if (c->read->timer_set) {
+        ngx_del_timer(c->read);
+    }
+
+#if (NGX_STAT_STUB)
+    (void) ngx_atomic_fetch_add(ngx_stat_reading, -1);
+    r->stat_reading = 0;
+    (void) ngx_atomic_fetch_add(ngx_stat_writing, 1);
+    r->stat_writing = 1;
+#endif
+
+    c->read->handler = ngx_http_request_handler;
+    c->write->handler = ngx_http_request_handler;
+    r->read_event_handler = ngx_http_block_reading;
+
+    ngx_http_handler(r);
+
+    ngx_http_run_posted_requests(c);
+}
+
+
+static ngx_int_t
+ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc)
+{
+    u_char  *h, ch;
+    size_t   i, dot_pos, host_len;
+
+    enum {
+        sw_usual = 0,
+        sw_literal,
+        sw_rest
+    } state;
+
+    dot_pos = host->len;
+    host_len = host->len;
+
+    h = host->data;
+
+    state = sw_usual;
+
+    for (i = 0; i < host->len; i++) {
+        ch = h[i];
+
+        switch (ch) {
+
+        case '.':
+            if (dot_pos == i - 1) {
+                return NGX_DECLINED;
+            }
+            dot_pos = i;
+            break;
+
+        case ':':
+            if (state == sw_usual) {
+                host_len = i;
+                state = sw_rest;
+            }
+            break;
+
+        case '[':
+            if (i == 0) {
+                state = sw_literal;
+            }
+            break;
+
+        case ']':
+            if (state == sw_literal) {
+                host_len = i + 1;
+                state = sw_rest;
+            }
+            break;
+
+        case '\0':
+            return NGX_DECLINED;
+
+        default:
+
+            if (ngx_path_separator(ch)) {
+                return NGX_DECLINED;
+            }
+
+            if (ch >= 'A' && ch <= 'Z') {
+                alloc = 1;
+            }
+
+            break;
+        }
+    }
+
+    if (dot_pos == host_len - 1) {
+        host_len--;
+    }
+
+    if (host_len == 0) {
+        return NGX_DECLINED;
+    }
+
+    if (alloc) {
+        host->data = ngx_pnalloc(pool, host_len);
+        if (host->data == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_strlow(host->data, h, host_len);
+    }
+
+    host->len = host_len;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_set_virtual_server(ngx_http_request_t *r, ngx_str_t *host)
+{
+    ngx_int_t                  rc;
+    ngx_http_connection_t     *hc;
+    ngx_http_core_loc_conf_t  *clcf;
+    ngx_http_core_srv_conf_t  *cscf;
+
+#if (NGX_SUPPRESS_WARN)
+    cscf = NULL;
+#endif
+
+    hc = r->http_connection;
+
+#if (NGX_HTTP_SSL && defined SSL_CTRL_SET_TLSEXT_HOSTNAME)
+
+    if (hc->ssl_servername) {
+        if (hc->ssl_servername->len == host->len
+            && ngx_strncmp(hc->ssl_servername->data,
+                           host->data, host->len) == 0)
+        {
+#if (NGX_PCRE)
+            if (hc->ssl_servername_regex
+                && ngx_http_regex_exec(r, hc->ssl_servername_regex,
+                                          hc->ssl_servername) != NGX_OK)
+            {
+                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+                return NGX_ERROR;
+            }
+#endif
+            return NGX_OK;
+        }
+    }
+
+#endif
+
+    rc = ngx_http_find_virtual_server(r->connection,
+                                      hc->addr_conf->virtual_names,
+                                      host, r, &cscf);
+
+    if (rc == NGX_ERROR) {
+        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NGX_ERROR;
+    }
+
+#if (NGX_HTTP_SSL && defined SSL_CTRL_SET_TLSEXT_HOSTNAME)
+
+    if (hc->ssl_servername) {
+        ngx_http_ssl_srv_conf_t  *sscf;
+
+        if (rc == NGX_DECLINED) {
+            cscf = hc->addr_conf->default_server;
+            rc = NGX_OK;
+        }
+
+        sscf = ngx_http_get_module_srv_conf(cscf->ctx, ngx_http_ssl_module);
+
+        if (sscf->verify) {
+            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                          "client attempted to request the server name "
+                          "different from the one that was negotiated");
+            ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST);
+            return NGX_ERROR;
+        }
+    }
+
+#endif
+
+    if (rc == NGX_DECLINED) {
+        return NGX_OK;
+    }
+
+    r->srv_conf = cscf->ctx->srv_conf;
+    r->loc_conf = cscf->ctx->loc_conf;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    ngx_set_connection_log(r->connection, clcf->error_log);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_find_virtual_server(ngx_connection_t *c,
+    ngx_http_virtual_names_t *virtual_names, ngx_str_t *host,
+    ngx_http_request_t *r, ngx_http_core_srv_conf_t **cscfp)
+{
+    ngx_http_core_srv_conf_t  *cscf;
+
+    if (virtual_names == NULL) {
+        return NGX_DECLINED;
+    }
+
+    cscf = ngx_hash_find_combined(&virtual_names->names,
+                                  ngx_hash_key(host->data, host->len),
+                                  host->data, host->len);
+
+    if (cscf) {
+        *cscfp = cscf;
+        return NGX_OK;
+    }
+
+#if (NGX_PCRE)
+
+    if (host->len && virtual_names->nregex) {
+        ngx_int_t                n;
+        ngx_uint_t               i;
+        ngx_http_server_name_t  *sn;
+
+        sn = virtual_names->regex;
+
+#if (NGX_HTTP_SSL && defined SSL_CTRL_SET_TLSEXT_HOSTNAME)
+
+        if (r == NULL) {
+            ngx_http_connection_t  *hc;
+
+            for (i = 0; i < virtual_names->nregex; i++) {
+
+                n = ngx_regex_exec(sn[i].regex->regex, host, NULL, 0);
+
+                if (n == NGX_REGEX_NO_MATCHED) {
+                    continue;
+                }
+
+                if (n >= 0) {
+                    hc = c->data;
+                    hc->ssl_servername_regex = sn[i].regex;
+
+                    *cscfp = sn[i].server;
+                    return NGX_OK;
+                }
+
+                ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                              ngx_regex_exec_n " failed: %i "
+                              "on \"%V\" using \"%V\"",
+                              n, host, &sn[i].regex->name);
+
+                return NGX_ERROR;
+            }
+
+            return NGX_DECLINED;
+        }
+
+#endif /* NGX_HTTP_SSL && defined SSL_CTRL_SET_TLSEXT_HOSTNAME */
+
+        for (i = 0; i < virtual_names->nregex; i++) {
+
+            n = ngx_http_regex_exec(r, sn[i].regex, host);
+
+            if (n == NGX_DECLINED) {
+                continue;
+            }
+
+            if (n == NGX_OK) {
+                *cscfp = sn[i].server;
+                return NGX_OK;
+            }
+
+            return NGX_ERROR;
+        }
+    }
+
+#endif /* NGX_PCRE */
+
+    return NGX_DECLINED;
+}
+
+
+static void
+ngx_http_request_handler(ngx_event_t *ev)
+{
+    ngx_connection_t    *c;
+    ngx_http_request_t  *r;
+
+    c = ev->data;
+    r = c->data;
+
+    ngx_http_set_log_request(c->log, r);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http run request: \"%V?%V\"", &r->uri, &r->args);
+
+    if (c->close) {
+        r->main->count++;
+        ngx_http_terminate_request(r, 0);
+        ngx_http_run_posted_requests(c);
+        return;
+    }
+
+    if (ev->delayed && ev->timedout) {
+        ev->delayed = 0;
+        ev->timedout = 0;
+    }
+
+    if (ev->write) {
+        r->write_event_handler(r);
+
+    } else {
+        r->read_event_handler(r);
+    }
+
+    ngx_http_run_posted_requests(c);
+}
+
+
+void
+ngx_http_run_posted_requests(ngx_connection_t *c)
+{
+    ngx_http_request_t         *r;
+    ngx_http_posted_request_t  *pr;
+
+    for ( ;; ) {
+
+        if (c->destroyed) {
+            return;
+        }
+
+        r = c->data;
+        pr = r->main->posted_requests;
+
+        if (pr == NULL) {
+            return;
+        }
+
+        r->main->posted_requests = pr->next;
+
+        r = pr->request;
+
+        ngx_http_set_log_request(c->log, r);
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http posted request: \"%V?%V\"", &r->uri, &r->args);
+
+        r->write_event_handler(r);
+    }
+}
+
+
+ngx_int_t
+ngx_http_post_request(ngx_http_request_t *r, ngx_http_posted_request_t *pr)
+{
+    ngx_http_posted_request_t  **p;
+
+    if (pr == NULL) {
+        pr = ngx_palloc(r->pool, sizeof(ngx_http_posted_request_t));
+        if (pr == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    pr->request = r;
+    pr->next = NULL;
+
+    for (p = &r->main->posted_requests; *p; p = &(*p)->next) { /* void */ }
+
+    *p = pr;
+
+    return NGX_OK;
+}
+
+
+void
+ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+    ngx_connection_t          *c;
+    ngx_http_request_t        *pr;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    c = r->connection;
+
+    ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http finalize request: %i, \"%V?%V\" a:%d, c:%d",
+                   rc, &r->uri, &r->args, r == c->data, r->main->count);
+
+    if (rc == NGX_DONE) {
+        ngx_http_finalize_connection(r);
+        return;
+    }
+
+    if (rc == NGX_OK && r->filter_finalize) {
+        c->error = 1;
+    }
+
+    if (rc == NGX_DECLINED) {
+        r->content_handler = NULL;
+        r->write_event_handler = ngx_http_core_run_phases;
+        ngx_http_core_run_phases(r);
+        return;
+    }
+
+    if (r != r->main && r->post_subrequest) {
+        rc = r->post_subrequest->handler(r, r->post_subrequest->data, rc);
+    }
+
+    if (rc == NGX_ERROR
+        || rc == NGX_HTTP_REQUEST_TIME_OUT
+        || rc == NGX_HTTP_CLIENT_CLOSED_REQUEST
+        || c->error)
+    {
+        if (ngx_http_post_action(r) == NGX_OK) {
+            return;
+        }
+
+        ngx_http_terminate_request(r, rc);
+        return;
+    }
+
+    if (rc >= NGX_HTTP_SPECIAL_RESPONSE
+        || rc == NGX_HTTP_CREATED
+        || rc == NGX_HTTP_NO_CONTENT)
+    {
+        if (rc == NGX_HTTP_CLOSE) {
+            ngx_http_terminate_request(r, rc);
+            return;
+        }
+
+        if (r == r->main) {
+            if (c->read->timer_set) {
+                ngx_del_timer(c->read);
+            }
+
+            if (c->write->timer_set) {
+                ngx_del_timer(c->write);
+            }
+        }
+
+        c->read->handler = ngx_http_request_handler;
+        c->write->handler = ngx_http_request_handler;
+
+        ngx_http_finalize_request(r, ngx_http_special_response_handler(r, rc));
+        return;
+    }
+
+    if (r != r->main) {
+        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+        if (r->background) {
+            if (!r->logged) {
+                if (clcf->log_subrequest) {
+                    ngx_http_log_request(r);
+                }
+
+                r->logged = 1;
+
+            } else {
+                ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                              "subrequest: \"%V?%V\" logged again",
+                              &r->uri, &r->args);
+            }
+
+            r->done = 1;
+            ngx_http_finalize_connection(r);
+            return;
+        }
+
+        if (r->buffered || r->postponed) {
+
+            if (ngx_http_set_write_handler(r) != NGX_OK) {
+                ngx_http_terminate_request(r, 0);
+            }
+
+            return;
+        }
+
+        pr = r->parent;
+
+        if (r == c->data) {
+
+            r->main->count--;
+
+            if (!r->logged) {
+                if (clcf->log_subrequest) {
+                    ngx_http_log_request(r);
+                }
+
+                r->logged = 1;
+
+            } else {
+                ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                              "subrequest: \"%V?%V\" logged again",
+                              &r->uri, &r->args);
+            }
+
+            r->done = 1;
+
+            if (pr->postponed && pr->postponed->request == r) {
+                pr->postponed = pr->postponed->next;
+            }
+
+            c->data = pr;
+
+        } else {
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                           "http finalize non-active request: \"%V?%V\"",
+                           &r->uri, &r->args);
+
+            r->write_event_handler = ngx_http_request_finalizer;
+
+            if (r->waited) {
+                r->done = 1;
+            }
+        }
+
+        if (ngx_http_post_request(pr, NULL) != NGX_OK) {
+            r->main->count++;
+            ngx_http_terminate_request(r, 0);
+            return;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http wake parent request: \"%V?%V\"",
+                       &pr->uri, &pr->args);
+
+        return;
+    }
+
+    if (r->buffered || c->buffered || r->postponed) {
+
+        if (ngx_http_set_write_handler(r) != NGX_OK) {
+            ngx_http_terminate_request(r, 0);
+        }
+
+        return;
+    }
+
+    if (r != c->data) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                      "http finalize non-active request: \"%V?%V\"",
+                      &r->uri, &r->args);
+        return;
+    }
+
+    r->done = 1;
+
+    r->read_event_handler = ngx_http_block_reading;
+    r->write_event_handler = ngx_http_request_empty_handler;
+
+    if (!r->post_action) {
+        r->request_complete = 1;
+    }
+
+    if (ngx_http_post_action(r) == NGX_OK) {
+        return;
+    }
+
+    if (c->read->timer_set) {
+        ngx_del_timer(c->read);
+    }
+
+    if (c->write->timer_set) {
+        c->write->delayed = 0;
+        ngx_del_timer(c->write);
+    }
+
+    if (c->read->eof) {
+        ngx_http_close_request(r, 0);
+        return;
+    }
+
+    ngx_http_finalize_connection(r);
+}
+
+
+static void
+ngx_http_terminate_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+    ngx_http_cleanup_t    *cln;
+    ngx_http_request_t    *mr;
+    ngx_http_ephemeral_t  *e;
+
+    mr = r->main;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http terminate request count:%d", mr->count);
+
+    if (rc > 0 && (mr->headers_out.status == 0 || mr->connection->sent == 0)) {
+        mr->headers_out.status = rc;
+    }
+
+    cln = mr->cleanup;
+    mr->cleanup = NULL;
+
+    while (cln) {
+        if (cln->handler) {
+            cln->handler(cln->data);
+        }
+
+        cln = cln->next;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http terminate cleanup count:%d blk:%d",
+                   mr->count, mr->blocked);
+
+    if (mr->write_event_handler) {
+
+        if (mr->blocked) {
+            r->connection->error = 1;
+            r->write_event_handler = ngx_http_request_finalizer;
+            return;
+        }
+
+        e = ngx_http_ephemeral(mr);
+        mr->posted_requests = NULL;
+        mr->write_event_handler = ngx_http_terminate_handler;
+        (void) ngx_http_post_request(mr, &e->terminal_posted_request);
+        return;
+    }
+
+    ngx_http_close_request(mr, rc);
+}
+
+
+static void
+ngx_http_terminate_handler(ngx_http_request_t *r)
+{
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http terminate handler count:%d", r->count);
+
+    r->count = 1;
+
+    ngx_http_close_request(r, 0);
+}
+
+
+static void
+ngx_http_finalize_connection(ngx_http_request_t *r)
+{
+    ngx_http_core_loc_conf_t  *clcf;
+
+#if (NGX_HTTP_V2)
+    if (r->stream) {
+        ngx_http_close_request(r, 0);
+        return;
+    }
+#endif
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (r->main->count != 1) {
+
+        if (r->discard_body) {
+            r->read_event_handler = ngx_http_discarded_request_body_handler;
+            ngx_add_timer(r->connection->read, clcf->lingering_timeout);
+
+            if (r->lingering_time == 0) {
+                r->lingering_time = ngx_time()
+                                      + (time_t) (clcf->lingering_time / 1000);
+            }
+        }
+
+        ngx_http_close_request(r, 0);
+        return;
+    }
+
+    r = r->main;
+
+    if (r->reading_body) {
+        r->keepalive = 0;
+        r->lingering_close = 1;
+    }
+
+    if (!ngx_terminate
+         && !ngx_exiting
+         && r->keepalive
+         && clcf->keepalive_timeout > 0)
+    {
+        ngx_http_set_keepalive(r);
+        return;
+    }
+
+    if (clcf->lingering_close == NGX_HTTP_LINGERING_ALWAYS
+        || (clcf->lingering_close == NGX_HTTP_LINGERING_ON
+            && (r->lingering_close
+                || r->header_in->pos < r->header_in->last
+                || r->connection->read->ready)))
+    {
+        ngx_http_set_lingering_close(r);
+        return;
+    }
+
+    ngx_http_close_request(r, 0);
+}
+
+
+static ngx_int_t
+ngx_http_set_write_handler(ngx_http_request_t *r)
+{
+    ngx_event_t               *wev;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    r->http_state = NGX_HTTP_WRITING_REQUEST_STATE;
+
+    r->read_event_handler = r->discard_body ?
+                                ngx_http_discarded_request_body_handler:
+                                ngx_http_test_reading;
+    r->write_event_handler = ngx_http_writer;
+
+    wev = r->connection->write;
+
+    if (wev->ready && wev->delayed) {
+        return NGX_OK;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+    if (!wev->delayed) {
+        ngx_add_timer(wev, clcf->send_timeout);
+    }
+
+    if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) {
+        ngx_http_close_request(r, 0);
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_writer(ngx_http_request_t *r)
+{
+    ngx_int_t                  rc;
+    ngx_event_t               *wev;
+    ngx_connection_t          *c;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    c = r->connection;
+    wev = c->write;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, wev->log, 0,
+                   "http writer handler: \"%V?%V\"", &r->uri, &r->args);
+
+    clcf = ngx_http_get_module_loc_conf(r->main, ngx_http_core_module);
+
+    if (wev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+                      "client timed out");
+        c->timedout = 1;
+
+        ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT);
+        return;
+    }
+
+    if (wev->delayed || r->aio) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0,
+                       "http writer delayed");
+
+        if (!wev->delayed) {
+            ngx_add_timer(wev, clcf->send_timeout);
+        }
+
+        if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) {
+            ngx_http_close_request(r, 0);
+        }
+
+        return;
+    }
+
+    rc = ngx_http_output_filter(r, NULL);
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http writer output filter: %i, \"%V?%V\"",
+                   rc, &r->uri, &r->args);
+
+    if (rc == NGX_ERROR) {
+        ngx_http_finalize_request(r, rc);
+        return;
+    }
+
+    if (r->buffered || r->postponed || (r == r->main && c->buffered)) {
+
+        if (!wev->delayed) {
+            ngx_add_timer(wev, clcf->send_timeout);
+        }
+
+        if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) {
+            ngx_http_close_request(r, 0);
+        }
+
+        return;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, wev->log, 0,
+                   "http writer done: \"%V?%V\"", &r->uri, &r->args);
+
+    r->write_event_handler = ngx_http_request_empty_handler;
+
+    ngx_http_finalize_request(r, rc);
+}
+
+
+static void
+ngx_http_request_finalizer(ngx_http_request_t *r)
+{
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http finalizer done: \"%V?%V\"", &r->uri, &r->args);
+
+    ngx_http_finalize_request(r, 0);
+}
+
+
+void
+ngx_http_block_reading(ngx_http_request_t *r)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http reading blocked");
+
+    /* aio does not call this handler */
+
+    if ((ngx_event_flags & NGX_USE_LEVEL_EVENT)
+        && r->connection->read->active)
+    {
+        if (ngx_del_event(r->connection->read, NGX_READ_EVENT, 0) != NGX_OK) {
+            ngx_http_close_request(r, 0);
+        }
+    }
+}
+
+
+void
+ngx_http_test_reading(ngx_http_request_t *r)
+{
+    int                n;
+    char               buf[1];
+    ngx_err_t          err;
+    ngx_event_t       *rev;
+    ngx_connection_t  *c;
+
+    c = r->connection;
+    rev = c->read;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http test reading");
+
+#if (NGX_HTTP_V2)
+
+    if (r->stream) {
+        if (c->error) {
+            err = 0;
+            goto closed;
+        }
+
+        return;
+    }
+
+#endif
+
+#if (NGX_HAVE_KQUEUE)
+
+    if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+
+        if (!rev->pending_eof) {
+            return;
+        }
+
+        rev->eof = 1;
+        c->error = 1;
+        err = rev->kq_errno;
+
+        goto closed;
+    }
+
+#endif
+
+#if (NGX_HAVE_EPOLLRDHUP)
+
+    if ((ngx_event_flags & NGX_USE_EPOLL_EVENT) && ngx_use_epoll_rdhup) {
+        socklen_t  len;
+
+        if (!rev->pending_eof) {
+            return;
+        }
+
+        rev->eof = 1;
+        c->error = 1;
+
+        err = 0;
+        len = sizeof(ngx_err_t);
+
+        /*
+         * BSDs and Linux return 0 and set a pending error in err
+         * Solaris returns -1 and sets errno
+         */
+
+        if (ngxvcl_kvfd_getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len)
+            == -1)
+        {
+            err = ngx_socket_errno;
+        }
+
+        goto closed;
+    }
+
+#endif
+
+    n = ngxvcl_recv(c->fd, buf, 1, MSG_PEEK);
+
+    if (n == 0) {
+        rev->eof = 1;
+        c->error = 1;
+        err = 0;
+
+        goto closed;
+
+    } else if (n == -1) {
+        err = ngx_socket_errno;
+
+        if (err != NGX_EAGAIN) {
+            rev->eof = 1;
+            c->error = 1;
+
+            goto closed;
+        }
+    }
+
+    /* aio does not call this handler */
+
+    if ((ngx_event_flags & NGX_USE_LEVEL_EVENT) && rev->active) {
+
+        if (ngx_del_event(rev, NGX_READ_EVENT, 0) != NGX_OK) {
+            ngx_http_close_request(r, 0);
+        }
+    }
+
+    return;
+
+closed:
+
+    if (err) {
+        rev->error = 1;
+    }
+
+    ngx_log_error(NGX_LOG_INFO, c->log, err,
+                  "client prematurely closed connection");
+
+    ngx_http_finalize_request(r, NGX_HTTP_CLIENT_CLOSED_REQUEST);
+}
+
+
+static void
+ngx_http_set_keepalive(ngx_http_request_t *r)
+{
+    int                        tcp_nodelay;
+    ngx_buf_t                 *b, *f;
+    ngx_chain_t               *cl, *ln;
+    ngx_event_t               *rev, *wev;
+    ngx_connection_t          *c;
+    ngx_http_connection_t     *hc;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    c = r->connection;
+    rev = c->read;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    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;
+    b = r->header_in;
+
+    if (b->pos < b->last) {
+
+        /* the pipelined request */
+
+        if (b != c->buffer) {
+
+            /*
+             * If the large header buffers were allocated while the previous
+             * request processing then we do not use c->buffer for
+             * the pipelined request (see ngx_http_create_request()).
+             *
+             * Now we would move the large header buffers to the free list.
+             */
+
+            for (cl = hc->busy; cl; /* void */) {
+                ln = cl;
+                cl = cl->next;
+
+                if (ln->buf == b) {
+                    ngx_free_chain(c->pool, ln);
+                    continue;
+                }
+
+                f = ln->buf;
+                f->pos = f->start;
+                f->last = f->start;
+
+                ln->next = hc->free;
+                hc->free = ln;
+            }
+
+            cl = ngx_alloc_chain_link(c->pool);
+            if (cl == NULL) {
+                ngx_http_close_request(r, 0);
+                return;
+            }
+
+            cl->buf = b;
+            cl->next = NULL;
+
+            hc->busy = cl;
+            hc->nbusy = 1;
+        }
+    }
+
+    /* guard against recursive call from ngx_http_finalize_connection() */
+    r->keepalive = 0;
+
+    ngx_http_free_request(r, 0);
+
+    c->data = hc;
+
+    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 (b->pos < b->last) {
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "pipelined request");
+
+        c->log->action = "reading client pipelined request line";
+
+        r = ngx_http_create_request(c);
+        if (r == NULL) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        r->pipeline = 1;
+
+        c->data = r;
+
+        c->sent = 0;
+        c->destroyed = 0;
+
+        if (rev->timer_set) {
+            ngx_del_timer(rev);
+        }
+
+        rev->handler = ngx_http_process_request_line;
+        ngx_post_event(rev, &ngx_posted_events);
+        return;
+    }
+
+    /*
+     * To keep a memory footprint as small as possible for an idle keepalive
+     * connection we try to free c->buffer's memory if it was allocated outside
+     * the c->pool.  The large header buffers are always allocated outside the
+     * c->pool and are freed too.
+     */
+
+    b = c->buffer;
+
+    if (ngx_pfree(c->pool, b->start) == NGX_OK) {
+
+        /*
+         * the special note for ngx_http_keepalive_handler() that
+         * c->buffer's memory was freed
+         */
+
+        b->pos = NULL;
+
+    } else {
+        b->pos = b->start;
+        b->last = b->start;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "hc free: %p",
+                   hc->free);
+
+    if (hc->free) {
+        for (cl = hc->free; cl; /* void */) {
+            ln = cl;
+            cl = cl->next;
+            ngx_pfree(c->pool, ln->buf->start);
+            ngx_free_chain(c->pool, ln);
+        }
+
+        hc->free = NULL;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "hc busy: %p %i",
+                   hc->busy, hc->nbusy);
+
+    if (hc->busy) {
+        for (cl = hc->busy; cl; /* void */) {
+            ln = cl;
+            cl = cl->next;
+            ngx_pfree(c->pool, ln->buf->start);
+            ngx_free_chain(c->pool, ln);
+        }
+
+        hc->busy = NULL;
+        hc->nbusy = 0;
+    }
+
+#if (NGX_HTTP_SSL)
+    if (c->ssl) {
+        ngx_ssl_free_buffer(c);
+    }
+#endif
+
+    rev->handler = ngx_http_keepalive_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;
+        }
+    }
+
+    c->log->action = "keepalive";
+
+    if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) {
+        if (ngx_tcp_push(c->fd) == -1) {
+            ngx_connection_error(c, ngx_socket_errno, ngx_tcp_push_n " failed");
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        c->tcp_nopush = NGX_TCP_NOPUSH_UNSET;
+        tcp_nodelay = ngx_tcp_nodelay_and_tcp_nopush ? 1 : 0;
+
+    } else {
+        tcp_nodelay = 1;
+    }
+
+    if (tcp_nodelay && clcf->tcp_nodelay && ngx_tcp_nodelay(c) != NGX_OK) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+#if 0
+    /* if ngx_http_request_t was freed then we need some other place */
+    r->http_state = NGX_HTTP_KEEPALIVE_STATE;
+#endif
+
+    c->idle = 1;
+    ngx_reusable_connection(c, 1);
+
+    ngx_add_timer(rev, clcf->keepalive_timeout);
+
+    if (rev->ready) {
+        ngx_post_event(rev, &ngx_posted_events);
+    }
+}
+
+
+static void
+ngx_http_keepalive_handler(ngx_event_t *rev)
+{
+    size_t             size;
+    ssize_t            n;
+    ngx_buf_t         *b;
+    ngx_connection_t  *c;
+
+    c = rev->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http keepalive handler");
+
+    if (rev->timedout || c->close) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+#if (NGX_HAVE_KQUEUE)
+
+    if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+        if (rev->pending_eof) {
+            c->log->handler = NULL;
+            ngx_log_error(NGX_LOG_INFO, c->log, rev->kq_errno,
+                          "kevent() reported that client %V closed "
+                          "keepalive connection", &c->addr_text);
+#if (NGX_HTTP_SSL)
+            if (c->ssl) {
+                c->ssl->no_send_shutdown = 1;
+            }
+#endif
+            ngx_http_close_connection(c);
+            return;
+        }
+    }
+
+#endif
+
+    b = c->buffer;
+    size = b->end - b->start;
+
+    if (b->pos == NULL) {
+
+        /*
+         * The c->buffer's memory was freed by ngx_http_set_keepalive().
+         * However, the c->buffer->start and c->buffer->end were not changed
+         * to keep the buffer size.
+         */
+
+        b->pos = ngx_palloc(c->pool, size);
+        if (b->pos == NULL) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        b->start = b->pos;
+        b->last = b->pos;
+        b->end = b->pos + size;
+    }
+
+    /*
+     * MSIE closes a keepalive connection with RST flag
+     * so we ignore ECONNRESET here.
+     */
+
+    c->log_error = NGX_ERROR_IGNORE_ECONNRESET;
+    ngx_set_socket_errno(0);
+
+    n = c->recv(c, b->last, size);
+    c->log_error = NGX_ERROR_INFO;
+
+    if (n == NGX_AGAIN) {
+        if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        /*
+         * Like ngx_http_set_keepalive() we are trying to not hold
+         * c->buffer's memory for a keepalive connection.
+         */
+
+        if (ngx_pfree(c->pool, b->start) == NGX_OK) {
+
+            /*
+             * the special note that c->buffer's memory was freed
+             */
+
+            b->pos = NULL;
+        }
+
+        return;
+    }
+
+    if (n == NGX_ERROR) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    c->log->handler = NULL;
+
+    if (n == 0) {
+        ngx_log_error(NGX_LOG_INFO, c->log, ngx_socket_errno,
+                      "client %V closed keepalive connection", &c->addr_text);
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    b->last += n;
+
+    c->log->handler = ngx_http_log_error;
+    c->log->action = "reading client request line";
+
+    c->idle = 0;
+    ngx_reusable_connection(c, 0);
+
+    c->data = ngx_http_create_request(c);
+    if (c->data == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    c->sent = 0;
+    c->destroyed = 0;
+
+    ngx_del_timer(rev);
+
+    rev->handler = ngx_http_process_request_line;
+    ngx_http_process_request_line(rev);
+}
+
+
+static void
+ngx_http_set_lingering_close(ngx_http_request_t *r)
+{
+    ngx_event_t               *rev, *wev;
+    ngx_connection_t          *c;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    c = r->connection;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    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;
+    }
+
+    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_request(r, 0);
+            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_request(r, 0);
+        return;
+    }
+
+    if (rev->ready) {
+        ngx_http_lingering_close_handler(rev);
+    }
+}
+
+
+static void
+ngx_http_lingering_close_handler(ngx_event_t *rev)
+{
+    ssize_t                    n;
+    ngx_msec_t                 timer;
+    ngx_connection_t          *c;
+    ngx_http_request_t        *r;
+    ngx_http_core_loc_conf_t  *clcf;
+    u_char                     buffer[NGX_HTTP_LINGERING_BUFFER_SIZE];
+
+    c = rev->data;
+    r = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http lingering close handler");
+
+    if (rev->timedout) {
+        ngx_http_close_request(r, 0);
+        return;
+    }
+
+    timer = (ngx_msec_t) r->lingering_time - (ngx_msec_t) ngx_time();
+    if ((ngx_msec_int_t) timer <= 0) {
+        ngx_http_close_request(r, 0);
+        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_ERROR || n == 0) {
+            ngx_http_close_request(r, 0);
+            return;
+        }
+
+    } while (rev->ready);
+
+    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+        ngx_http_close_request(r, 0);
+        return;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    timer *= 1000;
+
+    if (timer > clcf->lingering_timeout) {
+        timer = clcf->lingering_timeout;
+    }
+
+    ngx_add_timer(rev, timer);
+}
+
+
+void
+ngx_http_empty_handler(ngx_event_t *wev)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0, "http empty handler");
+
+    return;
+}
+
+
+void
+ngx_http_request_empty_handler(ngx_http_request_t *r)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http request empty handler");
+
+    return;
+}
+
+
+ngx_int_t
+ngx_http_send_special(ngx_http_request_t *r, ngx_uint_t flags)
+{
+    ngx_buf_t    *b;
+    ngx_chain_t   out;
+
+    b = ngx_calloc_buf(r->pool);
+    if (b == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (flags & NGX_HTTP_LAST) {
+
+        if (r == r->main && !r->post_action) {
+            b->last_buf = 1;
+
+        } else {
+            b->sync = 1;
+            b->last_in_chain = 1;
+        }
+    }
+
+    if (flags & NGX_HTTP_FLUSH) {
+        b->flush = 1;
+    }
+
+    out.buf = b;
+    out.next = NULL;
+
+    return ngx_http_output_filter(r, &out);
+}
+
+
+static ngx_int_t
+ngx_http_post_action(ngx_http_request_t *r)
+{
+    ngx_http_core_loc_conf_t  *clcf;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (clcf->post_action.data == NULL) {
+        return NGX_DECLINED;
+    }
+
+    if (r->post_action && r->uri_changes == 0) {
+        return NGX_DECLINED;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "post action: \"%V\"", &clcf->post_action);
+
+    r->main->count--;
+
+    r->http_version = NGX_HTTP_VERSION_9;
+    r->header_only = 1;
+    r->post_action = 1;
+
+    r->read_event_handler = ngx_http_block_reading;
+
+    if (clcf->post_action.data[0] == '/') {
+        ngx_http_internal_redirect(r, &clcf->post_action, NULL);
+
+    } else {
+        ngx_http_named_location(r, &clcf->post_action);
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+    ngx_connection_t  *c;
+
+    r = r->main;
+    c = r->connection;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http request count:%d blk:%d", r->count, r->blocked);
+
+    if (r->count == 0) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, 0, "http request count is zero");
+    }
+
+    r->count--;
+
+    if (r->count || r->blocked) {
+        return;
+    }
+
+#if (NGX_HTTP_V2)
+    if (r->stream) {
+        ngx_http_v2_close_stream(r->stream, rc);
+        return;
+    }
+#endif
+
+    ngx_http_free_request(r, rc);
+    ngx_http_close_connection(c);
+}
+
+
+void
+ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+    ngx_log_t                 *log;
+    ngx_pool_t                *pool;
+    struct linger              linger;
+    ngx_http_cleanup_t        *cln;
+    ngx_http_log_ctx_t        *ctx;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    log = r->connection->log;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http close request");
+
+    if (r->pool == NULL) {
+        ngx_log_error(NGX_LOG_ALERT, log, 0, "http request already closed");
+        return;
+    }
+
+    cln = r->cleanup;
+    r->cleanup = NULL;
+
+    while (cln) {
+        if (cln->handler) {
+            cln->handler(cln->data);
+        }
+
+        cln = cln->next;
+    }
+
+#if (NGX_STAT_STUB)
+
+    if (r->stat_reading) {
+        (void) ngx_atomic_fetch_add(ngx_stat_reading, -1);
+    }
+
+    if (r->stat_writing) {
+        (void) ngx_atomic_fetch_add(ngx_stat_writing, -1);
+    }
+
+#endif
+
+    if (rc > 0 && (r->headers_out.status == 0 || r->connection->sent == 0)) {
+        r->headers_out.status = rc;
+    }
+
+    log->action = "logging request";
+
+    ngx_http_log_request(r);
+
+    log->action = "closing request";
+
+    if (r->connection->timedout) {
+        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+        if (clcf->reset_timedout_connection) {
+            linger.l_onoff = 1;
+            linger.l_linger = 0;
+
+            if (ngxvcl_kvfd_setsockopt(r->connection->fd, SOL_SOCKET, SO_LINGER,
+                           (const void *) &linger, sizeof(struct linger)) == -1)
+            {
+                ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno,
+                              "setsockopt(SO_LINGER) failed");
+            }
+        }
+    }
+
+    /* the various request strings were allocated from r->pool */
+    ctx = log->data;
+    ctx->request = NULL;
+
+    r->request_line.len = 0;
+
+    r->connection->destroyed = 1;
+
+    /*
+     * Setting r->pool to NULL will increase probability to catch double close
+     * of request since the request object is allocated from its own pool.
+     */
+
+    pool = r->pool;
+    r->pool = NULL;
+
+    ngx_destroy_pool(pool);
+}
+
+
+static void
+ngx_http_log_request(ngx_http_request_t *r)
+{
+    ngx_uint_t                  i, n;
+    ngx_http_handler_pt        *log_handler;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+    log_handler = cmcf->phases[NGX_HTTP_LOG_PHASE].handlers.elts;
+    n = cmcf->phases[NGX_HTTP_LOG_PHASE].handlers.nelts;
+
+    for (i = 0; i < n; i++) {
+        log_handler[i](r);
+    }
+}
+
+
+void
+ngx_http_close_connection(ngx_connection_t *c)
+{
+    ngx_pool_t  *pool;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "close http connection: %d", c->fd);
+
+#if (NGX_HTTP_SSL)
+
+    if (c->ssl) {
+        if (ngx_ssl_shutdown(c) == NGX_AGAIN) {
+            c->ssl->handler = ngx_http_close_connection;
+            return;
+        }
+    }
+
+#endif
+
+#if (NGX_STAT_STUB)
+    (void) ngx_atomic_fetch_add(ngx_stat_active, -1);
+#endif
+
+    c->destroyed = 1;
+
+    pool = c->pool;
+
+    ngx_close_connection(c);
+
+    ngx_destroy_pool(pool);
+}
+
+
+static u_char *
+ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len)
+{
+    u_char              *p;
+    ngx_http_request_t  *r;
+    ngx_http_log_ctx_t  *ctx;
+
+    if (log->action) {
+        p = ngx_snprintf(buf, len, " while %s", log->action);
+        len -= p - buf;
+        buf = p;
+    }
+
+    ctx = log->data;
+
+    p = ngx_snprintf(buf, len, ", client: %V", &ctx->connection->addr_text);
+    len -= p - buf;
+
+    r = ctx->request;
+
+    if (r) {
+        return r->log_handler(r, ctx->current_request, p, len);
+
+    } else {
+        p = ngx_snprintf(p, len, ", server: %V",
+                         &ctx->connection->listening->addr_text);
+    }
+
+    return p;
+}
+
+
+static u_char *
+ngx_http_log_error_handler(ngx_http_request_t *r, ngx_http_request_t *sr,
+    u_char *buf, size_t len)
+{
+    char                      *uri_separator;
+    u_char                    *p;
+    ngx_http_upstream_t       *u;
+    ngx_http_core_srv_conf_t  *cscf;
+
+    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+    p = ngx_snprintf(buf, len, ", server: %V", &cscf->server_name);
+    len -= p - buf;
+    buf = p;
+
+    if (r->request_line.data == NULL && r->request_start) {
+        for (p = r->request_start; p < r->header_in->last; p++) {
+            if (*p == CR || *p == LF) {
+                break;
+            }
+        }
+
+        r->request_line.len = p - r->request_start;
+        r->request_line.data = r->request_start;
+    }
+
+    if (r->request_line.len) {
+        p = ngx_snprintf(buf, len, ", request: \"%V\"", &r->request_line);
+        len -= p - buf;
+        buf = p;
+    }
+
+    if (r != sr) {
+        p = ngx_snprintf(buf, len, ", subrequest: \"%V\"", &sr->uri);
+        len -= p - buf;
+        buf = p;
+    }
+
+    u = sr->upstream;
+
+    if (u && u->peer.name) {
+
+        uri_separator = "";
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+        if (u->peer.sockaddr && u->peer.sockaddr->sa_family == AF_UNIX) {
+            uri_separator = ":";
+        }
+#endif
+
+        p = ngx_snprintf(buf, len, ", upstream: \"%V%V%s%V\"",
+                         &u->schema, u->peer.name,
+                         uri_separator, &u->uri);
+        len -= p - buf;
+        buf = p;
+    }
+
+    if (r->headers_in.host) {
+        p = ngx_snprintf(buf, len, ", host: \"%V\"",
+                         &r->headers_in.host->value);
+        len -= p - buf;
+        buf = p;
+    }
+
+    if (r->headers_in.referer) {
+        p = ngx_snprintf(buf, len, ", referrer: \"%V\"",
+                         &r->headers_in.referer->value);
+        buf = p;
+    }
+
+    return buf;
+}
diff --git a/nginx/src/http/ngx_http_request.h b/nginx/src/http/ngx_http_request.h
new file mode 100644 (file)
index 0000000..39baa0f
--- /dev/null
@@ -0,0 +1,607 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_REQUEST_H_INCLUDED_
+#define _NGX_HTTP_REQUEST_H_INCLUDED_
+
+
+#define NGX_HTTP_MAX_URI_CHANGES           10
+#define NGX_HTTP_MAX_SUBREQUESTS           50
+
+/* must be 2^n */
+#define NGX_HTTP_LC_HEADER_LEN             32
+
+
+#define NGX_HTTP_DISCARD_BUFFER_SIZE       4096
+#define NGX_HTTP_LINGERING_BUFFER_SIZE     4096
+
+
+#define NGX_HTTP_VERSION_9                 9
+#define NGX_HTTP_VERSION_10                1000
+#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
+
+#define NGX_HTTP_CONNECTION_CLOSE          1
+#define NGX_HTTP_CONNECTION_KEEP_ALIVE     2
+
+
+#define NGX_NONE                           1
+
+
+#define NGX_HTTP_PARSE_HEADER_DONE         1
+
+#define NGX_HTTP_CLIENT_ERROR              10
+#define NGX_HTTP_PARSE_INVALID_METHOD      10
+#define NGX_HTTP_PARSE_INVALID_REQUEST     11
+#define NGX_HTTP_PARSE_INVALID_VERSION     12
+#define NGX_HTTP_PARSE_INVALID_09_METHOD   13
+
+#define NGX_HTTP_PARSE_INVALID_HEADER      14
+
+
+/* unused                                  1 */
+#define NGX_HTTP_SUBREQUEST_IN_MEMORY      2
+#define NGX_HTTP_SUBREQUEST_WAITED         4
+#define NGX_HTTP_SUBREQUEST_CLONE          8
+#define NGX_HTTP_SUBREQUEST_BACKGROUND     16
+
+#define NGX_HTTP_LOG_UNSAFE                1
+
+
+#define NGX_HTTP_CONTINUE                  100
+#define NGX_HTTP_SWITCHING_PROTOCOLS       101
+#define NGX_HTTP_PROCESSING                102
+
+#define NGX_HTTP_OK                        200
+#define NGX_HTTP_CREATED                   201
+#define NGX_HTTP_ACCEPTED                  202
+#define NGX_HTTP_NO_CONTENT                204
+#define NGX_HTTP_PARTIAL_CONTENT           206
+
+#define NGX_HTTP_SPECIAL_RESPONSE          300
+#define NGX_HTTP_MOVED_PERMANENTLY         301
+#define NGX_HTTP_MOVED_TEMPORARILY         302
+#define NGX_HTTP_SEE_OTHER                 303
+#define NGX_HTTP_NOT_MODIFIED              304
+#define NGX_HTTP_TEMPORARY_REDIRECT        307
+#define NGX_HTTP_PERMANENT_REDIRECT        308
+
+#define NGX_HTTP_BAD_REQUEST               400
+#define NGX_HTTP_UNAUTHORIZED              401
+#define NGX_HTTP_FORBIDDEN                 403
+#define NGX_HTTP_NOT_FOUND                 404
+#define NGX_HTTP_NOT_ALLOWED               405
+#define NGX_HTTP_REQUEST_TIME_OUT          408
+#define NGX_HTTP_CONFLICT                  409
+#define NGX_HTTP_LENGTH_REQUIRED           411
+#define NGX_HTTP_PRECONDITION_FAILED       412
+#define NGX_HTTP_REQUEST_ENTITY_TOO_LARGE  413
+#define NGX_HTTP_REQUEST_URI_TOO_LARGE     414
+#define NGX_HTTP_UNSUPPORTED_MEDIA_TYPE    415
+#define NGX_HTTP_RANGE_NOT_SATISFIABLE     416
+#define NGX_HTTP_MISDIRECTED_REQUEST       421
+#define NGX_HTTP_TOO_MANY_REQUESTS         429
+
+
+/* Our own HTTP codes */
+
+/* The special code to close connection without any response */
+#define NGX_HTTP_CLOSE                     444
+
+#define NGX_HTTP_NGINX_CODES               494
+
+#define NGX_HTTP_REQUEST_HEADER_TOO_LARGE  494
+
+#define NGX_HTTPS_CERT_ERROR               495
+#define NGX_HTTPS_NO_CERT                  496
+
+/*
+ * We use the special code for the plain HTTP requests that are sent to
+ * HTTPS port to distinguish it from 4XX in an error page redirection
+ */
+#define NGX_HTTP_TO_HTTPS                  497
+
+/* 498 is the canceled code for the requests with invalid host name */
+
+/*
+ * HTTP does not define the code for the case when a client closed
+ * the connection while we are processing its request so we introduce
+ * own code to log such situation when a client has closed the connection
+ * before we even try to send the HTTP header to it
+ */
+#define NGX_HTTP_CLIENT_CLOSED_REQUEST     499
+
+
+#define NGX_HTTP_INTERNAL_SERVER_ERROR     500
+#define NGX_HTTP_NOT_IMPLEMENTED           501
+#define NGX_HTTP_BAD_GATEWAY               502
+#define NGX_HTTP_SERVICE_UNAVAILABLE       503
+#define NGX_HTTP_GATEWAY_TIME_OUT          504
+#define NGX_HTTP_VERSION_NOT_SUPPORTED     505
+#define NGX_HTTP_INSUFFICIENT_STORAGE      507
+
+
+#define NGX_HTTP_LOWLEVEL_BUFFERED         0xf0
+#define NGX_HTTP_WRITE_BUFFERED            0x10
+#define NGX_HTTP_GZIP_BUFFERED             0x20
+#define NGX_HTTP_SSI_BUFFERED              0x01
+#define NGX_HTTP_SUB_BUFFERED              0x02
+#define NGX_HTTP_COPY_BUFFERED             0x04
+
+
+typedef enum {
+    NGX_HTTP_INITING_REQUEST_STATE = 0,
+    NGX_HTTP_READING_REQUEST_STATE,
+    NGX_HTTP_PROCESS_REQUEST_STATE,
+
+    NGX_HTTP_CONNECT_UPSTREAM_STATE,
+    NGX_HTTP_WRITING_UPSTREAM_STATE,
+    NGX_HTTP_READING_UPSTREAM_STATE,
+
+    NGX_HTTP_WRITING_REQUEST_STATE,
+    NGX_HTTP_LINGERING_CLOSE_STATE,
+    NGX_HTTP_KEEPALIVE_STATE
+} ngx_http_state_e;
+
+
+typedef struct {
+    ngx_str_t                         name;
+    ngx_uint_t                        offset;
+    ngx_http_header_handler_pt        handler;
+} ngx_http_header_t;
+
+
+typedef struct {
+    ngx_str_t                         name;
+    ngx_uint_t                        offset;
+} ngx_http_header_out_t;
+
+
+typedef struct {
+    ngx_list_t                        headers;
+
+    ngx_table_elt_t                  *host;
+    ngx_table_elt_t                  *connection;
+    ngx_table_elt_t                  *if_modified_since;
+    ngx_table_elt_t                  *if_unmodified_since;
+    ngx_table_elt_t                  *if_match;
+    ngx_table_elt_t                  *if_none_match;
+    ngx_table_elt_t                  *user_agent;
+    ngx_table_elt_t                  *referer;
+    ngx_table_elt_t                  *content_length;
+    ngx_table_elt_t                  *content_range;
+    ngx_table_elt_t                  *content_type;
+
+    ngx_table_elt_t                  *range;
+    ngx_table_elt_t                  *if_range;
+
+    ngx_table_elt_t                  *transfer_encoding;
+    ngx_table_elt_t                  *te;
+    ngx_table_elt_t                  *expect;
+    ngx_table_elt_t                  *upgrade;
+
+#if (NGX_HTTP_GZIP || NGX_HTTP_HEADERS)
+    ngx_table_elt_t                  *accept_encoding;
+    ngx_table_elt_t                  *via;
+#endif
+
+    ngx_table_elt_t                  *authorization;
+
+    ngx_table_elt_t                  *keep_alive;
+
+#if (NGX_HTTP_X_FORWARDED_FOR)
+    ngx_array_t                       x_forwarded_for;
+#endif
+
+#if (NGX_HTTP_REALIP)
+    ngx_table_elt_t                  *x_real_ip;
+#endif
+
+#if (NGX_HTTP_HEADERS)
+    ngx_table_elt_t                  *accept;
+    ngx_table_elt_t                  *accept_language;
+#endif
+
+#if (NGX_HTTP_DAV)
+    ngx_table_elt_t                  *depth;
+    ngx_table_elt_t                  *destination;
+    ngx_table_elt_t                  *overwrite;
+    ngx_table_elt_t                  *date;
+#endif
+
+    ngx_str_t                         user;
+    ngx_str_t                         passwd;
+
+    ngx_array_t                       cookies;
+
+    ngx_str_t                         server;
+    off_t                             content_length_n;
+    time_t                            keep_alive_n;
+
+    unsigned                          connection_type:2;
+    unsigned                          chunked:1;
+    unsigned                          msie:1;
+    unsigned                          msie6:1;
+    unsigned                          opera:1;
+    unsigned                          gecko:1;
+    unsigned                          chrome:1;
+    unsigned                          safari:1;
+    unsigned                          konqueror:1;
+} ngx_http_headers_in_t;
+
+
+typedef struct {
+    ngx_list_t                        headers;
+    ngx_list_t                        trailers;
+
+    ngx_uint_t                        status;
+    ngx_str_t                         status_line;
+
+    ngx_table_elt_t                  *server;
+    ngx_table_elt_t                  *date;
+    ngx_table_elt_t                  *content_length;
+    ngx_table_elt_t                  *content_encoding;
+    ngx_table_elt_t                  *location;
+    ngx_table_elt_t                  *refresh;
+    ngx_table_elt_t                  *last_modified;
+    ngx_table_elt_t                  *content_range;
+    ngx_table_elt_t                  *accept_ranges;
+    ngx_table_elt_t                  *www_authenticate;
+    ngx_table_elt_t                  *expires;
+    ngx_table_elt_t                  *etag;
+
+    ngx_str_t                        *override_charset;
+
+    size_t                            content_type_len;
+    ngx_str_t                         content_type;
+    ngx_str_t                         charset;
+    u_char                           *content_type_lowcase;
+    ngx_uint_t                        content_type_hash;
+
+    ngx_array_t                       cache_control;
+    ngx_array_t                       link;
+
+    off_t                             content_length_n;
+    off_t                             content_offset;
+    time_t                            date_time;
+    time_t                            last_modified_time;
+} ngx_http_headers_out_t;
+
+
+typedef void (*ngx_http_client_body_handler_pt)(ngx_http_request_t *r);
+
+typedef struct {
+    ngx_temp_file_t                  *temp_file;
+    ngx_chain_t                      *bufs;
+    ngx_buf_t                        *buf;
+    off_t                             rest;
+    off_t                             received;
+    ngx_chain_t                      *free;
+    ngx_chain_t                      *busy;
+    ngx_http_chunked_t               *chunked;
+    ngx_http_client_body_handler_pt   post_handler;
+} ngx_http_request_body_t;
+
+
+typedef struct ngx_http_addr_conf_s  ngx_http_addr_conf_t;
+
+typedef struct {
+    ngx_http_addr_conf_t             *addr_conf;
+    ngx_http_conf_ctx_t              *conf_ctx;
+
+#if (NGX_HTTP_SSL || NGX_COMPAT)
+    ngx_str_t                        *ssl_servername;
+#if (NGX_PCRE)
+    ngx_http_regex_t                 *ssl_servername_regex;
+#endif
+#endif
+
+    ngx_chain_t                      *busy;
+    ngx_int_t                         nbusy;
+
+    ngx_chain_t                      *free;
+
+    unsigned                          ssl:1;
+    unsigned                          proxy_protocol:1;
+} ngx_http_connection_t;
+
+
+typedef void (*ngx_http_cleanup_pt)(void *data);
+
+typedef struct ngx_http_cleanup_s  ngx_http_cleanup_t;
+
+struct ngx_http_cleanup_s {
+    ngx_http_cleanup_pt               handler;
+    void                             *data;
+    ngx_http_cleanup_t               *next;
+};
+
+
+typedef ngx_int_t (*ngx_http_post_subrequest_pt)(ngx_http_request_t *r,
+    void *data, ngx_int_t rc);
+
+typedef struct {
+    ngx_http_post_subrequest_pt       handler;
+    void                             *data;
+} ngx_http_post_subrequest_t;
+
+
+typedef struct ngx_http_postponed_request_s  ngx_http_postponed_request_t;
+
+struct ngx_http_postponed_request_s {
+    ngx_http_request_t               *request;
+    ngx_chain_t                      *out;
+    ngx_http_postponed_request_t     *next;
+};
+
+
+typedef struct ngx_http_posted_request_s  ngx_http_posted_request_t;
+
+struct ngx_http_posted_request_s {
+    ngx_http_request_t               *request;
+    ngx_http_posted_request_t        *next;
+};
+
+
+typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);
+typedef void (*ngx_http_event_handler_pt)(ngx_http_request_t *r);
+
+
+struct ngx_http_request_s {
+    uint32_t                          signature;         /* "HTTP" */
+
+    ngx_connection_t                 *connection;
+
+    void                            **ctx;
+    void                            **main_conf;
+    void                            **srv_conf;
+    void                            **loc_conf;
+
+    ngx_http_event_handler_pt         read_event_handler;
+    ngx_http_event_handler_pt         write_event_handler;
+
+#if (NGX_HTTP_CACHE)
+    ngx_http_cache_t                 *cache;
+#endif
+
+    ngx_http_upstream_t              *upstream;
+    ngx_array_t                      *upstream_states;
+                                         /* of ngx_http_upstream_state_t */
+
+    ngx_pool_t                       *pool;
+    ngx_buf_t                        *header_in;
+
+    ngx_http_headers_in_t             headers_in;
+    ngx_http_headers_out_t            headers_out;
+
+    ngx_http_request_body_t          *request_body;
+
+    time_t                            lingering_time;
+    time_t                            start_sec;
+    ngx_msec_t                        start_msec;
+
+    ngx_uint_t                        method;
+    ngx_uint_t                        http_version;
+
+    ngx_str_t                         request_line;
+    ngx_str_t                         uri;
+    ngx_str_t                         args;
+    ngx_str_t                         exten;
+    ngx_str_t                         unparsed_uri;
+
+    ngx_str_t                         method_name;
+    ngx_str_t                         http_protocol;
+
+    ngx_chain_t                      *out;
+    ngx_http_request_t               *main;
+    ngx_http_request_t               *parent;
+    ngx_http_postponed_request_t     *postponed;
+    ngx_http_post_subrequest_t       *post_subrequest;
+    ngx_http_posted_request_t        *posted_requests;
+
+    ngx_int_t                         phase_handler;
+    ngx_http_handler_pt               content_handler;
+    ngx_uint_t                        access_code;
+
+    ngx_http_variable_value_t        *variables;
+
+#if (NGX_PCRE)
+    ngx_uint_t                        ncaptures;
+    int                              *captures;
+    u_char                           *captures_data;
+#endif
+
+    size_t                            limit_rate;
+    size_t                            limit_rate_after;
+
+    /* used to learn the Apache compatible response length without a header */
+    size_t                            header_size;
+
+    off_t                             request_length;
+
+    ngx_uint_t                        err_status;
+
+    ngx_http_connection_t            *http_connection;
+    ngx_http_v2_stream_t             *stream;
+
+    ngx_http_log_handler_pt           log_handler;
+
+    ngx_http_cleanup_t               *cleanup;
+
+    unsigned                          count:16;
+    unsigned                          subrequests:8;
+    unsigned                          blocked:8;
+
+    unsigned                          aio:1;
+
+    unsigned                          http_state:4;
+
+    /* URI with "/." and on Win32 with "//" */
+    unsigned                          complex_uri:1;
+
+    /* URI with "%" */
+    unsigned                          quoted_uri:1;
+
+    /* URI with "+" */
+    unsigned                          plus_in_uri:1;
+
+    /* URI with " " */
+    unsigned                          space_in_uri:1;
+
+    unsigned                          invalid_header:1;
+
+    unsigned                          add_uri_to_alias:1;
+    unsigned                          valid_location:1;
+    unsigned                          valid_unparsed_uri:1;
+    unsigned                          uri_changed:1;
+    unsigned                          uri_changes:4;
+
+    unsigned                          request_body_in_single_buf:1;
+    unsigned                          request_body_in_file_only:1;
+    unsigned                          request_body_in_persistent_file:1;
+    unsigned                          request_body_in_clean_file:1;
+    unsigned                          request_body_file_group_access:1;
+    unsigned                          request_body_file_log_level:3;
+    unsigned                          request_body_no_buffering:1;
+
+    unsigned                          subrequest_in_memory:1;
+    unsigned                          waited:1;
+
+#if (NGX_HTTP_CACHE)
+    unsigned                          cached:1;
+#endif
+
+#if (NGX_HTTP_GZIP)
+    unsigned                          gzip_tested:1;
+    unsigned                          gzip_ok:1;
+    unsigned                          gzip_vary:1;
+#endif
+
+    unsigned                          proxy:1;
+    unsigned                          bypass_cache:1;
+    unsigned                          no_cache:1;
+
+    /*
+     * instead of using the request context data in
+     * ngx_http_limit_conn_module and ngx_http_limit_req_module
+     * we use the single bits in the request structure
+     */
+    unsigned                          limit_conn_set:1;
+    unsigned                          limit_req_set:1;
+
+#if 0
+    unsigned                          cacheable:1;
+#endif
+
+    unsigned                          pipeline:1;
+    unsigned                          chunked:1;
+    unsigned                          header_only:1;
+    unsigned                          expect_trailers:1;
+    unsigned                          keepalive:1;
+    unsigned                          lingering_close:1;
+    unsigned                          discard_body:1;
+    unsigned                          reading_body:1;
+    unsigned                          internal:1;
+    unsigned                          error_page:1;
+    unsigned                          filter_finalize:1;
+    unsigned                          post_action:1;
+    unsigned                          request_complete:1;
+    unsigned                          request_output:1;
+    unsigned                          header_sent:1;
+    unsigned                          expect_tested:1;
+    unsigned                          root_tested:1;
+    unsigned                          done:1;
+    unsigned                          logged:1;
+
+    unsigned                          buffered:4;
+
+    unsigned                          main_filter_need_in_memory:1;
+    unsigned                          filter_need_in_memory:1;
+    unsigned                          filter_need_temporary:1;
+    unsigned                          preserve_body:1;
+    unsigned                          allow_ranges:1;
+    unsigned                          subrequest_ranges:1;
+    unsigned                          single_range:1;
+    unsigned                          disable_not_modified:1;
+    unsigned                          stat_reading:1;
+    unsigned                          stat_writing:1;
+    unsigned                          stat_processing:1;
+
+    unsigned                          background:1;
+    unsigned                          health_check:1;
+
+    /* used to parse HTTP headers */
+
+    ngx_uint_t                        state;
+
+    ngx_uint_t                        header_hash;
+    ngx_uint_t                        lowcase_index;
+    u_char                            lowcase_header[NGX_HTTP_LC_HEADER_LEN];
+
+    u_char                           *header_name_start;
+    u_char                           *header_name_end;
+    u_char                           *header_start;
+    u_char                           *header_end;
+
+    /*
+     * a memory that can be reused after parsing a request line
+     * via ngx_http_ephemeral_t
+     */
+
+    u_char                           *uri_start;
+    u_char                           *uri_end;
+    u_char                           *uri_ext;
+    u_char                           *args_start;
+    u_char                           *request_start;
+    u_char                           *request_end;
+    u_char                           *method_end;
+    u_char                           *schema_start;
+    u_char                           *schema_end;
+    u_char                           *host_start;
+    u_char                           *host_end;
+    u_char                           *port_start;
+    u_char                           *port_end;
+
+    unsigned                          http_minor:16;
+    unsigned                          http_major:16;
+};
+
+
+typedef struct {
+    ngx_http_posted_request_t         terminal_posted_request;
+} ngx_http_ephemeral_t;
+
+
+#define ngx_http_ephemeral(r)  (void *) (&r->uri_start)
+
+
+extern ngx_http_header_t       ngx_http_headers_in[];
+extern ngx_http_header_out_t   ngx_http_headers_out[];
+
+
+#define ngx_http_set_log_request(log, r)                                      \
+    ((ngx_http_log_ctx_t *) log->data)->current_request = r
+
+
+#endif /* _NGX_HTTP_REQUEST_H_INCLUDED_ */
diff --git a/nginx/src/http/ngx_http_request_body.c b/nginx/src/http/ngx_http_request_body.c
new file mode 100644 (file)
index 0000000..c4f092e
--- /dev/null
@@ -0,0 +1,1169 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+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_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,
+    ngx_buf_t *b);
+static ngx_int_t ngx_http_test_expect(ngx_http_request_t *r);
+
+static ngx_int_t ngx_http_request_body_filter(ngx_http_request_t *r,
+    ngx_chain_t *in);
+static ngx_int_t ngx_http_request_body_length_filter(ngx_http_request_t *r,
+    ngx_chain_t *in);
+static ngx_int_t ngx_http_request_body_chunked_filter(ngx_http_request_t *r,
+    ngx_chain_t *in);
+
+
+ngx_int_t
+ngx_http_read_client_request_body(ngx_http_request_t *r,
+    ngx_http_client_body_handler_pt post_handler)
+{
+    size_t                     preread;
+    ssize_t                    size;
+    ngx_int_t                  rc;
+    ngx_buf_t                 *b;
+    ngx_chain_t                out;
+    ngx_http_request_body_t   *rb;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    r->main->count++;
+
+    if (r != r->main || r->request_body || r->discard_body) {
+        r->request_body_no_buffering = 0;
+        post_handler(r);
+        return NGX_OK;
+    }
+
+    if (ngx_http_test_expect(r) != NGX_OK) {
+        rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+        goto done;
+    }
+
+    rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t));
+    if (rb == NULL) {
+        rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+        goto done;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     rb->bufs = NULL;
+     *     rb->buf = NULL;
+     *     rb->free = NULL;
+     *     rb->busy = NULL;
+     *     rb->chunked = NULL;
+     */
+
+    rb->rest = -1;
+    rb->post_handler = post_handler;
+
+    r->request_body = rb;
+
+    if (r->headers_in.content_length_n < 0 && !r->headers_in.chunked) {
+        r->request_body_no_buffering = 0;
+        post_handler(r);
+        return NGX_OK;
+    }
+
+#if (NGX_HTTP_V2)
+    if (r->stream) {
+        rc = ngx_http_v2_read_request_body(r);
+        goto done;
+    }
+#endif
+
+    preread = r->header_in->last - r->header_in->pos;
+
+    if (preread) {
+
+        /* there is the pre-read part of the request body */
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http client request body preread %uz", preread);
+
+        out.buf = r->header_in;
+        out.next = NULL;
+
+        rc = ngx_http_request_body_filter(r, &out);
+
+        if (rc != NGX_OK) {
+            goto done;
+        }
+
+        r->request_length += preread - (r->header_in->last - r->header_in->pos);
+
+        if (!r->headers_in.chunked
+            && rb->rest > 0
+            && rb->rest <= (off_t) (r->header_in->end - r->header_in->last))
+        {
+            /* the whole request body may be placed in r->header_in */
+
+            b = ngx_calloc_buf(r->pool);
+            if (b == NULL) {
+                rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+                goto done;
+            }
+
+            b->temporary = 1;
+            b->start = r->header_in->pos;
+            b->pos = r->header_in->pos;
+            b->last = r->header_in->last;
+            b->end = r->header_in->end;
+
+            rb->buf = b;
+
+            r->read_event_handler = ngx_http_read_client_request_body_handler;
+            r->write_event_handler = ngx_http_request_empty_handler;
+
+            rc = ngx_http_do_read_client_request_body(r);
+            goto done;
+        }
+
+    } else {
+        /* set rb->rest */
+
+        if (ngx_http_request_body_filter(r, NULL) != NGX_OK) {
+            rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+            goto done;
+        }
+    }
+
+    if (rb->rest == 0) {
+        /* the whole request body was pre-read */
+        r->request_body_no_buffering = 0;
+        post_handler(r);
+        return NGX_OK;
+    }
+
+    if (rb->rest < 0) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                      "negative request body rest");
+        rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+        goto done;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    size = clcf->client_body_buffer_size;
+    size += size >> 2;
+
+    /* TODO: honor r->request_body_in_single_buf */
+
+    if (!r->headers_in.chunked && rb->rest < size) {
+        size = (ssize_t) rb->rest;
+
+        if (r->request_body_in_single_buf) {
+            size += preread;
+        }
+
+    } else {
+        size = clcf->client_body_buffer_size;
+    }
+
+    rb->buf = ngx_create_temp_buf(r->pool, size);
+    if (rb->buf == NULL) {
+        rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+        goto done;
+    }
+
+    r->read_event_handler = ngx_http_read_client_request_body_handler;
+    r->write_event_handler = ngx_http_request_empty_handler;
+
+    rc = ngx_http_do_read_client_request_body(r);
+
+done:
+
+    if (r->request_body_no_buffering
+        && (rc == NGX_OK || rc == NGX_AGAIN))
+    {
+        if (rc == NGX_OK) {
+            r->request_body_no_buffering = 0;
+
+        } else {
+            /* rc == NGX_AGAIN */
+            r->reading_body = 1;
+        }
+
+        r->read_event_handler = ngx_http_block_reading;
+        post_handler(r);
+    }
+
+    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+        r->main->count--;
+    }
+
+    return rc;
+}
+
+
+ngx_int_t
+ngx_http_read_unbuffered_request_body(ngx_http_request_t *r)
+{
+    ngx_int_t  rc;
+
+#if (NGX_HTTP_V2)
+    if (r->stream) {
+        rc = ngx_http_v2_read_unbuffered_request_body(r);
+
+        if (rc == NGX_OK) {
+            r->reading_body = 0;
+        }
+
+        return rc;
+    }
+#endif
+
+    if (r->connection->read->timedout) {
+        r->connection->timedout = 1;
+        return NGX_HTTP_REQUEST_TIME_OUT;
+    }
+
+    rc = ngx_http_do_read_client_request_body(r);
+
+    if (rc == NGX_OK) {
+        r->reading_body = 0;
+    }
+
+    return rc;
+}
+
+
+static void
+ngx_http_read_client_request_body_handler(ngx_http_request_t *r)
+{
+    ngx_int_t  rc;
+
+    if (r->connection->read->timedout) {
+        r->connection->timedout = 1;
+        ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT);
+        return;
+    }
+
+    rc = ngx_http_do_read_client_request_body(r);
+
+    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+        ngx_http_finalize_request(r, rc);
+    }
+}
+
+
+static ngx_int_t
+ngx_http_do_read_client_request_body(ngx_http_request_t *r)
+{
+    off_t                      rest;
+    size_t                     size;
+    ssize_t                    n;
+    ngx_int_t                  rc;
+    ngx_chain_t                out;
+    ngx_connection_t          *c;
+    ngx_http_request_body_t   *rb;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    c = r->connection;
+    rb = r->request_body;
+
+    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;
+                    }
+
+                } else {
+
+                    /* update chains */
+
+                    rc = ngx_http_request_body_filter(r, NULL);
+
+                    if (rc != NGX_OK) {
+                        return rc;
+                    }
+                }
+
+                if (rb->busy != NULL) {
+                    if (r->request_body_no_buffering) {
+                        if (c->read->timer_set) {
+                            ngx_del_timer(c->read);
+                        }
+
+                        if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+                            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+                        }
+
+                        return NGX_AGAIN;
+                    }
+
+                    return NGX_HTTP_INTERNAL_SERVER_ERROR;
+                }
+
+                rb->buf->pos = rb->buf->start;
+                rb->buf->last = rb->buf->start;
+            }
+
+            size = rb->buf->end - rb->buf->last;
+            rest = rb->rest - (rb->buf->last - rb->buf->pos);
+
+            if ((off_t) size > rest) {
+                size = (size_t) rest;
+            }
+
+            n = c->recv(c, rb->buf->last, size);
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                           "http client request body recv %z", n);
+
+            if (n == NGX_AGAIN) {
+                break;
+            }
+
+            if (n == 0) {
+                ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                              "client prematurely closed connection");
+            }
+
+            if (n == 0 || n == NGX_ERROR) {
+                c->error = 1;
+                return NGX_HTTP_BAD_REQUEST;
+            }
+
+            rb->buf->last += n;
+            r->request_length += n;
+
+            if (n == rest) {
+                /* 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;
+            }
+
+            if (rb->buf->last < rb->buf->end) {
+                break;
+            }
+        }
+
+        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 (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;
+
+                rc = ngx_http_request_body_filter(r, &out);
+
+                if (rc != NGX_OK) {
+                    return rc;
+                }
+            }
+
+            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;
+        }
+    }
+
+    if (c->read->timer_set) {
+        ngx_del_timer(c->read);
+    }
+
+    if (!r->request_body_no_buffering) {
+        r->read_event_handler = ngx_http_block_reading;
+        rb->post_handler(r);
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_write_request_body(ngx_http_request_t *r)
+{
+    ssize_t                    n;
+    ngx_chain_t               *cl, *ln;
+    ngx_temp_file_t           *tf;
+    ngx_http_request_body_t   *rb;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    rb = r->request_body;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http write client request body, bufs %p", rb->bufs);
+
+    if (rb->temp_file == NULL) {
+        tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t));
+        if (tf == NULL) {
+            return NGX_ERROR;
+        }
+
+        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+        tf->file.fd = NGX_INVALID_FILE;
+        tf->file.log = r->connection->log;
+        tf->path = clcf->client_body_temp_path;
+        tf->pool = r->pool;
+        tf->warn = "a client request body is buffered to a temporary file";
+        tf->log_level = r->request_body_file_log_level;
+        tf->persistent = r->request_body_in_persistent_file;
+        tf->clean = r->request_body_in_clean_file;
+
+        if (r->request_body_file_group_access) {
+            tf->access = 0660;
+        }
+
+        rb->temp_file = tf;
+
+        if (rb->bufs == NULL) {
+            /* empty body with r->request_body_in_file_only */
+
+            if (ngx_create_temp_file(&tf->file, tf->path, tf->pool,
+                                     tf->persistent, tf->clean, tf->access)
+                != NGX_OK)
+            {
+                return NGX_ERROR;
+            }
+
+            return NGX_OK;
+        }
+    }
+
+    if (rb->bufs == NULL) {
+        return NGX_OK;
+    }
+
+    n = ngx_write_chain_to_temp_file(rb->temp_file, rb->bufs);
+
+    /* TODO: n == 0 or not complete and level event */
+
+    if (n == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    rb->temp_file->offset += n;
+
+    /* mark all buffers as written */
+
+    for (cl = rb->bufs; cl; /* void */) {
+
+        cl->buf->pos = cl->buf->last;
+
+        ln = cl;
+        cl = cl->next;
+        ngx_free_chain(r->pool, ln);
+    }
+
+    rb->bufs = NULL;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_discard_request_body(ngx_http_request_t *r)
+{
+    ssize_t       size;
+    ngx_int_t     rc;
+    ngx_event_t  *rev;
+
+    if (r != r->main || r->discard_body || r->request_body) {
+        return NGX_OK;
+    }
+
+#if (NGX_HTTP_V2)
+    if (r->stream) {
+        r->stream->skip_data = 1;
+        return NGX_OK;
+    }
+#endif
+
+    if (ngx_http_test_expect(r) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    rev = r->connection->read;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http set discard body");
+
+    if (rev->timer_set) {
+        ngx_del_timer(rev);
+    }
+
+    if (r->headers_in.content_length_n <= 0 && !r->headers_in.chunked) {
+        return NGX_OK;
+    }
+
+    size = r->header_in->last - r->header_in->pos;
+
+    if (size || r->headers_in.chunked) {
+        rc = ngx_http_discard_request_body_filter(r, r->header_in);
+
+        if (rc != NGX_OK) {
+            return rc;
+        }
+
+        if (r->headers_in.content_length_n == 0) {
+            return NGX_OK;
+        }
+    }
+
+    rc = ngx_http_read_discarded_request_body(r);
+
+    if (rc == NGX_OK) {
+        r->lingering_close = 0;
+        return NGX_OK;
+    }
+
+    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+        return rc;
+    }
+
+    /* rc == NGX_AGAIN */
+
+    r->read_event_handler = ngx_http_discarded_request_body_handler;
+
+    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    r->count++;
+    r->discard_body = 1;
+
+    return NGX_OK;
+}
+
+
+void
+ngx_http_discarded_request_body_handler(ngx_http_request_t *r)
+{
+    ngx_int_t                  rc;
+    ngx_msec_t                 timer;
+    ngx_event_t               *rev;
+    ngx_connection_t          *c;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    c = r->connection;
+    rev = c->read;
+
+    if (rev->timedout) {
+        c->timedout = 1;
+        c->error = 1;
+        ngx_http_finalize_request(r, NGX_ERROR);
+        return;
+    }
+
+    if (r->lingering_time) {
+        timer = (ngx_msec_t) r->lingering_time - (ngx_msec_t) ngx_time();
+
+        if ((ngx_msec_int_t) timer <= 0) {
+            r->discard_body = 0;
+            r->lingering_close = 0;
+            ngx_http_finalize_request(r, NGX_ERROR);
+            return;
+        }
+
+    } else {
+        timer = 0;
+    }
+
+    rc = ngx_http_read_discarded_request_body(r);
+
+    if (rc == NGX_OK) {
+        r->discard_body = 0;
+        r->lingering_close = 0;
+        ngx_http_finalize_request(r, NGX_DONE);
+        return;
+    }
+
+    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+        c->error = 1;
+        ngx_http_finalize_request(r, NGX_ERROR);
+        return;
+    }
+
+    /* rc == NGX_AGAIN */
+
+    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+        c->error = 1;
+        ngx_http_finalize_request(r, NGX_ERROR);
+        return;
+    }
+
+    if (timer) {
+
+        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+        timer *= 1000;
+
+        if (timer > clcf->lingering_timeout) {
+            timer = clcf->lingering_timeout;
+        }
+
+        ngx_add_timer(rev, timer);
+    }
+}
+
+
+static ngx_int_t
+ngx_http_read_discarded_request_body(ngx_http_request_t *r)
+{
+    size_t     size;
+    ssize_t    n;
+    ngx_int_t  rc;
+    ngx_buf_t  b;
+    u_char     buffer[NGX_HTTP_DISCARD_BUFFER_SIZE];
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http read discarded body");
+
+    ngx_memzero(&b, sizeof(ngx_buf_t));
+
+    b.temporary = 1;
+
+    for ( ;; ) {
+        if (r->headers_in.content_length_n == 0) {
+            r->read_event_handler = ngx_http_block_reading;
+            return NGX_OK;
+        }
+
+        if (!r->connection->read->ready) {
+            return NGX_AGAIN;
+        }
+
+        size = (size_t) ngx_min(r->headers_in.content_length_n,
+                                NGX_HTTP_DISCARD_BUFFER_SIZE);
+
+        n = r->connection->recv(r->connection, buffer, size);
+
+        if (n == NGX_ERROR) {
+            r->connection->error = 1;
+            return NGX_OK;
+        }
+
+        if (n == NGX_AGAIN) {
+            return NGX_AGAIN;
+        }
+
+        if (n == 0) {
+            return NGX_OK;
+        }
+
+        b.pos = buffer;
+        b.last = buffer + n;
+
+        rc = ngx_http_discard_request_body_filter(r, &b);
+
+        if (rc != NGX_OK) {
+            return rc;
+        }
+    }
+}
+
+
+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;
+
+    if (r->headers_in.chunked) {
+
+        rb = r->request_body;
+
+        if (rb == NULL) {
+
+            rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t));
+            if (rb == NULL) {
+                return NGX_HTTP_INTERNAL_SERVER_ERROR;
+            }
+
+            rb->chunked = ngx_pcalloc(r->pool, sizeof(ngx_http_chunked_t));
+            if (rb->chunked == NULL) {
+                return NGX_HTTP_INTERNAL_SERVER_ERROR;
+            }
+
+            r->request_body = rb;
+        }
+
+        for ( ;; ) {
+
+            rc = ngx_http_parse_chunked(r, b, rb->chunked);
+
+            if (rc == NGX_OK) {
+
+                /* a chunk has been parsed successfully */
+
+                size = b->last - b->pos;
+
+                if ((off_t) size > rb->chunked->size) {
+                    b->pos += (size_t) rb->chunked->size;
+                    rb->chunked->size = 0;
+
+                } else {
+                    rb->chunked->size -= size;
+                    b->pos = b->last;
+                }
+
+                continue;
+            }
+
+            if (rc == NGX_DONE) {
+
+                /* a whole response has been parsed successfully */
+
+                r->headers_in.content_length_n = 0;
+                break;
+            }
+
+            if (rc == NGX_AGAIN) {
+
+                /* set amount of data we want to see next time */
+
+                r->headers_in.content_length_n = rb->chunked->length;
+                break;
+            }
+
+            /* invalid */
+
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "client sent invalid chunked body");
+
+            return NGX_HTTP_BAD_REQUEST;
+        }
+
+    } else {
+        size = b->last - b->pos;
+
+        if ((off_t) size > r->headers_in.content_length_n) {
+            b->pos += (size_t) r->headers_in.content_length_n;
+            r->headers_in.content_length_n = 0;
+
+        } else {
+            b->pos = b->last;
+            r->headers_in.content_length_n -= size;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_test_expect(ngx_http_request_t *r)
+{
+    ngx_int_t   n;
+    ngx_str_t  *expect;
+
+    if (r->expect_tested
+        || r->headers_in.expect == NULL
+        || r->http_version < NGX_HTTP_VERSION_11
+#if (NGX_HTTP_V2)
+        || r->stream != NULL
+#endif
+       )
+    {
+        return NGX_OK;
+    }
+
+    r->expect_tested = 1;
+
+    expect = &r->headers_in.expect->value;
+
+    if (expect->len != sizeof("100-continue") - 1
+        || ngx_strncasecmp(expect->data, (u_char *) "100-continue",
+                           sizeof("100-continue") - 1)
+           != 0)
+    {
+        return NGX_OK;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "send 100 Continue");
+
+    n = r->connection->send(r->connection,
+                            (u_char *) "HTTP/1.1 100 Continue" CRLF CRLF,
+                            sizeof("HTTP/1.1 100 Continue" CRLF CRLF) - 1);
+
+    if (n == sizeof("HTTP/1.1 100 Continue" CRLF CRLF) - 1) {
+        return NGX_OK;
+    }
+
+    /* we assume that such small packet should be send successfully */
+
+    r->connection->error = 1;
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    if (r->headers_in.chunked) {
+        return ngx_http_request_body_chunked_filter(r, in);
+
+    } else {
+        return ngx_http_request_body_length_filter(r, in);
+    }
+}
+
+
+static ngx_int_t
+ngx_http_request_body_length_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    size_t                     size;
+    ngx_int_t                  rc;
+    ngx_buf_t                 *b;
+    ngx_chain_t               *cl, *tl, *out, **ll;
+    ngx_http_request_body_t   *rb;
+
+    rb = r->request_body;
+
+    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;
+
+    for (cl = in; cl; cl = cl->next) {
+
+        if (rb->rest == 0) {
+            break;
+        }
+
+        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->temporary = 1;
+        b->tag = (ngx_buf_tag_t) &ngx_http_read_client_request_body;
+        b->start = cl->buf->pos;
+        b->pos = cl->buf->pos;
+        b->last = cl->buf->last;
+        b->end = cl->buf->end;
+        b->flush = r->request_body_no_buffering;
+
+        size = cl->buf->last - cl->buf->pos;
+
+        if ((off_t) size < rb->rest) {
+            cl->buf->pos = cl->buf->last;
+            rb->rest -= size;
+
+        } else {
+            cl->buf->pos += (size_t) rb->rest;
+            rb->rest = 0;
+            b->last = cl->buf->pos;
+            b->last_buf = 1;
+        }
+
+        *ll = tl;
+        ll = &tl->next;
+    }
+
+    rc = ngx_http_top_request_body_filter(r, out);
+
+    ngx_chain_update_chains(r->pool, &rb->free, &rb->busy, &out,
+                            (ngx_buf_tag_t) &ngx_http_read_client_request_body);
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_http_request_body_chunked_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    size_t                     size;
+    ngx_int_t                  rc;
+    ngx_buf_t                 *b;
+    ngx_chain_t               *cl, *out, *tl, **ll;
+    ngx_http_request_body_t   *rb;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    rb = r->request_body;
+
+    if (rb->rest == -1) {
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http request body chunked filter");
+
+        rb->chunked = ngx_pcalloc(r->pool, sizeof(ngx_http_chunked_t));
+        if (rb->chunked == NULL) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        r->headers_in.content_length_n = 0;
+        rb->rest = 3;
+    }
+
+    out = NULL;
+    ll = &out;
+
+    for (cl = in; cl; cl = cl->next) {
+
+        for ( ;; ) {
+
+            ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0,
+                           "http body chunked buf "
+                           "t:%d f:%d %p, pos %p, size: %z file: %O, size: %O",
+                           cl->buf->temporary, cl->buf->in_file,
+                           cl->buf->start, cl->buf->pos,
+                           cl->buf->last - cl->buf->pos,
+                           cl->buf->file_pos,
+                           cl->buf->file_last - cl->buf->file_pos);
+
+            rc = ngx_http_parse_chunked(r, cl->buf, rb->chunked);
+
+            if (rc == NGX_OK) {
+
+                /* a chunk has been parsed successfully */
+
+                clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+                if (clcf->client_max_body_size
+                    && clcf->client_max_body_size
+                       - r->headers_in.content_length_n < rb->chunked->size)
+                {
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                                  "client intended to send too large chunked "
+                                  "body: %O+%O bytes",
+                                  r->headers_in.content_length_n,
+                                  rb->chunked->size);
+
+                    r->lingering_close = 1;
+
+                    return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE;
+                }
+
+                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->temporary = 1;
+                b->tag = (ngx_buf_tag_t) &ngx_http_read_client_request_body;
+                b->start = cl->buf->pos;
+                b->pos = cl->buf->pos;
+                b->last = cl->buf->last;
+                b->end = cl->buf->end;
+                b->flush = r->request_body_no_buffering;
+
+                *ll = tl;
+                ll = &tl->next;
+
+                size = cl->buf->last - cl->buf->pos;
+
+                if ((off_t) size > rb->chunked->size) {
+                    cl->buf->pos += (size_t) rb->chunked->size;
+                    r->headers_in.content_length_n += rb->chunked->size;
+                    rb->chunked->size = 0;
+
+                } else {
+                    rb->chunked->size -= size;
+                    r->headers_in.content_length_n += size;
+                    cl->buf->pos = cl->buf->last;
+                }
+
+                b->last = cl->buf->pos;
+
+                continue;
+            }
+
+            if (rc == NGX_DONE) {
+
+                /* a whole response has been parsed successfully */
+
+                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;
+
+                break;
+            }
+
+            if (rc == NGX_AGAIN) {
+
+                /* set rb->rest, amount of data we want to see next time */
+
+                rb->rest = rb->chunked->length;
+
+                break;
+            }
+
+            /* invalid */
+
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "client sent invalid chunked body");
+
+            return NGX_HTTP_BAD_REQUEST;
+        }
+    }
+
+    rc = ngx_http_top_request_body_filter(r, out);
+
+    ngx_chain_update_chains(r->pool, &rb->free, &rb->busy, &out,
+                            (ngx_buf_tag_t) &ngx_http_read_client_request_body);
+
+    return rc;
+}
+
+
+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_http_request_body_t   *rb;
+
+    rb = r->request_body;
+
+#if (NGX_DEBUG)
+
+#if 0
+    for (cl = rb->bufs; cl; cl = cl->next) {
+        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",
+                       cl->buf->temporary, cl->buf->in_file,
+                       cl->buf->start, cl->buf->pos,
+                       cl->buf->last - cl->buf->pos,
+                       cl->buf->file_pos,
+                       cl->buf->file_last - cl->buf->file_pos);
+    }
+#endif
+
+    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",
+                       cl->buf->temporary, cl->buf->in_file,
+                       cl->buf->start, cl->buf->pos,
+                       cl->buf->last - cl->buf->pos,
+                       cl->buf->file_pos,
+                       cl->buf->file_last - cl->buf->file_pos);
+    }
+
+#endif
+
+    /* TODO: coalesce neighbouring buffers */
+
+    if (ngx_chain_add_copy(r->pool, &rb->bufs, in) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (r->request_body_no_buffering) {
+        return NGX_OK;
+    }
+
+    if (rb->rest > 0) {
+
+        if (rb->buf && rb->buf->last == rb->buf->end
+            && ngx_http_write_request_body(r) != NGX_OK)
+        {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        return NGX_OK;
+    }
+
+    /* rb->rest == 0 */
+
+    if (rb->temp_file || r->request_body_in_file_only) {
+
+        if (ngx_http_write_request_body(r) != NGX_OK) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        if (rb->temp_file->file.offset != 0) {
+
+            cl = ngx_chain_get_free_buf(r->pool, &rb->free);
+            if (cl == NULL) {
+                return NGX_HTTP_INTERNAL_SERVER_ERROR;
+            }
+
+            b = cl->buf;
+
+            ngx_memzero(b, sizeof(ngx_buf_t));
+
+            b->in_file = 1;
+            b->file_last = rb->temp_file->file.offset;
+            b->file = &rb->temp_file->file;
+
+            rb->bufs = cl;
+        }
+    }
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/ngx_http_script.c b/nginx/src/http/ngx_http_script.c
new file mode 100644 (file)
index 0000000..1a87735
--- /dev/null
@@ -0,0 +1,1765 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_script_init_arrays(ngx_http_script_compile_t *sc);
+static ngx_int_t ngx_http_script_done(ngx_http_script_compile_t *sc);
+static ngx_int_t ngx_http_script_add_copy_code(ngx_http_script_compile_t *sc,
+    ngx_str_t *value, ngx_uint_t last);
+static ngx_int_t ngx_http_script_add_var_code(ngx_http_script_compile_t *sc,
+    ngx_str_t *name);
+static ngx_int_t ngx_http_script_add_args_code(ngx_http_script_compile_t *sc);
+#if (NGX_PCRE)
+static ngx_int_t ngx_http_script_add_capture_code(ngx_http_script_compile_t *sc,
+    ngx_uint_t n);
+#endif
+static ngx_int_t
+    ngx_http_script_add_full_name_code(ngx_http_script_compile_t *sc);
+static size_t ngx_http_script_full_name_len_code(ngx_http_script_engine_t *e);
+static void ngx_http_script_full_name_code(ngx_http_script_engine_t *e);
+
+
+#define ngx_http_script_exit  (u_char *) &ngx_http_script_exit_code
+
+static uintptr_t ngx_http_script_exit_code = (uintptr_t) NULL;
+
+
+void
+ngx_http_script_flush_complex_value(ngx_http_request_t *r,
+    ngx_http_complex_value_t *val)
+{
+    ngx_uint_t *index;
+
+    index = val->flushes;
+
+    if (index) {
+        while (*index != (ngx_uint_t) -1) {
+
+            if (r->variables[*index].no_cacheable) {
+                r->variables[*index].valid = 0;
+                r->variables[*index].not_found = 0;
+            }
+
+            index++;
+        }
+    }
+}
+
+
+ngx_int_t
+ngx_http_complex_value(ngx_http_request_t *r, ngx_http_complex_value_t *val,
+    ngx_str_t *value)
+{
+    size_t                        len;
+    ngx_http_script_code_pt       code;
+    ngx_http_script_len_code_pt   lcode;
+    ngx_http_script_engine_t      e;
+
+    if (val->lengths == NULL) {
+        *value = val->value;
+        return NGX_OK;
+    }
+
+    ngx_http_script_flush_complex_value(r, val);
+
+    ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
+
+    e.ip = val->lengths;
+    e.request = r;
+    e.flushed = 1;
+
+    len = 0;
+
+    while (*(uintptr_t *) e.ip) {
+        lcode = *(ngx_http_script_len_code_pt *) e.ip;
+        len += lcode(&e);
+    }
+
+    value->len = len;
+    value->data = ngx_pnalloc(r->pool, len);
+    if (value->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    e.ip = val->values;
+    e.pos = value->data;
+    e.buf = *value;
+
+    while (*(uintptr_t *) e.ip) {
+        code = *(ngx_http_script_code_pt *) e.ip;
+        code((ngx_http_script_engine_t *) &e);
+    }
+
+    *value = e.buf;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_compile_complex_value(ngx_http_compile_complex_value_t *ccv)
+{
+    ngx_str_t                  *v;
+    ngx_uint_t                  i, n, nv, nc;
+    ngx_array_t                 flushes, lengths, values, *pf, *pl, *pv;
+    ngx_http_script_compile_t   sc;
+
+    v = ccv->value;
+
+    nv = 0;
+    nc = 0;
+
+    for (i = 0; i < v->len; i++) {
+        if (v->data[i] == '$') {
+            if (v->data[i + 1] >= '1' && v->data[i + 1] <= '9') {
+                nc++;
+
+            } else {
+                nv++;
+            }
+        }
+    }
+
+    if ((v->len == 0 || v->data[0] != '$')
+        && (ccv->conf_prefix || ccv->root_prefix))
+    {
+        if (ngx_conf_full_name(ccv->cf->cycle, v, ccv->conf_prefix) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        ccv->conf_prefix = 0;
+        ccv->root_prefix = 0;
+    }
+
+    ccv->complex_value->value = *v;
+    ccv->complex_value->flushes = NULL;
+    ccv->complex_value->lengths = NULL;
+    ccv->complex_value->values = NULL;
+
+    if (nv == 0 && nc == 0) {
+        return NGX_OK;
+    }
+
+    n = nv + 1;
+
+    if (ngx_array_init(&flushes, ccv->cf->pool, n, sizeof(ngx_uint_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    n = nv * (2 * sizeof(ngx_http_script_copy_code_t)
+                  + sizeof(ngx_http_script_var_code_t))
+        + sizeof(uintptr_t);
+
+    if (ngx_array_init(&lengths, ccv->cf->pool, n, 1) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    n = (nv * (2 * sizeof(ngx_http_script_copy_code_t)
+                   + sizeof(ngx_http_script_var_code_t))
+                + sizeof(uintptr_t)
+                + v->len
+                + sizeof(uintptr_t) - 1)
+            & ~(sizeof(uintptr_t) - 1);
+
+    if (ngx_array_init(&values, ccv->cf->pool, n, 1) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    pf = &flushes;
+    pl = &lengths;
+    pv = &values;
+
+    ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+    sc.cf = ccv->cf;
+    sc.source = v;
+    sc.flushes = &pf;
+    sc.lengths = &pl;
+    sc.values = &pv;
+    sc.complete_lengths = 1;
+    sc.complete_values = 1;
+    sc.zero = ccv->zero;
+    sc.conf_prefix = ccv->conf_prefix;
+    sc.root_prefix = ccv->root_prefix;
+
+    if (ngx_http_script_compile(&sc) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (flushes.nelts) {
+        ccv->complex_value->flushes = flushes.elts;
+        ccv->complex_value->flushes[flushes.nelts] = (ngx_uint_t) -1;
+    }
+
+    ccv->complex_value->lengths = lengths.elts;
+    ccv->complex_value->values = values.elts;
+
+    return NGX_OK;
+}
+
+
+char *
+ngx_http_set_complex_value_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 != NULL) {
+        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;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+ngx_int_t
+ngx_http_test_predicates(ngx_http_request_t *r, ngx_array_t *predicates)
+{
+    ngx_str_t                  val;
+    ngx_uint_t                 i;
+    ngx_http_complex_value_t  *cv;
+
+    if (predicates == NULL) {
+        return NGX_OK;
+    }
+
+    cv = predicates->elts;
+
+    for (i = 0; i < predicates->nelts; i++) {
+        if (ngx_http_complex_value(r, &cv[i], &val) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        if (val.len && (val.len != 1 || val.data[0] != '0')) {
+            return NGX_DECLINED;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+char *
+ngx_http_set_predicate_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ngx_str_t                          *value;
+    ngx_uint_t                          i;
+    ngx_array_t                       **a;
+    ngx_http_complex_value_t           *cv;
+    ngx_http_compile_complex_value_t    ccv;
+
+    a = (ngx_array_t **) (p + cmd->offset);
+
+    if (*a == NGX_CONF_UNSET_PTR) {
+        *a = ngx_array_create(cf->pool, 1, sizeof(ngx_http_complex_value_t));
+        if (*a == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    value = cf->args->elts;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+        cv = ngx_array_push(*a);
+        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;
+}
+
+
+ngx_uint_t
+ngx_http_script_variables_count(ngx_str_t *value)
+{
+    ngx_uint_t  i, n;
+
+    for (n = 0, i = 0; i < value->len; i++) {
+        if (value->data[i] == '$') {
+            n++;
+        }
+    }
+
+    return n;
+}
+
+
+ngx_int_t
+ngx_http_script_compile(ngx_http_script_compile_t *sc)
+{
+    u_char       ch;
+    ngx_str_t    name;
+    ngx_uint_t   i, bracket;
+
+    if (ngx_http_script_init_arrays(sc) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    for (i = 0; i < sc->source->len; /* void */ ) {
+
+        name.len = 0;
+
+        if (sc->source->data[i] == '$') {
+
+            if (++i == sc->source->len) {
+                goto invalid_variable;
+            }
+
+            if (sc->source->data[i] >= '1' && sc->source->data[i] <= '9') {
+#if (NGX_PCRE)
+                ngx_uint_t  n;
+
+                n = sc->source->data[i] - '0';
+
+                if (sc->captures_mask & ((ngx_uint_t) 1 << n)) {
+                    sc->dup_capture = 1;
+                }
+
+                sc->captures_mask |= (ngx_uint_t) 1 << n;
+
+                if (ngx_http_script_add_capture_code(sc, n) != NGX_OK) {
+                    return NGX_ERROR;
+                }
+
+                i++;
+
+                continue;
+#else
+                ngx_conf_log_error(NGX_LOG_EMERG, sc->cf, 0,
+                                   "using variable \"$%c\" requires "
+                                   "PCRE library", sc->source->data[i]);
+                return NGX_ERROR;
+#endif
+            }
+
+            if (sc->source->data[i] == '{') {
+                bracket = 1;
+
+                if (++i == sc->source->len) {
+                    goto invalid_variable;
+                }
+
+                name.data = &sc->source->data[i];
+
+            } else {
+                bracket = 0;
+                name.data = &sc->source->data[i];
+            }
+
+            for ( /* void */ ; i < sc->source->len; i++, name.len++) {
+                ch = sc->source->data[i];
+
+                if (ch == '}' && bracket) {
+                    i++;
+                    bracket = 0;
+                    break;
+                }
+
+                if ((ch >= 'A' && ch <= 'Z')
+                    || (ch >= 'a' && ch <= 'z')
+                    || (ch >= '0' && ch <= '9')
+                    || ch == '_')
+                {
+                    continue;
+                }
+
+                break;
+            }
+
+            if (bracket) {
+                ngx_conf_log_error(NGX_LOG_EMERG, sc->cf, 0,
+                                   "the closing bracket in \"%V\" "
+                                   "variable is missing", &name);
+                return NGX_ERROR;
+            }
+
+            if (name.len == 0) {
+                goto invalid_variable;
+            }
+
+            sc->variables++;
+
+            if (ngx_http_script_add_var_code(sc, &name) != NGX_OK) {
+                return NGX_ERROR;
+            }
+
+            continue;
+        }
+
+        if (sc->source->data[i] == '?' && sc->compile_args) {
+            sc->args = 1;
+            sc->compile_args = 0;
+
+            if (ngx_http_script_add_args_code(sc) != NGX_OK) {
+                return NGX_ERROR;
+            }
+
+            i++;
+
+            continue;
+        }
+
+        name.data = &sc->source->data[i];
+
+        while (i < sc->source->len) {
+
+            if (sc->source->data[i] == '$') {
+                break;
+            }
+
+            if (sc->source->data[i] == '?') {
+
+                sc->args = 1;
+
+                if (sc->compile_args) {
+                    break;
+                }
+            }
+
+            i++;
+            name.len++;
+        }
+
+        sc->size += name.len;
+
+        if (ngx_http_script_add_copy_code(sc, &name, (i == sc->source->len))
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    return ngx_http_script_done(sc);
+
+invalid_variable:
+
+    ngx_conf_log_error(NGX_LOG_EMERG, sc->cf, 0, "invalid variable name");
+
+    return NGX_ERROR;
+}
+
+
+u_char *
+ngx_http_script_run(ngx_http_request_t *r, ngx_str_t *value,
+    void *code_lengths, size_t len, void *code_values)
+{
+    ngx_uint_t                    i;
+    ngx_http_script_code_pt       code;
+    ngx_http_script_len_code_pt   lcode;
+    ngx_http_script_engine_t      e;
+    ngx_http_core_main_conf_t    *cmcf;
+
+    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+    for (i = 0; i < cmcf->variables.nelts; i++) {
+        if (r->variables[i].no_cacheable) {
+            r->variables[i].valid = 0;
+            r->variables[i].not_found = 0;
+        }
+    }
+
+    ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
+
+    e.ip = code_lengths;
+    e.request = r;
+    e.flushed = 1;
+
+    while (*(uintptr_t *) e.ip) {
+        lcode = *(ngx_http_script_len_code_pt *) e.ip;
+        len += lcode(&e);
+    }
+
+
+    value->len = len;
+    value->data = ngx_pnalloc(r->pool, len);
+    if (value->data == NULL) {
+        return NULL;
+    }
+
+    e.ip = code_values;
+    e.pos = value->data;
+
+    while (*(uintptr_t *) e.ip) {
+        code = *(ngx_http_script_code_pt *) e.ip;
+        code((ngx_http_script_engine_t *) &e);
+    }
+
+    return e.pos;
+}
+
+
+void
+ngx_http_script_flush_no_cacheable_variables(ngx_http_request_t *r,
+    ngx_array_t *indices)
+{
+    ngx_uint_t  n, *index;
+
+    if (indices) {
+        index = indices->elts;
+        for (n = 0; n < indices->nelts; n++) {
+            if (r->variables[index[n]].no_cacheable) {
+                r->variables[index[n]].valid = 0;
+                r->variables[index[n]].not_found = 0;
+            }
+        }
+    }
+}
+
+
+static ngx_int_t
+ngx_http_script_init_arrays(ngx_http_script_compile_t *sc)
+{
+    ngx_uint_t   n;
+
+    if (sc->flushes && *sc->flushes == NULL) {
+        n = sc->variables ? sc->variables : 1;
+        *sc->flushes = ngx_array_create(sc->cf->pool, n, sizeof(ngx_uint_t));
+        if (*sc->flushes == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    if (*sc->lengths == NULL) {
+        n = sc->variables * (2 * sizeof(ngx_http_script_copy_code_t)
+                             + sizeof(ngx_http_script_var_code_t))
+            + sizeof(uintptr_t);
+
+        *sc->lengths = ngx_array_create(sc->cf->pool, n, 1);
+        if (*sc->lengths == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    if (*sc->values == NULL) {
+        n = (sc->variables * (2 * sizeof(ngx_http_script_copy_code_t)
+                              + sizeof(ngx_http_script_var_code_t))
+                + sizeof(uintptr_t)
+                + sc->source->len
+                + sizeof(uintptr_t) - 1)
+            & ~(sizeof(uintptr_t) - 1);
+
+        *sc->values = ngx_array_create(sc->cf->pool, n, 1);
+        if (*sc->values == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    sc->variables = 0;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_script_done(ngx_http_script_compile_t *sc)
+{
+    ngx_str_t    zero;
+    uintptr_t   *code;
+
+    if (sc->zero) {
+
+        zero.len = 1;
+        zero.data = (u_char *) "\0";
+
+        if (ngx_http_script_add_copy_code(sc, &zero, 0) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    if (sc->conf_prefix || sc->root_prefix) {
+        if (ngx_http_script_add_full_name_code(sc) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    if (sc->complete_lengths) {
+        code = ngx_http_script_add_code(*sc->lengths, sizeof(uintptr_t), NULL);
+        if (code == NULL) {
+            return NGX_ERROR;
+        }
+
+        *code = (uintptr_t) NULL;
+    }
+
+    if (sc->complete_values) {
+        code = ngx_http_script_add_code(*sc->values, sizeof(uintptr_t),
+                                        &sc->main);
+        if (code == NULL) {
+            return NGX_ERROR;
+        }
+
+        *code = (uintptr_t) NULL;
+    }
+
+    return NGX_OK;
+}
+
+
+void *
+ngx_http_script_start_code(ngx_pool_t *pool, ngx_array_t **codes, size_t size)
+{
+    if (*codes == NULL) {
+        *codes = ngx_array_create(pool, 256, 1);
+        if (*codes == NULL) {
+            return NULL;
+        }
+    }
+
+    return ngx_array_push_n(*codes, size);
+}
+
+
+void *
+ngx_http_script_add_code(ngx_array_t *codes, size_t size, void *code)
+{
+    u_char  *elts, **p;
+    void    *new;
+
+    elts = codes->elts;
+
+    new = ngx_array_push_n(codes, size);
+    if (new == NULL) {
+        return NULL;
+    }
+
+    if (code) {
+        if (elts != codes->elts) {
+            p = code;
+            *p += (u_char *) codes->elts - elts;
+        }
+    }
+
+    return new;
+}
+
+
+static ngx_int_t
+ngx_http_script_add_copy_code(ngx_http_script_compile_t *sc, ngx_str_t *value,
+    ngx_uint_t last)
+{
+    u_char                       *p;
+    size_t                        size, len, zero;
+    ngx_http_script_copy_code_t  *code;
+
+    zero = (sc->zero && last);
+    len = value->len + zero;
+
+    code = ngx_http_script_add_code(*sc->lengths,
+                                    sizeof(ngx_http_script_copy_code_t), NULL);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = (ngx_http_script_code_pt) (void *)
+                                                 ngx_http_script_copy_len_code;
+    code->len = len;
+
+    size = (sizeof(ngx_http_script_copy_code_t) + len + sizeof(uintptr_t) - 1)
+            & ~(sizeof(uintptr_t) - 1);
+
+    code = ngx_http_script_add_code(*sc->values, size, &sc->main);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = ngx_http_script_copy_code;
+    code->len = len;
+
+    p = ngx_cpymem((u_char *) code + sizeof(ngx_http_script_copy_code_t),
+                   value->data, value->len);
+
+    if (zero) {
+        *p = '\0';
+        sc->zero = 0;
+    }
+
+    return NGX_OK;
+}
+
+
+size_t
+ngx_http_script_copy_len_code(ngx_http_script_engine_t *e)
+{
+    ngx_http_script_copy_code_t  *code;
+
+    code = (ngx_http_script_copy_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_http_script_copy_code_t);
+
+    return code->len;
+}
+
+
+void
+ngx_http_script_copy_code(ngx_http_script_engine_t *e)
+{
+    u_char                       *p;
+    ngx_http_script_copy_code_t  *code;
+
+    code = (ngx_http_script_copy_code_t *) e->ip;
+
+    p = e->pos;
+
+    if (!e->skip) {
+        e->pos = ngx_copy(p, e->ip + sizeof(ngx_http_script_copy_code_t),
+                          code->len);
+    }
+
+    e->ip += sizeof(ngx_http_script_copy_code_t)
+          + ((code->len + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1));
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+                   "http script copy: \"%*s\"", e->pos - p, p);
+}
+
+
+static ngx_int_t
+ngx_http_script_add_var_code(ngx_http_script_compile_t *sc, ngx_str_t *name)
+{
+    ngx_int_t                    index, *p;
+    ngx_http_script_var_code_t  *code;
+
+    index = ngx_http_get_variable_index(sc->cf, name);
+
+    if (index == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if (sc->flushes) {
+        p = ngx_array_push(*sc->flushes);
+        if (p == NULL) {
+            return NGX_ERROR;
+        }
+
+        *p = index;
+    }
+
+    code = ngx_http_script_add_code(*sc->lengths,
+                                    sizeof(ngx_http_script_var_code_t), NULL);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = (ngx_http_script_code_pt) (void *)
+                                             ngx_http_script_copy_var_len_code;
+    code->index = (uintptr_t) index;
+
+    code = ngx_http_script_add_code(*sc->values,
+                                    sizeof(ngx_http_script_var_code_t),
+                                    &sc->main);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = ngx_http_script_copy_var_code;
+    code->index = (uintptr_t) index;
+
+    return NGX_OK;
+}
+
+
+size_t
+ngx_http_script_copy_var_len_code(ngx_http_script_engine_t *e)
+{
+    ngx_http_variable_value_t   *value;
+    ngx_http_script_var_code_t  *code;
+
+    code = (ngx_http_script_var_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_http_script_var_code_t);
+
+    if (e->flushed) {
+        value = ngx_http_get_indexed_variable(e->request, code->index);
+
+    } else {
+        value = ngx_http_get_flushed_variable(e->request, code->index);
+    }
+
+    if (value && !value->not_found) {
+        return value->len;
+    }
+
+    return 0;
+}
+
+
+void
+ngx_http_script_copy_var_code(ngx_http_script_engine_t *e)
+{
+    u_char                      *p;
+    ngx_http_variable_value_t   *value;
+    ngx_http_script_var_code_t  *code;
+
+    code = (ngx_http_script_var_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_http_script_var_code_t);
+
+    if (!e->skip) {
+
+        if (e->flushed) {
+            value = ngx_http_get_indexed_variable(e->request, code->index);
+
+        } else {
+            value = ngx_http_get_flushed_variable(e->request, code->index);
+        }
+
+        if (value && !value->not_found) {
+            p = e->pos;
+            e->pos = ngx_copy(p, value->data, value->len);
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP,
+                           e->request->connection->log, 0,
+                           "http script var: \"%*s\"", e->pos - p, p);
+        }
+    }
+}
+
+
+static ngx_int_t
+ngx_http_script_add_args_code(ngx_http_script_compile_t *sc)
+{
+    uintptr_t   *code;
+
+    code = ngx_http_script_add_code(*sc->lengths, sizeof(uintptr_t), NULL);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    *code = (uintptr_t) ngx_http_script_mark_args_code;
+
+    code = ngx_http_script_add_code(*sc->values, sizeof(uintptr_t), &sc->main);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    *code = (uintptr_t) ngx_http_script_start_args_code;
+
+    return NGX_OK;
+}
+
+
+size_t
+ngx_http_script_mark_args_code(ngx_http_script_engine_t *e)
+{
+    e->is_args = 1;
+    e->ip += sizeof(uintptr_t);
+
+    return 1;
+}
+
+
+void
+ngx_http_script_start_args_code(ngx_http_script_engine_t *e)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+                   "http script args");
+
+    e->is_args = 1;
+    e->args = e->pos;
+    e->ip += sizeof(uintptr_t);
+}
+
+
+#if (NGX_PCRE)
+
+void
+ngx_http_script_regex_start_code(ngx_http_script_engine_t *e)
+{
+    size_t                         len;
+    ngx_int_t                      rc;
+    ngx_uint_t                     n;
+    ngx_http_request_t            *r;
+    ngx_http_script_engine_t       le;
+    ngx_http_script_len_code_pt    lcode;
+    ngx_http_script_regex_code_t  *code;
+
+    code = (ngx_http_script_regex_code_t *) e->ip;
+
+    r = e->request;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http script regex: \"%V\"", &code->name);
+
+    if (code->uri) {
+        e->line = r->uri;
+    } else {
+        e->sp--;
+        e->line.len = e->sp->len;
+        e->line.data = e->sp->data;
+    }
+
+    rc = ngx_http_regex_exec(r, code->regex, &e->line);
+
+    if (rc == NGX_DECLINED) {
+        if (e->log || (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP)) {
+            ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
+                          "\"%V\" does not match \"%V\"",
+                          &code->name, &e->line);
+        }
+
+        r->ncaptures = 0;
+
+        if (code->test) {
+            if (code->negative_test) {
+                e->sp->len = 1;
+                e->sp->data = (u_char *) "1";
+
+            } else {
+                e->sp->len = 0;
+                e->sp->data = (u_char *) "";
+            }
+
+            e->sp++;
+
+            e->ip += sizeof(ngx_http_script_regex_code_t);
+            return;
+        }
+
+        e->ip += code->next;
+        return;
+    }
+
+    if (rc == NGX_ERROR) {
+        e->ip = ngx_http_script_exit;
+        e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+        return;
+    }
+
+    if (e->log || (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP)) {
+        ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
+                      "\"%V\" matches \"%V\"", &code->name, &e->line);
+    }
+
+    if (code->test) {
+        if (code->negative_test) {
+            e->sp->len = 0;
+            e->sp->data = (u_char *) "";
+
+        } else {
+            e->sp->len = 1;
+            e->sp->data = (u_char *) "1";
+        }
+
+        e->sp++;
+
+        e->ip += sizeof(ngx_http_script_regex_code_t);
+        return;
+    }
+
+    if (code->status) {
+        e->status = code->status;
+
+        if (!code->redirect) {
+            e->ip = ngx_http_script_exit;
+            return;
+        }
+    }
+
+    if (code->uri) {
+        r->internal = 1;
+        r->valid_unparsed_uri = 0;
+
+        if (code->break_cycle) {
+            r->valid_location = 0;
+            r->uri_changed = 0;
+
+        } else {
+            r->uri_changed = 1;
+        }
+    }
+
+    if (code->lengths == NULL) {
+        e->buf.len = code->size;
+
+        if (code->uri) {
+            if (r->ncaptures && (r->quoted_uri || r->plus_in_uri)) {
+                e->buf.len += 2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len,
+                                                 NGX_ESCAPE_ARGS);
+            }
+        }
+
+        for (n = 2; n < r->ncaptures; n += 2) {
+            e->buf.len += r->captures[n + 1] - r->captures[n];
+        }
+
+    } else {
+        ngx_memzero(&le, sizeof(ngx_http_script_engine_t));
+
+        le.ip = code->lengths->elts;
+        le.line = e->line;
+        le.request = r;
+        le.quote = code->redirect;
+
+        len = 0;
+
+        while (*(uintptr_t *) le.ip) {
+            lcode = *(ngx_http_script_len_code_pt *) le.ip;
+            len += lcode(&le);
+        }
+
+        e->buf.len = len;
+    }
+
+    if (code->add_args && r->args.len) {
+        e->buf.len += r->args.len + 1;
+    }
+
+    e->buf.data = ngx_pnalloc(r->pool, e->buf.len);
+    if (e->buf.data == NULL) {
+        e->ip = ngx_http_script_exit;
+        e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+        return;
+    }
+
+    e->quote = code->redirect;
+
+    e->pos = e->buf.data;
+
+    e->ip += sizeof(ngx_http_script_regex_code_t);
+}
+
+
+void
+ngx_http_script_regex_end_code(ngx_http_script_engine_t *e)
+{
+    u_char                            *dst, *src;
+    ngx_http_request_t                *r;
+    ngx_http_script_regex_end_code_t  *code;
+
+    code = (ngx_http_script_regex_end_code_t *) e->ip;
+
+    r = e->request;
+
+    e->quote = 0;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http script regex end");
+
+    if (code->redirect) {
+
+        dst = e->buf.data;
+        src = e->buf.data;
+
+        ngx_unescape_uri(&dst, &src, e->pos - e->buf.data,
+                         NGX_UNESCAPE_REDIRECT);
+
+        if (src < e->pos) {
+            dst = ngx_movemem(dst, src, e->pos - src);
+        }
+
+        e->pos = dst;
+
+        if (code->add_args && r->args.len) {
+            *e->pos++ = (u_char) (code->args ? '&' : '?');
+            e->pos = ngx_copy(e->pos, r->args.data, r->args.len);
+        }
+
+        e->buf.len = e->pos - e->buf.data;
+
+        if (e->log || (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP)) {
+            ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
+                          "rewritten redirect: \"%V\"", &e->buf);
+        }
+
+        ngx_http_clear_location(r);
+
+        r->headers_out.location = ngx_list_push(&r->headers_out.headers);
+        if (r->headers_out.location == NULL) {
+            e->ip = ngx_http_script_exit;
+            e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+            return;
+        }
+
+        r->headers_out.location->hash = 1;
+        ngx_str_set(&r->headers_out.location->key, "Location");
+        r->headers_out.location->value = e->buf;
+
+        e->ip += sizeof(ngx_http_script_regex_end_code_t);
+        return;
+    }
+
+    if (e->args) {
+        e->buf.len = e->args - e->buf.data;
+
+        if (code->add_args && r->args.len) {
+            *e->pos++ = '&';
+            e->pos = ngx_copy(e->pos, r->args.data, r->args.len);
+        }
+
+        r->args.len = e->pos - e->args;
+        r->args.data = e->args;
+
+        e->args = NULL;
+
+    } else {
+        e->buf.len = e->pos - e->buf.data;
+
+        if (!code->add_args) {
+            r->args.len = 0;
+        }
+    }
+
+    if (e->log || (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP)) {
+        ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
+                      "rewritten data: \"%V\", args: \"%V\"",
+                      &e->buf, &r->args);
+    }
+
+    if (code->uri) {
+        r->uri = e->buf;
+
+        if (r->uri.len == 0) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "the rewritten URI has a zero length");
+            e->ip = ngx_http_script_exit;
+            e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+            return;
+        }
+
+        ngx_http_set_exten(r);
+    }
+
+    e->ip += sizeof(ngx_http_script_regex_end_code_t);
+}
+
+
+static ngx_int_t
+ngx_http_script_add_capture_code(ngx_http_script_compile_t *sc, ngx_uint_t n)
+{
+    ngx_http_script_copy_capture_code_t  *code;
+
+    code = ngx_http_script_add_code(*sc->lengths,
+                                    sizeof(ngx_http_script_copy_capture_code_t),
+                                    NULL);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = (ngx_http_script_code_pt) (void *)
+                                         ngx_http_script_copy_capture_len_code;
+    code->n = 2 * n;
+
+
+    code = ngx_http_script_add_code(*sc->values,
+                                    sizeof(ngx_http_script_copy_capture_code_t),
+                                    &sc->main);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = ngx_http_script_copy_capture_code;
+    code->n = 2 * n;
+
+    if (sc->ncaptures < n) {
+        sc->ncaptures = n;
+    }
+
+    return NGX_OK;
+}
+
+
+size_t
+ngx_http_script_copy_capture_len_code(ngx_http_script_engine_t *e)
+{
+    int                                  *cap;
+    u_char                               *p;
+    ngx_uint_t                            n;
+    ngx_http_request_t                   *r;
+    ngx_http_script_copy_capture_code_t  *code;
+
+    r = e->request;
+
+    code = (ngx_http_script_copy_capture_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_http_script_copy_capture_code_t);
+
+    n = code->n;
+
+    if (n < r->ncaptures) {
+
+        cap = r->captures;
+
+        if ((e->is_args || e->quote)
+            && (e->request->quoted_uri || e->request->plus_in_uri))
+        {
+            p = r->captures_data;
+
+            return cap[n + 1] - cap[n]
+                   + 2 * ngx_escape_uri(NULL, &p[cap[n]], cap[n + 1] - cap[n],
+                                        NGX_ESCAPE_ARGS);
+        } else {
+            return cap[n + 1] - cap[n];
+        }
+    }
+
+    return 0;
+}
+
+
+void
+ngx_http_script_copy_capture_code(ngx_http_script_engine_t *e)
+{
+    int                                  *cap;
+    u_char                               *p, *pos;
+    ngx_uint_t                            n;
+    ngx_http_request_t                   *r;
+    ngx_http_script_copy_capture_code_t  *code;
+
+    r = e->request;
+
+    code = (ngx_http_script_copy_capture_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_http_script_copy_capture_code_t);
+
+    n = code->n;
+
+    pos = e->pos;
+
+    if (n < r->ncaptures) {
+
+        cap = r->captures;
+        p = r->captures_data;
+
+        if ((e->is_args || e->quote)
+            && (e->request->quoted_uri || e->request->plus_in_uri))
+        {
+            e->pos = (u_char *) ngx_escape_uri(pos, &p[cap[n]],
+                                               cap[n + 1] - cap[n],
+                                               NGX_ESCAPE_ARGS);
+        } else {
+            e->pos = ngx_copy(pos, &p[cap[n]], cap[n + 1] - cap[n]);
+        }
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+                   "http script capture: \"%*s\"", e->pos - pos, pos);
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_script_add_full_name_code(ngx_http_script_compile_t *sc)
+{
+    ngx_http_script_full_name_code_t  *code;
+
+    code = ngx_http_script_add_code(*sc->lengths,
+                                    sizeof(ngx_http_script_full_name_code_t),
+                                    NULL);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = (ngx_http_script_code_pt) (void *)
+                                            ngx_http_script_full_name_len_code;
+    code->conf_prefix = sc->conf_prefix;
+
+    code = ngx_http_script_add_code(*sc->values,
+                                    sizeof(ngx_http_script_full_name_code_t),
+                                    &sc->main);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = ngx_http_script_full_name_code;
+    code->conf_prefix = sc->conf_prefix;
+
+    return NGX_OK;
+}
+
+
+static size_t
+ngx_http_script_full_name_len_code(ngx_http_script_engine_t *e)
+{
+    ngx_http_script_full_name_code_t  *code;
+
+    code = (ngx_http_script_full_name_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_http_script_full_name_code_t);
+
+    return code->conf_prefix ? ngx_cycle->conf_prefix.len:
+                               ngx_cycle->prefix.len;
+}
+
+
+static void
+ngx_http_script_full_name_code(ngx_http_script_engine_t *e)
+{
+    ngx_http_script_full_name_code_t  *code;
+
+    ngx_str_t  value, *prefix;
+
+    code = (ngx_http_script_full_name_code_t *) e->ip;
+
+    value.data = e->buf.data;
+    value.len = e->pos - e->buf.data;
+
+    prefix = code->conf_prefix ? (ngx_str_t *) &ngx_cycle->conf_prefix:
+                                 (ngx_str_t *) &ngx_cycle->prefix;
+
+    if (ngx_get_full_name(e->request->pool, prefix, &value) != NGX_OK) {
+        e->ip = ngx_http_script_exit;
+        e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+        return;
+    }
+
+    e->buf = value;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+                   "http script fullname: \"%V\"", &value);
+
+    e->ip += sizeof(ngx_http_script_full_name_code_t);
+}
+
+
+void
+ngx_http_script_return_code(ngx_http_script_engine_t *e)
+{
+    ngx_http_script_return_code_t  *code;
+
+    code = (ngx_http_script_return_code_t *) e->ip;
+
+    if (code->status < NGX_HTTP_BAD_REQUEST
+        || code->text.value.len
+        || code->text.lengths)
+    {
+        e->status = ngx_http_send_response(e->request, code->status, NULL,
+                                           &code->text);
+    } else {
+        e->status = code->status;
+    }
+
+    e->ip = ngx_http_script_exit;
+}
+
+
+void
+ngx_http_script_break_code(ngx_http_script_engine_t *e)
+{
+    e->request->uri_changed = 0;
+
+    e->ip = ngx_http_script_exit;
+}
+
+
+void
+ngx_http_script_if_code(ngx_http_script_engine_t *e)
+{
+    ngx_http_script_if_code_t  *code;
+
+    code = (ngx_http_script_if_code_t *) e->ip;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+                   "http script if");
+
+    e->sp--;
+
+    if (e->sp->len && (e->sp->len != 1 || e->sp->data[0] != '0')) {
+        if (code->loc_conf) {
+            e->request->loc_conf = code->loc_conf;
+            ngx_http_update_location_config(e->request);
+        }
+
+        e->ip += sizeof(ngx_http_script_if_code_t);
+        return;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+                   "http script if: false");
+
+    e->ip += code->next;
+}
+
+
+void
+ngx_http_script_equal_code(ngx_http_script_engine_t *e)
+{
+    ngx_http_variable_value_t  *val, *res;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+                   "http script equal");
+
+    e->sp--;
+    val = e->sp;
+    res = e->sp - 1;
+
+    e->ip += sizeof(uintptr_t);
+
+    if (val->len == res->len
+        && ngx_strncmp(val->data, res->data, res->len) == 0)
+    {
+        *res = ngx_http_variable_true_value;
+        return;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+                   "http script equal: no");
+
+    *res = ngx_http_variable_null_value;
+}
+
+
+void
+ngx_http_script_not_equal_code(ngx_http_script_engine_t *e)
+{
+    ngx_http_variable_value_t  *val, *res;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+                   "http script not equal");
+
+    e->sp--;
+    val = e->sp;
+    res = e->sp - 1;
+
+    e->ip += sizeof(uintptr_t);
+
+    if (val->len == res->len
+        && ngx_strncmp(val->data, res->data, res->len) == 0)
+    {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+                       "http script not equal: no");
+
+        *res = ngx_http_variable_null_value;
+        return;
+    }
+
+    *res = ngx_http_variable_true_value;
+}
+
+
+void
+ngx_http_script_file_code(ngx_http_script_engine_t *e)
+{
+    ngx_str_t                     path;
+    ngx_http_request_t           *r;
+    ngx_open_file_info_t          of;
+    ngx_http_core_loc_conf_t     *clcf;
+    ngx_http_variable_value_t    *value;
+    ngx_http_script_file_code_t  *code;
+
+    value = e->sp - 1;
+
+    code = (ngx_http_script_file_code_t *) e->ip;
+    e->ip += sizeof(ngx_http_script_file_code_t);
+
+    path.len = value->len - 1;
+    path.data = value->data;
+
+    r = e->request;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http script file op %p \"%V\"", (void *) code->op, &path);
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+    of.read_ahead = clcf->read_ahead;
+    of.directio = clcf->directio;
+    of.valid = clcf->open_file_cache_valid;
+    of.min_uses = clcf->open_file_cache_min_uses;
+    of.test_only = 1;
+    of.errors = clcf->open_file_cache_errors;
+    of.events = clcf->open_file_cache_events;
+
+    if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
+        e->ip = ngx_http_script_exit;
+        e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+        return;
+    }
+
+    if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
+        != NGX_OK)
+    {
+        if (of.err == 0) {
+            e->ip = ngx_http_script_exit;
+            e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+            return;
+        }
+
+        if (of.err != NGX_ENOENT
+            && of.err != NGX_ENOTDIR
+            && of.err != NGX_ENAMETOOLONG)
+        {
+            ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err,
+                          "%s \"%s\" failed", of.failed, value->data);
+        }
+
+        switch (code->op) {
+
+        case ngx_http_script_file_plain:
+        case ngx_http_script_file_dir:
+        case ngx_http_script_file_exists:
+        case ngx_http_script_file_exec:
+             goto false_value;
+
+        case ngx_http_script_file_not_plain:
+        case ngx_http_script_file_not_dir:
+        case ngx_http_script_file_not_exists:
+        case ngx_http_script_file_not_exec:
+             goto true_value;
+        }
+
+        goto false_value;
+    }
+
+    switch (code->op) {
+    case ngx_http_script_file_plain:
+        if (of.is_file) {
+             goto true_value;
+        }
+        goto false_value;
+
+    case ngx_http_script_file_not_plain:
+        if (of.is_file) {
+            goto false_value;
+        }
+        goto true_value;
+
+    case ngx_http_script_file_dir:
+        if (of.is_dir) {
+             goto true_value;
+        }
+        goto false_value;
+
+    case ngx_http_script_file_not_dir:
+        if (of.is_dir) {
+            goto false_value;
+        }
+        goto true_value;
+
+    case ngx_http_script_file_exists:
+        if (of.is_file || of.is_dir || of.is_link) {
+             goto true_value;
+        }
+        goto false_value;
+
+    case ngx_http_script_file_not_exists:
+        if (of.is_file || of.is_dir || of.is_link) {
+            goto false_value;
+        }
+        goto true_value;
+
+    case ngx_http_script_file_exec:
+        if (of.is_exec) {
+             goto true_value;
+        }
+        goto false_value;
+
+    case ngx_http_script_file_not_exec:
+        if (of.is_exec) {
+            goto false_value;
+        }
+        goto true_value;
+    }
+
+false_value:
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http script file op false");
+
+    *value = ngx_http_variable_null_value;
+    return;
+
+true_value:
+
+    *value = ngx_http_variable_true_value;
+    return;
+}
+
+
+void
+ngx_http_script_complex_value_code(ngx_http_script_engine_t *e)
+{
+    size_t                                 len;
+    ngx_http_script_engine_t               le;
+    ngx_http_script_len_code_pt            lcode;
+    ngx_http_script_complex_value_code_t  *code;
+
+    code = (ngx_http_script_complex_value_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_http_script_complex_value_code_t);
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+                   "http script complex value");
+
+    ngx_memzero(&le, sizeof(ngx_http_script_engine_t));
+
+    le.ip = code->lengths->elts;
+    le.line = e->line;
+    le.request = e->request;
+    le.quote = e->quote;
+
+    for (len = 0; *(uintptr_t *) le.ip; len += lcode(&le)) {
+        lcode = *(ngx_http_script_len_code_pt *) le.ip;
+    }
+
+    e->buf.len = len;
+    e->buf.data = ngx_pnalloc(e->request->pool, len);
+    if (e->buf.data == NULL) {
+        e->ip = ngx_http_script_exit;
+        e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+        return;
+    }
+
+    e->pos = e->buf.data;
+
+    e->sp->len = e->buf.len;
+    e->sp->data = e->buf.data;
+    e->sp++;
+}
+
+
+void
+ngx_http_script_value_code(ngx_http_script_engine_t *e)
+{
+    ngx_http_script_value_code_t  *code;
+
+    code = (ngx_http_script_value_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_http_script_value_code_t);
+
+    e->sp->len = code->text_len;
+    e->sp->data = (u_char *) code->text_data;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+                   "http script value: \"%v\"", e->sp);
+
+    e->sp++;
+}
+
+
+void
+ngx_http_script_set_var_code(ngx_http_script_engine_t *e)
+{
+    ngx_http_request_t          *r;
+    ngx_http_script_var_code_t  *code;
+
+    code = (ngx_http_script_var_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_http_script_var_code_t);
+
+    r = e->request;
+
+    e->sp--;
+
+    r->variables[code->index].len = e->sp->len;
+    r->variables[code->index].valid = 1;
+    r->variables[code->index].no_cacheable = 0;
+    r->variables[code->index].not_found = 0;
+    r->variables[code->index].data = e->sp->data;
+
+#if (NGX_DEBUG)
+    {
+    ngx_http_variable_t        *v;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+    v = cmcf->variables.elts;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+                   "http script set $%V", &v[code->index].name);
+    }
+#endif
+}
+
+
+void
+ngx_http_script_var_set_handler_code(ngx_http_script_engine_t *e)
+{
+    ngx_http_script_var_handler_code_t  *code;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+                   "http script set var handler");
+
+    code = (ngx_http_script_var_handler_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_http_script_var_handler_code_t);
+
+    e->sp--;
+
+    code->handler(e->request, e->sp, code->data);
+}
+
+
+void
+ngx_http_script_var_code(ngx_http_script_engine_t *e)
+{
+    ngx_http_variable_value_t   *value;
+    ngx_http_script_var_code_t  *code;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+                   "http script var");
+
+    code = (ngx_http_script_var_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_http_script_var_code_t);
+
+    value = ngx_http_get_flushed_variable(e->request, code->index);
+
+    if (value && !value->not_found) {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+                       "http script var: \"%v\"", value);
+
+        *e->sp = *value;
+        e->sp++;
+
+        return;
+    }
+
+    *e->sp = ngx_http_variable_null_value;
+    e->sp++;
+}
+
+
+void
+ngx_http_script_nop_code(ngx_http_script_engine_t *e)
+{
+    e->ip += sizeof(uintptr_t);
+}
diff --git a/nginx/src/http/ngx_http_script.h b/nginx/src/http/ngx_http_script.h
new file mode 100644 (file)
index 0000000..a5116d7
--- /dev/null
@@ -0,0 +1,257 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_SCRIPT_H_INCLUDED_
+#define _NGX_HTTP_SCRIPT_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    u_char                     *ip;
+    u_char                     *pos;
+    ngx_http_variable_value_t  *sp;
+
+    ngx_str_t                   buf;
+    ngx_str_t                   line;
+
+    /* the start of the rewritten arguments */
+    u_char                     *args;
+
+    unsigned                    flushed:1;
+    unsigned                    skip:1;
+    unsigned                    quote:1;
+    unsigned                    is_args:1;
+    unsigned                    log:1;
+
+    ngx_int_t                   status;
+    ngx_http_request_t         *request;
+} ngx_http_script_engine_t;
+
+
+typedef struct {
+    ngx_conf_t                 *cf;
+    ngx_str_t                  *source;
+
+    ngx_array_t               **flushes;
+    ngx_array_t               **lengths;
+    ngx_array_t               **values;
+
+    ngx_uint_t                  variables;
+    ngx_uint_t                  ncaptures;
+    ngx_uint_t                  captures_mask;
+    ngx_uint_t                  size;
+
+    void                       *main;
+
+    unsigned                    compile_args:1;
+    unsigned                    complete_lengths:1;
+    unsigned                    complete_values:1;
+    unsigned                    zero:1;
+    unsigned                    conf_prefix:1;
+    unsigned                    root_prefix:1;
+
+    unsigned                    dup_capture:1;
+    unsigned                    args:1;
+} ngx_http_script_compile_t;
+
+
+typedef struct {
+    ngx_str_t                   value;
+    ngx_uint_t                 *flushes;
+    void                       *lengths;
+    void                       *values;
+} ngx_http_complex_value_t;
+
+
+typedef struct {
+    ngx_conf_t                 *cf;
+    ngx_str_t                  *value;
+    ngx_http_complex_value_t   *complex_value;
+
+    unsigned                    zero:1;
+    unsigned                    conf_prefix:1;
+    unsigned                    root_prefix:1;
+} ngx_http_compile_complex_value_t;
+
+
+typedef void (*ngx_http_script_code_pt) (ngx_http_script_engine_t *e);
+typedef size_t (*ngx_http_script_len_code_pt) (ngx_http_script_engine_t *e);
+
+
+typedef struct {
+    ngx_http_script_code_pt     code;
+    uintptr_t                   len;
+} ngx_http_script_copy_code_t;
+
+
+typedef struct {
+    ngx_http_script_code_pt     code;
+    uintptr_t                   index;
+} ngx_http_script_var_code_t;
+
+
+typedef struct {
+    ngx_http_script_code_pt     code;
+    ngx_http_set_variable_pt    handler;
+    uintptr_t                   data;
+} ngx_http_script_var_handler_code_t;
+
+
+typedef struct {
+    ngx_http_script_code_pt     code;
+    uintptr_t                   n;
+} ngx_http_script_copy_capture_code_t;
+
+
+#if (NGX_PCRE)
+
+typedef struct {
+    ngx_http_script_code_pt     code;
+    ngx_http_regex_t           *regex;
+    ngx_array_t                *lengths;
+    uintptr_t                   size;
+    uintptr_t                   status;
+    uintptr_t                   next;
+
+    unsigned                    test:1;
+    unsigned                    negative_test:1;
+    unsigned                    uri:1;
+    unsigned                    args:1;
+
+    /* add the r->args to the new arguments */
+    unsigned                    add_args:1;
+
+    unsigned                    redirect:1;
+    unsigned                    break_cycle:1;
+
+    ngx_str_t                   name;
+} ngx_http_script_regex_code_t;
+
+
+typedef struct {
+    ngx_http_script_code_pt     code;
+
+    unsigned                    uri:1;
+    unsigned                    args:1;
+
+    /* add the r->args to the new arguments */
+    unsigned                    add_args:1;
+
+    unsigned                    redirect:1;
+} ngx_http_script_regex_end_code_t;
+
+#endif
+
+
+typedef struct {
+    ngx_http_script_code_pt     code;
+    uintptr_t                   conf_prefix;
+} ngx_http_script_full_name_code_t;
+
+
+typedef struct {
+    ngx_http_script_code_pt     code;
+    uintptr_t                   status;
+    ngx_http_complex_value_t    text;
+} ngx_http_script_return_code_t;
+
+
+typedef enum {
+    ngx_http_script_file_plain = 0,
+    ngx_http_script_file_not_plain,
+    ngx_http_script_file_dir,
+    ngx_http_script_file_not_dir,
+    ngx_http_script_file_exists,
+    ngx_http_script_file_not_exists,
+    ngx_http_script_file_exec,
+    ngx_http_script_file_not_exec
+} ngx_http_script_file_op_e;
+
+
+typedef struct {
+    ngx_http_script_code_pt     code;
+    uintptr_t                   op;
+} ngx_http_script_file_code_t;
+
+
+typedef struct {
+    ngx_http_script_code_pt     code;
+    uintptr_t                   next;
+    void                      **loc_conf;
+} ngx_http_script_if_code_t;
+
+
+typedef struct {
+    ngx_http_script_code_pt     code;
+    ngx_array_t                *lengths;
+} ngx_http_script_complex_value_code_t;
+
+
+typedef struct {
+    ngx_http_script_code_pt     code;
+    uintptr_t                   value;
+    uintptr_t                   text_len;
+    uintptr_t                   text_data;
+} ngx_http_script_value_code_t;
+
+
+void ngx_http_script_flush_complex_value(ngx_http_request_t *r,
+    ngx_http_complex_value_t *val);
+ngx_int_t ngx_http_complex_value(ngx_http_request_t *r,
+    ngx_http_complex_value_t *val, ngx_str_t *value);
+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);
+
+
+ngx_int_t ngx_http_test_predicates(ngx_http_request_t *r,
+    ngx_array_t *predicates);
+char *ngx_http_set_predicate_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+ngx_uint_t ngx_http_script_variables_count(ngx_str_t *value);
+ngx_int_t ngx_http_script_compile(ngx_http_script_compile_t *sc);
+u_char *ngx_http_script_run(ngx_http_request_t *r, ngx_str_t *value,
+    void *code_lengths, size_t reserved, void *code_values);
+void ngx_http_script_flush_no_cacheable_variables(ngx_http_request_t *r,
+    ngx_array_t *indices);
+
+void *ngx_http_script_start_code(ngx_pool_t *pool, ngx_array_t **codes,
+    size_t size);
+void *ngx_http_script_add_code(ngx_array_t *codes, size_t size, void *code);
+
+size_t ngx_http_script_copy_len_code(ngx_http_script_engine_t *e);
+void ngx_http_script_copy_code(ngx_http_script_engine_t *e);
+size_t ngx_http_script_copy_var_len_code(ngx_http_script_engine_t *e);
+void ngx_http_script_copy_var_code(ngx_http_script_engine_t *e);
+size_t ngx_http_script_copy_capture_len_code(ngx_http_script_engine_t *e);
+void ngx_http_script_copy_capture_code(ngx_http_script_engine_t *e);
+size_t ngx_http_script_mark_args_code(ngx_http_script_engine_t *e);
+void ngx_http_script_start_args_code(ngx_http_script_engine_t *e);
+#if (NGX_PCRE)
+void ngx_http_script_regex_start_code(ngx_http_script_engine_t *e);
+void ngx_http_script_regex_end_code(ngx_http_script_engine_t *e);
+#endif
+void ngx_http_script_return_code(ngx_http_script_engine_t *e);
+void ngx_http_script_break_code(ngx_http_script_engine_t *e);
+void ngx_http_script_if_code(ngx_http_script_engine_t *e);
+void ngx_http_script_equal_code(ngx_http_script_engine_t *e);
+void ngx_http_script_not_equal_code(ngx_http_script_engine_t *e);
+void ngx_http_script_file_code(ngx_http_script_engine_t *e);
+void ngx_http_script_complex_value_code(ngx_http_script_engine_t *e);
+void ngx_http_script_value_code(ngx_http_script_engine_t *e);
+void ngx_http_script_set_var_code(ngx_http_script_engine_t *e);
+void ngx_http_script_var_set_handler_code(ngx_http_script_engine_t *e);
+void ngx_http_script_var_code(ngx_http_script_engine_t *e);
+void ngx_http_script_nop_code(ngx_http_script_engine_t *e);
+
+
+#endif /* _NGX_HTTP_SCRIPT_H_INCLUDED_ */
diff --git a/nginx/src/http/ngx_http_special_response.c b/nginx/src/http/ngx_http_special_response.c
new file mode 100644 (file)
index 0000000..2c1ff17
--- /dev/null
@@ -0,0 +1,846 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <nginx.h>
+
+
+static ngx_int_t ngx_http_send_error_page(ngx_http_request_t *r,
+    ngx_http_err_page_t *err_page);
+static ngx_int_t ngx_http_send_special_response(ngx_http_request_t *r,
+    ngx_http_core_loc_conf_t *clcf, ngx_uint_t err);
+static ngx_int_t ngx_http_send_refresh(ngx_http_request_t *r);
+
+
+static u_char ngx_http_error_full_tail[] =
+"<hr><center>" NGINX_VER "</center>" CRLF
+"</body>" CRLF
+"</html>" CRLF
+;
+
+
+static u_char ngx_http_error_build_tail[] =
+"<hr><center>" NGINX_VER_BUILD "</center>" CRLF
+"</body>" CRLF
+"</html>" CRLF
+;
+
+
+static u_char ngx_http_error_tail[] =
+"<hr><center>nginx</center>" CRLF
+"</body>" CRLF
+"</html>" CRLF
+;
+
+
+static u_char ngx_http_msie_padding[] =
+"<!-- a padding to disable MSIE and Chrome friendly error page -->" CRLF
+"<!-- a padding to disable MSIE and Chrome friendly error page -->" CRLF
+"<!-- a padding to disable MSIE and Chrome friendly error page -->" CRLF
+"<!-- a padding to disable MSIE and Chrome friendly error page -->" CRLF
+"<!-- a padding to disable MSIE and Chrome friendly error page -->" CRLF
+"<!-- a padding to disable MSIE and Chrome friendly error page -->" CRLF
+;
+
+
+static u_char ngx_http_msie_refresh_head[] =
+"<html><head><meta http-equiv=\"Refresh\" content=\"0; URL=";
+
+
+static u_char ngx_http_msie_refresh_tail[] =
+"\"></head><body></body></html>" CRLF;
+
+
+static char ngx_http_error_301_page[] =
+"<html>" CRLF
+"<head><title>301 Moved Permanently</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>301 Moved Permanently</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_302_page[] =
+"<html>" CRLF
+"<head><title>302 Found</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>302 Found</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_303_page[] =
+"<html>" CRLF
+"<head><title>303 See Other</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>303 See Other</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_307_page[] =
+"<html>" CRLF
+"<head><title>307 Temporary Redirect</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>307 Temporary Redirect</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_308_page[] =
+"<html>" CRLF
+"<head><title>308 Permanent Redirect</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>308 Permanent Redirect</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_400_page[] =
+"<html>" CRLF
+"<head><title>400 Bad Request</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>400 Bad Request</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_401_page[] =
+"<html>" CRLF
+"<head><title>401 Authorization Required</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>401 Authorization Required</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_402_page[] =
+"<html>" CRLF
+"<head><title>402 Payment Required</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>402 Payment Required</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_403_page[] =
+"<html>" CRLF
+"<head><title>403 Forbidden</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>403 Forbidden</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_404_page[] =
+"<html>" CRLF
+"<head><title>404 Not Found</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>404 Not Found</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_405_page[] =
+"<html>" CRLF
+"<head><title>405 Not Allowed</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>405 Not Allowed</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_406_page[] =
+"<html>" CRLF
+"<head><title>406 Not Acceptable</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>406 Not Acceptable</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_408_page[] =
+"<html>" CRLF
+"<head><title>408 Request Time-out</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>408 Request Time-out</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_409_page[] =
+"<html>" CRLF
+"<head><title>409 Conflict</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>409 Conflict</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_410_page[] =
+"<html>" CRLF
+"<head><title>410 Gone</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>410 Gone</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_411_page[] =
+"<html>" CRLF
+"<head><title>411 Length Required</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>411 Length Required</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_412_page[] =
+"<html>" CRLF
+"<head><title>412 Precondition Failed</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>412 Precondition Failed</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_413_page[] =
+"<html>" CRLF
+"<head><title>413 Request Entity Too Large</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>413 Request Entity Too Large</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_414_page[] =
+"<html>" CRLF
+"<head><title>414 Request-URI Too Large</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>414 Request-URI Too Large</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_415_page[] =
+"<html>" CRLF
+"<head><title>415 Unsupported Media Type</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>415 Unsupported Media Type</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_416_page[] =
+"<html>" CRLF
+"<head><title>416 Requested Range Not Satisfiable</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>416 Requested Range Not Satisfiable</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_421_page[] =
+"<html>" CRLF
+"<head><title>421 Misdirected Request</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>421 Misdirected Request</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_429_page[] =
+"<html>" CRLF
+"<head><title>429 Too Many Requests</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>429 Too Many Requests</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_494_page[] =
+"<html>" CRLF
+"<head><title>400 Request Header Or Cookie Too Large</title></head>"
+CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>400 Bad Request</h1></center>" CRLF
+"<center>Request Header Or Cookie Too Large</center>" CRLF
+;
+
+
+static char ngx_http_error_495_page[] =
+"<html>" CRLF
+"<head><title>400 The SSL certificate error</title></head>"
+CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>400 Bad Request</h1></center>" CRLF
+"<center>The SSL certificate error</center>" CRLF
+;
+
+
+static char ngx_http_error_496_page[] =
+"<html>" CRLF
+"<head><title>400 No required SSL certificate was sent</title></head>"
+CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>400 Bad Request</h1></center>" CRLF
+"<center>No required SSL certificate was sent</center>" CRLF
+;
+
+
+static char ngx_http_error_497_page[] =
+"<html>" CRLF
+"<head><title>400 The plain HTTP request was sent to HTTPS port</title></head>"
+CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>400 Bad Request</h1></center>" CRLF
+"<center>The plain HTTP request was sent to HTTPS port</center>" CRLF
+;
+
+
+static char ngx_http_error_500_page[] =
+"<html>" CRLF
+"<head><title>500 Internal Server Error</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>500 Internal Server Error</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_501_page[] =
+"<html>" CRLF
+"<head><title>501 Not Implemented</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>501 Not Implemented</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_502_page[] =
+"<html>" CRLF
+"<head><title>502 Bad Gateway</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>502 Bad Gateway</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_503_page[] =
+"<html>" CRLF
+"<head><title>503 Service Temporarily Unavailable</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>503 Service Temporarily Unavailable</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_504_page[] =
+"<html>" CRLF
+"<head><title>504 Gateway Time-out</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>504 Gateway Time-out</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_505_page[] =
+"<html>" CRLF
+"<head><title>505 HTTP Version Not Supported</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>505 HTTP Version Not Supported</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_507_page[] =
+"<html>" CRLF
+"<head><title>507 Insufficient Storage</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>507 Insufficient Storage</h1></center>" CRLF
+;
+
+
+static ngx_str_t ngx_http_error_pages[] = {
+
+    ngx_null_string,                     /* 201, 204 */
+
+#define NGX_HTTP_LAST_2XX  202
+#define NGX_HTTP_OFF_3XX   (NGX_HTTP_LAST_2XX - 201)
+
+    /* ngx_null_string, */               /* 300 */
+    ngx_string(ngx_http_error_301_page),
+    ngx_string(ngx_http_error_302_page),
+    ngx_string(ngx_http_error_303_page),
+    ngx_null_string,                     /* 304 */
+    ngx_null_string,                     /* 305 */
+    ngx_null_string,                     /* 306 */
+    ngx_string(ngx_http_error_307_page),
+    ngx_string(ngx_http_error_308_page),
+
+#define NGX_HTTP_LAST_3XX  309
+#define NGX_HTTP_OFF_4XX   (NGX_HTTP_LAST_3XX - 301 + NGX_HTTP_OFF_3XX)
+
+    ngx_string(ngx_http_error_400_page),
+    ngx_string(ngx_http_error_401_page),
+    ngx_string(ngx_http_error_402_page),
+    ngx_string(ngx_http_error_403_page),
+    ngx_string(ngx_http_error_404_page),
+    ngx_string(ngx_http_error_405_page),
+    ngx_string(ngx_http_error_406_page),
+    ngx_null_string,                     /* 407 */
+    ngx_string(ngx_http_error_408_page),
+    ngx_string(ngx_http_error_409_page),
+    ngx_string(ngx_http_error_410_page),
+    ngx_string(ngx_http_error_411_page),
+    ngx_string(ngx_http_error_412_page),
+    ngx_string(ngx_http_error_413_page),
+    ngx_string(ngx_http_error_414_page),
+    ngx_string(ngx_http_error_415_page),
+    ngx_string(ngx_http_error_416_page),
+    ngx_null_string,                     /* 417 */
+    ngx_null_string,                     /* 418 */
+    ngx_null_string,                     /* 419 */
+    ngx_null_string,                     /* 420 */
+    ngx_string(ngx_http_error_421_page),
+    ngx_null_string,                     /* 422 */
+    ngx_null_string,                     /* 423 */
+    ngx_null_string,                     /* 424 */
+    ngx_null_string,                     /* 425 */
+    ngx_null_string,                     /* 426 */
+    ngx_null_string,                     /* 427 */
+    ngx_null_string,                     /* 428 */
+    ngx_string(ngx_http_error_429_page),
+
+#define NGX_HTTP_LAST_4XX  430
+#define NGX_HTTP_OFF_5XX   (NGX_HTTP_LAST_4XX - 400 + NGX_HTTP_OFF_4XX)
+
+    ngx_string(ngx_http_error_494_page), /* 494, request header too large */
+    ngx_string(ngx_http_error_495_page), /* 495, https certificate error */
+    ngx_string(ngx_http_error_496_page), /* 496, https no certificate */
+    ngx_string(ngx_http_error_497_page), /* 497, http to https */
+    ngx_string(ngx_http_error_404_page), /* 498, canceled */
+    ngx_null_string,                     /* 499, client has closed connection */
+
+    ngx_string(ngx_http_error_500_page),
+    ngx_string(ngx_http_error_501_page),
+    ngx_string(ngx_http_error_502_page),
+    ngx_string(ngx_http_error_503_page),
+    ngx_string(ngx_http_error_504_page),
+    ngx_string(ngx_http_error_505_page),
+    ngx_null_string,                     /* 506 */
+    ngx_string(ngx_http_error_507_page)
+
+#define NGX_HTTP_LAST_5XX  508
+
+};
+
+
+ngx_int_t
+ngx_http_special_response_handler(ngx_http_request_t *r, ngx_int_t error)
+{
+    ngx_uint_t                 i, err;
+    ngx_http_err_page_t       *err_page;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http special response: %i, \"%V?%V\"",
+                   error, &r->uri, &r->args);
+
+    r->err_status = error;
+
+    if (r->keepalive) {
+        switch (error) {
+            case NGX_HTTP_BAD_REQUEST:
+            case NGX_HTTP_REQUEST_ENTITY_TOO_LARGE:
+            case NGX_HTTP_REQUEST_URI_TOO_LARGE:
+            case NGX_HTTP_TO_HTTPS:
+            case NGX_HTTPS_CERT_ERROR:
+            case NGX_HTTPS_NO_CERT:
+            case NGX_HTTP_INTERNAL_SERVER_ERROR:
+            case NGX_HTTP_NOT_IMPLEMENTED:
+                r->keepalive = 0;
+        }
+    }
+
+    if (r->lingering_close) {
+        switch (error) {
+            case NGX_HTTP_BAD_REQUEST:
+            case NGX_HTTP_TO_HTTPS:
+            case NGX_HTTPS_CERT_ERROR:
+            case NGX_HTTPS_NO_CERT:
+                r->lingering_close = 0;
+        }
+    }
+
+    r->headers_out.content_type.len = 0;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (!r->error_page && clcf->error_pages && r->uri_changes != 0) {
+
+        if (clcf->recursive_error_pages == 0) {
+            r->error_page = 1;
+        }
+
+        err_page = clcf->error_pages->elts;
+
+        for (i = 0; i < clcf->error_pages->nelts; i++) {
+            if (err_page[i].status == error) {
+                return ngx_http_send_error_page(r, &err_page[i]);
+            }
+        }
+    }
+
+    r->expect_tested = 1;
+
+    if (ngx_http_discard_request_body(r) != NGX_OK) {
+        r->keepalive = 0;
+    }
+
+    if (clcf->msie_refresh
+        && r->headers_in.msie
+        && (error == NGX_HTTP_MOVED_PERMANENTLY
+            || error == NGX_HTTP_MOVED_TEMPORARILY))
+    {
+        return ngx_http_send_refresh(r);
+    }
+
+    if (error == NGX_HTTP_CREATED) {
+        /* 201 */
+        err = 0;
+
+    } else if (error == NGX_HTTP_NO_CONTENT) {
+        /* 204 */
+        err = 0;
+
+    } else if (error >= NGX_HTTP_MOVED_PERMANENTLY
+               && error < NGX_HTTP_LAST_3XX)
+    {
+        /* 3XX */
+        err = error - NGX_HTTP_MOVED_PERMANENTLY + NGX_HTTP_OFF_3XX;
+
+    } else if (error >= NGX_HTTP_BAD_REQUEST
+               && error < NGX_HTTP_LAST_4XX)
+    {
+        /* 4XX */
+        err = error - NGX_HTTP_BAD_REQUEST + NGX_HTTP_OFF_4XX;
+
+    } else if (error >= NGX_HTTP_NGINX_CODES
+               && error < NGX_HTTP_LAST_5XX)
+    {
+        /* 49X, 5XX */
+        err = error - NGX_HTTP_NGINX_CODES + NGX_HTTP_OFF_5XX;
+        switch (error) {
+            case NGX_HTTP_TO_HTTPS:
+            case NGX_HTTPS_CERT_ERROR:
+            case NGX_HTTPS_NO_CERT:
+            case NGX_HTTP_REQUEST_HEADER_TOO_LARGE:
+                r->err_status = NGX_HTTP_BAD_REQUEST;
+        }
+
+    } else {
+        /* unknown code, zero body */
+        err = 0;
+    }
+
+    return ngx_http_send_special_response(r, clcf, err);
+}
+
+
+ngx_int_t
+ngx_http_filter_finalize_request(ngx_http_request_t *r, ngx_module_t *m,
+    ngx_int_t error)
+{
+    void       *ctx;
+    ngx_int_t   rc;
+
+    ngx_http_clean_header(r);
+
+    ctx = NULL;
+
+    if (m) {
+        ctx = r->ctx[m->ctx_index];
+    }
+
+    /* clear the modules contexts */
+    ngx_memzero(r->ctx, sizeof(void *) * ngx_http_max_module);
+
+    if (m) {
+        r->ctx[m->ctx_index] = ctx;
+    }
+
+    r->filter_finalize = 1;
+
+    rc = ngx_http_special_response_handler(r, error);
+
+    /* NGX_ERROR resets any pending data */
+
+    switch (rc) {
+
+    case NGX_OK:
+    case NGX_DONE:
+        return NGX_ERROR;
+
+    default:
+        return rc;
+    }
+}
+
+
+void
+ngx_http_clean_header(ngx_http_request_t *r)
+{
+    ngx_memzero(&r->headers_out.status,
+                sizeof(ngx_http_headers_out_t)
+                    - offsetof(ngx_http_headers_out_t, status));
+
+    r->headers_out.headers.part.nelts = 0;
+    r->headers_out.headers.part.next = NULL;
+    r->headers_out.headers.last = &r->headers_out.headers.part;
+
+    r->headers_out.content_length_n = -1;
+    r->headers_out.last_modified_time = -1;
+}
+
+
+static ngx_int_t
+ngx_http_send_error_page(ngx_http_request_t *r, ngx_http_err_page_t *err_page)
+{
+    ngx_int_t                  overwrite;
+    ngx_str_t                  uri, args;
+    ngx_table_elt_t           *location;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    overwrite = err_page->overwrite;
+
+    if (overwrite && overwrite != NGX_HTTP_OK) {
+        r->expect_tested = 1;
+    }
+
+    if (overwrite >= 0) {
+        r->err_status = overwrite;
+    }
+
+    if (ngx_http_complex_value(r, &err_page->value, &uri) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (uri.len && uri.data[0] == '/') {
+
+        if (err_page->value.lengths) {
+            ngx_http_split_args(r, &uri, &args);
+
+        } else {
+            args = err_page->args;
+        }
+
+        if (r->method != NGX_HTTP_HEAD) {
+            r->method = NGX_HTTP_GET;
+            r->method_name = ngx_http_core_get_method;
+        }
+
+        return ngx_http_internal_redirect(r, &uri, &args);
+    }
+
+    if (uri.len && uri.data[0] == '@') {
+        return ngx_http_named_location(r, &uri);
+    }
+
+    location = ngx_list_push(&r->headers_out.headers);
+
+    if (location == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (overwrite != NGX_HTTP_MOVED_PERMANENTLY
+        && overwrite != NGX_HTTP_MOVED_TEMPORARILY
+        && overwrite != NGX_HTTP_SEE_OTHER
+        && overwrite != NGX_HTTP_TEMPORARY_REDIRECT
+        && overwrite != NGX_HTTP_PERMANENT_REDIRECT)
+    {
+        r->err_status = NGX_HTTP_MOVED_TEMPORARILY;
+    }
+
+    location->hash = 1;
+    ngx_str_set(&location->key, "Location");
+    location->value = uri;
+
+    ngx_http_clear_location(r);
+
+    r->headers_out.location = location;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (clcf->msie_refresh && r->headers_in.msie) {
+        return ngx_http_send_refresh(r);
+    }
+
+    return ngx_http_send_special_response(r, clcf, r->err_status
+                                                   - NGX_HTTP_MOVED_PERMANENTLY
+                                                   + NGX_HTTP_OFF_3XX);
+}
+
+
+static ngx_int_t
+ngx_http_send_special_response(ngx_http_request_t *r,
+    ngx_http_core_loc_conf_t *clcf, ngx_uint_t err)
+{
+    u_char       *tail;
+    size_t        len;
+    ngx_int_t     rc;
+    ngx_buf_t    *b;
+    ngx_uint_t    msie_padding;
+    ngx_chain_t   out[3];
+
+    if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
+        len = sizeof(ngx_http_error_full_tail) - 1;
+        tail = ngx_http_error_full_tail;
+
+    } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
+        len = sizeof(ngx_http_error_build_tail) - 1;
+        tail = ngx_http_error_build_tail;
+
+    } else {
+        len = sizeof(ngx_http_error_tail) - 1;
+        tail = ngx_http_error_tail;
+    }
+
+    msie_padding = 0;
+
+    if (ngx_http_error_pages[err].len) {
+        r->headers_out.content_length_n = ngx_http_error_pages[err].len + len;
+        if (clcf->msie_padding
+            && (r->headers_in.msie || r->headers_in.chrome)
+            && r->http_version >= NGX_HTTP_VERSION_10
+            && err >= NGX_HTTP_OFF_4XX)
+        {
+            r->headers_out.content_length_n +=
+                                         sizeof(ngx_http_msie_padding) - 1;
+            msie_padding = 1;
+        }
+
+        r->headers_out.content_type_len = sizeof("text/html") - 1;
+        ngx_str_set(&r->headers_out.content_type, "text/html");
+        r->headers_out.content_type_lowcase = NULL;
+
+    } else {
+        r->headers_out.content_length_n = 0;
+    }
+
+    if (r->headers_out.content_length) {
+        r->headers_out.content_length->hash = 0;
+        r->headers_out.content_length = NULL;
+    }
+
+    ngx_http_clear_accept_ranges(r);
+    ngx_http_clear_last_modified(r);
+    ngx_http_clear_etag(r);
+
+    rc = ngx_http_send_header(r);
+
+    if (rc == NGX_ERROR || r->header_only) {
+        return rc;
+    }
+
+    if (ngx_http_error_pages[err].len == 0) {
+        return ngx_http_send_special(r, NGX_HTTP_LAST);
+    }
+
+    b = ngx_calloc_buf(r->pool);
+    if (b == NULL) {
+        return NGX_ERROR;
+    }
+
+    b->memory = 1;
+    b->pos = ngx_http_error_pages[err].data;
+    b->last = ngx_http_error_pages[err].data + ngx_http_error_pages[err].len;
+
+    out[0].buf = b;
+    out[0].next = &out[1];
+
+    b = ngx_calloc_buf(r->pool);
+    if (b == NULL) {
+        return NGX_ERROR;
+    }
+
+    b->memory = 1;
+
+    b->pos = tail;
+    b->last = tail + len;
+
+    out[1].buf = b;
+    out[1].next = NULL;
+
+    if (msie_padding) {
+        b = ngx_calloc_buf(r->pool);
+        if (b == NULL) {
+            return NGX_ERROR;
+        }
+
+        b->memory = 1;
+        b->pos = ngx_http_msie_padding;
+        b->last = ngx_http_msie_padding + sizeof(ngx_http_msie_padding) - 1;
+
+        out[1].next = &out[2];
+        out[2].buf = b;
+        out[2].next = NULL;
+    }
+
+    if (r == r->main) {
+        b->last_buf = 1;
+    }
+
+    b->last_in_chain = 1;
+
+    return ngx_http_output_filter(r, &out[0]);
+}
+
+
+static ngx_int_t
+ngx_http_send_refresh(ngx_http_request_t *r)
+{
+    u_char       *p, *location;
+    size_t        len, size;
+    uintptr_t     escape;
+    ngx_int_t     rc;
+    ngx_buf_t    *b;
+    ngx_chain_t   out;
+
+    len = r->headers_out.location->value.len;
+    location = r->headers_out.location->value.data;
+
+    escape = 2 * ngx_escape_uri(NULL, location, len, NGX_ESCAPE_REFRESH);
+
+    size = sizeof(ngx_http_msie_refresh_head) - 1
+           + escape + len
+           + sizeof(ngx_http_msie_refresh_tail) - 1;
+
+    r->err_status = NGX_HTTP_OK;
+
+    r->headers_out.content_type_len = sizeof("text/html") - 1;
+    ngx_str_set(&r->headers_out.content_type, "text/html");
+    r->headers_out.content_type_lowcase = NULL;
+
+    r->headers_out.location->hash = 0;
+    r->headers_out.location = NULL;
+
+    r->headers_out.content_length_n = size;
+
+    if (r->headers_out.content_length) {
+        r->headers_out.content_length->hash = 0;
+        r->headers_out.content_length = NULL;
+    }
+
+    ngx_http_clear_accept_ranges(r);
+    ngx_http_clear_last_modified(r);
+    ngx_http_clear_etag(r);
+
+    rc = ngx_http_send_header(r);
+
+    if (rc == NGX_ERROR || r->header_only) {
+        return rc;
+    }
+
+    b = ngx_create_temp_buf(r->pool, size);
+    if (b == NULL) {
+        return NGX_ERROR;
+    }
+
+    p = ngx_cpymem(b->pos, ngx_http_msie_refresh_head,
+                   sizeof(ngx_http_msie_refresh_head) - 1);
+
+    if (escape == 0) {
+        p = ngx_cpymem(p, location, len);
+
+    } else {
+        p = (u_char *) ngx_escape_uri(p, location, len, NGX_ESCAPE_REFRESH);
+    }
+
+    b->last = ngx_cpymem(p, ngx_http_msie_refresh_tail,
+                         sizeof(ngx_http_msie_refresh_tail) - 1);
+
+    b->last_buf = (r == r->main) ? 1 : 0;
+    b->last_in_chain = 1;
+
+    out.buf = b;
+    out.next = NULL;
+
+    return ngx_http_output_filter(r, &out);
+}
diff --git a/nginx/src/http/ngx_http_upstream.c b/nginx/src/http/ngx_http_upstream.c
new file mode 100644 (file)
index 0000000..25beefc
--- /dev/null
@@ -0,0 +1,6431 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#if (NGX_HTTP_CACHE)
+static ngx_int_t ngx_http_upstream_cache(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+static ngx_int_t ngx_http_upstream_cache_get(ngx_http_request_t *r,
+    ngx_http_upstream_t *u, ngx_http_file_cache_t **cache);
+static ngx_int_t ngx_http_upstream_cache_send(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+static ngx_int_t ngx_http_upstream_cache_background_update(
+    ngx_http_request_t *r, ngx_http_upstream_t *u);
+static ngx_int_t ngx_http_upstream_cache_check_range(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+static ngx_int_t ngx_http_upstream_cache_status(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_upstream_cache_last_modified(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_upstream_cache_etag(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+#endif
+
+static void ngx_http_upstream_init_request(ngx_http_request_t *r);
+static void ngx_http_upstream_resolve_handler(ngx_resolver_ctx_t *ctx);
+static void ngx_http_upstream_rd_check_broken_connection(ngx_http_request_t *r);
+static void ngx_http_upstream_wr_check_broken_connection(ngx_http_request_t *r);
+static void ngx_http_upstream_check_broken_connection(ngx_http_request_t *r,
+    ngx_event_t *ev);
+static void ngx_http_upstream_connect(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+static ngx_int_t ngx_http_upstream_reinit(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+static void ngx_http_upstream_send_request(ngx_http_request_t *r,
+    ngx_http_upstream_t *u, ngx_uint_t do_write);
+static ngx_int_t ngx_http_upstream_send_request_body(ngx_http_request_t *r,
+    ngx_http_upstream_t *u, ngx_uint_t do_write);
+static void ngx_http_upstream_send_request_handler(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+static void ngx_http_upstream_read_request_handler(ngx_http_request_t *r);
+static void ngx_http_upstream_process_header(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+static ngx_int_t ngx_http_upstream_test_next(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+static ngx_int_t ngx_http_upstream_intercept_errors(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+static ngx_int_t ngx_http_upstream_test_connect(ngx_connection_t *c);
+static ngx_int_t ngx_http_upstream_process_headers(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+static ngx_int_t ngx_http_upstream_process_trailers(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+static void ngx_http_upstream_send_response(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+static void ngx_http_upstream_upgrade(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+static void ngx_http_upstream_upgraded_read_downstream(ngx_http_request_t *r);
+static void ngx_http_upstream_upgraded_write_downstream(ngx_http_request_t *r);
+static void ngx_http_upstream_upgraded_read_upstream(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+static void ngx_http_upstream_upgraded_write_upstream(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+static void ngx_http_upstream_process_upgraded(ngx_http_request_t *r,
+    ngx_uint_t from_upstream, ngx_uint_t do_write);
+static void
+    ngx_http_upstream_process_non_buffered_downstream(ngx_http_request_t *r);
+static void
+    ngx_http_upstream_process_non_buffered_upstream(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+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);
+static void ngx_http_upstream_thread_event_handler(ngx_event_t *ev);
+#endif
+static ngx_int_t ngx_http_upstream_output_filter(void *data,
+    ngx_chain_t *chain);
+static void ngx_http_upstream_process_downstream(ngx_http_request_t *r);
+static void ngx_http_upstream_process_upstream(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+static void ngx_http_upstream_process_request(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+static void ngx_http_upstream_store(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+static void ngx_http_upstream_dummy_handler(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+static void ngx_http_upstream_next(ngx_http_request_t *r,
+    ngx_http_upstream_t *u, ngx_uint_t ft_type);
+static void ngx_http_upstream_cleanup(void *data);
+static void ngx_http_upstream_finalize_request(ngx_http_request_t *r,
+    ngx_http_upstream_t *u, ngx_int_t rc);
+
+static ngx_int_t ngx_http_upstream_process_header_line(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_process_content_length(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_process_last_modified(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_process_set_cookie(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t
+    ngx_http_upstream_process_cache_control(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_ignore_header_line(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_process_expires(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_process_accel_expires(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_process_limit_rate(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_process_buffering(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_process_charset(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_process_connection(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t
+    ngx_http_upstream_process_transfer_encoding(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_process_vary(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_copy_header_line(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t
+    ngx_http_upstream_copy_multi_header_lines(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_copy_content_type(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_copy_last_modified(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_rewrite_location(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_rewrite_refresh(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_rewrite_set_cookie(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_copy_allow_ranges(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+
+#if (NGX_HTTP_GZIP)
+static ngx_int_t ngx_http_upstream_copy_content_encoding(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
+#endif
+
+static ngx_int_t ngx_http_upstream_add_variables(ngx_conf_t *cf);
+static ngx_int_t ngx_http_upstream_addr_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_upstream_status_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_upstream_response_time_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_upstream_response_length_variable(
+    ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_upstream_header_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_upstream_trailer_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_upstream_cookie_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+
+static char *ngx_http_upstream(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy);
+static char *ngx_http_upstream_server(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+static ngx_int_t ngx_http_upstream_set_local(ngx_http_request_t *r,
+  ngx_http_upstream_t *u, ngx_http_upstream_local_t *local);
+
+static void *ngx_http_upstream_create_main_conf(ngx_conf_t *cf);
+static char *ngx_http_upstream_init_main_conf(ngx_conf_t *cf, void *conf);
+
+#if (NGX_HTTP_SSL)
+static void ngx_http_upstream_ssl_init_connection(ngx_http_request_t *,
+    ngx_http_upstream_t *u, ngx_connection_t *c);
+static void ngx_http_upstream_ssl_handshake_handler(ngx_connection_t *c);
+static void ngx_http_upstream_ssl_handshake(ngx_http_request_t *,
+    ngx_http_upstream_t *u, 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);
+#endif
+
+
+static ngx_http_upstream_header_t  ngx_http_upstream_headers_in[] = {
+
+    { ngx_string("Status"),
+                 ngx_http_upstream_process_header_line,
+                 offsetof(ngx_http_upstream_headers_in_t, status),
+                 ngx_http_upstream_copy_header_line, 0, 0 },
+
+    { ngx_string("Content-Type"),
+                 ngx_http_upstream_process_header_line,
+                 offsetof(ngx_http_upstream_headers_in_t, content_type),
+                 ngx_http_upstream_copy_content_type, 0, 1 },
+
+    { ngx_string("Content-Length"),
+                 ngx_http_upstream_process_content_length, 0,
+                 ngx_http_upstream_ignore_header_line, 0, 0 },
+
+    { ngx_string("Date"),
+                 ngx_http_upstream_process_header_line,
+                 offsetof(ngx_http_upstream_headers_in_t, date),
+                 ngx_http_upstream_copy_header_line,
+                 offsetof(ngx_http_headers_out_t, date), 0 },
+
+    { ngx_string("Last-Modified"),
+                 ngx_http_upstream_process_last_modified, 0,
+                 ngx_http_upstream_copy_last_modified, 0, 0 },
+
+    { ngx_string("ETag"),
+                 ngx_http_upstream_process_header_line,
+                 offsetof(ngx_http_upstream_headers_in_t, etag),
+                 ngx_http_upstream_copy_header_line,
+                 offsetof(ngx_http_headers_out_t, etag), 0 },
+
+    { ngx_string("Server"),
+                 ngx_http_upstream_process_header_line,
+                 offsetof(ngx_http_upstream_headers_in_t, server),
+                 ngx_http_upstream_copy_header_line,
+                 offsetof(ngx_http_headers_out_t, server), 0 },
+
+    { ngx_string("WWW-Authenticate"),
+                 ngx_http_upstream_process_header_line,
+                 offsetof(ngx_http_upstream_headers_in_t, www_authenticate),
+                 ngx_http_upstream_copy_header_line, 0, 0 },
+
+    { ngx_string("Location"),
+                 ngx_http_upstream_process_header_line,
+                 offsetof(ngx_http_upstream_headers_in_t, location),
+                 ngx_http_upstream_rewrite_location, 0, 0 },
+
+    { ngx_string("Refresh"),
+                 ngx_http_upstream_ignore_header_line, 0,
+                 ngx_http_upstream_rewrite_refresh, 0, 0 },
+
+    { ngx_string("Set-Cookie"),
+                 ngx_http_upstream_process_set_cookie,
+                 offsetof(ngx_http_upstream_headers_in_t, cookies),
+                 ngx_http_upstream_rewrite_set_cookie, 0, 1 },
+
+    { ngx_string("Content-Disposition"),
+                 ngx_http_upstream_ignore_header_line, 0,
+                 ngx_http_upstream_copy_header_line, 0, 1 },
+
+    { ngx_string("Cache-Control"),
+                 ngx_http_upstream_process_cache_control, 0,
+                 ngx_http_upstream_copy_multi_header_lines,
+                 offsetof(ngx_http_headers_out_t, cache_control), 1 },
+
+    { ngx_string("Expires"),
+                 ngx_http_upstream_process_expires, 0,
+                 ngx_http_upstream_copy_header_line,
+                 offsetof(ngx_http_headers_out_t, expires), 1 },
+
+    { ngx_string("Accept-Ranges"),
+                 ngx_http_upstream_process_header_line,
+                 offsetof(ngx_http_upstream_headers_in_t, accept_ranges),
+                 ngx_http_upstream_copy_allow_ranges,
+                 offsetof(ngx_http_headers_out_t, accept_ranges), 1 },
+
+    { ngx_string("Content-Range"),
+                 ngx_http_upstream_ignore_header_line, 0,
+                 ngx_http_upstream_copy_header_line,
+                 offsetof(ngx_http_headers_out_t, content_range), 0 },
+
+    { ngx_string("Connection"),
+                 ngx_http_upstream_process_connection, 0,
+                 ngx_http_upstream_ignore_header_line, 0, 0 },
+
+    { ngx_string("Keep-Alive"),
+                 ngx_http_upstream_ignore_header_line, 0,
+                 ngx_http_upstream_ignore_header_line, 0, 0 },
+
+    { ngx_string("Vary"),
+                 ngx_http_upstream_process_vary, 0,
+                 ngx_http_upstream_copy_header_line, 0, 0 },
+
+    { ngx_string("Link"),
+                 ngx_http_upstream_ignore_header_line, 0,
+                 ngx_http_upstream_copy_multi_header_lines,
+                 offsetof(ngx_http_headers_out_t, link), 0 },
+
+    { ngx_string("X-Accel-Expires"),
+                 ngx_http_upstream_process_accel_expires, 0,
+                 ngx_http_upstream_copy_header_line, 0, 0 },
+
+    { ngx_string("X-Accel-Redirect"),
+                 ngx_http_upstream_process_header_line,
+                 offsetof(ngx_http_upstream_headers_in_t, x_accel_redirect),
+                 ngx_http_upstream_copy_header_line, 0, 0 },
+
+    { ngx_string("X-Accel-Limit-Rate"),
+                 ngx_http_upstream_process_limit_rate, 0,
+                 ngx_http_upstream_copy_header_line, 0, 0 },
+
+    { ngx_string("X-Accel-Buffering"),
+                 ngx_http_upstream_process_buffering, 0,
+                 ngx_http_upstream_copy_header_line, 0, 0 },
+
+    { ngx_string("X-Accel-Charset"),
+                 ngx_http_upstream_process_charset, 0,
+                 ngx_http_upstream_copy_header_line, 0, 0 },
+
+    { ngx_string("Transfer-Encoding"),
+                 ngx_http_upstream_process_transfer_encoding, 0,
+                 ngx_http_upstream_ignore_header_line, 0, 0 },
+
+#if (NGX_HTTP_GZIP)
+    { ngx_string("Content-Encoding"),
+                 ngx_http_upstream_process_header_line,
+                 offsetof(ngx_http_upstream_headers_in_t, content_encoding),
+                 ngx_http_upstream_copy_content_encoding, 0, 0 },
+#endif
+
+    { ngx_null_string, NULL, 0, NULL, 0, 0 }
+};
+
+
+static ngx_command_t  ngx_http_upstream_commands[] = {
+
+    { ngx_string("upstream"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1,
+      ngx_http_upstream,
+      0,
+      0,
+      NULL },
+
+    { ngx_string("server"),
+      NGX_HTTP_UPS_CONF|NGX_CONF_1MORE,
+      ngx_http_upstream_server,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_upstream_module_ctx = {
+    ngx_http_upstream_add_variables,       /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    ngx_http_upstream_create_main_conf,    /* create main configuration */
+    ngx_http_upstream_init_main_conf,      /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_upstream_module = {
+    NGX_MODULE_V1,
+    &ngx_http_upstream_module_ctx,         /* module context */
+    ngx_http_upstream_commands,            /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_variable_t  ngx_http_upstream_vars[] = {
+
+    { ngx_string("upstream_addr"), NULL,
+      ngx_http_upstream_addr_variable, 0,
+      NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("upstream_status"), NULL,
+      ngx_http_upstream_status_variable, 0,
+      NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("upstream_connect_time"), NULL,
+      ngx_http_upstream_response_time_variable, 2,
+      NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("upstream_header_time"), NULL,
+      ngx_http_upstream_response_time_variable, 1,
+      NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("upstream_response_time"), NULL,
+      ngx_http_upstream_response_time_variable, 0,
+      NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("upstream_response_length"), NULL,
+      ngx_http_upstream_response_length_variable, 0,
+      NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("upstream_bytes_received"), NULL,
+      ngx_http_upstream_response_length_variable, 1,
+      NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+#if (NGX_HTTP_CACHE)
+
+    { ngx_string("upstream_cache_status"), NULL,
+      ngx_http_upstream_cache_status, 0,
+      NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("upstream_cache_last_modified"), NULL,
+      ngx_http_upstream_cache_last_modified, 0,
+      NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
+
+    { ngx_string("upstream_cache_etag"), NULL,
+      ngx_http_upstream_cache_etag, 0,
+      NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
+
+#endif
+
+    { ngx_string("upstream_http_"), NULL, ngx_http_upstream_header_variable,
+      0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_PREFIX, 0 },
+
+    { ngx_string("upstream_trailer_"), NULL, ngx_http_upstream_trailer_variable,
+      0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_PREFIX, 0 },
+
+    { ngx_string("upstream_cookie_"), NULL, ngx_http_upstream_cookie_variable,
+      0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_PREFIX, 0 },
+
+      ngx_http_null_variable
+};
+
+
+static ngx_http_upstream_next_t  ngx_http_upstream_next_errors[] = {
+    { 500, NGX_HTTP_UPSTREAM_FT_HTTP_500 },
+    { 502, NGX_HTTP_UPSTREAM_FT_HTTP_502 },
+    { 503, NGX_HTTP_UPSTREAM_FT_HTTP_503 },
+    { 504, NGX_HTTP_UPSTREAM_FT_HTTP_504 },
+    { 403, NGX_HTTP_UPSTREAM_FT_HTTP_403 },
+    { 404, NGX_HTTP_UPSTREAM_FT_HTTP_404 },
+    { 429, NGX_HTTP_UPSTREAM_FT_HTTP_429 },
+    { 0, 0 }
+};
+
+
+ngx_conf_bitmask_t  ngx_http_upstream_cache_method_mask[] = {
+    { ngx_string("GET"), NGX_HTTP_GET },
+    { ngx_string("HEAD"), NGX_HTTP_HEAD },
+    { ngx_string("POST"), NGX_HTTP_POST },
+    { ngx_null_string, 0 }
+};
+
+
+ngx_conf_bitmask_t  ngx_http_upstream_ignore_headers_masks[] = {
+    { ngx_string("X-Accel-Redirect"), NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT },
+    { ngx_string("X-Accel-Expires"), NGX_HTTP_UPSTREAM_IGN_XA_EXPIRES },
+    { ngx_string("X-Accel-Limit-Rate"), NGX_HTTP_UPSTREAM_IGN_XA_LIMIT_RATE },
+    { ngx_string("X-Accel-Buffering"), NGX_HTTP_UPSTREAM_IGN_XA_BUFFERING },
+    { ngx_string("X-Accel-Charset"), NGX_HTTP_UPSTREAM_IGN_XA_CHARSET },
+    { ngx_string("Expires"), NGX_HTTP_UPSTREAM_IGN_EXPIRES },
+    { ngx_string("Cache-Control"), NGX_HTTP_UPSTREAM_IGN_CACHE_CONTROL },
+    { ngx_string("Set-Cookie"), NGX_HTTP_UPSTREAM_IGN_SET_COOKIE },
+    { ngx_string("Vary"), NGX_HTTP_UPSTREAM_IGN_VARY },
+    { ngx_null_string, 0 }
+};
+
+
+ngx_int_t
+ngx_http_upstream_create(ngx_http_request_t *r)
+{
+    ngx_http_upstream_t  *u;
+
+    u = r->upstream;
+
+    if (u && u->cleanup) {
+        r->main->count++;
+        ngx_http_upstream_cleanup(r);
+    }
+
+    u = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_t));
+    if (u == NULL) {
+        return NGX_ERROR;
+    }
+
+    r->upstream = u;
+
+    u->peer.log = r->connection->log;
+    u->peer.log_error = NGX_ERROR_ERR;
+
+#if (NGX_HTTP_CACHE)
+    r->cache = NULL;
+#endif
+
+    u->headers_in.content_length_n = -1;
+    u->headers_in.last_modified_time = -1;
+
+    return NGX_OK;
+}
+
+
+void
+ngx_http_upstream_init(ngx_http_request_t *r)
+{
+    ngx_connection_t     *c;
+
+    c = r->connection;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http init upstream, client timer: %d", c->read->timer_set);
+
+#if (NGX_HTTP_V2)
+    if (r->stream) {
+        ngx_http_upstream_init_request(r);
+        return;
+    }
+#endif
+
+    if (c->read->timer_set) {
+        ngx_del_timer(c->read);
+    }
+
+    if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
+
+        if (!c->write->active) {
+            if (ngx_add_event(c->write, NGX_WRITE_EVENT, NGX_CLEAR_EVENT)
+                == NGX_ERROR)
+            {
+                ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+                return;
+            }
+        }
+    }
+
+    ngx_http_upstream_init_request(r);
+}
+
+
+static void
+ngx_http_upstream_init_request(ngx_http_request_t *r)
+{
+    ngx_str_t                      *host;
+    ngx_uint_t                      i;
+    ngx_resolver_ctx_t             *ctx, temp;
+    ngx_http_cleanup_t             *cln;
+    ngx_http_upstream_t            *u;
+    ngx_http_core_loc_conf_t       *clcf;
+    ngx_http_upstream_srv_conf_t   *uscf, **uscfp;
+    ngx_http_upstream_main_conf_t  *umcf;
+
+    if (r->aio) {
+        return;
+    }
+
+    u = r->upstream;
+
+#if (NGX_HTTP_CACHE)
+
+    if (u->conf->cache) {
+        ngx_int_t  rc;
+
+        rc = ngx_http_upstream_cache(r, u);
+
+        if (rc == NGX_BUSY) {
+            r->write_event_handler = ngx_http_upstream_init_request;
+            return;
+        }
+
+        r->write_event_handler = ngx_http_request_empty_handler;
+
+        if (rc == NGX_ERROR) {
+            ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        if (rc == NGX_OK) {
+            rc = ngx_http_upstream_cache_send(r, u);
+
+            if (rc == NGX_DONE) {
+                return;
+            }
+
+            if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) {
+                rc = NGX_DECLINED;
+                r->cached = 0;
+                u->buffer.start = NULL;
+                u->cache_status = NGX_HTTP_CACHE_MISS;
+                u->request_sent = 1;
+            }
+
+            if (ngx_http_upstream_cache_background_update(r, u) != NGX_OK) {
+                rc = NGX_ERROR;
+            }
+        }
+
+        if (rc != NGX_DECLINED) {
+            ngx_http_finalize_request(r, rc);
+            return;
+        }
+    }
+
+#endif
+
+    u->store = u->conf->store;
+
+    if (!u->store && !r->post_action && !u->conf->ignore_client_abort) {
+        r->read_event_handler = ngx_http_upstream_rd_check_broken_connection;
+        r->write_event_handler = ngx_http_upstream_wr_check_broken_connection;
+    }
+
+    if (r->request_body) {
+        u->request_bufs = r->request_body->bufs;
+    }
+
+    if (u->create_request(r) != NGX_OK) {
+        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    if (ngx_http_upstream_set_local(r, u, u->conf->local) != NGX_OK) {
+        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    u->output.alignment = clcf->directio_alignment;
+    u->output.pool = r->pool;
+    u->output.bufs.num = 1;
+    u->output.bufs.size = clcf->client_body_buffer_size;
+
+    if (u->output.output_filter == NULL) {
+        u->output.output_filter = ngx_chain_writer;
+        u->output.filter_ctx = &u->writer;
+    }
+
+    u->writer.pool = r->pool;
+
+    if (r->upstream_states == NULL) {
+
+        r->upstream_states = ngx_array_create(r->pool, 1,
+                                            sizeof(ngx_http_upstream_state_t));
+        if (r->upstream_states == NULL) {
+            ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+    } else {
+
+        u->state = ngx_array_push(r->upstream_states);
+        if (u->state == NULL) {
+            ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        ngx_memzero(u->state, sizeof(ngx_http_upstream_state_t));
+    }
+
+    cln = ngx_http_cleanup_add(r, 0);
+    if (cln == NULL) {
+        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    cln->handler = ngx_http_upstream_cleanup;
+    cln->data = r;
+    u->cleanup = &cln->handler;
+
+    if (u->resolved == NULL) {
+
+        uscf = u->conf->upstream;
+
+    } else {
+
+#if (NGX_HTTP_SSL)
+        u->ssl_name = u->resolved->host;
+#endif
+
+        host = &u->resolved->host;
+
+        umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);
+
+        uscfp = umcf->upstreams.elts;
+
+        for (i = 0; i < umcf->upstreams.nelts; i++) {
+
+            uscf = uscfp[i];
+
+            if (uscf->host.len == host->len
+                && ((uscf->port == 0 && u->resolved->no_port)
+                     || uscf->port == u->resolved->port)
+                && ngx_strncasecmp(uscf->host.data, host->data, host->len) == 0)
+            {
+                goto found;
+            }
+        }
+
+        if (u->resolved->sockaddr) {
+
+            if (u->resolved->port == 0
+                && u->resolved->sockaddr->sa_family != AF_UNIX)
+            {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "no port in upstream \"%V\"", host);
+                ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+                return;
+            }
+
+            if (ngx_http_upstream_create_round_robin_peer(r, u->resolved)
+                != NGX_OK)
+            {
+                ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+                return;
+            }
+
+            ngx_http_upstream_connect(r, u);
+
+            return;
+        }
+
+        if (u->resolved->port == 0) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "no port in upstream \"%V\"", host);
+            ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        temp.name = *host;
+
+        ctx = ngx_resolve_start(clcf->resolver, &temp);
+        if (ctx == NULL) {
+            ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        if (ctx == NGX_NO_RESOLVER) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "no resolver defined to resolve %V", host);
+
+            ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY);
+            return;
+        }
+
+        ctx->name = *host;
+        ctx->handler = ngx_http_upstream_resolve_handler;
+        ctx->data = r;
+        ctx->timeout = clcf->resolver_timeout;
+
+        u->resolved->ctx = ctx;
+
+        if (ngx_resolve_name(ctx) != NGX_OK) {
+            u->resolved->ctx = NULL;
+            ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        return;
+    }
+
+found:
+
+    if (uscf == NULL) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                      "no upstream configuration");
+        ngx_http_upstream_finalize_request(r, u,
+                                           NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    u->upstream = uscf;
+
+#if (NGX_HTTP_SSL)
+    u->ssl_name = uscf->host;
+#endif
+
+    if (uscf->peer.init(r, uscf) != NGX_OK) {
+        ngx_http_upstream_finalize_request(r, u,
+                                           NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    u->peer.start_time = ngx_current_msec;
+
+    if (u->conf->next_upstream_tries
+        && u->peer.tries > u->conf->next_upstream_tries)
+    {
+        u->peer.tries = u->conf->next_upstream_tries;
+    }
+
+    ngx_http_upstream_connect(r, u);
+}
+
+
+#if (NGX_HTTP_CACHE)
+
+static ngx_int_t
+ngx_http_upstream_cache(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+    ngx_int_t               rc;
+    ngx_http_cache_t       *c;
+    ngx_http_file_cache_t  *cache;
+
+    c = r->cache;
+
+    if (c == NULL) {
+
+        if (!(r->method & u->conf->cache_methods)) {
+            return NGX_DECLINED;
+        }
+
+        rc = ngx_http_upstream_cache_get(r, u, &cache);
+
+        if (rc != NGX_OK) {
+            return rc;
+        }
+
+        if (r->method == NGX_HTTP_HEAD && u->conf->cache_convert_head) {
+            u->method = ngx_http_core_get_method;
+        }
+
+        if (ngx_http_file_cache_new(r) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        if (u->create_key(r) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        /* TODO: add keys */
+
+        ngx_http_file_cache_create_key(r);
+
+        if (r->cache->header_start + 256 >= u->conf->buffer_size) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "%V_buffer_size %uz is not enough for cache key, "
+                          "it should be increased to at least %uz",
+                          &u->conf->module, u->conf->buffer_size,
+                          ngx_align(r->cache->header_start + 256, 1024));
+
+            r->cache = NULL;
+            return NGX_DECLINED;
+        }
+
+        u->cacheable = 1;
+
+        c = r->cache;
+
+        c->body_start = u->conf->buffer_size;
+        c->min_uses = u->conf->cache_min_uses;
+        c->file_cache = cache;
+
+        switch (ngx_http_test_predicates(r, u->conf->cache_bypass)) {
+
+        case NGX_ERROR:
+            return NGX_ERROR;
+
+        case NGX_DECLINED:
+            u->cache_status = NGX_HTTP_CACHE_BYPASS;
+            return NGX_DECLINED;
+
+        default: /* NGX_OK */
+            break;
+        }
+
+        c->lock = u->conf->cache_lock;
+        c->lock_timeout = u->conf->cache_lock_timeout;
+        c->lock_age = u->conf->cache_lock_age;
+
+        u->cache_status = NGX_HTTP_CACHE_MISS;
+    }
+
+    rc = ngx_http_file_cache_open(r);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http upstream cache: %i", rc);
+
+    switch (rc) {
+
+    case NGX_HTTP_CACHE_STALE:
+
+        if (((u->conf->cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING)
+             || c->stale_updating) && !r->background
+            && u->conf->cache_background_update)
+        {
+            r->cache->background = 1;
+            u->cache_status = rc;
+            rc = NGX_OK;
+        }
+
+        break;
+
+    case NGX_HTTP_CACHE_UPDATING:
+
+        if (((u->conf->cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING)
+             || c->stale_updating) && !r->background)
+        {
+            u->cache_status = rc;
+            rc = NGX_OK;
+
+        } else {
+            rc = NGX_HTTP_CACHE_STALE;
+        }
+
+        break;
+
+    case NGX_OK:
+        u->cache_status = NGX_HTTP_CACHE_HIT;
+    }
+
+    switch (rc) {
+
+    case NGX_OK:
+
+        return NGX_OK;
+
+    case NGX_HTTP_CACHE_STALE:
+
+        c->valid_sec = 0;
+        c->updating_sec = 0;
+        c->error_sec = 0;
+
+        u->buffer.start = NULL;
+        u->cache_status = NGX_HTTP_CACHE_EXPIRED;
+
+        break;
+
+    case NGX_DECLINED:
+
+        if ((size_t) (u->buffer.end - u->buffer.start) < u->conf->buffer_size) {
+            u->buffer.start = NULL;
+
+        } else {
+            u->buffer.pos = u->buffer.start + c->header_start;
+            u->buffer.last = u->buffer.pos;
+        }
+
+        break;
+
+    case NGX_HTTP_CACHE_SCARCE:
+
+        u->cacheable = 0;
+
+        break;
+
+    case NGX_AGAIN:
+
+        return NGX_BUSY;
+
+    case NGX_ERROR:
+
+        return NGX_ERROR;
+
+    default:
+
+        /* cached NGX_HTTP_BAD_GATEWAY, NGX_HTTP_GATEWAY_TIME_OUT, etc. */
+
+        u->cache_status = NGX_HTTP_CACHE_HIT;
+
+        return rc;
+    }
+
+    if (ngx_http_upstream_cache_check_range(r, u) == NGX_DECLINED) {
+        u->cacheable = 0;
+    }
+
+    r->cached = 0;
+
+    return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_cache_get(ngx_http_request_t *r, ngx_http_upstream_t *u,
+    ngx_http_file_cache_t **cache)
+{
+    ngx_str_t               *name, val;
+    ngx_uint_t               i;
+    ngx_http_file_cache_t  **caches;
+
+    if (u->conf->cache_zone) {
+        *cache = u->conf->cache_zone->data;
+        return NGX_OK;
+    }
+
+    if (ngx_http_complex_value(r, u->conf->cache_value, &val) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (val.len == 0
+        || (val.len == 3 && ngx_strncmp(val.data, "off", 3) == 0))
+    {
+        return NGX_DECLINED;
+    }
+
+    caches = u->caches->elts;
+
+    for (i = 0; i < u->caches->nelts; i++) {
+        name = &caches[i]->shm_zone->shm.name;
+
+        if (name->len == val.len
+            && ngx_strncmp(name->data, val.data, val.len) == 0)
+        {
+            *cache = caches[i];
+            return NGX_OK;
+        }
+    }
+
+    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                  "cache \"%V\" not found", &val);
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_cache_send(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+    ngx_int_t          rc;
+    ngx_http_cache_t  *c;
+
+    r->cached = 1;
+    c = r->cache;
+
+    if (c->header_start == c->body_start) {
+        r->http_version = NGX_HTTP_VERSION_9;
+        return ngx_http_cache_send(r);
+    }
+
+    /* TODO: cache stack */
+
+    u->buffer = *c->buf;
+    u->buffer.pos += c->header_start;
+
+    ngx_memzero(&u->headers_in, sizeof(ngx_http_upstream_headers_in_t));
+    u->headers_in.content_length_n = -1;
+    u->headers_in.last_modified_time = -1;
+
+    if (ngx_list_init(&u->headers_in.headers, r->pool, 8,
+                      sizeof(ngx_table_elt_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_list_init(&u->headers_in.trailers, r->pool, 2,
+                      sizeof(ngx_table_elt_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    rc = u->process_header(r);
+
+    if (rc == NGX_OK) {
+
+        if (ngx_http_upstream_process_headers(r, u) != NGX_OK) {
+            return NGX_DONE;
+        }
+
+        return ngx_http_cache_send(r);
+    }
+
+    if (rc == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if (rc == NGX_AGAIN) {
+        rc = NGX_HTTP_UPSTREAM_INVALID_HEADER;
+    }
+
+    /* rc == NGX_HTTP_UPSTREAM_INVALID_HEADER */
+
+    ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
+                  "cache file \"%s\" contains invalid header",
+                  c->file.name.data);
+
+    /* TODO: delete file */
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_cache_background_update(ngx_http_request_t *r,
+    ngx_http_upstream_t *u)
+{
+    ngx_http_request_t  *sr;
+
+    if (!r->cached || !r->cache->background) {
+        return NGX_OK;
+    }
+
+    if (r == r->main) {
+        r->preserve_body = 1;
+    }
+
+    if (ngx_http_subrequest(r, &r->uri, &r->args, &sr, NULL,
+                            NGX_HTTP_SUBREQUEST_CLONE
+                            |NGX_HTTP_SUBREQUEST_BACKGROUND)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    sr->header_only = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_cache_check_range(ngx_http_request_t *r,
+    ngx_http_upstream_t *u)
+{
+    off_t             offset;
+    u_char           *p, *start;
+    ngx_table_elt_t  *h;
+
+    h = r->headers_in.range;
+
+    if (h == NULL
+        || !u->cacheable
+        || u->conf->cache_max_range_offset == NGX_MAX_OFF_T_VALUE)
+    {
+        return NGX_OK;
+    }
+
+    if (u->conf->cache_max_range_offset == 0) {
+        return NGX_DECLINED;
+    }
+
+    if (h->value.len < 7
+        || ngx_strncasecmp(h->value.data, (u_char *) "bytes=", 6) != 0)
+    {
+        return NGX_OK;
+    }
+
+    p = h->value.data + 6;
+
+    while (*p == ' ') { p++; }
+
+    if (*p == '-') {
+        return NGX_DECLINED;
+    }
+
+    start = p;
+
+    while (*p >= '0' && *p <= '9') { p++; }
+
+    offset = ngx_atoof(start, p - start);
+
+    if (offset >= u->conf->cache_max_range_offset) {
+        return NGX_DECLINED;
+    }
+
+    return NGX_OK;
+}
+
+#endif
+
+
+static void
+ngx_http_upstream_resolve_handler(ngx_resolver_ctx_t *ctx)
+{
+    ngx_uint_t                     run_posted;
+    ngx_connection_t              *c;
+    ngx_http_request_t            *r;
+    ngx_http_upstream_t           *u;
+    ngx_http_upstream_resolved_t  *ur;
+
+    run_posted = ctx->async;
+
+    r = ctx->data;
+    c = r->connection;
+
+    u = r->upstream;
+    ur = u->resolved;
+
+    ngx_http_set_log_request(c->log, r);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http upstream resolve: \"%V?%V\"", &r->uri, &r->args);
+
+    if (ctx->state) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "%V could not be resolved (%i: %s)",
+                      &ctx->name, ctx->state,
+                      ngx_resolver_strerror(ctx->state));
+
+        ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY);
+        goto failed;
+    }
+
+    ur->naddrs = ctx->naddrs;
+    ur->addrs = ctx->addrs;
+
+#if (NGX_DEBUG)
+    {
+    u_char      text[NGX_SOCKADDR_STRLEN];
+    ngx_str_t   addr;
+    ngx_uint_t  i;
+
+    addr.data = text;
+
+    for (i = 0; i < ctx->naddrs; i++) {
+        addr.len = ngx_sock_ntop(ur->addrs[i].sockaddr, ur->addrs[i].socklen,
+                                 text, NGX_SOCKADDR_STRLEN, 0);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "name was resolved to %V", &addr);
+    }
+    }
+#endif
+
+    if (ngx_http_upstream_create_round_robin_peer(r, ur) != NGX_OK) {
+        ngx_http_upstream_finalize_request(r, u,
+                                           NGX_HTTP_INTERNAL_SERVER_ERROR);
+        goto failed;
+    }
+
+    ngx_resolve_name_done(ctx);
+    ur->ctx = NULL;
+
+    u->peer.start_time = ngx_current_msec;
+
+    if (u->conf->next_upstream_tries
+        && u->peer.tries > u->conf->next_upstream_tries)
+    {
+        u->peer.tries = u->conf->next_upstream_tries;
+    }
+
+    ngx_http_upstream_connect(r, u);
+
+failed:
+
+    if (run_posted) {
+        ngx_http_run_posted_requests(c);
+    }
+}
+
+
+static void
+ngx_http_upstream_handler(ngx_event_t *ev)
+{
+    ngx_connection_t     *c;
+    ngx_http_request_t   *r;
+    ngx_http_upstream_t  *u;
+
+    c = ev->data;
+    r = c->data;
+
+    u = r->upstream;
+    c = r->connection;
+
+    ngx_http_set_log_request(c->log, r);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http upstream request: \"%V?%V\"", &r->uri, &r->args);
+
+    if (ev->delayed && ev->timedout) {
+        ev->delayed = 0;
+        ev->timedout = 0;
+    }
+
+    if (ev->write) {
+        u->write_event_handler(r, u);
+
+    } else {
+        u->read_event_handler(r, u);
+    }
+
+    ngx_http_run_posted_requests(c);
+}
+
+
+static void
+ngx_http_upstream_rd_check_broken_connection(ngx_http_request_t *r)
+{
+    ngx_http_upstream_check_broken_connection(r, r->connection->read);
+}
+
+
+static void
+ngx_http_upstream_wr_check_broken_connection(ngx_http_request_t *r)
+{
+    ngx_http_upstream_check_broken_connection(r, r->connection->write);
+}
+
+
+static void
+ngx_http_upstream_check_broken_connection(ngx_http_request_t *r,
+    ngx_event_t *ev)
+{
+    int                  n;
+    char                 buf[1];
+    ngx_err_t            err;
+    ngx_int_t            event;
+    ngx_connection_t     *c;
+    ngx_http_upstream_t  *u;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ev->log, 0,
+                   "http upstream check client, write event:%d, \"%V\"",
+                   ev->write, &r->uri);
+
+    c = r->connection;
+    u = r->upstream;
+
+    if (c->error) {
+        if ((ngx_event_flags & NGX_USE_LEVEL_EVENT) && ev->active) {
+
+            event = ev->write ? NGX_WRITE_EVENT : NGX_READ_EVENT;
+
+            if (ngx_del_event(ev, event, 0) != NGX_OK) {
+                ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+                return;
+            }
+        }
+
+        if (!u->cacheable) {
+            ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_CLIENT_CLOSED_REQUEST);
+        }
+
+        return;
+    }
+
+#if (NGX_HTTP_V2)
+    if (r->stream) {
+        return;
+    }
+#endif
+
+#if (NGX_HAVE_KQUEUE)
+
+    if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+
+        if (!ev->pending_eof) {
+            return;
+        }
+
+        ev->eof = 1;
+        c->error = 1;
+
+        if (ev->kq_errno) {
+            ev->error = 1;
+        }
+
+        if (!u->cacheable && u->peer.connection) {
+            ngx_log_error(NGX_LOG_INFO, ev->log, ev->kq_errno,
+                          "kevent() reported that client prematurely closed "
+                          "connection, so upstream connection is closed too");
+            ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_CLIENT_CLOSED_REQUEST);
+            return;
+        }
+
+        ngx_log_error(NGX_LOG_INFO, ev->log, ev->kq_errno,
+                      "kevent() reported that client prematurely closed "
+                      "connection");
+
+        if (u->peer.connection == NULL) {
+            ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_CLIENT_CLOSED_REQUEST);
+        }
+
+        return;
+    }
+
+#endif
+
+#if (NGX_HAVE_EPOLLRDHUP)
+
+    if ((ngx_event_flags & NGX_USE_EPOLL_EVENT) && ngx_use_epoll_rdhup) {
+        socklen_t  len;
+
+        if (!ev->pending_eof) {
+            return;
+        }
+
+        ev->eof = 1;
+        c->error = 1;
+
+        err = 0;
+        len = sizeof(ngx_err_t);
+
+        /*
+         * BSDs and Linux return 0 and set a pending error in err
+         * Solaris returns -1 and sets errno
+         */
+
+        if (ngxvcl_kvfd_getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len)
+            == -1)
+        {
+            err = ngx_socket_errno;
+        }
+
+        if (err) {
+            ev->error = 1;
+        }
+
+        if (!u->cacheable && u->peer.connection) {
+            ngx_log_error(NGX_LOG_INFO, ev->log, err,
+                        "epoll_wait() reported that client prematurely closed "
+                        "connection, so upstream connection is closed too");
+            ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_CLIENT_CLOSED_REQUEST);
+            return;
+        }
+
+        ngx_log_error(NGX_LOG_INFO, ev->log, err,
+                      "epoll_wait() reported that client prematurely closed "
+                      "connection");
+
+        if (u->peer.connection == NULL) {
+            ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_CLIENT_CLOSED_REQUEST);
+        }
+
+        return;
+    }
+
+#endif
+
+    n = ngxvcl_recv(c->fd, buf, 1, MSG_PEEK);
+
+    err = ngx_socket_errno;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ev->log, err,
+                   "http upstream recv(): %d", n);
+
+    if (ev->write && (n >= 0 || err == NGX_EAGAIN)) {
+        return;
+    }
+
+    if ((ngx_event_flags & NGX_USE_LEVEL_EVENT) && ev->active) {
+
+        event = ev->write ? NGX_WRITE_EVENT : NGX_READ_EVENT;
+
+        if (ngx_del_event(ev, event, 0) != NGX_OK) {
+            ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+    }
+
+    if (n > 0) {
+        return;
+    }
+
+    if (n == -1) {
+        if (err == NGX_EAGAIN) {
+            return;
+        }
+
+        ev->error = 1;
+
+    } else { /* n == 0 */
+        err = 0;
+    }
+
+    ev->eof = 1;
+    c->error = 1;
+
+    if (!u->cacheable && u->peer.connection) {
+        ngx_log_error(NGX_LOG_INFO, ev->log, err,
+                      "client prematurely closed connection, "
+                      "so upstream connection is closed too");
+        ngx_http_upstream_finalize_request(r, u,
+                                           NGX_HTTP_CLIENT_CLOSED_REQUEST);
+        return;
+    }
+
+    ngx_log_error(NGX_LOG_INFO, ev->log, err,
+                  "client prematurely closed connection");
+
+    if (u->peer.connection == NULL) {
+        ngx_http_upstream_finalize_request(r, u,
+                                           NGX_HTTP_CLIENT_CLOSED_REQUEST);
+    }
+}
+
+
+static void
+ngx_http_upstream_connect(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+    ngx_int_t          rc;
+    ngx_connection_t  *c;
+
+    r->connection->log->action = "connecting to upstream";
+
+    if (u->state && u->state->response_time) {
+        u->state->response_time = ngx_current_msec - u->state->response_time;
+    }
+
+    u->state = ngx_array_push(r->upstream_states);
+    if (u->state == NULL) {
+        ngx_http_upstream_finalize_request(r, u,
+                                           NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    ngx_memzero(u->state, sizeof(ngx_http_upstream_state_t));
+
+    u->state->response_time = ngx_current_msec;
+    u->state->connect_time = (ngx_msec_t) -1;
+    u->state->header_time = (ngx_msec_t) -1;
+
+    rc = ngx_event_connect_peer(&u->peer);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http upstream connect: %i", rc);
+
+    if (rc == NGX_ERROR) {
+        ngx_http_upstream_finalize_request(r, u,
+                                           NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    u->state->peer = u->peer.name;
+
+    if (rc == NGX_BUSY) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no live upstreams");
+        ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_NOLIVE);
+        return;
+    }
+
+    if (rc == NGX_DECLINED) {
+        ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
+        return;
+    }
+
+    /* rc == NGX_OK || rc == NGX_AGAIN || rc == NGX_DONE */
+
+    c = u->peer.connection;
+
+    c->data = r;
+
+    c->write->handler = ngx_http_upstream_handler;
+    c->read->handler = ngx_http_upstream_handler;
+
+    u->write_event_handler = ngx_http_upstream_send_request_handler;
+    u->read_event_handler = ngx_http_upstream_process_header;
+
+    c->sendfile &= r->connection->sendfile;
+    u->output.sendfile = c->sendfile;
+
+    if (c->pool == NULL) {
+
+        /* we need separate pool here to be able to cache SSL connections */
+
+        c->pool = ngx_create_pool(128, r->connection->log);
+        if (c->pool == NULL) {
+            ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+    }
+
+    c->log = r->connection->log;
+    c->pool->log = c->log;
+    c->read->log = c->log;
+    c->write->log = c->log;
+
+    /* init or reinit the ngx_output_chain() and ngx_chain_writer() contexts */
+
+    u->writer.out = NULL;
+    u->writer.last = &u->writer.out;
+    u->writer.connection = c;
+    u->writer.limit = 0;
+
+    if (u->request_sent) {
+        if (ngx_http_upstream_reinit(r, u) != NGX_OK) {
+            ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+    }
+
+    if (r->request_body
+        && r->request_body->buf
+        && r->request_body->temp_file
+        && r == r->main)
+    {
+        /*
+         * the r->request_body->buf can be reused for one request only,
+         * the subrequests should allocate their own temporary bufs
+         */
+
+        u->output.free = ngx_alloc_chain_link(r->pool);
+        if (u->output.free == NULL) {
+            ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        u->output.free->buf = r->request_body->buf;
+        u->output.free->next = NULL;
+        u->output.allocated = 1;
+
+        r->request_body->buf->pos = r->request_body->buf->start;
+        r->request_body->buf->last = r->request_body->buf->start;
+        r->request_body->buf->tag = u->output.tag;
+    }
+
+    u->request_sent = 0;
+    u->request_body_sent = 0;
+    u->request_body_blocked = 0;
+
+    if (rc == NGX_AGAIN) {
+        ngx_add_timer(c->write, u->conf->connect_timeout);
+        return;
+    }
+
+#if (NGX_HTTP_SSL)
+
+    if (u->ssl && c->ssl == NULL) {
+        ngx_http_upstream_ssl_init_connection(r, u, c);
+        return;
+    }
+
+#endif
+
+    ngx_http_upstream_send_request(r, u, 1);
+}
+
+
+#if (NGX_HTTP_SSL)
+
+static void
+ngx_http_upstream_ssl_init_connection(ngx_http_request_t *r,
+    ngx_http_upstream_t *u, ngx_connection_t *c)
+{
+    ngx_int_t                  rc;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    if (ngx_http_upstream_test_connect(c) != NGX_OK) {
+        ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
+        return;
+    }
+
+    if (ngx_ssl_create_connection(u->conf->ssl, c,
+                                  NGX_SSL_BUFFER|NGX_SSL_CLIENT)
+        != NGX_OK)
+    {
+        ngx_http_upstream_finalize_request(r, u,
+                                           NGX_HTTP_INTERNAL_SERVER_ERROR);
+        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,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+    }
+
+    if (u->conf->ssl_session_reuse) {
+        if (u->peer.set_session(&u->peer, u->peer.data) != NGX_OK) {
+            ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        /* abbreviated SSL handshake may interact badly with Nagle */
+
+        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+        if (clcf->tcp_nodelay && ngx_tcp_nodelay(c) != NGX_OK) {
+            ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+    }
+
+    r->connection->log->action = "SSL handshaking to upstream";
+
+    rc = ngx_ssl_handshake(c);
+
+    if (rc == NGX_AGAIN) {
+
+        if (!c->write->timer_set) {
+            ngx_add_timer(c->write, u->conf->connect_timeout);
+        }
+
+        c->ssl->handler = ngx_http_upstream_ssl_handshake_handler;
+        return;
+    }
+
+    ngx_http_upstream_ssl_handshake(r, u, c);
+}
+
+
+static void
+ngx_http_upstream_ssl_handshake_handler(ngx_connection_t *c)
+{
+    ngx_http_request_t   *r;
+    ngx_http_upstream_t  *u;
+
+    r = c->data;
+
+    u = r->upstream;
+    c = r->connection;
+
+    ngx_http_set_log_request(c->log, r);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http upstream ssl handshake: \"%V?%V\"",
+                   &r->uri, &r->args);
+
+    ngx_http_upstream_ssl_handshake(r, u, u->peer.connection);
+
+    ngx_http_run_posted_requests(c);
+}
+
+
+static void
+ngx_http_upstream_ssl_handshake(ngx_http_request_t *r, ngx_http_upstream_t *u,
+    ngx_connection_t *c)
+{
+    long  rc;
+
+    if (c->ssl->handshaked) {
+
+        if (u->conf->ssl_verify) {
+            rc = SSL_get_verify_result(c->ssl->connection);
+
+            if (rc != X509_V_OK) {
+                ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                              "upstream SSL certificate verify error: (%l:%s)",
+                              rc, X509_verify_cert_error_string(rc));
+                goto failed;
+            }
+
+            if (ngx_ssl_check_host(c, &u->ssl_name) != NGX_OK) {
+                ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                              "upstream SSL certificate does not match \"%V\"",
+                              &u->ssl_name);
+                goto failed;
+            }
+        }
+
+        if (u->conf->ssl_session_reuse) {
+            u->peer.save_session(&u->peer, u->peer.data);
+        }
+
+        c->write->handler = ngx_http_upstream_handler;
+        c->read->handler = ngx_http_upstream_handler;
+
+        ngx_http_upstream_send_request(r, u, 1);
+
+        return;
+    }
+
+    if (c->write->timedout) {
+        ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT);
+        return;
+    }
+
+failed:
+
+    ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
+}
+
+
+static ngx_int_t
+ngx_http_upstream_ssl_name(ngx_http_request_t *r, ngx_http_upstream_t *u,
+    ngx_connection_t *c)
+{
+    u_char     *p, *last;
+    ngx_str_t   name;
+
+    if (u->conf->ssl_name) {
+        if (ngx_http_complex_value(r, u->conf->ssl_name, &name) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+    } else {
+        name = u->ssl_name;
+    }
+
+    if (name.len == 0) {
+        goto done;
+    }
+
+    /*
+     * ssl name here may contain port, notably if derived from $proxy_host
+     * or $http_host; we have to strip it
+     */
+
+    p = name.data;
+    last = name.data + name.len;
+
+    if (*p == '[') {
+        p = ngx_strlchr(p, last, ']');
+
+        if (p == NULL) {
+            p = name.data;
+        }
+    }
+
+    p = ngx_strlchr(p, last, ':');
+
+    if (p != NULL) {
+        name.len = p - name.data;
+    }
+
+    if (!u->conf->ssl_server_name) {
+        goto done;
+    }
+
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+
+    /* as per RFC 6066, literal IPv4 and IPv6 addresses are not permitted */
+
+    if (name.len == 0 || *name.data == '[') {
+        goto done;
+    }
+
+    if (ngx_inet_addr(name.data, name.len) != INADDR_NONE) {
+        goto done;
+    }
+
+    /*
+     * SSL_set_tlsext_host_name() needs a null-terminated string,
+     * hence we explicitly null-terminate name here
+     */
+
+    p = ngx_pnalloc(r->pool, name.len + 1);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    (void) ngx_cpystrn(p, name.data, name.len + 1);
+
+    name.data = p;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "upstream SSL server name: \"%s\"", name.data);
+
+    if (SSL_set_tlsext_host_name(c->ssl->connection,
+                                 (char *) name.data)
+        == 0)
+    {
+        ngx_ssl_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "SSL_set_tlsext_host_name(\"%s\") failed", name.data);
+        return NGX_ERROR;
+    }
+
+#endif
+
+done:
+
+    u->ssl_name = name;
+
+    return NGX_OK;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_upstream_reinit(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+    off_t         file_pos;
+    ngx_chain_t  *cl;
+
+    if (u->reinit_request(r) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    u->keepalive = 0;
+    u->upgrade = 0;
+
+    ngx_memzero(&u->headers_in, sizeof(ngx_http_upstream_headers_in_t));
+    u->headers_in.content_length_n = -1;
+    u->headers_in.last_modified_time = -1;
+
+    if (ngx_list_init(&u->headers_in.headers, r->pool, 8,
+                      sizeof(ngx_table_elt_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_list_init(&u->headers_in.trailers, r->pool, 2,
+                      sizeof(ngx_table_elt_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    /* reinit the request chain */
+
+    file_pos = 0;
+
+    for (cl = u->request_bufs; cl; cl = cl->next) {
+        cl->buf->pos = cl->buf->start;
+
+        /* there is at most one file */
+
+        if (cl->buf->in_file) {
+            cl->buf->file_pos = file_pos;
+            file_pos = cl->buf->file_last;
+        }
+    }
+
+    /* reinit the subrequest's ngx_output_chain() context */
+
+    if (r->request_body && r->request_body->temp_file
+        && r != r->main && u->output.buf)
+    {
+        u->output.free = ngx_alloc_chain_link(r->pool);
+        if (u->output.free == NULL) {
+            return NGX_ERROR;
+        }
+
+        u->output.free->buf = u->output.buf;
+        u->output.free->next = NULL;
+
+        u->output.buf->pos = u->output.buf->start;
+        u->output.buf->last = u->output.buf->start;
+    }
+
+    u->output.buf = NULL;
+    u->output.in = NULL;
+    u->output.busy = NULL;
+
+    /* reinit u->buffer */
+
+    u->buffer.pos = u->buffer.start;
+
+#if (NGX_HTTP_CACHE)
+
+    if (r->cache) {
+        u->buffer.pos += r->cache->header_start;
+    }
+
+#endif
+
+    u->buffer.last = u->buffer.pos;
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_upstream_send_request(ngx_http_request_t *r, ngx_http_upstream_t *u,
+    ngx_uint_t do_write)
+{
+    ngx_int_t          rc;
+    ngx_connection_t  *c;
+
+    c = u->peer.connection;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http upstream send request");
+
+    if (u->state->connect_time == (ngx_msec_t) -1) {
+        u->state->connect_time = ngx_current_msec - u->state->response_time;
+    }
+
+    if (!u->request_sent && ngx_http_upstream_test_connect(c) != NGX_OK) {
+        ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
+        return;
+    }
+
+    c->log->action = "sending request to upstream";
+
+    rc = ngx_http_upstream_send_request_body(r, u, do_write);
+
+    if (rc == NGX_ERROR) {
+        ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
+        return;
+    }
+
+    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+        ngx_http_upstream_finalize_request(r, u, rc);
+        return;
+    }
+
+    if (rc == NGX_AGAIN) {
+        if (!c->write->ready || u->request_body_blocked) {
+            ngx_add_timer(c->write, u->conf->send_timeout);
+
+        } else if (c->write->timer_set) {
+            ngx_del_timer(c->write);
+        }
+
+        if (ngx_handle_write_event(c->write, u->conf->send_lowat) != NGX_OK) {
+            ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        if (c->write->ready && c->tcp_nopush == NGX_TCP_NOPUSH_SET) {
+            if (ngx_tcp_push(c->fd) == -1) {
+                ngx_log_error(NGX_LOG_CRIT, c->log, ngx_socket_errno,
+                              ngx_tcp_push_n " failed");
+                ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+                return;
+            }
+
+            c->tcp_nopush = NGX_TCP_NOPUSH_UNSET;
+        }
+
+        return;
+    }
+
+    /* rc == NGX_OK */
+
+    if (c->write->timer_set) {
+        ngx_del_timer(c->write);
+    }
+
+    if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) {
+        if (ngx_tcp_push(c->fd) == -1) {
+            ngx_log_error(NGX_LOG_CRIT, c->log, ngx_socket_errno,
+                          ngx_tcp_push_n " failed");
+            ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        c->tcp_nopush = NGX_TCP_NOPUSH_UNSET;
+    }
+
+    if (!u->conf->preserve_output) {
+        u->write_event_handler = ngx_http_upstream_dummy_handler;
+    }
+
+    if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+        ngx_http_upstream_finalize_request(r, u,
+                                           NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    if (!u->request_body_sent) {
+        u->request_body_sent = 1;
+
+        if (u->header_sent) {
+            return;
+        }
+
+        ngx_add_timer(c->read, u->conf->read_timeout);
+
+        if (c->read->ready) {
+            ngx_http_upstream_process_header(r, u);
+            return;
+        }
+    }
+}
+
+
+static ngx_int_t
+ngx_http_upstream_send_request_body(ngx_http_request_t *r,
+    ngx_http_upstream_t *u, ngx_uint_t do_write)
+{
+    ngx_int_t                  rc;
+    ngx_chain_t               *out, *cl, *ln;
+    ngx_connection_t          *c;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http upstream send request body");
+
+    if (!r->request_body_no_buffering) {
+
+        /* buffered request body */
+
+        if (!u->request_sent) {
+            u->request_sent = 1;
+            out = u->request_bufs;
+
+        } else {
+            out = NULL;
+        }
+
+        rc = ngx_output_chain(&u->output, out);
+
+        if (rc == NGX_AGAIN) {
+            u->request_body_blocked = 1;
+
+        } else {
+            u->request_body_blocked = 0;
+        }
+
+        return rc;
+    }
+
+    if (!u->request_sent) {
+        u->request_sent = 1;
+        out = u->request_bufs;
+
+        if (r->request_body->bufs) {
+            for (cl = out; cl->next; cl = out->next) { /* void */ }
+            cl->next = r->request_body->bufs;
+            r->request_body->bufs = NULL;
+        }
+
+        c = u->peer.connection;
+        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+        if (clcf->tcp_nodelay && ngx_tcp_nodelay(c) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        r->read_event_handler = ngx_http_upstream_read_request_handler;
+
+    } else {
+        out = NULL;
+    }
+
+    for ( ;; ) {
+
+        if (do_write) {
+            rc = ngx_output_chain(&u->output, out);
+
+            if (rc == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            while (out) {
+                ln = out;
+                out = out->next;
+                ngx_free_chain(r->pool, ln);
+            }
+
+            if (rc == NGX_AGAIN) {
+                u->request_body_blocked = 1;
+
+            } else {
+                u->request_body_blocked = 0;
+            }
+
+            if (rc == NGX_OK && !r->reading_body) {
+                break;
+            }
+        }
+
+        if (r->reading_body) {
+            /* read client request body */
+
+            rc = ngx_http_read_unbuffered_request_body(r);
+
+            if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+                return rc;
+            }
+
+            out = r->request_body->bufs;
+            r->request_body->bufs = NULL;
+        }
+
+        /* stop if there is nothing to send */
+
+        if (out == NULL) {
+            rc = NGX_AGAIN;
+            break;
+        }
+
+        do_write = 1;
+    }
+
+    if (!r->reading_body) {
+        if (!u->store && !r->post_action && !u->conf->ignore_client_abort) {
+            r->read_event_handler =
+                                  ngx_http_upstream_rd_check_broken_connection;
+        }
+    }
+
+    return rc;
+}
+
+
+static void
+ngx_http_upstream_send_request_handler(ngx_http_request_t *r,
+    ngx_http_upstream_t *u)
+{
+    ngx_connection_t  *c;
+
+    c = u->peer.connection;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http upstream send request handler");
+
+    if (c->write->timedout) {
+        ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT);
+        return;
+    }
+
+#if (NGX_HTTP_SSL)
+
+    if (u->ssl && c->ssl == NULL) {
+        ngx_http_upstream_ssl_init_connection(r, u, c);
+        return;
+    }
+
+#endif
+
+    if (u->header_sent && !u->conf->preserve_output) {
+        u->write_event_handler = ngx_http_upstream_dummy_handler;
+
+        (void) ngx_handle_write_event(c->write, 0);
+
+        return;
+    }
+
+    ngx_http_upstream_send_request(r, u, 1);
+}
+
+
+static void
+ngx_http_upstream_read_request_handler(ngx_http_request_t *r)
+{
+    ngx_connection_t     *c;
+    ngx_http_upstream_t  *u;
+
+    c = r->connection;
+    u = r->upstream;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http upstream read request handler");
+
+    if (c->read->timedout) {
+        c->timedout = 1;
+        ngx_http_upstream_finalize_request(r, u, NGX_HTTP_REQUEST_TIME_OUT);
+        return;
+    }
+
+    ngx_http_upstream_send_request(r, u, 0);
+}
+
+
+static void
+ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+    ssize_t            n;
+    ngx_int_t          rc;
+    ngx_connection_t  *c;
+
+    c = u->peer.connection;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http upstream process header");
+
+    c->log->action = "reading response header from upstream";
+
+    if (c->read->timedout) {
+        ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT);
+        return;
+    }
+
+    if (!u->request_sent && ngx_http_upstream_test_connect(c) != NGX_OK) {
+        ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
+        return;
+    }
+
+    if (u->buffer.start == NULL) {
+        u->buffer.start = ngx_palloc(r->pool, u->conf->buffer_size);
+        if (u->buffer.start == NULL) {
+            ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        u->buffer.pos = u->buffer.start;
+        u->buffer.last = u->buffer.start;
+        u->buffer.end = u->buffer.start + u->conf->buffer_size;
+        u->buffer.temporary = 1;
+
+        u->buffer.tag = u->output.tag;
+
+        if (ngx_list_init(&u->headers_in.headers, r->pool, 8,
+                          sizeof(ngx_table_elt_t))
+            != NGX_OK)
+        {
+            ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        if (ngx_list_init(&u->headers_in.trailers, r->pool, 2,
+                          sizeof(ngx_table_elt_t))
+            != NGX_OK)
+        {
+            ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+#if (NGX_HTTP_CACHE)
+
+        if (r->cache) {
+            u->buffer.pos += r->cache->header_start;
+            u->buffer.last = u->buffer.pos;
+        }
+#endif
+    }
+
+    for ( ;; ) {
+
+        n = c->recv(c, u->buffer.last, u->buffer.end - u->buffer.last);
+
+        if (n == NGX_AGAIN) {
+#if 0
+            ngx_add_timer(rev, u->read_timeout);
+#endif
+
+            if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+                ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+                return;
+            }
+
+            return;
+        }
+
+        if (n == 0) {
+            ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                          "upstream prematurely closed connection");
+        }
+
+        if (n == NGX_ERROR || n == 0) {
+            ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
+            return;
+        }
+
+        u->state->bytes_received += n;
+
+        u->buffer.last += n;
+
+#if 0
+        u->valid_header_in = 0;
+
+        u->peer.cached = 0;
+#endif
+
+        rc = u->process_header(r);
+
+        if (rc == NGX_AGAIN) {
+
+            if (u->buffer.last == u->buffer.end) {
+                ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                              "upstream sent too big header");
+
+                ngx_http_upstream_next(r, u,
+                                       NGX_HTTP_UPSTREAM_FT_INVALID_HEADER);
+                return;
+            }
+
+            continue;
+        }
+
+        break;
+    }
+
+    if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) {
+        ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_INVALID_HEADER);
+        return;
+    }
+
+    if (rc == NGX_ERROR) {
+        ngx_http_upstream_finalize_request(r, u,
+                                           NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    /* rc == NGX_OK */
+
+    u->state->header_time = ngx_current_msec - u->state->response_time;
+
+    if (u->headers_in.status_n >= NGX_HTTP_SPECIAL_RESPONSE) {
+
+        if (ngx_http_upstream_test_next(r, u) == NGX_OK) {
+            return;
+        }
+
+        if (ngx_http_upstream_intercept_errors(r, u) == NGX_OK) {
+            return;
+        }
+    }
+
+    if (ngx_http_upstream_process_headers(r, u) != NGX_OK) {
+        return;
+    }
+
+    ngx_http_upstream_send_response(r, u);
+}
+
+
+static ngx_int_t
+ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+    ngx_msec_t                 timeout;
+    ngx_uint_t                 status, mask;
+    ngx_http_upstream_next_t  *un;
+
+    status = u->headers_in.status_n;
+
+    for (un = ngx_http_upstream_next_errors; un->status; un++) {
+
+        if (status != un->status) {
+            continue;
+        }
+
+        timeout = u->conf->next_upstream_timeout;
+
+        if (u->request_sent
+            && (r->method & (NGX_HTTP_POST|NGX_HTTP_LOCK|NGX_HTTP_PATCH)))
+        {
+            mask = un->mask | NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT;
+
+        } else {
+            mask = un->mask;
+        }
+
+        if (u->peer.tries > 1
+            && ((u->conf->next_upstream & mask) == mask)
+            && !(u->request_sent && r->request_body_no_buffering)
+            && !(timeout && ngx_current_msec - u->peer.start_time >= timeout))
+        {
+            ngx_http_upstream_next(r, u, un->mask);
+            return NGX_OK;
+        }
+
+#if (NGX_HTTP_CACHE)
+
+        if (u->cache_status == NGX_HTTP_CACHE_EXPIRED
+            && ((u->conf->cache_use_stale & un->mask) || r->cache->stale_error))
+        {
+            ngx_int_t  rc;
+
+            rc = u->reinit_request(r);
+
+            if (rc != NGX_OK) {
+                ngx_http_upstream_finalize_request(r, u, rc);
+                return NGX_OK;
+            }
+
+            u->cache_status = NGX_HTTP_CACHE_STALE;
+            rc = ngx_http_upstream_cache_send(r, u);
+
+            if (rc == NGX_DONE) {
+                return NGX_OK;
+            }
+
+            if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) {
+                rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+            }
+
+            ngx_http_upstream_finalize_request(r, u, rc);
+            return NGX_OK;
+        }
+
+#endif
+    }
+
+#if (NGX_HTTP_CACHE)
+
+    if (status == NGX_HTTP_NOT_MODIFIED
+        && u->cache_status == NGX_HTTP_CACHE_EXPIRED
+        && u->conf->cache_revalidate)
+    {
+        time_t     now, valid, updating, error;
+        ngx_int_t  rc;
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http upstream not modified");
+
+        now = ngx_time();
+
+        valid = r->cache->valid_sec;
+        updating = r->cache->updating_sec;
+        error = r->cache->error_sec;
+
+        rc = u->reinit_request(r);
+
+        if (rc != NGX_OK) {
+            ngx_http_upstream_finalize_request(r, u, rc);
+            return NGX_OK;
+        }
+
+        u->cache_status = NGX_HTTP_CACHE_REVALIDATED;
+        rc = ngx_http_upstream_cache_send(r, u);
+
+        if (rc == NGX_DONE) {
+            return NGX_OK;
+        }
+
+        if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) {
+            rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        if (valid == 0) {
+            valid = r->cache->valid_sec;
+            updating = r->cache->updating_sec;
+            error = r->cache->error_sec;
+        }
+
+        if (valid == 0) {
+            valid = ngx_http_file_cache_valid(u->conf->cache_valid,
+                                              u->headers_in.status_n);
+            if (valid) {
+                valid = now + valid;
+            }
+        }
+
+        if (valid) {
+            r->cache->valid_sec = valid;
+            r->cache->updating_sec = updating;
+            r->cache->error_sec = error;
+
+            r->cache->date = now;
+
+            ngx_http_file_cache_update_header(r);
+        }
+
+        ngx_http_upstream_finalize_request(r, u, rc);
+        return NGX_OK;
+    }
+
+#endif
+
+    return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_intercept_errors(ngx_http_request_t *r,
+    ngx_http_upstream_t *u)
+{
+    ngx_int_t                  status;
+    ngx_uint_t                 i;
+    ngx_table_elt_t           *h;
+    ngx_http_err_page_t       *err_page;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    status = u->headers_in.status_n;
+
+    if (status == NGX_HTTP_NOT_FOUND && u->conf->intercept_404) {
+        ngx_http_upstream_finalize_request(r, u, NGX_HTTP_NOT_FOUND);
+        return NGX_OK;
+    }
+
+    if (!u->conf->intercept_errors) {
+        return NGX_DECLINED;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (clcf->error_pages == NULL) {
+        return NGX_DECLINED;
+    }
+
+    err_page = clcf->error_pages->elts;
+    for (i = 0; i < clcf->error_pages->nelts; i++) {
+
+        if (err_page[i].status == status) {
+
+            if (status == NGX_HTTP_UNAUTHORIZED
+                && u->headers_in.www_authenticate)
+            {
+                h = ngx_list_push(&r->headers_out.headers);
+
+                if (h == NULL) {
+                    ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+                    return NGX_OK;
+                }
+
+                *h = *u->headers_in.www_authenticate;
+
+                r->headers_out.www_authenticate = h;
+            }
+
+#if (NGX_HTTP_CACHE)
+
+            if (r->cache) {
+
+                if (u->cacheable) {
+                    time_t  valid;
+
+                    valid = r->cache->valid_sec;
+
+                    if (valid == 0) {
+                        valid = ngx_http_file_cache_valid(u->conf->cache_valid,
+                                                          status);
+                        if (valid) {
+                            r->cache->valid_sec = ngx_time() + valid;
+                        }
+                    }
+
+                    if (valid) {
+                        r->cache->error = status;
+                    }
+                }
+
+                ngx_http_file_cache_free(r->cache, u->pipe->temp_file);
+            }
+#endif
+            ngx_http_upstream_finalize_request(r, u, status);
+
+            return NGX_OK;
+        }
+    }
+
+    return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_test_connect(ngx_connection_t *c)
+{
+    int        err;
+    socklen_t  len;
+
+#if (NGX_HAVE_KQUEUE)
+
+    if (ngx_event_flags & NGX_USE_KQUEUE_EVENT)  {
+        if (c->write->pending_eof || c->read->pending_eof) {
+            if (c->write->pending_eof) {
+                err = c->write->kq_errno;
+
+            } else {
+                err = c->read->kq_errno;
+            }
+
+            c->log->action = "connecting to upstream";
+            (void) ngx_connection_error(c, err,
+                                    "kevent() reported that connect() failed");
+            return NGX_ERROR;
+        }
+
+    } else
+#endif
+    {
+        err = 0;
+        len = sizeof(int);
+
+        /*
+         * BSDs and Linux return 0 and set a pending error in err
+         * Solaris returns -1 and sets errno
+         */
+
+        if (ngxvcl_kvfd_getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len)
+            == -1)
+        {
+            err = ngx_socket_errno;
+        }
+
+        if (err) {
+            c->log->action = "connecting to upstream";
+            (void) ngx_connection_error(c, err, "connect() failed");
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_headers(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+    ngx_str_t                       uri, args;
+    ngx_uint_t                      i, flags;
+    ngx_list_part_t                *part;
+    ngx_table_elt_t                *h;
+    ngx_http_upstream_header_t     *hh;
+    ngx_http_upstream_main_conf_t  *umcf;
+
+    umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);
+
+    if (u->headers_in.x_accel_redirect
+        && !(u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT))
+    {
+        ngx_http_upstream_finalize_request(r, u, NGX_DECLINED);
+
+        part = &u->headers_in.headers.part;
+        h = part->elts;
+
+        for (i = 0; /* void */; i++) {
+
+            if (i >= part->nelts) {
+                if (part->next == NULL) {
+                    break;
+                }
+
+                part = part->next;
+                h = part->elts;
+                i = 0;
+            }
+
+            hh = ngx_hash_find(&umcf->headers_in_hash, h[i].hash,
+                               h[i].lowcase_key, h[i].key.len);
+
+            if (hh && hh->redirect) {
+                if (hh->copy_handler(r, &h[i], hh->conf) != NGX_OK) {
+                    ngx_http_finalize_request(r,
+                                              NGX_HTTP_INTERNAL_SERVER_ERROR);
+                    return NGX_DONE;
+                }
+            }
+        }
+
+        uri = u->headers_in.x_accel_redirect->value;
+
+        if (uri.data[0] == '@') {
+            ngx_http_named_location(r, &uri);
+
+        } else {
+            ngx_str_null(&args);
+            flags = NGX_HTTP_LOG_UNSAFE;
+
+            if (ngx_http_parse_unsafe_uri(r, &uri, &args, &flags) != NGX_OK) {
+                ngx_http_finalize_request(r, NGX_HTTP_NOT_FOUND);
+                return NGX_DONE;
+            }
+
+            if (r->method != NGX_HTTP_HEAD) {
+                r->method = NGX_HTTP_GET;
+                r->method_name = ngx_http_core_get_method;
+            }
+
+            ngx_http_internal_redirect(r, &uri, &args);
+        }
+
+        ngx_http_finalize_request(r, NGX_DONE);
+        return NGX_DONE;
+    }
+
+    part = &u->headers_in.headers.part;
+    h = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            h = part->elts;
+            i = 0;
+        }
+
+        if (ngx_hash_find(&u->conf->hide_headers_hash, h[i].hash,
+                          h[i].lowcase_key, h[i].key.len))
+        {
+            continue;
+        }
+
+        hh = ngx_hash_find(&umcf->headers_in_hash, h[i].hash,
+                           h[i].lowcase_key, h[i].key.len);
+
+        if (hh) {
+            if (hh->copy_handler(r, &h[i], hh->conf) != NGX_OK) {
+                ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+                return NGX_DONE;
+            }
+
+            continue;
+        }
+
+        if (ngx_http_upstream_copy_header_line(r, &h[i], 0) != NGX_OK) {
+            ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return NGX_DONE;
+        }
+    }
+
+    if (r->headers_out.server && r->headers_out.server->value.data == NULL) {
+        r->headers_out.server->hash = 0;
+    }
+
+    if (r->headers_out.date && r->headers_out.date->value.data == NULL) {
+        r->headers_out.date->hash = 0;
+    }
+
+    r->headers_out.status = u->headers_in.status_n;
+    r->headers_out.status_line = u->headers_in.status_line;
+
+    r->headers_out.content_length_n = u->headers_in.content_length_n;
+
+    r->disable_not_modified = !u->cacheable;
+
+    if (u->conf->force_ranges) {
+        r->allow_ranges = 1;
+        r->single_range = 1;
+
+#if (NGX_HTTP_CACHE)
+        if (r->cached) {
+            r->single_range = 0;
+        }
+#endif
+    }
+
+    u->length = -1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_trailers(ngx_http_request_t *r,
+    ngx_http_upstream_t *u)
+{
+    ngx_uint_t        i;
+    ngx_list_part_t  *part;
+    ngx_table_elt_t  *h, *ho;
+
+    if (!u->conf->pass_trailers) {
+        return NGX_OK;
+    }
+
+    part = &u->headers_in.trailers.part;
+    h = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            h = part->elts;
+            i = 0;
+        }
+
+        if (ngx_hash_find(&u->conf->hide_headers_hash, h[i].hash,
+                          h[i].lowcase_key, h[i].key.len))
+        {
+            continue;
+        }
+
+        ho = ngx_list_push(&r->headers_out.trailers);
+        if (ho == NULL) {
+            return NGX_ERROR;
+        }
+
+        *ho = h[i];
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_upstream_send_response(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+    ssize_t                    n;
+    ngx_int_t                  rc;
+    ngx_event_pipe_t          *p;
+    ngx_connection_t          *c;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    rc = ngx_http_send_header(r);
+
+    if (rc == NGX_ERROR || rc > NGX_OK || r->post_action) {
+        ngx_http_upstream_finalize_request(r, u, rc);
+        return;
+    }
+
+    u->header_sent = 1;
+
+    if (u->upgrade) {
+
+#if (NGX_HTTP_CACHE)
+
+        if (r->cache) {
+            ngx_http_file_cache_free(r->cache, u->pipe->temp_file);
+        }
+
+#endif
+
+        ngx_http_upstream_upgrade(r, u);
+        return;
+    }
+
+    c = r->connection;
+
+    if (r->header_only) {
+
+        if (!u->buffering) {
+            ngx_http_upstream_finalize_request(r, u, rc);
+            return;
+        }
+
+        if (!u->cacheable && !u->store) {
+            ngx_http_upstream_finalize_request(r, u, rc);
+            return;
+        }
+
+        u->pipe->downstream_error = 1;
+    }
+
+    if (r->request_body && r->request_body->temp_file
+        && r == r->main && !r->preserve_body
+        && !u->conf->preserve_output)
+    {
+        ngx_pool_run_cleanup_file(r->pool, r->request_body->temp_file->file.fd);
+        r->request_body->temp_file->file.fd = NGX_INVALID_FILE;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (!u->buffering) {
+
+#if (NGX_HTTP_CACHE)
+
+        if (r->cache) {
+            ngx_http_file_cache_free(r->cache, u->pipe->temp_file);
+        }
+
+#endif
+
+        if (u->input_filter == NULL) {
+            u->input_filter_init = ngx_http_upstream_non_buffered_filter_init;
+            u->input_filter = ngx_http_upstream_non_buffered_filter;
+            u->input_filter_ctx = r;
+        }
+
+        u->read_event_handler = ngx_http_upstream_process_non_buffered_upstream;
+        r->write_event_handler =
+                             ngx_http_upstream_process_non_buffered_downstream;
+
+        r->limit_rate = 0;
+
+        if (u->input_filter_init(u->input_filter_ctx) == NGX_ERROR) {
+            ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+            return;
+        }
+
+        if (clcf->tcp_nodelay && ngx_tcp_nodelay(c) != NGX_OK) {
+            ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+            return;
+        }
+
+        n = u->buffer.last - u->buffer.pos;
+
+        if (n) {
+            u->buffer.last = u->buffer.pos;
+
+            u->state->response_length += n;
+
+            if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) {
+                ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+                return;
+            }
+
+            ngx_http_upstream_process_non_buffered_downstream(r);
+
+        } else {
+            u->buffer.pos = u->buffer.start;
+            u->buffer.last = u->buffer.start;
+
+            if (ngx_http_send_special(r, NGX_HTTP_FLUSH) == NGX_ERROR) {
+                ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+                return;
+            }
+
+            if (u->peer.connection->read->ready || u->length == 0) {
+                ngx_http_upstream_process_non_buffered_upstream(r, u);
+            }
+        }
+
+        return;
+    }
+
+    /* TODO: preallocate event_pipe bufs, look "Content-Length" */
+
+#if (NGX_HTTP_CACHE)
+
+    if (r->cache && r->cache->file.fd != NGX_INVALID_FILE) {
+        ngx_pool_run_cleanup_file(r->pool, r->cache->file.fd);
+        r->cache->file.fd = NGX_INVALID_FILE;
+    }
+
+    switch (ngx_http_test_predicates(r, u->conf->no_cache)) {
+
+    case NGX_ERROR:
+        ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+        return;
+
+    case NGX_DECLINED:
+        u->cacheable = 0;
+        break;
+
+    default: /* NGX_OK */
+
+        if (u->cache_status == NGX_HTTP_CACHE_BYPASS) {
+
+            /* create cache if previously bypassed */
+
+            if (ngx_http_file_cache_create(r) != NGX_OK) {
+                ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+                return;
+            }
+        }
+
+        break;
+    }
+
+    if (u->cacheable) {
+        time_t  now, valid;
+
+        now = ngx_time();
+
+        valid = r->cache->valid_sec;
+
+        if (valid == 0) {
+            valid = ngx_http_file_cache_valid(u->conf->cache_valid,
+                                              u->headers_in.status_n);
+            if (valid) {
+                r->cache->valid_sec = now + valid;
+            }
+        }
+
+        if (valid) {
+            r->cache->date = now;
+            r->cache->body_start = (u_short) (u->buffer.pos - u->buffer.start);
+
+            if (u->headers_in.status_n == NGX_HTTP_OK
+                || u->headers_in.status_n == NGX_HTTP_PARTIAL_CONTENT)
+            {
+                r->cache->last_modified = u->headers_in.last_modified_time;
+
+                if (u->headers_in.etag) {
+                    r->cache->etag = u->headers_in.etag->value;
+
+                } else {
+                    ngx_str_null(&r->cache->etag);
+                }
+
+            } else {
+                r->cache->last_modified = -1;
+                ngx_str_null(&r->cache->etag);
+            }
+
+            if (ngx_http_file_cache_set_header(r, u->buffer.start) != NGX_OK) {
+                ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+                return;
+            }
+
+        } else {
+            u->cacheable = 0;
+        }
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http cacheable: %d", u->cacheable);
+
+    if (u->cacheable == 0 && r->cache) {
+        ngx_http_file_cache_free(r->cache, u->pipe->temp_file);
+    }
+
+    if (r->header_only && !u->cacheable && !u->store) {
+        ngx_http_upstream_finalize_request(r, u, 0);
+        return;
+    }
+
+#endif
+
+    p = u->pipe;
+
+    p->output_filter = ngx_http_upstream_output_filter;
+    p->output_ctx = r;
+    p->tag = u->output.tag;
+    p->bufs = u->conf->bufs;
+    p->busy_size = u->conf->busy_buffers_size;
+    p->upstream = u->peer.connection;
+    p->downstream = c;
+    p->pool = r->pool;
+    p->log = c->log;
+    p->limit_rate = u->conf->limit_rate;
+    p->start_sec = ngx_time();
+
+    p->cacheable = u->cacheable || u->store;
+
+    p->temp_file = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t));
+    if (p->temp_file == NULL) {
+        ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+        return;
+    }
+
+    p->temp_file->file.fd = NGX_INVALID_FILE;
+    p->temp_file->file.log = c->log;
+    p->temp_file->path = u->conf->temp_path;
+    p->temp_file->pool = r->pool;
+
+    if (p->cacheable) {
+        p->temp_file->persistent = 1;
+
+#if (NGX_HTTP_CACHE)
+        if (r->cache && !r->cache->file_cache->use_temp_path) {
+            p->temp_file->path = r->cache->file_cache->path;
+            p->temp_file->file.name = r->cache->file.name;
+        }
+#endif
+
+    } else {
+        p->temp_file->log_level = NGX_LOG_WARN;
+        p->temp_file->warn = "an upstream response is buffered "
+                             "to a temporary file";
+    }
+
+    p->max_temp_file_size = u->conf->max_temp_file_size;
+    p->temp_file_write_size = u->conf->temp_file_write_size;
+
+#if (NGX_THREADS)
+    if (clcf->aio == NGX_HTTP_AIO_THREADS && clcf->aio_write) {
+        p->thread_handler = ngx_http_upstream_thread_handler;
+        p->thread_ctx = r;
+    }
+#endif
+
+    p->preread_bufs = ngx_alloc_chain_link(r->pool);
+    if (p->preread_bufs == NULL) {
+        ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+        return;
+    }
+
+    p->preread_bufs->buf = &u->buffer;
+    p->preread_bufs->next = NULL;
+    u->buffer.recycled = 1;
+
+    p->preread_size = u->buffer.last - u->buffer.pos;
+
+    if (u->cacheable) {
+
+        p->buf_to_file = ngx_calloc_buf(r->pool);
+        if (p->buf_to_file == NULL) {
+            ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+            return;
+        }
+
+        p->buf_to_file->start = u->buffer.start;
+        p->buf_to_file->pos = u->buffer.start;
+        p->buf_to_file->last = u->buffer.pos;
+        p->buf_to_file->temporary = 1;
+    }
+
+    if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
+        /* the posted aio operation may corrupt a shadow buffer */
+        p->single_buf = 1;
+    }
+
+    /* TODO: p->free_bufs = 0 if use ngx_create_chain_of_bufs() */
+    p->free_bufs = 1;
+
+    /*
+     * event_pipe would do u->buffer.last += p->preread_size
+     * as though these bytes were read
+     */
+    u->buffer.last = u->buffer.pos;
+
+    if (u->conf->cyclic_temp_file) {
+
+        /*
+         * we need to disable the use of sendfile() if we use cyclic temp file
+         * because the writing a new data may interfere with sendfile()
+         * that uses the same kernel file pages (at least on FreeBSD)
+         */
+
+        p->cyclic_temp_file = 1;
+        c->sendfile = 0;
+
+    } else {
+        p->cyclic_temp_file = 0;
+    }
+
+    p->read_timeout = u->conf->read_timeout;
+    p->send_timeout = clcf->send_timeout;
+    p->send_lowat = clcf->send_lowat;
+
+    p->length = -1;
+
+    if (u->input_filter_init
+        && u->input_filter_init(p->input_ctx) != NGX_OK)
+    {
+        ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+        return;
+    }
+
+    u->read_event_handler = ngx_http_upstream_process_upstream;
+    r->write_event_handler = ngx_http_upstream_process_downstream;
+
+    ngx_http_upstream_process_upstream(r, u);
+}
+
+
+static void
+ngx_http_upstream_upgrade(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+    ngx_connection_t          *c;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    c = r->connection;
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    /* TODO: prevent upgrade if not requested or not possible */
+
+    if (r != r->main) {
+        ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                      "connection upgrade in subrequest");
+        ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+        return;
+    }
+
+    r->keepalive = 0;
+    c->log->action = "proxying upgraded connection";
+
+    u->read_event_handler = ngx_http_upstream_upgraded_read_upstream;
+    u->write_event_handler = ngx_http_upstream_upgraded_write_upstream;
+    r->read_event_handler = ngx_http_upstream_upgraded_read_downstream;
+    r->write_event_handler = ngx_http_upstream_upgraded_write_downstream;
+
+    if (clcf->tcp_nodelay) {
+
+        if (ngx_tcp_nodelay(c) != NGX_OK) {
+            ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+            return;
+        }
+
+        if (ngx_tcp_nodelay(u->peer.connection) != NGX_OK) {
+            ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+            return;
+        }
+    }
+
+    if (ngx_http_send_special(r, NGX_HTTP_FLUSH) == NGX_ERROR) {
+        ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+        return;
+    }
+
+    if (u->peer.connection->read->ready
+        || u->buffer.pos != u->buffer.last)
+    {
+        ngx_post_event(c->read, &ngx_posted_events);
+        ngx_http_upstream_process_upgraded(r, 1, 1);
+        return;
+    }
+
+    ngx_http_upstream_process_upgraded(r, 0, 1);
+}
+
+
+static void
+ngx_http_upstream_upgraded_read_downstream(ngx_http_request_t *r)
+{
+    ngx_http_upstream_process_upgraded(r, 0, 0);
+}
+
+
+static void
+ngx_http_upstream_upgraded_write_downstream(ngx_http_request_t *r)
+{
+    ngx_http_upstream_process_upgraded(r, 1, 1);
+}
+
+
+static void
+ngx_http_upstream_upgraded_read_upstream(ngx_http_request_t *r,
+    ngx_http_upstream_t *u)
+{
+    ngx_http_upstream_process_upgraded(r, 1, 0);
+}
+
+
+static void
+ngx_http_upstream_upgraded_write_upstream(ngx_http_request_t *r,
+    ngx_http_upstream_t *u)
+{
+    ngx_http_upstream_process_upgraded(r, 0, 1);
+}
+
+
+static void
+ngx_http_upstream_process_upgraded(ngx_http_request_t *r,
+    ngx_uint_t from_upstream, ngx_uint_t do_write)
+{
+    size_t                     size;
+    ssize_t                    n;
+    ngx_buf_t                 *b;
+    ngx_connection_t          *c, *downstream, *upstream, *dst, *src;
+    ngx_http_upstream_t       *u;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    c = r->connection;
+    u = r->upstream;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http upstream process upgraded, fu:%ui", from_upstream);
+
+    downstream = c;
+    upstream = u->peer.connection;
+
+    if (downstream->write->timedout) {
+        c->timedout = 1;
+        ngx_connection_error(c, NGX_ETIMEDOUT, "client timed out");
+        ngx_http_upstream_finalize_request(r, u, NGX_HTTP_REQUEST_TIME_OUT);
+        return;
+    }
+
+    if (upstream->read->timedout || upstream->write->timedout) {
+        ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out");
+        ngx_http_upstream_finalize_request(r, u, NGX_HTTP_GATEWAY_TIME_OUT);
+        return;
+    }
+
+    if (from_upstream) {
+        src = upstream;
+        dst = downstream;
+        b = &u->buffer;
+
+    } else {
+        src = downstream;
+        dst = upstream;
+        b = &u->from_client;
+
+        if (r->header_in->last > r->header_in->pos) {
+            b = r->header_in;
+            b->end = b->last;
+            do_write = 1;
+        }
+
+        if (b->start == NULL) {
+            b->start = ngx_palloc(r->pool, u->conf->buffer_size);
+            if (b->start == NULL) {
+                ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+                return;
+            }
+
+            b->pos = b->start;
+            b->last = b->start;
+            b->end = b->start + u->conf->buffer_size;
+            b->temporary = 1;
+            b->tag = u->output.tag;
+        }
+    }
+
+    for ( ;; ) {
+
+        if (do_write) {
+
+            size = b->last - b->pos;
+
+            if (size && dst->write->ready) {
+
+                n = dst->send(dst, b->pos, size);
+
+                if (n == NGX_ERROR) {
+                    ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+                    return;
+                }
+
+                if (n > 0) {
+                    b->pos += n;
+
+                    if (b->pos == b->last) {
+                        b->pos = b->start;
+                        b->last = b->start;
+                    }
+                }
+            }
+        }
+
+        size = b->end - b->last;
+
+        if (size && src->read->ready) {
+
+            n = src->recv(src, b->last, size);
+
+            if (n == NGX_AGAIN || n == 0) {
+                break;
+            }
+
+            if (n > 0) {
+                do_write = 1;
+                b->last += n;
+
+                if (from_upstream) {
+                    u->state->bytes_received += n;
+                }
+
+                continue;
+            }
+
+            if (n == NGX_ERROR) {
+                src->read->eof = 1;
+            }
+        }
+
+        break;
+    }
+
+    if ((upstream->read->eof && u->buffer.pos == u->buffer.last)
+        || (downstream->read->eof && u->from_client.pos == u->from_client.last)
+        || (downstream->read->eof && upstream->read->eof))
+    {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http upstream upgraded done");
+        ngx_http_upstream_finalize_request(r, u, 0);
+        return;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (ngx_handle_write_event(upstream->write, u->conf->send_lowat)
+        != NGX_OK)
+    {
+        ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+        return;
+    }
+
+    if (upstream->write->active && !upstream->write->ready) {
+        ngx_add_timer(upstream->write, u->conf->send_timeout);
+
+    } else if (upstream->write->timer_set) {
+        ngx_del_timer(upstream->write);
+    }
+
+    if (ngx_handle_read_event(upstream->read, 0) != NGX_OK) {
+        ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+        return;
+    }
+
+    if (upstream->read->active && !upstream->read->ready) {
+        ngx_add_timer(upstream->read, u->conf->read_timeout);
+
+    } else if (upstream->read->timer_set) {
+        ngx_del_timer(upstream->read);
+    }
+
+    if (ngx_handle_write_event(downstream->write, clcf->send_lowat)
+        != NGX_OK)
+    {
+        ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+        return;
+    }
+
+    if (ngx_handle_read_event(downstream->read, 0) != NGX_OK) {
+        ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+        return;
+    }
+
+    if (downstream->write->active && !downstream->write->ready) {
+        ngx_add_timer(downstream->write, clcf->send_timeout);
+
+    } else if (downstream->write->timer_set) {
+        ngx_del_timer(downstream->write);
+    }
+}
+
+
+static void
+ngx_http_upstream_process_non_buffered_downstream(ngx_http_request_t *r)
+{
+    ngx_event_t          *wev;
+    ngx_connection_t     *c;
+    ngx_http_upstream_t  *u;
+
+    c = r->connection;
+    u = r->upstream;
+    wev = c->write;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http upstream process non buffered downstream");
+
+    c->log->action = "sending to client";
+
+    if (wev->timedout) {
+        c->timedout = 1;
+        ngx_connection_error(c, NGX_ETIMEDOUT, "client timed out");
+        ngx_http_upstream_finalize_request(r, u, NGX_HTTP_REQUEST_TIME_OUT);
+        return;
+    }
+
+    ngx_http_upstream_process_non_buffered_request(r, 1);
+}
+
+
+static void
+ngx_http_upstream_process_non_buffered_upstream(ngx_http_request_t *r,
+    ngx_http_upstream_t *u)
+{
+    ngx_connection_t  *c;
+
+    c = u->peer.connection;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http upstream process non buffered upstream");
+
+    c->log->action = "reading upstream";
+
+    if (c->read->timedout) {
+        ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out");
+        ngx_http_upstream_finalize_request(r, u, NGX_HTTP_GATEWAY_TIME_OUT);
+        return;
+    }
+
+    ngx_http_upstream_process_non_buffered_request(r, 0);
+}
+
+
+static void
+ngx_http_upstream_process_non_buffered_request(ngx_http_request_t *r,
+    ngx_uint_t do_write)
+{
+    size_t                     size;
+    ssize_t                    n;
+    ngx_buf_t                 *b;
+    ngx_int_t                  rc;
+    ngx_connection_t          *downstream, *upstream;
+    ngx_http_upstream_t       *u;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    u = r->upstream;
+    downstream = r->connection;
+    upstream = u->peer.connection;
+
+    b = &u->buffer;
+
+    do_write = do_write || u->length == 0;
+
+    for ( ;; ) {
+
+        if (do_write) {
+
+            if (u->out_bufs || u->busy_bufs || downstream->buffered) {
+                rc = ngx_http_output_filter(r, u->out_bufs);
+
+                if (rc == NGX_ERROR) {
+                    ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+                    return;
+                }
+
+                ngx_chain_update_chains(r->pool, &u->free_bufs, &u->busy_bufs,
+                                        &u->out_bufs, u->output.tag);
+            }
+
+            if (u->busy_bufs == NULL) {
+
+                if (u->length == 0
+                    || (upstream->read->eof && u->length == -1))
+                {
+                    ngx_http_upstream_finalize_request(r, u, 0);
+                    return;
+                }
+
+                if (upstream->read->eof) {
+                    ngx_log_error(NGX_LOG_ERR, upstream->log, 0,
+                                  "upstream prematurely closed connection");
+
+                    ngx_http_upstream_finalize_request(r, u,
+                                                       NGX_HTTP_BAD_GATEWAY);
+                    return;
+                }
+
+                if (upstream->read->error) {
+                    ngx_http_upstream_finalize_request(r, u,
+                                                       NGX_HTTP_BAD_GATEWAY);
+                    return;
+                }
+
+                b->pos = b->start;
+                b->last = b->start;
+            }
+        }
+
+        size = b->end - b->last;
+
+        if (size && upstream->read->ready) {
+
+            n = upstream->recv(upstream, b->last, size);
+
+            if (n == NGX_AGAIN) {
+                break;
+            }
+
+            if (n > 0) {
+                u->state->bytes_received += n;
+                u->state->response_length += n;
+
+                if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) {
+                    ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+                    return;
+                }
+            }
+
+            do_write = 1;
+
+            continue;
+        }
+
+        break;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (downstream->data == r) {
+        if (ngx_handle_write_event(downstream->write, clcf->send_lowat)
+            != NGX_OK)
+        {
+            ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+            return;
+        }
+    }
+
+    if (downstream->write->active && !downstream->write->ready) {
+        ngx_add_timer(downstream->write, clcf->send_timeout);
+
+    } else if (downstream->write->timer_set) {
+        ngx_del_timer(downstream->write);
+    }
+
+    if (ngx_handle_read_event(upstream->read, 0) != NGX_OK) {
+        ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+        return;
+    }
+
+    if (upstream->read->active && !upstream->read->ready) {
+        ngx_add_timer(upstream->read, u->conf->read_timeout);
+
+    } else if (upstream->read->timer_set) {
+        ngx_del_timer(upstream->read);
+    }
+}
+
+
+static ngx_int_t
+ngx_http_upstream_non_buffered_filter_init(void *data)
+{
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_non_buffered_filter(void *data, ssize_t bytes)
+{
+    ngx_http_request_t  *r = data;
+
+    ngx_buf_t            *b;
+    ngx_chain_t          *cl, **ll;
+    ngx_http_upstream_t  *u;
+
+    u = r->upstream;
+
+    for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) {
+        ll = &cl->next;
+    }
+
+    cl = ngx_chain_get_free_buf(r->pool, &u->free_bufs);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    *ll = cl;
+
+    cl->buf->flush = 1;
+    cl->buf->memory = 1;
+
+    b = &u->buffer;
+
+    cl->buf->pos = b->last;
+    b->last += bytes;
+    cl->buf->last = b->last;
+    cl->buf->tag = u->output.tag;
+
+    if (u->length == -1) {
+        return NGX_OK;
+    }
+
+    u->length -= bytes;
+
+    return NGX_OK;
+}
+
+
+#if (NGX_THREADS)
+
+static ngx_int_t
+ngx_http_upstream_thread_handler(ngx_thread_task_t *task, ngx_file_t *file)
+{
+    ngx_str_t                  name;
+    ngx_event_pipe_t          *p;
+    ngx_thread_pool_t         *tp;
+    ngx_http_request_t        *r;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    r = file->thread_ctx;
+    p = r->upstream->pipe;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+    tp = clcf->thread_pool;
+
+    if (tp == NULL) {
+        if (ngx_http_complex_value(r, clcf->thread_pool_value, &name)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+
+        tp = ngx_thread_pool_get((ngx_cycle_t *) ngx_cycle, &name);
+
+        if (tp == NULL) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "thread pool \"%V\" not found", &name);
+            return NGX_ERROR;
+        }
+    }
+
+    task->event.data = r;
+    task->event.handler = ngx_http_upstream_thread_event_handler;
+
+    if (ngx_thread_task_post(tp, task) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    r->main->blocked++;
+    r->aio = 1;
+    p->aio = 1;
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_upstream_thread_event_handler(ngx_event_t *ev)
+{
+    ngx_connection_t    *c;
+    ngx_http_request_t  *r;
+
+    r = ev->data;
+    c = r->connection;
+
+    ngx_http_set_log_request(c->log, r);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http upstream thread: \"%V?%V\"", &r->uri, &r->args);
+
+    r->main->blocked--;
+    r->aio = 0;
+
+    if (r->done) {
+        /*
+         * trigger connection event handler if the subrequest was
+         * already finalized; this can happen if the handler is used
+         * for sendfile() in threads
+         */
+
+        c->write->handler(c->write);
+
+    } else {
+        r->write_event_handler(r);
+        ngx_http_run_posted_requests(c);
+    }
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_upstream_output_filter(void *data, ngx_chain_t *chain)
+{
+    ngx_int_t            rc;
+    ngx_event_pipe_t    *p;
+    ngx_http_request_t  *r;
+
+    r = data;
+    p = r->upstream->pipe;
+
+    rc = ngx_http_output_filter(r, chain);
+
+    p->aio = r->aio;
+
+    return rc;
+}
+
+
+static void
+ngx_http_upstream_process_downstream(ngx_http_request_t *r)
+{
+    ngx_event_t          *wev;
+    ngx_connection_t     *c;
+    ngx_event_pipe_t     *p;
+    ngx_http_upstream_t  *u;
+
+    c = r->connection;
+    u = r->upstream;
+    p = u->pipe;
+    wev = c->write;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http upstream process downstream");
+
+    c->log->action = "sending to client";
+
+#if (NGX_THREADS)
+    p->aio = r->aio;
+#endif
+
+    if (wev->timedout) {
+
+        p->downstream_error = 1;
+        c->timedout = 1;
+        ngx_connection_error(c, NGX_ETIMEDOUT, "client timed out");
+
+    } else {
+
+        if (wev->delayed) {
+
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                           "http downstream delayed");
+
+            if (ngx_handle_write_event(wev, p->send_lowat) != NGX_OK) {
+                ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+            }
+
+            return;
+        }
+
+        if (ngx_event_pipe(p, 1) == NGX_ABORT) {
+            ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+            return;
+        }
+    }
+
+    ngx_http_upstream_process_request(r, u);
+}
+
+
+static void
+ngx_http_upstream_process_upstream(ngx_http_request_t *r,
+    ngx_http_upstream_t *u)
+{
+    ngx_event_t       *rev;
+    ngx_event_pipe_t  *p;
+    ngx_connection_t  *c;
+
+    c = u->peer.connection;
+    p = u->pipe;
+    rev = c->read;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http upstream process upstream");
+
+    c->log->action = "reading upstream";
+
+    if (rev->timedout) {
+
+        p->upstream_error = 1;
+        ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out");
+
+    } else {
+
+        if (rev->delayed) {
+
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                           "http upstream delayed");
+
+            if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+                ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+            }
+
+            return;
+        }
+
+        if (ngx_event_pipe(p, 0) == NGX_ABORT) {
+            ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+            return;
+        }
+    }
+
+    ngx_http_upstream_process_request(r, u);
+}
+
+
+static void
+ngx_http_upstream_process_request(ngx_http_request_t *r,
+    ngx_http_upstream_t *u)
+{
+    ngx_temp_file_t   *tf;
+    ngx_event_pipe_t  *p;
+
+    p = u->pipe;
+
+#if (NGX_THREADS)
+
+    if (p->writing && !p->aio) {
+
+        /*
+         * make sure to call ngx_event_pipe()
+         * if there is an incomplete aio write
+         */
+
+        if (ngx_event_pipe(p, 1) == NGX_ABORT) {
+            ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+            return;
+        }
+    }
+
+    if (p->writing) {
+        return;
+    }
+
+#endif
+
+    if (u->peer.connection) {
+
+        if (u->store) {
+
+            if (p->upstream_eof || p->upstream_done) {
+
+                tf = p->temp_file;
+
+                if (u->headers_in.status_n == NGX_HTTP_OK
+                    && (p->upstream_done || p->length == -1)
+                    && (u->headers_in.content_length_n == -1
+                        || u->headers_in.content_length_n == tf->offset))
+                {
+                    ngx_http_upstream_store(r, u);
+                }
+            }
+        }
+
+#if (NGX_HTTP_CACHE)
+
+        if (u->cacheable) {
+
+            if (p->upstream_done) {
+                ngx_http_file_cache_update(r, p->temp_file);
+
+            } else if (p->upstream_eof) {
+
+                tf = p->temp_file;
+
+                if (p->length == -1
+                    && (u->headers_in.content_length_n == -1
+                        || u->headers_in.content_length_n
+                           == tf->offset - (off_t) r->cache->body_start))
+                {
+                    ngx_http_file_cache_update(r, tf);
+
+                } else {
+                    ngx_http_file_cache_free(r->cache, tf);
+                }
+
+            } else if (p->upstream_error) {
+                ngx_http_file_cache_free(r->cache, p->temp_file);
+            }
+        }
+
+#endif
+
+        if (p->upstream_done || p->upstream_eof || p->upstream_error) {
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http upstream exit: %p", p->out);
+
+            if (p->upstream_done
+                || (p->upstream_eof && p->length == -1))
+            {
+                ngx_http_upstream_finalize_request(r, u, 0);
+                return;
+            }
+
+            if (p->upstream_eof) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "upstream prematurely closed connection");
+            }
+
+            ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY);
+            return;
+        }
+    }
+
+    if (p->downstream_error) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http upstream downstream error");
+
+        if (!u->cacheable && !u->store && u->peer.connection) {
+            ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+        }
+    }
+}
+
+
+static void
+ngx_http_upstream_store(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+    size_t                  root;
+    time_t                  lm;
+    ngx_str_t               path;
+    ngx_temp_file_t        *tf;
+    ngx_ext_rename_file_t   ext;
+
+    tf = u->pipe->temp_file;
+
+    if (tf->file.fd == NGX_INVALID_FILE) {
+
+        /* create file for empty 200 response */
+
+        tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t));
+        if (tf == NULL) {
+            return;
+        }
+
+        tf->file.fd = NGX_INVALID_FILE;
+        tf->file.log = r->connection->log;
+        tf->path = u->conf->temp_path;
+        tf->pool = r->pool;
+        tf->persistent = 1;
+
+        if (ngx_create_temp_file(&tf->file, tf->path, tf->pool,
+                                 tf->persistent, tf->clean, tf->access)
+            != NGX_OK)
+        {
+            return;
+        }
+
+        u->pipe->temp_file = tf;
+    }
+
+    ext.access = u->conf->store_access;
+    ext.path_access = u->conf->store_access;
+    ext.time = -1;
+    ext.create_path = 1;
+    ext.delete_file = 1;
+    ext.log = r->connection->log;
+
+    if (u->headers_in.last_modified) {
+
+        lm = ngx_parse_http_time(u->headers_in.last_modified->value.data,
+                                 u->headers_in.last_modified->value.len);
+
+        if (lm != NGX_ERROR) {
+            ext.time = lm;
+            ext.fd = tf->file.fd;
+        }
+    }
+
+    if (u->conf->store_lengths == NULL) {
+
+        if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) {
+            return;
+        }
+
+    } else {
+        if (ngx_http_script_run(r, &path, u->conf->store_lengths->elts, 0,
+                                u->conf->store_values->elts)
+            == NULL)
+        {
+            return;
+        }
+    }
+
+    path.len--;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "upstream stores \"%s\" to \"%s\"",
+                   tf->file.name.data, path.data);
+
+    (void) ngx_ext_rename_file(&tf->file.name, &path, &ext);
+
+    u->store = 0;
+}
+
+
+static void
+ngx_http_upstream_dummy_handler(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http upstream dummy handler");
+}
+
+
+static void
+ngx_http_upstream_next(ngx_http_request_t *r, ngx_http_upstream_t *u,
+    ngx_uint_t ft_type)
+{
+    ngx_msec_t  timeout;
+    ngx_uint_t  status, state;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http next upstream, %xi", ft_type);
+
+    if (u->peer.sockaddr) {
+
+        if (ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_403
+            || ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_404)
+        {
+            state = NGX_PEER_NEXT;
+
+        } else {
+            state = NGX_PEER_FAILED;
+        }
+
+        u->peer.free(&u->peer, u->peer.data, state);
+        u->peer.sockaddr = NULL;
+    }
+
+    if (ft_type == NGX_HTTP_UPSTREAM_FT_TIMEOUT) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_ETIMEDOUT,
+                      "upstream timed out");
+    }
+
+    if (u->peer.cached && ft_type == NGX_HTTP_UPSTREAM_FT_ERROR) {
+        /* TODO: inform balancer instead */
+        u->peer.tries++;
+    }
+
+    switch (ft_type) {
+
+    case NGX_HTTP_UPSTREAM_FT_TIMEOUT:
+    case NGX_HTTP_UPSTREAM_FT_HTTP_504:
+        status = NGX_HTTP_GATEWAY_TIME_OUT;
+        break;
+
+    case NGX_HTTP_UPSTREAM_FT_HTTP_500:
+        status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+        break;
+
+    case NGX_HTTP_UPSTREAM_FT_HTTP_503:
+        status = NGX_HTTP_SERVICE_UNAVAILABLE;
+        break;
+
+    case NGX_HTTP_UPSTREAM_FT_HTTP_403:
+        status = NGX_HTTP_FORBIDDEN;
+        break;
+
+    case NGX_HTTP_UPSTREAM_FT_HTTP_404:
+        status = NGX_HTTP_NOT_FOUND;
+        break;
+
+    case NGX_HTTP_UPSTREAM_FT_HTTP_429:
+        status = NGX_HTTP_TOO_MANY_REQUESTS;
+        break;
+
+    /*
+     * NGX_HTTP_UPSTREAM_FT_BUSY_LOCK and NGX_HTTP_UPSTREAM_FT_MAX_WAITING
+     * never reach here
+     */
+
+    default:
+        status = NGX_HTTP_BAD_GATEWAY;
+    }
+
+    if (r->connection->error) {
+        ngx_http_upstream_finalize_request(r, u,
+                                           NGX_HTTP_CLIENT_CLOSED_REQUEST);
+        return;
+    }
+
+    u->state->status = status;
+
+    timeout = u->conf->next_upstream_timeout;
+
+    if (u->request_sent
+        && (r->method & (NGX_HTTP_POST|NGX_HTTP_LOCK|NGX_HTTP_PATCH)))
+    {
+        ft_type |= NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT;
+    }
+
+    if (u->peer.tries == 0
+        || ((u->conf->next_upstream & ft_type) != ft_type)
+        || (u->request_sent && r->request_body_no_buffering)
+        || (timeout && ngx_current_msec - u->peer.start_time >= timeout))
+    {
+#if (NGX_HTTP_CACHE)
+
+        if (u->cache_status == NGX_HTTP_CACHE_EXPIRED
+            && ((u->conf->cache_use_stale & ft_type) || r->cache->stale_error))
+        {
+            ngx_int_t  rc;
+
+            rc = u->reinit_request(r);
+
+            if (rc != NGX_OK) {
+                ngx_http_upstream_finalize_request(r, u, rc);
+                return;
+            }
+
+            u->cache_status = NGX_HTTP_CACHE_STALE;
+            rc = ngx_http_upstream_cache_send(r, u);
+
+            if (rc == NGX_DONE) {
+                return;
+            }
+
+            if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) {
+                rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+            }
+
+            ngx_http_upstream_finalize_request(r, u, rc);
+            return;
+        }
+#endif
+
+        ngx_http_upstream_finalize_request(r, u, status);
+        return;
+    }
+
+    if (u->peer.connection) {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "close http upstream connection: %d",
+                       u->peer.connection->fd);
+#if (NGX_HTTP_SSL)
+
+        if (u->peer.connection->ssl) {
+            u->peer.connection->ssl->no_wait_shutdown = 1;
+            u->peer.connection->ssl->no_send_shutdown = 1;
+
+            (void) ngx_ssl_shutdown(u->peer.connection);
+        }
+#endif
+
+        if (u->peer.connection->pool) {
+            ngx_destroy_pool(u->peer.connection->pool);
+        }
+
+        ngx_close_connection(u->peer.connection);
+        u->peer.connection = NULL;
+    }
+
+    ngx_http_upstream_connect(r, u);
+}
+
+
+static void
+ngx_http_upstream_cleanup(void *data)
+{
+    ngx_http_request_t *r = data;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "cleanup http upstream request: \"%V\"", &r->uri);
+
+    ngx_http_upstream_finalize_request(r, r->upstream, NGX_DONE);
+}
+
+
+static void
+ngx_http_upstream_finalize_request(ngx_http_request_t *r,
+    ngx_http_upstream_t *u, ngx_int_t rc)
+{
+    ngx_uint_t  flush;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "finalize http upstream request: %i", rc);
+
+    if (u->cleanup == NULL) {
+        /* the request was already finalized */
+        ngx_http_finalize_request(r, NGX_DONE);
+        return;
+    }
+
+    *u->cleanup = NULL;
+    u->cleanup = NULL;
+
+    if (u->resolved && u->resolved->ctx) {
+        ngx_resolve_name_done(u->resolved->ctx);
+        u->resolved->ctx = NULL;
+    }
+
+    if (u->state && u->state->response_time) {
+        u->state->response_time = ngx_current_msec - u->state->response_time;
+
+        if (u->pipe && u->pipe->read_length) {
+            u->state->bytes_received += u->pipe->read_length
+                                        - u->pipe->preread_size;
+            u->state->response_length = u->pipe->read_length;
+        }
+    }
+
+    u->finalize_request(r, rc);
+
+    if (u->peer.free && u->peer.sockaddr) {
+        u->peer.free(&u->peer, u->peer.data, 0);
+        u->peer.sockaddr = NULL;
+    }
+
+    if (u->peer.connection) {
+
+#if (NGX_HTTP_SSL)
+
+        /* TODO: do not shutdown persistent connection */
+
+        if (u->peer.connection->ssl) {
+
+            /*
+             * We send the "close notify" shutdown alert to the upstream only
+             * and do not wait its "close notify" shutdown alert.
+             * It is acceptable according to the TLS standard.
+             */
+
+            u->peer.connection->ssl->no_wait_shutdown = 1;
+
+            (void) ngx_ssl_shutdown(u->peer.connection);
+        }
+#endif
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "close http upstream connection: %d",
+                       u->peer.connection->fd);
+
+        if (u->peer.connection->pool) {
+            ngx_destroy_pool(u->peer.connection->pool);
+        }
+
+        ngx_close_connection(u->peer.connection);
+    }
+
+    u->peer.connection = NULL;
+
+    if (u->pipe && u->pipe->temp_file) {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http upstream temp fd: %d",
+                       u->pipe->temp_file->file.fd);
+    }
+
+    if (u->store && u->pipe && u->pipe->temp_file
+        && u->pipe->temp_file->file.fd != NGX_INVALID_FILE)
+    {
+        if (ngx_delete_file(u->pipe->temp_file->file.name.data)
+            == NGX_FILE_ERROR)
+        {
+            ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+                          ngx_delete_file_n " \"%s\" failed",
+                          u->pipe->temp_file->file.name.data);
+        }
+    }
+
+#if (NGX_HTTP_CACHE)
+
+    if (r->cache) {
+
+        if (u->cacheable) {
+
+            if (rc == NGX_HTTP_BAD_GATEWAY || rc == NGX_HTTP_GATEWAY_TIME_OUT) {
+                time_t  valid;
+
+                valid = ngx_http_file_cache_valid(u->conf->cache_valid, rc);
+
+                if (valid) {
+                    r->cache->valid_sec = ngx_time() + valid;
+                    r->cache->error = rc;
+                }
+            }
+        }
+
+        ngx_http_file_cache_free(r->cache, u->pipe->temp_file);
+    }
+
+#endif
+
+    r->read_event_handler = ngx_http_block_reading;
+
+    if (rc == NGX_DECLINED) {
+        return;
+    }
+
+    r->connection->log->action = "sending to client";
+
+    if (!u->header_sent
+        || rc == NGX_HTTP_REQUEST_TIME_OUT
+        || rc == NGX_HTTP_CLIENT_CLOSED_REQUEST)
+    {
+        ngx_http_finalize_request(r, rc);
+        return;
+    }
+
+    flush = 0;
+
+    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+        rc = NGX_ERROR;
+        flush = 1;
+    }
+
+    if (r->header_only
+        || (u->pipe && u->pipe->downstream_error))
+    {
+        ngx_http_finalize_request(r, rc);
+        return;
+    }
+
+    if (rc == 0) {
+
+        if (ngx_http_upstream_process_trailers(r, u) != NGX_OK) {
+            ngx_http_finalize_request(r, NGX_ERROR);
+            return;
+        }
+
+        rc = ngx_http_send_special(r, NGX_HTTP_LAST);
+
+    } else if (flush) {
+        r->keepalive = 0;
+        rc = ngx_http_send_special(r, NGX_HTTP_FLUSH);
+    }
+
+    ngx_http_finalize_request(r, rc);
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_header_line(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    ngx_table_elt_t  **ph;
+
+    ph = (ngx_table_elt_t **) ((char *) &r->upstream->headers_in + offset);
+
+    if (*ph == NULL) {
+        *ph = h;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_ignore_header_line(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_content_length(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset)
+{
+    ngx_http_upstream_t  *u;
+
+    u = r->upstream;
+
+    u->headers_in.content_length = h;
+    u->headers_in.content_length_n = ngx_atoof(h->value.data, h->value.len);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_last_modified(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset)
+{
+    ngx_http_upstream_t  *u;
+
+    u = r->upstream;
+
+    u->headers_in.last_modified = h;
+    u->headers_in.last_modified_time = ngx_parse_http_time(h->value.data,
+                                                           h->value.len);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_set_cookie(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    ngx_array_t           *pa;
+    ngx_table_elt_t      **ph;
+    ngx_http_upstream_t   *u;
+
+    u = r->upstream;
+    pa = &u->headers_in.cookies;
+
+    if (pa->elts == NULL) {
+        if (ngx_array_init(pa, r->pool, 1, sizeof(ngx_table_elt_t *)) != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    ph = ngx_array_push(pa);
+    if (ph == NULL) {
+        return NGX_ERROR;
+    }
+
+    *ph = h;
+
+#if (NGX_HTTP_CACHE)
+    if (!(u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_SET_COOKIE)) {
+        u->cacheable = 0;
+    }
+#endif
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_cache_control(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset)
+{
+    ngx_array_t          *pa;
+    ngx_table_elt_t     **ph;
+    ngx_http_upstream_t  *u;
+
+    u = r->upstream;
+    pa = &u->headers_in.cache_control;
+
+    if (pa->elts == NULL) {
+        if (ngx_array_init(pa, r->pool, 2, sizeof(ngx_table_elt_t *)) != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    ph = ngx_array_push(pa);
+    if (ph == NULL) {
+        return NGX_ERROR;
+    }
+
+    *ph = h;
+
+#if (NGX_HTTP_CACHE)
+    {
+    u_char     *p, *start, *last;
+    ngx_int_t   n;
+
+    if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_CACHE_CONTROL) {
+        return NGX_OK;
+    }
+
+    if (r->cache == NULL) {
+        return NGX_OK;
+    }
+
+    if (r->cache->valid_sec != 0 && u->headers_in.x_accel_expires != NULL) {
+        return NGX_OK;
+    }
+
+    start = h->value.data;
+    last = start + h->value.len;
+
+    if (ngx_strlcasestrn(start, last, (u_char *) "no-cache", 8 - 1) != NULL
+        || ngx_strlcasestrn(start, last, (u_char *) "no-store", 8 - 1) != NULL
+        || ngx_strlcasestrn(start, last, (u_char *) "private", 7 - 1) != NULL)
+    {
+        u->cacheable = 0;
+        return NGX_OK;
+    }
+
+    p = ngx_strlcasestrn(start, last, (u_char *) "s-maxage=", 9 - 1);
+    offset = 9;
+
+    if (p == NULL) {
+        p = ngx_strlcasestrn(start, last, (u_char *) "max-age=", 8 - 1);
+        offset = 8;
+    }
+
+    if (p) {
+        n = 0;
+
+        for (p += offset; p < last; p++) {
+            if (*p == ',' || *p == ';' || *p == ' ') {
+                break;
+            }
+
+            if (*p >= '0' && *p <= '9') {
+                n = n * 10 + (*p - '0');
+                continue;
+            }
+
+            u->cacheable = 0;
+            return NGX_OK;
+        }
+
+        if (n == 0) {
+            u->cacheable = 0;
+            return NGX_OK;
+        }
+
+        r->cache->valid_sec = ngx_time() + n;
+    }
+
+    p = ngx_strlcasestrn(start, last, (u_char *) "stale-while-revalidate=",
+                         23 - 1);
+
+    if (p) {
+        n = 0;
+
+        for (p += 23; p < last; p++) {
+            if (*p == ',' || *p == ';' || *p == ' ') {
+                break;
+            }
+
+            if (*p >= '0' && *p <= '9') {
+                n = n * 10 + (*p - '0');
+                continue;
+            }
+
+            u->cacheable = 0;
+            return NGX_OK;
+        }
+
+        r->cache->updating_sec = n;
+        r->cache->error_sec = n;
+    }
+
+    p = ngx_strlcasestrn(start, last, (u_char *) "stale-if-error=", 15 - 1);
+
+    if (p) {
+        n = 0;
+
+        for (p += 15; p < last; p++) {
+            if (*p == ',' || *p == ';' || *p == ' ') {
+                break;
+            }
+
+            if (*p >= '0' && *p <= '9') {
+                n = n * 10 + (*p - '0');
+                continue;
+            }
+
+            u->cacheable = 0;
+            return NGX_OK;
+        }
+
+        r->cache->error_sec = n;
+    }
+    }
+#endif
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_expires(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    ngx_http_upstream_t  *u;
+
+    u = r->upstream;
+    u->headers_in.expires = h;
+
+#if (NGX_HTTP_CACHE)
+    {
+    time_t  expires;
+
+    if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_EXPIRES) {
+        return NGX_OK;
+    }
+
+    if (r->cache == NULL) {
+        return NGX_OK;
+    }
+
+    if (r->cache->valid_sec != 0) {
+        return NGX_OK;
+    }
+
+    expires = ngx_parse_http_time(h->value.data, h->value.len);
+
+    if (expires == NGX_ERROR || expires < ngx_time()) {
+        u->cacheable = 0;
+        return NGX_OK;
+    }
+
+    r->cache->valid_sec = expires;
+    }
+#endif
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_accel_expires(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset)
+{
+    ngx_http_upstream_t  *u;
+
+    u = r->upstream;
+    u->headers_in.x_accel_expires = h;
+
+#if (NGX_HTTP_CACHE)
+    {
+    u_char     *p;
+    size_t      len;
+    ngx_int_t   n;
+
+    if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_EXPIRES) {
+        return NGX_OK;
+    }
+
+    if (r->cache == NULL) {
+        return NGX_OK;
+    }
+
+    len = h->value.len;
+    p = h->value.data;
+
+    if (p[0] != '@') {
+        n = ngx_atoi(p, len);
+
+        switch (n) {
+        case 0:
+            u->cacheable = 0;
+            /* fall through */
+
+        case NGX_ERROR:
+            return NGX_OK;
+
+        default:
+            r->cache->valid_sec = ngx_time() + n;
+            return NGX_OK;
+        }
+    }
+
+    p++;
+    len--;
+
+    n = ngx_atoi(p, len);
+
+    if (n != NGX_ERROR) {
+        r->cache->valid_sec = n;
+    }
+    }
+#endif
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_limit_rate(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    ngx_int_t             n;
+    ngx_http_upstream_t  *u;
+
+    u = r->upstream;
+    u->headers_in.x_accel_limit_rate = h;
+
+    if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_LIMIT_RATE) {
+        return NGX_OK;
+    }
+
+    n = ngx_atoi(h->value.data, h->value.len);
+
+    if (n != NGX_ERROR) {
+        r->limit_rate = (size_t) n;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_buffering(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    u_char                c0, c1, c2;
+    ngx_http_upstream_t  *u;
+
+    u = r->upstream;
+
+    if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_BUFFERING) {
+        return NGX_OK;
+    }
+
+    if (u->conf->change_buffering) {
+
+        if (h->value.len == 2) {
+            c0 = ngx_tolower(h->value.data[0]);
+            c1 = ngx_tolower(h->value.data[1]);
+
+            if (c0 == 'n' && c1 == 'o') {
+                u->buffering = 0;
+            }
+
+        } else if (h->value.len == 3) {
+            c0 = ngx_tolower(h->value.data[0]);
+            c1 = ngx_tolower(h->value.data[1]);
+            c2 = ngx_tolower(h->value.data[2]);
+
+            if (c0 == 'y' && c1 == 'e' && c2 == 's') {
+                u->buffering = 1;
+            }
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_charset(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    if (r->upstream->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_CHARSET) {
+        return NGX_OK;
+    }
+
+    r->headers_out.override_charset = &h->value;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_connection(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    r->upstream->headers_in.connection = h;
+
+    if (ngx_strlcasestrn(h->value.data, h->value.data + h->value.len,
+                         (u_char *) "close", 5 - 1)
+        != NULL)
+    {
+        r->upstream->headers_in.connection_close = 1;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_transfer_encoding(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset)
+{
+    r->upstream->headers_in.transfer_encoding = h;
+
+    if (ngx_strlcasestrn(h->value.data, h->value.data + h->value.len,
+                         (u_char *) "chunked", 7 - 1)
+        != NULL)
+    {
+        r->upstream->headers_in.chunked = 1;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_vary(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset)
+{
+    ngx_http_upstream_t  *u;
+
+    u = r->upstream;
+    u->headers_in.vary = h;
+
+#if (NGX_HTTP_CACHE)
+
+    if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_VARY) {
+        return NGX_OK;
+    }
+
+    if (r->cache == NULL) {
+        return NGX_OK;
+    }
+
+    if (h->value.len > NGX_HTTP_CACHE_VARY_LEN
+        || (h->value.len == 1 && h->value.data[0] == '*'))
+    {
+        u->cacheable = 0;
+    }
+
+    r->cache->vary = h->value;
+
+#endif
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_copy_header_line(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    ngx_table_elt_t  *ho, **ph;
+
+    ho = ngx_list_push(&r->headers_out.headers);
+    if (ho == NULL) {
+        return NGX_ERROR;
+    }
+
+    *ho = *h;
+
+    if (offset) {
+        ph = (ngx_table_elt_t **) ((char *) &r->headers_out + offset);
+        *ph = ho;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_copy_multi_header_lines(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset)
+{
+    ngx_array_t      *pa;
+    ngx_table_elt_t  *ho, **ph;
+
+    pa = (ngx_array_t *) ((char *) &r->headers_out + offset);
+
+    if (pa->elts == NULL) {
+        if (ngx_array_init(pa, r->pool, 2, sizeof(ngx_table_elt_t *)) != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    ho = ngx_list_push(&r->headers_out.headers);
+    if (ho == NULL) {
+        return NGX_ERROR;
+    }
+
+    *ho = *h;
+
+    ph = ngx_array_push(pa);
+    if (ph == NULL) {
+        return NGX_ERROR;
+    }
+
+    *ph = ho;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_copy_content_type(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    u_char  *p, *last;
+
+    r->headers_out.content_type_len = h->value.len;
+    r->headers_out.content_type = h->value;
+    r->headers_out.content_type_lowcase = NULL;
+
+    for (p = h->value.data; *p; p++) {
+
+        if (*p != ';') {
+            continue;
+        }
+
+        last = p;
+
+        while (*++p == ' ') { /* void */ }
+
+        if (*p == '\0') {
+            return NGX_OK;
+        }
+
+        if (ngx_strncasecmp(p, (u_char *) "charset=", 8) != 0) {
+            continue;
+        }
+
+        p += 8;
+
+        r->headers_out.content_type_len = last - h->value.data;
+
+        if (*p == '"') {
+            p++;
+        }
+
+        last = h->value.data + h->value.len;
+
+        if (*(last - 1) == '"') {
+            last--;
+        }
+
+        r->headers_out.charset.len = last - p;
+        r->headers_out.charset.data = p;
+
+        return NGX_OK;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_copy_last_modified(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    ngx_table_elt_t  *ho;
+
+    ho = ngx_list_push(&r->headers_out.headers);
+    if (ho == NULL) {
+        return NGX_ERROR;
+    }
+
+    *ho = *h;
+
+    r->headers_out.last_modified = ho;
+    r->headers_out.last_modified_time =
+                                    r->upstream->headers_in.last_modified_time;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_rewrite_location(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    ngx_int_t         rc;
+    ngx_table_elt_t  *ho;
+
+    ho = ngx_list_push(&r->headers_out.headers);
+    if (ho == NULL) {
+        return NGX_ERROR;
+    }
+
+    *ho = *h;
+
+    if (r->upstream->rewrite_redirect) {
+        rc = r->upstream->rewrite_redirect(r, ho, 0);
+
+        if (rc == NGX_DECLINED) {
+            return NGX_OK;
+        }
+
+        if (rc == NGX_OK) {
+            r->headers_out.location = ho;
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "rewritten location: \"%V\"", &ho->value);
+        }
+
+        return rc;
+    }
+
+    if (ho->value.data[0] != '/') {
+        r->headers_out.location = ho;
+    }
+
+    /*
+     * we do not set r->headers_out.location here to avoid handling
+     * relative redirects in ngx_http_header_filter()
+     */
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_rewrite_refresh(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    u_char           *p;
+    ngx_int_t         rc;
+    ngx_table_elt_t  *ho;
+
+    ho = ngx_list_push(&r->headers_out.headers);
+    if (ho == NULL) {
+        return NGX_ERROR;
+    }
+
+    *ho = *h;
+
+    if (r->upstream->rewrite_redirect) {
+
+        p = ngx_strcasestrn(ho->value.data, "url=", 4 - 1);
+
+        if (p) {
+            rc = r->upstream->rewrite_redirect(r, ho, p + 4 - ho->value.data);
+
+        } else {
+            return NGX_OK;
+        }
+
+        if (rc == NGX_DECLINED) {
+            return NGX_OK;
+        }
+
+        if (rc == NGX_OK) {
+            r->headers_out.refresh = ho;
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "rewritten refresh: \"%V\"", &ho->value);
+        }
+
+        return rc;
+    }
+
+    r->headers_out.refresh = ho;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_rewrite_set_cookie(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    ngx_int_t         rc;
+    ngx_table_elt_t  *ho;
+
+    ho = ngx_list_push(&r->headers_out.headers);
+    if (ho == NULL) {
+        return NGX_ERROR;
+    }
+
+    *ho = *h;
+
+    if (r->upstream->rewrite_cookie) {
+        rc = r->upstream->rewrite_cookie(r, ho);
+
+        if (rc == NGX_DECLINED) {
+            return NGX_OK;
+        }
+
+#if (NGX_DEBUG)
+        if (rc == NGX_OK) {
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "rewritten cookie: \"%V\"", &ho->value);
+        }
+#endif
+
+        return rc;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_copy_allow_ranges(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset)
+{
+    ngx_table_elt_t  *ho;
+
+    if (r->upstream->conf->force_ranges) {
+        return NGX_OK;
+    }
+
+#if (NGX_HTTP_CACHE)
+
+    if (r->cached) {
+        r->allow_ranges = 1;
+        return NGX_OK;
+    }
+
+    if (r->upstream->cacheable) {
+        r->allow_ranges = 1;
+        r->single_range = 1;
+        return NGX_OK;
+    }
+
+#endif
+
+    ho = ngx_list_push(&r->headers_out.headers);
+    if (ho == NULL) {
+        return NGX_ERROR;
+    }
+
+    *ho = *h;
+
+    r->headers_out.accept_ranges = ho;
+
+    return NGX_OK;
+}
+
+
+#if (NGX_HTTP_GZIP)
+
+static ngx_int_t
+ngx_http_upstream_copy_content_encoding(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset)
+{
+    ngx_table_elt_t  *ho;
+
+    ho = ngx_list_push(&r->headers_out.headers);
+    if (ho == NULL) {
+        return NGX_ERROR;
+    }
+
+    *ho = *h;
+
+    r->headers_out.content_encoding = ho;
+
+    return NGX_OK;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_upstream_add_variables(ngx_conf_t *cf)
+{
+    ngx_http_variable_t  *var, *v;
+
+    for (v = ngx_http_upstream_vars; v->name.len; v++) {
+        var = ngx_http_add_variable(cf, &v->name, v->flags);
+        if (var == NULL) {
+            return NGX_ERROR;
+        }
+
+        var->get_handler = v->get_handler;
+        var->data = v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_addr_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char                     *p;
+    size_t                      len;
+    ngx_uint_t                  i;
+    ngx_http_upstream_state_t  *state;
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    if (r->upstream_states == NULL || r->upstream_states->nelts == 0) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    len = 0;
+    state = r->upstream_states->elts;
+
+    for (i = 0; i < r->upstream_states->nelts; i++) {
+        if (state[i].peer) {
+            len += state[i].peer->len + 2;
+
+        } else {
+            len += 3;
+        }
+    }
+
+    p = ngx_pnalloc(r->pool, len);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->data = p;
+
+    i = 0;
+
+    for ( ;; ) {
+        if (state[i].peer) {
+            p = ngx_cpymem(p, state[i].peer->data, state[i].peer->len);
+        }
+
+        if (++i == r->upstream_states->nelts) {
+            break;
+        }
+
+        if (state[i].peer) {
+            *p++ = ',';
+            *p++ = ' ';
+
+        } else {
+            *p++ = ' ';
+            *p++ = ':';
+            *p++ = ' ';
+
+            if (++i == r->upstream_states->nelts) {
+                break;
+            }
+
+            continue;
+        }
+    }
+
+    v->len = p - v->data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_status_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char                     *p;
+    size_t                      len;
+    ngx_uint_t                  i;
+    ngx_http_upstream_state_t  *state;
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    if (r->upstream_states == NULL || r->upstream_states->nelts == 0) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    len = r->upstream_states->nelts * (3 + 2);
+
+    p = ngx_pnalloc(r->pool, len);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->data = p;
+
+    i = 0;
+    state = r->upstream_states->elts;
+
+    for ( ;; ) {
+        if (state[i].status) {
+            p = ngx_sprintf(p, "%ui", state[i].status);
+
+        } else {
+            *p++ = '-';
+        }
+
+        if (++i == r->upstream_states->nelts) {
+            break;
+        }
+
+        if (state[i].peer) {
+            *p++ = ',';
+            *p++ = ' ';
+
+        } else {
+            *p++ = ' ';
+            *p++ = ':';
+            *p++ = ' ';
+
+            if (++i == r->upstream_states->nelts) {
+                break;
+            }
+
+            continue;
+        }
+    }
+
+    v->len = p - v->data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_response_time_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char                     *p;
+    size_t                      len;
+    ngx_uint_t                  i;
+    ngx_msec_int_t              ms;
+    ngx_http_upstream_state_t  *state;
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    if (r->upstream_states == NULL || r->upstream_states->nelts == 0) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    len = r->upstream_states->nelts * (NGX_TIME_T_LEN + 4 + 2);
+
+    p = ngx_pnalloc(r->pool, len);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->data = p;
+
+    i = 0;
+    state = r->upstream_states->elts;
+
+    for ( ;; ) {
+        if (state[i].status) {
+
+            if (data == 1 && state[i].header_time != (ngx_msec_t) -1) {
+                ms = state[i].header_time;
+
+            } else if (data == 2 && state[i].connect_time != (ngx_msec_t) -1) {
+                ms = state[i].connect_time;
+
+            } else {
+                ms = state[i].response_time;
+            }
+
+            ms = ngx_max(ms, 0);
+            p = ngx_sprintf(p, "%T.%03M", (time_t) ms / 1000, ms % 1000);
+
+        } else {
+            *p++ = '-';
+        }
+
+        if (++i == r->upstream_states->nelts) {
+            break;
+        }
+
+        if (state[i].peer) {
+            *p++ = ',';
+            *p++ = ' ';
+
+        } else {
+            *p++ = ' ';
+            *p++ = ':';
+            *p++ = ' ';
+
+            if (++i == r->upstream_states->nelts) {
+                break;
+            }
+
+            continue;
+        }
+    }
+
+    v->len = p - v->data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_response_length_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char                     *p;
+    size_t                      len;
+    ngx_uint_t                  i;
+    ngx_http_upstream_state_t  *state;
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    if (r->upstream_states == NULL || r->upstream_states->nelts == 0) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    len = r->upstream_states->nelts * (NGX_OFF_T_LEN + 2);
+
+    p = ngx_pnalloc(r->pool, len);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->data = p;
+
+    i = 0;
+    state = r->upstream_states->elts;
+
+    for ( ;; ) {
+
+        if (data == 1) {
+            p = ngx_sprintf(p, "%O", state[i].bytes_received);
+
+        } else {
+            p = ngx_sprintf(p, "%O", state[i].response_length);
+        }
+
+        if (++i == r->upstream_states->nelts) {
+            break;
+        }
+
+        if (state[i].peer) {
+            *p++ = ',';
+            *p++ = ' ';
+
+        } else {
+            *p++ = ' ';
+            *p++ = ':';
+            *p++ = ' ';
+
+            if (++i == r->upstream_states->nelts) {
+                break;
+            }
+
+            continue;
+        }
+    }
+
+    v->len = p - v->data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_header_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    if (r->upstream == NULL) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    return ngx_http_variable_unknown_header(v, (ngx_str_t *) data,
+                                         &r->upstream->headers_in.headers.part,
+                                         sizeof("upstream_http_") - 1);
+}
+
+
+static ngx_int_t
+ngx_http_upstream_trailer_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    if (r->upstream == NULL) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    return ngx_http_variable_unknown_header(v, (ngx_str_t *) data,
+                                        &r->upstream->headers_in.trailers.part,
+                                        sizeof("upstream_trailer_") - 1);
+}
+
+
+static ngx_int_t
+ngx_http_upstream_cookie_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_str_t  *name = (ngx_str_t *) data;
+
+    ngx_str_t   cookie, s;
+
+    if (r->upstream == NULL) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    s.len = name->len - (sizeof("upstream_cookie_") - 1);
+    s.data = name->data + sizeof("upstream_cookie_") - 1;
+
+    if (ngx_http_parse_set_cookie_lines(&r->upstream->headers_in.cookies,
+                                        &s, &cookie)
+        == NGX_DECLINED)
+    {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    v->len = cookie.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = cookie.data;
+
+    return NGX_OK;
+}
+
+
+#if (NGX_HTTP_CACHE)
+
+static ngx_int_t
+ngx_http_upstream_cache_status(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_uint_t  n;
+
+    if (r->upstream == NULL || r->upstream->cache_status == 0) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    n = r->upstream->cache_status - 1;
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->len = ngx_http_cache_status[n].len;
+    v->data = ngx_http_cache_status[n].data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_cache_last_modified(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char  *p;
+
+    if (r->upstream == NULL
+        || !r->upstream->conf->cache_revalidate
+        || r->upstream->cache_status != NGX_HTTP_CACHE_EXPIRED
+        || r->cache->last_modified == -1)
+    {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    p = ngx_pnalloc(r->pool, sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->len = ngx_http_time(p, r->cache->last_modified) - p;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = p;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_cache_etag(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    if (r->upstream == NULL
+        || !r->upstream->conf->cache_revalidate
+        || r->upstream->cache_status != NGX_HTTP_CACHE_EXPIRED
+        || r->cache->etag.len == 0)
+    {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->len = r->cache->etag.len;
+    v->data = r->cache->etag.data;
+
+    return NGX_OK;
+}
+
+#endif
+
+
+static char *
+ngx_http_upstream(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
+{
+    char                          *rv;
+    void                          *mconf;
+    ngx_str_t                     *value;
+    ngx_url_t                      u;
+    ngx_uint_t                     m;
+    ngx_conf_t                     pcf;
+    ngx_http_module_t             *module;
+    ngx_http_conf_ctx_t           *ctx, *http_ctx;
+    ngx_http_upstream_srv_conf_t  *uscf;
+
+    ngx_memzero(&u, sizeof(ngx_url_t));
+
+    value = cf->args->elts;
+    u.host = value[1];
+    u.no_resolve = 1;
+    u.no_port = 1;
+
+    uscf = ngx_http_upstream_add(cf, &u, NGX_HTTP_UPSTREAM_CREATE
+                                         |NGX_HTTP_UPSTREAM_WEIGHT
+                                         |NGX_HTTP_UPSTREAM_MAX_CONNS
+                                         |NGX_HTTP_UPSTREAM_MAX_FAILS
+                                         |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
+                                         |NGX_HTTP_UPSTREAM_DOWN
+                                         |NGX_HTTP_UPSTREAM_BACKUP);
+    if (uscf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+
+    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
+    if (ctx == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    http_ctx = cf->ctx;
+    ctx->main_conf = http_ctx->main_conf;
+
+    /* the upstream{}'s srv_conf */
+
+    ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
+    if (ctx->srv_conf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ctx->srv_conf[ngx_http_upstream_module.ctx_index] = uscf;
+
+    uscf->srv_conf = ctx->srv_conf;
+
+
+    /* the upstream{}'s loc_conf */
+
+    ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
+    if (ctx->loc_conf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    for (m = 0; cf->cycle->modules[m]; m++) {
+        if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
+            continue;
+        }
+
+        module = cf->cycle->modules[m]->ctx;
+
+        if (module->create_srv_conf) {
+            mconf = module->create_srv_conf(cf);
+            if (mconf == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            ctx->srv_conf[cf->cycle->modules[m]->ctx_index] = mconf;
+        }
+
+        if (module->create_loc_conf) {
+            mconf = module->create_loc_conf(cf);
+            if (mconf == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            ctx->loc_conf[cf->cycle->modules[m]->ctx_index] = mconf;
+        }
+    }
+
+    uscf->servers = ngx_array_create(cf->pool, 4,
+                                     sizeof(ngx_http_upstream_server_t));
+    if (uscf->servers == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+
+    /* parse inside upstream{} */
+
+    pcf = *cf;
+    cf->ctx = ctx;
+    cf->cmd_type = NGX_HTTP_UPS_CONF;
+
+    rv = ngx_conf_parse(cf, NULL);
+
+    *cf = pcf;
+
+    if (rv != NGX_CONF_OK) {
+        return rv;
+    }
+
+    if (uscf->servers->nelts == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "no servers are inside upstream");
+        return NGX_CONF_ERROR;
+    }
+
+    return rv;
+}
+
+
+static char *
+ngx_http_upstream_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_upstream_srv_conf_t  *uscf = conf;
+
+    time_t                       fail_timeout;
+    ngx_str_t                   *value, s;
+    ngx_url_t                    u;
+    ngx_int_t                    weight, max_conns, max_fails;
+    ngx_uint_t                   i;
+    ngx_http_upstream_server_t  *us;
+
+    us = ngx_array_push(uscf->servers);
+    if (us == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memzero(us, sizeof(ngx_http_upstream_server_t));
+
+    value = cf->args->elts;
+
+    weight = 1;
+    max_conns = 0;
+    max_fails = 1;
+    fail_timeout = 10;
+
+    for (i = 2; i < cf->args->nelts; i++) {
+
+        if (ngx_strncmp(value[i].data, "weight=", 7) == 0) {
+
+            if (!(uscf->flags & NGX_HTTP_UPSTREAM_WEIGHT)) {
+                goto not_supported;
+            }
+
+            weight = ngx_atoi(&value[i].data[7], value[i].len - 7);
+
+            if (weight == NGX_ERROR || weight == 0) {
+                goto invalid;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "max_conns=", 10) == 0) {
+
+            if (!(uscf->flags & NGX_HTTP_UPSTREAM_MAX_CONNS)) {
+                goto not_supported;
+            }
+
+            max_conns = ngx_atoi(&value[i].data[10], value[i].len - 10);
+
+            if (max_conns == NGX_ERROR) {
+                goto invalid;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "max_fails=", 10) == 0) {
+
+            if (!(uscf->flags & NGX_HTTP_UPSTREAM_MAX_FAILS)) {
+                goto not_supported;
+            }
+
+            max_fails = ngx_atoi(&value[i].data[10], value[i].len - 10);
+
+            if (max_fails == NGX_ERROR) {
+                goto invalid;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "fail_timeout=", 13) == 0) {
+
+            if (!(uscf->flags & NGX_HTTP_UPSTREAM_FAIL_TIMEOUT)) {
+                goto not_supported;
+            }
+
+            s.len = value[i].len - 13;
+            s.data = &value[i].data[13];
+
+            fail_timeout = ngx_parse_time(&s, 1);
+
+            if (fail_timeout == (time_t) NGX_ERROR) {
+                goto invalid;
+            }
+
+            continue;
+        }
+
+        if (ngx_strcmp(value[i].data, "backup") == 0) {
+
+            if (!(uscf->flags & NGX_HTTP_UPSTREAM_BACKUP)) {
+                goto not_supported;
+            }
+
+            us->backup = 1;
+
+            continue;
+        }
+
+        if (ngx_strcmp(value[i].data, "down") == 0) {
+
+            if (!(uscf->flags & NGX_HTTP_UPSTREAM_DOWN)) {
+                goto not_supported;
+            }
+
+            us->down = 1;
+
+            continue;
+        }
+
+        goto invalid;
+    }
+
+    ngx_memzero(&u, sizeof(ngx_url_t));
+
+    u.url = value[1];
+    u.default_port = 80;
+
+    if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
+        if (u.err) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "%s in upstream \"%V\"", u.err, &u.url);
+        }
+
+        return NGX_CONF_ERROR;
+    }
+
+    us->name = u.url;
+    us->addrs = u.addrs;
+    us->naddrs = u.naddrs;
+    us->weight = weight;
+    us->max_conns = max_conns;
+    us->max_fails = max_fails;
+    us->fail_timeout = fail_timeout;
+
+    return NGX_CONF_OK;
+
+invalid:
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "invalid parameter \"%V\"", &value[i]);
+
+    return NGX_CONF_ERROR;
+
+not_supported:
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "balancing method does not support parameter \"%V\"",
+                       &value[i]);
+
+    return NGX_CONF_ERROR;
+}
+
+
+ngx_http_upstream_srv_conf_t *
+ngx_http_upstream_add(ngx_conf_t *cf, ngx_url_t *u, ngx_uint_t flags)
+{
+    ngx_uint_t                      i;
+    ngx_http_upstream_server_t     *us;
+    ngx_http_upstream_srv_conf_t   *uscf, **uscfp;
+    ngx_http_upstream_main_conf_t  *umcf;
+
+    if (!(flags & NGX_HTTP_UPSTREAM_CREATE)) {
+
+        if (ngx_parse_url(cf->pool, u) != NGX_OK) {
+            if (u->err) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "%s in upstream \"%V\"", u->err, &u->url);
+            }
+
+            return NULL;
+        }
+    }
+
+    umcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_upstream_module);
+
+    uscfp = umcf->upstreams.elts;
+
+    for (i = 0; i < umcf->upstreams.nelts; i++) {
+
+        if (uscfp[i]->host.len != u->host.len
+            || ngx_strncasecmp(uscfp[i]->host.data, u->host.data, u->host.len)
+               != 0)
+        {
+            continue;
+        }
+
+        if ((flags & NGX_HTTP_UPSTREAM_CREATE)
+             && (uscfp[i]->flags & NGX_HTTP_UPSTREAM_CREATE))
+        {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "duplicate upstream \"%V\"", &u->host);
+            return NULL;
+        }
+
+        if ((uscfp[i]->flags & NGX_HTTP_UPSTREAM_CREATE) && !u->no_port) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "upstream \"%V\" may not have port %d",
+                               &u->host, u->port);
+            return NULL;
+        }
+
+        if ((flags & NGX_HTTP_UPSTREAM_CREATE) && !uscfp[i]->no_port) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "upstream \"%V\" may not have port %d in %s:%ui",
+                          &u->host, uscfp[i]->port,
+                          uscfp[i]->file_name, uscfp[i]->line);
+            return NULL;
+        }
+
+        if (uscfp[i]->port && u->port
+            && uscfp[i]->port != u->port)
+        {
+            continue;
+        }
+
+        if (flags & NGX_HTTP_UPSTREAM_CREATE) {
+            uscfp[i]->flags = flags;
+            uscfp[i]->port = 0;
+        }
+
+        return uscfp[i];
+    }
+
+    uscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_srv_conf_t));
+    if (uscf == NULL) {
+        return NULL;
+    }
+
+    uscf->flags = flags;
+    uscf->host = u->host;
+    uscf->file_name = cf->conf_file->file.name.data;
+    uscf->line = cf->conf_file->line;
+    uscf->port = u->port;
+    uscf->no_port = u->no_port;
+
+    if (u->naddrs == 1 && (u->port || u->family == AF_UNIX)) {
+        uscf->servers = ngx_array_create(cf->pool, 1,
+                                         sizeof(ngx_http_upstream_server_t));
+        if (uscf->servers == NULL) {
+            return NULL;
+        }
+
+        us = ngx_array_push(uscf->servers);
+        if (us == NULL) {
+            return NULL;
+        }
+
+        ngx_memzero(us, sizeof(ngx_http_upstream_server_t));
+
+        us->addrs = u->addrs;
+        us->naddrs = 1;
+    }
+
+    uscfp = ngx_array_push(&umcf->upstreams);
+    if (uscfp == NULL) {
+        return NULL;
+    }
+
+    *uscfp = uscf;
+
+    return uscf;
+}
+
+
+char *
+ngx_http_upstream_bind_set_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf)
+{
+    char  *p = conf;
+
+    ngx_int_t                           rc;
+    ngx_str_t                          *value;
+    ngx_http_complex_value_t            cv;
+    ngx_http_upstream_local_t         **plocal, *local;
+    ngx_http_compile_complex_value_t    ccv;
+
+    plocal = (ngx_http_upstream_local_t **) (p + cmd->offset);
+
+    if (*plocal != NGX_CONF_UNSET_PTR) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    if (cf->args->nelts == 2 && ngx_strcmp(value[1].data, "off") == 0) {
+        *plocal = NULL;
+        return NGX_CONF_OK;
+    }
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[1];
+    ccv.complex_value = &cv;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    local = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_local_t));
+    if (local == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    *plocal = local;
+
+    if (cv.lengths) {
+        local->value = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
+        if (local->value == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        *local->value = cv;
+
+    } else {
+        local->addr = ngx_palloc(cf->pool, sizeof(ngx_addr_t));
+        if (local->addr == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        rc = ngx_parse_addr_port(cf->pool, local->addr, value[1].data,
+                                 value[1].len);
+
+        switch (rc) {
+        case NGX_OK:
+            local->addr->name = value[1];
+            break;
+
+        case NGX_DECLINED:
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid address \"%V\"", &value[1]);
+            /* fall through */
+
+        default:
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (cf->args->nelts > 2) {
+        if (ngx_strcmp(value[2].data, "transparent") == 0) {
+#if (NGX_HAVE_TRANSPARENT_PROXY)
+            ngx_core_conf_t  *ccf;
+
+            ccf = (ngx_core_conf_t *) ngx_get_conf(cf->cycle->conf_ctx,
+                                                   ngx_core_module);
+
+            ccf->transparent = 1;
+            local->transparent = 1;
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "transparent proxying is not supported "
+                               "on this platform, ignored");
+#endif
+        } else {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid parameter \"%V\"", &value[2]);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_set_local(ngx_http_request_t *r, ngx_http_upstream_t *u,
+    ngx_http_upstream_local_t *local)
+{
+    ngx_int_t    rc;
+    ngx_str_t    val;
+    ngx_addr_t  *addr;
+
+    if (local == NULL) {
+        u->peer.local = NULL;
+        return NGX_OK;
+    }
+
+#if (NGX_HAVE_TRANSPARENT_PROXY)
+    u->peer.transparent = local->transparent;
+#endif
+
+    if (local->value == NULL) {
+        u->peer.local = local->addr;
+        return NGX_OK;
+    }
+
+    if (ngx_http_complex_value(r, local->value, &val) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (val.len == 0) {
+        return NGX_OK;
+    }
+
+    addr = ngx_palloc(r->pool, sizeof(ngx_addr_t));
+    if (addr == NULL) {
+        return NGX_ERROR;
+    }
+
+    rc = ngx_parse_addr_port(r->pool, addr, val.data, val.len);
+    if (rc == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if (rc != NGX_OK) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "invalid local address \"%V\"", &val);
+        return NGX_OK;
+    }
+
+    addr->name = val;
+    u->peer.local = addr;
+
+    return NGX_OK;
+}
+
+
+char *
+ngx_http_upstream_param_set_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf)
+{
+    char  *p = conf;
+
+    ngx_str_t                   *value;
+    ngx_array_t                **a;
+    ngx_http_upstream_param_t   *param;
+
+    a = (ngx_array_t **) (p + cmd->offset);
+
+    if (*a == NULL) {
+        *a = ngx_array_create(cf->pool, 4, sizeof(ngx_http_upstream_param_t));
+        if (*a == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    param = ngx_array_push(*a);
+    if (param == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    value = cf->args->elts;
+
+    param->key = value[1];
+    param->value = value[2];
+    param->skip_empty = 0;
+
+    if (cf->args->nelts == 4) {
+        if (ngx_strcmp(value[3].data, "if_not_empty") != 0) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid parameter \"%V\"", &value[3]);
+            return NGX_CONF_ERROR;
+        }
+
+        param->skip_empty = 1;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+ngx_int_t
+ngx_http_upstream_hide_headers_hash(ngx_conf_t *cf,
+    ngx_http_upstream_conf_t *conf, ngx_http_upstream_conf_t *prev,
+    ngx_str_t *default_hide_headers, ngx_hash_init_t *hash)
+{
+    ngx_str_t       *h;
+    ngx_uint_t       i, j;
+    ngx_array_t      hide_headers;
+    ngx_hash_key_t  *hk;
+
+    if (conf->hide_headers == NGX_CONF_UNSET_PTR
+        && conf->pass_headers == NGX_CONF_UNSET_PTR)
+    {
+        conf->hide_headers = prev->hide_headers;
+        conf->pass_headers = prev->pass_headers;
+
+        conf->hide_headers_hash = prev->hide_headers_hash;
+
+        if (conf->hide_headers_hash.buckets) {
+            return NGX_OK;
+        }
+
+    } else {
+        if (conf->hide_headers == NGX_CONF_UNSET_PTR) {
+            conf->hide_headers = prev->hide_headers;
+        }
+
+        if (conf->pass_headers == NGX_CONF_UNSET_PTR) {
+            conf->pass_headers = prev->pass_headers;
+        }
+    }
+
+    if (ngx_array_init(&hide_headers, cf->temp_pool, 4, sizeof(ngx_hash_key_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    for (h = default_hide_headers; h->len; h++) {
+        hk = ngx_array_push(&hide_headers);
+        if (hk == NULL) {
+            return NGX_ERROR;
+        }
+
+        hk->key = *h;
+        hk->key_hash = ngx_hash_key_lc(h->data, h->len);
+        hk->value = (void *) 1;
+    }
+
+    if (conf->hide_headers != NGX_CONF_UNSET_PTR) {
+
+        h = conf->hide_headers->elts;
+
+        for (i = 0; i < conf->hide_headers->nelts; i++) {
+
+            hk = hide_headers.elts;
+
+            for (j = 0; j < hide_headers.nelts; j++) {
+                if (ngx_strcasecmp(h[i].data, hk[j].key.data) == 0) {
+                    goto exist;
+                }
+            }
+
+            hk = ngx_array_push(&hide_headers);
+            if (hk == NULL) {
+                return NGX_ERROR;
+            }
+
+            hk->key = h[i];
+            hk->key_hash = ngx_hash_key_lc(h[i].data, h[i].len);
+            hk->value = (void *) 1;
+
+        exist:
+
+            continue;
+        }
+    }
+
+    if (conf->pass_headers != NGX_CONF_UNSET_PTR) {
+
+        h = conf->pass_headers->elts;
+        hk = hide_headers.elts;
+
+        for (i = 0; i < conf->pass_headers->nelts; i++) {
+            for (j = 0; j < hide_headers.nelts; j++) {
+
+                if (hk[j].key.data == NULL) {
+                    continue;
+                }
+
+                if (ngx_strcasecmp(h[i].data, hk[j].key.data) == 0) {
+                    hk[j].key.data = NULL;
+                    break;
+                }
+            }
+        }
+    }
+
+    hash->hash = &conf->hide_headers_hash;
+    hash->key = ngx_hash_key_lc;
+    hash->pool = cf->pool;
+    hash->temp_pool = NULL;
+
+    if (ngx_hash_init(hash, hide_headers.elts, hide_headers.nelts) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    /*
+     * special handling to preserve conf->hide_headers_hash
+     * in the "http" section to inherit it to all servers
+     */
+
+    if (prev->hide_headers_hash.buckets == NULL
+        && conf->hide_headers == prev->hide_headers
+        && conf->pass_headers == prev->pass_headers)
+    {
+        prev->hide_headers_hash = conf->hide_headers_hash;
+    }
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_upstream_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_http_upstream_main_conf_t  *umcf;
+
+    umcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_main_conf_t));
+    if (umcf == NULL) {
+        return NULL;
+    }
+
+    if (ngx_array_init(&umcf->upstreams, cf->pool, 4,
+                       sizeof(ngx_http_upstream_srv_conf_t *))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    return umcf;
+}
+
+
+static char *
+ngx_http_upstream_init_main_conf(ngx_conf_t *cf, void *conf)
+{
+    ngx_http_upstream_main_conf_t  *umcf = conf;
+
+    ngx_uint_t                      i;
+    ngx_array_t                     headers_in;
+    ngx_hash_key_t                 *hk;
+    ngx_hash_init_t                 hash;
+    ngx_http_upstream_init_pt       init;
+    ngx_http_upstream_header_t     *header;
+    ngx_http_upstream_srv_conf_t  **uscfp;
+
+    uscfp = umcf->upstreams.elts;
+
+    for (i = 0; i < umcf->upstreams.nelts; i++) {
+
+        init = uscfp[i]->peer.init_upstream ? uscfp[i]->peer.init_upstream:
+                                            ngx_http_upstream_init_round_robin;
+
+        if (init(cf, uscfp[i]) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+
+    /* upstream_headers_in_hash */
+
+    if (ngx_array_init(&headers_in, cf->temp_pool, 32, sizeof(ngx_hash_key_t))
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    for (header = ngx_http_upstream_headers_in; header->name.len; header++) {
+        hk = ngx_array_push(&headers_in);
+        if (hk == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        hk->key = header->name;
+        hk->key_hash = ngx_hash_key_lc(header->name.data, header->name.len);
+        hk->value = header;
+    }
+
+    hash.hash = &umcf->headers_in_hash;
+    hash.key = ngx_hash_key_lc;
+    hash.max_size = 512;
+    hash.bucket_size = ngx_align(64, ngx_cacheline_size);
+    hash.name = "upstream_headers_in_hash";
+    hash.pool = cf->pool;
+    hash.temp_pool = NULL;
+
+    if (ngx_hash_init(&hash, headers_in.elts, headers_in.nelts) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/http/ngx_http_upstream.h b/nginx/src/http/ngx_http_upstream.h
new file mode 100644 (file)
index 0000000..c2f4dc0
--- /dev/null
@@ -0,0 +1,435 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_UPSTREAM_H_INCLUDED_
+#define _NGX_HTTP_UPSTREAM_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_connect.h>
+#include <ngx_event_pipe.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_UPSTREAM_FT_ERROR           0x00000002
+#define NGX_HTTP_UPSTREAM_FT_TIMEOUT         0x00000004
+#define NGX_HTTP_UPSTREAM_FT_INVALID_HEADER  0x00000008
+#define NGX_HTTP_UPSTREAM_FT_HTTP_500        0x00000010
+#define NGX_HTTP_UPSTREAM_FT_HTTP_502        0x00000020
+#define NGX_HTTP_UPSTREAM_FT_HTTP_503        0x00000040
+#define NGX_HTTP_UPSTREAM_FT_HTTP_504        0x00000080
+#define NGX_HTTP_UPSTREAM_FT_HTTP_403        0x00000100
+#define NGX_HTTP_UPSTREAM_FT_HTTP_404        0x00000200
+#define NGX_HTTP_UPSTREAM_FT_HTTP_429        0x00000400
+#define NGX_HTTP_UPSTREAM_FT_UPDATING        0x00000800
+#define NGX_HTTP_UPSTREAM_FT_BUSY_LOCK       0x00001000
+#define NGX_HTTP_UPSTREAM_FT_MAX_WAITING     0x00002000
+#define NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT  0x00004000
+#define NGX_HTTP_UPSTREAM_FT_NOLIVE          0x40000000
+#define NGX_HTTP_UPSTREAM_FT_OFF             0x80000000
+
+#define NGX_HTTP_UPSTREAM_FT_STATUS          (NGX_HTTP_UPSTREAM_FT_HTTP_500  \
+                                             |NGX_HTTP_UPSTREAM_FT_HTTP_502  \
+                                             |NGX_HTTP_UPSTREAM_FT_HTTP_503  \
+                                             |NGX_HTTP_UPSTREAM_FT_HTTP_504  \
+                                             |NGX_HTTP_UPSTREAM_FT_HTTP_403  \
+                                             |NGX_HTTP_UPSTREAM_FT_HTTP_404  \
+                                             |NGX_HTTP_UPSTREAM_FT_HTTP_429)
+
+#define NGX_HTTP_UPSTREAM_INVALID_HEADER     40
+
+
+#define NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT    0x00000002
+#define NGX_HTTP_UPSTREAM_IGN_XA_EXPIRES     0x00000004
+#define NGX_HTTP_UPSTREAM_IGN_EXPIRES        0x00000008
+#define NGX_HTTP_UPSTREAM_IGN_CACHE_CONTROL  0x00000010
+#define NGX_HTTP_UPSTREAM_IGN_SET_COOKIE     0x00000020
+#define NGX_HTTP_UPSTREAM_IGN_XA_LIMIT_RATE  0x00000040
+#define NGX_HTTP_UPSTREAM_IGN_XA_BUFFERING   0x00000080
+#define NGX_HTTP_UPSTREAM_IGN_XA_CHARSET     0x00000100
+#define NGX_HTTP_UPSTREAM_IGN_VARY           0x00000200
+
+
+typedef struct {
+    ngx_uint_t                       status;
+    ngx_msec_t                       response_time;
+    ngx_msec_t                       connect_time;
+    ngx_msec_t                       header_time;
+    ngx_msec_t                       queue_time;
+    off_t                            response_length;
+    off_t                            bytes_received;
+
+    ngx_str_t                       *peer;
+} ngx_http_upstream_state_t;
+
+
+typedef struct {
+    ngx_hash_t                       headers_in_hash;
+    ngx_array_t                      upstreams;
+                                             /* ngx_http_upstream_srv_conf_t */
+} ngx_http_upstream_main_conf_t;
+
+typedef struct ngx_http_upstream_srv_conf_s  ngx_http_upstream_srv_conf_t;
+
+typedef ngx_int_t (*ngx_http_upstream_init_pt)(ngx_conf_t *cf,
+    ngx_http_upstream_srv_conf_t *us);
+typedef ngx_int_t (*ngx_http_upstream_init_peer_pt)(ngx_http_request_t *r,
+    ngx_http_upstream_srv_conf_t *us);
+
+
+typedef struct {
+    ngx_http_upstream_init_pt        init_upstream;
+    ngx_http_upstream_init_peer_pt   init;
+    void                            *data;
+} ngx_http_upstream_peer_t;
+
+
+typedef struct {
+    ngx_str_t                        name;
+    ngx_addr_t                      *addrs;
+    ngx_uint_t                       naddrs;
+    ngx_uint_t                       weight;
+    ngx_uint_t                       max_conns;
+    ngx_uint_t                       max_fails;
+    time_t                           fail_timeout;
+    ngx_msec_t                       slow_start;
+    ngx_uint_t                       down;
+
+    unsigned                         backup:1;
+
+    NGX_COMPAT_BEGIN(6)
+    NGX_COMPAT_END
+} ngx_http_upstream_server_t;
+
+
+#define NGX_HTTP_UPSTREAM_CREATE        0x0001
+#define NGX_HTTP_UPSTREAM_WEIGHT        0x0002
+#define NGX_HTTP_UPSTREAM_MAX_FAILS     0x0004
+#define NGX_HTTP_UPSTREAM_FAIL_TIMEOUT  0x0008
+#define NGX_HTTP_UPSTREAM_DOWN          0x0010
+#define NGX_HTTP_UPSTREAM_BACKUP        0x0020
+#define NGX_HTTP_UPSTREAM_MAX_CONNS     0x0100
+
+
+struct ngx_http_upstream_srv_conf_s {
+    ngx_http_upstream_peer_t         peer;
+    void                           **srv_conf;
+
+    ngx_array_t                     *servers;  /* ngx_http_upstream_server_t */
+
+    ngx_uint_t                       flags;
+    ngx_str_t                        host;
+    u_char                          *file_name;
+    ngx_uint_t                       line;
+    in_port_t                        port;
+    ngx_uint_t                       no_port;  /* unsigned no_port:1 */
+
+#if (NGX_HTTP_UPSTREAM_ZONE)
+    ngx_shm_zone_t                  *shm_zone;
+#endif
+};
+
+
+typedef struct {
+    ngx_addr_t                      *addr;
+    ngx_http_complex_value_t        *value;
+#if (NGX_HAVE_TRANSPARENT_PROXY)
+    ngx_uint_t                       transparent; /* unsigned  transparent:1; */
+#endif
+} ngx_http_upstream_local_t;
+
+
+typedef struct {
+    ngx_http_upstream_srv_conf_t    *upstream;
+
+    ngx_msec_t                       connect_timeout;
+    ngx_msec_t                       send_timeout;
+    ngx_msec_t                       read_timeout;
+    ngx_msec_t                       next_upstream_timeout;
+
+    size_t                           send_lowat;
+    size_t                           buffer_size;
+    size_t                           limit_rate;
+
+    size_t                           busy_buffers_size;
+    size_t                           max_temp_file_size;
+    size_t                           temp_file_write_size;
+
+    size_t                           busy_buffers_size_conf;
+    size_t                           max_temp_file_size_conf;
+    size_t                           temp_file_write_size_conf;
+
+    ngx_bufs_t                       bufs;
+
+    ngx_uint_t                       ignore_headers;
+    ngx_uint_t                       next_upstream;
+    ngx_uint_t                       store_access;
+    ngx_uint_t                       next_upstream_tries;
+    ngx_flag_t                       buffering;
+    ngx_flag_t                       request_buffering;
+    ngx_flag_t                       pass_request_headers;
+    ngx_flag_t                       pass_request_body;
+
+    ngx_flag_t                       ignore_client_abort;
+    ngx_flag_t                       intercept_errors;
+    ngx_flag_t                       cyclic_temp_file;
+    ngx_flag_t                       force_ranges;
+
+    ngx_path_t                      *temp_path;
+
+    ngx_hash_t                       hide_headers_hash;
+    ngx_array_t                     *hide_headers;
+    ngx_array_t                     *pass_headers;
+
+    ngx_http_upstream_local_t       *local;
+
+#if (NGX_HTTP_CACHE)
+    ngx_shm_zone_t                  *cache_zone;
+    ngx_http_complex_value_t        *cache_value;
+
+    ngx_uint_t                       cache_min_uses;
+    ngx_uint_t                       cache_use_stale;
+    ngx_uint_t                       cache_methods;
+
+    off_t                            cache_max_range_offset;
+
+    ngx_flag_t                       cache_lock;
+    ngx_msec_t                       cache_lock_timeout;
+    ngx_msec_t                       cache_lock_age;
+
+    ngx_flag_t                       cache_revalidate;
+    ngx_flag_t                       cache_convert_head;
+    ngx_flag_t                       cache_background_update;
+
+    ngx_array_t                     *cache_valid;
+    ngx_array_t                     *cache_bypass;
+    ngx_array_t                     *cache_purge;
+    ngx_array_t                     *no_cache;
+#endif
+
+    ngx_array_t                     *store_lengths;
+    ngx_array_t                     *store_values;
+
+#if (NGX_HTTP_CACHE)
+    signed                           cache:2;
+#endif
+    signed                           store:2;
+    unsigned                         intercept_404:1;
+    unsigned                         change_buffering:1;
+    unsigned                         pass_trailers:1;
+    unsigned                         preserve_output:1;
+
+#if (NGX_HTTP_SSL || NGX_COMPAT)
+    ngx_ssl_t                       *ssl;
+    ngx_flag_t                       ssl_session_reuse;
+
+    ngx_http_complex_value_t        *ssl_name;
+    ngx_flag_t                       ssl_server_name;
+    ngx_flag_t                       ssl_verify;
+#endif
+
+    ngx_str_t                        module;
+
+    NGX_COMPAT_BEGIN(2)
+    NGX_COMPAT_END
+} ngx_http_upstream_conf_t;
+
+
+typedef struct {
+    ngx_str_t                        name;
+    ngx_http_header_handler_pt       handler;
+    ngx_uint_t                       offset;
+    ngx_http_header_handler_pt       copy_handler;
+    ngx_uint_t                       conf;
+    ngx_uint_t                       redirect;  /* unsigned   redirect:1; */
+} ngx_http_upstream_header_t;
+
+
+typedef struct {
+    ngx_list_t                       headers;
+    ngx_list_t                       trailers;
+
+    ngx_uint_t                       status_n;
+    ngx_str_t                        status_line;
+
+    ngx_table_elt_t                 *status;
+    ngx_table_elt_t                 *date;
+    ngx_table_elt_t                 *server;
+    ngx_table_elt_t                 *connection;
+
+    ngx_table_elt_t                 *expires;
+    ngx_table_elt_t                 *etag;
+    ngx_table_elt_t                 *x_accel_expires;
+    ngx_table_elt_t                 *x_accel_redirect;
+    ngx_table_elt_t                 *x_accel_limit_rate;
+
+    ngx_table_elt_t                 *content_type;
+    ngx_table_elt_t                 *content_length;
+
+    ngx_table_elt_t                 *last_modified;
+    ngx_table_elt_t                 *location;
+    ngx_table_elt_t                 *accept_ranges;
+    ngx_table_elt_t                 *www_authenticate;
+    ngx_table_elt_t                 *transfer_encoding;
+    ngx_table_elt_t                 *vary;
+
+#if (NGX_HTTP_GZIP)
+    ngx_table_elt_t                 *content_encoding;
+#endif
+
+    ngx_array_t                      cache_control;
+    ngx_array_t                      cookies;
+
+    off_t                            content_length_n;
+    time_t                           last_modified_time;
+
+    unsigned                         connection_close:1;
+    unsigned                         chunked:1;
+} ngx_http_upstream_headers_in_t;
+
+
+typedef struct {
+    ngx_str_t                        host;
+    in_port_t                        port;
+    ngx_uint_t                       no_port; /* unsigned no_port:1 */
+
+    ngx_uint_t                       naddrs;
+    ngx_resolver_addr_t             *addrs;
+
+    struct sockaddr                 *sockaddr;
+    socklen_t                        socklen;
+    ngx_str_t                        name;
+
+    ngx_resolver_ctx_t              *ctx;
+} ngx_http_upstream_resolved_t;
+
+
+typedef void (*ngx_http_upstream_handler_pt)(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+
+
+struct ngx_http_upstream_s {
+    ngx_http_upstream_handler_pt     read_event_handler;
+    ngx_http_upstream_handler_pt     write_event_handler;
+
+    ngx_peer_connection_t            peer;
+
+    ngx_event_pipe_t                *pipe;
+
+    ngx_chain_t                     *request_bufs;
+
+    ngx_output_chain_ctx_t           output;
+    ngx_chain_writer_ctx_t           writer;
+
+    ngx_http_upstream_conf_t        *conf;
+    ngx_http_upstream_srv_conf_t    *upstream;
+#if (NGX_HTTP_CACHE)
+    ngx_array_t                     *caches;
+#endif
+
+    ngx_http_upstream_headers_in_t   headers_in;
+
+    ngx_http_upstream_resolved_t    *resolved;
+
+    ngx_buf_t                        from_client;
+
+    ngx_buf_t                        buffer;
+    off_t                            length;
+
+    ngx_chain_t                     *out_bufs;
+    ngx_chain_t                     *busy_bufs;
+    ngx_chain_t                     *free_bufs;
+
+    ngx_int_t                      (*input_filter_init)(void *data);
+    ngx_int_t                      (*input_filter)(void *data, ssize_t bytes);
+    void                            *input_filter_ctx;
+
+#if (NGX_HTTP_CACHE)
+    ngx_int_t                      (*create_key)(ngx_http_request_t *r);
+#endif
+    ngx_int_t                      (*create_request)(ngx_http_request_t *r);
+    ngx_int_t                      (*reinit_request)(ngx_http_request_t *r);
+    ngx_int_t                      (*process_header)(ngx_http_request_t *r);
+    void                           (*abort_request)(ngx_http_request_t *r);
+    void                           (*finalize_request)(ngx_http_request_t *r,
+                                         ngx_int_t rc);
+    ngx_int_t                      (*rewrite_redirect)(ngx_http_request_t *r,
+                                         ngx_table_elt_t *h, size_t prefix);
+    ngx_int_t                      (*rewrite_cookie)(ngx_http_request_t *r,
+                                         ngx_table_elt_t *h);
+
+    ngx_msec_t                       timeout;
+
+    ngx_http_upstream_state_t       *state;
+
+    ngx_str_t                        method;
+    ngx_str_t                        schema;
+    ngx_str_t                        uri;
+
+#if (NGX_HTTP_SSL || NGX_COMPAT)
+    ngx_str_t                        ssl_name;
+#endif
+
+    ngx_http_cleanup_pt             *cleanup;
+
+    unsigned                         store:1;
+    unsigned                         cacheable:1;
+    unsigned                         accel:1;
+    unsigned                         ssl:1;
+#if (NGX_HTTP_CACHE)
+    unsigned                         cache_status:3;
+#endif
+
+    unsigned                         buffering:1;
+    unsigned                         keepalive:1;
+    unsigned                         upgrade:1;
+
+    unsigned                         request_sent:1;
+    unsigned                         request_body_sent:1;
+    unsigned                         request_body_blocked:1;
+    unsigned                         header_sent:1;
+};
+
+
+typedef struct {
+    ngx_uint_t                      status;
+    ngx_uint_t                      mask;
+} ngx_http_upstream_next_t;
+
+
+typedef struct {
+    ngx_str_t   key;
+    ngx_str_t   value;
+    ngx_uint_t  skip_empty;
+} ngx_http_upstream_param_t;
+
+
+ngx_int_t ngx_http_upstream_create(ngx_http_request_t *r);
+void ngx_http_upstream_init(ngx_http_request_t *r);
+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,
+    void *conf);
+char *ngx_http_upstream_param_set_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+ngx_int_t ngx_http_upstream_hide_headers_hash(ngx_conf_t *cf,
+    ngx_http_upstream_conf_t *conf, ngx_http_upstream_conf_t *prev,
+    ngx_str_t *default_hide_headers, ngx_hash_init_t *hash);
+
+
+#define ngx_http_conf_upstream_srv_conf(uscf, module)                         \
+    uscf->srv_conf[module.ctx_index]
+
+
+extern ngx_module_t        ngx_http_upstream_module;
+extern ngx_conf_bitmask_t  ngx_http_upstream_cache_method_mask[];
+extern ngx_conf_bitmask_t  ngx_http_upstream_ignore_headers_masks[];
+
+
+#endif /* _NGX_HTTP_UPSTREAM_H_INCLUDED_ */
diff --git a/nginx/src/http/ngx_http_upstream_round_robin.c b/nginx/src/http/ngx_http_upstream_round_robin.c
new file mode 100644 (file)
index 0000000..f6051ae
--- /dev/null
@@ -0,0 +1,842 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define ngx_http_upstream_tries(p) ((p)->number                               \
+                                    + ((p)->next ? (p)->next->number : 0))
+
+
+static ngx_http_upstream_rr_peer_t *ngx_http_upstream_get_peer(
+    ngx_http_upstream_rr_peer_data_t *rrp);
+
+#if (NGX_HTTP_SSL)
+
+static ngx_int_t ngx_http_upstream_empty_set_session(ngx_peer_connection_t *pc,
+    void *data);
+static void ngx_http_upstream_empty_save_session(ngx_peer_connection_t *pc,
+    void *data);
+
+#endif
+
+
+ngx_int_t
+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_http_upstream_server_t    *server;
+    ngx_http_upstream_rr_peer_t   *peer, **peerp;
+    ngx_http_upstream_rr_peers_t  *peers, *backup;
+
+    us->peer.init = ngx_http_upstream_init_round_robin_peer;
+
+    if (us->servers) {
+        server = us->servers->elts;
+
+        n = 0;
+        w = 0;
+
+        for (i = 0; i < us->servers->nelts; i++) {
+            if (server[i].backup) {
+                continue;
+            }
+
+            n += server[i].naddrs;
+            w += server[i].naddrs * server[i].weight;
+        }
+
+        if (n == 0) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "no servers in upstream \"%V\" in %s:%ui",
+                          &us->host, us->file_name, us->line);
+            return NGX_ERROR;
+        }
+
+        peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t));
+        if (peers == NULL) {
+            return NGX_ERROR;
+        }
+
+        peer = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peer_t) * n);
+        if (peer == NULL) {
+            return NGX_ERROR;
+        }
+
+        peers->single = (n == 1);
+        peers->number = n;
+        peers->weighted = (w != n);
+        peers->total_weight = w;
+        peers->name = &us->host;
+
+        n = 0;
+        peerp = &peers->peer;
+
+        for (i = 0; i < us->servers->nelts; i++) {
+            if (server[i].backup) {
+                continue;
+            }
+
+            for (j = 0; j < server[i].naddrs; j++) {
+                peer[n].sockaddr = server[i].addrs[j].sockaddr;
+                peer[n].socklen = server[i].addrs[j].socklen;
+                peer[n].name = server[i].addrs[j].name;
+                peer[n].weight = server[i].weight;
+                peer[n].effective_weight = server[i].weight;
+                peer[n].current_weight = 0;
+                peer[n].max_conns = server[i].max_conns;
+                peer[n].max_fails = server[i].max_fails;
+                peer[n].fail_timeout = server[i].fail_timeout;
+                peer[n].down = server[i].down;
+                peer[n].server = server[i].name;
+
+                *peerp = &peer[n];
+                peerp = &peer[n].next;
+                n++;
+            }
+        }
+
+        us->peer.data = peers;
+
+        /* backup servers */
+
+        n = 0;
+        w = 0;
+
+        for (i = 0; i < us->servers->nelts; i++) {
+            if (!server[i].backup) {
+                continue;
+            }
+
+            n += server[i].naddrs;
+            w += server[i].naddrs * server[i].weight;
+        }
+
+        if (n == 0) {
+            return NGX_OK;
+        }
+
+        backup = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t));
+        if (backup == NULL) {
+            return NGX_ERROR;
+        }
+
+        peer = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peer_t) * n);
+        if (peer == NULL) {
+            return NGX_ERROR;
+        }
+
+        peers->single = 0;
+        backup->single = 0;
+        backup->number = n;
+        backup->weighted = (w != n);
+        backup->total_weight = w;
+        backup->name = &us->host;
+
+        n = 0;
+        peerp = &backup->peer;
+
+        for (i = 0; i < us->servers->nelts; i++) {
+            if (!server[i].backup) {
+                continue;
+            }
+
+            for (j = 0; j < server[i].naddrs; j++) {
+                peer[n].sockaddr = server[i].addrs[j].sockaddr;
+                peer[n].socklen = server[i].addrs[j].socklen;
+                peer[n].name = server[i].addrs[j].name;
+                peer[n].weight = server[i].weight;
+                peer[n].effective_weight = server[i].weight;
+                peer[n].current_weight = 0;
+                peer[n].max_conns = server[i].max_conns;
+                peer[n].max_fails = server[i].max_fails;
+                peer[n].fail_timeout = server[i].fail_timeout;
+                peer[n].down = server[i].down;
+                peer[n].server = server[i].name;
+
+                *peerp = &peer[n];
+                peerp = &peer[n].next;
+                n++;
+            }
+        }
+
+        peers->next = backup;
+
+        return NGX_OK;
+    }
+
+
+    /* an upstream implicitly defined by proxy_pass, etc. */
+
+    if (us->port == 0) {
+        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                      "no port in upstream \"%V\" in %s:%ui",
+                      &us->host, us->file_name, us->line);
+        return NGX_ERROR;
+    }
+
+    ngx_memzero(&u, sizeof(ngx_url_t));
+
+    u.host = us->host;
+    u.port = us->port;
+
+    if (ngx_inet_resolve_host(cf->pool, &u) != NGX_OK) {
+        if (u.err) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "%s in upstream \"%V\" in %s:%ui",
+                          u.err, &us->host, us->file_name, us->line);
+        }
+
+        return NGX_ERROR;
+    }
+
+    n = u.naddrs;
+
+    peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t));
+    if (peers == NULL) {
+        return NGX_ERROR;
+    }
+
+    peer = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peer_t) * n);
+    if (peer == NULL) {
+        return NGX_ERROR;
+    }
+
+    peers->single = (n == 1);
+    peers->number = n;
+    peers->weighted = 0;
+    peers->total_weight = n;
+    peers->name = &us->host;
+
+    peerp = &peers->peer;
+
+    for (i = 0; i < u.naddrs; i++) {
+        peer[i].sockaddr = u.addrs[i].sockaddr;
+        peer[i].socklen = u.addrs[i].socklen;
+        peer[i].name = u.addrs[i].name;
+        peer[i].weight = 1;
+        peer[i].effective_weight = 1;
+        peer[i].current_weight = 0;
+        peer[i].max_conns = 0;
+        peer[i].max_fails = 1;
+        peer[i].fail_timeout = 10;
+        *peerp = &peer[i];
+        peerp = &peer[i].next;
+    }
+
+    us->peer.data = peers;
+
+    /* implicitly defined upstream has no backup servers */
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_upstream_init_round_robin_peer(ngx_http_request_t *r,
+    ngx_http_upstream_srv_conf_t *us)
+{
+    ngx_uint_t                         n;
+    ngx_http_upstream_rr_peer_data_t  *rrp;
+
+    rrp = r->upstream->peer.data;
+
+    if (rrp == NULL) {
+        rrp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_rr_peer_data_t));
+        if (rrp == NULL) {
+            return NGX_ERROR;
+        }
+
+        r->upstream->peer.data = rrp;
+    }
+
+    rrp->peers = us->peer.data;
+    rrp->current = NULL;
+    rrp->config = 0;
+
+    n = rrp->peers->number;
+
+    if (rrp->peers->next && rrp->peers->next->number > n) {
+        n = rrp->peers->next->number;
+    }
+
+    if (n <= 8 * sizeof(uintptr_t)) {
+        rrp->tried = &rrp->data;
+        rrp->data = 0;
+
+    } else {
+        n = (n + (8 * sizeof(uintptr_t) - 1)) / (8 * sizeof(uintptr_t));
+
+        rrp->tried = ngx_pcalloc(r->pool, n * sizeof(uintptr_t));
+        if (rrp->tried == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    r->upstream->peer.get = ngx_http_upstream_get_round_robin_peer;
+    r->upstream->peer.free = ngx_http_upstream_free_round_robin_peer;
+    r->upstream->peer.tries = ngx_http_upstream_tries(rrp->peers);
+#if (NGX_HTTP_SSL)
+    r->upstream->peer.set_session =
+                               ngx_http_upstream_set_round_robin_peer_session;
+    r->upstream->peer.save_session =
+                               ngx_http_upstream_save_round_robin_peer_session;
+#endif
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_upstream_create_round_robin_peer(ngx_http_request_t *r,
+    ngx_http_upstream_resolved_t *ur)
+{
+    u_char                            *p;
+    size_t                             len;
+    socklen_t                          socklen;
+    ngx_uint_t                         i, n;
+    struct sockaddr                   *sockaddr;
+    ngx_http_upstream_rr_peer_t       *peer, **peerp;
+    ngx_http_upstream_rr_peers_t      *peers;
+    ngx_http_upstream_rr_peer_data_t  *rrp;
+
+    rrp = r->upstream->peer.data;
+
+    if (rrp == NULL) {
+        rrp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_rr_peer_data_t));
+        if (rrp == NULL) {
+            return NGX_ERROR;
+        }
+
+        r->upstream->peer.data = rrp;
+    }
+
+    peers = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_rr_peers_t));
+    if (peers == NULL) {
+        return NGX_ERROR;
+    }
+
+    peer = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_rr_peer_t)
+                                * ur->naddrs);
+    if (peer == NULL) {
+        return NGX_ERROR;
+    }
+
+    peers->single = (ur->naddrs == 1);
+    peers->number = ur->naddrs;
+    peers->name = &ur->host;
+
+    if (ur->sockaddr) {
+        peer[0].sockaddr = ur->sockaddr;
+        peer[0].socklen = ur->socklen;
+        peer[0].name = ur->name.data ? ur->name : ur->host;
+        peer[0].weight = 1;
+        peer[0].effective_weight = 1;
+        peer[0].current_weight = 0;
+        peer[0].max_conns = 0;
+        peer[0].max_fails = 1;
+        peer[0].fail_timeout = 10;
+        peers->peer = peer;
+
+    } else {
+        peerp = &peers->peer;
+
+        for (i = 0; i < ur->naddrs; i++) {
+
+            socklen = ur->addrs[i].socklen;
+
+            sockaddr = ngx_palloc(r->pool, socklen);
+            if (sockaddr == NULL) {
+                return NGX_ERROR;
+            }
+
+            ngx_memcpy(sockaddr, ur->addrs[i].sockaddr, socklen);
+            ngx_inet_set_port(sockaddr, ur->port);
+
+            p = ngx_pnalloc(r->pool, NGX_SOCKADDR_STRLEN);
+            if (p == NULL) {
+                return NGX_ERROR;
+            }
+
+            len = ngx_sock_ntop(sockaddr, socklen, p, NGX_SOCKADDR_STRLEN, 1);
+
+            peer[i].sockaddr = sockaddr;
+            peer[i].socklen = socklen;
+            peer[i].name.len = len;
+            peer[i].name.data = p;
+            peer[i].weight = 1;
+            peer[i].effective_weight = 1;
+            peer[i].current_weight = 0;
+            peer[i].max_conns = 0;
+            peer[i].max_fails = 1;
+            peer[i].fail_timeout = 10;
+            *peerp = &peer[i];
+            peerp = &peer[i].next;
+        }
+    }
+
+    rrp->peers = peers;
+    rrp->current = NULL;
+    rrp->config = 0;
+
+    if (rrp->peers->number <= 8 * sizeof(uintptr_t)) {
+        rrp->tried = &rrp->data;
+        rrp->data = 0;
+
+    } else {
+        n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1))
+                / (8 * sizeof(uintptr_t));
+
+        rrp->tried = ngx_pcalloc(r->pool, n * sizeof(uintptr_t));
+        if (rrp->tried == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    r->upstream->peer.get = ngx_http_upstream_get_round_robin_peer;
+    r->upstream->peer.free = ngx_http_upstream_free_round_robin_peer;
+    r->upstream->peer.tries = ngx_http_upstream_tries(rrp->peers);
+#if (NGX_HTTP_SSL)
+    r->upstream->peer.set_session = ngx_http_upstream_empty_set_session;
+    r->upstream->peer.save_session = ngx_http_upstream_empty_save_session;
+#endif
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_upstream_get_round_robin_peer(ngx_peer_connection_t *pc, void *data)
+{
+    ngx_http_upstream_rr_peer_data_t  *rrp = data;
+
+    ngx_int_t                      rc;
+    ngx_uint_t                     i, n;
+    ngx_http_upstream_rr_peer_t   *peer;
+    ngx_http_upstream_rr_peers_t  *peers;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                   "get rr peer, try: %ui", pc->tries);
+
+    pc->cached = 0;
+    pc->connection = NULL;
+
+    peers = rrp->peers;
+    ngx_http_upstream_rr_peers_wlock(peers);
+
+    if (peers->single) {
+        peer = peers->peer;
+
+        if (peer->down) {
+            goto failed;
+        }
+
+        if (peer->max_conns && peer->conns >= peer->max_conns) {
+            goto failed;
+        }
+
+        rrp->current = peer;
+
+    } else {
+
+        /* there are several peers */
+
+        peer = ngx_http_upstream_get_peer(rrp);
+
+        if (peer == NULL) {
+            goto failed;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                       "get rr peer, current: %p %i",
+                       peer, peer->current_weight);
+    }
+
+    pc->sockaddr = peer->sockaddr;
+    pc->socklen = peer->socklen;
+    pc->name = &peer->name;
+
+    peer->conns++;
+
+    ngx_http_upstream_rr_peers_unlock(peers);
+
+    return NGX_OK;
+
+failed:
+
+    if (peers->next) {
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0, "backup servers");
+
+        rrp->peers = peers->next;
+
+        n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1))
+                / (8 * sizeof(uintptr_t));
+
+        for (i = 0; i < n; i++) {
+            rrp->tried[i] = 0;
+        }
+
+        ngx_http_upstream_rr_peers_unlock(peers);
+
+        rc = ngx_http_upstream_get_round_robin_peer(pc, rrp);
+
+        if (rc != NGX_BUSY) {
+            return rc;
+        }
+
+        ngx_http_upstream_rr_peers_wlock(peers);
+    }
+
+    ngx_http_upstream_rr_peers_unlock(peers);
+
+    pc->name = peers->name;
+
+    return NGX_BUSY;
+}
+
+
+static ngx_http_upstream_rr_peer_t *
+ngx_http_upstream_get_peer(ngx_http_upstream_rr_peer_data_t *rrp)
+{
+    time_t                        now;
+    uintptr_t                     m;
+    ngx_int_t                     total;
+    ngx_uint_t                    i, n, p;
+    ngx_http_upstream_rr_peer_t  *peer, *best;
+
+    now = ngx_time();
+
+    best = NULL;
+    total = 0;
+
+#if (NGX_SUPPRESS_WARN)
+    p = 0;
+#endif
+
+    for (peer = rrp->peers->peer, i = 0;
+         peer;
+         peer = peer->next, i++)
+    {
+        n = i / (8 * sizeof(uintptr_t));
+        m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));
+
+        if (rrp->tried[n] & m) {
+            continue;
+        }
+
+        if (peer->down) {
+            continue;
+        }
+
+        if (peer->max_fails
+            && peer->fails >= peer->max_fails
+            && now - peer->checked <= peer->fail_timeout)
+        {
+            continue;
+        }
+
+        if (peer->max_conns && peer->conns >= peer->max_conns) {
+            continue;
+        }
+
+        peer->current_weight += peer->effective_weight;
+        total += peer->effective_weight;
+
+        if (peer->effective_weight < peer->weight) {
+            peer->effective_weight++;
+        }
+
+        if (best == NULL || peer->current_weight > best->current_weight) {
+            best = peer;
+            p = i;
+        }
+    }
+
+    if (best == NULL) {
+        return NULL;
+    }
+
+    rrp->current = best;
+
+    n = p / (8 * sizeof(uintptr_t));
+    m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));
+
+    rrp->tried[n] |= m;
+
+    best->current_weight -= total;
+
+    if (now - best->checked > best->fail_timeout) {
+        best->checked = now;
+    }
+
+    return best;
+}
+
+
+void
+ngx_http_upstream_free_round_robin_peer(ngx_peer_connection_t *pc, void *data,
+    ngx_uint_t state)
+{
+    ngx_http_upstream_rr_peer_data_t  *rrp = data;
+
+    time_t                       now;
+    ngx_http_upstream_rr_peer_t  *peer;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                   "free rr peer %ui %ui", pc->tries, state);
+
+    /* TODO: NGX_PEER_KEEPALIVE */
+
+    peer = rrp->current;
+
+    ngx_http_upstream_rr_peers_rlock(rrp->peers);
+    ngx_http_upstream_rr_peer_lock(rrp->peers, peer);
+
+    if (rrp->peers->single) {
+
+        peer->conns--;
+
+        ngx_http_upstream_rr_peer_unlock(rrp->peers, peer);
+        ngx_http_upstream_rr_peers_unlock(rrp->peers);
+
+        pc->tries = 0;
+        return;
+    }
+
+    if (state & NGX_PEER_FAILED) {
+        now = ngx_time();
+
+        peer->fails++;
+        peer->accessed = now;
+        peer->checked = now;
+
+        if (peer->max_fails) {
+            peer->effective_weight -= peer->weight / peer->max_fails;
+
+            if (peer->fails >= peer->max_fails) {
+                ngx_log_error(NGX_LOG_WARN, pc->log, 0,
+                              "upstream server temporarily disabled");
+            }
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                       "free rr peer failed: %p %i",
+                       peer, peer->effective_weight);
+
+        if (peer->effective_weight < 0) {
+            peer->effective_weight = 0;
+        }
+
+    } else {
+
+        /* mark peer live if check passed */
+
+        if (peer->accessed < peer->checked) {
+            peer->fails = 0;
+        }
+    }
+
+    peer->conns--;
+
+    ngx_http_upstream_rr_peer_unlock(rrp->peers, peer);
+    ngx_http_upstream_rr_peers_unlock(rrp->peers);
+
+    if (pc->tries) {
+        pc->tries--;
+    }
+}
+
+
+#if (NGX_HTTP_SSL)
+
+ngx_int_t
+ngx_http_upstream_set_round_robin_peer_session(ngx_peer_connection_t *pc,
+    void *data)
+{
+    ngx_http_upstream_rr_peer_data_t  *rrp = data;
+
+    ngx_int_t                      rc;
+    ngx_ssl_session_t             *ssl_session;
+    ngx_http_upstream_rr_peer_t   *peer;
+#if (NGX_HTTP_UPSTREAM_ZONE)
+    int                            len;
+#if OPENSSL_VERSION_NUMBER >= 0x0090707fL
+    const
+#endif
+    u_char                        *p;
+    ngx_http_upstream_rr_peers_t  *peers;
+    u_char                         buf[NGX_SSL_MAX_SESSION_SIZE];
+#endif
+
+    peer = rrp->current;
+
+#if (NGX_HTTP_UPSTREAM_ZONE)
+    peers = rrp->peers;
+
+    if (peers->shpool) {
+        ngx_http_upstream_rr_peers_rlock(peers);
+        ngx_http_upstream_rr_peer_lock(peers, peer);
+
+        if (peer->ssl_session == NULL) {
+            ngx_http_upstream_rr_peer_unlock(peers, peer);
+            ngx_http_upstream_rr_peers_unlock(peers);
+            return NGX_OK;
+        }
+
+        len = peer->ssl_session_len;
+
+        ngx_memcpy(buf, peer->ssl_session, len);
+
+        ngx_http_upstream_rr_peer_unlock(peers, peer);
+        ngx_http_upstream_rr_peers_unlock(peers);
+
+        p = buf;
+        ssl_session = d2i_SSL_SESSION(NULL, &p, len);
+
+        rc = ngx_ssl_set_session(pc->connection, ssl_session);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                       "set session: %p", ssl_session);
+
+        ngx_ssl_free_session(ssl_session);
+
+        return rc;
+    }
+#endif
+
+    ssl_session = peer->ssl_session;
+
+    rc = ngx_ssl_set_session(pc->connection, ssl_session);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                   "set session: %p", ssl_session);
+
+    return rc;
+}
+
+
+void
+ngx_http_upstream_save_round_robin_peer_session(ngx_peer_connection_t *pc,
+    void *data)
+{
+    ngx_http_upstream_rr_peer_data_t  *rrp = data;
+
+    ngx_ssl_session_t             *old_ssl_session, *ssl_session;
+    ngx_http_upstream_rr_peer_t   *peer;
+#if (NGX_HTTP_UPSTREAM_ZONE)
+    int                            len;
+    u_char                        *p;
+    ngx_http_upstream_rr_peers_t  *peers;
+    u_char                         buf[NGX_SSL_MAX_SESSION_SIZE];
+#endif
+
+#if (NGX_HTTP_UPSTREAM_ZONE)
+    peers = rrp->peers;
+
+    if (peers->shpool) {
+
+        ssl_session = SSL_get0_session(pc->connection->ssl->connection);
+
+        if (ssl_session == NULL) {
+            return;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                       "save session: %p", ssl_session);
+
+        len = i2d_SSL_SESSION(ssl_session, NULL);
+
+        /* do not cache too big session */
+
+        if (len > NGX_SSL_MAX_SESSION_SIZE) {
+            return;
+        }
+
+        p = buf;
+        (void) i2d_SSL_SESSION(ssl_session, &p);
+
+        peer = rrp->current;
+
+        ngx_http_upstream_rr_peers_rlock(peers);
+        ngx_http_upstream_rr_peer_lock(peers, peer);
+
+        if (len > peer->ssl_session_len) {
+            ngx_shmtx_lock(&peers->shpool->mutex);
+
+            if (peer->ssl_session) {
+                ngx_slab_free_locked(peers->shpool, peer->ssl_session);
+            }
+
+            peer->ssl_session = ngx_slab_alloc_locked(peers->shpool, len);
+
+            ngx_shmtx_unlock(&peers->shpool->mutex);
+
+            if (peer->ssl_session == NULL) {
+                peer->ssl_session_len = 0;
+
+                ngx_http_upstream_rr_peer_unlock(peers, peer);
+                ngx_http_upstream_rr_peers_unlock(peers);
+                return;
+            }
+
+            peer->ssl_session_len = len;
+        }
+
+        ngx_memcpy(peer->ssl_session, buf, len);
+
+        ngx_http_upstream_rr_peer_unlock(peers, peer);
+        ngx_http_upstream_rr_peers_unlock(peers);
+
+        return;
+    }
+#endif
+
+    ssl_session = ngx_ssl_get_session(pc->connection);
+
+    if (ssl_session == NULL) {
+        return;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                   "save session: %p", ssl_session);
+
+    peer = rrp->current;
+
+    old_ssl_session = peer->ssl_session;
+    peer->ssl_session = ssl_session;
+
+    if (old_ssl_session) {
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+                       "old session: %p", old_ssl_session);
+
+        /* TODO: may block */
+
+        ngx_ssl_free_session(old_ssl_session);
+    }
+}
+
+
+static ngx_int_t
+ngx_http_upstream_empty_set_session(ngx_peer_connection_t *pc, void *data)
+{
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_upstream_empty_save_session(ngx_peer_connection_t *pc, void *data)
+{
+    return;
+}
+
+#endif
diff --git a/nginx/src/http/ngx_http_upstream_round_robin.h b/nginx/src/http/ngx_http_upstream_round_robin.h
new file mode 100644 (file)
index 0000000..45f258d
--- /dev/null
@@ -0,0 +1,156 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_UPSTREAM_ROUND_ROBIN_H_INCLUDED_
+#define _NGX_HTTP_UPSTREAM_ROUND_ROBIN_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct ngx_http_upstream_rr_peer_s   ngx_http_upstream_rr_peer_t;
+
+struct ngx_http_upstream_rr_peer_s {
+    struct sockaddr                *sockaddr;
+    socklen_t                       socklen;
+    ngx_str_t                       name;
+    ngx_str_t                       server;
+
+    ngx_int_t                       current_weight;
+    ngx_int_t                       effective_weight;
+    ngx_int_t                       weight;
+
+    ngx_uint_t                      conns;
+    ngx_uint_t                      max_conns;
+
+    ngx_uint_t                      fails;
+    time_t                          accessed;
+    time_t                          checked;
+
+    ngx_uint_t                      max_fails;
+    time_t                          fail_timeout;
+    ngx_msec_t                      slow_start;
+    ngx_msec_t                      start_time;
+
+    ngx_uint_t                      down;
+
+#if (NGX_HTTP_SSL || NGX_COMPAT)
+    void                           *ssl_session;
+    int                             ssl_session_len;
+#endif
+
+#if (NGX_HTTP_UPSTREAM_ZONE)
+    ngx_atomic_t                    lock;
+#endif
+
+    ngx_http_upstream_rr_peer_t    *next;
+
+    NGX_COMPAT_BEGIN(32)
+    NGX_COMPAT_END
+};
+
+
+typedef struct ngx_http_upstream_rr_peers_s  ngx_http_upstream_rr_peers_t;
+
+struct ngx_http_upstream_rr_peers_s {
+    ngx_uint_t                      number;
+
+#if (NGX_HTTP_UPSTREAM_ZONE)
+    ngx_slab_pool_t                *shpool;
+    ngx_atomic_t                    rwlock;
+    ngx_http_upstream_rr_peers_t   *zone_next;
+#endif
+
+    ngx_uint_t                      total_weight;
+
+    unsigned                        single:1;
+    unsigned                        weighted:1;
+
+    ngx_str_t                      *name;
+
+    ngx_http_upstream_rr_peers_t   *next;
+
+    ngx_http_upstream_rr_peer_t    *peer;
+};
+
+
+#if (NGX_HTTP_UPSTREAM_ZONE)
+
+#define ngx_http_upstream_rr_peers_rlock(peers)                               \
+                                                                              \
+    if (peers->shpool) {                                                      \
+        ngx_rwlock_rlock(&peers->rwlock);                                     \
+    }
+
+#define ngx_http_upstream_rr_peers_wlock(peers)                               \
+                                                                              \
+    if (peers->shpool) {                                                      \
+        ngx_rwlock_wlock(&peers->rwlock);                                     \
+    }
+
+#define ngx_http_upstream_rr_peers_unlock(peers)                              \
+                                                                              \
+    if (peers->shpool) {                                                      \
+        ngx_rwlock_unlock(&peers->rwlock);                                    \
+    }
+
+
+#define ngx_http_upstream_rr_peer_lock(peers, peer)                           \
+                                                                              \
+    if (peers->shpool) {                                                      \
+        ngx_rwlock_wlock(&peer->lock);                                        \
+    }
+
+#define ngx_http_upstream_rr_peer_unlock(peers, peer)                         \
+                                                                              \
+    if (peers->shpool) {                                                      \
+        ngx_rwlock_unlock(&peer->lock);                                       \
+    }
+
+#else
+
+#define ngx_http_upstream_rr_peers_rlock(peers)
+#define ngx_http_upstream_rr_peers_wlock(peers)
+#define ngx_http_upstream_rr_peers_unlock(peers)
+#define ngx_http_upstream_rr_peer_lock(peers, peer)
+#define ngx_http_upstream_rr_peer_unlock(peers, peer)
+
+#endif
+
+
+typedef struct {
+    ngx_uint_t                      config;
+    ngx_http_upstream_rr_peers_t   *peers;
+    ngx_http_upstream_rr_peer_t    *current;
+    uintptr_t                      *tried;
+    uintptr_t                       data;
+} ngx_http_upstream_rr_peer_data_t;
+
+
+ngx_int_t ngx_http_upstream_init_round_robin(ngx_conf_t *cf,
+    ngx_http_upstream_srv_conf_t *us);
+ngx_int_t ngx_http_upstream_init_round_robin_peer(ngx_http_request_t *r,
+    ngx_http_upstream_srv_conf_t *us);
+ngx_int_t ngx_http_upstream_create_round_robin_peer(ngx_http_request_t *r,
+    ngx_http_upstream_resolved_t *ur);
+ngx_int_t ngx_http_upstream_get_round_robin_peer(ngx_peer_connection_t *pc,
+    void *data);
+void ngx_http_upstream_free_round_robin_peer(ngx_peer_connection_t *pc,
+    void *data, ngx_uint_t state);
+
+#if (NGX_HTTP_SSL)
+ngx_int_t
+    ngx_http_upstream_set_round_robin_peer_session(ngx_peer_connection_t *pc,
+    void *data);
+void ngx_http_upstream_save_round_robin_peer_session(ngx_peer_connection_t *pc,
+    void *data);
+#endif
+
+
+#endif /* _NGX_HTTP_UPSTREAM_ROUND_ROBIN_H_INCLUDED_ */
diff --git a/nginx/src/http/ngx_http_variables.c b/nginx/src/http/ngx_http_variables.c
new file mode 100644 (file)
index 0000000..9bd5b6b
--- /dev/null
@@ -0,0 +1,2713 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <nginx.h>
+
+
+static ngx_http_variable_t *ngx_http_add_prefix_variable(ngx_conf_t *cf,
+    ngx_str_t *name, ngx_uint_t flags);
+
+static ngx_int_t ngx_http_variable_request(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+#if 0
+static void ngx_http_variable_request_set(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+#endif
+static ngx_int_t ngx_http_variable_request_get_size(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static void ngx_http_variable_request_set_size(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_header(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+
+static ngx_int_t ngx_http_variable_cookies(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_headers(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_headers_internal(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data, u_char sep);
+
+static ngx_int_t ngx_http_variable_unknown_header_in(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_unknown_header_out(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_unknown_trailer_out(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_request_line(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_cookie(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_argument(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+#if (NGX_HAVE_TCP_INFO)
+static ngx_int_t ngx_http_variable_tcpinfo(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+#endif
+
+static ngx_int_t ngx_http_variable_content_length(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_host(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_binary_remote_addr(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_remote_addr(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_remote_port(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_proxy_protocol_addr(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_proxy_protocol_port(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_server_addr(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_server_port(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_scheme(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_https(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static void ngx_http_variable_set_args(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_is_args(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_document_root(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_realpath_root(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_request_filename(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_server_name(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_request_method(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_remote_user(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_bytes_sent(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_body_bytes_sent(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_pipe(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_request_completion(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_request_body(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_request_body_file(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_request_length(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_request_time(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_request_id(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_status(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+
+static ngx_int_t ngx_http_variable_sent_content_type(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_sent_content_length(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_sent_location(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_sent_last_modified(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_sent_connection(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_sent_keep_alive(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_sent_transfer_encoding(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+
+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_nginx_version(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_hostname(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_pid(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_msec(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_time_iso8601(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_time_local(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+
+/*
+ * TODO:
+ *     Apache CGI: AUTH_TYPE, PATH_INFO (null), PATH_TRANSLATED
+ *                 REMOTE_HOST (null), REMOTE_IDENT (null),
+ *                 SERVER_SOFTWARE
+ *
+ *     Apache SSI: DOCUMENT_NAME, LAST_MODIFIED, USER_NAME (file owner)
+ */
+
+/*
+ * the $http_host, $http_user_agent, $http_referer, and $http_via
+ * variables may be handled by generic
+ * ngx_http_variable_unknown_header_in(), but for performance reasons
+ * they are handled using dedicated entries
+ */
+
+static ngx_http_variable_t  ngx_http_core_variables[] = {
+
+    { ngx_string("http_host"), NULL, ngx_http_variable_header,
+      offsetof(ngx_http_request_t, headers_in.host), 0, 0 },
+
+    { ngx_string("http_user_agent"), NULL, ngx_http_variable_header,
+      offsetof(ngx_http_request_t, headers_in.user_agent), 0, 0 },
+
+    { ngx_string("http_referer"), NULL, ngx_http_variable_header,
+      offsetof(ngx_http_request_t, headers_in.referer), 0, 0 },
+
+#if (NGX_HTTP_GZIP)
+    { ngx_string("http_via"), NULL, ngx_http_variable_header,
+      offsetof(ngx_http_request_t, headers_in.via), 0, 0 },
+#endif
+
+#if (NGX_HTTP_X_FORWARDED_FOR)
+    { ngx_string("http_x_forwarded_for"), NULL, ngx_http_variable_headers,
+      offsetof(ngx_http_request_t, headers_in.x_forwarded_for), 0, 0 },
+#endif
+
+    { ngx_string("http_cookie"), NULL, ngx_http_variable_cookies,
+      offsetof(ngx_http_request_t, headers_in.cookies), 0, 0 },
+
+    { ngx_string("content_length"), NULL, ngx_http_variable_content_length,
+      0, 0, 0 },
+
+    { ngx_string("content_type"), NULL, ngx_http_variable_header,
+      offsetof(ngx_http_request_t, headers_in.content_type), 0, 0 },
+
+    { ngx_string("host"), NULL, ngx_http_variable_host, 0, 0, 0 },
+
+    { ngx_string("binary_remote_addr"), NULL,
+      ngx_http_variable_binary_remote_addr, 0, 0, 0 },
+
+    { ngx_string("remote_addr"), NULL, ngx_http_variable_remote_addr, 0, 0, 0 },
+
+    { ngx_string("remote_port"), NULL, ngx_http_variable_remote_port, 0, 0, 0 },
+
+    { ngx_string("proxy_protocol_addr"), NULL,
+      ngx_http_variable_proxy_protocol_addr, 0, 0, 0 },
+
+    { ngx_string("proxy_protocol_port"), NULL,
+      ngx_http_variable_proxy_protocol_port, 0, 0, 0 },
+
+    { ngx_string("server_addr"), NULL, ngx_http_variable_server_addr, 0, 0, 0 },
+
+    { ngx_string("server_port"), NULL, ngx_http_variable_server_port, 0, 0, 0 },
+
+    { ngx_string("server_protocol"), NULL, ngx_http_variable_request,
+      offsetof(ngx_http_request_t, http_protocol), 0, 0 },
+
+    { ngx_string("scheme"), NULL, ngx_http_variable_scheme, 0, 0, 0 },
+
+    { ngx_string("https"), NULL, ngx_http_variable_https, 0, 0, 0 },
+
+    { ngx_string("request_uri"), NULL, ngx_http_variable_request,
+      offsetof(ngx_http_request_t, unparsed_uri), 0, 0 },
+
+    { ngx_string("uri"), NULL, ngx_http_variable_request,
+      offsetof(ngx_http_request_t, uri),
+      NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("document_uri"), NULL, ngx_http_variable_request,
+      offsetof(ngx_http_request_t, uri),
+      NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("request"), NULL, ngx_http_variable_request_line, 0, 0, 0 },
+
+    { ngx_string("document_root"), NULL,
+      ngx_http_variable_document_root, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("realpath_root"), NULL,
+      ngx_http_variable_realpath_root, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("query_string"), NULL, ngx_http_variable_request,
+      offsetof(ngx_http_request_t, args),
+      NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("args"),
+      ngx_http_variable_set_args,
+      ngx_http_variable_request,
+      offsetof(ngx_http_request_t, args),
+      NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("is_args"), NULL, ngx_http_variable_is_args,
+      0, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("request_filename"), NULL,
+      ngx_http_variable_request_filename, 0,
+      NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("server_name"), NULL, ngx_http_variable_server_name, 0, 0, 0 },
+
+    { ngx_string("request_method"), NULL,
+      ngx_http_variable_request_method, 0,
+      NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("remote_user"), NULL, ngx_http_variable_remote_user, 0, 0, 0 },
+
+    { ngx_string("bytes_sent"), NULL, ngx_http_variable_bytes_sent,
+      0, 0, 0 },
+
+    { ngx_string("body_bytes_sent"), NULL, ngx_http_variable_body_bytes_sent,
+      0, 0, 0 },
+
+    { ngx_string("pipe"), NULL, ngx_http_variable_pipe,
+      0, 0, 0 },
+
+    { ngx_string("request_completion"), NULL,
+      ngx_http_variable_request_completion,
+      0, 0, 0 },
+
+    { ngx_string("request_body"), NULL,
+      ngx_http_variable_request_body,
+      0, 0, 0 },
+
+    { ngx_string("request_body_file"), NULL,
+      ngx_http_variable_request_body_file,
+      0, 0, 0 },
+
+    { ngx_string("request_length"), NULL, ngx_http_variable_request_length,
+      0, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("request_time"), NULL, ngx_http_variable_request_time,
+      0, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("request_id"), NULL,
+      ngx_http_variable_request_id,
+      0, 0, 0 },
+
+    { ngx_string("status"), NULL,
+      ngx_http_variable_status, 0,
+      NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("sent_http_content_type"), NULL,
+      ngx_http_variable_sent_content_type, 0, 0, 0 },
+
+    { ngx_string("sent_http_content_length"), NULL,
+      ngx_http_variable_sent_content_length, 0, 0, 0 },
+
+    { ngx_string("sent_http_location"), NULL,
+      ngx_http_variable_sent_location, 0, 0, 0 },
+
+    { ngx_string("sent_http_last_modified"), NULL,
+      ngx_http_variable_sent_last_modified, 0, 0, 0 },
+
+    { ngx_string("sent_http_connection"), NULL,
+      ngx_http_variable_sent_connection, 0, 0, 0 },
+
+    { ngx_string("sent_http_keep_alive"), NULL,
+      ngx_http_variable_sent_keep_alive, 0, 0, 0 },
+
+    { ngx_string("sent_http_transfer_encoding"), NULL,
+      ngx_http_variable_sent_transfer_encoding, 0, 0, 0 },
+
+    { ngx_string("sent_http_cache_control"), NULL, ngx_http_variable_headers,
+      offsetof(ngx_http_request_t, headers_out.cache_control), 0, 0 },
+
+    { ngx_string("sent_http_link"), NULL, ngx_http_variable_headers,
+      offsetof(ngx_http_request_t, headers_out.link), 0, 0 },
+
+    { ngx_string("limit_rate"), ngx_http_variable_request_set_size,
+      ngx_http_variable_request_get_size,
+      offsetof(ngx_http_request_t, limit_rate),
+      NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("connection"), NULL,
+      ngx_http_variable_connection, 0, 0, 0 },
+
+    { ngx_string("connection_requests"), NULL,
+      ngx_http_variable_connection_requests, 0, 0, 0 },
+
+    { ngx_string("nginx_version"), NULL, ngx_http_variable_nginx_version,
+      0, 0, 0 },
+
+    { ngx_string("hostname"), NULL, ngx_http_variable_hostname,
+      0, 0, 0 },
+
+    { ngx_string("pid"), NULL, ngx_http_variable_pid,
+      0, 0, 0 },
+
+    { ngx_string("msec"), NULL, ngx_http_variable_msec,
+      0, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("time_iso8601"), NULL, ngx_http_variable_time_iso8601,
+      0, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("time_local"), NULL, ngx_http_variable_time_local,
+      0, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+#if (NGX_HAVE_TCP_INFO)
+    { ngx_string("tcpinfo_rtt"), NULL, ngx_http_variable_tcpinfo,
+      0, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("tcpinfo_rttvar"), NULL, ngx_http_variable_tcpinfo,
+      1, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("tcpinfo_snd_cwnd"), NULL, ngx_http_variable_tcpinfo,
+      2, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("tcpinfo_rcv_space"), NULL, ngx_http_variable_tcpinfo,
+      3, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+#endif
+
+    { ngx_string("http_"), NULL, ngx_http_variable_unknown_header_in,
+      0, NGX_HTTP_VAR_PREFIX, 0 },
+
+    { ngx_string("sent_http_"), NULL, ngx_http_variable_unknown_header_out,
+      0, NGX_HTTP_VAR_PREFIX, 0 },
+
+    { ngx_string("sent_trailer_"), NULL, ngx_http_variable_unknown_trailer_out,
+      0, NGX_HTTP_VAR_PREFIX, 0 },
+
+    { ngx_string("cookie_"), NULL, ngx_http_variable_cookie,
+      0, NGX_HTTP_VAR_PREFIX, 0 },
+
+    { ngx_string("arg_"), NULL, ngx_http_variable_argument,
+      0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_PREFIX, 0 },
+
+      ngx_http_null_variable
+};
+
+
+ngx_http_variable_value_t  ngx_http_variable_null_value =
+    ngx_http_variable("");
+ngx_http_variable_value_t  ngx_http_variable_true_value =
+    ngx_http_variable("1");
+
+
+static ngx_uint_t  ngx_http_variable_depth = 100;
+
+
+ngx_http_variable_t *
+ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags)
+{
+    ngx_int_t                   rc;
+    ngx_uint_t                  i;
+    ngx_hash_key_t             *key;
+    ngx_http_variable_t        *v;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    if (name->len == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid variable name \"$\"");
+        return NULL;
+    }
+
+    if (flags & NGX_HTTP_VAR_PREFIX) {
+        return ngx_http_add_prefix_variable(cf, name, flags);
+    }
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    key = cmcf->variables_keys->keys.elts;
+    for (i = 0; i < cmcf->variables_keys->keys.nelts; i++) {
+        if (name->len != key[i].key.len
+            || ngx_strncasecmp(name->data, key[i].key.data, name->len) != 0)
+        {
+            continue;
+        }
+
+        v = key[i].value;
+
+        if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "the duplicate \"%V\" variable", name);
+            return NULL;
+        }
+
+        if (!(flags & NGX_HTTP_VAR_WEAK)) {
+            v->flags &= ~NGX_HTTP_VAR_WEAK;
+        }
+
+        return v;
+    }
+
+    v = ngx_palloc(cf->pool, sizeof(ngx_http_variable_t));
+    if (v == NULL) {
+        return NULL;
+    }
+
+    v->name.len = name->len;
+    v->name.data = ngx_pnalloc(cf->pool, name->len);
+    if (v->name.data == NULL) {
+        return NULL;
+    }
+
+    ngx_strlow(v->name.data, name->data, name->len);
+
+    v->set_handler = NULL;
+    v->get_handler = NULL;
+    v->data = 0;
+    v->flags = flags;
+    v->index = 0;
+
+    rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v, 0);
+
+    if (rc == NGX_ERROR) {
+        return NULL;
+    }
+
+    if (rc == NGX_BUSY) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "conflicting variable name \"%V\"", name);
+        return NULL;
+    }
+
+    return v;
+}
+
+
+static ngx_http_variable_t *
+ngx_http_add_prefix_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags)
+{
+    ngx_uint_t                  i;
+    ngx_http_variable_t        *v;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    v = cmcf->prefix_variables.elts;
+    for (i = 0; i < cmcf->prefix_variables.nelts; i++) {
+        if (name->len != v[i].name.len
+            || ngx_strncasecmp(name->data, v[i].name.data, name->len) != 0)
+        {
+            continue;
+        }
+
+        v = &v[i];
+
+        if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "the duplicate \"%V\" variable", name);
+            return NULL;
+        }
+
+        if (!(flags & NGX_HTTP_VAR_WEAK)) {
+            v->flags &= ~NGX_HTTP_VAR_WEAK;
+        }
+
+        return v;
+    }
+
+    v = ngx_array_push(&cmcf->prefix_variables);
+    if (v == NULL) {
+        return NULL;
+    }
+
+    v->name.len = name->len;
+    v->name.data = ngx_pnalloc(cf->pool, name->len);
+    if (v->name.data == NULL) {
+        return NULL;
+    }
+
+    ngx_strlow(v->name.data, name->data, name->len);
+
+    v->set_handler = NULL;
+    v->get_handler = NULL;
+    v->data = 0;
+    v->flags = flags;
+    v->index = 0;
+
+    return v;
+}
+
+
+ngx_int_t
+ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name)
+{
+    ngx_uint_t                  i;
+    ngx_http_variable_t        *v;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    if (name->len == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid variable name \"$\"");
+        return NGX_ERROR;
+    }
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    v = cmcf->variables.elts;
+
+    if (v == NULL) {
+        if (ngx_array_init(&cmcf->variables, cf->pool, 4,
+                           sizeof(ngx_http_variable_t))
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+
+    } else {
+        for (i = 0; i < cmcf->variables.nelts; i++) {
+            if (name->len != v[i].name.len
+                || ngx_strncasecmp(name->data, v[i].name.data, name->len) != 0)
+            {
+                continue;
+            }
+
+            return i;
+        }
+    }
+
+    v = ngx_array_push(&cmcf->variables);
+    if (v == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->name.len = name->len;
+    v->name.data = ngx_pnalloc(cf->pool, name->len);
+    if (v->name.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_strlow(v->name.data, name->data, name->len);
+
+    v->set_handler = NULL;
+    v->get_handler = NULL;
+    v->data = 0;
+    v->flags = 0;
+    v->index = cmcf->variables.nelts - 1;
+
+    return v->index;
+}
+
+
+ngx_http_variable_value_t *
+ngx_http_get_indexed_variable(ngx_http_request_t *r, ngx_uint_t index)
+{
+    ngx_http_variable_t        *v;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+    if (cmcf->variables.nelts <= index) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                      "unknown variable index: %ui", index);
+        return NULL;
+    }
+
+    if (r->variables[index].not_found || r->variables[index].valid) {
+        return &r->variables[index];
+    }
+
+    v = cmcf->variables.elts;
+
+    if (ngx_http_variable_depth == 0) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "cycle while evaluating variable \"%V\"",
+                      &v[index].name);
+        return NULL;
+    }
+
+    ngx_http_variable_depth--;
+
+    if (v[index].get_handler(r, &r->variables[index], v[index].data)
+        == NGX_OK)
+    {
+        ngx_http_variable_depth++;
+
+        if (v[index].flags & NGX_HTTP_VAR_NOCACHEABLE) {
+            r->variables[index].no_cacheable = 1;
+        }
+
+        return &r->variables[index];
+    }
+
+    ngx_http_variable_depth++;
+
+    r->variables[index].valid = 0;
+    r->variables[index].not_found = 1;
+
+    return NULL;
+}
+
+
+ngx_http_variable_value_t *
+ngx_http_get_flushed_variable(ngx_http_request_t *r, ngx_uint_t index)
+{
+    ngx_http_variable_value_t  *v;
+
+    v = &r->variables[index];
+
+    if (v->valid || v->not_found) {
+        if (!v->no_cacheable) {
+            return v;
+        }
+
+        v->valid = 0;
+        v->not_found = 0;
+    }
+
+    return ngx_http_get_indexed_variable(r, index);
+}
+
+
+ngx_http_variable_value_t *
+ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key)
+{
+    size_t                      len;
+    ngx_uint_t                  i, n;
+    ngx_http_variable_t        *v;
+    ngx_http_variable_value_t  *vv;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+    v = ngx_hash_find(&cmcf->variables_hash, key, name->data, name->len);
+
+    if (v) {
+        if (v->flags & NGX_HTTP_VAR_INDEXED) {
+            return ngx_http_get_flushed_variable(r, v->index);
+        }
+
+        if (ngx_http_variable_depth == 0) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "cycle while evaluating variable \"%V\"", name);
+            return NULL;
+        }
+
+        ngx_http_variable_depth--;
+
+        vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t));
+
+        if (vv && v->get_handler(r, vv, v->data) == NGX_OK) {
+            ngx_http_variable_depth++;
+            return vv;
+        }
+
+        ngx_http_variable_depth++;
+        return NULL;
+    }
+
+    vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t));
+    if (vv == NULL) {
+        return NULL;
+    }
+
+    len = 0;
+
+    v = cmcf->prefix_variables.elts;
+    n = cmcf->prefix_variables.nelts;
+
+    for (i = 0; i < cmcf->prefix_variables.nelts; i++) {
+        if (name->len >= v[i].name.len && name->len > len
+            && ngx_strncmp(name->data, v[i].name.data, v[i].name.len) == 0)
+        {
+            len = v[i].name.len;
+            n = i;
+        }
+    }
+
+    if (n != cmcf->prefix_variables.nelts) {
+        if (v[n].get_handler(r, vv, (uintptr_t) name) == NGX_OK) {
+            return vv;
+        }
+
+        return NULL;
+    }
+
+    vv->not_found = 1;
+
+    return vv;
+}
+
+
+static ngx_int_t
+ngx_http_variable_request(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+    uintptr_t data)
+{
+    ngx_str_t  *s;
+
+    s = (ngx_str_t *) ((char *) r + data);
+
+    if (s->data) {
+        v->len = s->len;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = s->data;
+
+    } else {
+        v->not_found = 1;
+    }
+
+    return NGX_OK;
+}
+
+
+#if 0
+
+static void
+ngx_http_variable_request_set(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_str_t  *s;
+
+    s = (ngx_str_t *) ((char *) r + data);
+
+    s->len = v->len;
+    s->data = v->data;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_variable_request_get_size(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    size_t  *sp;
+
+    sp = (size_t *) ((char *) r + data);
+
+    v->data = ngx_pnalloc(r->pool, NGX_SIZE_T_LEN);
+    if (v->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->len = ngx_sprintf(v->data, "%uz", *sp) - v->data;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_variable_request_set_size(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ssize_t    s, *sp;
+    ngx_str_t  val;
+
+    val.len = v->len;
+    val.data = v->data;
+
+    s = ngx_parse_size(&val);
+
+    if (s == NGX_ERROR) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "invalid size \"%V\"", &val);
+        return;
+    }
+
+    sp = (ssize_t *) ((char *) r + data);
+
+    *sp = s;
+
+    return;
+}
+
+
+static ngx_int_t
+ngx_http_variable_header(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+    uintptr_t data)
+{
+    ngx_table_elt_t  *h;
+
+    h = *(ngx_table_elt_t **) ((char *) r + data);
+
+    if (h) {
+        v->len = h->value.len;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = h->value.data;
+
+    } else {
+        v->not_found = 1;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_cookies(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    return ngx_http_variable_headers_internal(r, v, data, ';');
+}
+
+
+static ngx_int_t
+ngx_http_variable_headers(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    return ngx_http_variable_headers_internal(r, v, data, ',');
+}
+
+
+static ngx_int_t
+ngx_http_variable_headers_internal(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data, u_char sep)
+{
+    size_t             len;
+    u_char            *p, *end;
+    ngx_uint_t         i, n;
+    ngx_array_t       *a;
+    ngx_table_elt_t  **h;
+
+    a = (ngx_array_t *) ((char *) r + data);
+
+    n = a->nelts;
+    h = a->elts;
+
+    len = 0;
+
+    for (i = 0; i < n; i++) {
+
+        if (h[i]->hash == 0) {
+            continue;
+        }
+
+        len += h[i]->value.len + 2;
+    }
+
+    if (len == 0) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    len -= 2;
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    if (n == 1) {
+        v->len = (*h)->value.len;
+        v->data = (*h)->value.data;
+
+        return NGX_OK;
+    }
+
+    p = ngx_pnalloc(r->pool, len);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->len = len;
+    v->data = p;
+
+    end = p + len;
+
+    for (i = 0; /* void */ ; i++) {
+
+        if (h[i]->hash == 0) {
+            continue;
+        }
+
+        p = ngx_copy(p, h[i]->value.data, h[i]->value.len);
+
+        if (p == end) {
+            break;
+        }
+
+        *p++ = sep; *p++ = ' ';
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_unknown_header_in(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    return ngx_http_variable_unknown_header(v, (ngx_str_t *) data,
+                                            &r->headers_in.headers.part,
+                                            sizeof("http_") - 1);
+}
+
+
+static ngx_int_t
+ngx_http_variable_unknown_header_out(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    return ngx_http_variable_unknown_header(v, (ngx_str_t *) data,
+                                            &r->headers_out.headers.part,
+                                            sizeof("sent_http_") - 1);
+}
+
+
+static ngx_int_t
+ngx_http_variable_unknown_trailer_out(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    return ngx_http_variable_unknown_header(v, (ngx_str_t *) data,
+                                            &r->headers_out.trailers.part,
+                                            sizeof("sent_trailer_") - 1);
+}
+
+
+ngx_int_t
+ngx_http_variable_unknown_header(ngx_http_variable_value_t *v, ngx_str_t *var,
+    ngx_list_part_t *part, size_t prefix)
+{
+    u_char            ch;
+    ngx_uint_t        i, n;
+    ngx_table_elt_t  *header;
+
+    header = part->elts;
+
+    for (i = 0; /* void */ ; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (header[i].hash == 0) {
+            continue;
+        }
+
+        for (n = 0; n + prefix < var->len && n < header[i].key.len; n++) {
+            ch = header[i].key.data[n];
+
+            if (ch >= 'A' && ch <= 'Z') {
+                ch |= 0x20;
+
+            } else if (ch == '-') {
+                ch = '_';
+            }
+
+            if (var->data[n + prefix] != ch) {
+                break;
+            }
+        }
+
+        if (n + prefix == var->len && n == header[i].key.len) {
+            v->len = header[i].value.len;
+            v->valid = 1;
+            v->no_cacheable = 0;
+            v->not_found = 0;
+            v->data = header[i].value.data;
+
+            return NGX_OK;
+        }
+    }
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_request_line(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char  *p, *s;
+
+    s = r->request_line.data;
+
+    if (s == NULL) {
+        s = r->request_start;
+
+        if (s == NULL) {
+            v->not_found = 1;
+            return NGX_OK;
+        }
+
+        for (p = s; p < r->header_in->last; p++) {
+            if (*p == CR || *p == LF) {
+                break;
+            }
+        }
+
+        r->request_line.len = p - s;
+        r->request_line.data = s;
+    }
+
+    v->len = r->request_line.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = s;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_cookie(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+    uintptr_t data)
+{
+    ngx_str_t *name = (ngx_str_t *) data;
+
+    ngx_str_t  cookie, s;
+
+    s.len = name->len - (sizeof("cookie_") - 1);
+    s.data = name->data + sizeof("cookie_") - 1;
+
+    if (ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &s, &cookie)
+        == NGX_DECLINED)
+    {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    v->len = cookie.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = cookie.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_argument(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+    uintptr_t data)
+{
+    ngx_str_t *name = (ngx_str_t *) data;
+
+    u_char     *arg;
+    size_t      len;
+    ngx_str_t   value;
+
+    len = name->len - (sizeof("arg_") - 1);
+    arg = name->data + sizeof("arg_") - 1;
+
+    if (ngx_http_arg(r, arg, len, &value) != NGX_OK) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    v->data = value.data;
+    v->len = value.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    return NGX_OK;
+}
+
+
+#if (NGX_HAVE_TCP_INFO)
+
+static ngx_int_t
+ngx_http_variable_tcpinfo(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+    uintptr_t data)
+{
+    struct tcp_info  ti;
+    socklen_t        len;
+    uint32_t         value;
+
+    len = sizeof(struct tcp_info);
+    if (ngxvcl_kvfd_getsockopt(r->connection->fd, IPPROTO_TCP, TCP_INFO, &ti, &len) == -1) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    v->data = ngx_pnalloc(r->pool, NGX_INT32_LEN);
+    if (v->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    switch (data) {
+    case 0:
+        value = ti.tcpi_rtt;
+        break;
+
+    case 1:
+        value = ti.tcpi_rttvar;
+        break;
+
+    case 2:
+        value = ti.tcpi_snd_cwnd;
+        break;
+
+    case 3:
+        value = ti.tcpi_rcv_space;
+        break;
+
+    /* suppress warning */
+    default:
+        value = 0;
+        break;
+    }
+
+    v->len = ngx_sprintf(v->data, "%uD", value) - v->data;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    return NGX_OK;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_variable_content_length(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char  *p;
+
+    if (r->headers_in.content_length) {
+        v->len = r->headers_in.content_length->value.len;
+        v->data = r->headers_in.content_length->value.data;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+
+    } else if (r->reading_body) {
+        v->not_found = 1;
+        v->no_cacheable = 1;
+
+    } else if (r->headers_in.content_length_n >= 0) {
+        p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN);
+        if (p == NULL) {
+            return NGX_ERROR;
+        }
+
+        v->len = ngx_sprintf(p, "%O", r->headers_in.content_length_n) - p;
+        v->data = p;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+
+    } else {
+        v->not_found = 1;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_host(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+    uintptr_t data)
+{
+    ngx_http_core_srv_conf_t  *cscf;
+
+    if (r->headers_in.server.len) {
+        v->len = r->headers_in.server.len;
+        v->data = r->headers_in.server.data;
+
+    } else {
+        cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+        v->len = cscf->server_name.len;
+        v->data = cscf->server_name.data;
+    }
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_binary_remote_addr(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    struct sockaddr_in   *sin;
+#if (NGX_HAVE_INET6)
+    struct sockaddr_in6  *sin6;
+#endif
+
+    switch (r->connection->sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+    case AF_INET6:
+        sin6 = (struct sockaddr_in6 *) r->connection->sockaddr;
+
+        v->len = sizeof(struct in6_addr);
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = sin6->sin6_addr.s6_addr;
+
+        break;
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+    case AF_UNIX:
+
+        v->len = r->connection->addr_text.len;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = r->connection->addr_text.data;
+
+        break;
+#endif
+
+    default: /* AF_INET */
+        sin = (struct sockaddr_in *) r->connection->sockaddr;
+
+        v->len = sizeof(in_addr_t);
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = (u_char *) &sin->sin_addr;
+
+        break;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_remote_addr(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    v->len = r->connection->addr_text.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = r->connection->addr_text.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_remote_port(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_uint_t  port;
+
+    v->len = 0;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    v->data = ngx_pnalloc(r->pool, sizeof("65535") - 1);
+    if (v->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    port = ngx_inet_get_port(r->connection->sockaddr);
+
+    if (port > 0 && port < 65536) {
+        v->len = ngx_sprintf(v->data, "%ui", port) - v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_proxy_protocol_addr(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    v->len = r->connection->proxy_protocol_addr.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = r->connection->proxy_protocol_addr.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_proxy_protocol_port(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_uint_t  port;
+
+    v->len = 0;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    v->data = ngx_pnalloc(r->pool, sizeof("65535") - 1);
+    if (v->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    port = r->connection->proxy_protocol_port;
+
+    if (port > 0 && port < 65536) {
+        v->len = ngx_sprintf(v->data, "%ui", port) - v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_server_addr(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_str_t  s;
+    u_char     addr[NGX_SOCKADDR_STRLEN];
+
+    s.len = NGX_SOCKADDR_STRLEN;
+    s.data = addr;
+
+    if (ngx_connection_local_sockaddr(r->connection, &s, 0) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    s.data = ngx_pnalloc(r->pool, s.len);
+    if (s.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(s.data, addr, s.len);
+
+    v->len = s.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = s.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_server_port(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_uint_t  port;
+
+    v->len = 0;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    if (ngx_connection_local_sockaddr(r->connection, NULL, 0) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    v->data = ngx_pnalloc(r->pool, sizeof("65535") - 1);
+    if (v->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    port = ngx_inet_get_port(r->connection->local_sockaddr);
+
+    if (port > 0 && port < 65536) {
+        v->len = ngx_sprintf(v->data, "%ui", port) - v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_scheme(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+#if (NGX_HTTP_SSL)
+
+    if (r->connection->ssl) {
+        v->len = sizeof("https") - 1;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = (u_char *) "https";
+
+        return NGX_OK;
+    }
+
+#endif
+
+    v->len = sizeof("http") - 1;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = (u_char *) "http";
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_https(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+#if (NGX_HTTP_SSL)
+
+    if (r->connection->ssl) {
+        v->len = sizeof("on") - 1;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = (u_char *) "on";
+
+        return NGX_OK;
+    }
+
+#endif
+
+    *v = ngx_http_variable_null_value;
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_variable_set_args(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    r->args.len = v->len;
+    r->args.data = v->data;
+    r->valid_unparsed_uri = 0;
+}
+
+
+static ngx_int_t
+ngx_http_variable_is_args(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    if (r->args.len == 0) {
+        *v = ngx_http_variable_null_value;
+        return NGX_OK;
+    }
+
+    v->len = 1;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = (u_char *) "?";
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_document_root(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_str_t                  path;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (clcf->root_lengths == NULL) {
+        v->len = clcf->root.len;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = clcf->root.data;
+
+    } else {
+        if (ngx_http_script_run(r, &path, clcf->root_lengths->elts, 0,
+                                clcf->root_values->elts)
+            == NULL)
+        {
+            return NGX_ERROR;
+        }
+
+        if (ngx_get_full_name(r->pool, (ngx_str_t *) &ngx_cycle->prefix, &path)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+
+        v->len = path.len;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = path.data;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_realpath_root(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char                    *real;
+    size_t                     len;
+    ngx_str_t                  path;
+    ngx_http_core_loc_conf_t  *clcf;
+#if (NGX_HAVE_MAX_PATH)
+    u_char                     buffer[NGX_MAX_PATH];
+#endif
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (clcf->root_lengths == NULL) {
+        path = clcf->root;
+
+    } else {
+        if (ngx_http_script_run(r, &path, clcf->root_lengths->elts, 1,
+                                clcf->root_values->elts)
+            == NULL)
+        {
+            return NGX_ERROR;
+        }
+
+        path.data[path.len - 1] = '\0';
+
+        if (ngx_get_full_name(r->pool, (ngx_str_t *) &ngx_cycle->prefix, &path)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+#if (NGX_HAVE_MAX_PATH)
+    real = buffer;
+#else
+    real = NULL;
+#endif
+
+    real = ngx_realpath(path.data, real);
+
+    if (real == NULL) {
+        ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+                      ngx_realpath_n " \"%s\" failed", path.data);
+        return NGX_ERROR;
+    }
+
+    len = ngx_strlen(real);
+
+    v->data = ngx_pnalloc(r->pool, len);
+    if (v->data == NULL) {
+#if !(NGX_HAVE_MAX_PATH)
+        ngx_free(real);
+#endif
+        return NGX_ERROR;
+    }
+
+    v->len = len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    ngx_memcpy(v->data, real, len);
+
+#if !(NGX_HAVE_MAX_PATH)
+    ngx_free(real);
+#endif
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_request_filename(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    size_t     root;
+    ngx_str_t  path;
+
+    if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) {
+        return NGX_ERROR;
+    }
+
+    /* ngx_http_map_uri_to_path() allocates memory for terminating '\0' */
+
+    v->len = path.len - 1;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = path.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_server_name(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_http_core_srv_conf_t  *cscf;
+
+    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+    v->len = cscf->server_name.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = cscf->server_name.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_request_method(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    if (r->main->method_name.data) {
+        v->len = r->main->method_name.len;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = r->main->method_name.data;
+
+    } else {
+        v->not_found = 1;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_remote_user(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_int_t  rc;
+
+    rc = ngx_http_auth_basic_user(r);
+
+    if (rc == NGX_DECLINED) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    if (rc == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    v->len = r->headers_in.user.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = r->headers_in.user.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_bytes_sent(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char  *p;
+
+    p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->len = ngx_sprintf(p, "%O", r->connection->sent) - 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_body_bytes_sent(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    off_t    sent;
+    u_char  *p;
+
+    sent = r->connection->sent - r->header_size;
+
+    if (sent < 0) {
+        sent = 0;
+    }
+
+    p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->len = ngx_sprintf(p, "%O", sent) - 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_pipe(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    v->data = (u_char *) (r->pipeline ? "p" : ".");
+    v->len = 1;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_status(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_uint_t  status;
+
+    v->data = ngx_pnalloc(r->pool, NGX_INT_T_LEN);
+    if (v->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (r->err_status) {
+        status = r->err_status;
+
+    } else if (r->headers_out.status) {
+        status = r->headers_out.status;
+
+    } else if (r->http_version == NGX_HTTP_VERSION_9) {
+        status = 9;
+
+    } else {
+        status = 0;
+    }
+
+    v->len = ngx_sprintf(v->data, "%03ui", status) - v->data;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_sent_content_type(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    if (r->headers_out.content_type.len) {
+        v->len = r->headers_out.content_type.len;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = r->headers_out.content_type.data;
+
+    } else {
+        v->not_found = 1;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_sent_content_length(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char  *p;
+
+    if (r->headers_out.content_length) {
+        v->len = r->headers_out.content_length->value.len;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = r->headers_out.content_length->value.data;
+
+        return NGX_OK;
+    }
+
+    if (r->headers_out.content_length_n >= 0) {
+        p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN);
+        if (p == NULL) {
+            return NGX_ERROR;
+        }
+
+        v->len = ngx_sprintf(p, "%O", r->headers_out.content_length_n) - p;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = p;
+
+        return NGX_OK;
+    }
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_sent_location(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_str_t  name;
+
+    if (r->headers_out.location) {
+        v->len = r->headers_out.location->value.len;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = r->headers_out.location->value.data;
+
+        return NGX_OK;
+    }
+
+    ngx_str_set(&name, "sent_http_location");
+
+    return ngx_http_variable_unknown_header(v, &name,
+                                            &r->headers_out.headers.part,
+                                            sizeof("sent_http_") - 1);
+}
+
+
+static ngx_int_t
+ngx_http_variable_sent_last_modified(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char  *p;
+
+    if (r->headers_out.last_modified) {
+        v->len = r->headers_out.last_modified->value.len;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = r->headers_out.last_modified->value.data;
+
+        return NGX_OK;
+    }
+
+    if (r->headers_out.last_modified_time >= 0) {
+        p = ngx_pnalloc(r->pool, sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1);
+        if (p == NULL) {
+            return NGX_ERROR;
+        }
+
+        v->len = ngx_http_time(p, r->headers_out.last_modified_time) - p;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = p;
+
+        return NGX_OK;
+    }
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_sent_connection(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    size_t   len;
+    char    *p;
+
+    if (r->headers_out.status == NGX_HTTP_SWITCHING_PROTOCOLS) {
+        len = sizeof("upgrade") - 1;
+        p = "upgrade";
+
+    } else if (r->keepalive) {
+        len = sizeof("keep-alive") - 1;
+        p = "keep-alive";
+
+    } else {
+        len = sizeof("close") - 1;
+        p = "close";
+    }
+
+    v->len = len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = (u_char *) p;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_sent_keep_alive(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char                    *p;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    if (r->keepalive) {
+        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+        if (clcf->keepalive_header) {
+
+            p = ngx_pnalloc(r->pool, sizeof("timeout=") - 1 + NGX_TIME_T_LEN);
+            if (p == NULL) {
+                return NGX_ERROR;
+            }
+
+            v->len = ngx_sprintf(p, "timeout=%T", clcf->keepalive_header) - p;
+            v->valid = 1;
+            v->no_cacheable = 0;
+            v->not_found = 0;
+            v->data = p;
+
+            return NGX_OK;
+        }
+    }
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_sent_transfer_encoding(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    if (r->chunked) {
+        v->len = sizeof("chunked") - 1;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = (u_char *) "chunked";
+
+    } else {
+        v->not_found = 1;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_request_completion(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    if (r->request_complete) {
+        v->len = 2;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = (u_char *) "OK";
+
+        return NGX_OK;
+    }
+
+    *v = ngx_http_variable_null_value;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_request_body(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char       *p;
+    size_t        len;
+    ngx_buf_t    *buf;
+    ngx_chain_t  *cl;
+
+    if (r->request_body == NULL
+        || r->request_body->bufs == NULL
+        || r->request_body->temp_file)
+    {
+        v->not_found = 1;
+
+        return NGX_OK;
+    }
+
+    cl = r->request_body->bufs;
+    buf = cl->buf;
+
+    if (cl->next == NULL) {
+        v->len = buf->last - buf->pos;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = buf->pos;
+
+        return NGX_OK;
+    }
+
+    len = buf->last - buf->pos;
+    cl = cl->next;
+
+    for ( /* void */ ; cl; cl = cl->next) {
+        buf = cl->buf;
+        len += buf->last - buf->pos;
+    }
+
+    p = ngx_pnalloc(r->pool, len);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->data = p;
+    cl = r->request_body->bufs;
+
+    for ( /* void */ ; cl; cl = cl->next) {
+        buf = cl->buf;
+        p = ngx_cpymem(p, buf->pos, buf->last - buf->pos);
+    }
+
+    v->len = len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_request_body_file(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    if (r->request_body == NULL || r->request_body->temp_file == NULL) {
+        v->not_found = 1;
+
+        return NGX_OK;
+    }
+
+    v->len = r->request_body->temp_file->file.name.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = r->request_body->temp_file->file.name.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_request_length(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char  *p;
+
+    p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->len = ngx_sprintf(p, "%O", r->request_length) - 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_request_time(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char          *p;
+    ngx_time_t      *tp;
+    ngx_msec_int_t   ms;
+
+    p = ngx_pnalloc(r->pool, NGX_TIME_T_LEN + 4);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    tp = ngx_timeofday();
+
+    ms = (ngx_msec_int_t)
+             ((tp->sec - r->start_sec) * 1000 + (tp->msec - r->start_msec));
+    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_request_id(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char  *id;
+
+#if (NGX_OPENSSL)
+    u_char   random_bytes[16];
+#endif
+
+    id = ngx_pnalloc(r->pool, 32);
+    if (id == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    v->len = 32;
+    v->data = id;
+
+#if (NGX_OPENSSL)
+
+    if (RAND_bytes(random_bytes, 16) == 1) {
+        ngx_hex_dump(id, random_bytes, 16);
+        return NGX_OK;
+    }
+
+    ngx_ssl_error(NGX_LOG_ERR, r->connection->log, 0, "RAND_bytes() failed");
+
+#endif
+
+    ngx_sprintf(id, "%08xD%08xD%08xD%08xD",
+                (uint32_t) ngx_random(), (uint32_t) ngx_random(),
+                (uint32_t) ngx_random(), (uint32_t) ngx_random());
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_connection(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char  *p;
+
+    p = ngx_pnalloc(r->pool, NGX_ATOMIC_T_LEN);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->len = ngx_sprintf(p, "%uA", r->connection->number) - 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_connection_requests(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char  *p;
+
+    p = ngx_pnalloc(r->pool, NGX_INT_T_LEN);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->len = ngx_sprintf(p, "%ui", r->connection->requests) - 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)
+{
+    v->len = sizeof(NGINX_VERSION) - 1;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = (u_char *) NGINX_VERSION;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_hostname(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    v->len = ngx_cycle->hostname.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = ngx_cycle->hostname.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_pid(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char  *p;
+
+    p = ngx_pnalloc(r->pool, NGX_INT64_LEN);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->len = ngx_sprintf(p, "%P", ngx_pid) - 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_msec(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char      *p;
+    ngx_time_t  *tp;
+
+    p = ngx_pnalloc(r->pool, NGX_TIME_T_LEN + 4);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    tp = ngx_timeofday();
+
+    v->len = ngx_sprintf(p, "%T.%03M", tp->sec, tp->msec) - 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_time_iso8601(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char  *p;
+
+    p = ngx_pnalloc(r->pool, ngx_cached_http_log_iso8601.len);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(p, ngx_cached_http_log_iso8601.data,
+               ngx_cached_http_log_iso8601.len);
+
+    v->len = ngx_cached_http_log_iso8601.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = p;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_time_local(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    u_char  *p;
+
+    p = ngx_pnalloc(r->pool, ngx_cached_http_log_time.len);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(p, ngx_cached_http_log_time.data, ngx_cached_http_log_time.len);
+
+    v->len = ngx_cached_http_log_time.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = p;
+
+    return NGX_OK;
+}
+
+
+void *
+ngx_http_map_find(ngx_http_request_t *r, ngx_http_map_t *map, ngx_str_t *match)
+{
+    void        *value;
+    u_char      *low;
+    size_t       len;
+    ngx_uint_t   key;
+
+    len = match->len;
+
+    if (len) {
+        low = ngx_pnalloc(r->pool, len);
+        if (low == NULL) {
+            return NULL;
+        }
+
+    } else {
+        low = NULL;
+    }
+
+    key = ngx_hash_strlow(low, match->data, len);
+
+    value = ngx_hash_find_combined(&map->hash, key, low, len);
+    if (value) {
+        return value;
+    }
+
+#if (NGX_PCRE)
+
+    if (len && map->nregex) {
+        ngx_int_t              n;
+        ngx_uint_t             i;
+        ngx_http_map_regex_t  *reg;
+
+        reg = map->regex;
+
+        for (i = 0; i < map->nregex; i++) {
+
+            n = ngx_http_regex_exec(r, reg[i].regex, match);
+
+            if (n == NGX_OK) {
+                return reg[i].value;
+            }
+
+            if (n == NGX_DECLINED) {
+                continue;
+            }
+
+            /* NGX_ERROR */
+
+            return NULL;
+        }
+    }
+
+#endif
+
+    return NULL;
+}
+
+
+#if (NGX_PCRE)
+
+static ngx_int_t
+ngx_http_variable_not_found(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+    uintptr_t data)
+{
+    v->not_found = 1;
+    return NGX_OK;
+}
+
+
+ngx_http_regex_t *
+ngx_http_regex_compile(ngx_conf_t *cf, ngx_regex_compile_t *rc)
+{
+    u_char                     *p;
+    size_t                      size;
+    ngx_str_t                   name;
+    ngx_uint_t                  i, n;
+    ngx_http_variable_t        *v;
+    ngx_http_regex_t           *re;
+    ngx_http_regex_variable_t  *rv;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    rc->pool = cf->pool;
+
+    if (ngx_regex_compile(rc) != NGX_OK) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc->err);
+        return NULL;
+    }
+
+    re = ngx_pcalloc(cf->pool, sizeof(ngx_http_regex_t));
+    if (re == NULL) {
+        return NULL;
+    }
+
+    re->regex = rc->regex;
+    re->ncaptures = rc->captures;
+    re->name = rc->pattern;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+    cmcf->ncaptures = ngx_max(cmcf->ncaptures, re->ncaptures);
+
+    n = (ngx_uint_t) rc->named_captures;
+
+    if (n == 0) {
+        return re;
+    }
+
+    rv = ngx_palloc(rc->pool, n * sizeof(ngx_http_regex_variable_t));
+    if (rv == NULL) {
+        return NULL;
+    }
+
+    re->variables = rv;
+    re->nvariables = n;
+
+    size = rc->name_size;
+    p = rc->names;
+
+    for (i = 0; i < n; i++) {
+        rv[i].capture = 2 * ((p[0] << 8) + p[1]);
+
+        name.data = &p[2];
+        name.len = ngx_strlen(name.data);
+
+        v = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
+        if (v == NULL) {
+            return NULL;
+        }
+
+        rv[i].index = ngx_http_get_variable_index(cf, &name);
+        if (rv[i].index == NGX_ERROR) {
+            return NULL;
+        }
+
+        v->get_handler = ngx_http_variable_not_found;
+
+        p += size;
+    }
+
+    return re;
+}
+
+
+ngx_int_t
+ngx_http_regex_exec(ngx_http_request_t *r, ngx_http_regex_t *re, ngx_str_t *s)
+{
+    ngx_int_t                   rc, index;
+    ngx_uint_t                  i, n, len;
+    ngx_http_variable_value_t  *vv;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+    if (re->ncaptures) {
+        len = cmcf->ncaptures;
+
+        if (r->captures == NULL) {
+            r->captures = ngx_palloc(r->pool, len * sizeof(int));
+            if (r->captures == NULL) {
+                return NGX_ERROR;
+            }
+        }
+
+    } else {
+        len = 0;
+    }
+
+    rc = ngx_regex_exec(re->regex, s, r->captures, len);
+
+    if (rc == NGX_REGEX_NO_MATCHED) {
+        return NGX_DECLINED;
+    }
+
+    if (rc < 0) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                      ngx_regex_exec_n " failed: %i on \"%V\" using \"%V\"",
+                      rc, s, &re->name);
+        return NGX_ERROR;
+    }
+
+    for (i = 0; i < re->nvariables; i++) {
+
+        n = re->variables[i].capture;
+        index = re->variables[i].index;
+        vv = &r->variables[index];
+
+        vv->len = r->captures[n + 1] - r->captures[n];
+        vv->valid = 1;
+        vv->no_cacheable = 0;
+        vv->not_found = 0;
+        vv->data = &s->data[r->captures[n]];
+
+#if (NGX_DEBUG)
+        {
+        ngx_http_variable_t  *v;
+
+        v = cmcf->variables.elts;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http regex set $%V to \"%v\"", &v[index].name, vv);
+        }
+#endif
+    }
+
+    r->ncaptures = rc * 2;
+    r->captures_data = s->data;
+
+    return NGX_OK;
+}
+
+#endif
+
+
+ngx_int_t
+ngx_http_variables_add_core_vars(ngx_conf_t *cf)
+{
+    ngx_http_variable_t        *cv, *v;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    cmcf->variables_keys = ngx_pcalloc(cf->temp_pool,
+                                       sizeof(ngx_hash_keys_arrays_t));
+    if (cmcf->variables_keys == NULL) {
+        return NGX_ERROR;
+    }
+
+    cmcf->variables_keys->pool = cf->pool;
+    cmcf->variables_keys->temp_pool = cf->pool;
+
+    if (ngx_hash_keys_array_init(cmcf->variables_keys, NGX_HASH_SMALL)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&cmcf->prefix_variables, cf->pool, 8,
+                       sizeof(ngx_http_variable_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    for (cv = ngx_http_core_variables; cv->name.len; cv++) {
+        v = ngx_http_add_variable(cf, &cv->name, cv->flags);
+        if (v == NULL) {
+            return NGX_ERROR;
+        }
+
+        *v = *cv;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_variables_init_vars(ngx_conf_t *cf)
+{
+    size_t                      len;
+    ngx_uint_t                  i, n;
+    ngx_hash_key_t             *key;
+    ngx_hash_init_t             hash;
+    ngx_http_variable_t        *v, *av, *pv;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    /* set the handlers for the indexed http variables */
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    v = cmcf->variables.elts;
+    pv = cmcf->prefix_variables.elts;
+    key = cmcf->variables_keys->keys.elts;
+
+    for (i = 0; i < cmcf->variables.nelts; i++) {
+
+        for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) {
+
+            av = key[n].value;
+
+            if (v[i].name.len == key[n].key.len
+                && ngx_strncmp(v[i].name.data, key[n].key.data, v[i].name.len)
+                   == 0)
+            {
+                v[i].get_handler = av->get_handler;
+                v[i].data = av->data;
+
+                av->flags |= NGX_HTTP_VAR_INDEXED;
+                v[i].flags = av->flags;
+
+                av->index = i;
+
+                if (av->get_handler == NULL
+                    || (av->flags & NGX_HTTP_VAR_WEAK))
+                {
+                    break;
+                }
+
+                goto next;
+            }
+        }
+
+        len = 0;
+        av = NULL;
+
+        for (n = 0; n < cmcf->prefix_variables.nelts; n++) {
+            if (v[i].name.len >= pv[n].name.len && v[i].name.len > len
+                && ngx_strncmp(v[i].name.data, pv[n].name.data, pv[n].name.len)
+                   == 0)
+            {
+                av = &pv[n];
+                len = pv[n].name.len;
+            }
+        }
+
+        if (av) {
+            v[i].get_handler = av->get_handler;
+            v[i].data = (uintptr_t) &v[i].name;
+            v[i].flags = av->flags;
+
+            goto next;
+        }
+
+        if (v[i].get_handler == NULL) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "unknown \"%V\" variable", &v[i].name);
+
+            return NGX_ERROR;
+        }
+
+    next:
+        continue;
+    }
+
+
+    for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) {
+        av = key[n].value;
+
+        if (av->flags & NGX_HTTP_VAR_NOHASH) {
+            key[n].key.data = NULL;
+        }
+    }
+
+
+    hash.hash = &cmcf->variables_hash;
+    hash.key = ngx_hash_key;
+    hash.max_size = cmcf->variables_hash_max_size;
+    hash.bucket_size = cmcf->variables_hash_bucket_size;
+    hash.name = "variables_hash";
+    hash.pool = cf->pool;
+    hash.temp_pool = NULL;
+
+    if (ngx_hash_init(&hash, cmcf->variables_keys->keys.elts,
+                      cmcf->variables_keys->keys.nelts)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    cmcf->variables_keys = NULL;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/ngx_http_variables.h b/nginx/src/http/ngx_http_variables.h
new file mode 100644 (file)
index 0000000..f3f7f3c
--- /dev/null
@@ -0,0 +1,116 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_VARIABLES_H_INCLUDED_
+#define _NGX_HTTP_VARIABLES_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef ngx_variable_value_t  ngx_http_variable_value_t;
+
+#define ngx_http_variable(v)     { sizeof(v) - 1, 1, 0, 0, 0, (u_char *) v }
+
+typedef struct ngx_http_variable_s  ngx_http_variable_t;
+
+typedef void (*ngx_http_set_variable_pt) (ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+typedef ngx_int_t (*ngx_http_get_variable_pt) (ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+
+
+#define NGX_HTTP_VAR_CHANGEABLE   1
+#define NGX_HTTP_VAR_NOCACHEABLE  2
+#define NGX_HTTP_VAR_INDEXED      4
+#define NGX_HTTP_VAR_NOHASH       8
+#define NGX_HTTP_VAR_WEAK         16
+#define NGX_HTTP_VAR_PREFIX       32
+
+
+struct ngx_http_variable_s {
+    ngx_str_t                     name;   /* must be first to build the hash */
+    ngx_http_set_variable_pt      set_handler;
+    ngx_http_get_variable_pt      get_handler;
+    uintptr_t                     data;
+    ngx_uint_t                    flags;
+    ngx_uint_t                    index;
+};
+
+#define ngx_http_null_variable  { ngx_null_string, NULL, NULL, 0, 0, 0 }
+
+
+ngx_http_variable_t *ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name,
+    ngx_uint_t flags);
+ngx_int_t ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name);
+ngx_http_variable_value_t *ngx_http_get_indexed_variable(ngx_http_request_t *r,
+    ngx_uint_t index);
+ngx_http_variable_value_t *ngx_http_get_flushed_variable(ngx_http_request_t *r,
+    ngx_uint_t index);
+
+ngx_http_variable_value_t *ngx_http_get_variable(ngx_http_request_t *r,
+    ngx_str_t *name, ngx_uint_t key);
+
+ngx_int_t ngx_http_variable_unknown_header(ngx_http_variable_value_t *v,
+    ngx_str_t *var, ngx_list_part_t *part, size_t prefix);
+
+
+#if (NGX_PCRE)
+
+typedef struct {
+    ngx_uint_t                    capture;
+    ngx_int_t                     index;
+} ngx_http_regex_variable_t;
+
+
+typedef struct {
+    ngx_regex_t                  *regex;
+    ngx_uint_t                    ncaptures;
+    ngx_http_regex_variable_t    *variables;
+    ngx_uint_t                    nvariables;
+    ngx_str_t                     name;
+} ngx_http_regex_t;
+
+
+typedef struct {
+    ngx_http_regex_t             *regex;
+    void                         *value;
+} ngx_http_map_regex_t;
+
+
+ngx_http_regex_t *ngx_http_regex_compile(ngx_conf_t *cf,
+    ngx_regex_compile_t *rc);
+ngx_int_t ngx_http_regex_exec(ngx_http_request_t *r, ngx_http_regex_t *re,
+    ngx_str_t *s);
+
+#endif
+
+
+typedef struct {
+    ngx_hash_combined_t           hash;
+#if (NGX_PCRE)
+    ngx_http_map_regex_t         *regex;
+    ngx_uint_t                    nregex;
+#endif
+} ngx_http_map_t;
+
+
+void *ngx_http_map_find(ngx_http_request_t *r, ngx_http_map_t *map,
+    ngx_str_t *match);
+
+
+ngx_int_t ngx_http_variables_add_core_vars(ngx_conf_t *cf);
+ngx_int_t ngx_http_variables_init_vars(ngx_conf_t *cf);
+
+
+extern ngx_http_variable_value_t  ngx_http_variable_null_value;
+extern ngx_http_variable_value_t  ngx_http_variable_true_value;
+
+
+#endif /* _NGX_HTTP_VARIABLES_H_INCLUDED_ */
diff --git a/nginx/src/http/ngx_http_write_filter_module.c b/nginx/src/http/ngx_http_write_filter_module.c
new file mode 100644 (file)
index 0000000..0036231
--- /dev/null
@@ -0,0 +1,327 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_write_filter_init(ngx_conf_t *cf);
+
+
+static ngx_http_module_t  ngx_http_write_filter_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_write_filter_init,            /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL,                                  /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_write_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_write_filter_module_ctx,     /* module context */
+    NULL,                                  /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+ngx_int_t
+ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    off_t                      size, sent, nsent, limit;
+    ngx_uint_t                 last, flush, sync;
+    ngx_msec_t                 delay;
+    ngx_chain_t               *cl, *ln, **ll, *chain;
+    ngx_connection_t          *c;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    c = r->connection;
+
+    if (c->error) {
+        return NGX_ERROR;
+    }
+
+    size = 0;
+    flush = 0;
+    sync = 0;
+    last = 0;
+    ll = &r->out;
+
+    /* find the size, the flush point and the last link of the saved chain */
+
+    for (cl = r->out; cl; cl = cl->next) {
+        ll = &cl->next;
+
+        ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "write old buf t:%d f:%d %p, pos %p, size: %z "
+                       "file: %O, size: %O",
+                       cl->buf->temporary, cl->buf->in_file,
+                       cl->buf->start, cl->buf->pos,
+                       cl->buf->last - cl->buf->pos,
+                       cl->buf->file_pos,
+                       cl->buf->file_last - cl->buf->file_pos);
+
+#if 1
+        if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) {
+            ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                          "zero size buf in writer "
+                          "t:%d r:%d f:%d %p %p-%p %p %O-%O",
+                          cl->buf->temporary,
+                          cl->buf->recycled,
+                          cl->buf->in_file,
+                          cl->buf->start,
+                          cl->buf->pos,
+                          cl->buf->last,
+                          cl->buf->file,
+                          cl->buf->file_pos,
+                          cl->buf->file_last);
+
+            ngx_debug_point();
+            return NGX_ERROR;
+        }
+#endif
+
+        size += ngx_buf_size(cl->buf);
+
+        if (cl->buf->flush || cl->buf->recycled) {
+            flush = 1;
+        }
+
+        if (cl->buf->sync) {
+            sync = 1;
+        }
+
+        if (cl->buf->last_buf) {
+            last = 1;
+        }
+    }
+
+    /* add the new chain to the existent one */
+
+    for (ln = in; ln; ln = ln->next) {
+        cl = ngx_alloc_chain_link(r->pool);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+        cl->buf = ln->buf;
+        *ll = cl;
+        ll = &cl->next;
+
+        ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "write new buf t:%d f:%d %p, pos %p, size: %z "
+                       "file: %O, size: %O",
+                       cl->buf->temporary, cl->buf->in_file,
+                       cl->buf->start, cl->buf->pos,
+                       cl->buf->last - cl->buf->pos,
+                       cl->buf->file_pos,
+                       cl->buf->file_last - cl->buf->file_pos);
+
+#if 1
+        if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) {
+            ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                          "zero size buf in writer "
+                          "t:%d r:%d f:%d %p %p-%p %p %O-%O",
+                          cl->buf->temporary,
+                          cl->buf->recycled,
+                          cl->buf->in_file,
+                          cl->buf->start,
+                          cl->buf->pos,
+                          cl->buf->last,
+                          cl->buf->file,
+                          cl->buf->file_pos,
+                          cl->buf->file_last);
+
+            ngx_debug_point();
+            return NGX_ERROR;
+        }
+#endif
+
+        size += ngx_buf_size(cl->buf);
+
+        if (cl->buf->flush || cl->buf->recycled) {
+            flush = 1;
+        }
+
+        if (cl->buf->sync) {
+            sync = 1;
+        }
+
+        if (cl->buf->last_buf) {
+            last = 1;
+        }
+    }
+
+    *ll = NULL;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http write filter: l:%ui f:%ui s:%O", last, flush, size);
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    /*
+     * avoid the output if there are no last buf, no flush point,
+     * there are the incoming bufs and the size of all bufs
+     * is smaller than "postpone_output" directive
+     */
+
+    if (!last && !flush && in && size < (off_t) clcf->postpone_output) {
+        return NGX_OK;
+    }
+
+    if (c->write->delayed) {
+        c->buffered |= NGX_HTTP_WRITE_BUFFERED;
+        return NGX_AGAIN;
+    }
+
+    if (size == 0
+        && !(c->buffered & NGX_LOWLEVEL_BUFFERED)
+        && !(last && c->need_last_buf))
+    {
+        if (last || flush || sync) {
+            for (cl = r->out; cl; /* void */) {
+                ln = cl;
+                cl = cl->next;
+                ngx_free_chain(r->pool, ln);
+            }
+
+            r->out = NULL;
+            c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
+
+            return NGX_OK;
+        }
+
+        ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                      "the http output chain is empty");
+
+        ngx_debug_point();
+
+        return NGX_ERROR;
+    }
+
+    if (r->limit_rate) {
+        if (r->limit_rate_after == 0) {
+            r->limit_rate_after = clcf->limit_rate_after;
+        }
+
+        limit = (off_t) r->limit_rate * (ngx_time() - r->start_sec + 1)
+                - (c->sent - r->limit_rate_after);
+
+        if (limit <= 0) {
+            c->write->delayed = 1;
+            delay = (ngx_msec_t) (- limit * 1000 / r->limit_rate + 1);
+            ngx_add_timer(c->write, delay);
+
+            c->buffered |= NGX_HTTP_WRITE_BUFFERED;
+
+            return NGX_AGAIN;
+        }
+
+        if (clcf->sendfile_max_chunk
+            && (off_t) clcf->sendfile_max_chunk < limit)
+        {
+            limit = clcf->sendfile_max_chunk;
+        }
+
+    } else {
+        limit = clcf->sendfile_max_chunk;
+    }
+
+    sent = c->sent;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http write filter limit %O", limit);
+
+    chain = c->send_chain(c, r->out, limit);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http write filter %p", chain);
+
+    if (chain == NGX_CHAIN_ERROR) {
+        c->error = 1;
+        return NGX_ERROR;
+    }
+
+    if (r->limit_rate) {
+
+        nsent = c->sent;
+
+        if (r->limit_rate_after) {
+
+            sent -= r->limit_rate_after;
+            if (sent < 0) {
+                sent = 0;
+            }
+
+            nsent -= r->limit_rate_after;
+            if (nsent < 0) {
+                nsent = 0;
+            }
+        }
+
+        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);
+    }
+
+    for (cl = r->out; cl && cl != chain; /* void */) {
+        ln = cl;
+        cl = cl->next;
+        ngx_free_chain(r->pool, ln);
+    }
+
+    r->out = chain;
+
+    if (chain) {
+        c->buffered |= NGX_HTTP_WRITE_BUFFERED;
+        return NGX_AGAIN;
+    }
+
+    c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
+
+    if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) {
+        return NGX_AGAIN;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_write_filter_init(ngx_conf_t *cf)
+{
+    ngx_http_top_body_filter = ngx_http_write_filter;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/v2/ngx_http_v2.c b/nginx/src/http/v2/ngx_http_v2.c
new file mode 100644 (file)
index 0000000..12214e1
--- /dev/null
@@ -0,0 +1,4776 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Valentin V. Bartenev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_http_v2_module.h>
+
+
+typedef struct {
+    ngx_str_t           name;
+    ngx_uint_t          offset;
+    ngx_uint_t          hash;
+    ngx_http_header_t  *hh;
+} ngx_http_v2_parse_header_t;
+
+
+/* errors */
+#define NGX_HTTP_V2_NO_ERROR                     0x0
+#define NGX_HTTP_V2_PROTOCOL_ERROR               0x1
+#define NGX_HTTP_V2_INTERNAL_ERROR               0x2
+#define NGX_HTTP_V2_FLOW_CTRL_ERROR              0x3
+#define NGX_HTTP_V2_SETTINGS_TIMEOUT             0x4
+#define NGX_HTTP_V2_STREAM_CLOSED                0x5
+#define NGX_HTTP_V2_SIZE_ERROR                   0x6
+#define NGX_HTTP_V2_REFUSED_STREAM               0x7
+#define NGX_HTTP_V2_CANCEL                       0x8
+#define NGX_HTTP_V2_COMP_ERROR                   0x9
+#define NGX_HTTP_V2_CONNECT_ERROR                0xa
+#define NGX_HTTP_V2_ENHANCE_YOUR_CALM            0xb
+#define NGX_HTTP_V2_INADEQUATE_SECURITY          0xc
+#define NGX_HTTP_V2_HTTP_1_1_REQUIRED            0xd
+
+/* frame sizes */
+#define NGX_HTTP_V2_SETTINGS_ACK_SIZE            0
+#define NGX_HTTP_V2_RST_STREAM_SIZE              4
+#define NGX_HTTP_V2_PRIORITY_SIZE                5
+#define NGX_HTTP_V2_PING_SIZE                    8
+#define NGX_HTTP_V2_GOAWAY_SIZE                  8
+#define NGX_HTTP_V2_WINDOW_UPDATE_SIZE           4
+
+#define NGX_HTTP_V2_SETTINGS_PARAM_SIZE          6
+
+/* settings fields */
+#define NGX_HTTP_V2_HEADER_TABLE_SIZE_SETTING    0x1
+#define NGX_HTTP_V2_ENABLE_PUSH_SETTING          0x2
+#define NGX_HTTP_V2_MAX_STREAMS_SETTING          0x3
+#define NGX_HTTP_V2_INIT_WINDOW_SIZE_SETTING     0x4
+#define NGX_HTTP_V2_MAX_FRAME_SIZE_SETTING       0x5
+
+#define NGX_HTTP_V2_FRAME_BUFFER_SIZE            24
+
+#define NGX_HTTP_V2_ROOT                         (void *) -1
+
+
+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 u_char *ngx_http_v2_state_proxy_protocol(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_preface(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_preface_end(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_head(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_data(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_read_data(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_headers(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_header_block(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_field_len(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_field_huff(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_field_raw(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_field_skip(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_header_complete(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_handle_continuation(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end, ngx_http_v2_handler_pt handler);
+static u_char *ngx_http_v2_state_priority(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_rst_stream(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_settings(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_settings_params(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_push_promise(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_ping(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_goaway(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_window_update(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_continuation(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_complete(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_skip_padded(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_skip(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_save(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end, ngx_http_v2_handler_pt handler);
+static u_char *ngx_http_v2_state_headers_save(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end, ngx_http_v2_handler_pt handler);
+static u_char *ngx_http_v2_connection_error(ngx_http_v2_connection_t *h2c,
+    ngx_uint_t err);
+
+static ngx_int_t ngx_http_v2_parse_int(ngx_http_v2_connection_t *h2c,
+    u_char **pos, u_char *end, ngx_uint_t prefix);
+
+static ngx_http_v2_stream_t *ngx_http_v2_create_stream(
+    ngx_http_v2_connection_t *h2c, ngx_uint_t push);
+static ngx_http_v2_node_t *ngx_http_v2_get_node_by_id(
+    ngx_http_v2_connection_t *h2c, ngx_uint_t sid, ngx_uint_t alloc);
+static ngx_http_v2_node_t *ngx_http_v2_get_closed_node(
+    ngx_http_v2_connection_t *h2c);
+#define ngx_http_v2_index_size(h2scf)  (h2scf->streams_index_mask + 1)
+#define ngx_http_v2_index(h2scf, sid)  ((sid >> 1) & h2scf->streams_index_mask)
+
+static ngx_int_t ngx_http_v2_send_settings(ngx_http_v2_connection_t *h2c);
+static ngx_int_t ngx_http_v2_settings_frame_handler(
+    ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame);
+static ngx_int_t ngx_http_v2_send_window_update(ngx_http_v2_connection_t *h2c,
+    ngx_uint_t sid, size_t window);
+static ngx_int_t ngx_http_v2_send_rst_stream(ngx_http_v2_connection_t *h2c,
+    ngx_uint_t sid, ngx_uint_t status);
+static ngx_int_t ngx_http_v2_send_goaway(ngx_http_v2_connection_t *h2c,
+    ngx_uint_t status);
+
+static ngx_http_v2_out_frame_t *ngx_http_v2_get_frame(
+    ngx_http_v2_connection_t *h2c, size_t length, ngx_uint_t type,
+    u_char flags, ngx_uint_t sid);
+static ngx_int_t ngx_http_v2_frame_handler(ngx_http_v2_connection_t *h2c,
+    ngx_http_v2_out_frame_t *frame);
+
+static ngx_int_t ngx_http_v2_validate_header(ngx_http_request_t *r,
+    ngx_http_v2_header_t *header);
+static ngx_int_t ngx_http_v2_pseudo_header(ngx_http_request_t *r,
+    ngx_http_v2_header_t *header);
+static ngx_int_t ngx_http_v2_parse_path(ngx_http_request_t *r,
+    ngx_str_t *value);
+static ngx_int_t ngx_http_v2_parse_method(ngx_http_request_t *r,
+    ngx_str_t *value);
+static ngx_int_t ngx_http_v2_parse_scheme(ngx_http_request_t *r,
+    ngx_str_t *value);
+static ngx_int_t ngx_http_v2_parse_authority(ngx_http_request_t *r,
+    ngx_str_t *value);
+static ngx_int_t ngx_http_v2_parse_header(ngx_http_request_t *r,
+    ngx_http_v2_parse_header_t *header, ngx_str_t *value);
+static ngx_int_t ngx_http_v2_construct_request_line(ngx_http_request_t *r);
+static ngx_int_t ngx_http_v2_cookie(ngx_http_request_t *r,
+    ngx_http_v2_header_t *header);
+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);
+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);
+
+static ngx_int_t ngx_http_v2_terminate_stream(ngx_http_v2_connection_t *h2c,
+    ngx_http_v2_stream_t *stream, ngx_uint_t status);
+static void ngx_http_v2_close_stream_handler(ngx_event_t *ev);
+static void ngx_http_v2_handle_connection_handler(ngx_event_t *rev);
+static void ngx_http_v2_idle_handler(ngx_event_t *rev);
+static void ngx_http_v2_finalize_connection(ngx_http_v2_connection_t *h2c,
+    ngx_uint_t status);
+
+static ngx_int_t ngx_http_v2_adjust_windows(ngx_http_v2_connection_t *h2c,
+    ssize_t delta);
+static void ngx_http_v2_set_dependency(ngx_http_v2_connection_t *h2c,
+    ngx_http_v2_node_t *node, ngx_uint_t depend, ngx_uint_t exclusive);
+static void ngx_http_v2_node_children_update(ngx_http_v2_node_t *node);
+
+static void ngx_http_v2_pool_cleanup(void *data);
+
+
+static ngx_http_v2_handler_pt ngx_http_v2_frame_states[] = {
+    ngx_http_v2_state_data,               /* NGX_HTTP_V2_DATA_FRAME */
+    ngx_http_v2_state_headers,            /* NGX_HTTP_V2_HEADERS_FRAME */
+    ngx_http_v2_state_priority,           /* NGX_HTTP_V2_PRIORITY_FRAME */
+    ngx_http_v2_state_rst_stream,         /* NGX_HTTP_V2_RST_STREAM_FRAME */
+    ngx_http_v2_state_settings,           /* NGX_HTTP_V2_SETTINGS_FRAME */
+    ngx_http_v2_state_push_promise,       /* NGX_HTTP_V2_PUSH_PROMISE_FRAME */
+    ngx_http_v2_state_ping,               /* NGX_HTTP_V2_PING_FRAME */
+    ngx_http_v2_state_goaway,             /* NGX_HTTP_V2_GOAWAY_FRAME */
+    ngx_http_v2_state_window_update,      /* NGX_HTTP_V2_WINDOW_UPDATE_FRAME */
+    ngx_http_v2_state_continuation        /* NGX_HTTP_V2_CONTINUATION_FRAME */
+};
+
+#define NGX_HTTP_V2_FRAME_STATES                                              \
+    (sizeof(ngx_http_v2_frame_states) / sizeof(ngx_http_v2_handler_pt))
+
+
+static ngx_http_v2_parse_header_t  ngx_http_v2_parse_headers[] = {
+    { ngx_string("host"),
+      offsetof(ngx_http_headers_in_t, host), 0, NULL },
+
+    { ngx_string("accept-encoding"),
+      offsetof(ngx_http_headers_in_t, accept_encoding), 0, NULL },
+
+    { ngx_string("accept-language"),
+      offsetof(ngx_http_headers_in_t, accept_language), 0, NULL },
+
+    { ngx_string("user-agent"),
+      offsetof(ngx_http_headers_in_t, user_agent), 0, NULL },
+
+    { ngx_null_string, 0, 0, NULL }
+};
+
+
+void
+ngx_http_v2_init(ngx_event_t *rev)
+{
+    ngx_connection_t          *c;
+    ngx_pool_cleanup_t        *cln;
+    ngx_http_connection_t     *hc;
+    ngx_http_v2_srv_conf_t    *h2scf;
+    ngx_http_v2_main_conf_t   *h2mcf;
+    ngx_http_v2_connection_t  *h2c;
+
+    c = rev->data;
+    hc = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "init http2 connection");
+
+    c->log->action = "processing HTTP/2 connection";
+
+    h2mcf = ngx_http_get_module_main_conf(hc->conf_ctx, ngx_http_v2_module);
+
+    if (h2mcf->recv_buffer == NULL) {
+        h2mcf->recv_buffer = ngx_palloc(ngx_cycle->pool,
+                                        h2mcf->recv_buffer_size);
+        if (h2mcf->recv_buffer == NULL) {
+            ngx_http_close_connection(c);
+            return;
+        }
+    }
+
+    h2c = ngx_pcalloc(c->pool, sizeof(ngx_http_v2_connection_t));
+    if (h2c == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    h2c->connection = c;
+    h2c->http_connection = hc;
+
+    h2c->send_window = NGX_HTTP_V2_DEFAULT_WINDOW;
+    h2c->recv_window = NGX_HTTP_V2_MAX_WINDOW;
+
+    h2c->init_window = NGX_HTTP_V2_DEFAULT_WINDOW;
+
+    h2c->frame_size = NGX_HTTP_V2_DEFAULT_FRAME_SIZE;
+
+    h2c->table_update = 1;
+
+    h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module);
+
+    h2c->concurrent_pushes = h2scf->concurrent_pushes;
+
+    h2c->pool = ngx_create_pool(h2scf->pool_size, h2c->connection->log);
+    if (h2c->pool == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    cln = ngx_pool_cleanup_add(c->pool, 0);
+    if (cln == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    cln->handler = ngx_http_v2_pool_cleanup;
+    cln->data = h2c;
+
+    h2c->streams_index = ngx_pcalloc(c->pool, ngx_http_v2_index_size(h2scf)
+                                              * sizeof(ngx_http_v2_node_t *));
+    if (h2c->streams_index == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    if (ngx_http_v2_send_settings(h2c) == NGX_ERROR) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    if (ngx_http_v2_send_window_update(h2c, 0, NGX_HTTP_V2_MAX_WINDOW
+                                               - NGX_HTTP_V2_DEFAULT_WINDOW)
+        == NGX_ERROR)
+    {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    h2c->state.handler = hc->proxy_protocol ? ngx_http_v2_state_proxy_protocol
+                                            : ngx_http_v2_state_preface;
+
+    ngx_queue_init(&h2c->waiting);
+    ngx_queue_init(&h2c->dependencies);
+    ngx_queue_init(&h2c->closed);
+
+    c->data = h2c;
+
+    rev->handler = ngx_http_v2_read_handler;
+    c->write->handler = ngx_http_v2_write_handler;
+
+    c->idle = 1;
+
+    ngx_http_v2_read_handler(rev);
+}
+
+
+static void
+ngx_http_v2_read_handler(ngx_event_t *rev)
+{
+    u_char                    *p, *end;
+    size_t                     available;
+    ssize_t                    n;
+    ngx_connection_t          *c;
+    ngx_http_v2_main_conf_t   *h2mcf;
+    ngx_http_v2_connection_t  *h2c;
+
+    c = rev->data;
+    h2c = c->data;
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+        ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_PROTOCOL_ERROR);
+        return;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http2 read handler");
+
+    h2c->blocked = 1;
+
+    if (c->close) {
+        c->close = 0;
+
+        if (!h2c->goaway) {
+            h2c->goaway = 1;
+
+            if (ngx_http_v2_send_goaway(h2c, NGX_HTTP_V2_NO_ERROR)
+                == NGX_ERROR)
+            {
+                ngx_http_v2_finalize_connection(h2c, 0);
+                return;
+            }
+
+            if (ngx_http_v2_send_output_queue(h2c) == NGX_ERROR) {
+                ngx_http_v2_finalize_connection(h2c, 0);
+                return;
+            }
+        }
+
+        h2c->blocked = 0;
+
+        return;
+    }
+
+    h2mcf = ngx_http_get_module_main_conf(h2c->http_connection->conf_ctx,
+                                          ngx_http_v2_module);
+
+    available = h2mcf->recv_buffer_size - 2 * NGX_HTTP_V2_STATE_BUFFER_SIZE;
+
+    do {
+        p = h2mcf->recv_buffer;
+
+        ngx_memcpy(p, h2c->state.buffer, NGX_HTTP_V2_STATE_BUFFER_SIZE);
+        end = p + h2c->state.buffer_used;
+
+        n = c->recv(c, end, available);
+
+        if (n == NGX_AGAIN) {
+            break;
+        }
+
+        if (n == 0
+            && (h2c->state.incomplete || h2c->processing || h2c->pushing))
+        {
+            ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                          "client prematurely closed connection");
+        }
+
+        if (n == 0 || n == NGX_ERROR) {
+            c->error = 1;
+            ngx_http_v2_finalize_connection(h2c, 0);
+            return;
+        }
+
+        end += n;
+
+        h2c->state.buffer_used = 0;
+        h2c->state.incomplete = 0;
+
+        do {
+            p = h2c->state.handler(h2c, p, end);
+
+            if (p == NULL) {
+                return;
+            }
+
+        } while (p != end);
+
+    } while (rev->ready);
+
+    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+        ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_INTERNAL_ERROR);
+        return;
+    }
+
+    if (h2c->last_out && ngx_http_v2_send_output_queue(h2c) == NGX_ERROR) {
+        ngx_http_v2_finalize_connection(h2c, 0);
+        return;
+    }
+
+    h2c->blocked = 0;
+
+    if (h2c->processing || h2c->pushing) {
+        if (rev->timer_set) {
+            ngx_del_timer(rev);
+        }
+
+        return;
+    }
+
+    ngx_http_v2_handle_connection(h2c);
+}
+
+
+static void
+ngx_http_v2_write_handler(ngx_event_t *wev)
+{
+    ngx_int_t                  rc;
+    ngx_connection_t          *c;
+    ngx_http_v2_connection_t  *h2c;
+
+    c = wev->data;
+    h2c = c->data;
+
+    if (wev->timedout) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http2 write event timed out");
+        c->error = 1;
+        ngx_http_v2_finalize_connection(h2c, 0);
+        return;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http2 write handler");
+
+    if (h2c->last_out == NULL && !c->buffered) {
+
+        if (wev->timer_set) {
+            ngx_del_timer(wev);
+        }
+
+        ngx_http_v2_handle_connection(h2c);
+        return;
+    }
+
+    h2c->blocked = 1;
+
+    rc = ngx_http_v2_send_output_queue(h2c);
+
+    if (rc == NGX_ERROR) {
+        ngx_http_v2_finalize_connection(h2c, 0);
+        return;
+    }
+
+    h2c->blocked = 0;
+
+    if (rc == NGX_AGAIN) {
+        return;
+    }
+
+    ngx_http_v2_handle_connection(h2c);
+}
+
+
+ngx_int_t
+ngx_http_v2_send_output_queue(ngx_http_v2_connection_t *h2c)
+{
+    int                        tcp_nodelay;
+    ngx_chain_t               *cl;
+    ngx_event_t               *wev;
+    ngx_connection_t          *c;
+    ngx_http_v2_out_frame_t   *out, *frame, *fn;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    c = h2c->connection;
+
+    if (c->error) {
+        return NGX_ERROR;
+    }
+
+    wev = c->write;
+
+    if (!wev->ready) {
+        return NGX_AGAIN;
+    }
+
+    cl = NULL;
+    out = NULL;
+
+    for (frame = h2c->last_out; frame; frame = fn) {
+        frame->last->next = cl;
+        cl = frame->first;
+
+        fn = frame->next;
+        frame->next = out;
+        out = frame;
+
+        ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http2 frame out: %p sid:%ui bl:%d len:%uz",
+                       out, out->stream ? out->stream->node->id : 0,
+                       out->blocked, out->length);
+    }
+
+    cl = c->send_chain(c, cl, 0);
+
+    if (cl == NGX_CHAIN_ERROR) {
+        goto error;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(h2c->http_connection->conf_ctx,
+                                        ngx_http_core_module);
+
+    if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) {
+        goto error;
+    }
+
+    if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) {
+        if (ngx_tcp_push(c->fd) == -1) {
+            ngx_connection_error(c, ngx_socket_errno, ngx_tcp_push_n " failed");
+            goto error;
+        }
+
+        c->tcp_nopush = NGX_TCP_NOPUSH_UNSET;
+        tcp_nodelay = ngx_tcp_nodelay_and_tcp_nopush ? 1 : 0;
+
+    } else {
+        tcp_nodelay = 1;
+    }
+
+    if (tcp_nodelay && clcf->tcp_nodelay && ngx_tcp_nodelay(c) != NGX_OK) {
+        goto error;
+    }
+
+    for ( /* void */ ; out; out = fn) {
+        fn = out->next;
+
+        if (out->handler(h2c, out) != NGX_OK) {
+            out->blocked = 1;
+            break;
+        }
+
+        ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http2 frame sent: %p sid:%ui bl:%d len:%uz",
+                       out, out->stream ? out->stream->node->id : 0,
+                       out->blocked, out->length);
+    }
+
+    frame = NULL;
+
+    for ( /* void */ ; out; out = fn) {
+        fn = out->next;
+        out->next = frame;
+        frame = out;
+    }
+
+    h2c->last_out = frame;
+
+    if (!wev->ready) {
+        ngx_add_timer(wev, clcf->send_timeout);
+        return NGX_AGAIN;
+    }
+
+    if (wev->timer_set) {
+        ngx_del_timer(wev);
+    }
+
+    return NGX_OK;
+
+error:
+
+    c->error = 1;
+
+    if (!h2c->blocked) {
+        ngx_post_event(wev, &ngx_posted_events);
+    }
+
+    return NGX_ERROR;
+}
+
+
+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;
+
+    if (h2c->last_out || h2c->processing || h2c->pushing) {
+        return;
+    }
+
+    c = h2c->connection;
+
+    if (c->error) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    if (c->buffered) {
+        h2c->blocked = 1;
+
+        rc = ngx_http_v2_send_output_queue(h2c);
+
+        h2c->blocked = 0;
+
+        if (rc == NGX_ERROR) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        if (rc == NGX_AGAIN) {
+            return;
+        }
+
+        /* rc == NGX_OK */
+    }
+
+    if (h2c->goaway) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx,
+                                         ngx_http_v2_module);
+    if (h2c->state.incomplete) {
+        ngx_add_timer(c->read, h2scf->recv_timeout);
+        return;
+    }
+
+    ngx_destroy_pool(h2c->pool);
+
+    h2c->pool = NULL;
+    h2c->free_frames = NULL;
+    h2c->frames = 0;
+    h2c->free_fake_connections = NULL;
+
+#if (NGX_HTTP_SSL)
+    if (c->ssl) {
+        ngx_ssl_free_buffer(c);
+    }
+#endif
+
+    c->destroyed = 1;
+    ngx_reusable_connection(c, 1);
+
+    c->write->handler = ngx_http_empty_handler;
+    c->read->handler = ngx_http_v2_idle_handler;
+
+    if (c->write->timer_set) {
+        ngx_del_timer(c->write);
+    }
+
+    ngx_add_timer(c->read, h2scf->idle_timeout);
+}
+
+
+static u_char *
+ngx_http_v2_state_proxy_protocol(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end)
+{
+    ngx_log_t  *log;
+
+    log = h2c->connection->log;
+    log->action = "reading PROXY protocol";
+
+    pos = ngx_proxy_protocol_read(h2c->connection, pos, end);
+
+    log->action = "processing HTTP/2 connection";
+
+    if (pos == NULL) {
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR);
+    }
+
+    return ngx_http_v2_state_preface(h2c, pos, end);
+}
+
+
+static u_char *
+ngx_http_v2_state_preface(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end)
+{
+    static const u_char preface[] = "PRI * HTTP/2.0\r\n";
+
+    if ((size_t) (end - pos) < sizeof(preface) - 1) {
+        return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_preface);
+    }
+
+    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);
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR);
+    }
+
+    return ngx_http_v2_state_preface_end(h2c, pos + sizeof(preface) - 1, end);
+}
+
+
+static u_char *
+ngx_http_v2_state_preface_end(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end)
+{
+    static const u_char preface[] = "\r\nSM\r\n\r\n";
+
+    if ((size_t) (end - pos) < sizeof(preface) - 1) {
+        return ngx_http_v2_state_save(h2c, pos, end,
+                                      ngx_http_v2_state_preface_end);
+    }
+
+    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);
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR);
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 preface verified");
+
+    return ngx_http_v2_state_head(h2c, pos + sizeof(preface) - 1, end);
+}
+
+
+static u_char *
+ngx_http_v2_state_head(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end)
+{
+    uint32_t    head;
+    ngx_uint_t  type;
+
+    if (end - pos < NGX_HTTP_V2_FRAME_HEADER_SIZE) {
+        return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_head);
+    }
+
+    head = ngx_http_v2_parse_uint32(pos);
+
+    h2c->state.length = ngx_http_v2_parse_length(head);
+    h2c->state.flags = pos[4];
+
+    h2c->state.sid = ngx_http_v2_parse_sid(&pos[5]);
+
+    pos += NGX_HTTP_V2_FRAME_HEADER_SIZE;
+
+    type = ngx_http_v2_parse_type(head);
+
+    ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 frame type:%ui f:%Xd l:%uz sid:%ui",
+                   type, h2c->state.flags, h2c->state.length, h2c->state.sid);
+
+    if (type >= NGX_HTTP_V2_FRAME_STATES) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent frame with unknown type %ui", type);
+        return ngx_http_v2_state_skip(h2c, pos, end);
+    }
+
+    return ngx_http_v2_frame_states[type](h2c, pos, end);
+}
+
+
+static u_char *
+ngx_http_v2_state_data(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end)
+{
+    size_t                 size;
+    ngx_http_v2_node_t    *node;
+    ngx_http_v2_stream_t  *stream;
+
+    size = h2c->state.length;
+
+    if (h2c->state.flags & NGX_HTTP_V2_PADDED_FLAG) {
+
+        if (h2c->state.length == 0) {
+            ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                          "client sent padded DATA frame "
+                          "with incorrect length: 0");
+
+            return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
+        }
+
+        if (end - pos == 0) {
+            return ngx_http_v2_state_save(h2c, pos, end,
+                                          ngx_http_v2_state_data);
+        }
+
+        h2c->state.padding = *pos++;
+
+        if (h2c->state.padding >= size) {
+            ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                          "client sent padded DATA frame "
+                          "with incorrect length: %uz, padding: %uz",
+                          size, h2c->state.padding);
+
+            return ngx_http_v2_connection_error(h2c,
+                                                NGX_HTTP_V2_PROTOCOL_ERROR);
+        }
+
+        h2c->state.length -= 1 + h2c->state.padding;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 DATA frame");
+
+    if (size > h2c->recv_window) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client violated connection flow control: "
+                      "received DATA frame length %uz, available window %uz",
+                      size, h2c->recv_window);
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_FLOW_CTRL_ERROR);
+    }
+
+    h2c->recv_window -= size;
+
+    if (h2c->recv_window < NGX_HTTP_V2_MAX_WINDOW / 4) {
+
+        if (ngx_http_v2_send_window_update(h2c, 0, NGX_HTTP_V2_MAX_WINDOW
+                                                   - h2c->recv_window)
+            == NGX_ERROR)
+        {
+            return ngx_http_v2_connection_error(h2c,
+                                                NGX_HTTP_V2_INTERNAL_ERROR);
+        }
+
+        h2c->recv_window = NGX_HTTP_V2_MAX_WINDOW;
+    }
+
+    node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 0);
+
+    if (node == NULL || node->stream == NULL) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                       "unknown http2 stream");
+
+        return ngx_http_v2_state_skip_padded(h2c, pos, end);
+    }
+
+    stream = node->stream;
+
+    if (size > stream->recv_window) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client violated flow control for stream %ui: "
+                      "received DATA frame length %uz, available window %uz",
+                      node->id, size, stream->recv_window);
+
+        if (ngx_http_v2_terminate_stream(h2c, stream,
+                                         NGX_HTTP_V2_FLOW_CTRL_ERROR)
+            == NGX_ERROR)
+        {
+            return ngx_http_v2_connection_error(h2c,
+                                                NGX_HTTP_V2_INTERNAL_ERROR);
+        }
+
+        return ngx_http_v2_state_skip_padded(h2c, pos, end);
+    }
+
+    stream->recv_window -= size;
+
+    if (stream->no_flow_control
+        && stream->recv_window < NGX_HTTP_V2_MAX_WINDOW / 4)
+    {
+        if (ngx_http_v2_send_window_update(h2c, node->id,
+                                           NGX_HTTP_V2_MAX_WINDOW
+                                           - stream->recv_window)
+            == NGX_ERROR)
+        {
+            return ngx_http_v2_connection_error(h2c,
+                                                NGX_HTTP_V2_INTERNAL_ERROR);
+        }
+
+        stream->recv_window = NGX_HTTP_V2_MAX_WINDOW;
+    }
+
+    if (stream->in_closed) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent DATA frame for half-closed stream %ui",
+                      node->id);
+
+        if (ngx_http_v2_terminate_stream(h2c, stream,
+                                         NGX_HTTP_V2_STREAM_CLOSED)
+            == NGX_ERROR)
+        {
+            return ngx_http_v2_connection_error(h2c,
+                                                NGX_HTTP_V2_INTERNAL_ERROR);
+        }
+
+        return ngx_http_v2_state_skip_padded(h2c, pos, end);
+    }
+
+    h2c->state.stream = stream;
+
+    return ngx_http_v2_state_read_data(h2c, pos, end);
+}
+
+
+static u_char *
+ngx_http_v2_state_read_data(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end)
+{
+    size_t                   size;
+    ngx_buf_t               *buf;
+    ngx_int_t                rc;
+    ngx_http_request_t      *r;
+    ngx_http_v2_stream_t    *stream;
+    ngx_http_v2_srv_conf_t  *h2scf;
+
+    stream = h2c->state.stream;
+
+    if (stream == NULL) {
+        return ngx_http_v2_state_skip_padded(h2c, pos, end);
+    }
+
+    if (stream->skip_data) {
+        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) {
+        size = h2c->state.length;
+        stream->in_closed = h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG;
+    }
+
+    r = stream->request;
+
+    if (r->request_body) {
+        rc = ngx_http_v2_process_request_body(r, pos, size, stream->in_closed);
+
+        if (rc != NGX_OK) {
+            stream->skip_data = 1;
+            ngx_http_finalize_request(r, rc);
+        }
+
+    } else if (size) {
+        buf = stream->preread;
+
+        if (buf == NULL) {
+            h2scf = ngx_http_get_module_srv_conf(r, ngx_http_v2_module);
+
+            buf = ngx_create_temp_buf(r->pool, h2scf->preread_size);
+            if (buf == NULL) {
+                return ngx_http_v2_connection_error(h2c,
+                                                    NGX_HTTP_V2_INTERNAL_ERROR);
+            }
+
+            stream->preread = buf;
+        }
+
+        if (size > (size_t) (buf->end - buf->last)) {
+            ngx_log_error(NGX_LOG_ALERT, h2c->connection->log, 0,
+                          "http2 preread buffer overflow");
+            return ngx_http_v2_connection_error(h2c,
+                                                NGX_HTTP_V2_INTERNAL_ERROR);
+        }
+
+        buf->last = ngx_cpymem(buf->last, pos, size);
+    }
+
+    pos += size;
+    h2c->state.length -= size;
+
+    if (h2c->state.length) {
+        return ngx_http_v2_state_save(h2c, pos, end,
+                                      ngx_http_v2_state_read_data);
+    }
+
+    if (h2c->state.padding) {
+        return ngx_http_v2_state_skip_padded(h2c, pos, end);
+    }
+
+    return ngx_http_v2_state_complete(h2c, pos, end);
+}
+
+
+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;
+
+    padded = h2c->state.flags & NGX_HTTP_V2_PADDED_FLAG;
+    priority = h2c->state.flags & NGX_HTTP_V2_PRIORITY_FLAG;
+
+    size = 0;
+
+    if (padded) {
+        size++;
+    }
+
+    if (priority) {
+        size += sizeof(uint32_t) + 1;
+    }
+
+    if (h2c->state.length < size) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent HEADERS frame with incorrect length %uz",
+                      h2c->state.length);
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
+    }
+
+    if (h2c->state.length == size) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent HEADERS frame with empty header block");
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
+    }
+
+    if (h2c->goaway) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                       "skipping http2 HEADERS frame");
+        return ngx_http_v2_state_skip(h2c, pos, end);
+    }
+
+    if ((size_t) (end - pos) < size) {
+        return ngx_http_v2_state_save(h2c, pos, end,
+                                      ngx_http_v2_state_headers);
+    }
+
+    h2c->state.length -= size;
+
+    if (padded) {
+        h2c->state.padding = *pos++;
+
+        if (h2c->state.padding > h2c->state.length) {
+            ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                          "client sent padded HEADERS frame "
+                          "with incorrect length: %uz, padding: %uz",
+                          h2c->state.length, h2c->state.padding);
+
+            return ngx_http_v2_connection_error(h2c,
+                                                NGX_HTTP_V2_PROTOCOL_ERROR);
+        }
+
+        h2c->state.length -= h2c->state.padding;
+    }
+
+    depend = 0;
+    excl = 0;
+    weight = NGX_HTTP_V2_DEFAULT_WEIGHT;
+
+    if (priority) {
+        dependency = ngx_http_v2_parse_uint32(pos);
+
+        depend = dependency & 0x7fffffff;
+        excl = dependency >> 31;
+        weight = pos[4] + 1;
+
+        pos += sizeof(uint32_t) + 1;
+    }
+
+    ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 HEADERS frame sid:%ui "
+                   "depends on %ui excl:%ui weight:%ui",
+                   h2c->state.sid, depend, excl, weight);
+
+    if (h2c->state.sid % 2 == 0 || h2c->state.sid <= h2c->last_sid) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent HEADERS frame with incorrect identifier "
+                      "%ui, the last was %ui", h2c->state.sid, h2c->last_sid);
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR);
+    }
+
+    h2c->last_sid = h2c->state.sid;
+
+    h2c->state.pool = ngx_create_pool(1024, h2c->connection->log);
+    if (h2c->state.pool == NULL) {
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR);
+    }
+
+    if (depend == h2c->state.sid) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent HEADERS frame for stream %ui "
+                      "with incorrect dependency", h2c->state.sid);
+
+        status = NGX_HTTP_V2_PROTOCOL_ERROR;
+        goto rst_stream;
+    }
+
+    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);
+
+        status = NGX_HTTP_V2_REFUSED_STREAM;
+        goto rst_stream;
+    }
+
+    if (!h2c->settings_ack
+        && !(h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG)
+        && h2scf->preread_size < NGX_HTTP_V2_DEFAULT_WINDOW)
+    {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent stream with data "
+                      "before settings were acknowledged");
+
+        status = NGX_HTTP_V2_REFUSED_STREAM;
+        goto rst_stream;
+    }
+
+    node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 1);
+
+    if (node == NULL) {
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR);
+    }
+
+    if (node->parent) {
+        ngx_queue_remove(&node->reuse);
+        h2c->closed_nodes--;
+    }
+
+    stream = ngx_http_v2_create_stream(h2c, 0);
+    if (stream == NULL) {
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR);
+    }
+
+    h2c->state.stream = stream;
+
+    stream->pool = h2c->state.pool;
+    h2c->state.keep_pool = 1;
+
+    stream->request->request_length = h2c->state.length;
+
+    stream->in_closed = h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG;
+    stream->node = node;
+
+    node->stream = stream;
+
+    if (priority || node->parent == NULL) {
+        node->weight = weight;
+        ngx_http_v2_set_dependency(h2c, node, depend, excl);
+    }
+
+    if (h2c->connection->requests >= h2scf->max_requests) {
+        h2c->goaway = 1;
+
+        if (ngx_http_v2_send_goaway(h2c, NGX_HTTP_V2_NO_ERROR) == NGX_ERROR) {
+            return ngx_http_v2_connection_error(h2c,
+                                                NGX_HTTP_V2_INTERNAL_ERROR);
+        }
+    }
+
+    return ngx_http_v2_state_header_block(h2c, pos, end);
+
+rst_stream:
+
+    if (ngx_http_v2_send_rst_stream(h2c, h2c->state.sid, status) != NGX_OK) {
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR);
+    }
+
+    return ngx_http_v2_state_header_block(h2c, pos, end);
+}
+
+
+static u_char *
+ngx_http_v2_state_header_block(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end)
+{
+    u_char      ch;
+    ngx_int_t   value;
+    ngx_uint_t  indexed, size_update, prefix;
+
+    if (end - pos < 1) {
+        return ngx_http_v2_state_headers_save(h2c, pos, end,
+                                              ngx_http_v2_state_header_block);
+    }
+
+    if (!(h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG)
+        && h2c->state.length < NGX_HTTP_V2_INT_OCTETS)
+    {
+        return ngx_http_v2_handle_continuation(h2c, pos, end,
+                                               ngx_http_v2_state_header_block);
+    }
+
+    size_update = 0;
+    indexed = 0;
+
+    ch = *pos;
+
+    if (ch >= (1 << 7)) {
+        /* indexed header field */
+        indexed = 1;
+        prefix = ngx_http_v2_prefix(7);
+
+    } else if (ch >= (1 << 6)) {
+        /* literal header field with incremental indexing */
+        h2c->state.index = 1;
+        prefix = ngx_http_v2_prefix(6);
+
+    } else if (ch >= (1 << 5)) {
+        /* dynamic table size update */
+        size_update = 1;
+        prefix = ngx_http_v2_prefix(5);
+
+    } else if (ch >= (1 << 4)) {
+        /* literal header field never indexed */
+        prefix = ngx_http_v2_prefix(4);
+
+    } else {
+        /* literal header field without indexing */
+        prefix = ngx_http_v2_prefix(4);
+    }
+
+    value = ngx_http_v2_parse_int(h2c, &pos, end, prefix);
+
+    if (value < 0) {
+        if (value == NGX_AGAIN) {
+            return ngx_http_v2_state_headers_save(h2c, pos, end,
+                                               ngx_http_v2_state_header_block);
+        }
+
+        if (value == NGX_DECLINED) {
+            ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                          "client sent header block with too long %s value",
+                          size_update ? "size update" : "header index");
+
+            return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR);
+        }
+
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent header block with incorrect length");
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
+    }
+
+    if (indexed) {
+        if (ngx_http_v2_get_indexed_header(h2c, value, 0) != NGX_OK) {
+            return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR);
+        }
+
+        return ngx_http_v2_state_process_header(h2c, pos, end);
+    }
+
+    if (size_update) {
+        if (ngx_http_v2_table_size(h2c, value) != NGX_OK) {
+            return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR);
+        }
+
+        return ngx_http_v2_state_header_complete(h2c, pos, end);
+    }
+
+    if (value == 0) {
+        h2c->state.parse_name = 1;
+
+    } else if (ngx_http_v2_get_indexed_header(h2c, value, 1) != NGX_OK) {
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR);
+    }
+
+    h2c->state.parse_value = 1;
+
+    return ngx_http_v2_state_field_len(h2c, pos, end);
+}
+
+
+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;
+
+    if (!(h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG)
+        && h2c->state.length < NGX_HTTP_V2_INT_OCTETS)
+    {
+        return ngx_http_v2_handle_continuation(h2c, pos, end,
+                                               ngx_http_v2_state_field_len);
+    }
+
+    if (h2c->state.length < 1) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent header block with incorrect length");
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
+    }
+
+    if (end - pos < 1) {
+        return ngx_http_v2_state_headers_save(h2c, pos, end,
+                                              ngx_http_v2_state_field_len);
+    }
+
+    huff = *pos >> 7;
+    len = ngx_http_v2_parse_int(h2c, &pos, end, ngx_http_v2_prefix(7));
+
+    if (len < 0) {
+        if (len == NGX_AGAIN) {
+            return ngx_http_v2_state_headers_save(h2c, pos, end,
+                                                  ngx_http_v2_state_field_len);
+        }
+
+        if (len == NGX_DECLINED) {
+            ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                        "client sent header field with too long length value");
+
+            return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR);
+        }
+
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent header block with incorrect length");
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "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);
+
+    if ((size_t) len > h2scf->max_field_size) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client exceeded http2_max_field_size limit");
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_ENHANCE_YOUR_CALM);
+    }
+
+    h2c->state.field_rest = len;
+
+    if (h2c->state.stream == NULL && !h2c->state.index) {
+        return ngx_http_v2_state_field_skip(h2c, pos, end);
+    }
+
+    alloc = (huff ? len * 8 / 5 : len) + 1;
+
+    h2c->state.field_start = ngx_pnalloc(h2c->state.pool, alloc);
+    if (h2c->state.field_start == NULL) {
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR);
+    }
+
+    h2c->state.field_end = h2c->state.field_start;
+
+    if (huff) {
+        return ngx_http_v2_state_field_huff(h2c, pos, end);
+    }
+
+    return ngx_http_v2_state_field_raw(h2c, pos, end);
+}
+
+
+static u_char *
+ngx_http_v2_state_field_huff(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end)
+{
+    size_t  size;
+
+    size = end - pos;
+
+    if (size > h2c->state.field_rest) {
+        size = h2c->state.field_rest;
+    }
+
+    if (size > h2c->state.length) {
+        size = h2c->state.length;
+    }
+
+    h2c->state.length -= size;
+    h2c->state.field_rest -= size;
+
+    if (ngx_http_v2_huff_decode(&h2c->state.field_state, pos, size,
+                                &h2c->state.field_end,
+                                h2c->state.field_rest == 0,
+                                h2c->connection->log)
+        != NGX_OK)
+    {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent invalid encoded header field");
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR);
+    }
+
+    pos += size;
+
+    if (h2c->state.field_rest == 0) {
+        *h2c->state.field_end = '\0';
+        return ngx_http_v2_state_process_header(h2c, pos, end);
+    }
+
+    if (h2c->state.length) {
+        return ngx_http_v2_state_headers_save(h2c, pos, end,
+                                              ngx_http_v2_state_field_huff);
+    }
+
+    if (h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent header field with incorrect length");
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
+    }
+
+    return ngx_http_v2_handle_continuation(h2c, pos, end,
+                                           ngx_http_v2_state_field_huff);
+}
+
+
+static u_char *
+ngx_http_v2_state_field_raw(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end)
+{
+    size_t  size;
+
+    size = end - pos;
+
+    if (size > h2c->state.field_rest) {
+        size = h2c->state.field_rest;
+    }
+
+    if (size > h2c->state.length) {
+        size = h2c->state.length;
+    }
+
+    h2c->state.length -= size;
+    h2c->state.field_rest -= size;
+
+    h2c->state.field_end = ngx_cpymem(h2c->state.field_end, pos, size);
+
+    pos += size;
+
+    if (h2c->state.field_rest == 0) {
+        *h2c->state.field_end = '\0';
+        return ngx_http_v2_state_process_header(h2c, pos, end);
+    }
+
+    if (h2c->state.length) {
+        return ngx_http_v2_state_headers_save(h2c, pos, end,
+                                              ngx_http_v2_state_field_raw);
+    }
+
+    if (h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent header field with incorrect length");
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
+    }
+
+    return ngx_http_v2_handle_continuation(h2c, pos, end,
+                                           ngx_http_v2_state_field_raw);
+}
+
+
+static u_char *
+ngx_http_v2_state_field_skip(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end)
+{
+    size_t  size;
+
+    size = end - pos;
+
+    if (size > h2c->state.field_rest) {
+        size = h2c->state.field_rest;
+    }
+
+    if (size > h2c->state.length) {
+        size = h2c->state.length;
+    }
+
+    h2c->state.length -= size;
+    h2c->state.field_rest -= size;
+
+    pos += size;
+
+    if (h2c->state.field_rest == 0) {
+        return ngx_http_v2_state_process_header(h2c, pos, end);
+    }
+
+    if (h2c->state.length) {
+        return ngx_http_v2_state_save(h2c, pos, end,
+                                      ngx_http_v2_state_field_skip);
+    }
+
+    if (h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent header field with incorrect length");
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
+    }
+
+    return ngx_http_v2_handle_continuation(h2c, pos, end,
+                                           ngx_http_v2_state_field_skip);
+}
+
+
+static u_char *
+ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end)
+{
+    size_t                      len;
+    ngx_int_t                   rc;
+    ngx_table_elt_t            *h;
+    ngx_http_header_t          *hh;
+    ngx_http_request_t         *r;
+    ngx_http_v2_header_t       *header;
+    ngx_http_core_srv_conf_t   *cscf;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    static ngx_str_t cookie = ngx_string("cookie");
+
+    header = &h2c->state.header;
+
+    if (h2c->state.parse_name) {
+        h2c->state.parse_name = 0;
+
+        header->name.len = h2c->state.field_end - h2c->state.field_start;
+        header->name.data = h2c->state.field_start;
+
+        return ngx_http_v2_state_field_len(h2c, pos, end);
+    }
+
+    if (h2c->state.parse_value) {
+        h2c->state.parse_value = 0;
+
+        header->value.len = h2c->state.field_end - h2c->state.field_start;
+        header->value.data = h2c->state.field_start;
+    }
+
+    len = header->name.len + header->value.len;
+
+    if (len > h2c->state.header_limit) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client exceeded http2_max_header_size limit");
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_ENHANCE_YOUR_CALM);
+    }
+
+    h2c->state.header_limit -= len;
+
+    if (h2c->state.index) {
+        if (ngx_http_v2_add_header(h2c, header) != NGX_OK) {
+            return ngx_http_v2_connection_error(h2c,
+                                                NGX_HTTP_V2_INTERNAL_ERROR);
+        }
+
+        h2c->state.index = 0;
+    }
+
+    if (h2c->state.stream == NULL) {
+        return ngx_http_v2_state_header_complete(h2c, pos, end);
+    }
+
+    r = h2c->state.stream->request;
+
+    /* TODO Optimization: validate headers while parsing. */
+    if (ngx_http_v2_validate_header(r, header) != NGX_OK) {
+        if (ngx_http_v2_terminate_stream(h2c, h2c->state.stream,
+                                         NGX_HTTP_V2_PROTOCOL_ERROR)
+            == NGX_ERROR)
+        {
+            return ngx_http_v2_connection_error(h2c,
+                                                NGX_HTTP_V2_INTERNAL_ERROR);
+        }
+
+        goto error;
+    }
+
+    if (header->name.data[0] == ':') {
+        rc = ngx_http_v2_pseudo_header(r, header);
+
+        if (rc == NGX_OK) {
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http2 header: \":%V: %V\"",
+                           &header->name, &header->value);
+
+            return ngx_http_v2_state_header_complete(h2c, pos, end);
+        }
+
+        if (rc == NGX_ABORT) {
+            goto error;
+        }
+
+        if (rc == NGX_DECLINED) {
+            ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+            goto error;
+        }
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR);
+    }
+
+    if (r->invalid_header) {
+        cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+        if (cscf->ignore_invalid_headers) {
+            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                          "client sent invalid header: \"%V\"", &header->name);
+
+            return ngx_http_v2_state_header_complete(h2c, pos, end);
+        }
+    }
+
+    if (header->name.len == cookie.len
+        && ngx_memcmp(header->name.data, cookie.data, cookie.len) == 0)
+    {
+        if (ngx_http_v2_cookie(r, header) != NGX_OK) {
+            return ngx_http_v2_connection_error(h2c,
+                                                NGX_HTTP_V2_INTERNAL_ERROR);
+        }
+
+    } else {
+        h = ngx_list_push(&r->headers_in.headers);
+        if (h == NULL) {
+            return ngx_http_v2_connection_error(h2c,
+                                                NGX_HTTP_V2_INTERNAL_ERROR);
+        }
+
+        h->key.len = header->name.len;
+        h->key.data = header->name.data;
+
+        /*
+         * TODO Optimization: precalculate hash
+         * and handler for indexed headers.
+         */
+        h->hash = ngx_hash_key(h->key.data, h->key.len);
+
+        h->value.len = header->value.len;
+        h->value.data = header->value.data;
+
+        h->lowcase_key = h->key.data;
+
+        cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+        hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
+                           h->lowcase_key, h->key.len);
+
+        if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
+            goto error;
+        }
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http2 header: \"%V: %V\"",
+                   &header->name, &header->value);
+
+    return ngx_http_v2_state_header_complete(h2c, pos, end);
+
+error:
+
+    h2c->state.stream = NULL;
+
+    return ngx_http_v2_state_header_complete(h2c, pos, end);
+}
+
+
+static u_char *
+ngx_http_v2_state_header_complete(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end)
+{
+    ngx_http_v2_stream_t  *stream;
+
+    if (h2c->state.length) {
+        h2c->state.handler = ngx_http_v2_state_header_block;
+        return pos;
+    }
+
+    if (!(h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG)) {
+        return ngx_http_v2_handle_continuation(h2c, pos, end,
+                                             ngx_http_v2_state_header_complete);
+    }
+
+    stream = h2c->state.stream;
+
+    if (stream) {
+        ngx_http_v2_run_request(stream->request);
+    }
+
+    if (!h2c->state.keep_pool) {
+        ngx_destroy_pool(h2c->state.pool);
+    }
+
+    h2c->state.pool = NULL;
+    h2c->state.keep_pool = 0;
+
+    if (h2c->state.padding) {
+        return ngx_http_v2_state_skip_padded(h2c, pos, end);
+    }
+
+    return ngx_http_v2_state_complete(h2c, pos, end);
+}
+
+
+static u_char *
+ngx_http_v2_handle_continuation(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end, ngx_http_v2_handler_pt handler)
+{
+    u_char    *p;
+    size_t     len, skip;
+    uint32_t   head;
+
+    len = h2c->state.length;
+
+    if (h2c->state.padding && (size_t) (end - pos) > len) {
+        skip = ngx_min(h2c->state.padding, (end - pos) - len);
+
+        h2c->state.padding -= skip;
+
+        p = pos;
+        pos += skip;
+        ngx_memmove(pos, p, len);
+    }
+
+    if ((size_t) (end - pos) < len + NGX_HTTP_V2_FRAME_HEADER_SIZE) {
+        return ngx_http_v2_state_headers_save(h2c, pos, end, handler);
+    }
+
+    p = pos + len;
+
+    head = ngx_http_v2_parse_uint32(p);
+
+    if (ngx_http_v2_parse_type(head) != NGX_HTTP_V2_CONTINUATION_FRAME) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+             "client sent inappropriate frame while CONTINUATION was expected");
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR);
+    }
+
+    h2c->state.flags |= p[4];
+
+    if (h2c->state.sid != ngx_http_v2_parse_sid(&p[5])) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                    "client sent CONTINUATION frame with incorrect identifier");
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR);
+    }
+
+    p = pos;
+    pos += NGX_HTTP_V2_FRAME_HEADER_SIZE;
+
+    ngx_memcpy(pos, p, len);
+
+    len = ngx_http_v2_parse_length(head);
+
+    h2c->state.length += len;
+
+    if (h2c->state.stream) {
+        h2c->state.stream->request->request_length += len;
+    }
+
+    h2c->state.handler = handler;
+    return pos;
+}
+
+
+static u_char *
+ngx_http_v2_state_priority(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end)
+{
+    ngx_uint_t           depend, dependency, excl, weight;
+    ngx_http_v2_node_t  *node;
+
+    if (h2c->state.length != NGX_HTTP_V2_PRIORITY_SIZE) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent PRIORITY frame with incorrect length %uz",
+                      h2c->state.length);
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
+    }
+
+    if (end - pos < NGX_HTTP_V2_PRIORITY_SIZE) {
+        return ngx_http_v2_state_save(h2c, pos, end,
+                                      ngx_http_v2_state_priority);
+    }
+
+    dependency = ngx_http_v2_parse_uint32(pos);
+
+    depend = dependency & 0x7fffffff;
+    excl = dependency >> 31;
+    weight = pos[4] + 1;
+
+    pos += NGX_HTTP_V2_PRIORITY_SIZE;
+
+    ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 PRIORITY frame sid:%ui "
+                   "depends on %ui excl:%ui weight:%ui",
+                   h2c->state.sid, depend, excl, weight);
+
+    if (h2c->state.sid == 0) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent PRIORITY frame with incorrect identifier");
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR);
+    }
+
+    if (depend == h2c->state.sid) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent PRIORITY frame for stream %ui "
+                      "with incorrect dependency", h2c->state.sid);
+
+        node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 0);
+
+        if (node && node->stream) {
+            if (ngx_http_v2_terminate_stream(h2c, node->stream,
+                                             NGX_HTTP_V2_PROTOCOL_ERROR)
+                == NGX_ERROR)
+            {
+                return ngx_http_v2_connection_error(h2c,
+                                                    NGX_HTTP_V2_INTERNAL_ERROR);
+            }
+
+        } else {
+            if (ngx_http_v2_send_rst_stream(h2c, h2c->state.sid,
+                                            NGX_HTTP_V2_PROTOCOL_ERROR)
+                == NGX_ERROR)
+            {
+                return ngx_http_v2_connection_error(h2c,
+                                                    NGX_HTTP_V2_INTERNAL_ERROR);
+            }
+        }
+
+        return ngx_http_v2_state_complete(h2c, pos, end);
+    }
+
+    node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 1);
+
+    if (node == NULL) {
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR);
+    }
+
+    node->weight = weight;
+
+    if (node->stream == NULL) {
+        if (node->parent == NULL) {
+            h2c->closed_nodes++;
+
+        } else {
+            ngx_queue_remove(&node->reuse);
+        }
+
+        ngx_queue_insert_tail(&h2c->closed, &node->reuse);
+    }
+
+    ngx_http_v2_set_dependency(h2c, node, depend, excl);
+
+    return ngx_http_v2_state_complete(h2c, pos, end);
+}
+
+
+static u_char *
+ngx_http_v2_state_rst_stream(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end)
+{
+    ngx_uint_t             status;
+    ngx_event_t           *ev;
+    ngx_connection_t      *fc;
+    ngx_http_v2_node_t    *node;
+    ngx_http_v2_stream_t  *stream;
+
+    if (h2c->state.length != NGX_HTTP_V2_RST_STREAM_SIZE) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent RST_STREAM frame with incorrect length %uz",
+                      h2c->state.length);
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
+    }
+
+    if (end - pos < NGX_HTTP_V2_RST_STREAM_SIZE) {
+        return ngx_http_v2_state_save(h2c, pos, end,
+                                      ngx_http_v2_state_rst_stream);
+    }
+
+    status = ngx_http_v2_parse_uint32(pos);
+
+    pos += NGX_HTTP_V2_RST_STREAM_SIZE;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 RST_STREAM frame, sid:%ui status:%ui",
+                   h2c->state.sid, status);
+
+    if (h2c->state.sid == 0) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent RST_STREAM frame with incorrect identifier");
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR);
+    }
+
+    node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 0);
+
+    if (node == NULL || node->stream == NULL) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                       "unknown http2 stream");
+
+        return ngx_http_v2_state_complete(h2c, pos, end);
+    }
+
+    stream = node->stream;
+
+    stream->in_closed = 1;
+    stream->out_closed = 1;
+
+    fc = stream->request->connection;
+    fc->error = 1;
+
+    switch (status) {
+
+    case NGX_HTTP_V2_CANCEL:
+        ngx_log_error(NGX_LOG_INFO, fc->log, 0,
+                      "client canceled stream %ui", h2c->state.sid);
+        break;
+
+    case NGX_HTTP_V2_REFUSED_STREAM:
+        ngx_log_error(NGX_LOG_INFO, fc->log, 0,
+                      "client refused stream %ui", h2c->state.sid);
+        break;
+
+    case NGX_HTTP_V2_INTERNAL_ERROR:
+        ngx_log_error(NGX_LOG_INFO, fc->log, 0,
+                      "client terminated stream %ui due to internal error",
+                      h2c->state.sid);
+        break;
+
+    default:
+        ngx_log_error(NGX_LOG_INFO, fc->log, 0,
+                      "client terminated stream %ui with status %ui",
+                      h2c->state.sid, status);
+        break;
+    }
+
+    ev = fc->read;
+    ev->handler(ev);
+
+    return ngx_http_v2_state_complete(h2c, pos, end);
+}
+
+
+static u_char *
+ngx_http_v2_state_settings(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end)
+{
+    if (h2c->state.flags == NGX_HTTP_V2_ACK_FLAG) {
+
+        if (h2c->state.length != 0) {
+            ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                          "client sent SETTINGS frame with the ACK flag "
+                          "and nonzero length");
+
+            return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
+        }
+
+        h2c->settings_ack = 1;
+
+        return ngx_http_v2_state_complete(h2c, pos, end);
+    }
+
+    if (h2c->state.length % NGX_HTTP_V2_SETTINGS_PARAM_SIZE) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent SETTINGS frame with incorrect length %uz",
+                      h2c->state.length);
+
+        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);
+}
+
+
+static u_char *
+ngx_http_v2_state_settings_params(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end)
+{
+    ssize_t                   window_delta;
+    ngx_uint_t                id, value;
+    ngx_http_v2_srv_conf_t   *h2scf;
+    ngx_http_v2_out_frame_t  *frame;
+
+    window_delta = 0;
+
+    while (h2c->state.length) {
+        if (end - pos < NGX_HTTP_V2_SETTINGS_PARAM_SIZE) {
+            return ngx_http_v2_state_save(h2c, pos, end,
+                                          ngx_http_v2_state_settings_params);
+        }
+
+        h2c->state.length -= NGX_HTTP_V2_SETTINGS_PARAM_SIZE;
+
+        id = ngx_http_v2_parse_uint16(pos);
+        value = ngx_http_v2_parse_uint32(&pos[2]);
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                       "http2 setting %ui:%ui", id, value);
+
+        switch (id) {
+
+        case NGX_HTTP_V2_INIT_WINDOW_SIZE_SETTING:
+
+            if (value > NGX_HTTP_V2_MAX_WINDOW) {
+                ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                              "client sent SETTINGS frame with incorrect "
+                              "INITIAL_WINDOW_SIZE value %ui", value);
+
+                return ngx_http_v2_connection_error(h2c,
+                                                  NGX_HTTP_V2_FLOW_CTRL_ERROR);
+            }
+
+            window_delta = value - h2c->init_window;
+            break;
+
+        case NGX_HTTP_V2_MAX_FRAME_SIZE_SETTING:
+
+            if (value > NGX_HTTP_V2_MAX_FRAME_SIZE
+                || value < NGX_HTTP_V2_DEFAULT_FRAME_SIZE)
+            {
+                ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                              "client sent SETTINGS frame with incorrect "
+                              "MAX_FRAME_SIZE value %ui", value);
+
+                return ngx_http_v2_connection_error(h2c,
+                                                    NGX_HTTP_V2_PROTOCOL_ERROR);
+            }
+
+            h2c->frame_size = value;
+            break;
+
+        case NGX_HTTP_V2_ENABLE_PUSH_SETTING:
+
+            if (value > 1) {
+                ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                              "client sent SETTINGS frame with incorrect "
+                              "ENABLE_PUSH value %ui", value);
+
+                return ngx_http_v2_connection_error(h2c,
+                                                    NGX_HTTP_V2_PROTOCOL_ERROR);
+            }
+
+            h2c->push_disabled = !value;
+            break;
+
+        case NGX_HTTP_V2_MAX_STREAMS_SETTING:
+            h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx,
+                                                 ngx_http_v2_module);
+
+            h2c->concurrent_pushes = ngx_min(value, h2scf->concurrent_pushes);
+            break;
+
+        default:
+            break;
+        }
+
+        pos += NGX_HTTP_V2_SETTINGS_PARAM_SIZE;
+    }
+
+    frame = ngx_http_v2_get_frame(h2c, NGX_HTTP_V2_SETTINGS_ACK_SIZE,
+                                  NGX_HTTP_V2_SETTINGS_FRAME,
+                                  NGX_HTTP_V2_ACK_FLAG, 0);
+    if (frame == NULL) {
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR);
+    }
+
+    ngx_http_v2_queue_ordered_frame(h2c, frame);
+
+    if (window_delta) {
+        h2c->init_window += window_delta;
+
+        if (ngx_http_v2_adjust_windows(h2c, window_delta) != NGX_OK) {
+            return ngx_http_v2_connection_error(h2c,
+                                                NGX_HTTP_V2_INTERNAL_ERROR);
+        }
+    }
+
+    return ngx_http_v2_state_complete(h2c, pos, end);
+}
+
+
+static u_char *
+ngx_http_v2_state_push_promise(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end)
+{
+    ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                  "client sent PUSH_PROMISE frame");
+
+    return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR);
+}
+
+
+static u_char *
+ngx_http_v2_state_ping(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end)
+{
+    ngx_buf_t                *buf;
+    ngx_http_v2_out_frame_t  *frame;
+
+    if (h2c->state.length != NGX_HTTP_V2_PING_SIZE) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent PING frame with incorrect length %uz",
+                      h2c->state.length);
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
+    }
+
+    if (end - pos < NGX_HTTP_V2_PING_SIZE) {
+        return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_ping);
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 PING frame");
+
+    if (h2c->state.flags & NGX_HTTP_V2_ACK_FLAG) {
+        return ngx_http_v2_state_skip(h2c, pos, end);
+    }
+
+    frame = ngx_http_v2_get_frame(h2c, NGX_HTTP_V2_PING_SIZE,
+                                  NGX_HTTP_V2_PING_FRAME,
+                                  NGX_HTTP_V2_ACK_FLAG, 0);
+    if (frame == NULL) {
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR);
+    }
+
+    buf = frame->first->buf;
+
+    buf->last = ngx_cpymem(buf->last, pos, NGX_HTTP_V2_PING_SIZE);
+
+    ngx_http_v2_queue_blocked_frame(h2c, frame);
+
+    return ngx_http_v2_state_complete(h2c, pos + NGX_HTTP_V2_PING_SIZE, end);
+}
+
+
+static u_char *
+ngx_http_v2_state_goaway(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end)
+{
+#if (NGX_DEBUG)
+    ngx_uint_t  last_sid, error;
+#endif
+
+    if (h2c->state.length < NGX_HTTP_V2_GOAWAY_SIZE) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent GOAWAY frame "
+                      "with incorrect length %uz", h2c->state.length);
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
+    }
+
+    if (end - pos < NGX_HTTP_V2_GOAWAY_SIZE) {
+        return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_goaway);
+    }
+
+#if (NGX_DEBUG)
+    h2c->state.length -= NGX_HTTP_V2_GOAWAY_SIZE;
+
+    last_sid = ngx_http_v2_parse_sid(pos);
+    error = ngx_http_v2_parse_uint32(&pos[4]);
+
+    pos += NGX_HTTP_V2_GOAWAY_SIZE;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 GOAWAY frame: last sid %ui, error %ui",
+                   last_sid, error);
+#endif
+
+    return ngx_http_v2_state_skip(h2c, pos, end);
+}
+
+
+static u_char *
+ngx_http_v2_state_window_update(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end)
+{
+    size_t                 window;
+    ngx_event_t           *wev;
+    ngx_queue_t           *q;
+    ngx_http_v2_node_t    *node;
+    ngx_http_v2_stream_t  *stream;
+
+    if (h2c->state.length != NGX_HTTP_V2_WINDOW_UPDATE_SIZE) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent WINDOW_UPDATE frame "
+                      "with incorrect length %uz", h2c->state.length);
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
+    }
+
+    if (end - pos < NGX_HTTP_V2_WINDOW_UPDATE_SIZE) {
+        return ngx_http_v2_state_save(h2c, pos, end,
+                                      ngx_http_v2_state_window_update);
+    }
+
+    window = ngx_http_v2_parse_window(pos);
+
+    pos += NGX_HTTP_V2_WINDOW_UPDATE_SIZE;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 WINDOW_UPDATE frame sid:%ui window:%uz",
+                   h2c->state.sid, window);
+
+    if (window == 0) {
+        if (h2c->state.sid == 0) {
+            ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                          "client sent WINDOW_UPDATE frame "
+                          "with incorrect window increment 0");
+
+            return ngx_http_v2_connection_error(h2c,
+                                                NGX_HTTP_V2_PROTOCOL_ERROR);
+        }
+
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent WINDOW_UPDATE frame for stream %ui "
+                      "with incorrect window increment 0", h2c->state.sid);
+
+        node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 0);
+
+        if (node && node->stream) {
+            if (ngx_http_v2_terminate_stream(h2c, node->stream,
+                                             NGX_HTTP_V2_PROTOCOL_ERROR)
+                == NGX_ERROR)
+            {
+                return ngx_http_v2_connection_error(h2c,
+                                                    NGX_HTTP_V2_INTERNAL_ERROR);
+            }
+
+        } else {
+            if (ngx_http_v2_send_rst_stream(h2c, h2c->state.sid,
+                                            NGX_HTTP_V2_PROTOCOL_ERROR)
+                == NGX_ERROR)
+            {
+                return ngx_http_v2_connection_error(h2c,
+                                                    NGX_HTTP_V2_INTERNAL_ERROR);
+            }
+        }
+
+        return ngx_http_v2_state_complete(h2c, pos, end);
+    }
+
+    if (h2c->state.sid) {
+        node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 0);
+
+        if (node == NULL || node->stream == NULL) {
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                           "unknown http2 stream");
+
+            return ngx_http_v2_state_complete(h2c, pos, end);
+        }
+
+        stream = node->stream;
+
+        if (window > (size_t) (NGX_HTTP_V2_MAX_WINDOW - stream->send_window)) {
+
+            ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                          "client violated flow control for stream %ui: "
+                          "received WINDOW_UPDATE frame "
+                          "with window increment %uz "
+                          "not allowed for window %z",
+                          h2c->state.sid, window, stream->send_window);
+
+            if (ngx_http_v2_terminate_stream(h2c, stream,
+                                             NGX_HTTP_V2_FLOW_CTRL_ERROR)
+                == NGX_ERROR)
+            {
+                return ngx_http_v2_connection_error(h2c,
+                                                    NGX_HTTP_V2_INTERNAL_ERROR);
+            }
+
+            return ngx_http_v2_state_complete(h2c, pos, end);
+        }
+
+        stream->send_window += window;
+
+        if (stream->exhausted) {
+            stream->exhausted = 0;
+
+            wev = stream->request->connection->write;
+
+            wev->active = 0;
+            wev->ready = 1;
+
+            if (!wev->delayed) {
+                wev->handler(wev);
+            }
+        }
+
+        return ngx_http_v2_state_complete(h2c, pos, end);
+    }
+
+    if (window > NGX_HTTP_V2_MAX_WINDOW - h2c->send_window) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client violated connection flow control: "
+                      "received WINDOW_UPDATE frame "
+                      "with window increment %uz "
+                      "not allowed for window %uz",
+                      window, h2c->send_window);
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_FLOW_CTRL_ERROR);
+    }
+
+    h2c->send_window += window;
+
+    while (!ngx_queue_empty(&h2c->waiting)) {
+        q = ngx_queue_head(&h2c->waiting);
+
+        ngx_queue_remove(q);
+
+        stream = ngx_queue_data(q, ngx_http_v2_stream_t, queue);
+
+        stream->waiting = 0;
+
+        wev = stream->request->connection->write;
+
+        wev->active = 0;
+        wev->ready = 1;
+
+        if (!wev->delayed) {
+            wev->handler(wev);
+
+            if (h2c->send_window == 0) {
+                break;
+            }
+        }
+    }
+
+    return ngx_http_v2_state_complete(h2c, pos, end);
+}
+
+
+static u_char *
+ngx_http_v2_state_continuation(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end)
+{
+    ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                  "client sent unexpected CONTINUATION frame");
+
+    return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR);
+}
+
+
+static u_char *
+ngx_http_v2_state_complete(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end)
+{
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 frame complete pos:%p end:%p", pos, end);
+
+    if (pos > end) {
+        ngx_log_error(NGX_LOG_ALERT, h2c->connection->log, 0,
+                      "receive buffer overrun");
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR);
+    }
+
+    h2c->state.stream = NULL;
+    h2c->state.handler = ngx_http_v2_state_head;
+
+    return pos;
+}
+
+
+static u_char *
+ngx_http_v2_state_skip_padded(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end)
+{
+    h2c->state.length += h2c->state.padding;
+    h2c->state.padding = 0;
+
+    return ngx_http_v2_state_skip(h2c, pos, end);
+}
+
+
+static u_char *
+ngx_http_v2_state_skip(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end)
+{
+    size_t  size;
+
+    size = end - pos;
+
+    if (size < h2c->state.length) {
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                       "http2 frame skip %uz of %uz", size, h2c->state.length);
+
+        h2c->state.length -= size;
+        return ngx_http_v2_state_save(h2c, end, end, ngx_http_v2_state_skip);
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 frame skip %uz", h2c->state.length);
+
+    return ngx_http_v2_state_complete(h2c, pos + h2c->state.length, end);
+}
+
+
+static u_char *
+ngx_http_v2_state_save(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end,
+    ngx_http_v2_handler_pt handler)
+{
+    size_t  size;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 frame state save pos:%p end:%p handler:%p",
+                   pos, end, handler);
+
+    size = end - pos;
+
+    if (size > NGX_HTTP_V2_STATE_BUFFER_SIZE) {
+        ngx_log_error(NGX_LOG_ALERT, h2c->connection->log, 0,
+                      "state buffer overflow: %uz bytes required", size);
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR);
+    }
+
+    ngx_memcpy(h2c->state.buffer, pos, NGX_HTTP_V2_STATE_BUFFER_SIZE);
+
+    h2c->state.buffer_used = size;
+    h2c->state.handler = handler;
+    h2c->state.incomplete = 1;
+
+    return end;
+}
+
+
+static u_char *
+ngx_http_v2_state_headers_save(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end, ngx_http_v2_handler_pt handler)
+{
+    ngx_event_t               *rev;
+    ngx_http_request_t        *r;
+    ngx_http_core_srv_conf_t  *cscf;
+
+    if (h2c->state.stream) {
+        r = h2c->state.stream->request;
+        rev = r->connection->read;
+
+        if (!rev->timer_set) {
+            cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+            ngx_add_timer(rev, cscf->client_header_timeout);
+        }
+    }
+
+    return ngx_http_v2_state_save(h2c, pos, end, handler);
+}
+
+
+static u_char *
+ngx_http_v2_connection_error(ngx_http_v2_connection_t *h2c,
+    ngx_uint_t err)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 state connection error");
+
+    if (err == NGX_HTTP_V2_INTERNAL_ERROR) {
+        ngx_debug_point();
+    }
+
+    ngx_http_v2_finalize_connection(h2c, err);
+
+    return NULL;
+}
+
+
+static ngx_int_t
+ngx_http_v2_parse_int(ngx_http_v2_connection_t *h2c, u_char **pos, u_char *end,
+    ngx_uint_t prefix)
+{
+    u_char      *start, *p;
+    ngx_uint_t   value, octet, shift;
+
+    start = *pos;
+    p = start;
+
+    value = *p++ & prefix;
+
+    if (value != prefix) {
+        if (h2c->state.length == 0) {
+            return NGX_ERROR;
+        }
+
+        h2c->state.length--;
+
+        *pos = p;
+        return value;
+    }
+
+    if (end - start > NGX_HTTP_V2_INT_OCTETS) {
+        end = start + NGX_HTTP_V2_INT_OCTETS;
+    }
+
+    for (shift = 0; p != end; shift += 7) {
+        octet = *p++;
+
+        value += (octet & 0x7f) << shift;
+
+        if (octet < 128) {
+            if ((size_t) (p - start) > h2c->state.length) {
+                return NGX_ERROR;
+            }
+
+            h2c->state.length -= p - start;
+
+            *pos = p;
+            return value;
+        }
+    }
+
+    if ((size_t) (end - start) >= h2c->state.length) {
+        return NGX_ERROR;
+    }
+
+    if (end == start + NGX_HTTP_V2_INT_OCTETS) {
+        return NGX_DECLINED;
+    }
+
+    return NGX_AGAIN;
+}
+
+
+ngx_http_v2_stream_t *
+ngx_http_v2_push_stream(ngx_http_v2_stream_t *parent, ngx_str_t *path)
+{
+    ngx_int_t                     rc;
+    ngx_str_t                     value;
+    ngx_pool_t                   *pool;
+    ngx_uint_t                    index;
+    ngx_table_elt_t             **h;
+    ngx_connection_t             *fc;
+    ngx_http_request_t           *r;
+    ngx_http_v2_node_t           *node;
+    ngx_http_v2_stream_t         *stream;
+    ngx_http_v2_srv_conf_t       *h2scf;
+    ngx_http_v2_connection_t     *h2c;
+    ngx_http_v2_parse_header_t   *header;
+
+    h2c = parent->connection;
+
+    pool = ngx_create_pool(1024, h2c->connection->log);
+    if (pool == NULL) {
+        goto rst_stream;
+    }
+
+    node = ngx_http_v2_get_node_by_id(h2c, h2c->last_push, 1);
+
+    if (node == NULL) {
+        ngx_destroy_pool(pool);
+        goto rst_stream;
+    }
+
+    stream = ngx_http_v2_create_stream(h2c, 1);
+    if (stream == NULL) {
+
+        if (node->parent == NULL) {
+            h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx,
+                                                 ngx_http_v2_module);
+
+            index = ngx_http_v2_index(h2scf, h2c->last_push);
+            h2c->streams_index[index] = node->index;
+
+            ngx_queue_insert_tail(&h2c->closed, &node->reuse);
+            h2c->closed_nodes++;
+        }
+
+        ngx_destroy_pool(pool);
+        goto rst_stream;
+    }
+
+    if (node->parent) {
+        ngx_queue_remove(&node->reuse);
+        h2c->closed_nodes--;
+    }
+
+    stream->pool = pool;
+
+    r = stream->request;
+    fc = r->connection;
+
+    stream->in_closed = 1;
+    stream->node = node;
+
+    node->stream = stream;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 push stream sid:%ui "
+                   "depends on %ui excl:0 weight:16",
+                   h2c->last_push, parent->node->id);
+
+    node->weight = NGX_HTTP_V2_DEFAULT_WEIGHT;
+    ngx_http_v2_set_dependency(h2c, node, parent->node->id, 0);
+
+    r->method_name = ngx_http_core_get_method;
+    r->method = NGX_HTTP_GET;
+
+    r->schema_start = (u_char *) "https";
+
+#if (NGX_HTTP_SSL)
+    if (fc->ssl) {
+        r->schema_end = r->schema_start + 5;
+
+    } else
+#endif
+    {
+        r->schema_end = r->schema_start + 4;
+    }
+
+    value.data = ngx_pstrdup(pool, path);
+    if (value.data == NULL) {
+        goto close;
+    }
+
+    value.len = path->len;
+
+    rc = ngx_http_v2_parse_path(r, &value);
+
+    if (rc != NGX_OK) {
+        goto error;
+    }
+
+    for (header = ngx_http_v2_parse_headers; header->name.len; header++) {
+        h = (ngx_table_elt_t **)
+                ((char *) &parent->request->headers_in + header->offset);
+
+        if (*h == NULL) {
+            continue;
+        }
+
+        value.len = (*h)->value.len;
+
+        value.data = ngx_pnalloc(pool, value.len + 1);
+        if (value.data == NULL) {
+            goto close;
+        }
+
+        ngx_memcpy(value.data, (*h)->value.data, value.len);
+        value.data[value.len] = '\0';
+
+        rc = ngx_http_v2_parse_header(r, header, &value);
+
+        if (rc != NGX_OK) {
+            goto error;
+        }
+    }
+
+    fc->write->handler = ngx_http_v2_run_request_handler;
+    ngx_post_event(fc->write, &ngx_posted_events);
+
+    return stream;
+
+error:
+
+    if (rc == NGX_ABORT) {
+        /* header handler has already finalized request */
+        return NULL;
+    }
+
+    if (rc == NGX_DECLINED) {
+        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+        return NULL;
+    }
+
+close:
+
+    ngx_http_v2_close_stream(stream, NGX_HTTP_INTERNAL_SERVER_ERROR);
+
+    return NULL;
+
+rst_stream:
+
+    if (ngx_http_v2_send_rst_stream(h2c, h2c->last_push,
+                                    NGX_HTTP_INTERNAL_SERVER_ERROR)
+        != NGX_OK)
+    {
+        h2c->connection->error = 1;
+    }
+
+    return NULL;
+}
+
+
+static ngx_int_t
+ngx_http_v2_send_settings(ngx_http_v2_connection_t *h2c)
+{
+    size_t                    len;
+    ngx_buf_t                *buf;
+    ngx_chain_t              *cl;
+    ngx_http_v2_srv_conf_t   *h2scf;
+    ngx_http_v2_out_frame_t  *frame;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 send SETTINGS frame");
+
+    frame = ngx_palloc(h2c->pool, sizeof(ngx_http_v2_out_frame_t));
+    if (frame == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl = ngx_alloc_chain_link(h2c->pool);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    len = NGX_HTTP_V2_SETTINGS_PARAM_SIZE * 3;
+
+    buf = ngx_create_temp_buf(h2c->pool, NGX_HTTP_V2_FRAME_HEADER_SIZE + len);
+    if (buf == NULL) {
+        return NGX_ERROR;
+    }
+
+    buf->last_buf = 1;
+
+    cl->buf = buf;
+    cl->next = NULL;
+
+    frame->first = cl;
+    frame->last = cl;
+    frame->handler = ngx_http_v2_settings_frame_handler;
+    frame->stream = NULL;
+#if (NGX_DEBUG)
+    frame->length = len;
+#endif
+    frame->blocked = 0;
+
+    buf->last = ngx_http_v2_write_len_and_type(buf->last, len,
+                                               NGX_HTTP_V2_SETTINGS_FRAME);
+
+    *buf->last++ = NGX_HTTP_V2_NO_FLAG;
+
+    buf->last = ngx_http_v2_write_sid(buf->last, 0);
+
+    h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx,
+                                         ngx_http_v2_module);
+
+    buf->last = ngx_http_v2_write_uint16(buf->last,
+                                         NGX_HTTP_V2_MAX_STREAMS_SETTING);
+    buf->last = ngx_http_v2_write_uint32(buf->last,
+                                         h2scf->concurrent_streams);
+
+    buf->last = ngx_http_v2_write_uint16(buf->last,
+                                         NGX_HTTP_V2_INIT_WINDOW_SIZE_SETTING);
+    buf->last = ngx_http_v2_write_uint32(buf->last, h2scf->preread_size);
+
+    buf->last = ngx_http_v2_write_uint16(buf->last,
+                                         NGX_HTTP_V2_MAX_FRAME_SIZE_SETTING);
+    buf->last = ngx_http_v2_write_uint32(buf->last,
+                                         NGX_HTTP_V2_MAX_FRAME_SIZE);
+
+    ngx_http_v2_queue_blocked_frame(h2c, frame);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_settings_frame_handler(ngx_http_v2_connection_t *h2c,
+    ngx_http_v2_out_frame_t *frame)
+{
+    ngx_buf_t  *buf;
+
+    buf = frame->first->buf;
+
+    if (buf->pos != buf->last) {
+        return NGX_AGAIN;
+    }
+
+    ngx_free_chain(h2c->pool, frame->first);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_send_window_update(ngx_http_v2_connection_t *h2c, ngx_uint_t sid,
+    size_t window)
+{
+    ngx_buf_t                *buf;
+    ngx_http_v2_out_frame_t  *frame;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 send WINDOW_UPDATE frame sid:%ui, window:%uz",
+                   sid, window);
+
+    frame = ngx_http_v2_get_frame(h2c, NGX_HTTP_V2_WINDOW_UPDATE_SIZE,
+                                  NGX_HTTP_V2_WINDOW_UPDATE_FRAME,
+                                  NGX_HTTP_V2_NO_FLAG, sid);
+    if (frame == NULL) {
+        return NGX_ERROR;
+    }
+
+    buf = frame->first->buf;
+
+    buf->last = ngx_http_v2_write_uint32(buf->last, window);
+
+    ngx_http_v2_queue_blocked_frame(h2c, frame);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_send_rst_stream(ngx_http_v2_connection_t *h2c, ngx_uint_t sid,
+    ngx_uint_t status)
+{
+    ngx_buf_t                *buf;
+    ngx_http_v2_out_frame_t  *frame;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 send RST_STREAM frame sid:%ui, status:%ui",
+                   sid, status);
+
+    frame = ngx_http_v2_get_frame(h2c, NGX_HTTP_V2_RST_STREAM_SIZE,
+                                  NGX_HTTP_V2_RST_STREAM_FRAME,
+                                  NGX_HTTP_V2_NO_FLAG, sid);
+    if (frame == NULL) {
+        return NGX_ERROR;
+    }
+
+    buf = frame->first->buf;
+
+    buf->last = ngx_http_v2_write_uint32(buf->last, status);
+
+    ngx_http_v2_queue_blocked_frame(h2c, frame);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_send_goaway(ngx_http_v2_connection_t *h2c, ngx_uint_t status)
+{
+    ngx_buf_t                *buf;
+    ngx_http_v2_out_frame_t  *frame;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 send GOAWAY frame: last sid %ui, error %ui",
+                   h2c->last_sid, status);
+
+    frame = ngx_http_v2_get_frame(h2c, NGX_HTTP_V2_GOAWAY_SIZE,
+                                  NGX_HTTP_V2_GOAWAY_FRAME,
+                                  NGX_HTTP_V2_NO_FLAG, 0);
+    if (frame == NULL) {
+        return NGX_ERROR;
+    }
+
+    buf = frame->first->buf;
+
+    buf->last = ngx_http_v2_write_sid(buf->last, h2c->last_sid);
+    buf->last = ngx_http_v2_write_uint32(buf->last, status);
+
+    ngx_http_v2_queue_blocked_frame(h2c, frame);
+
+    return NGX_OK;
+}
+
+
+static ngx_http_v2_out_frame_t *
+ngx_http_v2_get_frame(ngx_http_v2_connection_t *h2c, size_t length,
+    ngx_uint_t type, u_char flags, ngx_uint_t sid)
+{
+    ngx_buf_t                *buf;
+    ngx_pool_t               *pool;
+    ngx_http_v2_out_frame_t  *frame;
+
+    frame = h2c->free_frames;
+
+    if (frame) {
+        h2c->free_frames = frame->next;
+
+        buf = frame->first->buf;
+        buf->pos = buf->start;
+
+        frame->blocked = 0;
+
+    } else if (h2c->frames < 10000) {
+        pool = h2c->pool ? h2c->pool : h2c->connection->pool;
+
+        frame = ngx_pcalloc(pool, sizeof(ngx_http_v2_out_frame_t));
+        if (frame == NULL) {
+            return NULL;
+        }
+
+        frame->first = ngx_alloc_chain_link(pool);
+        if (frame->first == NULL) {
+            return NULL;
+        }
+
+        buf = ngx_create_temp_buf(pool, NGX_HTTP_V2_FRAME_BUFFER_SIZE);
+        if (buf == NULL) {
+            return NULL;
+        }
+
+        buf->last_buf = 1;
+
+        frame->first->buf = buf;
+        frame->last = frame->first;
+
+        frame->handler = ngx_http_v2_frame_handler;
+
+        h2c->frames++;
+
+    } else {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "http2 flood detected");
+
+        h2c->connection->error = 1;
+        return NULL;
+    }
+
+#if (NGX_DEBUG)
+    if (length > NGX_HTTP_V2_FRAME_BUFFER_SIZE - NGX_HTTP_V2_FRAME_HEADER_SIZE)
+    {
+        ngx_log_error(NGX_LOG_ALERT, h2c->connection->log, 0,
+                      "requested control frame is too large: %uz", length);
+        return NULL;
+    }
+
+    frame->length = length;
+#endif
+
+    buf->last = ngx_http_v2_write_len_and_type(buf->pos, length, type);
+
+    *buf->last++ = flags;
+
+    buf->last = ngx_http_v2_write_sid(buf->last, sid);
+
+    return frame;
+}
+
+
+static ngx_int_t
+ngx_http_v2_frame_handler(ngx_http_v2_connection_t *h2c,
+    ngx_http_v2_out_frame_t *frame)
+{
+    ngx_buf_t  *buf;
+
+    buf = frame->first->buf;
+
+    if (buf->pos != buf->last) {
+        return NGX_AGAIN;
+    }
+
+    frame->next = h2c->free_frames;
+    h2c->free_frames = frame;
+
+    return NGX_OK;
+}
+
+
+static ngx_http_v2_stream_t *
+ngx_http_v2_create_stream(ngx_http_v2_connection_t *h2c, ngx_uint_t push)
+{
+    ngx_log_t                 *log;
+    ngx_event_t               *rev, *wev;
+    ngx_connection_t          *fc;
+    ngx_http_log_ctx_t        *ctx;
+    ngx_http_request_t        *r;
+    ngx_http_v2_stream_t      *stream;
+    ngx_http_v2_srv_conf_t    *h2scf;
+    ngx_http_core_srv_conf_t  *cscf;
+
+    fc = h2c->free_fake_connections;
+
+    if (fc) {
+        h2c->free_fake_connections = fc->data;
+
+        rev = fc->read;
+        wev = fc->write;
+        log = fc->log;
+        ctx = log->data;
+
+    } else {
+        fc = ngx_palloc(h2c->pool, sizeof(ngx_connection_t));
+        if (fc == NULL) {
+            return NULL;
+        }
+
+        rev = ngx_palloc(h2c->pool, sizeof(ngx_event_t));
+        if (rev == NULL) {
+            return NULL;
+        }
+
+        wev = ngx_palloc(h2c->pool, sizeof(ngx_event_t));
+        if (wev == NULL) {
+            return NULL;
+        }
+
+        log = ngx_palloc(h2c->pool, sizeof(ngx_log_t));
+        if (log == NULL) {
+            return NULL;
+        }
+
+        ctx = ngx_palloc(h2c->pool, sizeof(ngx_http_log_ctx_t));
+        if (ctx == NULL) {
+            return NULL;
+        }
+
+        ctx->connection = fc;
+        ctx->request = NULL;
+        ctx->current_request = NULL;
+    }
+
+    ngx_memcpy(log, h2c->connection->log, sizeof(ngx_log_t));
+
+    log->data = ctx;
+
+    if (push) {
+        log->action = "processing pushed request headers";
+
+    } else {
+        log->action = "reading client request headers";
+    }
+
+    ngx_memzero(rev, sizeof(ngx_event_t));
+
+    rev->data = fc;
+    rev->ready = 1;
+    rev->handler = ngx_http_v2_close_stream_handler;
+    rev->log = log;
+
+    ngx_memcpy(wev, rev, sizeof(ngx_event_t));
+
+    wev->write = 1;
+
+    ngx_memcpy(fc, h2c->connection, sizeof(ngx_connection_t));
+
+    fc->data = h2c->http_connection;
+    fc->read = rev;
+    fc->write = wev;
+    fc->sent = 0;
+    fc->log = log;
+    fc->buffered = 0;
+    fc->sndlowat = 1;
+    fc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED;
+
+    r = ngx_http_create_request(fc);
+    if (r == NULL) {
+        return NULL;
+    }
+
+    ngx_str_set(&r->http_protocol, "HTTP/2.0");
+
+    r->http_version = NGX_HTTP_VERSION_20;
+    r->valid_location = 1;
+
+    fc->data = r;
+    h2c->connection->requests++;
+
+    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+    r->header_in = ngx_create_temp_buf(r->pool,
+                                       cscf->client_header_buffer_size);
+    if (r->header_in == NULL) {
+        ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NULL;
+    }
+
+    if (ngx_list_init(&r->headers_in.headers, r->pool, 20,
+                      sizeof(ngx_table_elt_t))
+        != NGX_OK)
+    {
+        ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NULL;
+    }
+
+    r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE;
+
+    stream = ngx_pcalloc(r->pool, sizeof(ngx_http_v2_stream_t));
+    if (stream == NULL) {
+        ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NULL;
+    }
+
+    r->stream = stream;
+
+    stream->request = r;
+    stream->connection = h2c;
+
+    h2scf = ngx_http_get_module_srv_conf(r, ngx_http_v2_module);
+
+    stream->send_window = h2c->init_window;
+    stream->recv_window = h2scf->preread_size;
+
+    if (push) {
+        h2c->pushing++;
+
+    } else {
+        h2c->processing++;
+    }
+
+    return stream;
+}
+
+
+static ngx_http_v2_node_t *
+ngx_http_v2_get_node_by_id(ngx_http_v2_connection_t *h2c, ngx_uint_t sid,
+    ngx_uint_t alloc)
+{
+    ngx_uint_t               index;
+    ngx_http_v2_node_t      *node;
+    ngx_http_v2_srv_conf_t  *h2scf;
+
+    h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx,
+                                         ngx_http_v2_module);
+
+    index = ngx_http_v2_index(h2scf, sid);
+
+    for (node = h2c->streams_index[index]; node; node = node->index) {
+
+        if (node->id == sid) {
+            return node;
+        }
+    }
+
+    if (!alloc) {
+        return NULL;
+    }
+
+    if (h2c->closed_nodes < 32) {
+        node = ngx_pcalloc(h2c->connection->pool, sizeof(ngx_http_v2_node_t));
+        if (node == NULL) {
+            return NULL;
+        }
+
+    } else {
+        node = ngx_http_v2_get_closed_node(h2c);
+    }
+
+    node->id = sid;
+
+    ngx_queue_init(&node->children);
+
+    node->index = h2c->streams_index[index];
+    h2c->streams_index[index] = node;
+
+    return node;
+}
+
+
+static ngx_http_v2_node_t *
+ngx_http_v2_get_closed_node(ngx_http_v2_connection_t *h2c)
+{
+    ngx_uint_t               weight;
+    ngx_queue_t             *q, *children;
+    ngx_http_v2_node_t      *node, **next, *n, *parent, *child;
+    ngx_http_v2_srv_conf_t  *h2scf;
+
+    h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx,
+                                         ngx_http_v2_module);
+
+    h2c->closed_nodes--;
+
+    q = ngx_queue_head(&h2c->closed);
+
+    ngx_queue_remove(q);
+
+    node = ngx_queue_data(q, ngx_http_v2_node_t, reuse);
+
+    next = &h2c->streams_index[ngx_http_v2_index(h2scf, node->id)];
+
+    for ( ;; ) {
+        n = *next;
+
+        if (n == node) {
+            *next = n->index;
+            break;
+        }
+
+        next = &n->index;
+    }
+
+    ngx_queue_remove(&node->queue);
+
+    weight = 0;
+
+    for (q = ngx_queue_head(&node->children);
+         q != ngx_queue_sentinel(&node->children);
+         q = ngx_queue_next(q))
+    {
+        child = ngx_queue_data(q, ngx_http_v2_node_t, queue);
+        weight += child->weight;
+    }
+
+    parent = node->parent;
+
+    for (q = ngx_queue_head(&node->children);
+         q != ngx_queue_sentinel(&node->children);
+         q = ngx_queue_next(q))
+    {
+        child = ngx_queue_data(q, ngx_http_v2_node_t, queue);
+        child->parent = parent;
+        child->weight = node->weight * child->weight / weight;
+
+        if (child->weight == 0) {
+            child->weight = 1;
+        }
+    }
+
+    if (parent == NGX_HTTP_V2_ROOT) {
+        node->rank = 0;
+        node->rel_weight = 1.0;
+
+        children = &h2c->dependencies;
+
+    } else {
+        node->rank = parent->rank;
+        node->rel_weight = parent->rel_weight;
+
+        children = &parent->children;
+    }
+
+    ngx_http_v2_node_children_update(node);
+    ngx_queue_add(children, &node->children);
+
+    ngx_memzero(node, sizeof(ngx_http_v2_node_t));
+
+    return node;
+}
+
+
+static ngx_int_t
+ngx_http_v2_validate_header(ngx_http_request_t *r, ngx_http_v2_header_t *header)
+{
+    u_char                     ch;
+    ngx_uint_t                 i;
+    ngx_http_core_srv_conf_t  *cscf;
+
+    if (header->name.len == 0) {
+        return NGX_ERROR;
+    }
+
+    r->invalid_header = 0;
+
+    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+    for (i = (header->name.data[0] == ':'); i != header->name.len; i++) {
+        ch = header->name.data[i];
+
+        if ((ch >= 'a' && ch <= 'z')
+            || (ch == '-')
+            || (ch >= '0' && ch <= '9')
+            || (ch == '_' && cscf->underscores_in_headers))
+        {
+            continue;
+        }
+
+        if (ch == '\0' || ch == LF || ch == CR || ch == ':'
+            || (ch >= 'A' && ch <= 'Z'))
+        {
+            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                          "client sent invalid header name: \"%V\"",
+                          &header->name);
+
+            return NGX_ERROR;
+        }
+
+        r->invalid_header = 1;
+    }
+
+    for (i = 0; i != header->value.len; i++) {
+        ch = header->value.data[i];
+
+        if (ch == '\0' || ch == LF || ch == CR) {
+            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                          "client sent header \"%V\" with "
+                          "invalid value: \"%V\"",
+                          &header->name, &header->value);
+
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_pseudo_header(ngx_http_request_t *r, ngx_http_v2_header_t *header)
+{
+    header->name.len--;
+    header->name.data++;
+
+    switch (header->name.len) {
+    case 4:
+        if (ngx_memcmp(header->name.data, "path", sizeof("path") - 1)
+            == 0)
+        {
+            return ngx_http_v2_parse_path(r, &header->value);
+        }
+
+        break;
+
+    case 6:
+        if (ngx_memcmp(header->name.data, "method", sizeof("method") - 1)
+            == 0)
+        {
+            return ngx_http_v2_parse_method(r, &header->value);
+        }
+
+        if (ngx_memcmp(header->name.data, "scheme", sizeof("scheme") - 1)
+            == 0)
+        {
+            return ngx_http_v2_parse_scheme(r, &header->value);
+        }
+
+        break;
+
+    case 9:
+        if (ngx_memcmp(header->name.data, "authority", sizeof("authority") - 1)
+            == 0)
+        {
+            return ngx_http_v2_parse_authority(r, &header->value);
+        }
+
+        break;
+    }
+
+    ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                  "client sent unknown pseudo-header \":%V\"",
+                  &header->name);
+
+    return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_http_v2_parse_path(ngx_http_request_t *r, ngx_str_t *value)
+{
+    if (r->unparsed_uri.len) {
+        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                      "client sent duplicate :path header");
+
+        return NGX_DECLINED;
+    }
+
+    if (value->len == 0) {
+        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                      "client sent empty :path header");
+
+        return NGX_DECLINED;
+    }
+
+    r->uri_start = value->data;
+    r->uri_end = value->data + value->len;
+
+    if (ngx_http_parse_uri(r) != NGX_OK) {
+        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                      "client sent invalid :path header: \"%V\"", value);
+
+        return NGX_DECLINED;
+    }
+
+    if (ngx_http_process_request_uri(r) != NGX_OK) {
+        /*
+         * request has been finalized already
+         * in ngx_http_process_request_uri()
+         */
+        return NGX_ABORT;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_parse_method(ngx_http_request_t *r, ngx_str_t *value)
+{
+    size_t         k, len;
+    ngx_uint_t     n;
+    const u_char  *p, *m;
+
+    /*
+     * This array takes less than 256 sequential bytes,
+     * and if typical CPU cache line size is 64 bytes,
+     * it is prefetched for 4 load operations.
+     */
+    static const struct {
+        u_char            len;
+        const u_char      method[11];
+        uint32_t          value;
+    } tests[] = {
+        { 3, "GET",       NGX_HTTP_GET },
+        { 4, "POST",      NGX_HTTP_POST },
+        { 4, "HEAD",      NGX_HTTP_HEAD },
+        { 7, "OPTIONS",   NGX_HTTP_OPTIONS },
+        { 8, "PROPFIND",  NGX_HTTP_PROPFIND },
+        { 3, "PUT",       NGX_HTTP_PUT },
+        { 5, "MKCOL",     NGX_HTTP_MKCOL },
+        { 6, "DELETE",    NGX_HTTP_DELETE },
+        { 4, "COPY",      NGX_HTTP_COPY },
+        { 4, "MOVE",      NGX_HTTP_MOVE },
+        { 9, "PROPPATCH", NGX_HTTP_PROPPATCH },
+        { 4, "LOCK",      NGX_HTTP_LOCK },
+        { 6, "UNLOCK",    NGX_HTTP_UNLOCK },
+        { 5, "PATCH",     NGX_HTTP_PATCH },
+        { 5, "TRACE",     NGX_HTTP_TRACE }
+    }, *test;
+
+    if (r->method_name.len) {
+        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                      "client sent duplicate :method header");
+
+        return NGX_DECLINED;
+    }
+
+    if (value->len == 0) {
+        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                      "client sent empty :method header");
+
+        return NGX_DECLINED;
+    }
+
+    r->method_name.len = value->len;
+    r->method_name.data = value->data;
+
+    len = r->method_name.len;
+    n = sizeof(tests) / sizeof(tests[0]);
+    test = tests;
+
+    do {
+        if (len == test->len) {
+            p = r->method_name.data;
+            m = test->method;
+            k = len;
+
+            do {
+                if (*p++ != *m++) {
+                    goto next;
+                }
+            } while (--k);
+
+            r->method = test->value;
+            return NGX_OK;
+        }
+
+    next:
+        test++;
+
+    } while (--n);
+
+    p = r->method_name.data;
+
+    do {
+        if ((*p < 'A' || *p > 'Z') && *p != '_' && *p != '-') {
+            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                          "client sent invalid method: \"%V\"",
+                          &r->method_name);
+
+            return NGX_DECLINED;
+        }
+
+        p++;
+
+    } while (--len);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_parse_scheme(ngx_http_request_t *r, ngx_str_t *value)
+{
+    if (r->schema_start) {
+        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                      "client sent duplicate :scheme header");
+
+        return NGX_DECLINED;
+    }
+
+    if (value->len == 0) {
+        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                      "client sent empty :scheme header");
+
+        return NGX_DECLINED;
+    }
+
+    r->schema_start = value->data;
+    r->schema_end = value->data + value->len;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_parse_authority(ngx_http_request_t *r, ngx_str_t *value)
+{
+    return ngx_http_v2_parse_header(r, &ngx_http_v2_parse_headers[0], value);
+}
+
+
+static ngx_int_t
+ngx_http_v2_parse_header(ngx_http_request_t *r,
+    ngx_http_v2_parse_header_t *header, ngx_str_t *value)
+{
+    ngx_table_elt_t            *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    h = ngx_list_push(&r->headers_in.headers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    h->key.len = header->name.len;
+    h->key.data = header->name.data;
+    h->lowcase_key = header->name.data;
+
+    if (header->hh == NULL) {
+        header->hash = ngx_hash_key(header->name.data, header->name.len);
+
+        cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+        header->hh = ngx_hash_find(&cmcf->headers_in_hash, header->hash,
+                                   h->lowcase_key, h->key.len);
+        if (header->hh == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    h->hash = header->hash;
+
+    h->value.len = value->len;
+    h->value.data = value->data;
+
+    if (header->hh->handler(r, h, header->hh->offset) != NGX_OK) {
+        /* header handler has already finalized request */
+        return NGX_ABORT;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_construct_request_line(ngx_http_request_t *r)
+{
+    u_char  *p;
+
+    static const u_char ending[] = " HTTP/2.0";
+
+    if (r->method_name.len == 0
+        || r->schema_start == NULL
+        || r->unparsed_uri.len == 0)
+    {
+        if (r->method_name.len == 0) {
+            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                          "client sent no :method header");
+
+        } else if (r->schema_start == NULL) {
+            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                          "client sent no :scheme header");
+
+        } else {
+            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                          "client sent no :path header");
+        }
+
+        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+        return NGX_ERROR;
+    }
+
+    r->request_line.len = r->method_name.len + 1
+                          + r->unparsed_uri.len
+                          + sizeof(ending) - 1;
+
+    p = ngx_pnalloc(r->pool, r->request_line.len + 1);
+    if (p == NULL) {
+        ngx_http_v2_close_stream(r->stream, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NGX_ERROR;
+    }
+
+    r->request_line.data = p;
+
+    p = ngx_cpymem(p, r->method_name.data, r->method_name.len);
+
+    *p++ = ' ';
+
+    p = ngx_cpymem(p, r->unparsed_uri.data, r->unparsed_uri.len);
+
+    ngx_memcpy(p, ending, sizeof(ending));
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http2 request line: \"%V\"", &r->request_line);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_cookie(ngx_http_request_t *r, ngx_http_v2_header_t *header)
+{
+    ngx_str_t    *val;
+    ngx_array_t  *cookies;
+
+    cookies = r->stream->cookies;
+
+    if (cookies == NULL) {
+        cookies = ngx_array_create(r->pool, 2, sizeof(ngx_str_t));
+        if (cookies == NULL) {
+            return NGX_ERROR;
+        }
+
+        r->stream->cookies = cookies;
+    }
+
+    val = ngx_array_push(cookies);
+    if (val == NULL) {
+        return NGX_ERROR;
+    }
+
+    val->len = header->value.len;
+    val->data = header->value.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_construct_cookie_header(ngx_http_request_t *r)
+{
+    u_char                     *buf, *p, *end;
+    size_t                      len;
+    ngx_str_t                  *vals;
+    ngx_uint_t                  i;
+    ngx_array_t                *cookies;
+    ngx_table_elt_t            *h;
+    ngx_http_header_t          *hh;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    static ngx_str_t cookie = ngx_string("cookie");
+
+    cookies = r->stream->cookies;
+
+    if (cookies == NULL) {
+        return NGX_OK;
+    }
+
+    vals = cookies->elts;
+
+    i = 0;
+    len = 0;
+
+    do {
+        len += vals[i].len + 2;
+    } while (++i != cookies->nelts);
+
+    len -= 2;
+
+    buf = ngx_pnalloc(r->pool, len + 1);
+    if (buf == NULL) {
+        ngx_http_v2_close_stream(r->stream, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NGX_ERROR;
+    }
+
+    p = buf;
+    end = buf + len;
+
+    for (i = 0; /* void */ ; i++) {
+
+        p = ngx_cpymem(p, vals[i].data, vals[i].len);
+
+        if (p == end) {
+            *p = '\0';
+            break;
+        }
+
+        *p++ = ';'; *p++ = ' ';
+    }
+
+    h = ngx_list_push(&r->headers_in.headers);
+    if (h == NULL) {
+        ngx_http_v2_close_stream(r->stream, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NGX_ERROR;
+    }
+
+    h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash(
+                                    ngx_hash('c', 'o'), 'o'), 'k'), 'i'), 'e');
+
+    h->key.len = cookie.len;
+    h->key.data = cookie.data;
+
+    h->value.len = len;
+    h->value.data = buf;
+
+    h->lowcase_key = cookie.data;
+
+    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+    hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
+                       h->lowcase_key, h->key.len);
+
+    if (hh == NULL) {
+        ngx_http_v2_close_stream(r->stream, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NGX_ERROR;
+    }
+
+    if (hh->handler(r, h, hh->offset) != NGX_OK) {
+        /*
+         * request has been finalized already
+         * in ngx_http_process_multi_header_lines()
+         */
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_v2_run_request(ngx_http_request_t *r)
+{
+    if (ngx_http_v2_construct_request_line(r) != NGX_OK) {
+        return;
+    }
+
+    if (ngx_http_v2_construct_cookie_header(r) != NGX_OK) {
+        return;
+    }
+
+    r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;
+
+    if (ngx_http_process_request_header(r) != NGX_OK) {
+        return;
+    }
+
+    if (r->headers_in.content_length_n > 0 && r->stream->in_closed) {
+        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                      "client prematurely closed stream");
+
+        r->stream->skip_data = 1;
+
+        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+        return;
+    }
+
+    if (r->headers_in.content_length_n == -1 && !r->stream->in_closed) {
+        r->headers_in.chunked = 1;
+    }
+
+    ngx_http_process_request(r);
+}
+
+
+static void
+ngx_http_v2_run_request_handler(ngx_event_t *ev)
+{
+    ngx_connection_t    *fc;
+    ngx_http_request_t  *r;
+
+    fc = ev->data;
+    r = fc->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                   "http2 run request handler");
+
+    ngx_http_v2_run_request(r);
+}
+
+
+ngx_int_t
+ngx_http_v2_read_request_body(ngx_http_request_t *r)
+{
+    off_t                      len;
+    size_t                     size;
+    ngx_buf_t                 *buf;
+    ngx_int_t                  rc;
+    ngx_http_v2_stream_t      *stream;
+    ngx_http_v2_srv_conf_t    *h2scf;
+    ngx_http_request_body_t   *rb;
+    ngx_http_core_loc_conf_t  *clcf;
+    ngx_http_v2_connection_t  *h2c;
+
+    stream = r->stream;
+    rb = r->request_body;
+
+    if (stream->skip_data) {
+        r->request_body_no_buffering = 0;
+        rb->post_handler(r);
+        return NGX_OK;
+    }
+
+    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;
+        }
+
+        /*
+         * We need a room to store data up to the stream's initial window size,
+         * at least until this window will be exhausted.
+         */
+
+        if (len < (off_t) h2scf->preread_size) {
+            len = h2scf->preread_size;
+        }
+
+        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;
+        }
+    }
+
+    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 (buf) {
+            rc = ngx_http_v2_process_request_body(r, buf->pos,
+                                                  buf->last - buf->pos, 1);
+            ngx_pfree(r->pool, buf->start);
+            return rc;
+        }
+
+        return ngx_http_v2_process_request_body(r, NULL, 0, 1);
+    }
+
+    if (buf) {
+        rc = ngx_http_v2_process_request_body(r, buf->pos,
+                                              buf->last - buf->pos, 0);
+
+        ngx_pfree(r->pool, buf->start);
+
+        if (rc != NGX_OK) {
+            stream->skip_data = 1;
+            return rc;
+        }
+    }
+
+    if (r->request_body_no_buffering) {
+        size = (size_t) len - h2scf->preread_size;
+
+    } else {
+        stream->no_flow_control = 1;
+        size = NGX_HTTP_V2_MAX_WINDOW - stream->recv_window;
+    }
+
+    if (size) {
+        if (ngx_http_v2_send_window_update(stream->connection,
+                                           stream->node->id, size)
+            == NGX_ERROR)
+        {
+            stream->skip_data = 1;
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        h2c = stream->connection;
+
+        if (!h2c->blocked) {
+            if (ngx_http_v2_send_output_queue(h2c) == NGX_ERROR) {
+                stream->skip_data = 1;
+                return NGX_HTTP_INTERNAL_SERVER_ERROR;
+            }
+        }
+
+        stream->recv_window += size;
+    }
+
+    if (!buf) {
+        ngx_add_timer(r->connection->read, clcf->client_body_timeout);
+    }
+
+    r->read_event_handler = ngx_http_v2_read_client_request_body_handler;
+    r->write_event_handler = ngx_http_request_empty_handler;
+
+    return NGX_AGAIN;
+}
+
+
+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)
+{
+    ngx_buf_t                 *buf;
+    ngx_int_t                  rc;
+    ngx_connection_t          *fc;
+    ngx_http_request_body_t   *rb;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    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;
+
+            r->request_body_in_file_only = 1;
+
+        } 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");
+
+                return NGX_HTTP_BAD_REQUEST;
+            }
+
+            buf->last = ngx_cpymem(buf->last, pos, size);
+        }
+    }
+
+    if (last) {
+        rb->rest = 0;
+
+        if (fc->read->timer_set) {
+            ngx_del_timer(fc->read);
+        }
+
+        if (r->request_body_no_buffering) {
+            ngx_post_event(fc->read, &ngx_posted_events);
+            return NGX_OK;
+        }
+
+        rc = ngx_http_v2_filter_request_body(r);
+
+        if (rc != NGX_OK) {
+            return rc;
+        }
+
+        if (buf->sync) {
+            /* prevent reusing this buffer in the upstream module */
+            rb->buf = NULL;
+        }
+
+        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;
+    }
+
+    if (size == 0) {
+        return NGX_OK;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+    ngx_add_timer(fc->read, clcf->client_body_timeout);
+
+    if (r->request_body_no_buffering) {
+        ngx_post_event(fc->read, &ngx_posted_events);
+        return NGX_OK;
+    }
+
+    if (buf->sync) {
+        return ngx_http_v2_filter_request_body(r);
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_filter_request_body(ngx_http_request_t *r)
+{
+    ngx_buf_t                 *b, *buf;
+    ngx_int_t                  rc;
+    ngx_chain_t               *cl;
+    ngx_http_request_body_t   *rb;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    rb = r->request_body;
+    buf = rb->buf;
+
+    if (buf->pos == buf->last && rb->rest) {
+        cl = NULL;
+        goto update;
+    }
+
+    cl = ngx_chain_get_free_buf(r->pool, &rb->free);
+    if (cl == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    b = cl->buf;
+
+    ngx_memzero(b, sizeof(ngx_buf_t));
+
+    if (buf->pos != buf->last) {
+        r->request_length += buf->last - buf->pos;
+        rb->received += buf->last - buf->pos;
+
+        if (r->headers_in.content_length_n != -1) {
+            if (rb->received > r->headers_in.content_length_n) {
+                ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                              "client intended to send body data "
+                              "larger than declared");
+
+                return NGX_HTTP_BAD_REQUEST;
+            }
+
+        } else {
+            clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+            if (clcf->client_max_body_size
+                && rb->received > clcf->client_max_body_size)
+            {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "client intended to send too large chunked body: "
+                              "%O bytes", rb->received);
+
+                return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE;
+            }
+        }
+
+        b->temporary = 1;
+        b->pos = buf->pos;
+        b->last = buf->last;
+        b->start = b->pos;
+        b->end = b->last;
+
+        buf->pos = buf->last;
+    }
+
+    if (!rb->rest) {
+        if (r->headers_in.content_length_n != -1
+            && r->headers_in.content_length_n != rb->received)
+        {
+            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                          "client prematurely closed stream: "
+                          "only %O out of %O bytes of request body received",
+                          rb->received, r->headers_in.content_length_n);
+
+            return NGX_HTTP_BAD_REQUEST;
+        }
+
+        b->last_buf = 1;
+    }
+
+    b->tag = (ngx_buf_tag_t) &ngx_http_v2_filter_request_body;
+    b->flush = r->request_body_no_buffering;
+
+update:
+
+    rc = ngx_http_top_request_body_filter(r, cl);
+
+    ngx_chain_update_chains(r->pool, &rb->free, &rb->busy, &cl,
+                            (ngx_buf_tag_t) &ngx_http_v2_filter_request_body);
+
+    return rc;
+}
+
+
+static void
+ngx_http_v2_read_client_request_body_handler(ngx_http_request_t *r)
+{
+    ngx_connection_t  *fc;
+
+    fc = r->connection;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                   "http2 read client request body handler");
+
+    if (fc->read->timedout) {
+        ngx_log_error(NGX_LOG_INFO, fc->log, NGX_ETIMEDOUT, "client timed out");
+
+        fc->timedout = 1;
+        r->stream->skip_data = 1;
+
+        ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT);
+        return;
+    }
+
+    if (fc->error) {
+        ngx_log_error(NGX_LOG_INFO, fc->log, 0,
+                      "client prematurely closed stream");
+
+        r->stream->skip_data = 1;
+
+        ngx_http_finalize_request(r, NGX_HTTP_CLIENT_CLOSED_REQUEST);
+        return;
+    }
+}
+
+
+ngx_int_t
+ngx_http_v2_read_unbuffered_request_body(ngx_http_request_t *r)
+{
+    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;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    stream = r->stream;
+    fc = r->connection;
+
+    if (fc->read->timedout) {
+        if (stream->recv_window) {
+            stream->skip_data = 1;
+            fc->timedout = 1;
+
+            return NGX_HTTP_REQUEST_TIME_OUT;
+        }
+
+        fc->read->timedout = 0;
+    }
+
+    if (fc->error) {
+        stream->skip_data = 1;
+        return NGX_HTTP_BAD_REQUEST;
+    }
+
+    rc = ngx_http_v2_filter_request_body(r);
+
+    if (rc != NGX_OK) {
+        stream->skip_data = 1;
+        return rc;
+    }
+
+    if (!r->request_body->rest) {
+        return NGX_OK;
+    }
+
+    if (r->request_body->busy != NULL) {
+        return NGX_AGAIN;
+    }
+
+    buf = r->request_body->buf;
+
+    buf->pos = buf->start;
+    buf->last = buf->start;
+
+    window = buf->end - buf->start;
+    h2c = stream->connection;
+
+    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;
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        return NGX_AGAIN;
+    }
+
+    if (ngx_http_v2_send_window_update(h2c, stream->node->id,
+                                       window - stream->recv_window)
+        == NGX_ERROR)
+    {
+        stream->skip_data = 1;
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (ngx_http_v2_send_output_queue(h2c) == NGX_ERROR) {
+        stream->skip_data = 1;
+        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;
+}
+
+
+static ngx_int_t
+ngx_http_v2_terminate_stream(ngx_http_v2_connection_t *h2c,
+    ngx_http_v2_stream_t *stream, ngx_uint_t status)
+{
+    ngx_event_t       *rev;
+    ngx_connection_t  *fc;
+
+    if (stream->rst_sent) {
+        return NGX_OK;
+    }
+
+    if (ngx_http_v2_send_rst_stream(h2c, stream->node->id, status)
+        == NGX_ERROR)
+    {
+        return NGX_ERROR;
+    }
+
+    stream->rst_sent = 1;
+    stream->skip_data = 1;
+
+    fc = stream->request->connection;
+    fc->error = 1;
+
+    rev = fc->read;
+    rev->handler(rev);
+
+    return NGX_OK;
+}
+
+
+void
+ngx_http_v2_close_stream(ngx_http_v2_stream_t *stream, ngx_int_t rc)
+{
+    ngx_pool_t                *pool;
+    ngx_uint_t                 push;
+    ngx_event_t               *ev;
+    ngx_connection_t          *fc;
+    ngx_http_v2_node_t        *node;
+    ngx_http_v2_connection_t  *h2c;
+
+    h2c = stream->connection;
+    node = stream->node;
+
+    ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 close stream %ui, queued %ui, "
+                   "processing %ui, pushing %ui",
+                   node->id, stream->queued, h2c->processing, h2c->pushing);
+
+    fc = stream->request->connection;
+
+    if (stream->queued) {
+        fc->write->handler = ngx_http_v2_close_stream_handler;
+        fc->read->handler = ngx_http_empty_handler;
+        return;
+    }
+
+    if (!stream->rst_sent && !h2c->connection->error) {
+
+        if (!stream->out_closed) {
+            if (ngx_http_v2_send_rst_stream(h2c, node->id,
+                                      fc->timedout ? NGX_HTTP_V2_PROTOCOL_ERROR
+                                                   : NGX_HTTP_V2_INTERNAL_ERROR)
+                != NGX_OK)
+            {
+                h2c->connection->error = 1;
+            }
+
+        } else if (!stream->in_closed) {
+#if 0
+            if (ngx_http_v2_send_rst_stream(h2c, node->id, NGX_HTTP_V2_NO_ERROR)
+                != NGX_OK)
+            {
+                h2c->connection->error = 1;
+            }
+#else
+            /*
+             * At the time of writing at least the latest versions of Chrome
+             * do not properly handle RST_STREAM with NO_ERROR status.
+             *
+             * See: https://bugs.chromium.org/p/chromium/issues/detail?id=603182
+             *
+             * As a workaround, the stream window is maximized before closing
+             * the stream.  This allows a client to send up to 2 GB of data
+             * before getting blocked on flow control.
+             */
+
+            if (stream->recv_window < NGX_HTTP_V2_MAX_WINDOW
+                && ngx_http_v2_send_window_update(h2c, node->id,
+                                                  NGX_HTTP_V2_MAX_WINDOW
+                                                  - stream->recv_window)
+                   != NGX_OK)
+            {
+                h2c->connection->error = 1;
+            }
+#endif
+        }
+    }
+
+    if (h2c->state.stream == stream) {
+        h2c->state.stream = NULL;
+    }
+
+    push = stream->node->id % 2 == 0;
+
+    node->stream = NULL;
+
+    ngx_queue_insert_tail(&h2c->closed, &node->reuse);
+    h2c->closed_nodes++;
+
+    /*
+     * This pool keeps decoded request headers which can be used by log phase
+     * handlers in ngx_http_free_request().
+     *
+     * The pointer is stored into local variable because the stream object
+     * will be destroyed after a call to ngx_http_free_request().
+     */
+    pool = stream->pool;
+
+    ngx_http_free_request(stream->request, rc);
+
+    if (pool != h2c->state.pool) {
+        ngx_destroy_pool(pool);
+
+    } else {
+        /* pool will be destroyed when the complete header is parsed */
+        h2c->state.keep_pool = 0;
+    }
+
+    ev = fc->read;
+
+    if (ev->timer_set) {
+        ngx_del_timer(ev);
+    }
+
+    if (ev->posted) {
+        ngx_delete_posted_event(ev);
+    }
+
+    ev = fc->write;
+
+    if (ev->timer_set) {
+        ngx_del_timer(ev);
+    }
+
+    if (ev->posted) {
+        ngx_delete_posted_event(ev);
+    }
+
+    fc->data = h2c->free_fake_connections;
+    h2c->free_fake_connections = fc;
+
+    if (push) {
+        h2c->pushing--;
+
+    } else {
+        h2c->processing--;
+    }
+
+    if (h2c->processing || h2c->pushing || h2c->blocked) {
+        return;
+    }
+
+    ev = h2c->connection->read;
+
+    ev->handler = ngx_http_v2_handle_connection_handler;
+    ngx_post_event(ev, &ngx_posted_events);
+}
+
+
+static void
+ngx_http_v2_close_stream_handler(ngx_event_t *ev)
+{
+    ngx_connection_t    *fc;
+    ngx_http_request_t  *r;
+
+    fc = ev->data;
+    r = fc->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                   "http2 close stream handler");
+
+    if (ev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, fc->log, NGX_ETIMEDOUT, "client timed out");
+
+        fc->timedout = 1;
+
+        ngx_http_v2_close_stream(r->stream, NGX_HTTP_REQUEST_TIME_OUT);
+        return;
+    }
+
+    ngx_http_v2_close_stream(r->stream, 0);
+}
+
+
+static void
+ngx_http_v2_handle_connection_handler(ngx_event_t *rev)
+{
+    ngx_connection_t          *c;
+    ngx_http_v2_connection_t  *h2c;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                   "http2 handle connection handler");
+
+    c = rev->data;
+    h2c = c->data;
+
+    if (c->error) {
+        ngx_http_v2_finalize_connection(h2c, 0);
+        return;
+    }
+
+    rev->handler = ngx_http_v2_read_handler;
+
+    if (rev->ready) {
+        ngx_http_v2_read_handler(rev);
+        return;
+    }
+
+    if (h2c->last_out && ngx_http_v2_send_output_queue(h2c) == NGX_ERROR) {
+        ngx_http_v2_finalize_connection(h2c, 0);
+        return;
+    }
+
+    ngx_http_v2_handle_connection(c->data);
+}
+
+
+static void
+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;
+
+    c = rev->data;
+    h2c = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http2 idle handler");
+
+    if (rev->timedout || c->close) {
+        ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_NO_ERROR);
+        return;
+    }
+
+#if (NGX_HAVE_KQUEUE)
+
+    if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+        if (rev->pending_eof) {
+            c->log->handler = NULL;
+            ngx_log_error(NGX_LOG_INFO, c->log, rev->kq_errno,
+                          "kevent() reported that client %V closed "
+                          "idle connection", &c->addr_text);
+#if (NGX_HTTP_SSL)
+            if (c->ssl) {
+                c->ssl->no_send_shutdown = 1;
+            }
+#endif
+            ngx_http_close_connection(c);
+            return;
+        }
+    }
+
+#endif
+
+    h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx,
+                                         ngx_http_v2_module);
+
+    if (h2c->idle++ > 10 * h2scf->max_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);
+        return;
+    }
+
+    c->destroyed = 0;
+    ngx_reusable_connection(c, 0);
+
+    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);
+        return;
+    }
+
+    c->write->handler = ngx_http_v2_write_handler;
+
+    rev->handler = ngx_http_v2_read_handler;
+    ngx_http_v2_read_handler(rev);
+}
+
+
+static void
+ngx_http_v2_finalize_connection(ngx_http_v2_connection_t *h2c,
+    ngx_uint_t status)
+{
+    ngx_uint_t               i, size;
+    ngx_event_t             *ev;
+    ngx_connection_t        *c, *fc;
+    ngx_http_request_t      *r;
+    ngx_http_v2_node_t      *node;
+    ngx_http_v2_stream_t    *stream;
+    ngx_http_v2_srv_conf_t  *h2scf;
+
+    c = h2c->connection;
+
+    h2c->blocked = 1;
+
+    if (!c->error && !h2c->goaway) {
+        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;
+    }
+
+    c->read->handler = ngx_http_empty_handler;
+    c->write->handler = ngx_http_empty_handler;
+
+    h2c->last_out = NULL;
+
+    h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx,
+                                         ngx_http_v2_module);
+
+    size = ngx_http_v2_index_size(h2scf);
+
+    for (i = 0; i < size; i++) {
+
+        for (node = h2c->streams_index[i]; node; node = node->index) {
+            stream = node->stream;
+
+            if (stream == NULL) {
+                continue;
+            }
+
+            stream->waiting = 0;
+
+            r = stream->request;
+            fc = r->connection;
+
+            fc->error = 1;
+
+            if (stream->queued) {
+                stream->queued = 0;
+
+                ev = fc->write;
+                ev->active = 0;
+                ev->ready = 1;
+
+            } else {
+                ev = fc->read;
+            }
+
+            ev->eof = 1;
+            ev->handler(ev);
+        }
+    }
+
+    h2c->blocked = 0;
+
+    if (h2c->processing || h2c->pushing) {
+        return;
+    }
+
+    ngx_http_close_connection(c);
+}
+
+
+static ngx_int_t
+ngx_http_v2_adjust_windows(ngx_http_v2_connection_t *h2c, ssize_t delta)
+{
+    ngx_uint_t               i, size;
+    ngx_event_t             *wev;
+    ngx_http_v2_node_t      *node;
+    ngx_http_v2_stream_t    *stream;
+    ngx_http_v2_srv_conf_t  *h2scf;
+
+    h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx,
+                                         ngx_http_v2_module);
+
+    size = ngx_http_v2_index_size(h2scf);
+
+    for (i = 0; i < size; i++) {
+
+        for (node = h2c->streams_index[i]; node; node = node->index) {
+            stream = node->stream;
+
+            if (stream == NULL) {
+                continue;
+            }
+
+            if (delta > 0
+                && stream->send_window
+                      > (ssize_t) (NGX_HTTP_V2_MAX_WINDOW - delta))
+            {
+                if (ngx_http_v2_terminate_stream(h2c, stream,
+                                                 NGX_HTTP_V2_FLOW_CTRL_ERROR)
+                    == NGX_ERROR)
+                {
+                    return NGX_ERROR;
+                }
+
+                continue;
+            }
+
+            stream->send_window += delta;
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                           "http2:%ui adjusted window: %z",
+                           node->id, stream->send_window);
+
+            if (stream->send_window > 0 && stream->exhausted) {
+                stream->exhausted = 0;
+
+                wev = stream->request->connection->write;
+
+                wev->active = 0;
+                wev->ready = 1;
+
+                if (!wev->delayed) {
+                    wev->handler(wev);
+                }
+            }
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_v2_set_dependency(ngx_http_v2_connection_t *h2c,
+    ngx_http_v2_node_t *node, ngx_uint_t depend, ngx_uint_t exclusive)
+{
+    ngx_queue_t         *children, *q;
+    ngx_http_v2_node_t  *parent, *child, *next;
+
+    parent = depend ? ngx_http_v2_get_node_by_id(h2c, depend, 0) : NULL;
+
+    if (parent == NULL) {
+        parent = NGX_HTTP_V2_ROOT;
+
+        if (depend != 0) {
+            exclusive = 0;
+        }
+
+        node->rank = 1;
+        node->rel_weight = (1.0 / 256) * node->weight;
+
+        children = &h2c->dependencies;
+
+    } else {
+        if (node->parent != NULL) {
+
+            for (next = parent->parent;
+                 next != NGX_HTTP_V2_ROOT && next->rank >= node->rank;
+                 next = next->parent)
+            {
+                if (next != node) {
+                    continue;
+                }
+
+                ngx_queue_remove(&parent->queue);
+                ngx_queue_insert_after(&node->queue, &parent->queue);
+
+                parent->parent = node->parent;
+
+                if (node->parent == NGX_HTTP_V2_ROOT) {
+                    parent->rank = 1;
+                    parent->rel_weight = (1.0 / 256) * parent->weight;
+
+                } else {
+                    parent->rank = node->parent->rank + 1;
+                    parent->rel_weight = (node->parent->rel_weight / 256)
+                                         * parent->weight;
+                }
+
+                if (!exclusive) {
+                    ngx_http_v2_node_children_update(parent);
+                }
+
+                break;
+            }
+        }
+
+        node->rank = parent->rank + 1;
+        node->rel_weight = (parent->rel_weight / 256) * node->weight;
+
+        if (parent->stream == NULL) {
+            ngx_queue_remove(&parent->reuse);
+            ngx_queue_insert_tail(&h2c->closed, &parent->reuse);
+        }
+
+        children = &parent->children;
+    }
+
+    if (exclusive) {
+        for (q = ngx_queue_head(children);
+             q != ngx_queue_sentinel(children);
+             q = ngx_queue_next(q))
+        {
+            child = ngx_queue_data(q, ngx_http_v2_node_t, queue);
+            child->parent = node;
+        }
+
+        ngx_queue_add(&node->children, children);
+        ngx_queue_init(children);
+    }
+
+    if (node->parent != NULL) {
+        ngx_queue_remove(&node->queue);
+    }
+
+    ngx_queue_insert_tail(children, &node->queue);
+
+    node->parent = parent;
+
+    ngx_http_v2_node_children_update(node);
+}
+
+
+static void
+ngx_http_v2_node_children_update(ngx_http_v2_node_t *node)
+{
+    ngx_queue_t         *q;
+    ngx_http_v2_node_t  *child;
+
+    for (q = ngx_queue_head(&node->children);
+         q != ngx_queue_sentinel(&node->children);
+         q = ngx_queue_next(q))
+    {
+        child = ngx_queue_data(q, ngx_http_v2_node_t, queue);
+
+        child->rank = node->rank + 1;
+        child->rel_weight = (node->rel_weight / 256) * child->weight;
+
+        ngx_http_v2_node_children_update(child);
+    }
+}
+
+
+static void
+ngx_http_v2_pool_cleanup(void *data)
+{
+    ngx_http_v2_connection_t  *h2c = data;
+
+    if (h2c->state.pool) {
+        ngx_destroy_pool(h2c->state.pool);
+    }
+
+    if (h2c->pool) {
+        ngx_destroy_pool(h2c->pool);
+    }
+}
diff --git a/nginx/src/http/v2/ngx_http_v2.h b/nginx/src/http/v2/ngx_http_v2.h
new file mode 100644 (file)
index 0000000..bec2216
--- /dev/null
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Valentin V. Bartenev
+ */
+
+
+#ifndef _NGX_HTTP_V2_H_INCLUDED_
+#define _NGX_HTTP_V2_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_V2_ALPN_ADVERTISE       "\x02h2"
+#define NGX_HTTP_V2_NPN_ADVERTISE        NGX_HTTP_V2_ALPN_ADVERTISE
+
+#define NGX_HTTP_V2_STATE_BUFFER_SIZE    16
+
+#define NGX_HTTP_V2_DEFAULT_FRAME_SIZE   (1 << 14)
+#define NGX_HTTP_V2_MAX_FRAME_SIZE       ((1 << 24) - 1)
+
+#define NGX_HTTP_V2_INT_OCTETS           4
+#define NGX_HTTP_V2_MAX_FIELD                                                 \
+    (127 + (1 << (NGX_HTTP_V2_INT_OCTETS - 1) * 7) - 1)
+
+#define NGX_HTTP_V2_STREAM_ID_SIZE       4
+
+#define NGX_HTTP_V2_FRAME_HEADER_SIZE    9
+
+/* frame types */
+#define NGX_HTTP_V2_DATA_FRAME           0x0
+#define NGX_HTTP_V2_HEADERS_FRAME        0x1
+#define NGX_HTTP_V2_PRIORITY_FRAME       0x2
+#define NGX_HTTP_V2_RST_STREAM_FRAME     0x3
+#define NGX_HTTP_V2_SETTINGS_FRAME       0x4
+#define NGX_HTTP_V2_PUSH_PROMISE_FRAME   0x5
+#define NGX_HTTP_V2_PING_FRAME           0x6
+#define NGX_HTTP_V2_GOAWAY_FRAME         0x7
+#define NGX_HTTP_V2_WINDOW_UPDATE_FRAME  0x8
+#define NGX_HTTP_V2_CONTINUATION_FRAME   0x9
+
+/* frame flags */
+#define NGX_HTTP_V2_NO_FLAG              0x00
+#define NGX_HTTP_V2_ACK_FLAG             0x01
+#define NGX_HTTP_V2_END_STREAM_FLAG      0x01
+#define NGX_HTTP_V2_END_HEADERS_FLAG     0x04
+#define NGX_HTTP_V2_PADDED_FLAG          0x08
+#define NGX_HTTP_V2_PRIORITY_FLAG        0x20
+
+#define NGX_HTTP_V2_MAX_WINDOW           ((1U << 31) - 1)
+#define NGX_HTTP_V2_DEFAULT_WINDOW       65535
+
+#define NGX_HTTP_V2_DEFAULT_WEIGHT       16
+
+
+typedef struct ngx_http_v2_connection_s   ngx_http_v2_connection_t;
+typedef struct ngx_http_v2_node_s         ngx_http_v2_node_t;
+typedef struct ngx_http_v2_out_frame_s    ngx_http_v2_out_frame_t;
+
+
+typedef u_char *(*ngx_http_v2_handler_pt) (ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
+
+
+typedef struct {
+    ngx_str_t                        name;
+    ngx_str_t                        value;
+} ngx_http_v2_header_t;
+
+
+typedef struct {
+    ngx_uint_t                       sid;
+    size_t                           length;
+    size_t                           padding;
+    unsigned                         flags:8;
+
+    unsigned                         incomplete:1;
+    unsigned                         keep_pool:1;
+
+    /* HPACK */
+    unsigned                         parse_name:1;
+    unsigned                         parse_value:1;
+    unsigned                         index:1;
+    ngx_http_v2_header_t             header;
+    size_t                           header_limit;
+    u_char                           field_state;
+    u_char                          *field_start;
+    u_char                          *field_end;
+    size_t                           field_rest;
+    ngx_pool_t                      *pool;
+
+    ngx_http_v2_stream_t            *stream;
+
+    u_char                           buffer[NGX_HTTP_V2_STATE_BUFFER_SIZE];
+    size_t                           buffer_used;
+    ngx_http_v2_handler_pt           handler;
+} ngx_http_v2_state_t;
+
+
+
+typedef struct {
+    ngx_http_v2_header_t           **entries;
+
+    ngx_uint_t                       added;
+    ngx_uint_t                       deleted;
+    ngx_uint_t                       reused;
+    ngx_uint_t                       allocated;
+
+    size_t                           size;
+    size_t                           free;
+    u_char                          *storage;
+    u_char                          *pos;
+} ngx_http_v2_hpack_t;
+
+
+struct ngx_http_v2_connection_s {
+    ngx_connection_t                *connection;
+    ngx_http_connection_t           *http_connection;
+
+    ngx_uint_t                       processing;
+    ngx_uint_t                       frames;
+    ngx_uint_t                       idle;
+
+    ngx_uint_t                       pushing;
+    ngx_uint_t                       concurrent_pushes;
+
+    size_t                           send_window;
+    size_t                           recv_window;
+    size_t                           init_window;
+
+    size_t                           frame_size;
+
+    ngx_queue_t                      waiting;
+
+    ngx_http_v2_state_t              state;
+
+    ngx_http_v2_hpack_t              hpack;
+
+    ngx_pool_t                      *pool;
+
+    ngx_http_v2_out_frame_t         *free_frames;
+    ngx_connection_t                *free_fake_connections;
+
+    ngx_http_v2_node_t             **streams_index;
+
+    ngx_http_v2_out_frame_t         *last_out;
+
+    ngx_queue_t                      dependencies;
+    ngx_queue_t                      closed;
+
+    ngx_uint_t                       last_sid;
+    ngx_uint_t                       last_push;
+
+    unsigned                         closed_nodes:8;
+    unsigned                         settings_ack:1;
+    unsigned                         table_update:1;
+    unsigned                         blocked:1;
+    unsigned                         goaway:1;
+    unsigned                         push_disabled:1;
+};
+
+
+struct ngx_http_v2_node_s {
+    ngx_uint_t                       id;
+    ngx_http_v2_node_t              *index;
+    ngx_http_v2_node_t              *parent;
+    ngx_queue_t                      queue;
+    ngx_queue_t                      children;
+    ngx_queue_t                      reuse;
+    ngx_uint_t                       rank;
+    ngx_uint_t                       weight;
+    double                           rel_weight;
+    ngx_http_v2_stream_t            *stream;
+};
+
+
+struct ngx_http_v2_stream_s {
+    ngx_http_request_t              *request;
+    ngx_http_v2_connection_t        *connection;
+    ngx_http_v2_node_t              *node;
+
+    ngx_uint_t                       queued;
+
+    /*
+     * A change to SETTINGS_INITIAL_WINDOW_SIZE could cause the
+     * send_window to become negative, hence it's signed.
+     */
+    ssize_t                          send_window;
+    size_t                           recv_window;
+
+    ngx_buf_t                       *preread;
+
+    ngx_http_v2_out_frame_t         *free_frames;
+    ngx_chain_t                     *free_frame_headers;
+    ngx_chain_t                     *free_bufs;
+
+    ngx_queue_t                      queue;
+
+    ngx_array_t                     *cookies;
+
+    ngx_pool_t                      *pool;
+
+    unsigned                         waiting:1;
+    unsigned                         blocked:1;
+    unsigned                         exhausted:1;
+    unsigned                         in_closed:1;
+    unsigned                         out_closed:1;
+    unsigned                         rst_sent:1;
+    unsigned                         no_flow_control:1;
+    unsigned                         skip_data:1;
+};
+
+
+struct ngx_http_v2_out_frame_s {
+    ngx_http_v2_out_frame_t         *next;
+    ngx_chain_t                     *first;
+    ngx_chain_t                     *last;
+    ngx_int_t                      (*handler)(ngx_http_v2_connection_t *h2c,
+                                        ngx_http_v2_out_frame_t *frame);
+
+    ngx_http_v2_stream_t            *stream;
+    size_t                           length;
+
+    unsigned                         blocked:1;
+    unsigned                         fin:1;
+};
+
+
+static ngx_inline void
+ngx_http_v2_queue_frame(ngx_http_v2_connection_t *h2c,
+    ngx_http_v2_out_frame_t *frame)
+{
+    ngx_http_v2_out_frame_t  **out;
+
+    for (out = &h2c->last_out; *out; out = &(*out)->next) {
+
+        if ((*out)->blocked || (*out)->stream == NULL) {
+            break;
+        }
+
+        if ((*out)->stream->node->rank < frame->stream->node->rank
+            || ((*out)->stream->node->rank == frame->stream->node->rank
+                && (*out)->stream->node->rel_weight
+                   >= frame->stream->node->rel_weight))
+        {
+            break;
+        }
+    }
+
+    frame->next = *out;
+    *out = frame;
+}
+
+
+static ngx_inline void
+ngx_http_v2_queue_blocked_frame(ngx_http_v2_connection_t *h2c,
+    ngx_http_v2_out_frame_t *frame)
+{
+    ngx_http_v2_out_frame_t  **out;
+
+    for (out = &h2c->last_out; *out; out = &(*out)->next) {
+
+        if ((*out)->blocked || (*out)->stream == NULL) {
+            break;
+        }
+    }
+
+    frame->next = *out;
+    *out = frame;
+}
+
+
+static ngx_inline void
+ngx_http_v2_queue_ordered_frame(ngx_http_v2_connection_t *h2c,
+    ngx_http_v2_out_frame_t *frame)
+{
+    frame->next = h2c->last_out;
+    h2c->last_out = frame;
+}
+
+
+void ngx_http_v2_init(ngx_event_t *rev);
+
+ngx_int_t ngx_http_v2_read_request_body(ngx_http_request_t *r);
+ngx_int_t ngx_http_v2_read_unbuffered_request_body(ngx_http_request_t *r);
+
+ngx_http_v2_stream_t *ngx_http_v2_push_stream(ngx_http_v2_stream_t *parent,
+    ngx_str_t *path);
+
+void ngx_http_v2_close_stream(ngx_http_v2_stream_t *stream, ngx_int_t rc);
+
+ngx_int_t ngx_http_v2_send_output_queue(ngx_http_v2_connection_t *h2c);
+
+
+ngx_str_t *ngx_http_v2_get_static_name(ngx_uint_t index);
+ngx_str_t *ngx_http_v2_get_static_value(ngx_uint_t index);
+
+ngx_int_t ngx_http_v2_get_indexed_header(ngx_http_v2_connection_t *h2c,
+    ngx_uint_t index, ngx_uint_t name_only);
+ngx_int_t ngx_http_v2_add_header(ngx_http_v2_connection_t *h2c,
+    ngx_http_v2_header_t *header);
+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)
+
+
+#if (NGX_HAVE_NONALIGNED)
+
+#define ngx_http_v2_parse_uint16(p)  ntohs(*(uint16_t *) (p))
+#define ngx_http_v2_parse_uint32(p)  ntohl(*(uint32_t *) (p))
+
+#else
+
+#define ngx_http_v2_parse_uint16(p)  ((p)[0] << 8 | (p)[1])
+#define ngx_http_v2_parse_uint32(p)                                           \
+    ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3])
+
+#endif
+
+#define ngx_http_v2_parse_length(p)  ((p) >> 8)
+#define ngx_http_v2_parse_type(p)    ((p) & 0xff)
+#define ngx_http_v2_parse_sid(p)     (ngx_http_v2_parse_uint32(p) & 0x7fffffff)
+#define ngx_http_v2_parse_window(p)  (ngx_http_v2_parse_uint32(p) & 0x7fffffff)
+
+
+#define ngx_http_v2_write_uint16_aligned(p, s)                                \
+    (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t))
+#define ngx_http_v2_write_uint32_aligned(p, s)                                \
+    (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t))
+
+#if (NGX_HAVE_NONALIGNED)
+
+#define ngx_http_v2_write_uint16  ngx_http_v2_write_uint16_aligned
+#define ngx_http_v2_write_uint32  ngx_http_v2_write_uint32_aligned
+
+#else
+
+#define ngx_http_v2_write_uint16(p, s)                                        \
+    ((p)[0] = (u_char) ((s) >> 8),                                            \
+     (p)[1] = (u_char)  (s),                                                  \
+     (p) + sizeof(uint16_t))
+
+#define ngx_http_v2_write_uint32(p, s)                                        \
+    ((p)[0] = (u_char) ((s) >> 24),                                           \
+     (p)[1] = (u_char) ((s) >> 16),                                           \
+     (p)[2] = (u_char) ((s) >> 8),                                            \
+     (p)[3] = (u_char)  (s),                                                  \
+     (p) + sizeof(uint32_t))
+
+#endif
+
+#define ngx_http_v2_write_len_and_type(p, l, t)                               \
+    ngx_http_v2_write_uint32_aligned(p, (l) << 8 | (t))
+
+#define ngx_http_v2_write_sid  ngx_http_v2_write_uint32
+
+
+#define ngx_http_v2_indexed(i)      (128 + (i))
+#define ngx_http_v2_inc_indexed(i)  (64 + (i))
+
+#define ngx_http_v2_write_name(dst, src, len, tmp)                            \
+    ngx_http_v2_string_encode(dst, src, len, tmp, 1)
+#define ngx_http_v2_write_value(dst, src, len, tmp)                           \
+    ngx_http_v2_string_encode(dst, src, len, tmp, 0)
+
+#define NGX_HTTP_V2_ENCODE_RAW            0
+#define NGX_HTTP_V2_ENCODE_HUFF           0x80
+
+#define NGX_HTTP_V2_AUTHORITY_INDEX       1
+
+#define NGX_HTTP_V2_METHOD_INDEX          2
+#define NGX_HTTP_V2_METHOD_GET_INDEX      2
+#define NGX_HTTP_V2_METHOD_POST_INDEX     3
+
+#define NGX_HTTP_V2_PATH_INDEX            4
+#define NGX_HTTP_V2_PATH_ROOT_INDEX       4
+
+#define NGX_HTTP_V2_SCHEME_HTTP_INDEX     6
+#define NGX_HTTP_V2_SCHEME_HTTPS_INDEX    7
+
+#define NGX_HTTP_V2_STATUS_INDEX          8
+#define NGX_HTTP_V2_STATUS_200_INDEX      8
+#define NGX_HTTP_V2_STATUS_204_INDEX      9
+#define NGX_HTTP_V2_STATUS_206_INDEX      10
+#define NGX_HTTP_V2_STATUS_304_INDEX      11
+#define NGX_HTTP_V2_STATUS_400_INDEX      12
+#define NGX_HTTP_V2_STATUS_404_INDEX      13
+#define NGX_HTTP_V2_STATUS_500_INDEX      14
+
+#define NGX_HTTP_V2_ACCEPT_ENCODING_INDEX 16
+#define NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX 17
+#define NGX_HTTP_V2_CONTENT_LENGTH_INDEX  28
+#define NGX_HTTP_V2_CONTENT_TYPE_INDEX    31
+#define NGX_HTTP_V2_DATE_INDEX            33
+#define NGX_HTTP_V2_LAST_MODIFIED_INDEX   44
+#define NGX_HTTP_V2_LOCATION_INDEX        46
+#define NGX_HTTP_V2_SERVER_INDEX          54
+#define NGX_HTTP_V2_USER_AGENT_INDEX      58
+#define NGX_HTTP_V2_VARY_INDEX            59
+
+
+u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len,
+    u_char *tmp, ngx_uint_t lower);
+
+
+#endif /* _NGX_HTTP_V2_H_INCLUDED_ */
diff --git a/nginx/src/http/v2/ngx_http_v2_encode.c b/nginx/src/http/v2/ngx_http_v2_encode.c
new file mode 100644 (file)
index 0000000..ac79208
--- /dev/null
@@ -0,0 +1,62 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Valentin V. Bartenev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix,
+    ngx_uint_t value);
+
+
+u_char *
+ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp,
+    ngx_uint_t lower)
+{
+    size_t  hlen;
+
+    hlen = ngx_http_v2_huff_encode(src, len, tmp, lower);
+
+    if (hlen > 0) {
+        *dst = NGX_HTTP_V2_ENCODE_HUFF;
+        dst = ngx_http_v2_write_int(dst, ngx_http_v2_prefix(7), hlen);
+        return ngx_cpymem(dst, tmp, hlen);
+    }
+
+    *dst = NGX_HTTP_V2_ENCODE_RAW;
+    dst = ngx_http_v2_write_int(dst, ngx_http_v2_prefix(7), len);
+
+    if (lower) {
+        ngx_strlow(dst, src, len);
+        return dst + len;
+    }
+
+    return ngx_cpymem(dst, src, len);
+}
+
+
+static u_char *
+ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value)
+{
+    if (value < prefix) {
+        *pos++ |= value;
+        return pos;
+    }
+
+    *pos++ |= prefix;
+    value -= prefix;
+
+    while (value >= 128) {
+        *pos++ = value % 128 + 128;
+        value /= 128;
+    }
+
+    *pos++ = (u_char) value;
+
+    return pos;
+}
diff --git a/nginx/src/http/v2/ngx_http_v2_filter_module.c b/nginx/src/http/v2/ngx_http_v2_filter_module.c
new file mode 100644 (file)
index 0000000..029e8ec
--- /dev/null
@@ -0,0 +1,2145 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Valentin V. Bartenev
+ * Copyright (C) Ruslan Ermilov
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <nginx.h>
+#include <ngx_http_v2_module.h>
+
+
+/*
+ * This returns precise number of octets for values in range 0..253
+ * and estimate number for the rest, but not smaller than required.
+ */
+
+#define ngx_http_v2_integer_octets(v)  (1 + (v) / 127)
+
+#define ngx_http_v2_literal_size(h)                                           \
+    (ngx_http_v2_integer_octets(sizeof(h) - 1) + sizeof(h) - 1)
+
+
+#define NGX_HTTP_V2_NO_TRAILERS           (ngx_http_v2_out_frame_t *) -1
+
+
+typedef struct {
+    ngx_str_t      name;
+    u_char         index;
+    ngx_uint_t     offset;
+} ngx_http_v2_push_header_t;
+
+
+static ngx_http_v2_push_header_t  ngx_http_v2_push_headers[] = {
+    { ngx_string(":authority"), NGX_HTTP_V2_AUTHORITY_INDEX,
+      offsetof(ngx_http_headers_in_t, host) },
+
+    { ngx_string("accept-encoding"), NGX_HTTP_V2_ACCEPT_ENCODING_INDEX,
+      offsetof(ngx_http_headers_in_t, accept_encoding) },
+
+    { ngx_string("accept-language"), NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX,
+      offsetof(ngx_http_headers_in_t, accept_language) },
+
+    { ngx_string("user-agent"), NGX_HTTP_V2_USER_AGENT_INDEX,
+      offsetof(ngx_http_headers_in_t, user_agent) },
+};
+
+#define NGX_HTTP_V2_PUSH_HEADERS                                              \
+    (sizeof(ngx_http_v2_push_headers) / sizeof(ngx_http_v2_push_header_t))
+
+
+static ngx_int_t ngx_http_v2_push_resources(ngx_http_request_t *r);
+static ngx_int_t ngx_http_v2_push_resource(ngx_http_request_t *r,
+    ngx_str_t *path, ngx_str_t *binary);
+
+static ngx_http_v2_out_frame_t *ngx_http_v2_create_headers_frame(
+    ngx_http_request_t *r, u_char *pos, u_char *end, ngx_uint_t fin);
+static ngx_http_v2_out_frame_t *ngx_http_v2_create_push_frame(
+    ngx_http_request_t *r, u_char *pos, u_char *end);
+static ngx_http_v2_out_frame_t *ngx_http_v2_create_trailers_frame(
+    ngx_http_request_t *r);
+
+static ngx_chain_t *ngx_http_v2_send_chain(ngx_connection_t *fc,
+    ngx_chain_t *in, off_t limit);
+
+static ngx_chain_t *ngx_http_v2_filter_get_shadow(
+    ngx_http_v2_stream_t *stream, ngx_buf_t *buf, off_t offset, off_t size);
+static ngx_http_v2_out_frame_t *ngx_http_v2_filter_get_data_frame(
+    ngx_http_v2_stream_t *stream, size_t len, ngx_chain_t *first,
+    ngx_chain_t *last);
+
+static ngx_inline ngx_int_t ngx_http_v2_flow_control(
+    ngx_http_v2_connection_t *h2c, ngx_http_v2_stream_t *stream);
+static void ngx_http_v2_waiting_queue(ngx_http_v2_connection_t *h2c,
+    ngx_http_v2_stream_t *stream);
+
+static ngx_inline ngx_int_t ngx_http_v2_filter_send(
+    ngx_connection_t *fc, ngx_http_v2_stream_t *stream);
+
+static ngx_int_t ngx_http_v2_headers_frame_handler(
+    ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame);
+static ngx_int_t ngx_http_v2_push_frame_handler(
+    ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame);
+static ngx_int_t ngx_http_v2_data_frame_handler(
+    ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame);
+static ngx_inline void ngx_http_v2_handle_frame(
+    ngx_http_v2_stream_t *stream, ngx_http_v2_out_frame_t *frame);
+static ngx_inline void ngx_http_v2_handle_stream(
+    ngx_http_v2_connection_t *h2c, ngx_http_v2_stream_t *stream);
+
+static void ngx_http_v2_filter_cleanup(void *data);
+
+static ngx_int_t ngx_http_v2_filter_init(ngx_conf_t *cf);
+
+
+static ngx_http_module_t  ngx_http_v2_filter_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_v2_filter_init,               /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_v2_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_v2_filter_module_ctx,        /* module context */
+    NULL,                                  /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+
+
+static ngx_int_t
+ngx_http_v2_header_filter(ngx_http_request_t *r)
+{
+    u_char                     status, *pos, *start, *p, *tmp;
+    size_t                     len, tmp_len;
+    ngx_str_t                  host, location;
+    ngx_uint_t                 i, port, fin;
+    ngx_list_part_t           *part;
+    ngx_table_elt_t           *header;
+    ngx_connection_t          *fc;
+    ngx_http_cleanup_t        *cln;
+    ngx_http_v2_stream_t      *stream;
+    ngx_http_v2_out_frame_t   *frame;
+    ngx_http_v2_connection_t  *h2c;
+    ngx_http_core_loc_conf_t  *clcf;
+    ngx_http_core_srv_conf_t  *cscf;
+    u_char                     addr[NGX_SOCKADDR_STRLEN];
+
+    static const u_char nginx[5] = "\x84\xaa\x63\x55\xe7";
+#if (NGX_HTTP_GZIP)
+    static const u_char accept_encoding[12] =
+        "\x8b\x84\x84\x2d\x69\x5b\x05\x44\x3c\x86\xaa\x6f";
+#endif
+
+    static size_t nginx_ver_len = ngx_http_v2_literal_size(NGINX_VER);
+    static u_char nginx_ver[ngx_http_v2_literal_size(NGINX_VER)];
+
+    static size_t nginx_ver_build_len =
+                                  ngx_http_v2_literal_size(NGINX_VER_BUILD);
+    static u_char nginx_ver_build[ngx_http_v2_literal_size(NGINX_VER_BUILD)];
+
+    stream = r->stream;
+
+    if (!stream) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http2 header filter");
+
+    if (r->header_sent) {
+        return NGX_OK;
+    }
+
+    r->header_sent = 1;
+
+    if (r != r->main) {
+        return NGX_OK;
+    }
+
+    fc = r->connection;
+
+    if (fc->error) {
+        return NGX_ERROR;
+    }
+
+    if (r->method == NGX_HTTP_HEAD) {
+        r->header_only = 1;
+    }
+
+    switch (r->headers_out.status) {
+
+    case NGX_HTTP_OK:
+        status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_200_INDEX);
+        break;
+
+    case NGX_HTTP_NO_CONTENT:
+        r->header_only = 1;
+
+        ngx_str_null(&r->headers_out.content_type);
+
+        r->headers_out.content_length = NULL;
+        r->headers_out.content_length_n = -1;
+
+        r->headers_out.last_modified_time = -1;
+        r->headers_out.last_modified = NULL;
+
+        status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_204_INDEX);
+        break;
+
+    case NGX_HTTP_PARTIAL_CONTENT:
+        status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_206_INDEX);
+        break;
+
+    case NGX_HTTP_NOT_MODIFIED:
+        r->header_only = 1;
+        status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_304_INDEX);
+        break;
+
+    default:
+        r->headers_out.last_modified_time = -1;
+        r->headers_out.last_modified = NULL;
+
+        switch (r->headers_out.status) {
+
+        case NGX_HTTP_BAD_REQUEST:
+            status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_400_INDEX);
+            break;
+
+        case NGX_HTTP_NOT_FOUND:
+            status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_404_INDEX);
+            break;
+
+        case NGX_HTTP_INTERNAL_SERVER_ERROR:
+            status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_500_INDEX);
+            break;
+
+        default:
+            status = 0;
+        }
+    }
+
+    h2c = stream->connection;
+
+    if (!h2c->push_disabled && !h2c->goaway
+        && stream->node->id % 2 == 1
+        && r->method != NGX_HTTP_HEAD)
+    {
+        if (ngx_http_v2_push_resources(r) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    len = h2c->table_update ? 1 : 0;
+
+    len += status ? 1 : 1 + ngx_http_v2_literal_size("418");
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (r->headers_out.server == NULL) {
+
+        if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
+            len += 1 + nginx_ver_len;
+
+        } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
+            len += 1 + nginx_ver_build_len;
+
+        } else {
+            len += 1 + sizeof(nginx);
+        }
+    }
+
+    if (r->headers_out.date == NULL) {
+        len += 1 + ngx_http_v2_literal_size("Wed, 31 Dec 1986 18:00:00 GMT");
+    }
+
+    if (r->headers_out.content_type.len) {
+        len += 1 + NGX_HTTP_V2_INT_OCTETS + r->headers_out.content_type.len;
+
+        if (r->headers_out.content_type_len == r->headers_out.content_type.len
+            && r->headers_out.charset.len)
+        {
+            len += sizeof("; charset=") - 1 + r->headers_out.charset.len;
+        }
+    }
+
+    if (r->headers_out.content_length == NULL
+        && r->headers_out.content_length_n >= 0)
+    {
+        len += 1 + ngx_http_v2_integer_octets(NGX_OFF_T_LEN) + NGX_OFF_T_LEN;
+    }
+
+    if (r->headers_out.last_modified == NULL
+        && r->headers_out.last_modified_time != -1)
+    {
+        len += 1 + ngx_http_v2_literal_size("Wed, 31 Dec 1986 18:00:00 GMT");
+    }
+
+    if (r->headers_out.location && r->headers_out.location->value.len) {
+
+        if (r->headers_out.location->value.data[0] == '/'
+            && clcf->absolute_redirect)
+        {
+            if (clcf->server_name_in_redirect) {
+                cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+                host = cscf->server_name;
+
+            } else if (r->headers_in.server.len) {
+                host = r->headers_in.server;
+
+            } else {
+                host.len = NGX_SOCKADDR_STRLEN;
+                host.data = addr;
+
+                if (ngx_connection_local_sockaddr(fc, &host, 0) != NGX_OK) {
+                    return NGX_ERROR;
+                }
+            }
+
+            port = ngx_inet_get_port(fc->local_sockaddr);
+
+            location.len = sizeof("https://") - 1 + host.len
+                           + r->headers_out.location->value.len;
+
+            if (clcf->port_in_redirect) {
+
+#if (NGX_HTTP_SSL)
+                if (fc->ssl)
+                    port = (port == 443) ? 0 : port;
+                else
+#endif
+                    port = (port == 80) ? 0 : port;
+
+            } else {
+                port = 0;
+            }
+
+            if (port) {
+                location.len += sizeof(":65535") - 1;
+            }
+
+            location.data = ngx_pnalloc(r->pool, location.len);
+            if (location.data == NULL) {
+                return NGX_ERROR;
+            }
+
+            p = ngx_cpymem(location.data, "http", sizeof("http") - 1);
+
+#if (NGX_HTTP_SSL)
+            if (fc->ssl) {
+                *p++ = 's';
+            }
+#endif
+
+            *p++ = ':'; *p++ = '/'; *p++ = '/';
+            p = ngx_cpymem(p, host.data, host.len);
+
+            if (port) {
+                p = ngx_sprintf(p, ":%ui", port);
+            }
+
+            p = ngx_cpymem(p, r->headers_out.location->value.data,
+                              r->headers_out.location->value.len);
+
+            /* update r->headers_out.location->value for possible logging */
+
+            r->headers_out.location->value.len = p - location.data;
+            r->headers_out.location->value.data = location.data;
+            ngx_str_set(&r->headers_out.location->key, "Location");
+        }
+
+        r->headers_out.location->hash = 0;
+
+        len += 1 + NGX_HTTP_V2_INT_OCTETS + r->headers_out.location->value.len;
+    }
+
+    tmp_len = len;
+
+#if (NGX_HTTP_GZIP)
+    if (r->gzip_vary) {
+        if (clcf->gzip_vary) {
+            len += 1 + sizeof(accept_encoding);
+
+        } else {
+            r->gzip_vary = 0;
+        }
+    }
+#endif
+
+    part = &r->headers_out.headers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (header[i].hash == 0) {
+            continue;
+        }
+
+        if (header[i].key.len > NGX_HTTP_V2_MAX_FIELD) {
+            ngx_log_error(NGX_LOG_CRIT, fc->log, 0,
+                          "too long response header name: \"%V\"",
+                          &header[i].key);
+            return NGX_ERROR;
+        }
+
+        if (header[i].value.len > NGX_HTTP_V2_MAX_FIELD) {
+            ngx_log_error(NGX_LOG_CRIT, fc->log, 0,
+                          "too long response header value: \"%V: %V\"",
+                          &header[i].key, &header[i].value);
+            return NGX_ERROR;
+        }
+
+        len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len
+                 + NGX_HTTP_V2_INT_OCTETS + header[i].value.len;
+
+        if (header[i].key.len > tmp_len) {
+            tmp_len = header[i].key.len;
+        }
+
+        if (header[i].value.len > tmp_len) {
+            tmp_len = header[i].value.len;
+        }
+    }
+
+    tmp = ngx_palloc(r->pool, tmp_len);
+    pos = ngx_pnalloc(r->pool, len);
+
+    if (pos == NULL || tmp == NULL) {
+        return NGX_ERROR;
+    }
+
+    start = pos;
+
+    if (h2c->table_update) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                       "http2 table size update: 0");
+        *pos++ = (1 << 5) | 0;
+        h2c->table_update = 0;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                   "http2 output header: \":status: %03ui\"",
+                   r->headers_out.status);
+
+    if (status) {
+        *pos++ = status;
+
+    } else {
+        *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX);
+        *pos++ = NGX_HTTP_V2_ENCODE_RAW | 3;
+        pos = ngx_sprintf(pos, "%03ui", r->headers_out.status);
+    }
+
+    if (r->headers_out.server == NULL) {
+
+        if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                           "http2 output header: \"server: %s\"",
+                           NGINX_VER);
+
+        } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                           "http2 output header: \"server: %s\"",
+                           NGINX_VER_BUILD);
+
+        } else {
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                           "http2 output header: \"server: nginx\"");
+        }
+
+        *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX);
+
+        if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
+            if (nginx_ver[0] == '\0') {
+                p = ngx_http_v2_write_value(nginx_ver, (u_char *) NGINX_VER,
+                                            sizeof(NGINX_VER) - 1, tmp);
+                nginx_ver_len = p - nginx_ver;
+            }
+
+            pos = ngx_cpymem(pos, nginx_ver, nginx_ver_len);
+
+        } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
+            if (nginx_ver_build[0] == '\0') {
+                p = ngx_http_v2_write_value(nginx_ver_build,
+                                            (u_char *) NGINX_VER_BUILD,
+                                            sizeof(NGINX_VER_BUILD) - 1, tmp);
+                nginx_ver_build_len = p - nginx_ver_build;
+            }
+
+            pos = ngx_cpymem(pos, nginx_ver_build, nginx_ver_build_len);
+
+        } else {
+            pos = ngx_cpymem(pos, nginx, sizeof(nginx));
+        }
+    }
+
+    if (r->headers_out.date == NULL) {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                       "http2 output header: \"date: %V\"",
+                       &ngx_cached_http_time);
+
+        *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_DATE_INDEX);
+        pos = ngx_http_v2_write_value(pos, ngx_cached_http_time.data,
+                                      ngx_cached_http_time.len, tmp);
+    }
+
+    if (r->headers_out.content_type.len) {
+        *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_TYPE_INDEX);
+
+        if (r->headers_out.content_type_len == r->headers_out.content_type.len
+            && r->headers_out.charset.len)
+        {
+            len = r->headers_out.content_type.len + sizeof("; charset=") - 1
+                  + r->headers_out.charset.len;
+
+            p = ngx_pnalloc(r->pool, len);
+            if (p == NULL) {
+                return NGX_ERROR;
+            }
+
+            p = ngx_cpymem(p, r->headers_out.content_type.data,
+                           r->headers_out.content_type.len);
+
+            p = ngx_cpymem(p, "; charset=", sizeof("; charset=") - 1);
+
+            p = ngx_cpymem(p, r->headers_out.charset.data,
+                           r->headers_out.charset.len);
+
+            /* updated r->headers_out.content_type is also needed for logging */
+
+            r->headers_out.content_type.len = len;
+            r->headers_out.content_type.data = p - len;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                       "http2 output header: \"content-type: %V\"",
+                       &r->headers_out.content_type);
+
+        pos = ngx_http_v2_write_value(pos, r->headers_out.content_type.data,
+                                      r->headers_out.content_type.len, tmp);
+    }
+
+    if (r->headers_out.content_length == NULL
+        && r->headers_out.content_length_n >= 0)
+    {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                       "http2 output header: \"content-length: %O\"",
+                       r->headers_out.content_length_n);
+
+        *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_LENGTH_INDEX);
+
+        p = pos;
+        pos = ngx_sprintf(pos + 1, "%O", r->headers_out.content_length_n);
+        *p = NGX_HTTP_V2_ENCODE_RAW | (u_char) (pos - p - 1);
+    }
+
+    if (r->headers_out.last_modified == NULL
+        && r->headers_out.last_modified_time != -1)
+    {
+        *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LAST_MODIFIED_INDEX);
+
+        ngx_http_time(pos, r->headers_out.last_modified_time);
+        len = sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                       "http2 output header: \"last-modified: %*s\"",
+                       len, pos);
+
+        /*
+         * Date will always be encoded using huffman in the temporary buffer,
+         * so it's safe here to use src and dst pointing to the same address.
+         */
+        pos = ngx_http_v2_write_value(pos, pos, len, tmp);
+    }
+
+    if (r->headers_out.location && r->headers_out.location->value.len) {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                       "http2 output header: \"location: %V\"",
+                       &r->headers_out.location->value);
+
+        *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LOCATION_INDEX);
+        pos = ngx_http_v2_write_value(pos, r->headers_out.location->value.data,
+                                      r->headers_out.location->value.len, tmp);
+    }
+
+#if (NGX_HTTP_GZIP)
+    if (r->gzip_vary) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                       "http2 output header: \"vary: Accept-Encoding\"");
+
+        *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_VARY_INDEX);
+        pos = ngx_cpymem(pos, accept_encoding, sizeof(accept_encoding));
+    }
+#endif
+
+    part = &r->headers_out.headers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (header[i].hash == 0) {
+            continue;
+        }
+
+#if (NGX_DEBUG)
+        if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) {
+            ngx_strlow(tmp, header[i].key.data, header[i].key.len);
+
+            ngx_log_debug3(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                           "http2 output header: \"%*s: %V\"",
+                           header[i].key.len, tmp, &header[i].value);
+        }
+#endif
+
+        *pos++ = 0;
+
+        pos = ngx_http_v2_write_name(pos, header[i].key.data,
+                                     header[i].key.len, tmp);
+
+        pos = ngx_http_v2_write_value(pos, header[i].value.data,
+                                      header[i].value.len, tmp);
+    }
+
+    fin = r->header_only
+          || (r->headers_out.content_length_n == 0 && !r->expect_trailers);
+
+    frame = ngx_http_v2_create_headers_frame(r, start, pos, fin);
+    if (frame == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_http_v2_queue_blocked_frame(h2c, frame);
+
+    stream->queued++;
+
+    cln = ngx_http_cleanup_add(r, 0);
+    if (cln == NULL) {
+        return NGX_ERROR;
+    }
+
+    cln->handler = ngx_http_v2_filter_cleanup;
+    cln->data = stream;
+
+    fc->send_chain = ngx_http_v2_send_chain;
+    fc->need_last_buf = 1;
+
+    return ngx_http_v2_filter_send(fc, stream);
+}
+
+
+static ngx_int_t
+ngx_http_v2_push_resources(ngx_http_request_t *r)
+{
+    u_char                     *start, *end, *last;
+    ngx_int_t                   rc;
+    ngx_str_t                   path;
+    ngx_uint_t                  i, push;
+    ngx_table_elt_t           **h;
+    ngx_http_v2_loc_conf_t     *h2lcf;
+    ngx_http_complex_value_t   *pushes;
+    ngx_str_t                   binary[NGX_HTTP_V2_PUSH_HEADERS];
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http2 push resources");
+
+    ngx_memzero(binary, NGX_HTTP_V2_PUSH_HEADERS * sizeof(ngx_str_t));
+
+    h2lcf = ngx_http_get_module_loc_conf(r, ngx_http_v2_module);
+
+    if (h2lcf->pushes) {
+        pushes = h2lcf->pushes->elts;
+
+        for (i = 0; i < h2lcf->pushes->nelts; i++) {
+
+            if (ngx_http_complex_value(r, &pushes[i], &path) != NGX_OK) {
+                return NGX_ERROR;
+            }
+
+            if (path.len == 0) {
+                continue;
+            }
+
+            if (path.len == 3 && ngx_strncmp(path.data, "off", 3) == 0) {
+                continue;
+            }
+
+            rc = ngx_http_v2_push_resource(r, &path, binary);
+
+            if (rc == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            if (rc == NGX_ABORT) {
+                return NGX_OK;
+            }
+
+            /* NGX_OK, NGX_DECLINED */
+        }
+    }
+
+    if (!h2lcf->push_preload) {
+        return NGX_OK;
+    }
+
+    h = r->headers_out.link.elts;
+
+    for (i = 0; i < r->headers_out.link.nelts; i++) {
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http2 parse link: \"%V\"", &h[i]->value);
+
+        start = h[i]->value.data;
+        end = h[i]->value.data + h[i]->value.len;
+
+    next_link:
+
+        while (start < end && *start == ' ') { start++; }
+
+        if (start == end || *start++ != '<') {
+            continue;
+        }
+
+        while (start < end && *start == ' ') { start++; }
+
+        for (last = start; last < end && *last != '>'; last++) {
+            /* void */
+        }
+
+        if (last == start || last == end) {
+            continue;
+        }
+
+        path.len = last - start;
+        path.data = start;
+
+        start = last + 1;
+
+        while (start < end && *start == ' ') { start++; }
+
+        if (start == end) {
+            continue;
+        }
+
+        if (*start == ',') {
+            start++;
+            goto next_link;
+        }
+
+        if (*start++ != ';') {
+            continue;
+        }
+
+        last = ngx_strlchr(start, end, ',');
+
+        if (last == NULL) {
+            last = end;
+        }
+
+        push = 0;
+
+        for ( ;; ) {
+
+            while (start < last && *start == ' ') { start++; }
+
+            if (last - start >= 6
+                && ngx_strncasecmp(start, (u_char *) "nopush", 6) == 0)
+            {
+                start += 6;
+
+                if (start == last || *start == ' ' || *start == ';') {
+                    push = 0;
+                    break;
+                }
+
+                goto next_param;
+            }
+
+            if (last - start >= 11
+                && ngx_strncasecmp(start, (u_char *) "rel=preload", 11) == 0)
+            {
+                start += 11;
+
+                if (start == last || *start == ' ' || *start == ';') {
+                    push = 1;
+                }
+
+                goto next_param;
+            }
+
+            if (last - start >= 4
+                && ngx_strncasecmp(start, (u_char *) "rel=", 4) == 0)
+            {
+                start += 4;
+
+                while (start < last && *start == ' ') { start++; }
+
+                if (start == last || *start++ != '"') {
+                    goto next_param;
+                }
+
+                for ( ;; ) {
+
+                    while (start < last && *start == ' ') { start++; }
+
+                    if (last - start >= 7
+                        && ngx_strncasecmp(start, (u_char *) "preload", 7) == 0)
+                    {
+                        start += 7;
+
+                        if (start < last && (*start == ' ' || *start == '"')) {
+                            push = 1;
+                            break;
+                        }
+                    }
+
+                    while (start < last && *start != ' ' && *start != '"') {
+                        start++;
+                    }
+
+                    if (start == last) {
+                        break;
+                    }
+
+                    if (*start == '"') {
+                        break;
+                    }
+
+                    start++;
+                }
+            }
+
+        next_param:
+
+            start = ngx_strlchr(start, last, ';');
+
+            if (start == NULL) {
+                break;
+            }
+
+            start++;
+        }
+
+        if (push) {
+            while (path.len && path.data[path.len - 1] == ' ') {
+                path.len--;
+            }
+        }
+
+        if (push && path.len
+            && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/'))
+        {
+            rc = ngx_http_v2_push_resource(r, &path, binary);
+
+            if (rc == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            if (rc == NGX_ABORT) {
+                return NGX_OK;
+            }
+
+            /* NGX_OK, NGX_DECLINED */
+        }
+
+        if (last < end) {
+            start = last + 1;
+            goto next_link;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path,
+    ngx_str_t *binary)
+{
+    u_char                      *start, *pos, *tmp;
+    size_t                       len;
+    ngx_str_t                   *value;
+    ngx_uint_t                   i;
+    ngx_table_elt_t            **h;
+    ngx_connection_t            *fc;
+    ngx_http_v2_stream_t        *stream;
+    ngx_http_v2_out_frame_t     *frame;
+    ngx_http_v2_connection_t    *h2c;
+    ngx_http_v2_push_header_t   *ph;
+
+    fc = r->connection;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push resource");
+
+    stream = r->stream;
+    h2c = stream->connection;
+
+    if (!ngx_path_separator(path->data[0])) {
+        ngx_log_error(NGX_LOG_WARN, fc->log, 0,
+                      "non-absolute path \"%V\" not pushed", path);
+        return NGX_DECLINED;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 pushing:%ui limit:%ui",
+                   h2c->pushing, h2c->concurrent_pushes);
+
+    if (h2c->pushing >= h2c->concurrent_pushes) {
+        return NGX_ABORT;
+    }
+
+    if (h2c->last_push == 0x7ffffffe) {
+        return NGX_ABORT;
+    }
+
+    if (path->len > NGX_HTTP_V2_MAX_FIELD) {
+        return NGX_DECLINED;
+    }
+
+    if (r->headers_in.host == NULL) {
+        return NGX_ABORT;
+    }
+
+    ph = ngx_http_v2_push_headers;
+
+    if (binary[0].len) {
+        tmp = ngx_palloc(r->pool, path->len);
+        if (tmp == NULL) {
+            return NGX_ERROR;
+        }
+
+    } else {
+        len = path->len;
+
+        for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) {
+            h = (ngx_table_elt_t **) ((char *) &r->headers_in + ph[i].offset);
+
+            if (*h) {
+                len = ngx_max(len, (*h)->value.len);
+            }
+        }
+
+        tmp = ngx_palloc(r->pool, len);
+        if (tmp == NULL) {
+            return NGX_ERROR;
+        }
+
+        for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) {
+            h = (ngx_table_elt_t **) ((char *) &r->headers_in + ph[i].offset);
+
+            if (*h == NULL) {
+                continue;
+            }
+
+            value = &(*h)->value;
+
+            len = 1 + NGX_HTTP_V2_INT_OCTETS + value->len;
+
+            pos = ngx_pnalloc(r->pool, len);
+            if (pos == NULL) {
+                return NGX_ERROR;
+            }
+
+            binary[i].data = pos;
+
+            *pos++ = ngx_http_v2_inc_indexed(ph[i].index);
+            pos = ngx_http_v2_write_value(pos, value->data, value->len, tmp);
+
+            binary[i].len = pos - binary[i].data;
+        }
+    }
+
+    len = (h2c->table_update ? 1 : 0)
+          + 1
+          + 1 + NGX_HTTP_V2_INT_OCTETS + path->len
+          + 1;
+
+    for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) {
+        len += binary[i].len;
+    }
+
+    pos = ngx_pnalloc(r->pool, len);
+    if (pos == NULL) {
+        return NGX_ERROR;
+    }
+
+    start = pos;
+
+    if (h2c->table_update) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                       "http2 table size update: 0");
+        *pos++ = (1 << 5) | 0;
+        h2c->table_update = 0;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                   "http2 push header: \":method: GET\"");
+
+    *pos++ = ngx_http_v2_indexed(NGX_HTTP_V2_METHOD_GET_INDEX);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                   "http2 push header: \":path: %V\"", path);
+
+    *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX);
+    pos = ngx_http_v2_write_value(pos, path->data, path->len, tmp);
+
+#if (NGX_HTTP_SSL)
+    if (fc->ssl) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                       "http2 push header: \":scheme: https\"");
+        *pos++ = ngx_http_v2_indexed(NGX_HTTP_V2_SCHEME_HTTPS_INDEX);
+
+    } else
+#endif
+    {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                       "http2 push header: \":scheme: http\"");
+        *pos++ = ngx_http_v2_indexed(NGX_HTTP_V2_SCHEME_HTTP_INDEX);
+    }
+
+    for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) {
+        h = (ngx_table_elt_t **) ((char *) &r->headers_in + ph[i].offset);
+
+        if (*h == NULL) {
+            continue;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                       "http2 push header: \"%V: %V\"",
+                       &ph[i].name, &(*h)->value);
+
+        pos = ngx_cpymem(pos, binary[i].data, binary[i].len);
+    }
+
+    frame = ngx_http_v2_create_push_frame(r, start, pos);
+    if (frame == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_http_v2_queue_blocked_frame(h2c, frame);
+
+    stream->queued++;
+
+    stream = ngx_http_v2_push_stream(stream, path);
+
+    if (stream) {
+        stream->request->request_length = pos - start;
+        return NGX_OK;
+    }
+
+    return NGX_ERROR;
+}
+
+
+static ngx_http_v2_out_frame_t *
+ngx_http_v2_create_headers_frame(ngx_http_request_t *r, u_char *pos,
+    u_char *end, ngx_uint_t fin)
+{
+    u_char                    type, flags;
+    size_t                    rest, frame_size;
+    ngx_buf_t                *b;
+    ngx_chain_t              *cl, **ll;
+    ngx_http_v2_stream_t     *stream;
+    ngx_http_v2_out_frame_t  *frame;
+
+    stream = r->stream;
+    rest = end - pos;
+
+    frame = ngx_palloc(r->pool, sizeof(ngx_http_v2_out_frame_t));
+    if (frame == NULL) {
+        return NULL;
+    }
+
+    frame->handler = ngx_http_v2_headers_frame_handler;
+    frame->stream = stream;
+    frame->length = rest;
+    frame->blocked = 1;
+    frame->fin = fin;
+
+    ll = &frame->first;
+
+    type = NGX_HTTP_V2_HEADERS_FRAME;
+    flags = fin ? NGX_HTTP_V2_END_STREAM_FLAG : NGX_HTTP_V2_NO_FLAG;
+    frame_size = stream->connection->frame_size;
+
+    for ( ;; ) {
+        if (rest <= frame_size) {
+            frame_size = rest;
+            flags |= NGX_HTTP_V2_END_HEADERS_FLAG;
+        }
+
+        b = ngx_create_temp_buf(r->pool, NGX_HTTP_V2_FRAME_HEADER_SIZE);
+        if (b == NULL) {
+            return NULL;
+        }
+
+        b->last = ngx_http_v2_write_len_and_type(b->last, frame_size, type);
+        *b->last++ = flags;
+        b->last = ngx_http_v2_write_sid(b->last, stream->node->id);
+
+        b->tag = (ngx_buf_tag_t) &ngx_http_v2_module;
+
+        cl = ngx_alloc_chain_link(r->pool);
+        if (cl == NULL) {
+            return NULL;
+        }
+
+        cl->buf = b;
+
+        *ll = cl;
+        ll = &cl->next;
+
+        b = ngx_calloc_buf(r->pool);
+        if (b == NULL) {
+            return NULL;
+        }
+
+        b->pos = pos;
+
+        pos += frame_size;
+
+        b->last = pos;
+        b->start = b->pos;
+        b->end = b->last;
+        b->temporary = 1;
+
+        cl = ngx_alloc_chain_link(r->pool);
+        if (cl == NULL) {
+            return NULL;
+        }
+
+        cl->buf = b;
+
+        *ll = cl;
+        ll = &cl->next;
+
+        rest -= frame_size;
+
+        if (rest) {
+            frame->length += NGX_HTTP_V2_FRAME_HEADER_SIZE;
+
+            type = NGX_HTTP_V2_CONTINUATION_FRAME;
+            flags = NGX_HTTP_V2_NO_FLAG;
+            continue;
+        }
+
+        b->last_buf = fin;
+        cl->next = NULL;
+        frame->last = cl;
+
+        ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http2:%ui create HEADERS frame %p: len:%uz fin:%ui",
+                       stream->node->id, frame, frame->length, fin);
+
+        return frame;
+    }
+}
+
+
+static ngx_http_v2_out_frame_t *
+ngx_http_v2_create_push_frame(ngx_http_request_t *r, u_char *pos, u_char *end)
+{
+    u_char                     type, flags;
+    size_t                     rest, frame_size, len;
+    ngx_buf_t                 *b;
+    ngx_chain_t               *cl, **ll;
+    ngx_http_v2_stream_t      *stream;
+    ngx_http_v2_out_frame_t   *frame;
+    ngx_http_v2_connection_t  *h2c;
+
+    stream = r->stream;
+    h2c = stream->connection;
+    rest = NGX_HTTP_V2_STREAM_ID_SIZE + (end - pos);
+
+    frame = ngx_palloc(r->pool, sizeof(ngx_http_v2_out_frame_t));
+    if (frame == NULL) {
+        return NULL;
+    }
+
+    frame->handler = ngx_http_v2_push_frame_handler;
+    frame->stream = stream;
+    frame->length = rest;
+    frame->blocked = 1;
+    frame->fin = 0;
+
+    ll = &frame->first;
+
+    type = NGX_HTTP_V2_PUSH_PROMISE_FRAME;
+    flags = NGX_HTTP_V2_NO_FLAG;
+    frame_size = h2c->frame_size;
+
+    for ( ;; ) {
+        if (rest <= frame_size) {
+            frame_size = rest;
+            flags |= NGX_HTTP_V2_END_HEADERS_FLAG;
+        }
+
+        b = ngx_create_temp_buf(r->pool,
+                                NGX_HTTP_V2_FRAME_HEADER_SIZE
+                                + ((type == NGX_HTTP_V2_PUSH_PROMISE_FRAME)
+                                   ? NGX_HTTP_V2_STREAM_ID_SIZE : 0));
+        if (b == NULL) {
+            return NULL;
+        }
+
+        b->last = ngx_http_v2_write_len_and_type(b->last, frame_size, type);
+        *b->last++ = flags;
+        b->last = ngx_http_v2_write_sid(b->last, stream->node->id);
+
+        b->tag = (ngx_buf_tag_t) &ngx_http_v2_module;
+
+        if (type == NGX_HTTP_V2_PUSH_PROMISE_FRAME) {
+            h2c->last_push += 2;
+
+            b->last = ngx_http_v2_write_sid(b->last, h2c->last_push);
+            len = frame_size - NGX_HTTP_V2_STREAM_ID_SIZE;
+
+        } else {
+            len = frame_size;
+        }
+
+        cl = ngx_alloc_chain_link(r->pool);
+        if (cl == NULL) {
+            return NULL;
+        }
+
+        cl->buf = b;
+
+        *ll = cl;
+        ll = &cl->next;
+
+        b = ngx_calloc_buf(r->pool);
+        if (b == NULL) {
+            return NULL;
+        }
+
+        b->pos = pos;
+
+        pos += len;
+
+        b->last = pos;
+        b->start = b->pos;
+        b->end = b->last;
+        b->temporary = 1;
+
+        cl = ngx_alloc_chain_link(r->pool);
+        if (cl == NULL) {
+            return NULL;
+        }
+
+        cl->buf = b;
+
+        *ll = cl;
+        ll = &cl->next;
+
+        rest -= frame_size;
+
+        if (rest) {
+            frame->length += NGX_HTTP_V2_FRAME_HEADER_SIZE;
+
+            type = NGX_HTTP_V2_CONTINUATION_FRAME;
+            continue;
+        }
+
+        cl->next = NULL;
+        frame->last = cl;
+
+        ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http2:%ui create PUSH_PROMISE frame %p: "
+                       "sid:%ui len:%uz",
+                       stream->node->id, frame, h2c->last_push,
+                       frame->length);
+
+        return frame;
+    }
+}
+
+
+static ngx_http_v2_out_frame_t *
+ngx_http_v2_create_trailers_frame(ngx_http_request_t *r)
+{
+    u_char            *pos, *start, *tmp;
+    size_t             len, tmp_len;
+    ngx_uint_t         i;
+    ngx_list_part_t   *part;
+    ngx_table_elt_t   *header;
+    ngx_connection_t  *fc;
+
+    fc = r->connection;
+    len = 0;
+    tmp_len = 0;
+
+    part = &r->headers_out.trailers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (header[i].hash == 0) {
+            continue;
+        }
+
+        if (header[i].key.len > NGX_HTTP_V2_MAX_FIELD) {
+            ngx_log_error(NGX_LOG_CRIT, fc->log, 0,
+                          "too long response trailer name: \"%V\"",
+                          &header[i].key);
+            return NULL;
+        }
+
+        if (header[i].value.len > NGX_HTTP_V2_MAX_FIELD) {
+            ngx_log_error(NGX_LOG_CRIT, fc->log, 0,
+                          "too long response trailer value: \"%V: %V\"",
+                          &header[i].key, &header[i].value);
+            return NULL;
+        }
+
+        len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len
+                 + NGX_HTTP_V2_INT_OCTETS + header[i].value.len;
+
+        if (header[i].key.len > tmp_len) {
+            tmp_len = header[i].key.len;
+        }
+
+        if (header[i].value.len > tmp_len) {
+            tmp_len = header[i].value.len;
+        }
+    }
+
+    if (len == 0) {
+        return NGX_HTTP_V2_NO_TRAILERS;
+    }
+
+    tmp = ngx_palloc(r->pool, tmp_len);
+    pos = ngx_pnalloc(r->pool, len);
+
+    if (pos == NULL || tmp == NULL) {
+        return NULL;
+    }
+
+    start = pos;
+
+    part = &r->headers_out.trailers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (header[i].hash == 0) {
+            continue;
+        }
+
+#if (NGX_DEBUG)
+        if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) {
+            ngx_strlow(tmp, header[i].key.data, header[i].key.len);
+
+            ngx_log_debug3(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                           "http2 output trailer: \"%*s: %V\"",
+                           header[i].key.len, tmp, &header[i].value);
+        }
+#endif
+
+        *pos++ = 0;
+
+        pos = ngx_http_v2_write_name(pos, header[i].key.data,
+                                     header[i].key.len, tmp);
+
+        pos = ngx_http_v2_write_value(pos, header[i].value.data,
+                                      header[i].value.len, tmp);
+    }
+
+    return ngx_http_v2_create_headers_frame(r, start, pos, 1);
+}
+
+
+static ngx_chain_t *
+ngx_http_v2_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit)
+{
+    off_t                      size, offset;
+    size_t                     rest, frame_size;
+    ngx_chain_t               *cl, *out, **ln;
+    ngx_http_request_t        *r;
+    ngx_http_v2_stream_t      *stream;
+    ngx_http_v2_loc_conf_t    *h2lcf;
+    ngx_http_v2_out_frame_t   *frame, *trailers;
+    ngx_http_v2_connection_t  *h2c;
+
+    r = fc->data;
+    stream = r->stream;
+
+#if (NGX_SUPPRESS_WARN)
+    size = 0;
+#endif
+
+    while (in) {
+        size = ngx_buf_size(in->buf);
+
+        if (size || in->buf->last_buf) {
+            break;
+        }
+
+        in = in->next;
+    }
+
+    if (in == NULL || stream->out_closed) {
+
+        if (stream->queued) {
+            fc->write->active = 1;
+            fc->write->ready = 0;
+
+        } else {
+            fc->buffered &= ~NGX_HTTP_V2_BUFFERED;
+        }
+
+        return NULL;
+    }
+
+    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 (in->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) {
+        cl = ngx_alloc_chain_link(r->pool);
+        if (cl == NULL) {
+            return NGX_CHAIN_ERROR;
+        }
+
+        cl->buf = in->buf;
+        in->buf = cl->buf->shadow;
+
+        offset = ngx_buf_in_memory(in->buf)
+                 ? (cl->buf->pos - in->buf->pos)
+                 : (cl->buf->file_pos - in->buf->file_pos);
+
+        cl->next = stream->free_bufs;
+        stream->free_bufs = cl;
+
+    } else {
+        offset = 0;
+    }
+
+    if (limit == 0 || limit > (off_t) h2c->send_window) {
+        limit = h2c->send_window;
+    }
+
+    if (limit > stream->send_window) {
+        limit = (stream->send_window > 0) ? stream->send_window : 0;
+    }
+
+    h2lcf = ngx_http_get_module_loc_conf(r, ngx_http_v2_module);
+
+    frame_size = (h2lcf->chunk_size < h2c->frame_size)
+                 ? h2lcf->chunk_size : h2c->frame_size;
+
+    trailers = NGX_HTTP_V2_NO_TRAILERS;
+
+#if (NGX_SUPPRESS_WARN)
+    cl = NULL;
+#endif
+
+    for ( ;; ) {
+        if ((off_t) frame_size > limit) {
+            frame_size = (size_t) limit;
+        }
+
+        ln = &out;
+        rest = frame_size;
+
+        while ((off_t) rest >= size) {
+
+            if (offset) {
+                cl = ngx_http_v2_filter_get_shadow(stream, in->buf,
+                                                   offset, size);
+                if (cl == NULL) {
+                    return NGX_CHAIN_ERROR;
+                }
+
+                offset = 0;
+
+            } else {
+                cl = ngx_alloc_chain_link(r->pool);
+                if (cl == NULL) {
+                    return NGX_CHAIN_ERROR;
+                }
+
+                cl->buf = in->buf;
+            }
+
+            *ln = cl;
+            ln = &cl->next;
+
+            rest -= (size_t) size;
+            in = in->next;
+
+            if (in == NULL) {
+                frame_size -= rest;
+                rest = 0;
+                break;
+            }
+
+            size = ngx_buf_size(in->buf);
+        }
+
+        if (rest) {
+            cl = ngx_http_v2_filter_get_shadow(stream, in->buf, offset, rest);
+            if (cl == NULL) {
+                return NGX_CHAIN_ERROR;
+            }
+
+            cl->buf->flush = 0;
+            cl->buf->last_buf = 0;
+
+            *ln = cl;
+
+            offset += rest;
+            size -= rest;
+        }
+
+        if (cl->buf->last_buf) {
+            trailers = ngx_http_v2_create_trailers_frame(r);
+            if (trailers == NULL) {
+                return NGX_CHAIN_ERROR;
+            }
+
+            if (trailers != NGX_HTTP_V2_NO_TRAILERS) {
+                cl->buf->last_buf = 0;
+            }
+        }
+
+        if (frame_size || cl->buf->last_buf) {
+            frame = ngx_http_v2_filter_get_data_frame(stream, frame_size,
+                                                      out, cl);
+            if (frame == NULL) {
+                return NGX_CHAIN_ERROR;
+            }
+
+            ngx_http_v2_queue_frame(h2c, frame);
+
+            h2c->send_window -= frame_size;
+
+            stream->send_window -= frame_size;
+            stream->queued++;
+        }
+
+        if (in == NULL) {
+
+            if (trailers != NGX_HTTP_V2_NO_TRAILERS) {
+                ngx_http_v2_queue_frame(h2c, trailers);
+                stream->queued++;
+            }
+
+            break;
+        }
+
+        limit -= frame_size;
+
+        if (limit == 0) {
+            break;
+        }
+    }
+
+    if (offset) {
+        cl = ngx_http_v2_filter_get_shadow(stream, in->buf, offset, size);
+        if (cl == NULL) {
+            return NGX_CHAIN_ERROR;
+        }
+
+        in->buf = cl->buf;
+        ngx_free_chain(r->pool, cl);
+    }
+
+    if (ngx_http_v2_filter_send(fc, stream) == NGX_ERROR) {
+        return NGX_CHAIN_ERROR;
+    }
+
+    if (in && ngx_http_v2_flow_control(h2c, stream) == NGX_DECLINED) {
+        fc->write->active = 1;
+        fc->write->ready = 0;
+    }
+
+    return in;
+}
+
+
+static ngx_chain_t *
+ngx_http_v2_filter_get_shadow(ngx_http_v2_stream_t *stream, ngx_buf_t *buf,
+    off_t offset, off_t size)
+{
+    ngx_buf_t    *chunk;
+    ngx_chain_t  *cl;
+
+    cl = ngx_chain_get_free_buf(stream->request->pool, &stream->free_bufs);
+    if (cl == NULL) {
+        return NULL;
+    }
+
+    chunk = cl->buf;
+
+    ngx_memcpy(chunk, buf, sizeof(ngx_buf_t));
+
+    chunk->tag = (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow;
+    chunk->shadow = buf;
+
+    if (ngx_buf_in_memory(chunk)) {
+        chunk->pos += offset;
+        chunk->last = chunk->pos + size;
+    }
+
+    if (chunk->in_file) {
+        chunk->file_pos += offset;
+        chunk->file_last = chunk->file_pos + size;
+    }
+
+    return cl;
+}
+
+
+static ngx_http_v2_out_frame_t *
+ngx_http_v2_filter_get_data_frame(ngx_http_v2_stream_t *stream,
+    size_t len, ngx_chain_t *first, ngx_chain_t *last)
+{
+    u_char                    flags;
+    ngx_buf_t                *buf;
+    ngx_chain_t              *cl;
+    ngx_http_v2_out_frame_t  *frame;
+
+    frame = stream->free_frames;
+
+    if (frame) {
+        stream->free_frames = frame->next;
+
+    } else {
+        frame = ngx_palloc(stream->request->pool,
+                           sizeof(ngx_http_v2_out_frame_t));
+        if (frame == NULL) {
+            return NULL;
+        }
+    }
+
+    flags = last->buf->last_buf ? NGX_HTTP_V2_END_STREAM_FLAG : 0;
+
+    ngx_log_debug4(NGX_LOG_DEBUG_HTTP, stream->request->connection->log, 0,
+                   "http2:%ui create DATA frame %p: len:%uz flags:%ui",
+                   stream->node->id, frame, len, (ngx_uint_t) flags);
+
+    cl = ngx_chain_get_free_buf(stream->request->pool,
+                                &stream->free_frame_headers);
+    if (cl == NULL) {
+        return NULL;
+    }
+
+    buf = cl->buf;
+
+    if (buf->start == NULL) {
+        buf->start = ngx_palloc(stream->request->pool,
+                                NGX_HTTP_V2_FRAME_HEADER_SIZE);
+        if (buf->start == NULL) {
+            return NULL;
+        }
+
+        buf->end = buf->start + NGX_HTTP_V2_FRAME_HEADER_SIZE;
+        buf->last = buf->end;
+
+        buf->tag = (ngx_buf_tag_t) &ngx_http_v2_module;
+        buf->memory = 1;
+    }
+
+    buf->pos = buf->start;
+    buf->last = buf->pos;
+
+    buf->last = ngx_http_v2_write_len_and_type(buf->last, len,
+                                               NGX_HTTP_V2_DATA_FRAME);
+    *buf->last++ = flags;
+
+    buf->last = ngx_http_v2_write_sid(buf->last, stream->node->id);
+
+    cl->next = first;
+    first = cl;
+
+    last->buf->flush = 1;
+
+    frame->first = first;
+    frame->last = last;
+    frame->handler = ngx_http_v2_data_frame_handler;
+    frame->stream = stream;
+    frame->length = len;
+    frame->blocked = 0;
+    frame->fin = last->buf->last_buf;
+
+    return frame;
+}
+
+
+static ngx_inline ngx_int_t
+ngx_http_v2_flow_control(ngx_http_v2_connection_t *h2c,
+    ngx_http_v2_stream_t *stream)
+{
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2:%ui windows: conn:%uz stream:%z",
+                   stream->node->id, h2c->send_window, stream->send_window);
+
+    if (stream->send_window <= 0) {
+        stream->exhausted = 1;
+        return NGX_DECLINED;
+    }
+
+    if (h2c->send_window == 0) {
+        ngx_http_v2_waiting_queue(h2c, stream);
+        return NGX_DECLINED;
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_v2_waiting_queue(ngx_http_v2_connection_t *h2c,
+    ngx_http_v2_stream_t *stream)
+{
+    ngx_queue_t           *q;
+    ngx_http_v2_stream_t  *s;
+
+    if (stream->waiting) {
+        return;
+    }
+
+    stream->waiting = 1;
+
+    for (q = ngx_queue_last(&h2c->waiting);
+         q != ngx_queue_sentinel(&h2c->waiting);
+         q = ngx_queue_prev(q))
+    {
+        s = ngx_queue_data(q, ngx_http_v2_stream_t, queue);
+
+        if (s->node->rank < stream->node->rank
+            || (s->node->rank == stream->node->rank
+                && s->node->rel_weight >= stream->node->rel_weight))
+        {
+            break;
+        }
+    }
+
+    ngx_queue_insert_after(q, &stream->queue);
+}
+
+
+static ngx_inline ngx_int_t
+ngx_http_v2_filter_send(ngx_connection_t *fc, ngx_http_v2_stream_t *stream)
+{
+    stream->blocked = 1;
+
+    if (ngx_http_v2_send_output_queue(stream->connection) == NGX_ERROR) {
+        fc->error = 1;
+        return NGX_ERROR;
+    }
+
+    stream->blocked = 0;
+
+    if (stream->queued) {
+        fc->buffered |= NGX_HTTP_V2_BUFFERED;
+        fc->write->active = 1;
+        fc->write->ready = 0;
+        return NGX_AGAIN;
+    }
+
+    fc->buffered &= ~NGX_HTTP_V2_BUFFERED;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_headers_frame_handler(ngx_http_v2_connection_t *h2c,
+    ngx_http_v2_out_frame_t *frame)
+{
+    ngx_chain_t           *cl, *ln;
+    ngx_http_v2_stream_t  *stream;
+
+    stream = frame->stream;
+    cl = frame->first;
+
+    for ( ;; ) {
+        if (cl->buf->pos != cl->buf->last) {
+            frame->first = cl;
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                           "http2:%ui HEADERS frame %p was sent partially",
+                           stream->node->id, frame);
+
+            return NGX_AGAIN;
+        }
+
+        ln = cl->next;
+
+        if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_module) {
+            cl->next = stream->free_frame_headers;
+            stream->free_frame_headers = cl;
+
+        } else {
+            cl->next = stream->free_bufs;
+            stream->free_bufs = cl;
+        }
+
+        if (cl == frame->last) {
+            break;
+        }
+
+        cl = ln;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2:%ui HEADERS frame %p was sent",
+                   stream->node->id, frame);
+
+    stream->request->header_size += NGX_HTTP_V2_FRAME_HEADER_SIZE
+                                    + frame->length;
+
+    ngx_http_v2_handle_frame(stream, frame);
+
+    ngx_http_v2_handle_stream(h2c, stream);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_push_frame_handler(ngx_http_v2_connection_t *h2c,
+    ngx_http_v2_out_frame_t *frame)
+{
+    ngx_chain_t           *cl, *ln;
+    ngx_http_v2_stream_t  *stream;
+
+    stream = frame->stream;
+    cl = frame->first;
+
+    for ( ;; ) {
+        if (cl->buf->pos != cl->buf->last) {
+            frame->first = cl;
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                           "http2:%ui PUSH_PROMISE frame %p was sent partially",
+                           stream->node->id, frame);
+
+            return NGX_AGAIN;
+        }
+
+        ln = cl->next;
+
+        if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_module) {
+            cl->next = stream->free_frame_headers;
+            stream->free_frame_headers = cl;
+
+        } else {
+            cl->next = stream->free_bufs;
+            stream->free_bufs = cl;
+        }
+
+        if (cl == frame->last) {
+            break;
+        }
+
+        cl = ln;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2:%ui PUSH_PROMISE frame %p was sent",
+                   stream->node->id, frame);
+
+    stream->request->header_size += NGX_HTTP_V2_FRAME_HEADER_SIZE
+                                    + frame->length;
+
+    ngx_http_v2_handle_frame(stream, frame);
+
+    ngx_http_v2_handle_stream(h2c, stream);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_data_frame_handler(ngx_http_v2_connection_t *h2c,
+    ngx_http_v2_out_frame_t *frame)
+{
+    ngx_buf_t             *buf;
+    ngx_chain_t           *cl, *ln;
+    ngx_http_v2_stream_t  *stream;
+
+    stream = frame->stream;
+    cl = frame->first;
+
+    if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_module) {
+
+        if (cl->buf->pos != cl->buf->last) {
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                           "http2:%ui DATA frame %p was sent partially",
+                           stream->node->id, frame);
+
+            return NGX_AGAIN;
+        }
+
+        ln = cl->next;
+
+        cl->next = stream->free_frame_headers;
+        stream->free_frame_headers = cl;
+
+        if (cl == frame->last) {
+            goto done;
+        }
+
+        cl = ln;
+    }
+
+    for ( ;; ) {
+        if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) {
+            buf = cl->buf->shadow;
+
+            if (ngx_buf_in_memory(buf)) {
+                buf->pos = cl->buf->pos;
+            }
+
+            if (buf->in_file) {
+                buf->file_pos = cl->buf->file_pos;
+            }
+        }
+
+        if (ngx_buf_size(cl->buf) != 0) {
+
+            if (cl != frame->first) {
+                frame->first = cl;
+                ngx_http_v2_handle_stream(h2c, stream);
+            }
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                           "http2:%ui DATA frame %p was sent partially",
+                           stream->node->id, frame);
+
+            return NGX_AGAIN;
+        }
+
+        ln = cl->next;
+
+        if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) {
+            cl->next = stream->free_bufs;
+            stream->free_bufs = cl;
+
+        } else {
+            ngx_free_chain(stream->request->pool, cl);
+        }
+
+        if (cl == frame->last) {
+            goto done;
+        }
+
+        cl = ln;
+    }
+
+done:
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2:%ui DATA frame %p was sent",
+                   stream->node->id, frame);
+
+    stream->request->header_size += NGX_HTTP_V2_FRAME_HEADER_SIZE;
+
+    ngx_http_v2_handle_frame(stream, frame);
+
+    ngx_http_v2_handle_stream(h2c, stream);
+
+    return NGX_OK;
+}
+
+
+static ngx_inline void
+ngx_http_v2_handle_frame(ngx_http_v2_stream_t *stream,
+    ngx_http_v2_out_frame_t *frame)
+{
+    ngx_http_request_t  *r;
+
+    r = stream->request;
+
+    r->connection->sent += NGX_HTTP_V2_FRAME_HEADER_SIZE + frame->length;
+
+    if (frame->fin) {
+        stream->out_closed = 1;
+    }
+
+    frame->next = stream->free_frames;
+    stream->free_frames = frame;
+
+    stream->queued--;
+}
+
+
+static ngx_inline void
+ngx_http_v2_handle_stream(ngx_http_v2_connection_t *h2c,
+    ngx_http_v2_stream_t *stream)
+{
+    ngx_event_t       *wev;
+    ngx_connection_t  *fc;
+
+    if (stream->waiting || stream->blocked) {
+        return;
+    }
+
+    fc = stream->request->connection;
+
+    if (!fc->error && stream->exhausted) {
+        return;
+    }
+
+    wev = fc->write;
+
+    wev->active = 0;
+    wev->ready = 1;
+
+    if (!fc->error && wev->delayed) {
+        return;
+    }
+
+    ngx_post_event(wev, &ngx_posted_events);
+}
+
+
+static void
+ngx_http_v2_filter_cleanup(void *data)
+{
+    ngx_http_v2_stream_t *stream = data;
+
+    size_t                     window;
+    ngx_event_t               *wev;
+    ngx_queue_t               *q;
+    ngx_http_v2_out_frame_t   *frame, **fn;
+    ngx_http_v2_connection_t  *h2c;
+
+    if (stream->waiting) {
+        stream->waiting = 0;
+        ngx_queue_remove(&stream->queue);
+    }
+
+    if (stream->queued == 0) {
+        return;
+    }
+
+    window = 0;
+    h2c = stream->connection;
+    fn = &h2c->last_out;
+
+    for ( ;; ) {
+        frame = *fn;
+
+        if (frame == NULL) {
+            break;
+        }
+
+        if (frame->stream == stream && !frame->blocked) {
+            *fn = frame->next;
+
+            window += frame->length;
+
+            if (--stream->queued == 0) {
+                break;
+            }
+
+            continue;
+        }
+
+        fn = &frame->next;
+    }
+
+    if (h2c->send_window == 0 && window) {
+
+        while (!ngx_queue_empty(&h2c->waiting)) {
+            q = ngx_queue_head(&h2c->waiting);
+
+            ngx_queue_remove(q);
+
+            stream = ngx_queue_data(q, ngx_http_v2_stream_t, queue);
+
+            stream->waiting = 0;
+
+            wev = stream->request->connection->write;
+
+            wev->active = 0;
+            wev->ready = 1;
+
+            if (!wev->delayed) {
+                ngx_post_event(wev, &ngx_posted_events);
+            }
+        }
+    }
+
+    h2c->send_window += window;
+}
+
+
+static ngx_int_t
+ngx_http_v2_filter_init(ngx_conf_t *cf)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_v2_header_filter;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/v2/ngx_http_v2_huff_decode.c b/nginx/src/http/v2/ngx_http_v2_huff_decode.c
new file mode 100644 (file)
index 0000000..49ca576
--- /dev/null
@@ -0,0 +1,2714 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Valentin V. Bartenev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    u_char  next;
+    u_char  emit;
+    u_char  sym;
+    u_char  ending;
+} ngx_http_v2_huff_decode_code_t;
+
+
+static ngx_inline ngx_int_t ngx_http_v2_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] =
+{
+    /* 0 */
+    {
+        {0x04, 0x00, 0x00, 0x00}, {0x05, 0x00, 0x00, 0x00},
+        {0x07, 0x00, 0x00, 0x00}, {0x08, 0x00, 0x00, 0x00},
+        {0x0b, 0x00, 0x00, 0x00}, {0x0c, 0x00, 0x00, 0x00},
+        {0x10, 0x00, 0x00, 0x00}, {0x13, 0x00, 0x00, 0x00},
+        {0x19, 0x00, 0x00, 0x00}, {0x1c, 0x00, 0x00, 0x00},
+        {0x20, 0x00, 0x00, 0x00}, {0x23, 0x00, 0x00, 0x00},
+        {0x2a, 0x00, 0x00, 0x00}, {0x31, 0x00, 0x00, 0x00},
+        {0x39, 0x00, 0x00, 0x00}, {0x40, 0x00, 0x00, 0x01}
+    },
+    {
+        {0x00, 0x01, 0x30, 0x01}, {0x00, 0x01, 0x31, 0x01},
+        {0x00, 0x01, 0x32, 0x01}, {0x00, 0x01, 0x61, 0x01},
+        {0x00, 0x01, 0x63, 0x01}, {0x00, 0x01, 0x65, 0x01},
+        {0x00, 0x01, 0x69, 0x01}, {0x00, 0x01, 0x6f, 0x01},
+        {0x00, 0x01, 0x73, 0x01}, {0x00, 0x01, 0x74, 0x01},
+        {0x0d, 0x00, 0x00, 0x00}, {0x0e, 0x00, 0x00, 0x00},
+        {0x11, 0x00, 0x00, 0x00}, {0x12, 0x00, 0x00, 0x00},
+        {0x14, 0x00, 0x00, 0x00}, {0x15, 0x00, 0x00, 0x00}
+    },
+    {
+        {0x01, 0x01, 0x30, 0x00}, {0x16, 0x01, 0x30, 0x01},
+        {0x01, 0x01, 0x31, 0x00}, {0x16, 0x01, 0x31, 0x01},
+        {0x01, 0x01, 0x32, 0x00}, {0x16, 0x01, 0x32, 0x01},
+        {0x01, 0x01, 0x61, 0x00}, {0x16, 0x01, 0x61, 0x01},
+        {0x01, 0x01, 0x63, 0x00}, {0x16, 0x01, 0x63, 0x01},
+        {0x01, 0x01, 0x65, 0x00}, {0x16, 0x01, 0x65, 0x01},
+        {0x01, 0x01, 0x69, 0x00}, {0x16, 0x01, 0x69, 0x01},
+        {0x01, 0x01, 0x6f, 0x00}, {0x16, 0x01, 0x6f, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x30, 0x00}, {0x09, 0x01, 0x30, 0x00},
+        {0x17, 0x01, 0x30, 0x00}, {0x28, 0x01, 0x30, 0x01},
+        {0x02, 0x01, 0x31, 0x00}, {0x09, 0x01, 0x31, 0x00},
+        {0x17, 0x01, 0x31, 0x00}, {0x28, 0x01, 0x31, 0x01},
+        {0x02, 0x01, 0x32, 0x00}, {0x09, 0x01, 0x32, 0x00},
+        {0x17, 0x01, 0x32, 0x00}, {0x28, 0x01, 0x32, 0x01},
+        {0x02, 0x01, 0x61, 0x00}, {0x09, 0x01, 0x61, 0x00},
+        {0x17, 0x01, 0x61, 0x00}, {0x28, 0x01, 0x61, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x30, 0x00}, {0x06, 0x01, 0x30, 0x00},
+        {0x0a, 0x01, 0x30, 0x00}, {0x0f, 0x01, 0x30, 0x00},
+        {0x18, 0x01, 0x30, 0x00}, {0x1f, 0x01, 0x30, 0x00},
+        {0x29, 0x01, 0x30, 0x00}, {0x38, 0x01, 0x30, 0x01},
+        {0x03, 0x01, 0x31, 0x00}, {0x06, 0x01, 0x31, 0x00},
+        {0x0a, 0x01, 0x31, 0x00}, {0x0f, 0x01, 0x31, 0x00},
+        {0x18, 0x01, 0x31, 0x00}, {0x1f, 0x01, 0x31, 0x00},
+        {0x29, 0x01, 0x31, 0x00}, {0x38, 0x01, 0x31, 0x01}
+    },
+    /* 5 */
+    {
+        {0x03, 0x01, 0x32, 0x00}, {0x06, 0x01, 0x32, 0x00},
+        {0x0a, 0x01, 0x32, 0x00}, {0x0f, 0x01, 0x32, 0x00},
+        {0x18, 0x01, 0x32, 0x00}, {0x1f, 0x01, 0x32, 0x00},
+        {0x29, 0x01, 0x32, 0x00}, {0x38, 0x01, 0x32, 0x01},
+        {0x03, 0x01, 0x61, 0x00}, {0x06, 0x01, 0x61, 0x00},
+        {0x0a, 0x01, 0x61, 0x00}, {0x0f, 0x01, 0x61, 0x00},
+        {0x18, 0x01, 0x61, 0x00}, {0x1f, 0x01, 0x61, 0x00},
+        {0x29, 0x01, 0x61, 0x00}, {0x38, 0x01, 0x61, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x63, 0x00}, {0x09, 0x01, 0x63, 0x00},
+        {0x17, 0x01, 0x63, 0x00}, {0x28, 0x01, 0x63, 0x01},
+        {0x02, 0x01, 0x65, 0x00}, {0x09, 0x01, 0x65, 0x00},
+        {0x17, 0x01, 0x65, 0x00}, {0x28, 0x01, 0x65, 0x01},
+        {0x02, 0x01, 0x69, 0x00}, {0x09, 0x01, 0x69, 0x00},
+        {0x17, 0x01, 0x69, 0x00}, {0x28, 0x01, 0x69, 0x01},
+        {0x02, 0x01, 0x6f, 0x00}, {0x09, 0x01, 0x6f, 0x00},
+        {0x17, 0x01, 0x6f, 0x00}, {0x28, 0x01, 0x6f, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x63, 0x00}, {0x06, 0x01, 0x63, 0x00},
+        {0x0a, 0x01, 0x63, 0x00}, {0x0f, 0x01, 0x63, 0x00},
+        {0x18, 0x01, 0x63, 0x00}, {0x1f, 0x01, 0x63, 0x00},
+        {0x29, 0x01, 0x63, 0x00}, {0x38, 0x01, 0x63, 0x01},
+        {0x03, 0x01, 0x65, 0x00}, {0x06, 0x01, 0x65, 0x00},
+        {0x0a, 0x01, 0x65, 0x00}, {0x0f, 0x01, 0x65, 0x00},
+        {0x18, 0x01, 0x65, 0x00}, {0x1f, 0x01, 0x65, 0x00},
+        {0x29, 0x01, 0x65, 0x00}, {0x38, 0x01, 0x65, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x69, 0x00}, {0x06, 0x01, 0x69, 0x00},
+        {0x0a, 0x01, 0x69, 0x00}, {0x0f, 0x01, 0x69, 0x00},
+        {0x18, 0x01, 0x69, 0x00}, {0x1f, 0x01, 0x69, 0x00},
+        {0x29, 0x01, 0x69, 0x00}, {0x38, 0x01, 0x69, 0x01},
+        {0x03, 0x01, 0x6f, 0x00}, {0x06, 0x01, 0x6f, 0x00},
+        {0x0a, 0x01, 0x6f, 0x00}, {0x0f, 0x01, 0x6f, 0x00},
+        {0x18, 0x01, 0x6f, 0x00}, {0x1f, 0x01, 0x6f, 0x00},
+        {0x29, 0x01, 0x6f, 0x00}, {0x38, 0x01, 0x6f, 0x01}
+    },
+    {
+        {0x01, 0x01, 0x73, 0x00}, {0x16, 0x01, 0x73, 0x01},
+        {0x01, 0x01, 0x74, 0x00}, {0x16, 0x01, 0x74, 0x01},
+        {0x00, 0x01, 0x20, 0x01}, {0x00, 0x01, 0x25, 0x01},
+        {0x00, 0x01, 0x2d, 0x01}, {0x00, 0x01, 0x2e, 0x01},
+        {0x00, 0x01, 0x2f, 0x01}, {0x00, 0x01, 0x33, 0x01},
+        {0x00, 0x01, 0x34, 0x01}, {0x00, 0x01, 0x35, 0x01},
+        {0x00, 0x01, 0x36, 0x01}, {0x00, 0x01, 0x37, 0x01},
+        {0x00, 0x01, 0x38, 0x01}, {0x00, 0x01, 0x39, 0x01}
+    },
+    /* 10 */
+    {
+        {0x02, 0x01, 0x73, 0x00}, {0x09, 0x01, 0x73, 0x00},
+        {0x17, 0x01, 0x73, 0x00}, {0x28, 0x01, 0x73, 0x01},
+        {0x02, 0x01, 0x74, 0x00}, {0x09, 0x01, 0x74, 0x00},
+        {0x17, 0x01, 0x74, 0x00}, {0x28, 0x01, 0x74, 0x01},
+        {0x01, 0x01, 0x20, 0x00}, {0x16, 0x01, 0x20, 0x01},
+        {0x01, 0x01, 0x25, 0x00}, {0x16, 0x01, 0x25, 0x01},
+        {0x01, 0x01, 0x2d, 0x00}, {0x16, 0x01, 0x2d, 0x01},
+        {0x01, 0x01, 0x2e, 0x00}, {0x16, 0x01, 0x2e, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x73, 0x00}, {0x06, 0x01, 0x73, 0x00},
+        {0x0a, 0x01, 0x73, 0x00}, {0x0f, 0x01, 0x73, 0x00},
+        {0x18, 0x01, 0x73, 0x00}, {0x1f, 0x01, 0x73, 0x00},
+        {0x29, 0x01, 0x73, 0x00}, {0x38, 0x01, 0x73, 0x01},
+        {0x03, 0x01, 0x74, 0x00}, {0x06, 0x01, 0x74, 0x00},
+        {0x0a, 0x01, 0x74, 0x00}, {0x0f, 0x01, 0x74, 0x00},
+        {0x18, 0x01, 0x74, 0x00}, {0x1f, 0x01, 0x74, 0x00},
+        {0x29, 0x01, 0x74, 0x00}, {0x38, 0x01, 0x74, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x20, 0x00}, {0x09, 0x01, 0x20, 0x00},
+        {0x17, 0x01, 0x20, 0x00}, {0x28, 0x01, 0x20, 0x01},
+        {0x02, 0x01, 0x25, 0x00}, {0x09, 0x01, 0x25, 0x00},
+        {0x17, 0x01, 0x25, 0x00}, {0x28, 0x01, 0x25, 0x01},
+        {0x02, 0x01, 0x2d, 0x00}, {0x09, 0x01, 0x2d, 0x00},
+        {0x17, 0x01, 0x2d, 0x00}, {0x28, 0x01, 0x2d, 0x01},
+        {0x02, 0x01, 0x2e, 0x00}, {0x09, 0x01, 0x2e, 0x00},
+        {0x17, 0x01, 0x2e, 0x00}, {0x28, 0x01, 0x2e, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x20, 0x00}, {0x06, 0x01, 0x20, 0x00},
+        {0x0a, 0x01, 0x20, 0x00}, {0x0f, 0x01, 0x20, 0x00},
+        {0x18, 0x01, 0x20, 0x00}, {0x1f, 0x01, 0x20, 0x00},
+        {0x29, 0x01, 0x20, 0x00}, {0x38, 0x01, 0x20, 0x01},
+        {0x03, 0x01, 0x25, 0x00}, {0x06, 0x01, 0x25, 0x00},
+        {0x0a, 0x01, 0x25, 0x00}, {0x0f, 0x01, 0x25, 0x00},
+        {0x18, 0x01, 0x25, 0x00}, {0x1f, 0x01, 0x25, 0x00},
+        {0x29, 0x01, 0x25, 0x00}, {0x38, 0x01, 0x25, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x2d, 0x00}, {0x06, 0x01, 0x2d, 0x00},
+        {0x0a, 0x01, 0x2d, 0x00}, {0x0f, 0x01, 0x2d, 0x00},
+        {0x18, 0x01, 0x2d, 0x00}, {0x1f, 0x01, 0x2d, 0x00},
+        {0x29, 0x01, 0x2d, 0x00}, {0x38, 0x01, 0x2d, 0x01},
+        {0x03, 0x01, 0x2e, 0x00}, {0x06, 0x01, 0x2e, 0x00},
+        {0x0a, 0x01, 0x2e, 0x00}, {0x0f, 0x01, 0x2e, 0x00},
+        {0x18, 0x01, 0x2e, 0x00}, {0x1f, 0x01, 0x2e, 0x00},
+        {0x29, 0x01, 0x2e, 0x00}, {0x38, 0x01, 0x2e, 0x01}
+    },
+    /* 15 */
+    {
+        {0x01, 0x01, 0x2f, 0x00}, {0x16, 0x01, 0x2f, 0x01},
+        {0x01, 0x01, 0x33, 0x00}, {0x16, 0x01, 0x33, 0x01},
+        {0x01, 0x01, 0x34, 0x00}, {0x16, 0x01, 0x34, 0x01},
+        {0x01, 0x01, 0x35, 0x00}, {0x16, 0x01, 0x35, 0x01},
+        {0x01, 0x01, 0x36, 0x00}, {0x16, 0x01, 0x36, 0x01},
+        {0x01, 0x01, 0x37, 0x00}, {0x16, 0x01, 0x37, 0x01},
+        {0x01, 0x01, 0x38, 0x00}, {0x16, 0x01, 0x38, 0x01},
+        {0x01, 0x01, 0x39, 0x00}, {0x16, 0x01, 0x39, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x2f, 0x00}, {0x09, 0x01, 0x2f, 0x00},
+        {0x17, 0x01, 0x2f, 0x00}, {0x28, 0x01, 0x2f, 0x01},
+        {0x02, 0x01, 0x33, 0x00}, {0x09, 0x01, 0x33, 0x00},
+        {0x17, 0x01, 0x33, 0x00}, {0x28, 0x01, 0x33, 0x01},
+        {0x02, 0x01, 0x34, 0x00}, {0x09, 0x01, 0x34, 0x00},
+        {0x17, 0x01, 0x34, 0x00}, {0x28, 0x01, 0x34, 0x01},
+        {0x02, 0x01, 0x35, 0x00}, {0x09, 0x01, 0x35, 0x00},
+        {0x17, 0x01, 0x35, 0x00}, {0x28, 0x01, 0x35, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x2f, 0x00}, {0x06, 0x01, 0x2f, 0x00},
+        {0x0a, 0x01, 0x2f, 0x00}, {0x0f, 0x01, 0x2f, 0x00},
+        {0x18, 0x01, 0x2f, 0x00}, {0x1f, 0x01, 0x2f, 0x00},
+        {0x29, 0x01, 0x2f, 0x00}, {0x38, 0x01, 0x2f, 0x01},
+        {0x03, 0x01, 0x33, 0x00}, {0x06, 0x01, 0x33, 0x00},
+        {0x0a, 0x01, 0x33, 0x00}, {0x0f, 0x01, 0x33, 0x00},
+        {0x18, 0x01, 0x33, 0x00}, {0x1f, 0x01, 0x33, 0x00},
+        {0x29, 0x01, 0x33, 0x00}, {0x38, 0x01, 0x33, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x34, 0x00}, {0x06, 0x01, 0x34, 0x00},
+        {0x0a, 0x01, 0x34, 0x00}, {0x0f, 0x01, 0x34, 0x00},
+        {0x18, 0x01, 0x34, 0x00}, {0x1f, 0x01, 0x34, 0x00},
+        {0x29, 0x01, 0x34, 0x00}, {0x38, 0x01, 0x34, 0x01},
+        {0x03, 0x01, 0x35, 0x00}, {0x06, 0x01, 0x35, 0x00},
+        {0x0a, 0x01, 0x35, 0x00}, {0x0f, 0x01, 0x35, 0x00},
+        {0x18, 0x01, 0x35, 0x00}, {0x1f, 0x01, 0x35, 0x00},
+        {0x29, 0x01, 0x35, 0x00}, {0x38, 0x01, 0x35, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x36, 0x00}, {0x09, 0x01, 0x36, 0x00},
+        {0x17, 0x01, 0x36, 0x00}, {0x28, 0x01, 0x36, 0x01},
+        {0x02, 0x01, 0x37, 0x00}, {0x09, 0x01, 0x37, 0x00},
+        {0x17, 0x01, 0x37, 0x00}, {0x28, 0x01, 0x37, 0x01},
+        {0x02, 0x01, 0x38, 0x00}, {0x09, 0x01, 0x38, 0x00},
+        {0x17, 0x01, 0x38, 0x00}, {0x28, 0x01, 0x38, 0x01},
+        {0x02, 0x01, 0x39, 0x00}, {0x09, 0x01, 0x39, 0x00},
+        {0x17, 0x01, 0x39, 0x00}, {0x28, 0x01, 0x39, 0x01}
+    },
+    /* 20 */
+    {
+        {0x03, 0x01, 0x36, 0x00}, {0x06, 0x01, 0x36, 0x00},
+        {0x0a, 0x01, 0x36, 0x00}, {0x0f, 0x01, 0x36, 0x00},
+        {0x18, 0x01, 0x36, 0x00}, {0x1f, 0x01, 0x36, 0x00},
+        {0x29, 0x01, 0x36, 0x00}, {0x38, 0x01, 0x36, 0x01},
+        {0x03, 0x01, 0x37, 0x00}, {0x06, 0x01, 0x37, 0x00},
+        {0x0a, 0x01, 0x37, 0x00}, {0x0f, 0x01, 0x37, 0x00},
+        {0x18, 0x01, 0x37, 0x00}, {0x1f, 0x01, 0x37, 0x00},
+        {0x29, 0x01, 0x37, 0x00}, {0x38, 0x01, 0x37, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x38, 0x00}, {0x06, 0x01, 0x38, 0x00},
+        {0x0a, 0x01, 0x38, 0x00}, {0x0f, 0x01, 0x38, 0x00},
+        {0x18, 0x01, 0x38, 0x00}, {0x1f, 0x01, 0x38, 0x00},
+        {0x29, 0x01, 0x38, 0x00}, {0x38, 0x01, 0x38, 0x01},
+        {0x03, 0x01, 0x39, 0x00}, {0x06, 0x01, 0x39, 0x00},
+        {0x0a, 0x01, 0x39, 0x00}, {0x0f, 0x01, 0x39, 0x00},
+        {0x18, 0x01, 0x39, 0x00}, {0x1f, 0x01, 0x39, 0x00},
+        {0x29, 0x01, 0x39, 0x00}, {0x38, 0x01, 0x39, 0x01}
+    },
+    {
+        {0x1a, 0x00, 0x00, 0x00}, {0x1b, 0x00, 0x00, 0x00},
+        {0x1d, 0x00, 0x00, 0x00}, {0x1e, 0x00, 0x00, 0x00},
+        {0x21, 0x00, 0x00, 0x00}, {0x22, 0x00, 0x00, 0x00},
+        {0x24, 0x00, 0x00, 0x00}, {0x25, 0x00, 0x00, 0x00},
+        {0x2b, 0x00, 0x00, 0x00}, {0x2e, 0x00, 0x00, 0x00},
+        {0x32, 0x00, 0x00, 0x00}, {0x35, 0x00, 0x00, 0x00},
+        {0x3a, 0x00, 0x00, 0x00}, {0x3d, 0x00, 0x00, 0x00},
+        {0x41, 0x00, 0x00, 0x00}, {0x44, 0x00, 0x00, 0x01}
+    },
+    {
+        {0x00, 0x01, 0x3d, 0x01}, {0x00, 0x01, 0x41, 0x01},
+        {0x00, 0x01, 0x5f, 0x01}, {0x00, 0x01, 0x62, 0x01},
+        {0x00, 0x01, 0x64, 0x01}, {0x00, 0x01, 0x66, 0x01},
+        {0x00, 0x01, 0x67, 0x01}, {0x00, 0x01, 0x68, 0x01},
+        {0x00, 0x01, 0x6c, 0x01}, {0x00, 0x01, 0x6d, 0x01},
+        {0x00, 0x01, 0x6e, 0x01}, {0x00, 0x01, 0x70, 0x01},
+        {0x00, 0x01, 0x72, 0x01}, {0x00, 0x01, 0x75, 0x01},
+        {0x26, 0x00, 0x00, 0x00}, {0x27, 0x00, 0x00, 0x00}
+    },
+    {
+        {0x01, 0x01, 0x3d, 0x00}, {0x16, 0x01, 0x3d, 0x01},
+        {0x01, 0x01, 0x41, 0x00}, {0x16, 0x01, 0x41, 0x01},
+        {0x01, 0x01, 0x5f, 0x00}, {0x16, 0x01, 0x5f, 0x01},
+        {0x01, 0x01, 0x62, 0x00}, {0x16, 0x01, 0x62, 0x01},
+        {0x01, 0x01, 0x64, 0x00}, {0x16, 0x01, 0x64, 0x01},
+        {0x01, 0x01, 0x66, 0x00}, {0x16, 0x01, 0x66, 0x01},
+        {0x01, 0x01, 0x67, 0x00}, {0x16, 0x01, 0x67, 0x01},
+        {0x01, 0x01, 0x68, 0x00}, {0x16, 0x01, 0x68, 0x01}
+    },
+    /* 25 */
+    {
+        {0x02, 0x01, 0x3d, 0x00}, {0x09, 0x01, 0x3d, 0x00},
+        {0x17, 0x01, 0x3d, 0x00}, {0x28, 0x01, 0x3d, 0x01},
+        {0x02, 0x01, 0x41, 0x00}, {0x09, 0x01, 0x41, 0x00},
+        {0x17, 0x01, 0x41, 0x00}, {0x28, 0x01, 0x41, 0x01},
+        {0x02, 0x01, 0x5f, 0x00}, {0x09, 0x01, 0x5f, 0x00},
+        {0x17, 0x01, 0x5f, 0x00}, {0x28, 0x01, 0x5f, 0x01},
+        {0x02, 0x01, 0x62, 0x00}, {0x09, 0x01, 0x62, 0x00},
+        {0x17, 0x01, 0x62, 0x00}, {0x28, 0x01, 0x62, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x3d, 0x00}, {0x06, 0x01, 0x3d, 0x00},
+        {0x0a, 0x01, 0x3d, 0x00}, {0x0f, 0x01, 0x3d, 0x00},
+        {0x18, 0x01, 0x3d, 0x00}, {0x1f, 0x01, 0x3d, 0x00},
+        {0x29, 0x01, 0x3d, 0x00}, {0x38, 0x01, 0x3d, 0x01},
+        {0x03, 0x01, 0x41, 0x00}, {0x06, 0x01, 0x41, 0x00},
+        {0x0a, 0x01, 0x41, 0x00}, {0x0f, 0x01, 0x41, 0x00},
+        {0x18, 0x01, 0x41, 0x00}, {0x1f, 0x01, 0x41, 0x00},
+        {0x29, 0x01, 0x41, 0x00}, {0x38, 0x01, 0x41, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x5f, 0x00}, {0x06, 0x01, 0x5f, 0x00},
+        {0x0a, 0x01, 0x5f, 0x00}, {0x0f, 0x01, 0x5f, 0x00},
+        {0x18, 0x01, 0x5f, 0x00}, {0x1f, 0x01, 0x5f, 0x00},
+        {0x29, 0x01, 0x5f, 0x00}, {0x38, 0x01, 0x5f, 0x01},
+        {0x03, 0x01, 0x62, 0x00}, {0x06, 0x01, 0x62, 0x00},
+        {0x0a, 0x01, 0x62, 0x00}, {0x0f, 0x01, 0x62, 0x00},
+        {0x18, 0x01, 0x62, 0x00}, {0x1f, 0x01, 0x62, 0x00},
+        {0x29, 0x01, 0x62, 0x00}, {0x38, 0x01, 0x62, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x64, 0x00}, {0x09, 0x01, 0x64, 0x00},
+        {0x17, 0x01, 0x64, 0x00}, {0x28, 0x01, 0x64, 0x01},
+        {0x02, 0x01, 0x66, 0x00}, {0x09, 0x01, 0x66, 0x00},
+        {0x17, 0x01, 0x66, 0x00}, {0x28, 0x01, 0x66, 0x01},
+        {0x02, 0x01, 0x67, 0x00}, {0x09, 0x01, 0x67, 0x00},
+        {0x17, 0x01, 0x67, 0x00}, {0x28, 0x01, 0x67, 0x01},
+        {0x02, 0x01, 0x68, 0x00}, {0x09, 0x01, 0x68, 0x00},
+        {0x17, 0x01, 0x68, 0x00}, {0x28, 0x01, 0x68, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x64, 0x00}, {0x06, 0x01, 0x64, 0x00},
+        {0x0a, 0x01, 0x64, 0x00}, {0x0f, 0x01, 0x64, 0x00},
+        {0x18, 0x01, 0x64, 0x00}, {0x1f, 0x01, 0x64, 0x00},
+        {0x29, 0x01, 0x64, 0x00}, {0x38, 0x01, 0x64, 0x01},
+        {0x03, 0x01, 0x66, 0x00}, {0x06, 0x01, 0x66, 0x00},
+        {0x0a, 0x01, 0x66, 0x00}, {0x0f, 0x01, 0x66, 0x00},
+        {0x18, 0x01, 0x66, 0x00}, {0x1f, 0x01, 0x66, 0x00},
+        {0x29, 0x01, 0x66, 0x00}, {0x38, 0x01, 0x66, 0x01}
+    },
+    /* 30 */
+    {
+        {0x03, 0x01, 0x67, 0x00}, {0x06, 0x01, 0x67, 0x00},
+        {0x0a, 0x01, 0x67, 0x00}, {0x0f, 0x01, 0x67, 0x00},
+        {0x18, 0x01, 0x67, 0x00}, {0x1f, 0x01, 0x67, 0x00},
+        {0x29, 0x01, 0x67, 0x00}, {0x38, 0x01, 0x67, 0x01},
+        {0x03, 0x01, 0x68, 0x00}, {0x06, 0x01, 0x68, 0x00},
+        {0x0a, 0x01, 0x68, 0x00}, {0x0f, 0x01, 0x68, 0x00},
+        {0x18, 0x01, 0x68, 0x00}, {0x1f, 0x01, 0x68, 0x00},
+        {0x29, 0x01, 0x68, 0x00}, {0x38, 0x01, 0x68, 0x01}
+    },
+    {
+        {0x01, 0x01, 0x6c, 0x00}, {0x16, 0x01, 0x6c, 0x01},
+        {0x01, 0x01, 0x6d, 0x00}, {0x16, 0x01, 0x6d, 0x01},
+        {0x01, 0x01, 0x6e, 0x00}, {0x16, 0x01, 0x6e, 0x01},
+        {0x01, 0x01, 0x70, 0x00}, {0x16, 0x01, 0x70, 0x01},
+        {0x01, 0x01, 0x72, 0x00}, {0x16, 0x01, 0x72, 0x01},
+        {0x01, 0x01, 0x75, 0x00}, {0x16, 0x01, 0x75, 0x01},
+        {0x00, 0x01, 0x3a, 0x01}, {0x00, 0x01, 0x42, 0x01},
+        {0x00, 0x01, 0x43, 0x01}, {0x00, 0x01, 0x44, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x6c, 0x00}, {0x09, 0x01, 0x6c, 0x00},
+        {0x17, 0x01, 0x6c, 0x00}, {0x28, 0x01, 0x6c, 0x01},
+        {0x02, 0x01, 0x6d, 0x00}, {0x09, 0x01, 0x6d, 0x00},
+        {0x17, 0x01, 0x6d, 0x00}, {0x28, 0x01, 0x6d, 0x01},
+        {0x02, 0x01, 0x6e, 0x00}, {0x09, 0x01, 0x6e, 0x00},
+        {0x17, 0x01, 0x6e, 0x00}, {0x28, 0x01, 0x6e, 0x01},
+        {0x02, 0x01, 0x70, 0x00}, {0x09, 0x01, 0x70, 0x00},
+        {0x17, 0x01, 0x70, 0x00}, {0x28, 0x01, 0x70, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x6c, 0x00}, {0x06, 0x01, 0x6c, 0x00},
+        {0x0a, 0x01, 0x6c, 0x00}, {0x0f, 0x01, 0x6c, 0x00},
+        {0x18, 0x01, 0x6c, 0x00}, {0x1f, 0x01, 0x6c, 0x00},
+        {0x29, 0x01, 0x6c, 0x00}, {0x38, 0x01, 0x6c, 0x01},
+        {0x03, 0x01, 0x6d, 0x00}, {0x06, 0x01, 0x6d, 0x00},
+        {0x0a, 0x01, 0x6d, 0x00}, {0x0f, 0x01, 0x6d, 0x00},
+        {0x18, 0x01, 0x6d, 0x00}, {0x1f, 0x01, 0x6d, 0x00},
+        {0x29, 0x01, 0x6d, 0x00}, {0x38, 0x01, 0x6d, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x6e, 0x00}, {0x06, 0x01, 0x6e, 0x00},
+        {0x0a, 0x01, 0x6e, 0x00}, {0x0f, 0x01, 0x6e, 0x00},
+        {0x18, 0x01, 0x6e, 0x00}, {0x1f, 0x01, 0x6e, 0x00},
+        {0x29, 0x01, 0x6e, 0x00}, {0x38, 0x01, 0x6e, 0x01},
+        {0x03, 0x01, 0x70, 0x00}, {0x06, 0x01, 0x70, 0x00},
+        {0x0a, 0x01, 0x70, 0x00}, {0x0f, 0x01, 0x70, 0x00},
+        {0x18, 0x01, 0x70, 0x00}, {0x1f, 0x01, 0x70, 0x00},
+        {0x29, 0x01, 0x70, 0x00}, {0x38, 0x01, 0x70, 0x01}
+    },
+    /* 35 */
+    {
+        {0x02, 0x01, 0x72, 0x00}, {0x09, 0x01, 0x72, 0x00},
+        {0x17, 0x01, 0x72, 0x00}, {0x28, 0x01, 0x72, 0x01},
+        {0x02, 0x01, 0x75, 0x00}, {0x09, 0x01, 0x75, 0x00},
+        {0x17, 0x01, 0x75, 0x00}, {0x28, 0x01, 0x75, 0x01},
+        {0x01, 0x01, 0x3a, 0x00}, {0x16, 0x01, 0x3a, 0x01},
+        {0x01, 0x01, 0x42, 0x00}, {0x16, 0x01, 0x42, 0x01},
+        {0x01, 0x01, 0x43, 0x00}, {0x16, 0x01, 0x43, 0x01},
+        {0x01, 0x01, 0x44, 0x00}, {0x16, 0x01, 0x44, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x72, 0x00}, {0x06, 0x01, 0x72, 0x00},
+        {0x0a, 0x01, 0x72, 0x00}, {0x0f, 0x01, 0x72, 0x00},
+        {0x18, 0x01, 0x72, 0x00}, {0x1f, 0x01, 0x72, 0x00},
+        {0x29, 0x01, 0x72, 0x00}, {0x38, 0x01, 0x72, 0x01},
+        {0x03, 0x01, 0x75, 0x00}, {0x06, 0x01, 0x75, 0x00},
+        {0x0a, 0x01, 0x75, 0x00}, {0x0f, 0x01, 0x75, 0x00},
+        {0x18, 0x01, 0x75, 0x00}, {0x1f, 0x01, 0x75, 0x00},
+        {0x29, 0x01, 0x75, 0x00}, {0x38, 0x01, 0x75, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x3a, 0x00}, {0x09, 0x01, 0x3a, 0x00},
+        {0x17, 0x01, 0x3a, 0x00}, {0x28, 0x01, 0x3a, 0x01},
+        {0x02, 0x01, 0x42, 0x00}, {0x09, 0x01, 0x42, 0x00},
+        {0x17, 0x01, 0x42, 0x00}, {0x28, 0x01, 0x42, 0x01},
+        {0x02, 0x01, 0x43, 0x00}, {0x09, 0x01, 0x43, 0x00},
+        {0x17, 0x01, 0x43, 0x00}, {0x28, 0x01, 0x43, 0x01},
+        {0x02, 0x01, 0x44, 0x00}, {0x09, 0x01, 0x44, 0x00},
+        {0x17, 0x01, 0x44, 0x00}, {0x28, 0x01, 0x44, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x3a, 0x00}, {0x06, 0x01, 0x3a, 0x00},
+        {0x0a, 0x01, 0x3a, 0x00}, {0x0f, 0x01, 0x3a, 0x00},
+        {0x18, 0x01, 0x3a, 0x00}, {0x1f, 0x01, 0x3a, 0x00},
+        {0x29, 0x01, 0x3a, 0x00}, {0x38, 0x01, 0x3a, 0x01},
+        {0x03, 0x01, 0x42, 0x00}, {0x06, 0x01, 0x42, 0x00},
+        {0x0a, 0x01, 0x42, 0x00}, {0x0f, 0x01, 0x42, 0x00},
+        {0x18, 0x01, 0x42, 0x00}, {0x1f, 0x01, 0x42, 0x00},
+        {0x29, 0x01, 0x42, 0x00}, {0x38, 0x01, 0x42, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x43, 0x00}, {0x06, 0x01, 0x43, 0x00},
+        {0x0a, 0x01, 0x43, 0x00}, {0x0f, 0x01, 0x43, 0x00},
+        {0x18, 0x01, 0x43, 0x00}, {0x1f, 0x01, 0x43, 0x00},
+        {0x29, 0x01, 0x43, 0x00}, {0x38, 0x01, 0x43, 0x01},
+        {0x03, 0x01, 0x44, 0x00}, {0x06, 0x01, 0x44, 0x00},
+        {0x0a, 0x01, 0x44, 0x00}, {0x0f, 0x01, 0x44, 0x00},
+        {0x18, 0x01, 0x44, 0x00}, {0x1f, 0x01, 0x44, 0x00},
+        {0x29, 0x01, 0x44, 0x00}, {0x38, 0x01, 0x44, 0x01}
+    },
+    /* 40 */
+    {
+        {0x2c, 0x00, 0x00, 0x00}, {0x2d, 0x00, 0x00, 0x00},
+        {0x2f, 0x00, 0x00, 0x00}, {0x30, 0x00, 0x00, 0x00},
+        {0x33, 0x00, 0x00, 0x00}, {0x34, 0x00, 0x00, 0x00},
+        {0x36, 0x00, 0x00, 0x00}, {0x37, 0x00, 0x00, 0x00},
+        {0x3b, 0x00, 0x00, 0x00}, {0x3c, 0x00, 0x00, 0x00},
+        {0x3e, 0x00, 0x00, 0x00}, {0x3f, 0x00, 0x00, 0x00},
+        {0x42, 0x00, 0x00, 0x00}, {0x43, 0x00, 0x00, 0x00},
+        {0x45, 0x00, 0x00, 0x00}, {0x48, 0x00, 0x00, 0x01}
+    },
+    {
+        {0x00, 0x01, 0x45, 0x01}, {0x00, 0x01, 0x46, 0x01},
+        {0x00, 0x01, 0x47, 0x01}, {0x00, 0x01, 0x48, 0x01},
+        {0x00, 0x01, 0x49, 0x01}, {0x00, 0x01, 0x4a, 0x01},
+        {0x00, 0x01, 0x4b, 0x01}, {0x00, 0x01, 0x4c, 0x01},
+        {0x00, 0x01, 0x4d, 0x01}, {0x00, 0x01, 0x4e, 0x01},
+        {0x00, 0x01, 0x4f, 0x01}, {0x00, 0x01, 0x50, 0x01},
+        {0x00, 0x01, 0x51, 0x01}, {0x00, 0x01, 0x52, 0x01},
+        {0x00, 0x01, 0x53, 0x01}, {0x00, 0x01, 0x54, 0x01}
+    },
+    {
+        {0x01, 0x01, 0x45, 0x00}, {0x16, 0x01, 0x45, 0x01},
+        {0x01, 0x01, 0x46, 0x00}, {0x16, 0x01, 0x46, 0x01},
+        {0x01, 0x01, 0x47, 0x00}, {0x16, 0x01, 0x47, 0x01},
+        {0x01, 0x01, 0x48, 0x00}, {0x16, 0x01, 0x48, 0x01},
+        {0x01, 0x01, 0x49, 0x00}, {0x16, 0x01, 0x49, 0x01},
+        {0x01, 0x01, 0x4a, 0x00}, {0x16, 0x01, 0x4a, 0x01},
+        {0x01, 0x01, 0x4b, 0x00}, {0x16, 0x01, 0x4b, 0x01},
+        {0x01, 0x01, 0x4c, 0x00}, {0x16, 0x01, 0x4c, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x45, 0x00}, {0x09, 0x01, 0x45, 0x00},
+        {0x17, 0x01, 0x45, 0x00}, {0x28, 0x01, 0x45, 0x01},
+        {0x02, 0x01, 0x46, 0x00}, {0x09, 0x01, 0x46, 0x00},
+        {0x17, 0x01, 0x46, 0x00}, {0x28, 0x01, 0x46, 0x01},
+        {0x02, 0x01, 0x47, 0x00}, {0x09, 0x01, 0x47, 0x00},
+        {0x17, 0x01, 0x47, 0x00}, {0x28, 0x01, 0x47, 0x01},
+        {0x02, 0x01, 0x48, 0x00}, {0x09, 0x01, 0x48, 0x00},
+        {0x17, 0x01, 0x48, 0x00}, {0x28, 0x01, 0x48, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x45, 0x00}, {0x06, 0x01, 0x45, 0x00},
+        {0x0a, 0x01, 0x45, 0x00}, {0x0f, 0x01, 0x45, 0x00},
+        {0x18, 0x01, 0x45, 0x00}, {0x1f, 0x01, 0x45, 0x00},
+        {0x29, 0x01, 0x45, 0x00}, {0x38, 0x01, 0x45, 0x01},
+        {0x03, 0x01, 0x46, 0x00}, {0x06, 0x01, 0x46, 0x00},
+        {0x0a, 0x01, 0x46, 0x00}, {0x0f, 0x01, 0x46, 0x00},
+        {0x18, 0x01, 0x46, 0x00}, {0x1f, 0x01, 0x46, 0x00},
+        {0x29, 0x01, 0x46, 0x00}, {0x38, 0x01, 0x46, 0x01}
+    },
+    /* 45 */
+    {
+        {0x03, 0x01, 0x47, 0x00}, {0x06, 0x01, 0x47, 0x00},
+        {0x0a, 0x01, 0x47, 0x00}, {0x0f, 0x01, 0x47, 0x00},
+        {0x18, 0x01, 0x47, 0x00}, {0x1f, 0x01, 0x47, 0x00},
+        {0x29, 0x01, 0x47, 0x00}, {0x38, 0x01, 0x47, 0x01},
+        {0x03, 0x01, 0x48, 0x00}, {0x06, 0x01, 0x48, 0x00},
+        {0x0a, 0x01, 0x48, 0x00}, {0x0f, 0x01, 0x48, 0x00},
+        {0x18, 0x01, 0x48, 0x00}, {0x1f, 0x01, 0x48, 0x00},
+        {0x29, 0x01, 0x48, 0x00}, {0x38, 0x01, 0x48, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x49, 0x00}, {0x09, 0x01, 0x49, 0x00},
+        {0x17, 0x01, 0x49, 0x00}, {0x28, 0x01, 0x49, 0x01},
+        {0x02, 0x01, 0x4a, 0x00}, {0x09, 0x01, 0x4a, 0x00},
+        {0x17, 0x01, 0x4a, 0x00}, {0x28, 0x01, 0x4a, 0x01},
+        {0x02, 0x01, 0x4b, 0x00}, {0x09, 0x01, 0x4b, 0x00},
+        {0x17, 0x01, 0x4b, 0x00}, {0x28, 0x01, 0x4b, 0x01},
+        {0x02, 0x01, 0x4c, 0x00}, {0x09, 0x01, 0x4c, 0x00},
+        {0x17, 0x01, 0x4c, 0x00}, {0x28, 0x01, 0x4c, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x49, 0x00}, {0x06, 0x01, 0x49, 0x00},
+        {0x0a, 0x01, 0x49, 0x00}, {0x0f, 0x01, 0x49, 0x00},
+        {0x18, 0x01, 0x49, 0x00}, {0x1f, 0x01, 0x49, 0x00},
+        {0x29, 0x01, 0x49, 0x00}, {0x38, 0x01, 0x49, 0x01},
+        {0x03, 0x01, 0x4a, 0x00}, {0x06, 0x01, 0x4a, 0x00},
+        {0x0a, 0x01, 0x4a, 0x00}, {0x0f, 0x01, 0x4a, 0x00},
+        {0x18, 0x01, 0x4a, 0x00}, {0x1f, 0x01, 0x4a, 0x00},
+        {0x29, 0x01, 0x4a, 0x00}, {0x38, 0x01, 0x4a, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x4b, 0x00}, {0x06, 0x01, 0x4b, 0x00},
+        {0x0a, 0x01, 0x4b, 0x00}, {0x0f, 0x01, 0x4b, 0x00},
+        {0x18, 0x01, 0x4b, 0x00}, {0x1f, 0x01, 0x4b, 0x00},
+        {0x29, 0x01, 0x4b, 0x00}, {0x38, 0x01, 0x4b, 0x01},
+        {0x03, 0x01, 0x4c, 0x00}, {0x06, 0x01, 0x4c, 0x00},
+        {0x0a, 0x01, 0x4c, 0x00}, {0x0f, 0x01, 0x4c, 0x00},
+        {0x18, 0x01, 0x4c, 0x00}, {0x1f, 0x01, 0x4c, 0x00},
+        {0x29, 0x01, 0x4c, 0x00}, {0x38, 0x01, 0x4c, 0x01}
+    },
+    {
+        {0x01, 0x01, 0x4d, 0x00}, {0x16, 0x01, 0x4d, 0x01},
+        {0x01, 0x01, 0x4e, 0x00}, {0x16, 0x01, 0x4e, 0x01},
+        {0x01, 0x01, 0x4f, 0x00}, {0x16, 0x01, 0x4f, 0x01},
+        {0x01, 0x01, 0x50, 0x00}, {0x16, 0x01, 0x50, 0x01},
+        {0x01, 0x01, 0x51, 0x00}, {0x16, 0x01, 0x51, 0x01},
+        {0x01, 0x01, 0x52, 0x00}, {0x16, 0x01, 0x52, 0x01},
+        {0x01, 0x01, 0x53, 0x00}, {0x16, 0x01, 0x53, 0x01},
+        {0x01, 0x01, 0x54, 0x00}, {0x16, 0x01, 0x54, 0x01}
+    },
+    /* 50 */
+    {
+        {0x02, 0x01, 0x4d, 0x00}, {0x09, 0x01, 0x4d, 0x00},
+        {0x17, 0x01, 0x4d, 0x00}, {0x28, 0x01, 0x4d, 0x01},
+        {0x02, 0x01, 0x4e, 0x00}, {0x09, 0x01, 0x4e, 0x00},
+        {0x17, 0x01, 0x4e, 0x00}, {0x28, 0x01, 0x4e, 0x01},
+        {0x02, 0x01, 0x4f, 0x00}, {0x09, 0x01, 0x4f, 0x00},
+        {0x17, 0x01, 0x4f, 0x00}, {0x28, 0x01, 0x4f, 0x01},
+        {0x02, 0x01, 0x50, 0x00}, {0x09, 0x01, 0x50, 0x00},
+        {0x17, 0x01, 0x50, 0x00}, {0x28, 0x01, 0x50, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x4d, 0x00}, {0x06, 0x01, 0x4d, 0x00},
+        {0x0a, 0x01, 0x4d, 0x00}, {0x0f, 0x01, 0x4d, 0x00},
+        {0x18, 0x01, 0x4d, 0x00}, {0x1f, 0x01, 0x4d, 0x00},
+        {0x29, 0x01, 0x4d, 0x00}, {0x38, 0x01, 0x4d, 0x01},
+        {0x03, 0x01, 0x4e, 0x00}, {0x06, 0x01, 0x4e, 0x00},
+        {0x0a, 0x01, 0x4e, 0x00}, {0x0f, 0x01, 0x4e, 0x00},
+        {0x18, 0x01, 0x4e, 0x00}, {0x1f, 0x01, 0x4e, 0x00},
+        {0x29, 0x01, 0x4e, 0x00}, {0x38, 0x01, 0x4e, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x4f, 0x00}, {0x06, 0x01, 0x4f, 0x00},
+        {0x0a, 0x01, 0x4f, 0x00}, {0x0f, 0x01, 0x4f, 0x00},
+        {0x18, 0x01, 0x4f, 0x00}, {0x1f, 0x01, 0x4f, 0x00},
+        {0x29, 0x01, 0x4f, 0x00}, {0x38, 0x01, 0x4f, 0x01},
+        {0x03, 0x01, 0x50, 0x00}, {0x06, 0x01, 0x50, 0x00},
+        {0x0a, 0x01, 0x50, 0x00}, {0x0f, 0x01, 0x50, 0x00},
+        {0x18, 0x01, 0x50, 0x00}, {0x1f, 0x01, 0x50, 0x00},
+        {0x29, 0x01, 0x50, 0x00}, {0x38, 0x01, 0x50, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x51, 0x00}, {0x09, 0x01, 0x51, 0x00},
+        {0x17, 0x01, 0x51, 0x00}, {0x28, 0x01, 0x51, 0x01},
+        {0x02, 0x01, 0x52, 0x00}, {0x09, 0x01, 0x52, 0x00},
+        {0x17, 0x01, 0x52, 0x00}, {0x28, 0x01, 0x52, 0x01},
+        {0x02, 0x01, 0x53, 0x00}, {0x09, 0x01, 0x53, 0x00},
+        {0x17, 0x01, 0x53, 0x00}, {0x28, 0x01, 0x53, 0x01},
+        {0x02, 0x01, 0x54, 0x00}, {0x09, 0x01, 0x54, 0x00},
+        {0x17, 0x01, 0x54, 0x00}, {0x28, 0x01, 0x54, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x51, 0x00}, {0x06, 0x01, 0x51, 0x00},
+        {0x0a, 0x01, 0x51, 0x00}, {0x0f, 0x01, 0x51, 0x00},
+        {0x18, 0x01, 0x51, 0x00}, {0x1f, 0x01, 0x51, 0x00},
+        {0x29, 0x01, 0x51, 0x00}, {0x38, 0x01, 0x51, 0x01},
+        {0x03, 0x01, 0x52, 0x00}, {0x06, 0x01, 0x52, 0x00},
+        {0x0a, 0x01, 0x52, 0x00}, {0x0f, 0x01, 0x52, 0x00},
+        {0x18, 0x01, 0x52, 0x00}, {0x1f, 0x01, 0x52, 0x00},
+        {0x29, 0x01, 0x52, 0x00}, {0x38, 0x01, 0x52, 0x01}
+    },
+    /* 55 */
+    {
+        {0x03, 0x01, 0x53, 0x00}, {0x06, 0x01, 0x53, 0x00},
+        {0x0a, 0x01, 0x53, 0x00}, {0x0f, 0x01, 0x53, 0x00},
+        {0x18, 0x01, 0x53, 0x00}, {0x1f, 0x01, 0x53, 0x00},
+        {0x29, 0x01, 0x53, 0x00}, {0x38, 0x01, 0x53, 0x01},
+        {0x03, 0x01, 0x54, 0x00}, {0x06, 0x01, 0x54, 0x00},
+        {0x0a, 0x01, 0x54, 0x00}, {0x0f, 0x01, 0x54, 0x00},
+        {0x18, 0x01, 0x54, 0x00}, {0x1f, 0x01, 0x54, 0x00},
+        {0x29, 0x01, 0x54, 0x00}, {0x38, 0x01, 0x54, 0x01}
+    },
+    {
+        {0x00, 0x01, 0x55, 0x01}, {0x00, 0x01, 0x56, 0x01},
+        {0x00, 0x01, 0x57, 0x01}, {0x00, 0x01, 0x59, 0x01},
+        {0x00, 0x01, 0x6a, 0x01}, {0x00, 0x01, 0x6b, 0x01},
+        {0x00, 0x01, 0x71, 0x01}, {0x00, 0x01, 0x76, 0x01},
+        {0x00, 0x01, 0x77, 0x01}, {0x00, 0x01, 0x78, 0x01},
+        {0x00, 0x01, 0x79, 0x01}, {0x00, 0x01, 0x7a, 0x01},
+        {0x46, 0x00, 0x00, 0x00}, {0x47, 0x00, 0x00, 0x00},
+        {0x49, 0x00, 0x00, 0x00}, {0x4a, 0x00, 0x00, 0x01}
+    },
+    {
+        {0x01, 0x01, 0x55, 0x00}, {0x16, 0x01, 0x55, 0x01},
+        {0x01, 0x01, 0x56, 0x00}, {0x16, 0x01, 0x56, 0x01},
+        {0x01, 0x01, 0x57, 0x00}, {0x16, 0x01, 0x57, 0x01},
+        {0x01, 0x01, 0x59, 0x00}, {0x16, 0x01, 0x59, 0x01},
+        {0x01, 0x01, 0x6a, 0x00}, {0x16, 0x01, 0x6a, 0x01},
+        {0x01, 0x01, 0x6b, 0x00}, {0x16, 0x01, 0x6b, 0x01},
+        {0x01, 0x01, 0x71, 0x00}, {0x16, 0x01, 0x71, 0x01},
+        {0x01, 0x01, 0x76, 0x00}, {0x16, 0x01, 0x76, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x55, 0x00}, {0x09, 0x01, 0x55, 0x00},
+        {0x17, 0x01, 0x55, 0x00}, {0x28, 0x01, 0x55, 0x01},
+        {0x02, 0x01, 0x56, 0x00}, {0x09, 0x01, 0x56, 0x00},
+        {0x17, 0x01, 0x56, 0x00}, {0x28, 0x01, 0x56, 0x01},
+        {0x02, 0x01, 0x57, 0x00}, {0x09, 0x01, 0x57, 0x00},
+        {0x17, 0x01, 0x57, 0x00}, {0x28, 0x01, 0x57, 0x01},
+        {0x02, 0x01, 0x59, 0x00}, {0x09, 0x01, 0x59, 0x00},
+        {0x17, 0x01, 0x59, 0x00}, {0x28, 0x01, 0x59, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x55, 0x00}, {0x06, 0x01, 0x55, 0x00},
+        {0x0a, 0x01, 0x55, 0x00}, {0x0f, 0x01, 0x55, 0x00},
+        {0x18, 0x01, 0x55, 0x00}, {0x1f, 0x01, 0x55, 0x00},
+        {0x29, 0x01, 0x55, 0x00}, {0x38, 0x01, 0x55, 0x01},
+        {0x03, 0x01, 0x56, 0x00}, {0x06, 0x01, 0x56, 0x00},
+        {0x0a, 0x01, 0x56, 0x00}, {0x0f, 0x01, 0x56, 0x00},
+        {0x18, 0x01, 0x56, 0x00}, {0x1f, 0x01, 0x56, 0x00},
+        {0x29, 0x01, 0x56, 0x00}, {0x38, 0x01, 0x56, 0x01}
+    },
+    /* 60 */
+    {
+        {0x03, 0x01, 0x57, 0x00}, {0x06, 0x01, 0x57, 0x00},
+        {0x0a, 0x01, 0x57, 0x00}, {0x0f, 0x01, 0x57, 0x00},
+        {0x18, 0x01, 0x57, 0x00}, {0x1f, 0x01, 0x57, 0x00},
+        {0x29, 0x01, 0x57, 0x00}, {0x38, 0x01, 0x57, 0x01},
+        {0x03, 0x01, 0x59, 0x00}, {0x06, 0x01, 0x59, 0x00},
+        {0x0a, 0x01, 0x59, 0x00}, {0x0f, 0x01, 0x59, 0x00},
+        {0x18, 0x01, 0x59, 0x00}, {0x1f, 0x01, 0x59, 0x00},
+        {0x29, 0x01, 0x59, 0x00}, {0x38, 0x01, 0x59, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x6a, 0x00}, {0x09, 0x01, 0x6a, 0x00},
+        {0x17, 0x01, 0x6a, 0x00}, {0x28, 0x01, 0x6a, 0x01},
+        {0x02, 0x01, 0x6b, 0x00}, {0x09, 0x01, 0x6b, 0x00},
+        {0x17, 0x01, 0x6b, 0x00}, {0x28, 0x01, 0x6b, 0x01},
+        {0x02, 0x01, 0x71, 0x00}, {0x09, 0x01, 0x71, 0x00},
+        {0x17, 0x01, 0x71, 0x00}, {0x28, 0x01, 0x71, 0x01},
+        {0x02, 0x01, 0x76, 0x00}, {0x09, 0x01, 0x76, 0x00},
+        {0x17, 0x01, 0x76, 0x00}, {0x28, 0x01, 0x76, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x6a, 0x00}, {0x06, 0x01, 0x6a, 0x00},
+        {0x0a, 0x01, 0x6a, 0x00}, {0x0f, 0x01, 0x6a, 0x00},
+        {0x18, 0x01, 0x6a, 0x00}, {0x1f, 0x01, 0x6a, 0x00},
+        {0x29, 0x01, 0x6a, 0x00}, {0x38, 0x01, 0x6a, 0x01},
+        {0x03, 0x01, 0x6b, 0x00}, {0x06, 0x01, 0x6b, 0x00},
+        {0x0a, 0x01, 0x6b, 0x00}, {0x0f, 0x01, 0x6b, 0x00},
+        {0x18, 0x01, 0x6b, 0x00}, {0x1f, 0x01, 0x6b, 0x00},
+        {0x29, 0x01, 0x6b, 0x00}, {0x38, 0x01, 0x6b, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x71, 0x00}, {0x06, 0x01, 0x71, 0x00},
+        {0x0a, 0x01, 0x71, 0x00}, {0x0f, 0x01, 0x71, 0x00},
+        {0x18, 0x01, 0x71, 0x00}, {0x1f, 0x01, 0x71, 0x00},
+        {0x29, 0x01, 0x71, 0x00}, {0x38, 0x01, 0x71, 0x01},
+        {0x03, 0x01, 0x76, 0x00}, {0x06, 0x01, 0x76, 0x00},
+        {0x0a, 0x01, 0x76, 0x00}, {0x0f, 0x01, 0x76, 0x00},
+        {0x18, 0x01, 0x76, 0x00}, {0x1f, 0x01, 0x76, 0x00},
+        {0x29, 0x01, 0x76, 0x00}, {0x38, 0x01, 0x76, 0x01}
+    },
+    {
+        {0x01, 0x01, 0x77, 0x00}, {0x16, 0x01, 0x77, 0x01},
+        {0x01, 0x01, 0x78, 0x00}, {0x16, 0x01, 0x78, 0x01},
+        {0x01, 0x01, 0x79, 0x00}, {0x16, 0x01, 0x79, 0x01},
+        {0x01, 0x01, 0x7a, 0x00}, {0x16, 0x01, 0x7a, 0x01},
+        {0x00, 0x01, 0x26, 0x01}, {0x00, 0x01, 0x2a, 0x01},
+        {0x00, 0x01, 0x2c, 0x01}, {0x00, 0x01, 0x3b, 0x01},
+        {0x00, 0x01, 0x58, 0x01}, {0x00, 0x01, 0x5a, 0x01},
+        {0x4b, 0x00, 0x00, 0x00}, {0x4e, 0x00, 0x00, 0x01}
+    },
+    /* 65 */
+    {
+        {0x02, 0x01, 0x77, 0x00}, {0x09, 0x01, 0x77, 0x00},
+        {0x17, 0x01, 0x77, 0x00}, {0x28, 0x01, 0x77, 0x01},
+        {0x02, 0x01, 0x78, 0x00}, {0x09, 0x01, 0x78, 0x00},
+        {0x17, 0x01, 0x78, 0x00}, {0x28, 0x01, 0x78, 0x01},
+        {0x02, 0x01, 0x79, 0x00}, {0x09, 0x01, 0x79, 0x00},
+        {0x17, 0x01, 0x79, 0x00}, {0x28, 0x01, 0x79, 0x01},
+        {0x02, 0x01, 0x7a, 0x00}, {0x09, 0x01, 0x7a, 0x00},
+        {0x17, 0x01, 0x7a, 0x00}, {0x28, 0x01, 0x7a, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x77, 0x00}, {0x06, 0x01, 0x77, 0x00},
+        {0x0a, 0x01, 0x77, 0x00}, {0x0f, 0x01, 0x77, 0x00},
+        {0x18, 0x01, 0x77, 0x00}, {0x1f, 0x01, 0x77, 0x00},
+        {0x29, 0x01, 0x77, 0x00}, {0x38, 0x01, 0x77, 0x01},
+        {0x03, 0x01, 0x78, 0x00}, {0x06, 0x01, 0x78, 0x00},
+        {0x0a, 0x01, 0x78, 0x00}, {0x0f, 0x01, 0x78, 0x00},
+        {0x18, 0x01, 0x78, 0x00}, {0x1f, 0x01, 0x78, 0x00},
+        {0x29, 0x01, 0x78, 0x00}, {0x38, 0x01, 0x78, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x79, 0x00}, {0x06, 0x01, 0x79, 0x00},
+        {0x0a, 0x01, 0x79, 0x00}, {0x0f, 0x01, 0x79, 0x00},
+        {0x18, 0x01, 0x79, 0x00}, {0x1f, 0x01, 0x79, 0x00},
+        {0x29, 0x01, 0x79, 0x00}, {0x38, 0x01, 0x79, 0x01},
+        {0x03, 0x01, 0x7a, 0x00}, {0x06, 0x01, 0x7a, 0x00},
+        {0x0a, 0x01, 0x7a, 0x00}, {0x0f, 0x01, 0x7a, 0x00},
+        {0x18, 0x01, 0x7a, 0x00}, {0x1f, 0x01, 0x7a, 0x00},
+        {0x29, 0x01, 0x7a, 0x00}, {0x38, 0x01, 0x7a, 0x01}
+    },
+    {
+        {0x01, 0x01, 0x26, 0x00}, {0x16, 0x01, 0x26, 0x01},
+        {0x01, 0x01, 0x2a, 0x00}, {0x16, 0x01, 0x2a, 0x01},
+        {0x01, 0x01, 0x2c, 0x00}, {0x16, 0x01, 0x2c, 0x01},
+        {0x01, 0x01, 0x3b, 0x00}, {0x16, 0x01, 0x3b, 0x01},
+        {0x01, 0x01, 0x58, 0x00}, {0x16, 0x01, 0x58, 0x01},
+        {0x01, 0x01, 0x5a, 0x00}, {0x16, 0x01, 0x5a, 0x01},
+        {0x4c, 0x00, 0x00, 0x00}, {0x4d, 0x00, 0x00, 0x00},
+        {0x4f, 0x00, 0x00, 0x00}, {0x51, 0x00, 0x00, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x26, 0x00}, {0x09, 0x01, 0x26, 0x00},
+        {0x17, 0x01, 0x26, 0x00}, {0x28, 0x01, 0x26, 0x01},
+        {0x02, 0x01, 0x2a, 0x00}, {0x09, 0x01, 0x2a, 0x00},
+        {0x17, 0x01, 0x2a, 0x00}, {0x28, 0x01, 0x2a, 0x01},
+        {0x02, 0x01, 0x2c, 0x00}, {0x09, 0x01, 0x2c, 0x00},
+        {0x17, 0x01, 0x2c, 0x00}, {0x28, 0x01, 0x2c, 0x01},
+        {0x02, 0x01, 0x3b, 0x00}, {0x09, 0x01, 0x3b, 0x00},
+        {0x17, 0x01, 0x3b, 0x00}, {0x28, 0x01, 0x3b, 0x01}
+    },
+    /* 70 */
+    {
+        {0x03, 0x01, 0x26, 0x00}, {0x06, 0x01, 0x26, 0x00},
+        {0x0a, 0x01, 0x26, 0x00}, {0x0f, 0x01, 0x26, 0x00},
+        {0x18, 0x01, 0x26, 0x00}, {0x1f, 0x01, 0x26, 0x00},
+        {0x29, 0x01, 0x26, 0x00}, {0x38, 0x01, 0x26, 0x01},
+        {0x03, 0x01, 0x2a, 0x00}, {0x06, 0x01, 0x2a, 0x00},
+        {0x0a, 0x01, 0x2a, 0x00}, {0x0f, 0x01, 0x2a, 0x00},
+        {0x18, 0x01, 0x2a, 0x00}, {0x1f, 0x01, 0x2a, 0x00},
+        {0x29, 0x01, 0x2a, 0x00}, {0x38, 0x01, 0x2a, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x2c, 0x00}, {0x06, 0x01, 0x2c, 0x00},
+        {0x0a, 0x01, 0x2c, 0x00}, {0x0f, 0x01, 0x2c, 0x00},
+        {0x18, 0x01, 0x2c, 0x00}, {0x1f, 0x01, 0x2c, 0x00},
+        {0x29, 0x01, 0x2c, 0x00}, {0x38, 0x01, 0x2c, 0x01},
+        {0x03, 0x01, 0x3b, 0x00}, {0x06, 0x01, 0x3b, 0x00},
+        {0x0a, 0x01, 0x3b, 0x00}, {0x0f, 0x01, 0x3b, 0x00},
+        {0x18, 0x01, 0x3b, 0x00}, {0x1f, 0x01, 0x3b, 0x00},
+        {0x29, 0x01, 0x3b, 0x00}, {0x38, 0x01, 0x3b, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x58, 0x00}, {0x09, 0x01, 0x58, 0x00},
+        {0x17, 0x01, 0x58, 0x00}, {0x28, 0x01, 0x58, 0x01},
+        {0x02, 0x01, 0x5a, 0x00}, {0x09, 0x01, 0x5a, 0x00},
+        {0x17, 0x01, 0x5a, 0x00}, {0x28, 0x01, 0x5a, 0x01},
+        {0x00, 0x01, 0x21, 0x01}, {0x00, 0x01, 0x22, 0x01},
+        {0x00, 0x01, 0x28, 0x01}, {0x00, 0x01, 0x29, 0x01},
+        {0x00, 0x01, 0x3f, 0x01}, {0x50, 0x00, 0x00, 0x00},
+        {0x52, 0x00, 0x00, 0x00}, {0x54, 0x00, 0x00, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x58, 0x00}, {0x06, 0x01, 0x58, 0x00},
+        {0x0a, 0x01, 0x58, 0x00}, {0x0f, 0x01, 0x58, 0x00},
+        {0x18, 0x01, 0x58, 0x00}, {0x1f, 0x01, 0x58, 0x00},
+        {0x29, 0x01, 0x58, 0x00}, {0x38, 0x01, 0x58, 0x01},
+        {0x03, 0x01, 0x5a, 0x00}, {0x06, 0x01, 0x5a, 0x00},
+        {0x0a, 0x01, 0x5a, 0x00}, {0x0f, 0x01, 0x5a, 0x00},
+        {0x18, 0x01, 0x5a, 0x00}, {0x1f, 0x01, 0x5a, 0x00},
+        {0x29, 0x01, 0x5a, 0x00}, {0x38, 0x01, 0x5a, 0x01}
+    },
+    {
+        {0x01, 0x01, 0x21, 0x00}, {0x16, 0x01, 0x21, 0x01},
+        {0x01, 0x01, 0x22, 0x00}, {0x16, 0x01, 0x22, 0x01},
+        {0x01, 0x01, 0x28, 0x00}, {0x16, 0x01, 0x28, 0x01},
+        {0x01, 0x01, 0x29, 0x00}, {0x16, 0x01, 0x29, 0x01},
+        {0x01, 0x01, 0x3f, 0x00}, {0x16, 0x01, 0x3f, 0x01},
+        {0x00, 0x01, 0x27, 0x01}, {0x00, 0x01, 0x2b, 0x01},
+        {0x00, 0x01, 0x7c, 0x01}, {0x53, 0x00, 0x00, 0x00},
+        {0x55, 0x00, 0x00, 0x00}, {0x58, 0x00, 0x00, 0x01}
+    },
+    /* 75 */
+    {
+        {0x02, 0x01, 0x21, 0x00}, {0x09, 0x01, 0x21, 0x00},
+        {0x17, 0x01, 0x21, 0x00}, {0x28, 0x01, 0x21, 0x01},
+        {0x02, 0x01, 0x22, 0x00}, {0x09, 0x01, 0x22, 0x00},
+        {0x17, 0x01, 0x22, 0x00}, {0x28, 0x01, 0x22, 0x01},
+        {0x02, 0x01, 0x28, 0x00}, {0x09, 0x01, 0x28, 0x00},
+        {0x17, 0x01, 0x28, 0x00}, {0x28, 0x01, 0x28, 0x01},
+        {0x02, 0x01, 0x29, 0x00}, {0x09, 0x01, 0x29, 0x00},
+        {0x17, 0x01, 0x29, 0x00}, {0x28, 0x01, 0x29, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x21, 0x00}, {0x06, 0x01, 0x21, 0x00},
+        {0x0a, 0x01, 0x21, 0x00}, {0x0f, 0x01, 0x21, 0x00},
+        {0x18, 0x01, 0x21, 0x00}, {0x1f, 0x01, 0x21, 0x00},
+        {0x29, 0x01, 0x21, 0x00}, {0x38, 0x01, 0x21, 0x01},
+        {0x03, 0x01, 0x22, 0x00}, {0x06, 0x01, 0x22, 0x00},
+        {0x0a, 0x01, 0x22, 0x00}, {0x0f, 0x01, 0x22, 0x00},
+        {0x18, 0x01, 0x22, 0x00}, {0x1f, 0x01, 0x22, 0x00},
+        {0x29, 0x01, 0x22, 0x00}, {0x38, 0x01, 0x22, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x28, 0x00}, {0x06, 0x01, 0x28, 0x00},
+        {0x0a, 0x01, 0x28, 0x00}, {0x0f, 0x01, 0x28, 0x00},
+        {0x18, 0x01, 0x28, 0x00}, {0x1f, 0x01, 0x28, 0x00},
+        {0x29, 0x01, 0x28, 0x00}, {0x38, 0x01, 0x28, 0x01},
+        {0x03, 0x01, 0x29, 0x00}, {0x06, 0x01, 0x29, 0x00},
+        {0x0a, 0x01, 0x29, 0x00}, {0x0f, 0x01, 0x29, 0x00},
+        {0x18, 0x01, 0x29, 0x00}, {0x1f, 0x01, 0x29, 0x00},
+        {0x29, 0x01, 0x29, 0x00}, {0x38, 0x01, 0x29, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x3f, 0x00}, {0x09, 0x01, 0x3f, 0x00},
+        {0x17, 0x01, 0x3f, 0x00}, {0x28, 0x01, 0x3f, 0x01},
+        {0x01, 0x01, 0x27, 0x00}, {0x16, 0x01, 0x27, 0x01},
+        {0x01, 0x01, 0x2b, 0x00}, {0x16, 0x01, 0x2b, 0x01},
+        {0x01, 0x01, 0x7c, 0x00}, {0x16, 0x01, 0x7c, 0x01},
+        {0x00, 0x01, 0x23, 0x01}, {0x00, 0x01, 0x3e, 0x01},
+        {0x56, 0x00, 0x00, 0x00}, {0x57, 0x00, 0x00, 0x00},
+        {0x59, 0x00, 0x00, 0x00}, {0x5a, 0x00, 0x00, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x3f, 0x00}, {0x06, 0x01, 0x3f, 0x00},
+        {0x0a, 0x01, 0x3f, 0x00}, {0x0f, 0x01, 0x3f, 0x00},
+        {0x18, 0x01, 0x3f, 0x00}, {0x1f, 0x01, 0x3f, 0x00},
+        {0x29, 0x01, 0x3f, 0x00}, {0x38, 0x01, 0x3f, 0x01},
+        {0x02, 0x01, 0x27, 0x00}, {0x09, 0x01, 0x27, 0x00},
+        {0x17, 0x01, 0x27, 0x00}, {0x28, 0x01, 0x27, 0x01},
+        {0x02, 0x01, 0x2b, 0x00}, {0x09, 0x01, 0x2b, 0x00},
+        {0x17, 0x01, 0x2b, 0x00}, {0x28, 0x01, 0x2b, 0x01}
+    },
+    /* 80 */
+    {
+        {0x03, 0x01, 0x27, 0x00}, {0x06, 0x01, 0x27, 0x00},
+        {0x0a, 0x01, 0x27, 0x00}, {0x0f, 0x01, 0x27, 0x00},
+        {0x18, 0x01, 0x27, 0x00}, {0x1f, 0x01, 0x27, 0x00},
+        {0x29, 0x01, 0x27, 0x00}, {0x38, 0x01, 0x27, 0x01},
+        {0x03, 0x01, 0x2b, 0x00}, {0x06, 0x01, 0x2b, 0x00},
+        {0x0a, 0x01, 0x2b, 0x00}, {0x0f, 0x01, 0x2b, 0x00},
+        {0x18, 0x01, 0x2b, 0x00}, {0x1f, 0x01, 0x2b, 0x00},
+        {0x29, 0x01, 0x2b, 0x00}, {0x38, 0x01, 0x2b, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x7c, 0x00}, {0x09, 0x01, 0x7c, 0x00},
+        {0x17, 0x01, 0x7c, 0x00}, {0x28, 0x01, 0x7c, 0x01},
+        {0x01, 0x01, 0x23, 0x00}, {0x16, 0x01, 0x23, 0x01},
+        {0x01, 0x01, 0x3e, 0x00}, {0x16, 0x01, 0x3e, 0x01},
+        {0x00, 0x01, 0x00, 0x01}, {0x00, 0x01, 0x24, 0x01},
+        {0x00, 0x01, 0x40, 0x01}, {0x00, 0x01, 0x5b, 0x01},
+        {0x00, 0x01, 0x5d, 0x01}, {0x00, 0x01, 0x7e, 0x01},
+        {0x5b, 0x00, 0x00, 0x00}, {0x5c, 0x00, 0x00, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x7c, 0x00}, {0x06, 0x01, 0x7c, 0x00},
+        {0x0a, 0x01, 0x7c, 0x00}, {0x0f, 0x01, 0x7c, 0x00},
+        {0x18, 0x01, 0x7c, 0x00}, {0x1f, 0x01, 0x7c, 0x00},
+        {0x29, 0x01, 0x7c, 0x00}, {0x38, 0x01, 0x7c, 0x01},
+        {0x02, 0x01, 0x23, 0x00}, {0x09, 0x01, 0x23, 0x00},
+        {0x17, 0x01, 0x23, 0x00}, {0x28, 0x01, 0x23, 0x01},
+        {0x02, 0x01, 0x3e, 0x00}, {0x09, 0x01, 0x3e, 0x00},
+        {0x17, 0x01, 0x3e, 0x00}, {0x28, 0x01, 0x3e, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x23, 0x00}, {0x06, 0x01, 0x23, 0x00},
+        {0x0a, 0x01, 0x23, 0x00}, {0x0f, 0x01, 0x23, 0x00},
+        {0x18, 0x01, 0x23, 0x00}, {0x1f, 0x01, 0x23, 0x00},
+        {0x29, 0x01, 0x23, 0x00}, {0x38, 0x01, 0x23, 0x01},
+        {0x03, 0x01, 0x3e, 0x00}, {0x06, 0x01, 0x3e, 0x00},
+        {0x0a, 0x01, 0x3e, 0x00}, {0x0f, 0x01, 0x3e, 0x00},
+        {0x18, 0x01, 0x3e, 0x00}, {0x1f, 0x01, 0x3e, 0x00},
+        {0x29, 0x01, 0x3e, 0x00}, {0x38, 0x01, 0x3e, 0x01}
+    },
+    {
+        {0x01, 0x01, 0x00, 0x00}, {0x16, 0x01, 0x00, 0x01},
+        {0x01, 0x01, 0x24, 0x00}, {0x16, 0x01, 0x24, 0x01},
+        {0x01, 0x01, 0x40, 0x00}, {0x16, 0x01, 0x40, 0x01},
+        {0x01, 0x01, 0x5b, 0x00}, {0x16, 0x01, 0x5b, 0x01},
+        {0x01, 0x01, 0x5d, 0x00}, {0x16, 0x01, 0x5d, 0x01},
+        {0x01, 0x01, 0x7e, 0x00}, {0x16, 0x01, 0x7e, 0x01},
+        {0x00, 0x01, 0x5e, 0x01}, {0x00, 0x01, 0x7d, 0x01},
+        {0x5d, 0x00, 0x00, 0x00}, {0x5e, 0x00, 0x00, 0x01}
+    },
+    /* 85 */
+    {
+        {0x02, 0x01, 0x00, 0x00}, {0x09, 0x01, 0x00, 0x00},
+        {0x17, 0x01, 0x00, 0x00}, {0x28, 0x01, 0x00, 0x01},
+        {0x02, 0x01, 0x24, 0x00}, {0x09, 0x01, 0x24, 0x00},
+        {0x17, 0x01, 0x24, 0x00}, {0x28, 0x01, 0x24, 0x01},
+        {0x02, 0x01, 0x40, 0x00}, {0x09, 0x01, 0x40, 0x00},
+        {0x17, 0x01, 0x40, 0x00}, {0x28, 0x01, 0x40, 0x01},
+        {0x02, 0x01, 0x5b, 0x00}, {0x09, 0x01, 0x5b, 0x00},
+        {0x17, 0x01, 0x5b, 0x00}, {0x28, 0x01, 0x5b, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x00, 0x00}, {0x06, 0x01, 0x00, 0x00},
+        {0x0a, 0x01, 0x00, 0x00}, {0x0f, 0x01, 0x00, 0x00},
+        {0x18, 0x01, 0x00, 0x00}, {0x1f, 0x01, 0x00, 0x00},
+        {0x29, 0x01, 0x00, 0x00}, {0x38, 0x01, 0x00, 0x01},
+        {0x03, 0x01, 0x24, 0x00}, {0x06, 0x01, 0x24, 0x00},
+        {0x0a, 0x01, 0x24, 0x00}, {0x0f, 0x01, 0x24, 0x00},
+        {0x18, 0x01, 0x24, 0x00}, {0x1f, 0x01, 0x24, 0x00},
+        {0x29, 0x01, 0x24, 0x00}, {0x38, 0x01, 0x24, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x40, 0x00}, {0x06, 0x01, 0x40, 0x00},
+        {0x0a, 0x01, 0x40, 0x00}, {0x0f, 0x01, 0x40, 0x00},
+        {0x18, 0x01, 0x40, 0x00}, {0x1f, 0x01, 0x40, 0x00},
+        {0x29, 0x01, 0x40, 0x00}, {0x38, 0x01, 0x40, 0x01},
+        {0x03, 0x01, 0x5b, 0x00}, {0x06, 0x01, 0x5b, 0x00},
+        {0x0a, 0x01, 0x5b, 0x00}, {0x0f, 0x01, 0x5b, 0x00},
+        {0x18, 0x01, 0x5b, 0x00}, {0x1f, 0x01, 0x5b, 0x00},
+        {0x29, 0x01, 0x5b, 0x00}, {0x38, 0x01, 0x5b, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x5d, 0x00}, {0x09, 0x01, 0x5d, 0x00},
+        {0x17, 0x01, 0x5d, 0x00}, {0x28, 0x01, 0x5d, 0x01},
+        {0x02, 0x01, 0x7e, 0x00}, {0x09, 0x01, 0x7e, 0x00},
+        {0x17, 0x01, 0x7e, 0x00}, {0x28, 0x01, 0x7e, 0x01},
+        {0x01, 0x01, 0x5e, 0x00}, {0x16, 0x01, 0x5e, 0x01},
+        {0x01, 0x01, 0x7d, 0x00}, {0x16, 0x01, 0x7d, 0x01},
+        {0x00, 0x01, 0x3c, 0x01}, {0x00, 0x01, 0x60, 0x01},
+        {0x00, 0x01, 0x7b, 0x01}, {0x5f, 0x00, 0x00, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x5d, 0x00}, {0x06, 0x01, 0x5d, 0x00},
+        {0x0a, 0x01, 0x5d, 0x00}, {0x0f, 0x01, 0x5d, 0x00},
+        {0x18, 0x01, 0x5d, 0x00}, {0x1f, 0x01, 0x5d, 0x00},
+        {0x29, 0x01, 0x5d, 0x00}, {0x38, 0x01, 0x5d, 0x01},
+        {0x03, 0x01, 0x7e, 0x00}, {0x06, 0x01, 0x7e, 0x00},
+        {0x0a, 0x01, 0x7e, 0x00}, {0x0f, 0x01, 0x7e, 0x00},
+        {0x18, 0x01, 0x7e, 0x00}, {0x1f, 0x01, 0x7e, 0x00},
+        {0x29, 0x01, 0x7e, 0x00}, {0x38, 0x01, 0x7e, 0x01}
+    },
+    /* 90 */
+    {
+        {0x02, 0x01, 0x5e, 0x00}, {0x09, 0x01, 0x5e, 0x00},
+        {0x17, 0x01, 0x5e, 0x00}, {0x28, 0x01, 0x5e, 0x01},
+        {0x02, 0x01, 0x7d, 0x00}, {0x09, 0x01, 0x7d, 0x00},
+        {0x17, 0x01, 0x7d, 0x00}, {0x28, 0x01, 0x7d, 0x01},
+        {0x01, 0x01, 0x3c, 0x00}, {0x16, 0x01, 0x3c, 0x01},
+        {0x01, 0x01, 0x60, 0x00}, {0x16, 0x01, 0x60, 0x01},
+        {0x01, 0x01, 0x7b, 0x00}, {0x16, 0x01, 0x7b, 0x01},
+        {0x60, 0x00, 0x00, 0x00}, {0x6e, 0x00, 0x00, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x5e, 0x00}, {0x06, 0x01, 0x5e, 0x00},
+        {0x0a, 0x01, 0x5e, 0x00}, {0x0f, 0x01, 0x5e, 0x00},
+        {0x18, 0x01, 0x5e, 0x00}, {0x1f, 0x01, 0x5e, 0x00},
+        {0x29, 0x01, 0x5e, 0x00}, {0x38, 0x01, 0x5e, 0x01},
+        {0x03, 0x01, 0x7d, 0x00}, {0x06, 0x01, 0x7d, 0x00},
+        {0x0a, 0x01, 0x7d, 0x00}, {0x0f, 0x01, 0x7d, 0x00},
+        {0x18, 0x01, 0x7d, 0x00}, {0x1f, 0x01, 0x7d, 0x00},
+        {0x29, 0x01, 0x7d, 0x00}, {0x38, 0x01, 0x7d, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x3c, 0x00}, {0x09, 0x01, 0x3c, 0x00},
+        {0x17, 0x01, 0x3c, 0x00}, {0x28, 0x01, 0x3c, 0x01},
+        {0x02, 0x01, 0x60, 0x00}, {0x09, 0x01, 0x60, 0x00},
+        {0x17, 0x01, 0x60, 0x00}, {0x28, 0x01, 0x60, 0x01},
+        {0x02, 0x01, 0x7b, 0x00}, {0x09, 0x01, 0x7b, 0x00},
+        {0x17, 0x01, 0x7b, 0x00}, {0x28, 0x01, 0x7b, 0x01},
+        {0x61, 0x00, 0x00, 0x00}, {0x65, 0x00, 0x00, 0x00},
+        {0x6f, 0x00, 0x00, 0x00}, {0x85, 0x00, 0x00, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x3c, 0x00}, {0x06, 0x01, 0x3c, 0x00},
+        {0x0a, 0x01, 0x3c, 0x00}, {0x0f, 0x01, 0x3c, 0x00},
+        {0x18, 0x01, 0x3c, 0x00}, {0x1f, 0x01, 0x3c, 0x00},
+        {0x29, 0x01, 0x3c, 0x00}, {0x38, 0x01, 0x3c, 0x01},
+        {0x03, 0x01, 0x60, 0x00}, {0x06, 0x01, 0x60, 0x00},
+        {0x0a, 0x01, 0x60, 0x00}, {0x0f, 0x01, 0x60, 0x00},
+        {0x18, 0x01, 0x60, 0x00}, {0x1f, 0x01, 0x60, 0x00},
+        {0x29, 0x01, 0x60, 0x00}, {0x38, 0x01, 0x60, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x7b, 0x00}, {0x06, 0x01, 0x7b, 0x00},
+        {0x0a, 0x01, 0x7b, 0x00}, {0x0f, 0x01, 0x7b, 0x00},
+        {0x18, 0x01, 0x7b, 0x00}, {0x1f, 0x01, 0x7b, 0x00},
+        {0x29, 0x01, 0x7b, 0x00}, {0x38, 0x01, 0x7b, 0x01},
+        {0x62, 0x00, 0x00, 0x00}, {0x63, 0x00, 0x00, 0x00},
+        {0x66, 0x00, 0x00, 0x00}, {0x69, 0x00, 0x00, 0x00},
+        {0x70, 0x00, 0x00, 0x00}, {0x77, 0x00, 0x00, 0x00},
+        {0x86, 0x00, 0x00, 0x00}, {0x99, 0x00, 0x00, 0x01}
+    },
+    /* 95 */
+    {
+        {0x00, 0x01, 0x5c, 0x01}, {0x00, 0x01, 0xc3, 0x01},
+        {0x00, 0x01, 0xd0, 0x01}, {0x64, 0x00, 0x00, 0x00},
+        {0x67, 0x00, 0x00, 0x00}, {0x68, 0x00, 0x00, 0x00},
+        {0x6a, 0x00, 0x00, 0x00}, {0x6b, 0x00, 0x00, 0x00},
+        {0x71, 0x00, 0x00, 0x00}, {0x74, 0x00, 0x00, 0x00},
+        {0x78, 0x00, 0x00, 0x00}, {0x7e, 0x00, 0x00, 0x00},
+        {0x87, 0x00, 0x00, 0x00}, {0x8e, 0x00, 0x00, 0x00},
+        {0x9a, 0x00, 0x00, 0x00}, {0xa9, 0x00, 0x00, 0x01}
+    },
+    {
+        {0x01, 0x01, 0x5c, 0x00}, {0x16, 0x01, 0x5c, 0x01},
+        {0x01, 0x01, 0xc3, 0x00}, {0x16, 0x01, 0xc3, 0x01},
+        {0x01, 0x01, 0xd0, 0x00}, {0x16, 0x01, 0xd0, 0x01},
+        {0x00, 0x01, 0x80, 0x01}, {0x00, 0x01, 0x82, 0x01},
+        {0x00, 0x01, 0x83, 0x01}, {0x00, 0x01, 0xa2, 0x01},
+        {0x00, 0x01, 0xb8, 0x01}, {0x00, 0x01, 0xc2, 0x01},
+        {0x00, 0x01, 0xe0, 0x01}, {0x00, 0x01, 0xe2, 0x01},
+        {0x6c, 0x00, 0x00, 0x00}, {0x6d, 0x00, 0x00, 0x00}
+    },
+    {
+        {0x02, 0x01, 0x5c, 0x00}, {0x09, 0x01, 0x5c, 0x00},
+        {0x17, 0x01, 0x5c, 0x00}, {0x28, 0x01, 0x5c, 0x01},
+        {0x02, 0x01, 0xc3, 0x00}, {0x09, 0x01, 0xc3, 0x00},
+        {0x17, 0x01, 0xc3, 0x00}, {0x28, 0x01, 0xc3, 0x01},
+        {0x02, 0x01, 0xd0, 0x00}, {0x09, 0x01, 0xd0, 0x00},
+        {0x17, 0x01, 0xd0, 0x00}, {0x28, 0x01, 0xd0, 0x01},
+        {0x01, 0x01, 0x80, 0x00}, {0x16, 0x01, 0x80, 0x01},
+        {0x01, 0x01, 0x82, 0x00}, {0x16, 0x01, 0x82, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x5c, 0x00}, {0x06, 0x01, 0x5c, 0x00},
+        {0x0a, 0x01, 0x5c, 0x00}, {0x0f, 0x01, 0x5c, 0x00},
+        {0x18, 0x01, 0x5c, 0x00}, {0x1f, 0x01, 0x5c, 0x00},
+        {0x29, 0x01, 0x5c, 0x00}, {0x38, 0x01, 0x5c, 0x01},
+        {0x03, 0x01, 0xc3, 0x00}, {0x06, 0x01, 0xc3, 0x00},
+        {0x0a, 0x01, 0xc3, 0x00}, {0x0f, 0x01, 0xc3, 0x00},
+        {0x18, 0x01, 0xc3, 0x00}, {0x1f, 0x01, 0xc3, 0x00},
+        {0x29, 0x01, 0xc3, 0x00}, {0x38, 0x01, 0xc3, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xd0, 0x00}, {0x06, 0x01, 0xd0, 0x00},
+        {0x0a, 0x01, 0xd0, 0x00}, {0x0f, 0x01, 0xd0, 0x00},
+        {0x18, 0x01, 0xd0, 0x00}, {0x1f, 0x01, 0xd0, 0x00},
+        {0x29, 0x01, 0xd0, 0x00}, {0x38, 0x01, 0xd0, 0x01},
+        {0x02, 0x01, 0x80, 0x00}, {0x09, 0x01, 0x80, 0x00},
+        {0x17, 0x01, 0x80, 0x00}, {0x28, 0x01, 0x80, 0x01},
+        {0x02, 0x01, 0x82, 0x00}, {0x09, 0x01, 0x82, 0x00},
+        {0x17, 0x01, 0x82, 0x00}, {0x28, 0x01, 0x82, 0x01}
+    },
+    /* 100 */
+    {
+        {0x03, 0x01, 0x80, 0x00}, {0x06, 0x01, 0x80, 0x00},
+        {0x0a, 0x01, 0x80, 0x00}, {0x0f, 0x01, 0x80, 0x00},
+        {0x18, 0x01, 0x80, 0x00}, {0x1f, 0x01, 0x80, 0x00},
+        {0x29, 0x01, 0x80, 0x00}, {0x38, 0x01, 0x80, 0x01},
+        {0x03, 0x01, 0x82, 0x00}, {0x06, 0x01, 0x82, 0x00},
+        {0x0a, 0x01, 0x82, 0x00}, {0x0f, 0x01, 0x82, 0x00},
+        {0x18, 0x01, 0x82, 0x00}, {0x1f, 0x01, 0x82, 0x00},
+        {0x29, 0x01, 0x82, 0x00}, {0x38, 0x01, 0x82, 0x01}
+    },
+    {
+        {0x01, 0x01, 0x83, 0x00}, {0x16, 0x01, 0x83, 0x01},
+        {0x01, 0x01, 0xa2, 0x00}, {0x16, 0x01, 0xa2, 0x01},
+        {0x01, 0x01, 0xb8, 0x00}, {0x16, 0x01, 0xb8, 0x01},
+        {0x01, 0x01, 0xc2, 0x00}, {0x16, 0x01, 0xc2, 0x01},
+        {0x01, 0x01, 0xe0, 0x00}, {0x16, 0x01, 0xe0, 0x01},
+        {0x01, 0x01, 0xe2, 0x00}, {0x16, 0x01, 0xe2, 0x01},
+        {0x00, 0x01, 0x99, 0x01}, {0x00, 0x01, 0xa1, 0x01},
+        {0x00, 0x01, 0xa7, 0x01}, {0x00, 0x01, 0xac, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x83, 0x00}, {0x09, 0x01, 0x83, 0x00},
+        {0x17, 0x01, 0x83, 0x00}, {0x28, 0x01, 0x83, 0x01},
+        {0x02, 0x01, 0xa2, 0x00}, {0x09, 0x01, 0xa2, 0x00},
+        {0x17, 0x01, 0xa2, 0x00}, {0x28, 0x01, 0xa2, 0x01},
+        {0x02, 0x01, 0xb8, 0x00}, {0x09, 0x01, 0xb8, 0x00},
+        {0x17, 0x01, 0xb8, 0x00}, {0x28, 0x01, 0xb8, 0x01},
+        {0x02, 0x01, 0xc2, 0x00}, {0x09, 0x01, 0xc2, 0x00},
+        {0x17, 0x01, 0xc2, 0x00}, {0x28, 0x01, 0xc2, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x83, 0x00}, {0x06, 0x01, 0x83, 0x00},
+        {0x0a, 0x01, 0x83, 0x00}, {0x0f, 0x01, 0x83, 0x00},
+        {0x18, 0x01, 0x83, 0x00}, {0x1f, 0x01, 0x83, 0x00},
+        {0x29, 0x01, 0x83, 0x00}, {0x38, 0x01, 0x83, 0x01},
+        {0x03, 0x01, 0xa2, 0x00}, {0x06, 0x01, 0xa2, 0x00},
+        {0x0a, 0x01, 0xa2, 0x00}, {0x0f, 0x01, 0xa2, 0x00},
+        {0x18, 0x01, 0xa2, 0x00}, {0x1f, 0x01, 0xa2, 0x00},
+        {0x29, 0x01, 0xa2, 0x00}, {0x38, 0x01, 0xa2, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xb8, 0x00}, {0x06, 0x01, 0xb8, 0x00},
+        {0x0a, 0x01, 0xb8, 0x00}, {0x0f, 0x01, 0xb8, 0x00},
+        {0x18, 0x01, 0xb8, 0x00}, {0x1f, 0x01, 0xb8, 0x00},
+        {0x29, 0x01, 0xb8, 0x00}, {0x38, 0x01, 0xb8, 0x01},
+        {0x03, 0x01, 0xc2, 0x00}, {0x06, 0x01, 0xc2, 0x00},
+        {0x0a, 0x01, 0xc2, 0x00}, {0x0f, 0x01, 0xc2, 0x00},
+        {0x18, 0x01, 0xc2, 0x00}, {0x1f, 0x01, 0xc2, 0x00},
+        {0x29, 0x01, 0xc2, 0x00}, {0x38, 0x01, 0xc2, 0x01}
+    },
+    /* 105 */
+    {
+        {0x02, 0x01, 0xe0, 0x00}, {0x09, 0x01, 0xe0, 0x00},
+        {0x17, 0x01, 0xe0, 0x00}, {0x28, 0x01, 0xe0, 0x01},
+        {0x02, 0x01, 0xe2, 0x00}, {0x09, 0x01, 0xe2, 0x00},
+        {0x17, 0x01, 0xe2, 0x00}, {0x28, 0x01, 0xe2, 0x01},
+        {0x01, 0x01, 0x99, 0x00}, {0x16, 0x01, 0x99, 0x01},
+        {0x01, 0x01, 0xa1, 0x00}, {0x16, 0x01, 0xa1, 0x01},
+        {0x01, 0x01, 0xa7, 0x00}, {0x16, 0x01, 0xa7, 0x01},
+        {0x01, 0x01, 0xac, 0x00}, {0x16, 0x01, 0xac, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xe0, 0x00}, {0x06, 0x01, 0xe0, 0x00},
+        {0x0a, 0x01, 0xe0, 0x00}, {0x0f, 0x01, 0xe0, 0x00},
+        {0x18, 0x01, 0xe0, 0x00}, {0x1f, 0x01, 0xe0, 0x00},
+        {0x29, 0x01, 0xe0, 0x00}, {0x38, 0x01, 0xe0, 0x01},
+        {0x03, 0x01, 0xe2, 0x00}, {0x06, 0x01, 0xe2, 0x00},
+        {0x0a, 0x01, 0xe2, 0x00}, {0x0f, 0x01, 0xe2, 0x00},
+        {0x18, 0x01, 0xe2, 0x00}, {0x1f, 0x01, 0xe2, 0x00},
+        {0x29, 0x01, 0xe2, 0x00}, {0x38, 0x01, 0xe2, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x99, 0x00}, {0x09, 0x01, 0x99, 0x00},
+        {0x17, 0x01, 0x99, 0x00}, {0x28, 0x01, 0x99, 0x01},
+        {0x02, 0x01, 0xa1, 0x00}, {0x09, 0x01, 0xa1, 0x00},
+        {0x17, 0x01, 0xa1, 0x00}, {0x28, 0x01, 0xa1, 0x01},
+        {0x02, 0x01, 0xa7, 0x00}, {0x09, 0x01, 0xa7, 0x00},
+        {0x17, 0x01, 0xa7, 0x00}, {0x28, 0x01, 0xa7, 0x01},
+        {0x02, 0x01, 0xac, 0x00}, {0x09, 0x01, 0xac, 0x00},
+        {0x17, 0x01, 0xac, 0x00}, {0x28, 0x01, 0xac, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x99, 0x00}, {0x06, 0x01, 0x99, 0x00},
+        {0x0a, 0x01, 0x99, 0x00}, {0x0f, 0x01, 0x99, 0x00},
+        {0x18, 0x01, 0x99, 0x00}, {0x1f, 0x01, 0x99, 0x00},
+        {0x29, 0x01, 0x99, 0x00}, {0x38, 0x01, 0x99, 0x01},
+        {0x03, 0x01, 0xa1, 0x00}, {0x06, 0x01, 0xa1, 0x00},
+        {0x0a, 0x01, 0xa1, 0x00}, {0x0f, 0x01, 0xa1, 0x00},
+        {0x18, 0x01, 0xa1, 0x00}, {0x1f, 0x01, 0xa1, 0x00},
+        {0x29, 0x01, 0xa1, 0x00}, {0x38, 0x01, 0xa1, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xa7, 0x00}, {0x06, 0x01, 0xa7, 0x00},
+        {0x0a, 0x01, 0xa7, 0x00}, {0x0f, 0x01, 0xa7, 0x00},
+        {0x18, 0x01, 0xa7, 0x00}, {0x1f, 0x01, 0xa7, 0x00},
+        {0x29, 0x01, 0xa7, 0x00}, {0x38, 0x01, 0xa7, 0x01},
+        {0x03, 0x01, 0xac, 0x00}, {0x06, 0x01, 0xac, 0x00},
+        {0x0a, 0x01, 0xac, 0x00}, {0x0f, 0x01, 0xac, 0x00},
+        {0x18, 0x01, 0xac, 0x00}, {0x1f, 0x01, 0xac, 0x00},
+        {0x29, 0x01, 0xac, 0x00}, {0x38, 0x01, 0xac, 0x01}
+    },
+    /* 110 */
+    {
+        {0x72, 0x00, 0x00, 0x00}, {0x73, 0x00, 0x00, 0x00},
+        {0x75, 0x00, 0x00, 0x00}, {0x76, 0x00, 0x00, 0x00},
+        {0x79, 0x00, 0x00, 0x00}, {0x7b, 0x00, 0x00, 0x00},
+        {0x7f, 0x00, 0x00, 0x00}, {0x82, 0x00, 0x00, 0x00},
+        {0x88, 0x00, 0x00, 0x00}, {0x8b, 0x00, 0x00, 0x00},
+        {0x8f, 0x00, 0x00, 0x00}, {0x92, 0x00, 0x00, 0x00},
+        {0x9b, 0x00, 0x00, 0x00}, {0xa2, 0x00, 0x00, 0x00},
+        {0xaa, 0x00, 0x00, 0x00}, {0xb4, 0x00, 0x00, 0x01}
+    },
+    {
+        {0x00, 0x01, 0xb0, 0x01}, {0x00, 0x01, 0xb1, 0x01},
+        {0x00, 0x01, 0xb3, 0x01}, {0x00, 0x01, 0xd1, 0x01},
+        {0x00, 0x01, 0xd8, 0x01}, {0x00, 0x01, 0xd9, 0x01},
+        {0x00, 0x01, 0xe3, 0x01}, {0x00, 0x01, 0xe5, 0x01},
+        {0x00, 0x01, 0xe6, 0x01}, {0x7a, 0x00, 0x00, 0x00},
+        {0x7c, 0x00, 0x00, 0x00}, {0x7d, 0x00, 0x00, 0x00},
+        {0x80, 0x00, 0x00, 0x00}, {0x81, 0x00, 0x00, 0x00},
+        {0x83, 0x00, 0x00, 0x00}, {0x84, 0x00, 0x00, 0x00}
+    },
+    {
+        {0x01, 0x01, 0xb0, 0x00}, {0x16, 0x01, 0xb0, 0x01},
+        {0x01, 0x01, 0xb1, 0x00}, {0x16, 0x01, 0xb1, 0x01},
+        {0x01, 0x01, 0xb3, 0x00}, {0x16, 0x01, 0xb3, 0x01},
+        {0x01, 0x01, 0xd1, 0x00}, {0x16, 0x01, 0xd1, 0x01},
+        {0x01, 0x01, 0xd8, 0x00}, {0x16, 0x01, 0xd8, 0x01},
+        {0x01, 0x01, 0xd9, 0x00}, {0x16, 0x01, 0xd9, 0x01},
+        {0x01, 0x01, 0xe3, 0x00}, {0x16, 0x01, 0xe3, 0x01},
+        {0x01, 0x01, 0xe5, 0x00}, {0x16, 0x01, 0xe5, 0x01}
+    },
+    {
+        {0x02, 0x01, 0xb0, 0x00}, {0x09, 0x01, 0xb0, 0x00},
+        {0x17, 0x01, 0xb0, 0x00}, {0x28, 0x01, 0xb0, 0x01},
+        {0x02, 0x01, 0xb1, 0x00}, {0x09, 0x01, 0xb1, 0x00},
+        {0x17, 0x01, 0xb1, 0x00}, {0x28, 0x01, 0xb1, 0x01},
+        {0x02, 0x01, 0xb3, 0x00}, {0x09, 0x01, 0xb3, 0x00},
+        {0x17, 0x01, 0xb3, 0x00}, {0x28, 0x01, 0xb3, 0x01},
+        {0x02, 0x01, 0xd1, 0x00}, {0x09, 0x01, 0xd1, 0x00},
+        {0x17, 0x01, 0xd1, 0x00}, {0x28, 0x01, 0xd1, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xb0, 0x00}, {0x06, 0x01, 0xb0, 0x00},
+        {0x0a, 0x01, 0xb0, 0x00}, {0x0f, 0x01, 0xb0, 0x00},
+        {0x18, 0x01, 0xb0, 0x00}, {0x1f, 0x01, 0xb0, 0x00},
+        {0x29, 0x01, 0xb0, 0x00}, {0x38, 0x01, 0xb0, 0x01},
+        {0x03, 0x01, 0xb1, 0x00}, {0x06, 0x01, 0xb1, 0x00},
+        {0x0a, 0x01, 0xb1, 0x00}, {0x0f, 0x01, 0xb1, 0x00},
+        {0x18, 0x01, 0xb1, 0x00}, {0x1f, 0x01, 0xb1, 0x00},
+        {0x29, 0x01, 0xb1, 0x00}, {0x38, 0x01, 0xb1, 0x01}
+    },
+    /* 115 */
+    {
+        {0x03, 0x01, 0xb3, 0x00}, {0x06, 0x01, 0xb3, 0x00},
+        {0x0a, 0x01, 0xb3, 0x00}, {0x0f, 0x01, 0xb3, 0x00},
+        {0x18, 0x01, 0xb3, 0x00}, {0x1f, 0x01, 0xb3, 0x00},
+        {0x29, 0x01, 0xb3, 0x00}, {0x38, 0x01, 0xb3, 0x01},
+        {0x03, 0x01, 0xd1, 0x00}, {0x06, 0x01, 0xd1, 0x00},
+        {0x0a, 0x01, 0xd1, 0x00}, {0x0f, 0x01, 0xd1, 0x00},
+        {0x18, 0x01, 0xd1, 0x00}, {0x1f, 0x01, 0xd1, 0x00},
+        {0x29, 0x01, 0xd1, 0x00}, {0x38, 0x01, 0xd1, 0x01}
+    },
+    {
+        {0x02, 0x01, 0xd8, 0x00}, {0x09, 0x01, 0xd8, 0x00},
+        {0x17, 0x01, 0xd8, 0x00}, {0x28, 0x01, 0xd8, 0x01},
+        {0x02, 0x01, 0xd9, 0x00}, {0x09, 0x01, 0xd9, 0x00},
+        {0x17, 0x01, 0xd9, 0x00}, {0x28, 0x01, 0xd9, 0x01},
+        {0x02, 0x01, 0xe3, 0x00}, {0x09, 0x01, 0xe3, 0x00},
+        {0x17, 0x01, 0xe3, 0x00}, {0x28, 0x01, 0xe3, 0x01},
+        {0x02, 0x01, 0xe5, 0x00}, {0x09, 0x01, 0xe5, 0x00},
+        {0x17, 0x01, 0xe5, 0x00}, {0x28, 0x01, 0xe5, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xd8, 0x00}, {0x06, 0x01, 0xd8, 0x00},
+        {0x0a, 0x01, 0xd8, 0x00}, {0x0f, 0x01, 0xd8, 0x00},
+        {0x18, 0x01, 0xd8, 0x00}, {0x1f, 0x01, 0xd8, 0x00},
+        {0x29, 0x01, 0xd8, 0x00}, {0x38, 0x01, 0xd8, 0x01},
+        {0x03, 0x01, 0xd9, 0x00}, {0x06, 0x01, 0xd9, 0x00},
+        {0x0a, 0x01, 0xd9, 0x00}, {0x0f, 0x01, 0xd9, 0x00},
+        {0x18, 0x01, 0xd9, 0x00}, {0x1f, 0x01, 0xd9, 0x00},
+        {0x29, 0x01, 0xd9, 0x00}, {0x38, 0x01, 0xd9, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xe3, 0x00}, {0x06, 0x01, 0xe3, 0x00},
+        {0x0a, 0x01, 0xe3, 0x00}, {0x0f, 0x01, 0xe3, 0x00},
+        {0x18, 0x01, 0xe3, 0x00}, {0x1f, 0x01, 0xe3, 0x00},
+        {0x29, 0x01, 0xe3, 0x00}, {0x38, 0x01, 0xe3, 0x01},
+        {0x03, 0x01, 0xe5, 0x00}, {0x06, 0x01, 0xe5, 0x00},
+        {0x0a, 0x01, 0xe5, 0x00}, {0x0f, 0x01, 0xe5, 0x00},
+        {0x18, 0x01, 0xe5, 0x00}, {0x1f, 0x01, 0xe5, 0x00},
+        {0x29, 0x01, 0xe5, 0x00}, {0x38, 0x01, 0xe5, 0x01}
+    },
+    {
+        {0x01, 0x01, 0xe6, 0x00}, {0x16, 0x01, 0xe6, 0x01},
+        {0x00, 0x01, 0x81, 0x01}, {0x00, 0x01, 0x84, 0x01},
+        {0x00, 0x01, 0x85, 0x01}, {0x00, 0x01, 0x86, 0x01},
+        {0x00, 0x01, 0x88, 0x01}, {0x00, 0x01, 0x92, 0x01},
+        {0x00, 0x01, 0x9a, 0x01}, {0x00, 0x01, 0x9c, 0x01},
+        {0x00, 0x01, 0xa0, 0x01}, {0x00, 0x01, 0xa3, 0x01},
+        {0x00, 0x01, 0xa4, 0x01}, {0x00, 0x01, 0xa9, 0x01},
+        {0x00, 0x01, 0xaa, 0x01}, {0x00, 0x01, 0xad, 0x01}
+    },
+    /* 120 */
+    {
+        {0x02, 0x01, 0xe6, 0x00}, {0x09, 0x01, 0xe6, 0x00},
+        {0x17, 0x01, 0xe6, 0x00}, {0x28, 0x01, 0xe6, 0x01},
+        {0x01, 0x01, 0x81, 0x00}, {0x16, 0x01, 0x81, 0x01},
+        {0x01, 0x01, 0x84, 0x00}, {0x16, 0x01, 0x84, 0x01},
+        {0x01, 0x01, 0x85, 0x00}, {0x16, 0x01, 0x85, 0x01},
+        {0x01, 0x01, 0x86, 0x00}, {0x16, 0x01, 0x86, 0x01},
+        {0x01, 0x01, 0x88, 0x00}, {0x16, 0x01, 0x88, 0x01},
+        {0x01, 0x01, 0x92, 0x00}, {0x16, 0x01, 0x92, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xe6, 0x00}, {0x06, 0x01, 0xe6, 0x00},
+        {0x0a, 0x01, 0xe6, 0x00}, {0x0f, 0x01, 0xe6, 0x00},
+        {0x18, 0x01, 0xe6, 0x00}, {0x1f, 0x01, 0xe6, 0x00},
+        {0x29, 0x01, 0xe6, 0x00}, {0x38, 0x01, 0xe6, 0x01},
+        {0x02, 0x01, 0x81, 0x00}, {0x09, 0x01, 0x81, 0x00},
+        {0x17, 0x01, 0x81, 0x00}, {0x28, 0x01, 0x81, 0x01},
+        {0x02, 0x01, 0x84, 0x00}, {0x09, 0x01, 0x84, 0x00},
+        {0x17, 0x01, 0x84, 0x00}, {0x28, 0x01, 0x84, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x81, 0x00}, {0x06, 0x01, 0x81, 0x00},
+        {0x0a, 0x01, 0x81, 0x00}, {0x0f, 0x01, 0x81, 0x00},
+        {0x18, 0x01, 0x81, 0x00}, {0x1f, 0x01, 0x81, 0x00},
+        {0x29, 0x01, 0x81, 0x00}, {0x38, 0x01, 0x81, 0x01},
+        {0x03, 0x01, 0x84, 0x00}, {0x06, 0x01, 0x84, 0x00},
+        {0x0a, 0x01, 0x84, 0x00}, {0x0f, 0x01, 0x84, 0x00},
+        {0x18, 0x01, 0x84, 0x00}, {0x1f, 0x01, 0x84, 0x00},
+        {0x29, 0x01, 0x84, 0x00}, {0x38, 0x01, 0x84, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x85, 0x00}, {0x09, 0x01, 0x85, 0x00},
+        {0x17, 0x01, 0x85, 0x00}, {0x28, 0x01, 0x85, 0x01},
+        {0x02, 0x01, 0x86, 0x00}, {0x09, 0x01, 0x86, 0x00},
+        {0x17, 0x01, 0x86, 0x00}, {0x28, 0x01, 0x86, 0x01},
+        {0x02, 0x01, 0x88, 0x00}, {0x09, 0x01, 0x88, 0x00},
+        {0x17, 0x01, 0x88, 0x00}, {0x28, 0x01, 0x88, 0x01},
+        {0x02, 0x01, 0x92, 0x00}, {0x09, 0x01, 0x92, 0x00},
+        {0x17, 0x01, 0x92, 0x00}, {0x28, 0x01, 0x92, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x85, 0x00}, {0x06, 0x01, 0x85, 0x00},
+        {0x0a, 0x01, 0x85, 0x00}, {0x0f, 0x01, 0x85, 0x00},
+        {0x18, 0x01, 0x85, 0x00}, {0x1f, 0x01, 0x85, 0x00},
+        {0x29, 0x01, 0x85, 0x00}, {0x38, 0x01, 0x85, 0x01},
+        {0x03, 0x01, 0x86, 0x00}, {0x06, 0x01, 0x86, 0x00},
+        {0x0a, 0x01, 0x86, 0x00}, {0x0f, 0x01, 0x86, 0x00},
+        {0x18, 0x01, 0x86, 0x00}, {0x1f, 0x01, 0x86, 0x00},
+        {0x29, 0x01, 0x86, 0x00}, {0x38, 0x01, 0x86, 0x01}
+    },
+    /* 125 */
+    {
+        {0x03, 0x01, 0x88, 0x00}, {0x06, 0x01, 0x88, 0x00},
+        {0x0a, 0x01, 0x88, 0x00}, {0x0f, 0x01, 0x88, 0x00},
+        {0x18, 0x01, 0x88, 0x00}, {0x1f, 0x01, 0x88, 0x00},
+        {0x29, 0x01, 0x88, 0x00}, {0x38, 0x01, 0x88, 0x01},
+        {0x03, 0x01, 0x92, 0x00}, {0x06, 0x01, 0x92, 0x00},
+        {0x0a, 0x01, 0x92, 0x00}, {0x0f, 0x01, 0x92, 0x00},
+        {0x18, 0x01, 0x92, 0x00}, {0x1f, 0x01, 0x92, 0x00},
+        {0x29, 0x01, 0x92, 0x00}, {0x38, 0x01, 0x92, 0x01}
+    },
+    {
+        {0x01, 0x01, 0x9a, 0x00}, {0x16, 0x01, 0x9a, 0x01},
+        {0x01, 0x01, 0x9c, 0x00}, {0x16, 0x01, 0x9c, 0x01},
+        {0x01, 0x01, 0xa0, 0x00}, {0x16, 0x01, 0xa0, 0x01},
+        {0x01, 0x01, 0xa3, 0x00}, {0x16, 0x01, 0xa3, 0x01},
+        {0x01, 0x01, 0xa4, 0x00}, {0x16, 0x01, 0xa4, 0x01},
+        {0x01, 0x01, 0xa9, 0x00}, {0x16, 0x01, 0xa9, 0x01},
+        {0x01, 0x01, 0xaa, 0x00}, {0x16, 0x01, 0xaa, 0x01},
+        {0x01, 0x01, 0xad, 0x00}, {0x16, 0x01, 0xad, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x9a, 0x00}, {0x09, 0x01, 0x9a, 0x00},
+        {0x17, 0x01, 0x9a, 0x00}, {0x28, 0x01, 0x9a, 0x01},
+        {0x02, 0x01, 0x9c, 0x00}, {0x09, 0x01, 0x9c, 0x00},
+        {0x17, 0x01, 0x9c, 0x00}, {0x28, 0x01, 0x9c, 0x01},
+        {0x02, 0x01, 0xa0, 0x00}, {0x09, 0x01, 0xa0, 0x00},
+        {0x17, 0x01, 0xa0, 0x00}, {0x28, 0x01, 0xa0, 0x01},
+        {0x02, 0x01, 0xa3, 0x00}, {0x09, 0x01, 0xa3, 0x00},
+        {0x17, 0x01, 0xa3, 0x00}, {0x28, 0x01, 0xa3, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x9a, 0x00}, {0x06, 0x01, 0x9a, 0x00},
+        {0x0a, 0x01, 0x9a, 0x00}, {0x0f, 0x01, 0x9a, 0x00},
+        {0x18, 0x01, 0x9a, 0x00}, {0x1f, 0x01, 0x9a, 0x00},
+        {0x29, 0x01, 0x9a, 0x00}, {0x38, 0x01, 0x9a, 0x01},
+        {0x03, 0x01, 0x9c, 0x00}, {0x06, 0x01, 0x9c, 0x00},
+        {0x0a, 0x01, 0x9c, 0x00}, {0x0f, 0x01, 0x9c, 0x00},
+        {0x18, 0x01, 0x9c, 0x00}, {0x1f, 0x01, 0x9c, 0x00},
+        {0x29, 0x01, 0x9c, 0x00}, {0x38, 0x01, 0x9c, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xa0, 0x00}, {0x06, 0x01, 0xa0, 0x00},
+        {0x0a, 0x01, 0xa0, 0x00}, {0x0f, 0x01, 0xa0, 0x00},
+        {0x18, 0x01, 0xa0, 0x00}, {0x1f, 0x01, 0xa0, 0x00},
+        {0x29, 0x01, 0xa0, 0x00}, {0x38, 0x01, 0xa0, 0x01},
+        {0x03, 0x01, 0xa3, 0x00}, {0x06, 0x01, 0xa3, 0x00},
+        {0x0a, 0x01, 0xa3, 0x00}, {0x0f, 0x01, 0xa3, 0x00},
+        {0x18, 0x01, 0xa3, 0x00}, {0x1f, 0x01, 0xa3, 0x00},
+        {0x29, 0x01, 0xa3, 0x00}, {0x38, 0x01, 0xa3, 0x01}
+    },
+    /* 130 */
+    {
+        {0x02, 0x01, 0xa4, 0x00}, {0x09, 0x01, 0xa4, 0x00},
+        {0x17, 0x01, 0xa4, 0x00}, {0x28, 0x01, 0xa4, 0x01},
+        {0x02, 0x01, 0xa9, 0x00}, {0x09, 0x01, 0xa9, 0x00},
+        {0x17, 0x01, 0xa9, 0x00}, {0x28, 0x01, 0xa9, 0x01},
+        {0x02, 0x01, 0xaa, 0x00}, {0x09, 0x01, 0xaa, 0x00},
+        {0x17, 0x01, 0xaa, 0x00}, {0x28, 0x01, 0xaa, 0x01},
+        {0x02, 0x01, 0xad, 0x00}, {0x09, 0x01, 0xad, 0x00},
+        {0x17, 0x01, 0xad, 0x00}, {0x28, 0x01, 0xad, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xa4, 0x00}, {0x06, 0x01, 0xa4, 0x00},
+        {0x0a, 0x01, 0xa4, 0x00}, {0x0f, 0x01, 0xa4, 0x00},
+        {0x18, 0x01, 0xa4, 0x00}, {0x1f, 0x01, 0xa4, 0x00},
+        {0x29, 0x01, 0xa4, 0x00}, {0x38, 0x01, 0xa4, 0x01},
+        {0x03, 0x01, 0xa9, 0x00}, {0x06, 0x01, 0xa9, 0x00},
+        {0x0a, 0x01, 0xa9, 0x00}, {0x0f, 0x01, 0xa9, 0x00},
+        {0x18, 0x01, 0xa9, 0x00}, {0x1f, 0x01, 0xa9, 0x00},
+        {0x29, 0x01, 0xa9, 0x00}, {0x38, 0x01, 0xa9, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xaa, 0x00}, {0x06, 0x01, 0xaa, 0x00},
+        {0x0a, 0x01, 0xaa, 0x00}, {0x0f, 0x01, 0xaa, 0x00},
+        {0x18, 0x01, 0xaa, 0x00}, {0x1f, 0x01, 0xaa, 0x00},
+        {0x29, 0x01, 0xaa, 0x00}, {0x38, 0x01, 0xaa, 0x01},
+        {0x03, 0x01, 0xad, 0x00}, {0x06, 0x01, 0xad, 0x00},
+        {0x0a, 0x01, 0xad, 0x00}, {0x0f, 0x01, 0xad, 0x00},
+        {0x18, 0x01, 0xad, 0x00}, {0x1f, 0x01, 0xad, 0x00},
+        {0x29, 0x01, 0xad, 0x00}, {0x38, 0x01, 0xad, 0x01}
+    },
+    {
+        {0x89, 0x00, 0x00, 0x00}, {0x8a, 0x00, 0x00, 0x00},
+        {0x8c, 0x00, 0x00, 0x00}, {0x8d, 0x00, 0x00, 0x00},
+        {0x90, 0x00, 0x00, 0x00}, {0x91, 0x00, 0x00, 0x00},
+        {0x93, 0x00, 0x00, 0x00}, {0x96, 0x00, 0x00, 0x00},
+        {0x9c, 0x00, 0x00, 0x00}, {0x9f, 0x00, 0x00, 0x00},
+        {0xa3, 0x00, 0x00, 0x00}, {0xa6, 0x00, 0x00, 0x00},
+        {0xab, 0x00, 0x00, 0x00}, {0xae, 0x00, 0x00, 0x00},
+        {0xb5, 0x00, 0x00, 0x00}, {0xbe, 0x00, 0x00, 0x01}
+    },
+    {
+        {0x00, 0x01, 0xb2, 0x01}, {0x00, 0x01, 0xb5, 0x01},
+        {0x00, 0x01, 0xb9, 0x01}, {0x00, 0x01, 0xba, 0x01},
+        {0x00, 0x01, 0xbb, 0x01}, {0x00, 0x01, 0xbd, 0x01},
+        {0x00, 0x01, 0xbe, 0x01}, {0x00, 0x01, 0xc4, 0x01},
+        {0x00, 0x01, 0xc6, 0x01}, {0x00, 0x01, 0xe4, 0x01},
+        {0x00, 0x01, 0xe8, 0x01}, {0x00, 0x01, 0xe9, 0x01},
+        {0x94, 0x00, 0x00, 0x00}, {0x95, 0x00, 0x00, 0x00},
+        {0x97, 0x00, 0x00, 0x00}, {0x98, 0x00, 0x00, 0x00}
+    },
+    /* 135 */
+    {
+        {0x01, 0x01, 0xb2, 0x00}, {0x16, 0x01, 0xb2, 0x01},
+        {0x01, 0x01, 0xb5, 0x00}, {0x16, 0x01, 0xb5, 0x01},
+        {0x01, 0x01, 0xb9, 0x00}, {0x16, 0x01, 0xb9, 0x01},
+        {0x01, 0x01, 0xba, 0x00}, {0x16, 0x01, 0xba, 0x01},
+        {0x01, 0x01, 0xbb, 0x00}, {0x16, 0x01, 0xbb, 0x01},
+        {0x01, 0x01, 0xbd, 0x00}, {0x16, 0x01, 0xbd, 0x01},
+        {0x01, 0x01, 0xbe, 0x00}, {0x16, 0x01, 0xbe, 0x01},
+        {0x01, 0x01, 0xc4, 0x00}, {0x16, 0x01, 0xc4, 0x01}
+    },
+    {
+        {0x02, 0x01, 0xb2, 0x00}, {0x09, 0x01, 0xb2, 0x00},
+        {0x17, 0x01, 0xb2, 0x00}, {0x28, 0x01, 0xb2, 0x01},
+        {0x02, 0x01, 0xb5, 0x00}, {0x09, 0x01, 0xb5, 0x00},
+        {0x17, 0x01, 0xb5, 0x00}, {0x28, 0x01, 0xb5, 0x01},
+        {0x02, 0x01, 0xb9, 0x00}, {0x09, 0x01, 0xb9, 0x00},
+        {0x17, 0x01, 0xb9, 0x00}, {0x28, 0x01, 0xb9, 0x01},
+        {0x02, 0x01, 0xba, 0x00}, {0x09, 0x01, 0xba, 0x00},
+        {0x17, 0x01, 0xba, 0x00}, {0x28, 0x01, 0xba, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xb2, 0x00}, {0x06, 0x01, 0xb2, 0x00},
+        {0x0a, 0x01, 0xb2, 0x00}, {0x0f, 0x01, 0xb2, 0x00},
+        {0x18, 0x01, 0xb2, 0x00}, {0x1f, 0x01, 0xb2, 0x00},
+        {0x29, 0x01, 0xb2, 0x00}, {0x38, 0x01, 0xb2, 0x01},
+        {0x03, 0x01, 0xb5, 0x00}, {0x06, 0x01, 0xb5, 0x00},
+        {0x0a, 0x01, 0xb5, 0x00}, {0x0f, 0x01, 0xb5, 0x00},
+        {0x18, 0x01, 0xb5, 0x00}, {0x1f, 0x01, 0xb5, 0x00},
+        {0x29, 0x01, 0xb5, 0x00}, {0x38, 0x01, 0xb5, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xb9, 0x00}, {0x06, 0x01, 0xb9, 0x00},
+        {0x0a, 0x01, 0xb9, 0x00}, {0x0f, 0x01, 0xb9, 0x00},
+        {0x18, 0x01, 0xb9, 0x00}, {0x1f, 0x01, 0xb9, 0x00},
+        {0x29, 0x01, 0xb9, 0x00}, {0x38, 0x01, 0xb9, 0x01},
+        {0x03, 0x01, 0xba, 0x00}, {0x06, 0x01, 0xba, 0x00},
+        {0x0a, 0x01, 0xba, 0x00}, {0x0f, 0x01, 0xba, 0x00},
+        {0x18, 0x01, 0xba, 0x00}, {0x1f, 0x01, 0xba, 0x00},
+        {0x29, 0x01, 0xba, 0x00}, {0x38, 0x01, 0xba, 0x01}
+    },
+    {
+        {0x02, 0x01, 0xbb, 0x00}, {0x09, 0x01, 0xbb, 0x00},
+        {0x17, 0x01, 0xbb, 0x00}, {0x28, 0x01, 0xbb, 0x01},
+        {0x02, 0x01, 0xbd, 0x00}, {0x09, 0x01, 0xbd, 0x00},
+        {0x17, 0x01, 0xbd, 0x00}, {0x28, 0x01, 0xbd, 0x01},
+        {0x02, 0x01, 0xbe, 0x00}, {0x09, 0x01, 0xbe, 0x00},
+        {0x17, 0x01, 0xbe, 0x00}, {0x28, 0x01, 0xbe, 0x01},
+        {0x02, 0x01, 0xc4, 0x00}, {0x09, 0x01, 0xc4, 0x00},
+        {0x17, 0x01, 0xc4, 0x00}, {0x28, 0x01, 0xc4, 0x01}
+    },
+    /* 140 */
+    {
+        {0x03, 0x01, 0xbb, 0x00}, {0x06, 0x01, 0xbb, 0x00},
+        {0x0a, 0x01, 0xbb, 0x00}, {0x0f, 0x01, 0xbb, 0x00},
+        {0x18, 0x01, 0xbb, 0x00}, {0x1f, 0x01, 0xbb, 0x00},
+        {0x29, 0x01, 0xbb, 0x00}, {0x38, 0x01, 0xbb, 0x01},
+        {0x03, 0x01, 0xbd, 0x00}, {0x06, 0x01, 0xbd, 0x00},
+        {0x0a, 0x01, 0xbd, 0x00}, {0x0f, 0x01, 0xbd, 0x00},
+        {0x18, 0x01, 0xbd, 0x00}, {0x1f, 0x01, 0xbd, 0x00},
+        {0x29, 0x01, 0xbd, 0x00}, {0x38, 0x01, 0xbd, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xbe, 0x00}, {0x06, 0x01, 0xbe, 0x00},
+        {0x0a, 0x01, 0xbe, 0x00}, {0x0f, 0x01, 0xbe, 0x00},
+        {0x18, 0x01, 0xbe, 0x00}, {0x1f, 0x01, 0xbe, 0x00},
+        {0x29, 0x01, 0xbe, 0x00}, {0x38, 0x01, 0xbe, 0x01},
+        {0x03, 0x01, 0xc4, 0x00}, {0x06, 0x01, 0xc4, 0x00},
+        {0x0a, 0x01, 0xc4, 0x00}, {0x0f, 0x01, 0xc4, 0x00},
+        {0x18, 0x01, 0xc4, 0x00}, {0x1f, 0x01, 0xc4, 0x00},
+        {0x29, 0x01, 0xc4, 0x00}, {0x38, 0x01, 0xc4, 0x01}
+    },
+    {
+        {0x01, 0x01, 0xc6, 0x00}, {0x16, 0x01, 0xc6, 0x01},
+        {0x01, 0x01, 0xe4, 0x00}, {0x16, 0x01, 0xe4, 0x01},
+        {0x01, 0x01, 0xe8, 0x00}, {0x16, 0x01, 0xe8, 0x01},
+        {0x01, 0x01, 0xe9, 0x00}, {0x16, 0x01, 0xe9, 0x01},
+        {0x00, 0x01, 0x01, 0x01}, {0x00, 0x01, 0x87, 0x01},
+        {0x00, 0x01, 0x89, 0x01}, {0x00, 0x01, 0x8a, 0x01},
+        {0x00, 0x01, 0x8b, 0x01}, {0x00, 0x01, 0x8c, 0x01},
+        {0x00, 0x01, 0x8d, 0x01}, {0x00, 0x01, 0x8f, 0x01}
+    },
+    {
+        {0x02, 0x01, 0xc6, 0x00}, {0x09, 0x01, 0xc6, 0x00},
+        {0x17, 0x01, 0xc6, 0x00}, {0x28, 0x01, 0xc6, 0x01},
+        {0x02, 0x01, 0xe4, 0x00}, {0x09, 0x01, 0xe4, 0x00},
+        {0x17, 0x01, 0xe4, 0x00}, {0x28, 0x01, 0xe4, 0x01},
+        {0x02, 0x01, 0xe8, 0x00}, {0x09, 0x01, 0xe8, 0x00},
+        {0x17, 0x01, 0xe8, 0x00}, {0x28, 0x01, 0xe8, 0x01},
+        {0x02, 0x01, 0xe9, 0x00}, {0x09, 0x01, 0xe9, 0x00},
+        {0x17, 0x01, 0xe9, 0x00}, {0x28, 0x01, 0xe9, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xc6, 0x00}, {0x06, 0x01, 0xc6, 0x00},
+        {0x0a, 0x01, 0xc6, 0x00}, {0x0f, 0x01, 0xc6, 0x00},
+        {0x18, 0x01, 0xc6, 0x00}, {0x1f, 0x01, 0xc6, 0x00},
+        {0x29, 0x01, 0xc6, 0x00}, {0x38, 0x01, 0xc6, 0x01},
+        {0x03, 0x01, 0xe4, 0x00}, {0x06, 0x01, 0xe4, 0x00},
+        {0x0a, 0x01, 0xe4, 0x00}, {0x0f, 0x01, 0xe4, 0x00},
+        {0x18, 0x01, 0xe4, 0x00}, {0x1f, 0x01, 0xe4, 0x00},
+        {0x29, 0x01, 0xe4, 0x00}, {0x38, 0x01, 0xe4, 0x01}
+    },
+    /* 145 */
+    {
+        {0x03, 0x01, 0xe8, 0x00}, {0x06, 0x01, 0xe8, 0x00},
+        {0x0a, 0x01, 0xe8, 0x00}, {0x0f, 0x01, 0xe8, 0x00},
+        {0x18, 0x01, 0xe8, 0x00}, {0x1f, 0x01, 0xe8, 0x00},
+        {0x29, 0x01, 0xe8, 0x00}, {0x38, 0x01, 0xe8, 0x01},
+        {0x03, 0x01, 0xe9, 0x00}, {0x06, 0x01, 0xe9, 0x00},
+        {0x0a, 0x01, 0xe9, 0x00}, {0x0f, 0x01, 0xe9, 0x00},
+        {0x18, 0x01, 0xe9, 0x00}, {0x1f, 0x01, 0xe9, 0x00},
+        {0x29, 0x01, 0xe9, 0x00}, {0x38, 0x01, 0xe9, 0x01}
+    },
+    {
+        {0x01, 0x01, 0x01, 0x00}, {0x16, 0x01, 0x01, 0x01},
+        {0x01, 0x01, 0x87, 0x00}, {0x16, 0x01, 0x87, 0x01},
+        {0x01, 0x01, 0x89, 0x00}, {0x16, 0x01, 0x89, 0x01},
+        {0x01, 0x01, 0x8a, 0x00}, {0x16, 0x01, 0x8a, 0x01},
+        {0x01, 0x01, 0x8b, 0x00}, {0x16, 0x01, 0x8b, 0x01},
+        {0x01, 0x01, 0x8c, 0x00}, {0x16, 0x01, 0x8c, 0x01},
+        {0x01, 0x01, 0x8d, 0x00}, {0x16, 0x01, 0x8d, 0x01},
+        {0x01, 0x01, 0x8f, 0x00}, {0x16, 0x01, 0x8f, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x01, 0x00}, {0x09, 0x01, 0x01, 0x00},
+        {0x17, 0x01, 0x01, 0x00}, {0x28, 0x01, 0x01, 0x01},
+        {0x02, 0x01, 0x87, 0x00}, {0x09, 0x01, 0x87, 0x00},
+        {0x17, 0x01, 0x87, 0x00}, {0x28, 0x01, 0x87, 0x01},
+        {0x02, 0x01, 0x89, 0x00}, {0x09, 0x01, 0x89, 0x00},
+        {0x17, 0x01, 0x89, 0x00}, {0x28, 0x01, 0x89, 0x01},
+        {0x02, 0x01, 0x8a, 0x00}, {0x09, 0x01, 0x8a, 0x00},
+        {0x17, 0x01, 0x8a, 0x00}, {0x28, 0x01, 0x8a, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x01, 0x00}, {0x06, 0x01, 0x01, 0x00},
+        {0x0a, 0x01, 0x01, 0x00}, {0x0f, 0x01, 0x01, 0x00},
+        {0x18, 0x01, 0x01, 0x00}, {0x1f, 0x01, 0x01, 0x00},
+        {0x29, 0x01, 0x01, 0x00}, {0x38, 0x01, 0x01, 0x01},
+        {0x03, 0x01, 0x87, 0x00}, {0x06, 0x01, 0x87, 0x00},
+        {0x0a, 0x01, 0x87, 0x00}, {0x0f, 0x01, 0x87, 0x00},
+        {0x18, 0x01, 0x87, 0x00}, {0x1f, 0x01, 0x87, 0x00},
+        {0x29, 0x01, 0x87, 0x00}, {0x38, 0x01, 0x87, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x89, 0x00}, {0x06, 0x01, 0x89, 0x00},
+        {0x0a, 0x01, 0x89, 0x00}, {0x0f, 0x01, 0x89, 0x00},
+        {0x18, 0x01, 0x89, 0x00}, {0x1f, 0x01, 0x89, 0x00},
+        {0x29, 0x01, 0x89, 0x00}, {0x38, 0x01, 0x89, 0x01},
+        {0x03, 0x01, 0x8a, 0x00}, {0x06, 0x01, 0x8a, 0x00},
+        {0x0a, 0x01, 0x8a, 0x00}, {0x0f, 0x01, 0x8a, 0x00},
+        {0x18, 0x01, 0x8a, 0x00}, {0x1f, 0x01, 0x8a, 0x00},
+        {0x29, 0x01, 0x8a, 0x00}, {0x38, 0x01, 0x8a, 0x01}
+    },
+    /* 150 */
+    {
+        {0x02, 0x01, 0x8b, 0x00}, {0x09, 0x01, 0x8b, 0x00},
+        {0x17, 0x01, 0x8b, 0x00}, {0x28, 0x01, 0x8b, 0x01},
+        {0x02, 0x01, 0x8c, 0x00}, {0x09, 0x01, 0x8c, 0x00},
+        {0x17, 0x01, 0x8c, 0x00}, {0x28, 0x01, 0x8c, 0x01},
+        {0x02, 0x01, 0x8d, 0x00}, {0x09, 0x01, 0x8d, 0x00},
+        {0x17, 0x01, 0x8d, 0x00}, {0x28, 0x01, 0x8d, 0x01},
+        {0x02, 0x01, 0x8f, 0x00}, {0x09, 0x01, 0x8f, 0x00},
+        {0x17, 0x01, 0x8f, 0x00}, {0x28, 0x01, 0x8f, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x8b, 0x00}, {0x06, 0x01, 0x8b, 0x00},
+        {0x0a, 0x01, 0x8b, 0x00}, {0x0f, 0x01, 0x8b, 0x00},
+        {0x18, 0x01, 0x8b, 0x00}, {0x1f, 0x01, 0x8b, 0x00},
+        {0x29, 0x01, 0x8b, 0x00}, {0x38, 0x01, 0x8b, 0x01},
+        {0x03, 0x01, 0x8c, 0x00}, {0x06, 0x01, 0x8c, 0x00},
+        {0x0a, 0x01, 0x8c, 0x00}, {0x0f, 0x01, 0x8c, 0x00},
+        {0x18, 0x01, 0x8c, 0x00}, {0x1f, 0x01, 0x8c, 0x00},
+        {0x29, 0x01, 0x8c, 0x00}, {0x38, 0x01, 0x8c, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x8d, 0x00}, {0x06, 0x01, 0x8d, 0x00},
+        {0x0a, 0x01, 0x8d, 0x00}, {0x0f, 0x01, 0x8d, 0x00},
+        {0x18, 0x01, 0x8d, 0x00}, {0x1f, 0x01, 0x8d, 0x00},
+        {0x29, 0x01, 0x8d, 0x00}, {0x38, 0x01, 0x8d, 0x01},
+        {0x03, 0x01, 0x8f, 0x00}, {0x06, 0x01, 0x8f, 0x00},
+        {0x0a, 0x01, 0x8f, 0x00}, {0x0f, 0x01, 0x8f, 0x00},
+        {0x18, 0x01, 0x8f, 0x00}, {0x1f, 0x01, 0x8f, 0x00},
+        {0x29, 0x01, 0x8f, 0x00}, {0x38, 0x01, 0x8f, 0x01}
+    },
+    {
+        {0x9d, 0x00, 0x00, 0x00}, {0x9e, 0x00, 0x00, 0x00},
+        {0xa0, 0x00, 0x00, 0x00}, {0xa1, 0x00, 0x00, 0x00},
+        {0xa4, 0x00, 0x00, 0x00}, {0xa5, 0x00, 0x00, 0x00},
+        {0xa7, 0x00, 0x00, 0x00}, {0xa8, 0x00, 0x00, 0x00},
+        {0xac, 0x00, 0x00, 0x00}, {0xad, 0x00, 0x00, 0x00},
+        {0xaf, 0x00, 0x00, 0x00}, {0xb1, 0x00, 0x00, 0x00},
+        {0xb6, 0x00, 0x00, 0x00}, {0xb9, 0x00, 0x00, 0x00},
+        {0xbf, 0x00, 0x00, 0x00}, {0xcf, 0x00, 0x00, 0x01}
+    },
+    {
+        {0x00, 0x01, 0x93, 0x01}, {0x00, 0x01, 0x95, 0x01},
+        {0x00, 0x01, 0x96, 0x01}, {0x00, 0x01, 0x97, 0x01},
+        {0x00, 0x01, 0x98, 0x01}, {0x00, 0x01, 0x9b, 0x01},
+        {0x00, 0x01, 0x9d, 0x01}, {0x00, 0x01, 0x9e, 0x01},
+        {0x00, 0x01, 0xa5, 0x01}, {0x00, 0x01, 0xa6, 0x01},
+        {0x00, 0x01, 0xa8, 0x01}, {0x00, 0x01, 0xae, 0x01},
+        {0x00, 0x01, 0xaf, 0x01}, {0x00, 0x01, 0xb4, 0x01},
+        {0x00, 0x01, 0xb6, 0x01}, {0x00, 0x01, 0xb7, 0x01}
+    },
+    /* 155 */
+    {
+        {0x01, 0x01, 0x93, 0x00}, {0x16, 0x01, 0x93, 0x01},
+        {0x01, 0x01, 0x95, 0x00}, {0x16, 0x01, 0x95, 0x01},
+        {0x01, 0x01, 0x96, 0x00}, {0x16, 0x01, 0x96, 0x01},
+        {0x01, 0x01, 0x97, 0x00}, {0x16, 0x01, 0x97, 0x01},
+        {0x01, 0x01, 0x98, 0x00}, {0x16, 0x01, 0x98, 0x01},
+        {0x01, 0x01, 0x9b, 0x00}, {0x16, 0x01, 0x9b, 0x01},
+        {0x01, 0x01, 0x9d, 0x00}, {0x16, 0x01, 0x9d, 0x01},
+        {0x01, 0x01, 0x9e, 0x00}, {0x16, 0x01, 0x9e, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x93, 0x00}, {0x09, 0x01, 0x93, 0x00},
+        {0x17, 0x01, 0x93, 0x00}, {0x28, 0x01, 0x93, 0x01},
+        {0x02, 0x01, 0x95, 0x00}, {0x09, 0x01, 0x95, 0x00},
+        {0x17, 0x01, 0x95, 0x00}, {0x28, 0x01, 0x95, 0x01},
+        {0x02, 0x01, 0x96, 0x00}, {0x09, 0x01, 0x96, 0x00},
+        {0x17, 0x01, 0x96, 0x00}, {0x28, 0x01, 0x96, 0x01},
+        {0x02, 0x01, 0x97, 0x00}, {0x09, 0x01, 0x97, 0x00},
+        {0x17, 0x01, 0x97, 0x00}, {0x28, 0x01, 0x97, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x93, 0x00}, {0x06, 0x01, 0x93, 0x00},
+        {0x0a, 0x01, 0x93, 0x00}, {0x0f, 0x01, 0x93, 0x00},
+        {0x18, 0x01, 0x93, 0x00}, {0x1f, 0x01, 0x93, 0x00},
+        {0x29, 0x01, 0x93, 0x00}, {0x38, 0x01, 0x93, 0x01},
+        {0x03, 0x01, 0x95, 0x00}, {0x06, 0x01, 0x95, 0x00},
+        {0x0a, 0x01, 0x95, 0x00}, {0x0f, 0x01, 0x95, 0x00},
+        {0x18, 0x01, 0x95, 0x00}, {0x1f, 0x01, 0x95, 0x00},
+        {0x29, 0x01, 0x95, 0x00}, {0x38, 0x01, 0x95, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x96, 0x00}, {0x06, 0x01, 0x96, 0x00},
+        {0x0a, 0x01, 0x96, 0x00}, {0x0f, 0x01, 0x96, 0x00},
+        {0x18, 0x01, 0x96, 0x00}, {0x1f, 0x01, 0x96, 0x00},
+        {0x29, 0x01, 0x96, 0x00}, {0x38, 0x01, 0x96, 0x01},
+        {0x03, 0x01, 0x97, 0x00}, {0x06, 0x01, 0x97, 0x00},
+        {0x0a, 0x01, 0x97, 0x00}, {0x0f, 0x01, 0x97, 0x00},
+        {0x18, 0x01, 0x97, 0x00}, {0x1f, 0x01, 0x97, 0x00},
+        {0x29, 0x01, 0x97, 0x00}, {0x38, 0x01, 0x97, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x98, 0x00}, {0x09, 0x01, 0x98, 0x00},
+        {0x17, 0x01, 0x98, 0x00}, {0x28, 0x01, 0x98, 0x01},
+        {0x02, 0x01, 0x9b, 0x00}, {0x09, 0x01, 0x9b, 0x00},
+        {0x17, 0x01, 0x9b, 0x00}, {0x28, 0x01, 0x9b, 0x01},
+        {0x02, 0x01, 0x9d, 0x00}, {0x09, 0x01, 0x9d, 0x00},
+        {0x17, 0x01, 0x9d, 0x00}, {0x28, 0x01, 0x9d, 0x01},
+        {0x02, 0x01, 0x9e, 0x00}, {0x09, 0x01, 0x9e, 0x00},
+        {0x17, 0x01, 0x9e, 0x00}, {0x28, 0x01, 0x9e, 0x01}
+    },
+    /* 160 */
+    {
+        {0x03, 0x01, 0x98, 0x00}, {0x06, 0x01, 0x98, 0x00},
+        {0x0a, 0x01, 0x98, 0x00}, {0x0f, 0x01, 0x98, 0x00},
+        {0x18, 0x01, 0x98, 0x00}, {0x1f, 0x01, 0x98, 0x00},
+        {0x29, 0x01, 0x98, 0x00}, {0x38, 0x01, 0x98, 0x01},
+        {0x03, 0x01, 0x9b, 0x00}, {0x06, 0x01, 0x9b, 0x00},
+        {0x0a, 0x01, 0x9b, 0x00}, {0x0f, 0x01, 0x9b, 0x00},
+        {0x18, 0x01, 0x9b, 0x00}, {0x1f, 0x01, 0x9b, 0x00},
+        {0x29, 0x01, 0x9b, 0x00}, {0x38, 0x01, 0x9b, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x9d, 0x00}, {0x06, 0x01, 0x9d, 0x00},
+        {0x0a, 0x01, 0x9d, 0x00}, {0x0f, 0x01, 0x9d, 0x00},
+        {0x18, 0x01, 0x9d, 0x00}, {0x1f, 0x01, 0x9d, 0x00},
+        {0x29, 0x01, 0x9d, 0x00}, {0x38, 0x01, 0x9d, 0x01},
+        {0x03, 0x01, 0x9e, 0x00}, {0x06, 0x01, 0x9e, 0x00},
+        {0x0a, 0x01, 0x9e, 0x00}, {0x0f, 0x01, 0x9e, 0x00},
+        {0x18, 0x01, 0x9e, 0x00}, {0x1f, 0x01, 0x9e, 0x00},
+        {0x29, 0x01, 0x9e, 0x00}, {0x38, 0x01, 0x9e, 0x01}
+    },
+    {
+        {0x01, 0x01, 0xa5, 0x00}, {0x16, 0x01, 0xa5, 0x01},
+        {0x01, 0x01, 0xa6, 0x00}, {0x16, 0x01, 0xa6, 0x01},
+        {0x01, 0x01, 0xa8, 0x00}, {0x16, 0x01, 0xa8, 0x01},
+        {0x01, 0x01, 0xae, 0x00}, {0x16, 0x01, 0xae, 0x01},
+        {0x01, 0x01, 0xaf, 0x00}, {0x16, 0x01, 0xaf, 0x01},
+        {0x01, 0x01, 0xb4, 0x00}, {0x16, 0x01, 0xb4, 0x01},
+        {0x01, 0x01, 0xb6, 0x00}, {0x16, 0x01, 0xb6, 0x01},
+        {0x01, 0x01, 0xb7, 0x00}, {0x16, 0x01, 0xb7, 0x01}
+    },
+    {
+        {0x02, 0x01, 0xa5, 0x00}, {0x09, 0x01, 0xa5, 0x00},
+        {0x17, 0x01, 0xa5, 0x00}, {0x28, 0x01, 0xa5, 0x01},
+        {0x02, 0x01, 0xa6, 0x00}, {0x09, 0x01, 0xa6, 0x00},
+        {0x17, 0x01, 0xa6, 0x00}, {0x28, 0x01, 0xa6, 0x01},
+        {0x02, 0x01, 0xa8, 0x00}, {0x09, 0x01, 0xa8, 0x00},
+        {0x17, 0x01, 0xa8, 0x00}, {0x28, 0x01, 0xa8, 0x01},
+        {0x02, 0x01, 0xae, 0x00}, {0x09, 0x01, 0xae, 0x00},
+        {0x17, 0x01, 0xae, 0x00}, {0x28, 0x01, 0xae, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xa5, 0x00}, {0x06, 0x01, 0xa5, 0x00},
+        {0x0a, 0x01, 0xa5, 0x00}, {0x0f, 0x01, 0xa5, 0x00},
+        {0x18, 0x01, 0xa5, 0x00}, {0x1f, 0x01, 0xa5, 0x00},
+        {0x29, 0x01, 0xa5, 0x00}, {0x38, 0x01, 0xa5, 0x01},
+        {0x03, 0x01, 0xa6, 0x00}, {0x06, 0x01, 0xa6, 0x00},
+        {0x0a, 0x01, 0xa6, 0x00}, {0x0f, 0x01, 0xa6, 0x00},
+        {0x18, 0x01, 0xa6, 0x00}, {0x1f, 0x01, 0xa6, 0x00},
+        {0x29, 0x01, 0xa6, 0x00}, {0x38, 0x01, 0xa6, 0x01}
+    },
+    /* 165 */
+    {
+        {0x03, 0x01, 0xa8, 0x00}, {0x06, 0x01, 0xa8, 0x00},
+        {0x0a, 0x01, 0xa8, 0x00}, {0x0f, 0x01, 0xa8, 0x00},
+        {0x18, 0x01, 0xa8, 0x00}, {0x1f, 0x01, 0xa8, 0x00},
+        {0x29, 0x01, 0xa8, 0x00}, {0x38, 0x01, 0xa8, 0x01},
+        {0x03, 0x01, 0xae, 0x00}, {0x06, 0x01, 0xae, 0x00},
+        {0x0a, 0x01, 0xae, 0x00}, {0x0f, 0x01, 0xae, 0x00},
+        {0x18, 0x01, 0xae, 0x00}, {0x1f, 0x01, 0xae, 0x00},
+        {0x29, 0x01, 0xae, 0x00}, {0x38, 0x01, 0xae, 0x01}
+    },
+    {
+        {0x02, 0x01, 0xaf, 0x00}, {0x09, 0x01, 0xaf, 0x00},
+        {0x17, 0x01, 0xaf, 0x00}, {0x28, 0x01, 0xaf, 0x01},
+        {0x02, 0x01, 0xb4, 0x00}, {0x09, 0x01, 0xb4, 0x00},
+        {0x17, 0x01, 0xb4, 0x00}, {0x28, 0x01, 0xb4, 0x01},
+        {0x02, 0x01, 0xb6, 0x00}, {0x09, 0x01, 0xb6, 0x00},
+        {0x17, 0x01, 0xb6, 0x00}, {0x28, 0x01, 0xb6, 0x01},
+        {0x02, 0x01, 0xb7, 0x00}, {0x09, 0x01, 0xb7, 0x00},
+        {0x17, 0x01, 0xb7, 0x00}, {0x28, 0x01, 0xb7, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xaf, 0x00}, {0x06, 0x01, 0xaf, 0x00},
+        {0x0a, 0x01, 0xaf, 0x00}, {0x0f, 0x01, 0xaf, 0x00},
+        {0x18, 0x01, 0xaf, 0x00}, {0x1f, 0x01, 0xaf, 0x00},
+        {0x29, 0x01, 0xaf, 0x00}, {0x38, 0x01, 0xaf, 0x01},
+        {0x03, 0x01, 0xb4, 0x00}, {0x06, 0x01, 0xb4, 0x00},
+        {0x0a, 0x01, 0xb4, 0x00}, {0x0f, 0x01, 0xb4, 0x00},
+        {0x18, 0x01, 0xb4, 0x00}, {0x1f, 0x01, 0xb4, 0x00},
+        {0x29, 0x01, 0xb4, 0x00}, {0x38, 0x01, 0xb4, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xb6, 0x00}, {0x06, 0x01, 0xb6, 0x00},
+        {0x0a, 0x01, 0xb6, 0x00}, {0x0f, 0x01, 0xb6, 0x00},
+        {0x18, 0x01, 0xb6, 0x00}, {0x1f, 0x01, 0xb6, 0x00},
+        {0x29, 0x01, 0xb6, 0x00}, {0x38, 0x01, 0xb6, 0x01},
+        {0x03, 0x01, 0xb7, 0x00}, {0x06, 0x01, 0xb7, 0x00},
+        {0x0a, 0x01, 0xb7, 0x00}, {0x0f, 0x01, 0xb7, 0x00},
+        {0x18, 0x01, 0xb7, 0x00}, {0x1f, 0x01, 0xb7, 0x00},
+        {0x29, 0x01, 0xb7, 0x00}, {0x38, 0x01, 0xb7, 0x01}
+    },
+    {
+        {0x00, 0x01, 0xbc, 0x01}, {0x00, 0x01, 0xbf, 0x01},
+        {0x00, 0x01, 0xc5, 0x01}, {0x00, 0x01, 0xe7, 0x01},
+        {0x00, 0x01, 0xef, 0x01}, {0xb0, 0x00, 0x00, 0x00},
+        {0xb2, 0x00, 0x00, 0x00}, {0xb3, 0x00, 0x00, 0x00},
+        {0xb7, 0x00, 0x00, 0x00}, {0xb8, 0x00, 0x00, 0x00},
+        {0xba, 0x00, 0x00, 0x00}, {0xbb, 0x00, 0x00, 0x00},
+        {0xc0, 0x00, 0x00, 0x00}, {0xc7, 0x00, 0x00, 0x00},
+        {0xd0, 0x00, 0x00, 0x00}, {0xdf, 0x00, 0x00, 0x01}
+    },
+    /* 170 */
+    {
+        {0x01, 0x01, 0xbc, 0x00}, {0x16, 0x01, 0xbc, 0x01},
+        {0x01, 0x01, 0xbf, 0x00}, {0x16, 0x01, 0xbf, 0x01},
+        {0x01, 0x01, 0xc5, 0x00}, {0x16, 0x01, 0xc5, 0x01},
+        {0x01, 0x01, 0xe7, 0x00}, {0x16, 0x01, 0xe7, 0x01},
+        {0x01, 0x01, 0xef, 0x00}, {0x16, 0x01, 0xef, 0x01},
+        {0x00, 0x01, 0x09, 0x01}, {0x00, 0x01, 0x8e, 0x01},
+        {0x00, 0x01, 0x90, 0x01}, {0x00, 0x01, 0x91, 0x01},
+        {0x00, 0x01, 0x94, 0x01}, {0x00, 0x01, 0x9f, 0x01}
+    },
+    {
+        {0x02, 0x01, 0xbc, 0x00}, {0x09, 0x01, 0xbc, 0x00},
+        {0x17, 0x01, 0xbc, 0x00}, {0x28, 0x01, 0xbc, 0x01},
+        {0x02, 0x01, 0xbf, 0x00}, {0x09, 0x01, 0xbf, 0x00},
+        {0x17, 0x01, 0xbf, 0x00}, {0x28, 0x01, 0xbf, 0x01},
+        {0x02, 0x01, 0xc5, 0x00}, {0x09, 0x01, 0xc5, 0x00},
+        {0x17, 0x01, 0xc5, 0x00}, {0x28, 0x01, 0xc5, 0x01},
+        {0x02, 0x01, 0xe7, 0x00}, {0x09, 0x01, 0xe7, 0x00},
+        {0x17, 0x01, 0xe7, 0x00}, {0x28, 0x01, 0xe7, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xbc, 0x00}, {0x06, 0x01, 0xbc, 0x00},
+        {0x0a, 0x01, 0xbc, 0x00}, {0x0f, 0x01, 0xbc, 0x00},
+        {0x18, 0x01, 0xbc, 0x00}, {0x1f, 0x01, 0xbc, 0x00},
+        {0x29, 0x01, 0xbc, 0x00}, {0x38, 0x01, 0xbc, 0x01},
+        {0x03, 0x01, 0xbf, 0x00}, {0x06, 0x01, 0xbf, 0x00},
+        {0x0a, 0x01, 0xbf, 0x00}, {0x0f, 0x01, 0xbf, 0x00},
+        {0x18, 0x01, 0xbf, 0x00}, {0x1f, 0x01, 0xbf, 0x00},
+        {0x29, 0x01, 0xbf, 0x00}, {0x38, 0x01, 0xbf, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xc5, 0x00}, {0x06, 0x01, 0xc5, 0x00},
+        {0x0a, 0x01, 0xc5, 0x00}, {0x0f, 0x01, 0xc5, 0x00},
+        {0x18, 0x01, 0xc5, 0x00}, {0x1f, 0x01, 0xc5, 0x00},
+        {0x29, 0x01, 0xc5, 0x00}, {0x38, 0x01, 0xc5, 0x01},
+        {0x03, 0x01, 0xe7, 0x00}, {0x06, 0x01, 0xe7, 0x00},
+        {0x0a, 0x01, 0xe7, 0x00}, {0x0f, 0x01, 0xe7, 0x00},
+        {0x18, 0x01, 0xe7, 0x00}, {0x1f, 0x01, 0xe7, 0x00},
+        {0x29, 0x01, 0xe7, 0x00}, {0x38, 0x01, 0xe7, 0x01}
+    },
+    {
+        {0x02, 0x01, 0xef, 0x00}, {0x09, 0x01, 0xef, 0x00},
+        {0x17, 0x01, 0xef, 0x00}, {0x28, 0x01, 0xef, 0x01},
+        {0x01, 0x01, 0x09, 0x00}, {0x16, 0x01, 0x09, 0x01},
+        {0x01, 0x01, 0x8e, 0x00}, {0x16, 0x01, 0x8e, 0x01},
+        {0x01, 0x01, 0x90, 0x00}, {0x16, 0x01, 0x90, 0x01},
+        {0x01, 0x01, 0x91, 0x00}, {0x16, 0x01, 0x91, 0x01},
+        {0x01, 0x01, 0x94, 0x00}, {0x16, 0x01, 0x94, 0x01},
+        {0x01, 0x01, 0x9f, 0x00}, {0x16, 0x01, 0x9f, 0x01}
+    },
+    /* 175 */
+    {
+        {0x03, 0x01, 0xef, 0x00}, {0x06, 0x01, 0xef, 0x00},
+        {0x0a, 0x01, 0xef, 0x00}, {0x0f, 0x01, 0xef, 0x00},
+        {0x18, 0x01, 0xef, 0x00}, {0x1f, 0x01, 0xef, 0x00},
+        {0x29, 0x01, 0xef, 0x00}, {0x38, 0x01, 0xef, 0x01},
+        {0x02, 0x01, 0x09, 0x00}, {0x09, 0x01, 0x09, 0x00},
+        {0x17, 0x01, 0x09, 0x00}, {0x28, 0x01, 0x09, 0x01},
+        {0x02, 0x01, 0x8e, 0x00}, {0x09, 0x01, 0x8e, 0x00},
+        {0x17, 0x01, 0x8e, 0x00}, {0x28, 0x01, 0x8e, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x09, 0x00}, {0x06, 0x01, 0x09, 0x00},
+        {0x0a, 0x01, 0x09, 0x00}, {0x0f, 0x01, 0x09, 0x00},
+        {0x18, 0x01, 0x09, 0x00}, {0x1f, 0x01, 0x09, 0x00},
+        {0x29, 0x01, 0x09, 0x00}, {0x38, 0x01, 0x09, 0x01},
+        {0x03, 0x01, 0x8e, 0x00}, {0x06, 0x01, 0x8e, 0x00},
+        {0x0a, 0x01, 0x8e, 0x00}, {0x0f, 0x01, 0x8e, 0x00},
+        {0x18, 0x01, 0x8e, 0x00}, {0x1f, 0x01, 0x8e, 0x00},
+        {0x29, 0x01, 0x8e, 0x00}, {0x38, 0x01, 0x8e, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x90, 0x00}, {0x09, 0x01, 0x90, 0x00},
+        {0x17, 0x01, 0x90, 0x00}, {0x28, 0x01, 0x90, 0x01},
+        {0x02, 0x01, 0x91, 0x00}, {0x09, 0x01, 0x91, 0x00},
+        {0x17, 0x01, 0x91, 0x00}, {0x28, 0x01, 0x91, 0x01},
+        {0x02, 0x01, 0x94, 0x00}, {0x09, 0x01, 0x94, 0x00},
+        {0x17, 0x01, 0x94, 0x00}, {0x28, 0x01, 0x94, 0x01},
+        {0x02, 0x01, 0x9f, 0x00}, {0x09, 0x01, 0x9f, 0x00},
+        {0x17, 0x01, 0x9f, 0x00}, {0x28, 0x01, 0x9f, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x90, 0x00}, {0x06, 0x01, 0x90, 0x00},
+        {0x0a, 0x01, 0x90, 0x00}, {0x0f, 0x01, 0x90, 0x00},
+        {0x18, 0x01, 0x90, 0x00}, {0x1f, 0x01, 0x90, 0x00},
+        {0x29, 0x01, 0x90, 0x00}, {0x38, 0x01, 0x90, 0x01},
+        {0x03, 0x01, 0x91, 0x00}, {0x06, 0x01, 0x91, 0x00},
+        {0x0a, 0x01, 0x91, 0x00}, {0x0f, 0x01, 0x91, 0x00},
+        {0x18, 0x01, 0x91, 0x00}, {0x1f, 0x01, 0x91, 0x00},
+        {0x29, 0x01, 0x91, 0x00}, {0x38, 0x01, 0x91, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x94, 0x00}, {0x06, 0x01, 0x94, 0x00},
+        {0x0a, 0x01, 0x94, 0x00}, {0x0f, 0x01, 0x94, 0x00},
+        {0x18, 0x01, 0x94, 0x00}, {0x1f, 0x01, 0x94, 0x00},
+        {0x29, 0x01, 0x94, 0x00}, {0x38, 0x01, 0x94, 0x01},
+        {0x03, 0x01, 0x9f, 0x00}, {0x06, 0x01, 0x9f, 0x00},
+        {0x0a, 0x01, 0x9f, 0x00}, {0x0f, 0x01, 0x9f, 0x00},
+        {0x18, 0x01, 0x9f, 0x00}, {0x1f, 0x01, 0x9f, 0x00},
+        {0x29, 0x01, 0x9f, 0x00}, {0x38, 0x01, 0x9f, 0x01}
+    },
+    /* 180 */
+    {
+        {0x00, 0x01, 0xab, 0x01}, {0x00, 0x01, 0xce, 0x01},
+        {0x00, 0x01, 0xd7, 0x01}, {0x00, 0x01, 0xe1, 0x01},
+        {0x00, 0x01, 0xec, 0x01}, {0x00, 0x01, 0xed, 0x01},
+        {0xbc, 0x00, 0x00, 0x00}, {0xbd, 0x00, 0x00, 0x00},
+        {0xc1, 0x00, 0x00, 0x00}, {0xc4, 0x00, 0x00, 0x00},
+        {0xc8, 0x00, 0x00, 0x00}, {0xcb, 0x00, 0x00, 0x00},
+        {0xd1, 0x00, 0x00, 0x00}, {0xd8, 0x00, 0x00, 0x00},
+        {0xe0, 0x00, 0x00, 0x00}, {0xee, 0x00, 0x00, 0x01}
+    },
+    {
+        {0x01, 0x01, 0xab, 0x00}, {0x16, 0x01, 0xab, 0x01},
+        {0x01, 0x01, 0xce, 0x00}, {0x16, 0x01, 0xce, 0x01},
+        {0x01, 0x01, 0xd7, 0x00}, {0x16, 0x01, 0xd7, 0x01},
+        {0x01, 0x01, 0xe1, 0x00}, {0x16, 0x01, 0xe1, 0x01},
+        {0x01, 0x01, 0xec, 0x00}, {0x16, 0x01, 0xec, 0x01},
+        {0x01, 0x01, 0xed, 0x00}, {0x16, 0x01, 0xed, 0x01},
+        {0x00, 0x01, 0xc7, 0x01}, {0x00, 0x01, 0xcf, 0x01},
+        {0x00, 0x01, 0xea, 0x01}, {0x00, 0x01, 0xeb, 0x01}
+    },
+    {
+        {0x02, 0x01, 0xab, 0x00}, {0x09, 0x01, 0xab, 0x00},
+        {0x17, 0x01, 0xab, 0x00}, {0x28, 0x01, 0xab, 0x01},
+        {0x02, 0x01, 0xce, 0x00}, {0x09, 0x01, 0xce, 0x00},
+        {0x17, 0x01, 0xce, 0x00}, {0x28, 0x01, 0xce, 0x01},
+        {0x02, 0x01, 0xd7, 0x00}, {0x09, 0x01, 0xd7, 0x00},
+        {0x17, 0x01, 0xd7, 0x00}, {0x28, 0x01, 0xd7, 0x01},
+        {0x02, 0x01, 0xe1, 0x00}, {0x09, 0x01, 0xe1, 0x00},
+        {0x17, 0x01, 0xe1, 0x00}, {0x28, 0x01, 0xe1, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xab, 0x00}, {0x06, 0x01, 0xab, 0x00},
+        {0x0a, 0x01, 0xab, 0x00}, {0x0f, 0x01, 0xab, 0x00},
+        {0x18, 0x01, 0xab, 0x00}, {0x1f, 0x01, 0xab, 0x00},
+        {0x29, 0x01, 0xab, 0x00}, {0x38, 0x01, 0xab, 0x01},
+        {0x03, 0x01, 0xce, 0x00}, {0x06, 0x01, 0xce, 0x00},
+        {0x0a, 0x01, 0xce, 0x00}, {0x0f, 0x01, 0xce, 0x00},
+        {0x18, 0x01, 0xce, 0x00}, {0x1f, 0x01, 0xce, 0x00},
+        {0x29, 0x01, 0xce, 0x00}, {0x38, 0x01, 0xce, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xd7, 0x00}, {0x06, 0x01, 0xd7, 0x00},
+        {0x0a, 0x01, 0xd7, 0x00}, {0x0f, 0x01, 0xd7, 0x00},
+        {0x18, 0x01, 0xd7, 0x00}, {0x1f, 0x01, 0xd7, 0x00},
+        {0x29, 0x01, 0xd7, 0x00}, {0x38, 0x01, 0xd7, 0x01},
+        {0x03, 0x01, 0xe1, 0x00}, {0x06, 0x01, 0xe1, 0x00},
+        {0x0a, 0x01, 0xe1, 0x00}, {0x0f, 0x01, 0xe1, 0x00},
+        {0x18, 0x01, 0xe1, 0x00}, {0x1f, 0x01, 0xe1, 0x00},
+        {0x29, 0x01, 0xe1, 0x00}, {0x38, 0x01, 0xe1, 0x01}
+    },
+    /* 185 */
+    {
+        {0x02, 0x01, 0xec, 0x00}, {0x09, 0x01, 0xec, 0x00},
+        {0x17, 0x01, 0xec, 0x00}, {0x28, 0x01, 0xec, 0x01},
+        {0x02, 0x01, 0xed, 0x00}, {0x09, 0x01, 0xed, 0x00},
+        {0x17, 0x01, 0xed, 0x00}, {0x28, 0x01, 0xed, 0x01},
+        {0x01, 0x01, 0xc7, 0x00}, {0x16, 0x01, 0xc7, 0x01},
+        {0x01, 0x01, 0xcf, 0x00}, {0x16, 0x01, 0xcf, 0x01},
+        {0x01, 0x01, 0xea, 0x00}, {0x16, 0x01, 0xea, 0x01},
+        {0x01, 0x01, 0xeb, 0x00}, {0x16, 0x01, 0xeb, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xec, 0x00}, {0x06, 0x01, 0xec, 0x00},
+        {0x0a, 0x01, 0xec, 0x00}, {0x0f, 0x01, 0xec, 0x00},
+        {0x18, 0x01, 0xec, 0x00}, {0x1f, 0x01, 0xec, 0x00},
+        {0x29, 0x01, 0xec, 0x00}, {0x38, 0x01, 0xec, 0x01},
+        {0x03, 0x01, 0xed, 0x00}, {0x06, 0x01, 0xed, 0x00},
+        {0x0a, 0x01, 0xed, 0x00}, {0x0f, 0x01, 0xed, 0x00},
+        {0x18, 0x01, 0xed, 0x00}, {0x1f, 0x01, 0xed, 0x00},
+        {0x29, 0x01, 0xed, 0x00}, {0x38, 0x01, 0xed, 0x01}
+    },
+    {
+        {0x02, 0x01, 0xc7, 0x00}, {0x09, 0x01, 0xc7, 0x00},
+        {0x17, 0x01, 0xc7, 0x00}, {0x28, 0x01, 0xc7, 0x01},
+        {0x02, 0x01, 0xcf, 0x00}, {0x09, 0x01, 0xcf, 0x00},
+        {0x17, 0x01, 0xcf, 0x00}, {0x28, 0x01, 0xcf, 0x01},
+        {0x02, 0x01, 0xea, 0x00}, {0x09, 0x01, 0xea, 0x00},
+        {0x17, 0x01, 0xea, 0x00}, {0x28, 0x01, 0xea, 0x01},
+        {0x02, 0x01, 0xeb, 0x00}, {0x09, 0x01, 0xeb, 0x00},
+        {0x17, 0x01, 0xeb, 0x00}, {0x28, 0x01, 0xeb, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xc7, 0x00}, {0x06, 0x01, 0xc7, 0x00},
+        {0x0a, 0x01, 0xc7, 0x00}, {0x0f, 0x01, 0xc7, 0x00},
+        {0x18, 0x01, 0xc7, 0x00}, {0x1f, 0x01, 0xc7, 0x00},
+        {0x29, 0x01, 0xc7, 0x00}, {0x38, 0x01, 0xc7, 0x01},
+        {0x03, 0x01, 0xcf, 0x00}, {0x06, 0x01, 0xcf, 0x00},
+        {0x0a, 0x01, 0xcf, 0x00}, {0x0f, 0x01, 0xcf, 0x00},
+        {0x18, 0x01, 0xcf, 0x00}, {0x1f, 0x01, 0xcf, 0x00},
+        {0x29, 0x01, 0xcf, 0x00}, {0x38, 0x01, 0xcf, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xea, 0x00}, {0x06, 0x01, 0xea, 0x00},
+        {0x0a, 0x01, 0xea, 0x00}, {0x0f, 0x01, 0xea, 0x00},
+        {0x18, 0x01, 0xea, 0x00}, {0x1f, 0x01, 0xea, 0x00},
+        {0x29, 0x01, 0xea, 0x00}, {0x38, 0x01, 0xea, 0x01},
+        {0x03, 0x01, 0xeb, 0x00}, {0x06, 0x01, 0xeb, 0x00},
+        {0x0a, 0x01, 0xeb, 0x00}, {0x0f, 0x01, 0xeb, 0x00},
+        {0x18, 0x01, 0xeb, 0x00}, {0x1f, 0x01, 0xeb, 0x00},
+        {0x29, 0x01, 0xeb, 0x00}, {0x38, 0x01, 0xeb, 0x01}
+    },
+    /* 190 */
+    {
+        {0xc2, 0x00, 0x00, 0x00}, {0xc3, 0x00, 0x00, 0x00},
+        {0xc5, 0x00, 0x00, 0x00}, {0xc6, 0x00, 0x00, 0x00},
+        {0xc9, 0x00, 0x00, 0x00}, {0xca, 0x00, 0x00, 0x00},
+        {0xcc, 0x00, 0x00, 0x00}, {0xcd, 0x00, 0x00, 0x00},
+        {0xd2, 0x00, 0x00, 0x00}, {0xd5, 0x00, 0x00, 0x00},
+        {0xd9, 0x00, 0x00, 0x00}, {0xdc, 0x00, 0x00, 0x00},
+        {0xe1, 0x00, 0x00, 0x00}, {0xe7, 0x00, 0x00, 0x00},
+        {0xef, 0x00, 0x00, 0x00}, {0xf6, 0x00, 0x00, 0x01}
+    },
+    {
+        {0x00, 0x01, 0xc0, 0x01}, {0x00, 0x01, 0xc1, 0x01},
+        {0x00, 0x01, 0xc8, 0x01}, {0x00, 0x01, 0xc9, 0x01},
+        {0x00, 0x01, 0xca, 0x01}, {0x00, 0x01, 0xcd, 0x01},
+        {0x00, 0x01, 0xd2, 0x01}, {0x00, 0x01, 0xd5, 0x01},
+        {0x00, 0x01, 0xda, 0x01}, {0x00, 0x01, 0xdb, 0x01},
+        {0x00, 0x01, 0xee, 0x01}, {0x00, 0x01, 0xf0, 0x01},
+        {0x00, 0x01, 0xf2, 0x01}, {0x00, 0x01, 0xf3, 0x01},
+        {0x00, 0x01, 0xff, 0x01}, {0xce, 0x00, 0x00, 0x00}
+    },
+    {
+        {0x01, 0x01, 0xc0, 0x00}, {0x16, 0x01, 0xc0, 0x01},
+        {0x01, 0x01, 0xc1, 0x00}, {0x16, 0x01, 0xc1, 0x01},
+        {0x01, 0x01, 0xc8, 0x00}, {0x16, 0x01, 0xc8, 0x01},
+        {0x01, 0x01, 0xc9, 0x00}, {0x16, 0x01, 0xc9, 0x01},
+        {0x01, 0x01, 0xca, 0x00}, {0x16, 0x01, 0xca, 0x01},
+        {0x01, 0x01, 0xcd, 0x00}, {0x16, 0x01, 0xcd, 0x01},
+        {0x01, 0x01, 0xd2, 0x00}, {0x16, 0x01, 0xd2, 0x01},
+        {0x01, 0x01, 0xd5, 0x00}, {0x16, 0x01, 0xd5, 0x01}
+    },
+    {
+        {0x02, 0x01, 0xc0, 0x00}, {0x09, 0x01, 0xc0, 0x00},
+        {0x17, 0x01, 0xc0, 0x00}, {0x28, 0x01, 0xc0, 0x01},
+        {0x02, 0x01, 0xc1, 0x00}, {0x09, 0x01, 0xc1, 0x00},
+        {0x17, 0x01, 0xc1, 0x00}, {0x28, 0x01, 0xc1, 0x01},
+        {0x02, 0x01, 0xc8, 0x00}, {0x09, 0x01, 0xc8, 0x00},
+        {0x17, 0x01, 0xc8, 0x00}, {0x28, 0x01, 0xc8, 0x01},
+        {0x02, 0x01, 0xc9, 0x00}, {0x09, 0x01, 0xc9, 0x00},
+        {0x17, 0x01, 0xc9, 0x00}, {0x28, 0x01, 0xc9, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xc0, 0x00}, {0x06, 0x01, 0xc0, 0x00},
+        {0x0a, 0x01, 0xc0, 0x00}, {0x0f, 0x01, 0xc0, 0x00},
+        {0x18, 0x01, 0xc0, 0x00}, {0x1f, 0x01, 0xc0, 0x00},
+        {0x29, 0x01, 0xc0, 0x00}, {0x38, 0x01, 0xc0, 0x01},
+        {0x03, 0x01, 0xc1, 0x00}, {0x06, 0x01, 0xc1, 0x00},
+        {0x0a, 0x01, 0xc1, 0x00}, {0x0f, 0x01, 0xc1, 0x00},
+        {0x18, 0x01, 0xc1, 0x00}, {0x1f, 0x01, 0xc1, 0x00},
+        {0x29, 0x01, 0xc1, 0x00}, {0x38, 0x01, 0xc1, 0x01}
+    },
+    /* 195 */
+    {
+        {0x03, 0x01, 0xc8, 0x00}, {0x06, 0x01, 0xc8, 0x00},
+        {0x0a, 0x01, 0xc8, 0x00}, {0x0f, 0x01, 0xc8, 0x00},
+        {0x18, 0x01, 0xc8, 0x00}, {0x1f, 0x01, 0xc8, 0x00},
+        {0x29, 0x01, 0xc8, 0x00}, {0x38, 0x01, 0xc8, 0x01},
+        {0x03, 0x01, 0xc9, 0x00}, {0x06, 0x01, 0xc9, 0x00},
+        {0x0a, 0x01, 0xc9, 0x00}, {0x0f, 0x01, 0xc9, 0x00},
+        {0x18, 0x01, 0xc9, 0x00}, {0x1f, 0x01, 0xc9, 0x00},
+        {0x29, 0x01, 0xc9, 0x00}, {0x38, 0x01, 0xc9, 0x01}
+    },
+    {
+        {0x02, 0x01, 0xca, 0x00}, {0x09, 0x01, 0xca, 0x00},
+        {0x17, 0x01, 0xca, 0x00}, {0x28, 0x01, 0xca, 0x01},
+        {0x02, 0x01, 0xcd, 0x00}, {0x09, 0x01, 0xcd, 0x00},
+        {0x17, 0x01, 0xcd, 0x00}, {0x28, 0x01, 0xcd, 0x01},
+        {0x02, 0x01, 0xd2, 0x00}, {0x09, 0x01, 0xd2, 0x00},
+        {0x17, 0x01, 0xd2, 0x00}, {0x28, 0x01, 0xd2, 0x01},
+        {0x02, 0x01, 0xd5, 0x00}, {0x09, 0x01, 0xd5, 0x00},
+        {0x17, 0x01, 0xd5, 0x00}, {0x28, 0x01, 0xd5, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xca, 0x00}, {0x06, 0x01, 0xca, 0x00},
+        {0x0a, 0x01, 0xca, 0x00}, {0x0f, 0x01, 0xca, 0x00},
+        {0x18, 0x01, 0xca, 0x00}, {0x1f, 0x01, 0xca, 0x00},
+        {0x29, 0x01, 0xca, 0x00}, {0x38, 0x01, 0xca, 0x01},
+        {0x03, 0x01, 0xcd, 0x00}, {0x06, 0x01, 0xcd, 0x00},
+        {0x0a, 0x01, 0xcd, 0x00}, {0x0f, 0x01, 0xcd, 0x00},
+        {0x18, 0x01, 0xcd, 0x00}, {0x1f, 0x01, 0xcd, 0x00},
+        {0x29, 0x01, 0xcd, 0x00}, {0x38, 0x01, 0xcd, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xd2, 0x00}, {0x06, 0x01, 0xd2, 0x00},
+        {0x0a, 0x01, 0xd2, 0x00}, {0x0f, 0x01, 0xd2, 0x00},
+        {0x18, 0x01, 0xd2, 0x00}, {0x1f, 0x01, 0xd2, 0x00},
+        {0x29, 0x01, 0xd2, 0x00}, {0x38, 0x01, 0xd2, 0x01},
+        {0x03, 0x01, 0xd5, 0x00}, {0x06, 0x01, 0xd5, 0x00},
+        {0x0a, 0x01, 0xd5, 0x00}, {0x0f, 0x01, 0xd5, 0x00},
+        {0x18, 0x01, 0xd5, 0x00}, {0x1f, 0x01, 0xd5, 0x00},
+        {0x29, 0x01, 0xd5, 0x00}, {0x38, 0x01, 0xd5, 0x01}
+    },
+    {
+        {0x01, 0x01, 0xda, 0x00}, {0x16, 0x01, 0xda, 0x01},
+        {0x01, 0x01, 0xdb, 0x00}, {0x16, 0x01, 0xdb, 0x01},
+        {0x01, 0x01, 0xee, 0x00}, {0x16, 0x01, 0xee, 0x01},
+        {0x01, 0x01, 0xf0, 0x00}, {0x16, 0x01, 0xf0, 0x01},
+        {0x01, 0x01, 0xf2, 0x00}, {0x16, 0x01, 0xf2, 0x01},
+        {0x01, 0x01, 0xf3, 0x00}, {0x16, 0x01, 0xf3, 0x01},
+        {0x01, 0x01, 0xff, 0x00}, {0x16, 0x01, 0xff, 0x01},
+        {0x00, 0x01, 0xcb, 0x01}, {0x00, 0x01, 0xcc, 0x01}
+    },
+    /* 200 */
+    {
+        {0x02, 0x01, 0xda, 0x00}, {0x09, 0x01, 0xda, 0x00},
+        {0x17, 0x01, 0xda, 0x00}, {0x28, 0x01, 0xda, 0x01},
+        {0x02, 0x01, 0xdb, 0x00}, {0x09, 0x01, 0xdb, 0x00},
+        {0x17, 0x01, 0xdb, 0x00}, {0x28, 0x01, 0xdb, 0x01},
+        {0x02, 0x01, 0xee, 0x00}, {0x09, 0x01, 0xee, 0x00},
+        {0x17, 0x01, 0xee, 0x00}, {0x28, 0x01, 0xee, 0x01},
+        {0x02, 0x01, 0xf0, 0x00}, {0x09, 0x01, 0xf0, 0x00},
+        {0x17, 0x01, 0xf0, 0x00}, {0x28, 0x01, 0xf0, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xda, 0x00}, {0x06, 0x01, 0xda, 0x00},
+        {0x0a, 0x01, 0xda, 0x00}, {0x0f, 0x01, 0xda, 0x00},
+        {0x18, 0x01, 0xda, 0x00}, {0x1f, 0x01, 0xda, 0x00},
+        {0x29, 0x01, 0xda, 0x00}, {0x38, 0x01, 0xda, 0x01},
+        {0x03, 0x01, 0xdb, 0x00}, {0x06, 0x01, 0xdb, 0x00},
+        {0x0a, 0x01, 0xdb, 0x00}, {0x0f, 0x01, 0xdb, 0x00},
+        {0x18, 0x01, 0xdb, 0x00}, {0x1f, 0x01, 0xdb, 0x00},
+        {0x29, 0x01, 0xdb, 0x00}, {0x38, 0x01, 0xdb, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xee, 0x00}, {0x06, 0x01, 0xee, 0x00},
+        {0x0a, 0x01, 0xee, 0x00}, {0x0f, 0x01, 0xee, 0x00},
+        {0x18, 0x01, 0xee, 0x00}, {0x1f, 0x01, 0xee, 0x00},
+        {0x29, 0x01, 0xee, 0x00}, {0x38, 0x01, 0xee, 0x01},
+        {0x03, 0x01, 0xf0, 0x00}, {0x06, 0x01, 0xf0, 0x00},
+        {0x0a, 0x01, 0xf0, 0x00}, {0x0f, 0x01, 0xf0, 0x00},
+        {0x18, 0x01, 0xf0, 0x00}, {0x1f, 0x01, 0xf0, 0x00},
+        {0x29, 0x01, 0xf0, 0x00}, {0x38, 0x01, 0xf0, 0x01}
+    },
+    {
+        {0x02, 0x01, 0xf2, 0x00}, {0x09, 0x01, 0xf2, 0x00},
+        {0x17, 0x01, 0xf2, 0x00}, {0x28, 0x01, 0xf2, 0x01},
+        {0x02, 0x01, 0xf3, 0x00}, {0x09, 0x01, 0xf3, 0x00},
+        {0x17, 0x01, 0xf3, 0x00}, {0x28, 0x01, 0xf3, 0x01},
+        {0x02, 0x01, 0xff, 0x00}, {0x09, 0x01, 0xff, 0x00},
+        {0x17, 0x01, 0xff, 0x00}, {0x28, 0x01, 0xff, 0x01},
+        {0x01, 0x01, 0xcb, 0x00}, {0x16, 0x01, 0xcb, 0x01},
+        {0x01, 0x01, 0xcc, 0x00}, {0x16, 0x01, 0xcc, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xf2, 0x00}, {0x06, 0x01, 0xf2, 0x00},
+        {0x0a, 0x01, 0xf2, 0x00}, {0x0f, 0x01, 0xf2, 0x00},
+        {0x18, 0x01, 0xf2, 0x00}, {0x1f, 0x01, 0xf2, 0x00},
+        {0x29, 0x01, 0xf2, 0x00}, {0x38, 0x01, 0xf2, 0x01},
+        {0x03, 0x01, 0xf3, 0x00}, {0x06, 0x01, 0xf3, 0x00},
+        {0x0a, 0x01, 0xf3, 0x00}, {0x0f, 0x01, 0xf3, 0x00},
+        {0x18, 0x01, 0xf3, 0x00}, {0x1f, 0x01, 0xf3, 0x00},
+        {0x29, 0x01, 0xf3, 0x00}, {0x38, 0x01, 0xf3, 0x01}
+    },
+    /* 205 */
+    {
+        {0x03, 0x01, 0xff, 0x00}, {0x06, 0x01, 0xff, 0x00},
+        {0x0a, 0x01, 0xff, 0x00}, {0x0f, 0x01, 0xff, 0x00},
+        {0x18, 0x01, 0xff, 0x00}, {0x1f, 0x01, 0xff, 0x00},
+        {0x29, 0x01, 0xff, 0x00}, {0x38, 0x01, 0xff, 0x01},
+        {0x02, 0x01, 0xcb, 0x00}, {0x09, 0x01, 0xcb, 0x00},
+        {0x17, 0x01, 0xcb, 0x00}, {0x28, 0x01, 0xcb, 0x01},
+        {0x02, 0x01, 0xcc, 0x00}, {0x09, 0x01, 0xcc, 0x00},
+        {0x17, 0x01, 0xcc, 0x00}, {0x28, 0x01, 0xcc, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xcb, 0x00}, {0x06, 0x01, 0xcb, 0x00},
+        {0x0a, 0x01, 0xcb, 0x00}, {0x0f, 0x01, 0xcb, 0x00},
+        {0x18, 0x01, 0xcb, 0x00}, {0x1f, 0x01, 0xcb, 0x00},
+        {0x29, 0x01, 0xcb, 0x00}, {0x38, 0x01, 0xcb, 0x01},
+        {0x03, 0x01, 0xcc, 0x00}, {0x06, 0x01, 0xcc, 0x00},
+        {0x0a, 0x01, 0xcc, 0x00}, {0x0f, 0x01, 0xcc, 0x00},
+        {0x18, 0x01, 0xcc, 0x00}, {0x1f, 0x01, 0xcc, 0x00},
+        {0x29, 0x01, 0xcc, 0x00}, {0x38, 0x01, 0xcc, 0x01}
+    },
+    {
+        {0xd3, 0x00, 0x00, 0x00}, {0xd4, 0x00, 0x00, 0x00},
+        {0xd6, 0x00, 0x00, 0x00}, {0xd7, 0x00, 0x00, 0x00},
+        {0xda, 0x00, 0x00, 0x00}, {0xdb, 0x00, 0x00, 0x00},
+        {0xdd, 0x00, 0x00, 0x00}, {0xde, 0x00, 0x00, 0x00},
+        {0xe2, 0x00, 0x00, 0x00}, {0xe4, 0x00, 0x00, 0x00},
+        {0xe8, 0x00, 0x00, 0x00}, {0xeb, 0x00, 0x00, 0x00},
+        {0xf0, 0x00, 0x00, 0x00}, {0xf3, 0x00, 0x00, 0x00},
+        {0xf7, 0x00, 0x00, 0x00}, {0xfa, 0x00, 0x00, 0x01}
+    },
+    {
+        {0x00, 0x01, 0xd3, 0x01}, {0x00, 0x01, 0xd4, 0x01},
+        {0x00, 0x01, 0xd6, 0x01}, {0x00, 0x01, 0xdd, 0x01},
+        {0x00, 0x01, 0xde, 0x01}, {0x00, 0x01, 0xdf, 0x01},
+        {0x00, 0x01, 0xf1, 0x01}, {0x00, 0x01, 0xf4, 0x01},
+        {0x00, 0x01, 0xf5, 0x01}, {0x00, 0x01, 0xf6, 0x01},
+        {0x00, 0x01, 0xf7, 0x01}, {0x00, 0x01, 0xf8, 0x01},
+        {0x00, 0x01, 0xfa, 0x01}, {0x00, 0x01, 0xfb, 0x01},
+        {0x00, 0x01, 0xfc, 0x01}, {0x00, 0x01, 0xfd, 0x01}
+    },
+    {
+        {0x01, 0x01, 0xd3, 0x00}, {0x16, 0x01, 0xd3, 0x01},
+        {0x01, 0x01, 0xd4, 0x00}, {0x16, 0x01, 0xd4, 0x01},
+        {0x01, 0x01, 0xd6, 0x00}, {0x16, 0x01, 0xd6, 0x01},
+        {0x01, 0x01, 0xdd, 0x00}, {0x16, 0x01, 0xdd, 0x01},
+        {0x01, 0x01, 0xde, 0x00}, {0x16, 0x01, 0xde, 0x01},
+        {0x01, 0x01, 0xdf, 0x00}, {0x16, 0x01, 0xdf, 0x01},
+        {0x01, 0x01, 0xf1, 0x00}, {0x16, 0x01, 0xf1, 0x01},
+        {0x01, 0x01, 0xf4, 0x00}, {0x16, 0x01, 0xf4, 0x01}
+    },
+    /* 210 */
+    {
+        {0x02, 0x01, 0xd3, 0x00}, {0x09, 0x01, 0xd3, 0x00},
+        {0x17, 0x01, 0xd3, 0x00}, {0x28, 0x01, 0xd3, 0x01},
+        {0x02, 0x01, 0xd4, 0x00}, {0x09, 0x01, 0xd4, 0x00},
+        {0x17, 0x01, 0xd4, 0x00}, {0x28, 0x01, 0xd4, 0x01},
+        {0x02, 0x01, 0xd6, 0x00}, {0x09, 0x01, 0xd6, 0x00},
+        {0x17, 0x01, 0xd6, 0x00}, {0x28, 0x01, 0xd6, 0x01},
+        {0x02, 0x01, 0xdd, 0x00}, {0x09, 0x01, 0xdd, 0x00},
+        {0x17, 0x01, 0xdd, 0x00}, {0x28, 0x01, 0xdd, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xd3, 0x00}, {0x06, 0x01, 0xd3, 0x00},
+        {0x0a, 0x01, 0xd3, 0x00}, {0x0f, 0x01, 0xd3, 0x00},
+        {0x18, 0x01, 0xd3, 0x00}, {0x1f, 0x01, 0xd3, 0x00},
+        {0x29, 0x01, 0xd3, 0x00}, {0x38, 0x01, 0xd3, 0x01},
+        {0x03, 0x01, 0xd4, 0x00}, {0x06, 0x01, 0xd4, 0x00},
+        {0x0a, 0x01, 0xd4, 0x00}, {0x0f, 0x01, 0xd4, 0x00},
+        {0x18, 0x01, 0xd4, 0x00}, {0x1f, 0x01, 0xd4, 0x00},
+        {0x29, 0x01, 0xd4, 0x00}, {0x38, 0x01, 0xd4, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xd6, 0x00}, {0x06, 0x01, 0xd6, 0x00},
+        {0x0a, 0x01, 0xd6, 0x00}, {0x0f, 0x01, 0xd6, 0x00},
+        {0x18, 0x01, 0xd6, 0x00}, {0x1f, 0x01, 0xd6, 0x00},
+        {0x29, 0x01, 0xd6, 0x00}, {0x38, 0x01, 0xd6, 0x01},
+        {0x03, 0x01, 0xdd, 0x00}, {0x06, 0x01, 0xdd, 0x00},
+        {0x0a, 0x01, 0xdd, 0x00}, {0x0f, 0x01, 0xdd, 0x00},
+        {0x18, 0x01, 0xdd, 0x00}, {0x1f, 0x01, 0xdd, 0x00},
+        {0x29, 0x01, 0xdd, 0x00}, {0x38, 0x01, 0xdd, 0x01}
+    },
+    {
+        {0x02, 0x01, 0xde, 0x00}, {0x09, 0x01, 0xde, 0x00},
+        {0x17, 0x01, 0xde, 0x00}, {0x28, 0x01, 0xde, 0x01},
+        {0x02, 0x01, 0xdf, 0x00}, {0x09, 0x01, 0xdf, 0x00},
+        {0x17, 0x01, 0xdf, 0x00}, {0x28, 0x01, 0xdf, 0x01},
+        {0x02, 0x01, 0xf1, 0x00}, {0x09, 0x01, 0xf1, 0x00},
+        {0x17, 0x01, 0xf1, 0x00}, {0x28, 0x01, 0xf1, 0x01},
+        {0x02, 0x01, 0xf4, 0x00}, {0x09, 0x01, 0xf4, 0x00},
+        {0x17, 0x01, 0xf4, 0x00}, {0x28, 0x01, 0xf4, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xde, 0x00}, {0x06, 0x01, 0xde, 0x00},
+        {0x0a, 0x01, 0xde, 0x00}, {0x0f, 0x01, 0xde, 0x00},
+        {0x18, 0x01, 0xde, 0x00}, {0x1f, 0x01, 0xde, 0x00},
+        {0x29, 0x01, 0xde, 0x00}, {0x38, 0x01, 0xde, 0x01},
+        {0x03, 0x01, 0xdf, 0x00}, {0x06, 0x01, 0xdf, 0x00},
+        {0x0a, 0x01, 0xdf, 0x00}, {0x0f, 0x01, 0xdf, 0x00},
+        {0x18, 0x01, 0xdf, 0x00}, {0x1f, 0x01, 0xdf, 0x00},
+        {0x29, 0x01, 0xdf, 0x00}, {0x38, 0x01, 0xdf, 0x01}
+    },
+    /* 215 */
+    {
+        {0x03, 0x01, 0xf1, 0x00}, {0x06, 0x01, 0xf1, 0x00},
+        {0x0a, 0x01, 0xf1, 0x00}, {0x0f, 0x01, 0xf1, 0x00},
+        {0x18, 0x01, 0xf1, 0x00}, {0x1f, 0x01, 0xf1, 0x00},
+        {0x29, 0x01, 0xf1, 0x00}, {0x38, 0x01, 0xf1, 0x01},
+        {0x03, 0x01, 0xf4, 0x00}, {0x06, 0x01, 0xf4, 0x00},
+        {0x0a, 0x01, 0xf4, 0x00}, {0x0f, 0x01, 0xf4, 0x00},
+        {0x18, 0x01, 0xf4, 0x00}, {0x1f, 0x01, 0xf4, 0x00},
+        {0x29, 0x01, 0xf4, 0x00}, {0x38, 0x01, 0xf4, 0x01}
+    },
+    {
+        {0x01, 0x01, 0xf5, 0x00}, {0x16, 0x01, 0xf5, 0x01},
+        {0x01, 0x01, 0xf6, 0x00}, {0x16, 0x01, 0xf6, 0x01},
+        {0x01, 0x01, 0xf7, 0x00}, {0x16, 0x01, 0xf7, 0x01},
+        {0x01, 0x01, 0xf8, 0x00}, {0x16, 0x01, 0xf8, 0x01},
+        {0x01, 0x01, 0xfa, 0x00}, {0x16, 0x01, 0xfa, 0x01},
+        {0x01, 0x01, 0xfb, 0x00}, {0x16, 0x01, 0xfb, 0x01},
+        {0x01, 0x01, 0xfc, 0x00}, {0x16, 0x01, 0xfc, 0x01},
+        {0x01, 0x01, 0xfd, 0x00}, {0x16, 0x01, 0xfd, 0x01}
+    },
+    {
+        {0x02, 0x01, 0xf5, 0x00}, {0x09, 0x01, 0xf5, 0x00},
+        {0x17, 0x01, 0xf5, 0x00}, {0x28, 0x01, 0xf5, 0x01},
+        {0x02, 0x01, 0xf6, 0x00}, {0x09, 0x01, 0xf6, 0x00},
+        {0x17, 0x01, 0xf6, 0x00}, {0x28, 0x01, 0xf6, 0x01},
+        {0x02, 0x01, 0xf7, 0x00}, {0x09, 0x01, 0xf7, 0x00},
+        {0x17, 0x01, 0xf7, 0x00}, {0x28, 0x01, 0xf7, 0x01},
+        {0x02, 0x01, 0xf8, 0x00}, {0x09, 0x01, 0xf8, 0x00},
+        {0x17, 0x01, 0xf8, 0x00}, {0x28, 0x01, 0xf8, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xf5, 0x00}, {0x06, 0x01, 0xf5, 0x00},
+        {0x0a, 0x01, 0xf5, 0x00}, {0x0f, 0x01, 0xf5, 0x00},
+        {0x18, 0x01, 0xf5, 0x00}, {0x1f, 0x01, 0xf5, 0x00},
+        {0x29, 0x01, 0xf5, 0x00}, {0x38, 0x01, 0xf5, 0x01},
+        {0x03, 0x01, 0xf6, 0x00}, {0x06, 0x01, 0xf6, 0x00},
+        {0x0a, 0x01, 0xf6, 0x00}, {0x0f, 0x01, 0xf6, 0x00},
+        {0x18, 0x01, 0xf6, 0x00}, {0x1f, 0x01, 0xf6, 0x00},
+        {0x29, 0x01, 0xf6, 0x00}, {0x38, 0x01, 0xf6, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xf7, 0x00}, {0x06, 0x01, 0xf7, 0x00},
+        {0x0a, 0x01, 0xf7, 0x00}, {0x0f, 0x01, 0xf7, 0x00},
+        {0x18, 0x01, 0xf7, 0x00}, {0x1f, 0x01, 0xf7, 0x00},
+        {0x29, 0x01, 0xf7, 0x00}, {0x38, 0x01, 0xf7, 0x01},
+        {0x03, 0x01, 0xf8, 0x00}, {0x06, 0x01, 0xf8, 0x00},
+        {0x0a, 0x01, 0xf8, 0x00}, {0x0f, 0x01, 0xf8, 0x00},
+        {0x18, 0x01, 0xf8, 0x00}, {0x1f, 0x01, 0xf8, 0x00},
+        {0x29, 0x01, 0xf8, 0x00}, {0x38, 0x01, 0xf8, 0x01}
+    },
+    /* 220 */
+    {
+        {0x02, 0x01, 0xfa, 0x00}, {0x09, 0x01, 0xfa, 0x00},
+        {0x17, 0x01, 0xfa, 0x00}, {0x28, 0x01, 0xfa, 0x01},
+        {0x02, 0x01, 0xfb, 0x00}, {0x09, 0x01, 0xfb, 0x00},
+        {0x17, 0x01, 0xfb, 0x00}, {0x28, 0x01, 0xfb, 0x01},
+        {0x02, 0x01, 0xfc, 0x00}, {0x09, 0x01, 0xfc, 0x00},
+        {0x17, 0x01, 0xfc, 0x00}, {0x28, 0x01, 0xfc, 0x01},
+        {0x02, 0x01, 0xfd, 0x00}, {0x09, 0x01, 0xfd, 0x00},
+        {0x17, 0x01, 0xfd, 0x00}, {0x28, 0x01, 0xfd, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xfa, 0x00}, {0x06, 0x01, 0xfa, 0x00},
+        {0x0a, 0x01, 0xfa, 0x00}, {0x0f, 0x01, 0xfa, 0x00},
+        {0x18, 0x01, 0xfa, 0x00}, {0x1f, 0x01, 0xfa, 0x00},
+        {0x29, 0x01, 0xfa, 0x00}, {0x38, 0x01, 0xfa, 0x01},
+        {0x03, 0x01, 0xfb, 0x00}, {0x06, 0x01, 0xfb, 0x00},
+        {0x0a, 0x01, 0xfb, 0x00}, {0x0f, 0x01, 0xfb, 0x00},
+        {0x18, 0x01, 0xfb, 0x00}, {0x1f, 0x01, 0xfb, 0x00},
+        {0x29, 0x01, 0xfb, 0x00}, {0x38, 0x01, 0xfb, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xfc, 0x00}, {0x06, 0x01, 0xfc, 0x00},
+        {0x0a, 0x01, 0xfc, 0x00}, {0x0f, 0x01, 0xfc, 0x00},
+        {0x18, 0x01, 0xfc, 0x00}, {0x1f, 0x01, 0xfc, 0x00},
+        {0x29, 0x01, 0xfc, 0x00}, {0x38, 0x01, 0xfc, 0x01},
+        {0x03, 0x01, 0xfd, 0x00}, {0x06, 0x01, 0xfd, 0x00},
+        {0x0a, 0x01, 0xfd, 0x00}, {0x0f, 0x01, 0xfd, 0x00},
+        {0x18, 0x01, 0xfd, 0x00}, {0x1f, 0x01, 0xfd, 0x00},
+        {0x29, 0x01, 0xfd, 0x00}, {0x38, 0x01, 0xfd, 0x01}
+    },
+    {
+        {0x00, 0x01, 0xfe, 0x01}, {0xe3, 0x00, 0x00, 0x00},
+        {0xe5, 0x00, 0x00, 0x00}, {0xe6, 0x00, 0x00, 0x00},
+        {0xe9, 0x00, 0x00, 0x00}, {0xea, 0x00, 0x00, 0x00},
+        {0xec, 0x00, 0x00, 0x00}, {0xed, 0x00, 0x00, 0x00},
+        {0xf1, 0x00, 0x00, 0x00}, {0xf2, 0x00, 0x00, 0x00},
+        {0xf4, 0x00, 0x00, 0x00}, {0xf5, 0x00, 0x00, 0x00},
+        {0xf8, 0x00, 0x00, 0x00}, {0xf9, 0x00, 0x00, 0x00},
+        {0xfb, 0x00, 0x00, 0x00}, {0xfc, 0x00, 0x00, 0x01}
+    },
+    {
+        {0x01, 0x01, 0xfe, 0x00}, {0x16, 0x01, 0xfe, 0x01},
+        {0x00, 0x01, 0x02, 0x01}, {0x00, 0x01, 0x03, 0x01},
+        {0x00, 0x01, 0x04, 0x01}, {0x00, 0x01, 0x05, 0x01},
+        {0x00, 0x01, 0x06, 0x01}, {0x00, 0x01, 0x07, 0x01},
+        {0x00, 0x01, 0x08, 0x01}, {0x00, 0x01, 0x0b, 0x01},
+        {0x00, 0x01, 0x0c, 0x01}, {0x00, 0x01, 0x0e, 0x01},
+        {0x00, 0x01, 0x0f, 0x01}, {0x00, 0x01, 0x10, 0x01},
+        {0x00, 0x01, 0x11, 0x01}, {0x00, 0x01, 0x12, 0x01}
+    },
+    /* 225 */
+    {
+        {0x02, 0x01, 0xfe, 0x00}, {0x09, 0x01, 0xfe, 0x00},
+        {0x17, 0x01, 0xfe, 0x00}, {0x28, 0x01, 0xfe, 0x01},
+        {0x01, 0x01, 0x02, 0x00}, {0x16, 0x01, 0x02, 0x01},
+        {0x01, 0x01, 0x03, 0x00}, {0x16, 0x01, 0x03, 0x01},
+        {0x01, 0x01, 0x04, 0x00}, {0x16, 0x01, 0x04, 0x01},
+        {0x01, 0x01, 0x05, 0x00}, {0x16, 0x01, 0x05, 0x01},
+        {0x01, 0x01, 0x06, 0x00}, {0x16, 0x01, 0x06, 0x01},
+        {0x01, 0x01, 0x07, 0x00}, {0x16, 0x01, 0x07, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xfe, 0x00}, {0x06, 0x01, 0xfe, 0x00},
+        {0x0a, 0x01, 0xfe, 0x00}, {0x0f, 0x01, 0xfe, 0x00},
+        {0x18, 0x01, 0xfe, 0x00}, {0x1f, 0x01, 0xfe, 0x00},
+        {0x29, 0x01, 0xfe, 0x00}, {0x38, 0x01, 0xfe, 0x01},
+        {0x02, 0x01, 0x02, 0x00}, {0x09, 0x01, 0x02, 0x00},
+        {0x17, 0x01, 0x02, 0x00}, {0x28, 0x01, 0x02, 0x01},
+        {0x02, 0x01, 0x03, 0x00}, {0x09, 0x01, 0x03, 0x00},
+        {0x17, 0x01, 0x03, 0x00}, {0x28, 0x01, 0x03, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x02, 0x00}, {0x06, 0x01, 0x02, 0x00},
+        {0x0a, 0x01, 0x02, 0x00}, {0x0f, 0x01, 0x02, 0x00},
+        {0x18, 0x01, 0x02, 0x00}, {0x1f, 0x01, 0x02, 0x00},
+        {0x29, 0x01, 0x02, 0x00}, {0x38, 0x01, 0x02, 0x01},
+        {0x03, 0x01, 0x03, 0x00}, {0x06, 0x01, 0x03, 0x00},
+        {0x0a, 0x01, 0x03, 0x00}, {0x0f, 0x01, 0x03, 0x00},
+        {0x18, 0x01, 0x03, 0x00}, {0x1f, 0x01, 0x03, 0x00},
+        {0x29, 0x01, 0x03, 0x00}, {0x38, 0x01, 0x03, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x04, 0x00}, {0x09, 0x01, 0x04, 0x00},
+        {0x17, 0x01, 0x04, 0x00}, {0x28, 0x01, 0x04, 0x01},
+        {0x02, 0x01, 0x05, 0x00}, {0x09, 0x01, 0x05, 0x00},
+        {0x17, 0x01, 0x05, 0x00}, {0x28, 0x01, 0x05, 0x01},
+        {0x02, 0x01, 0x06, 0x00}, {0x09, 0x01, 0x06, 0x00},
+        {0x17, 0x01, 0x06, 0x00}, {0x28, 0x01, 0x06, 0x01},
+        {0x02, 0x01, 0x07, 0x00}, {0x09, 0x01, 0x07, 0x00},
+        {0x17, 0x01, 0x07, 0x00}, {0x28, 0x01, 0x07, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x04, 0x00}, {0x06, 0x01, 0x04, 0x00},
+        {0x0a, 0x01, 0x04, 0x00}, {0x0f, 0x01, 0x04, 0x00},
+        {0x18, 0x01, 0x04, 0x00}, {0x1f, 0x01, 0x04, 0x00},
+        {0x29, 0x01, 0x04, 0x00}, {0x38, 0x01, 0x04, 0x01},
+        {0x03, 0x01, 0x05, 0x00}, {0x06, 0x01, 0x05, 0x00},
+        {0x0a, 0x01, 0x05, 0x00}, {0x0f, 0x01, 0x05, 0x00},
+        {0x18, 0x01, 0x05, 0x00}, {0x1f, 0x01, 0x05, 0x00},
+        {0x29, 0x01, 0x05, 0x00}, {0x38, 0x01, 0x05, 0x01}
+    },
+    /* 230 */
+    {
+        {0x03, 0x01, 0x06, 0x00}, {0x06, 0x01, 0x06, 0x00},
+        {0x0a, 0x01, 0x06, 0x00}, {0x0f, 0x01, 0x06, 0x00},
+        {0x18, 0x01, 0x06, 0x00}, {0x1f, 0x01, 0x06, 0x00},
+        {0x29, 0x01, 0x06, 0x00}, {0x38, 0x01, 0x06, 0x01},
+        {0x03, 0x01, 0x07, 0x00}, {0x06, 0x01, 0x07, 0x00},
+        {0x0a, 0x01, 0x07, 0x00}, {0x0f, 0x01, 0x07, 0x00},
+        {0x18, 0x01, 0x07, 0x00}, {0x1f, 0x01, 0x07, 0x00},
+        {0x29, 0x01, 0x07, 0x00}, {0x38, 0x01, 0x07, 0x01}
+    },
+    {
+        {0x01, 0x01, 0x08, 0x00}, {0x16, 0x01, 0x08, 0x01},
+        {0x01, 0x01, 0x0b, 0x00}, {0x16, 0x01, 0x0b, 0x01},
+        {0x01, 0x01, 0x0c, 0x00}, {0x16, 0x01, 0x0c, 0x01},
+        {0x01, 0x01, 0x0e, 0x00}, {0x16, 0x01, 0x0e, 0x01},
+        {0x01, 0x01, 0x0f, 0x00}, {0x16, 0x01, 0x0f, 0x01},
+        {0x01, 0x01, 0x10, 0x00}, {0x16, 0x01, 0x10, 0x01},
+        {0x01, 0x01, 0x11, 0x00}, {0x16, 0x01, 0x11, 0x01},
+        {0x01, 0x01, 0x12, 0x00}, {0x16, 0x01, 0x12, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x08, 0x00}, {0x09, 0x01, 0x08, 0x00},
+        {0x17, 0x01, 0x08, 0x00}, {0x28, 0x01, 0x08, 0x01},
+        {0x02, 0x01, 0x0b, 0x00}, {0x09, 0x01, 0x0b, 0x00},
+        {0x17, 0x01, 0x0b, 0x00}, {0x28, 0x01, 0x0b, 0x01},
+        {0x02, 0x01, 0x0c, 0x00}, {0x09, 0x01, 0x0c, 0x00},
+        {0x17, 0x01, 0x0c, 0x00}, {0x28, 0x01, 0x0c, 0x01},
+        {0x02, 0x01, 0x0e, 0x00}, {0x09, 0x01, 0x0e, 0x00},
+        {0x17, 0x01, 0x0e, 0x00}, {0x28, 0x01, 0x0e, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x08, 0x00}, {0x06, 0x01, 0x08, 0x00},
+        {0x0a, 0x01, 0x08, 0x00}, {0x0f, 0x01, 0x08, 0x00},
+        {0x18, 0x01, 0x08, 0x00}, {0x1f, 0x01, 0x08, 0x00},
+        {0x29, 0x01, 0x08, 0x00}, {0x38, 0x01, 0x08, 0x01},
+        {0x03, 0x01, 0x0b, 0x00}, {0x06, 0x01, 0x0b, 0x00},
+        {0x0a, 0x01, 0x0b, 0x00}, {0x0f, 0x01, 0x0b, 0x00},
+        {0x18, 0x01, 0x0b, 0x00}, {0x1f, 0x01, 0x0b, 0x00},
+        {0x29, 0x01, 0x0b, 0x00}, {0x38, 0x01, 0x0b, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x0c, 0x00}, {0x06, 0x01, 0x0c, 0x00},
+        {0x0a, 0x01, 0x0c, 0x00}, {0x0f, 0x01, 0x0c, 0x00},
+        {0x18, 0x01, 0x0c, 0x00}, {0x1f, 0x01, 0x0c, 0x00},
+        {0x29, 0x01, 0x0c, 0x00}, {0x38, 0x01, 0x0c, 0x01},
+        {0x03, 0x01, 0x0e, 0x00}, {0x06, 0x01, 0x0e, 0x00},
+        {0x0a, 0x01, 0x0e, 0x00}, {0x0f, 0x01, 0x0e, 0x00},
+        {0x18, 0x01, 0x0e, 0x00}, {0x1f, 0x01, 0x0e, 0x00},
+        {0x29, 0x01, 0x0e, 0x00}, {0x38, 0x01, 0x0e, 0x01}
+    },
+    /* 235 */
+    {
+        {0x02, 0x01, 0x0f, 0x00}, {0x09, 0x01, 0x0f, 0x00},
+        {0x17, 0x01, 0x0f, 0x00}, {0x28, 0x01, 0x0f, 0x01},
+        {0x02, 0x01, 0x10, 0x00}, {0x09, 0x01, 0x10, 0x00},
+        {0x17, 0x01, 0x10, 0x00}, {0x28, 0x01, 0x10, 0x01},
+        {0x02, 0x01, 0x11, 0x00}, {0x09, 0x01, 0x11, 0x00},
+        {0x17, 0x01, 0x11, 0x00}, {0x28, 0x01, 0x11, 0x01},
+        {0x02, 0x01, 0x12, 0x00}, {0x09, 0x01, 0x12, 0x00},
+        {0x17, 0x01, 0x12, 0x00}, {0x28, 0x01, 0x12, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x0f, 0x00}, {0x06, 0x01, 0x0f, 0x00},
+        {0x0a, 0x01, 0x0f, 0x00}, {0x0f, 0x01, 0x0f, 0x00},
+        {0x18, 0x01, 0x0f, 0x00}, {0x1f, 0x01, 0x0f, 0x00},
+        {0x29, 0x01, 0x0f, 0x00}, {0x38, 0x01, 0x0f, 0x01},
+        {0x03, 0x01, 0x10, 0x00}, {0x06, 0x01, 0x10, 0x00},
+        {0x0a, 0x01, 0x10, 0x00}, {0x0f, 0x01, 0x10, 0x00},
+        {0x18, 0x01, 0x10, 0x00}, {0x1f, 0x01, 0x10, 0x00},
+        {0x29, 0x01, 0x10, 0x00}, {0x38, 0x01, 0x10, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x11, 0x00}, {0x06, 0x01, 0x11, 0x00},
+        {0x0a, 0x01, 0x11, 0x00}, {0x0f, 0x01, 0x11, 0x00},
+        {0x18, 0x01, 0x11, 0x00}, {0x1f, 0x01, 0x11, 0x00},
+        {0x29, 0x01, 0x11, 0x00}, {0x38, 0x01, 0x11, 0x01},
+        {0x03, 0x01, 0x12, 0x00}, {0x06, 0x01, 0x12, 0x00},
+        {0x0a, 0x01, 0x12, 0x00}, {0x0f, 0x01, 0x12, 0x00},
+        {0x18, 0x01, 0x12, 0x00}, {0x1f, 0x01, 0x12, 0x00},
+        {0x29, 0x01, 0x12, 0x00}, {0x38, 0x01, 0x12, 0x01}
+    },
+    {
+        {0x00, 0x01, 0x13, 0x01}, {0x00, 0x01, 0x14, 0x01},
+        {0x00, 0x01, 0x15, 0x01}, {0x00, 0x01, 0x17, 0x01},
+        {0x00, 0x01, 0x18, 0x01}, {0x00, 0x01, 0x19, 0x01},
+        {0x00, 0x01, 0x1a, 0x01}, {0x00, 0x01, 0x1b, 0x01},
+        {0x00, 0x01, 0x1c, 0x01}, {0x00, 0x01, 0x1d, 0x01},
+        {0x00, 0x01, 0x1e, 0x01}, {0x00, 0x01, 0x1f, 0x01},
+        {0x00, 0x01, 0x7f, 0x01}, {0x00, 0x01, 0xdc, 0x01},
+        {0x00, 0x01, 0xf9, 0x01}, {0xfd, 0x00, 0x00, 0x01}
+    },
+    {
+        {0x01, 0x01, 0x13, 0x00}, {0x16, 0x01, 0x13, 0x01},
+        {0x01, 0x01, 0x14, 0x00}, {0x16, 0x01, 0x14, 0x01},
+        {0x01, 0x01, 0x15, 0x00}, {0x16, 0x01, 0x15, 0x01},
+        {0x01, 0x01, 0x17, 0x00}, {0x16, 0x01, 0x17, 0x01},
+        {0x01, 0x01, 0x18, 0x00}, {0x16, 0x01, 0x18, 0x01},
+        {0x01, 0x01, 0x19, 0x00}, {0x16, 0x01, 0x19, 0x01},
+        {0x01, 0x01, 0x1a, 0x00}, {0x16, 0x01, 0x1a, 0x01},
+        {0x01, 0x01, 0x1b, 0x00}, {0x16, 0x01, 0x1b, 0x01}
+    },
+    /* 240 */
+    {
+        {0x02, 0x01, 0x13, 0x00}, {0x09, 0x01, 0x13, 0x00},
+        {0x17, 0x01, 0x13, 0x00}, {0x28, 0x01, 0x13, 0x01},
+        {0x02, 0x01, 0x14, 0x00}, {0x09, 0x01, 0x14, 0x00},
+        {0x17, 0x01, 0x14, 0x00}, {0x28, 0x01, 0x14, 0x01},
+        {0x02, 0x01, 0x15, 0x00}, {0x09, 0x01, 0x15, 0x00},
+        {0x17, 0x01, 0x15, 0x00}, {0x28, 0x01, 0x15, 0x01},
+        {0x02, 0x01, 0x17, 0x00}, {0x09, 0x01, 0x17, 0x00},
+        {0x17, 0x01, 0x17, 0x00}, {0x28, 0x01, 0x17, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x13, 0x00}, {0x06, 0x01, 0x13, 0x00},
+        {0x0a, 0x01, 0x13, 0x00}, {0x0f, 0x01, 0x13, 0x00},
+        {0x18, 0x01, 0x13, 0x00}, {0x1f, 0x01, 0x13, 0x00},
+        {0x29, 0x01, 0x13, 0x00}, {0x38, 0x01, 0x13, 0x01},
+        {0x03, 0x01, 0x14, 0x00}, {0x06, 0x01, 0x14, 0x00},
+        {0x0a, 0x01, 0x14, 0x00}, {0x0f, 0x01, 0x14, 0x00},
+        {0x18, 0x01, 0x14, 0x00}, {0x1f, 0x01, 0x14, 0x00},
+        {0x29, 0x01, 0x14, 0x00}, {0x38, 0x01, 0x14, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x15, 0x00}, {0x06, 0x01, 0x15, 0x00},
+        {0x0a, 0x01, 0x15, 0x00}, {0x0f, 0x01, 0x15, 0x00},
+        {0x18, 0x01, 0x15, 0x00}, {0x1f, 0x01, 0x15, 0x00},
+        {0x29, 0x01, 0x15, 0x00}, {0x38, 0x01, 0x15, 0x01},
+        {0x03, 0x01, 0x17, 0x00}, {0x06, 0x01, 0x17, 0x00},
+        {0x0a, 0x01, 0x17, 0x00}, {0x0f, 0x01, 0x17, 0x00},
+        {0x18, 0x01, 0x17, 0x00}, {0x1f, 0x01, 0x17, 0x00},
+        {0x29, 0x01, 0x17, 0x00}, {0x38, 0x01, 0x17, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x18, 0x00}, {0x09, 0x01, 0x18, 0x00},
+        {0x17, 0x01, 0x18, 0x00}, {0x28, 0x01, 0x18, 0x01},
+        {0x02, 0x01, 0x19, 0x00}, {0x09, 0x01, 0x19, 0x00},
+        {0x17, 0x01, 0x19, 0x00}, {0x28, 0x01, 0x19, 0x01},
+        {0x02, 0x01, 0x1a, 0x00}, {0x09, 0x01, 0x1a, 0x00},
+        {0x17, 0x01, 0x1a, 0x00}, {0x28, 0x01, 0x1a, 0x01},
+        {0x02, 0x01, 0x1b, 0x00}, {0x09, 0x01, 0x1b, 0x00},
+        {0x17, 0x01, 0x1b, 0x00}, {0x28, 0x01, 0x1b, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x18, 0x00}, {0x06, 0x01, 0x18, 0x00},
+        {0x0a, 0x01, 0x18, 0x00}, {0x0f, 0x01, 0x18, 0x00},
+        {0x18, 0x01, 0x18, 0x00}, {0x1f, 0x01, 0x18, 0x00},
+        {0x29, 0x01, 0x18, 0x00}, {0x38, 0x01, 0x18, 0x01},
+        {0x03, 0x01, 0x19, 0x00}, {0x06, 0x01, 0x19, 0x00},
+        {0x0a, 0x01, 0x19, 0x00}, {0x0f, 0x01, 0x19, 0x00},
+        {0x18, 0x01, 0x19, 0x00}, {0x1f, 0x01, 0x19, 0x00},
+        {0x29, 0x01, 0x19, 0x00}, {0x38, 0x01, 0x19, 0x01}
+    },
+    /* 245 */
+    {
+        {0x03, 0x01, 0x1a, 0x00}, {0x06, 0x01, 0x1a, 0x00},
+        {0x0a, 0x01, 0x1a, 0x00}, {0x0f, 0x01, 0x1a, 0x00},
+        {0x18, 0x01, 0x1a, 0x00}, {0x1f, 0x01, 0x1a, 0x00},
+        {0x29, 0x01, 0x1a, 0x00}, {0x38, 0x01, 0x1a, 0x01},
+        {0x03, 0x01, 0x1b, 0x00}, {0x06, 0x01, 0x1b, 0x00},
+        {0x0a, 0x01, 0x1b, 0x00}, {0x0f, 0x01, 0x1b, 0x00},
+        {0x18, 0x01, 0x1b, 0x00}, {0x1f, 0x01, 0x1b, 0x00},
+        {0x29, 0x01, 0x1b, 0x00}, {0x38, 0x01, 0x1b, 0x01}
+    },
+    {
+        {0x01, 0x01, 0x1c, 0x00}, {0x16, 0x01, 0x1c, 0x01},
+        {0x01, 0x01, 0x1d, 0x00}, {0x16, 0x01, 0x1d, 0x01},
+        {0x01, 0x01, 0x1e, 0x00}, {0x16, 0x01, 0x1e, 0x01},
+        {0x01, 0x01, 0x1f, 0x00}, {0x16, 0x01, 0x1f, 0x01},
+        {0x01, 0x01, 0x7f, 0x00}, {0x16, 0x01, 0x7f, 0x01},
+        {0x01, 0x01, 0xdc, 0x00}, {0x16, 0x01, 0xdc, 0x01},
+        {0x01, 0x01, 0xf9, 0x00}, {0x16, 0x01, 0xf9, 0x01},
+        {0xfe, 0x00, 0x00, 0x00}, {0xff, 0x00, 0x00, 0x01}
+    },
+    {
+        {0x02, 0x01, 0x1c, 0x00}, {0x09, 0x01, 0x1c, 0x00},
+        {0x17, 0x01, 0x1c, 0x00}, {0x28, 0x01, 0x1c, 0x01},
+        {0x02, 0x01, 0x1d, 0x00}, {0x09, 0x01, 0x1d, 0x00},
+        {0x17, 0x01, 0x1d, 0x00}, {0x28, 0x01, 0x1d, 0x01},
+        {0x02, 0x01, 0x1e, 0x00}, {0x09, 0x01, 0x1e, 0x00},
+        {0x17, 0x01, 0x1e, 0x00}, {0x28, 0x01, 0x1e, 0x01},
+        {0x02, 0x01, 0x1f, 0x00}, {0x09, 0x01, 0x1f, 0x00},
+        {0x17, 0x01, 0x1f, 0x00}, {0x28, 0x01, 0x1f, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x1c, 0x00}, {0x06, 0x01, 0x1c, 0x00},
+        {0x0a, 0x01, 0x1c, 0x00}, {0x0f, 0x01, 0x1c, 0x00},
+        {0x18, 0x01, 0x1c, 0x00}, {0x1f, 0x01, 0x1c, 0x00},
+        {0x29, 0x01, 0x1c, 0x00}, {0x38, 0x01, 0x1c, 0x01},
+        {0x03, 0x01, 0x1d, 0x00}, {0x06, 0x01, 0x1d, 0x00},
+        {0x0a, 0x01, 0x1d, 0x00}, {0x0f, 0x01, 0x1d, 0x00},
+        {0x18, 0x01, 0x1d, 0x00}, {0x1f, 0x01, 0x1d, 0x00},
+        {0x29, 0x01, 0x1d, 0x00}, {0x38, 0x01, 0x1d, 0x01}
+    },
+    {
+        {0x03, 0x01, 0x1e, 0x00}, {0x06, 0x01, 0x1e, 0x00},
+        {0x0a, 0x01, 0x1e, 0x00}, {0x0f, 0x01, 0x1e, 0x00},
+        {0x18, 0x01, 0x1e, 0x00}, {0x1f, 0x01, 0x1e, 0x00},
+        {0x29, 0x01, 0x1e, 0x00}, {0x38, 0x01, 0x1e, 0x01},
+        {0x03, 0x01, 0x1f, 0x00}, {0x06, 0x01, 0x1f, 0x00},
+        {0x0a, 0x01, 0x1f, 0x00}, {0x0f, 0x01, 0x1f, 0x00},
+        {0x18, 0x01, 0x1f, 0x00}, {0x1f, 0x01, 0x1f, 0x00},
+        {0x29, 0x01, 0x1f, 0x00}, {0x38, 0x01, 0x1f, 0x01}
+    },
+    /* 250 */
+    {
+        {0x02, 0x01, 0x7f, 0x00}, {0x09, 0x01, 0x7f, 0x00},
+        {0x17, 0x01, 0x7f, 0x00}, {0x28, 0x01, 0x7f, 0x01},
+        {0x02, 0x01, 0xdc, 0x00}, {0x09, 0x01, 0xdc, 0x00},
+        {0x17, 0x01, 0xdc, 0x00}, {0x28, 0x01, 0xdc, 0x01},
+        {0x02, 0x01, 0xf9, 0x00}, {0x09, 0x01, 0xf9, 0x00},
+        {0x17, 0x01, 0xf9, 0x00}, {0x28, 0x01, 0xf9, 0x01},
+        {0x00, 0x01, 0x0a, 0x01}, {0x00, 0x01, 0x0d, 0x01},
+        {0x00, 0x01, 0x16, 0x01}, {0xfa, 0x00, 0x00, 0x00}
+    },
+    {
+        {0x03, 0x01, 0x7f, 0x00}, {0x06, 0x01, 0x7f, 0x00},
+        {0x0a, 0x01, 0x7f, 0x00}, {0x0f, 0x01, 0x7f, 0x00},
+        {0x18, 0x01, 0x7f, 0x00}, {0x1f, 0x01, 0x7f, 0x00},
+        {0x29, 0x01, 0x7f, 0x00}, {0x38, 0x01, 0x7f, 0x01},
+        {0x03, 0x01, 0xdc, 0x00}, {0x06, 0x01, 0xdc, 0x00},
+        {0x0a, 0x01, 0xdc, 0x00}, {0x0f, 0x01, 0xdc, 0x00},
+        {0x18, 0x01, 0xdc, 0x00}, {0x1f, 0x01, 0xdc, 0x00},
+        {0x29, 0x01, 0xdc, 0x00}, {0x38, 0x01, 0xdc, 0x01}
+    },
+    {
+        {0x03, 0x01, 0xf9, 0x00}, {0x06, 0x01, 0xf9, 0x00},
+        {0x0a, 0x01, 0xf9, 0x00}, {0x0f, 0x01, 0xf9, 0x00},
+        {0x18, 0x01, 0xf9, 0x00}, {0x1f, 0x01, 0xf9, 0x00},
+        {0x29, 0x01, 0xf9, 0x00}, {0x38, 0x01, 0xf9, 0x01},
+        {0x01, 0x01, 0x0a, 0x00}, {0x16, 0x01, 0x0a, 0x01},
+        {0x01, 0x01, 0x0d, 0x00}, {0x16, 0x01, 0x0d, 0x01},
+        {0x01, 0x01, 0x16, 0x00}, {0x16, 0x01, 0x16, 0x01},
+        {0xfc, 0x00, 0x00, 0x00}, {0xfc, 0x00, 0x00, 0x00}
+    },
+    {
+        {0x02, 0x01, 0x0a, 0x00}, {0x09, 0x01, 0x0a, 0x00},
+        {0x17, 0x01, 0x0a, 0x00}, {0x28, 0x01, 0x0a, 0x01},
+        {0x02, 0x01, 0x0d, 0x00}, {0x09, 0x01, 0x0d, 0x00},
+        {0x17, 0x01, 0x0d, 0x00}, {0x28, 0x01, 0x0d, 0x01},
+        {0x02, 0x01, 0x16, 0x00}, {0x09, 0x01, 0x16, 0x00},
+        {0x17, 0x01, 0x16, 0x00}, {0x28, 0x01, 0x16, 0x01},
+        {0xfd, 0x00, 0x00, 0x00}, {0xfd, 0x00, 0x00, 0x00},
+        {0xfd, 0x00, 0x00, 0x00}, {0xfd, 0x00, 0x00, 0x00}
+    },
+    {
+        {0x03, 0x01, 0x0a, 0x00}, {0x06, 0x01, 0x0a, 0x00},
+        {0x0a, 0x01, 0x0a, 0x00}, {0x0f, 0x01, 0x0a, 0x00},
+        {0x18, 0x01, 0x0a, 0x00}, {0x1f, 0x01, 0x0a, 0x00},
+        {0x29, 0x01, 0x0a, 0x00}, {0x38, 0x01, 0x0a, 0x01},
+        {0x03, 0x01, 0x0d, 0x00}, {0x06, 0x01, 0x0d, 0x00},
+        {0x0a, 0x01, 0x0d, 0x00}, {0x0f, 0x01, 0x0d, 0x00},
+        {0x18, 0x01, 0x0d, 0x00}, {0x1f, 0x01, 0x0d, 0x00},
+        {0x29, 0x01, 0x0d, 0x00}, {0x38, 0x01, 0x0d, 0x01}
+    },
+    /* 255 */
+    {
+        {0x03, 0x01, 0x16, 0x00}, {0x06, 0x01, 0x16, 0x00},
+        {0x0a, 0x01, 0x16, 0x00}, {0x0f, 0x01, 0x16, 0x00},
+        {0x18, 0x01, 0x16, 0x00}, {0x1f, 0x01, 0x16, 0x00},
+        {0x29, 0x01, 0x16, 0x00}, {0x38, 0x01, 0x16, 0x01},
+        {0xff, 0x00, 0x00, 0x00}, {0xff, 0x00, 0x00, 0x00},
+        {0xff, 0x00, 0x00, 0x00}, {0xff, 0x00, 0x00, 0x00},
+        {0xff, 0x00, 0x00, 0x00}, {0xff, 0x00, 0x00, 0x00},
+        {0xff, 0x00, 0x00, 0x00}, {0xff, 0x00, 0x00, 0x00}
+    }
+};
+
+
+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)
+{
+    u_char  *end, ch, ending;
+
+    ch = 0;
+    ending = 1;
+
+    end = src + len;
+
+    while (src != end) {
+        ch = *src++;
+
+        if (ngx_http_v2_huff_decode_bits(state, &ending, ch >> 4, dst)
+            != NGX_OK)
+        {
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0,
+                           "http2 huffman decoding error at state %d: "
+                           "bad code 0x%Xd", *state, ch >> 4);
+
+            return NGX_ERROR;
+        }
+
+        if (ngx_http_v2_huff_decode_bits(state, &ending, ch & 0xf, dst)
+            != NGX_OK)
+        {
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0,
+                           "http2 huffman decoding error at state %d: "
+                           "bad code 0x%Xd", *state, ch & 0xf);
+
+            return NGX_ERROR;
+        }
+    }
+
+    if (last) {
+        if (!ending) {
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+                           "http2 huffman decoding error: "
+                           "incomplete code 0x%Xd", ch);
+
+            return NGX_ERROR;
+        }
+
+        *state = 0;
+    }
+
+    return NGX_OK;
+}
+
+
+
+static ngx_inline ngx_int_t
+ngx_http_v2_huff_decode_bits(u_char *state, u_char *ending, ngx_uint_t bits,
+    u_char **dst)
+{
+    ngx_http_v2_huff_decode_code_t  code;
+
+    code = ngx_http_v2_huff_decode_codes[*state][bits];
+
+    if (code.next == *state) {
+        return NGX_ERROR;
+    }
+
+    if (code.emit) {
+        *(*dst)++ = code.sym;
+    }
+
+    *ending = code.ending;
+    *state = code.next;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/http/v2/ngx_http_v2_huff_encode.c b/nginx/src/http/v2/ngx_http_v2_huff_encode.c
new file mode 100644 (file)
index 0000000..3f822cd
--- /dev/null
@@ -0,0 +1,254 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Valentin V. Bartenev
+ * Copyright (C) 2015 Vlad Krasnov
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    uint32_t  code;
+    uint32_t  len;
+} ngx_http_v2_huff_encode_code_t;
+
+
+static ngx_http_v2_huff_encode_code_t  ngx_http_v2_huff_encode_table[256] =
+{
+    {0x00001ff8, 13}, {0x007fffd8, 23}, {0x0fffffe2, 28}, {0x0fffffe3, 28},
+    {0x0fffffe4, 28}, {0x0fffffe5, 28}, {0x0fffffe6, 28}, {0x0fffffe7, 28},
+    {0x0fffffe8, 28}, {0x00ffffea, 24}, {0x3ffffffc, 30}, {0x0fffffe9, 28},
+    {0x0fffffea, 28}, {0x3ffffffd, 30}, {0x0fffffeb, 28}, {0x0fffffec, 28},
+    {0x0fffffed, 28}, {0x0fffffee, 28}, {0x0fffffef, 28}, {0x0ffffff0, 28},
+    {0x0ffffff1, 28}, {0x0ffffff2, 28}, {0x3ffffffe, 30}, {0x0ffffff3, 28},
+    {0x0ffffff4, 28}, {0x0ffffff5, 28}, {0x0ffffff6, 28}, {0x0ffffff7, 28},
+    {0x0ffffff8, 28}, {0x0ffffff9, 28}, {0x0ffffffa, 28}, {0x0ffffffb, 28},
+    {0x00000014,  6}, {0x000003f8, 10}, {0x000003f9, 10}, {0x00000ffa, 12},
+    {0x00001ff9, 13}, {0x00000015,  6}, {0x000000f8,  8}, {0x000007fa, 11},
+    {0x000003fa, 10}, {0x000003fb, 10}, {0x000000f9,  8}, {0x000007fb, 11},
+    {0x000000fa,  8}, {0x00000016,  6}, {0x00000017,  6}, {0x00000018,  6},
+    {0x00000000,  5}, {0x00000001,  5}, {0x00000002,  5}, {0x00000019,  6},
+    {0x0000001a,  6}, {0x0000001b,  6}, {0x0000001c,  6}, {0x0000001d,  6},
+    {0x0000001e,  6}, {0x0000001f,  6}, {0x0000005c,  7}, {0x000000fb,  8},
+    {0x00007ffc, 15}, {0x00000020,  6}, {0x00000ffb, 12}, {0x000003fc, 10},
+    {0x00001ffa, 13}, {0x00000021,  6}, {0x0000005d,  7}, {0x0000005e,  7},
+    {0x0000005f,  7}, {0x00000060,  7}, {0x00000061,  7}, {0x00000062,  7},
+    {0x00000063,  7}, {0x00000064,  7}, {0x00000065,  7}, {0x00000066,  7},
+    {0x00000067,  7}, {0x00000068,  7}, {0x00000069,  7}, {0x0000006a,  7},
+    {0x0000006b,  7}, {0x0000006c,  7}, {0x0000006d,  7}, {0x0000006e,  7},
+    {0x0000006f,  7}, {0x00000070,  7}, {0x00000071,  7}, {0x00000072,  7},
+    {0x000000fc,  8}, {0x00000073,  7}, {0x000000fd,  8}, {0x00001ffb, 13},
+    {0x0007fff0, 19}, {0x00001ffc, 13}, {0x00003ffc, 14}, {0x00000022,  6},
+    {0x00007ffd, 15}, {0x00000003,  5}, {0x00000023,  6}, {0x00000004,  5},
+    {0x00000024,  6}, {0x00000005,  5}, {0x00000025,  6}, {0x00000026,  6},
+    {0x00000027,  6}, {0x00000006,  5}, {0x00000074,  7}, {0x00000075,  7},
+    {0x00000028,  6}, {0x00000029,  6}, {0x0000002a,  6}, {0x00000007,  5},
+    {0x0000002b,  6}, {0x00000076,  7}, {0x0000002c,  6}, {0x00000008,  5},
+    {0x00000009,  5}, {0x0000002d,  6}, {0x00000077,  7}, {0x00000078,  7},
+    {0x00000079,  7}, {0x0000007a,  7}, {0x0000007b,  7}, {0x00007ffe, 15},
+    {0x000007fc, 11}, {0x00003ffd, 14}, {0x00001ffd, 13}, {0x0ffffffc, 28},
+    {0x000fffe6, 20}, {0x003fffd2, 22}, {0x000fffe7, 20}, {0x000fffe8, 20},
+    {0x003fffd3, 22}, {0x003fffd4, 22}, {0x003fffd5, 22}, {0x007fffd9, 23},
+    {0x003fffd6, 22}, {0x007fffda, 23}, {0x007fffdb, 23}, {0x007fffdc, 23},
+    {0x007fffdd, 23}, {0x007fffde, 23}, {0x00ffffeb, 24}, {0x007fffdf, 23},
+    {0x00ffffec, 24}, {0x00ffffed, 24}, {0x003fffd7, 22}, {0x007fffe0, 23},
+    {0x00ffffee, 24}, {0x007fffe1, 23}, {0x007fffe2, 23}, {0x007fffe3, 23},
+    {0x007fffe4, 23}, {0x001fffdc, 21}, {0x003fffd8, 22}, {0x007fffe5, 23},
+    {0x003fffd9, 22}, {0x007fffe6, 23}, {0x007fffe7, 23}, {0x00ffffef, 24},
+    {0x003fffda, 22}, {0x001fffdd, 21}, {0x000fffe9, 20}, {0x003fffdb, 22},
+    {0x003fffdc, 22}, {0x007fffe8, 23}, {0x007fffe9, 23}, {0x001fffde, 21},
+    {0x007fffea, 23}, {0x003fffdd, 22}, {0x003fffde, 22}, {0x00fffff0, 24},
+    {0x001fffdf, 21}, {0x003fffdf, 22}, {0x007fffeb, 23}, {0x007fffec, 23},
+    {0x001fffe0, 21}, {0x001fffe1, 21}, {0x003fffe0, 22}, {0x001fffe2, 21},
+    {0x007fffed, 23}, {0x003fffe1, 22}, {0x007fffee, 23}, {0x007fffef, 23},
+    {0x000fffea, 20}, {0x003fffe2, 22}, {0x003fffe3, 22}, {0x003fffe4, 22},
+    {0x007ffff0, 23}, {0x003fffe5, 22}, {0x003fffe6, 22}, {0x007ffff1, 23},
+    {0x03ffffe0, 26}, {0x03ffffe1, 26}, {0x000fffeb, 20}, {0x0007fff1, 19},
+    {0x003fffe7, 22}, {0x007ffff2, 23}, {0x003fffe8, 22}, {0x01ffffec, 25},
+    {0x03ffffe2, 26}, {0x03ffffe3, 26}, {0x03ffffe4, 26}, {0x07ffffde, 27},
+    {0x07ffffdf, 27}, {0x03ffffe5, 26}, {0x00fffff1, 24}, {0x01ffffed, 25},
+    {0x0007fff2, 19}, {0x001fffe3, 21}, {0x03ffffe6, 26}, {0x07ffffe0, 27},
+    {0x07ffffe1, 27}, {0x03ffffe7, 26}, {0x07ffffe2, 27}, {0x00fffff2, 24},
+    {0x001fffe4, 21}, {0x001fffe5, 21}, {0x03ffffe8, 26}, {0x03ffffe9, 26},
+    {0x0ffffffd, 28}, {0x07ffffe3, 27}, {0x07ffffe4, 27}, {0x07ffffe5, 27},
+    {0x000fffec, 20}, {0x00fffff3, 24}, {0x000fffed, 20}, {0x001fffe6, 21},
+    {0x003fffe9, 22}, {0x001fffe7, 21}, {0x001fffe8, 21}, {0x007ffff3, 23},
+    {0x003fffea, 22}, {0x003fffeb, 22}, {0x01ffffee, 25}, {0x01ffffef, 25},
+    {0x00fffff4, 24}, {0x00fffff5, 24}, {0x03ffffea, 26}, {0x007ffff4, 23},
+    {0x03ffffeb, 26}, {0x07ffffe6, 27}, {0x03ffffec, 26}, {0x03ffffed, 26},
+    {0x07ffffe7, 27}, {0x07ffffe8, 27}, {0x07ffffe9, 27}, {0x07ffffea, 27},
+    {0x07ffffeb, 27}, {0x0ffffffe, 28}, {0x07ffffec, 27}, {0x07ffffed, 27},
+    {0x07ffffee, 27}, {0x07ffffef, 27}, {0x07fffff0, 27}, {0x03ffffee, 26}
+};
+
+
+/* same as above, but embeds lowercase transformation */
+static ngx_http_v2_huff_encode_code_t  ngx_http_v2_huff_encode_table_lc[256] =
+{
+    {0x00001ff8, 13}, {0x007fffd8, 23}, {0x0fffffe2, 28}, {0x0fffffe3, 28},
+    {0x0fffffe4, 28}, {0x0fffffe5, 28}, {0x0fffffe6, 28}, {0x0fffffe7, 28},
+    {0x0fffffe8, 28}, {0x00ffffea, 24}, {0x3ffffffc, 30}, {0x0fffffe9, 28},
+    {0x0fffffea, 28}, {0x3ffffffd, 30}, {0x0fffffeb, 28}, {0x0fffffec, 28},
+    {0x0fffffed, 28}, {0x0fffffee, 28}, {0x0fffffef, 28}, {0x0ffffff0, 28},
+    {0x0ffffff1, 28}, {0x0ffffff2, 28}, {0x3ffffffe, 30}, {0x0ffffff3, 28},
+    {0x0ffffff4, 28}, {0x0ffffff5, 28}, {0x0ffffff6, 28}, {0x0ffffff7, 28},
+    {0x0ffffff8, 28}, {0x0ffffff9, 28}, {0x0ffffffa, 28}, {0x0ffffffb, 28},
+    {0x00000014,  6}, {0x000003f8, 10}, {0x000003f9, 10}, {0x00000ffa, 12},
+    {0x00001ff9, 13}, {0x00000015,  6}, {0x000000f8,  8}, {0x000007fa, 11},
+    {0x000003fa, 10}, {0x000003fb, 10}, {0x000000f9,  8}, {0x000007fb, 11},
+    {0x000000fa,  8}, {0x00000016,  6}, {0x00000017,  6}, {0x00000018,  6},
+    {0x00000000,  5}, {0x00000001,  5}, {0x00000002,  5}, {0x00000019,  6},
+    {0x0000001a,  6}, {0x0000001b,  6}, {0x0000001c,  6}, {0x0000001d,  6},
+    {0x0000001e,  6}, {0x0000001f,  6}, {0x0000005c,  7}, {0x000000fb,  8},
+    {0x00007ffc, 15}, {0x00000020,  6}, {0x00000ffb, 12}, {0x000003fc, 10},
+    {0x00001ffa, 13}, {0x00000003,  5}, {0x00000023,  6}, {0x00000004,  5},
+    {0x00000024,  6}, {0x00000005,  5}, {0x00000025,  6}, {0x00000026,  6},
+    {0x00000027,  6}, {0x00000006,  5}, {0x00000074,  7}, {0x00000075,  7},
+    {0x00000028,  6}, {0x00000029,  6}, {0x0000002a,  6}, {0x00000007,  5},
+    {0x0000002b,  6}, {0x00000076,  7}, {0x0000002c,  6}, {0x00000008,  5},
+    {0x00000009,  5}, {0x0000002d,  6}, {0x00000077,  7}, {0x00000078,  7},
+    {0x00000079,  7}, {0x0000007a,  7}, {0x0000007b,  7}, {0x00001ffb, 13},
+    {0x0007fff0, 19}, {0x00001ffc, 13}, {0x00003ffc, 14}, {0x00000022,  6},
+    {0x00007ffd, 15}, {0x00000003,  5}, {0x00000023,  6}, {0x00000004,  5},
+    {0x00000024,  6}, {0x00000005,  5}, {0x00000025,  6}, {0x00000026,  6},
+    {0x00000027,  6}, {0x00000006,  5}, {0x00000074,  7}, {0x00000075,  7},
+    {0x00000028,  6}, {0x00000029,  6}, {0x0000002a,  6}, {0x00000007,  5},
+    {0x0000002b,  6}, {0x00000076,  7}, {0x0000002c,  6}, {0x00000008,  5},
+    {0x00000009,  5}, {0x0000002d,  6}, {0x00000077,  7}, {0x00000078,  7},
+    {0x00000079,  7}, {0x0000007a,  7}, {0x0000007b,  7}, {0x00007ffe, 15},
+    {0x000007fc, 11}, {0x00003ffd, 14}, {0x00001ffd, 13}, {0x0ffffffc, 28},
+    {0x000fffe6, 20}, {0x003fffd2, 22}, {0x000fffe7, 20}, {0x000fffe8, 20},
+    {0x003fffd3, 22}, {0x003fffd4, 22}, {0x003fffd5, 22}, {0x007fffd9, 23},
+    {0x003fffd6, 22}, {0x007fffda, 23}, {0x007fffdb, 23}, {0x007fffdc, 23},
+    {0x007fffdd, 23}, {0x007fffde, 23}, {0x00ffffeb, 24}, {0x007fffdf, 23},
+    {0x00ffffec, 24}, {0x00ffffed, 24}, {0x003fffd7, 22}, {0x007fffe0, 23},
+    {0x00ffffee, 24}, {0x007fffe1, 23}, {0x007fffe2, 23}, {0x007fffe3, 23},
+    {0x007fffe4, 23}, {0x001fffdc, 21}, {0x003fffd8, 22}, {0x007fffe5, 23},
+    {0x003fffd9, 22}, {0x007fffe6, 23}, {0x007fffe7, 23}, {0x00ffffef, 24},
+    {0x003fffda, 22}, {0x001fffdd, 21}, {0x000fffe9, 20}, {0x003fffdb, 22},
+    {0x003fffdc, 22}, {0x007fffe8, 23}, {0x007fffe9, 23}, {0x001fffde, 21},
+    {0x007fffea, 23}, {0x003fffdd, 22}, {0x003fffde, 22}, {0x00fffff0, 24},
+    {0x001fffdf, 21}, {0x003fffdf, 22}, {0x007fffeb, 23}, {0x007fffec, 23},
+    {0x001fffe0, 21}, {0x001fffe1, 21}, {0x003fffe0, 22}, {0x001fffe2, 21},
+    {0x007fffed, 23}, {0x003fffe1, 22}, {0x007fffee, 23}, {0x007fffef, 23},
+    {0x000fffea, 20}, {0x003fffe2, 22}, {0x003fffe3, 22}, {0x003fffe4, 22},
+    {0x007ffff0, 23}, {0x003fffe5, 22}, {0x003fffe6, 22}, {0x007ffff1, 23},
+    {0x03ffffe0, 26}, {0x03ffffe1, 26}, {0x000fffeb, 20}, {0x0007fff1, 19},
+    {0x003fffe7, 22}, {0x007ffff2, 23}, {0x003fffe8, 22}, {0x01ffffec, 25},
+    {0x03ffffe2, 26}, {0x03ffffe3, 26}, {0x03ffffe4, 26}, {0x07ffffde, 27},
+    {0x07ffffdf, 27}, {0x03ffffe5, 26}, {0x00fffff1, 24}, {0x01ffffed, 25},
+    {0x0007fff2, 19}, {0x001fffe3, 21}, {0x03ffffe6, 26}, {0x07ffffe0, 27},
+    {0x07ffffe1, 27}, {0x03ffffe7, 26}, {0x07ffffe2, 27}, {0x00fffff2, 24},
+    {0x001fffe4, 21}, {0x001fffe5, 21}, {0x03ffffe8, 26}, {0x03ffffe9, 26},
+    {0x0ffffffd, 28}, {0x07ffffe3, 27}, {0x07ffffe4, 27}, {0x07ffffe5, 27},
+    {0x000fffec, 20}, {0x00fffff3, 24}, {0x000fffed, 20}, {0x001fffe6, 21},
+    {0x003fffe9, 22}, {0x001fffe7, 21}, {0x001fffe8, 21}, {0x007ffff3, 23},
+    {0x003fffea, 22}, {0x003fffeb, 22}, {0x01ffffee, 25}, {0x01ffffef, 25},
+    {0x00fffff4, 24}, {0x00fffff5, 24}, {0x03ffffea, 26}, {0x007ffff4, 23},
+    {0x03ffffeb, 26}, {0x07ffffe6, 27}, {0x03ffffec, 26}, {0x03ffffed, 26},
+    {0x07ffffe7, 27}, {0x07ffffe8, 27}, {0x07ffffe9, 27}, {0x07ffffea, 27},
+    {0x07ffffeb, 27}, {0x0ffffffe, 28}, {0x07ffffec, 27}, {0x07ffffed, 27},
+    {0x07ffffee, 27}, {0x07ffffef, 27}, {0x07fffff0, 27}, {0x03ffffee, 26}
+};
+
+
+#if (NGX_PTR_SIZE == 8)
+
+#if (NGX_HAVE_LITTLE_ENDIAN)
+
+#if (NGX_HAVE_GCC_BSWAP64)
+#define ngx_http_v2_huff_encode_buf(dst, buf)                                 \
+    (*(uint64_t *) (dst) = __builtin_bswap64(buf))
+#else
+#define ngx_http_v2_huff_encode_buf(dst, buf)                                 \
+    ((dst)[0] = (u_char) ((buf) >> 56),                                       \
+     (dst)[1] = (u_char) ((buf) >> 48),                                       \
+     (dst)[2] = (u_char) ((buf) >> 40),                                       \
+     (dst)[3] = (u_char) ((buf) >> 32),                                       \
+     (dst)[4] = (u_char) ((buf) >> 24),                                       \
+     (dst)[5] = (u_char) ((buf) >> 16),                                       \
+     (dst)[6] = (u_char) ((buf) >> 8),                                        \
+     (dst)[7] = (u_char)  (buf))
+#endif
+
+#else /* !NGX_HAVE_LITTLE_ENDIAN */
+#define ngx_http_v2_huff_encode_buf(dst, buf)                                 \
+    (*(uint64_t *) (dst) = (buf))
+#endif
+
+#else /* NGX_PTR_SIZE == 4 */
+
+#define ngx_http_v2_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)
+{
+    u_char                          *end;
+    size_t                           hlen;
+    ngx_uint_t                       buf, pending, code;
+    ngx_http_v2_huff_encode_code_t  *table, *next;
+
+    table = lower ? ngx_http_v2_huff_encode_table_lc
+                  : ngx_http_v2_huff_encode_table;
+    hlen = 0;
+    buf = 0;
+    pending = 0;
+
+    end = src + len;
+
+    while (src != end) {
+        next = &table[*src++];
+
+        code = next->code;
+        pending += next->len;
+
+        /* accumulate bits */
+        if (pending < sizeof(buf) * 8) {
+            buf |= code << (sizeof(buf) * 8 - pending);
+            continue;
+        }
+
+        if (hlen + sizeof(buf) >= len) {
+            return 0;
+        }
+
+        pending -= sizeof(buf) * 8;
+
+        buf |= code >> pending;
+
+        ngx_http_v2_huff_encode_buf(&dst[hlen], buf);
+
+        hlen += sizeof(buf);
+
+        buf = pending ? code << (sizeof(buf) * 8 - pending) : 0;
+    }
+
+    if (pending == 0) {
+        return hlen;
+    }
+
+    buf |= (ngx_uint_t) -1 >> pending;
+
+    pending = ngx_align(pending, 8);
+
+    if (hlen + pending / 8 >= len) {
+        return 0;
+    }
+
+    buf >>= sizeof(buf) * 8 - pending;
+
+    do {
+        pending -= 8;
+        dst[hlen++] = (u_char) (buf >> pending);
+    } while (pending);
+
+    return hlen;
+}
diff --git a/nginx/src/http/v2/ngx_http_v2_module.c b/nginx/src/http/v2/ngx_http_v2_module.c
new file mode 100644 (file)
index 0000000..c54dc10
--- /dev/null
@@ -0,0 +1,610 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Valentin V. Bartenev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_http_v2_module.h>
+
+
+static ngx_int_t ngx_http_v2_add_variables(ngx_conf_t *cf);
+
+static ngx_int_t ngx_http_v2_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+
+static ngx_int_t ngx_http_v2_module_init(ngx_cycle_t *cycle);
+
+static void *ngx_http_v2_create_main_conf(ngx_conf_t *cf);
+static char *ngx_http_v2_init_main_conf(ngx_conf_t *cf, void *conf);
+static void *ngx_http_v2_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_http_v2_merge_srv_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static void *ngx_http_v2_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_v2_merge_loc_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+
+static char *ngx_http_v2_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+static char *ngx_http_v2_recv_buffer_size(ngx_conf_t *cf, void *post,
+    void *data);
+static char *ngx_http_v2_pool_size(ngx_conf_t *cf, void *post, void *data);
+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,
+    void *conf);
+
+
+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 =
+    { ngx_http_v2_pool_size };
+static ngx_conf_post_t  ngx_http_v2_preread_size_post =
+    { ngx_http_v2_preread_size };
+static ngx_conf_post_t  ngx_http_v2_streams_index_mask_post =
+    { ngx_http_v2_streams_index_mask };
+static ngx_conf_post_t  ngx_http_v2_chunk_size_post =
+    { ngx_http_v2_chunk_size };
+
+
+static ngx_command_t  ngx_http_v2_commands[] = {
+
+    { ngx_string("http2_recv_buffer_size"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      offsetof(ngx_http_v2_main_conf_t, recv_buffer_size),
+      &ngx_http_v2_recv_buffer_size_post },
+
+    { ngx_string("http2_pool_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, pool_size),
+      &ngx_http_v2_pool_size_post },
+
+    { ngx_string("http2_max_concurrent_streams"),
+      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, concurrent_streams),
+      NULL },
+
+    { ngx_string("http2_max_concurrent_pushes"),
+      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, concurrent_pushes),
+      NULL },
+
+    { 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_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_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_string("http2_body_preread_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, preread_size),
+      &ngx_http_v2_preread_size_post },
+
+    { ngx_string("http2_streams_index_size"),
+      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, streams_index_mask),
+      &ngx_http_v2_streams_index_mask_post },
+
+    { 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_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_string("http2_chunk_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_v2_loc_conf_t, chunk_size),
+      &ngx_http_v2_chunk_size_post },
+
+    { ngx_string("http2_push_preload"),
+      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_v2_loc_conf_t, push_preload),
+      NULL },
+
+    { ngx_string("http2_push"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_v2_push,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      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 },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_v2_module_ctx = {
+    ngx_http_v2_add_variables,             /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    ngx_http_v2_create_main_conf,          /* create main configuration */
+    ngx_http_v2_init_main_conf,            /* init main configuration */
+
+    ngx_http_v2_create_srv_conf,           /* create server configuration */
+    ngx_http_v2_merge_srv_conf,            /* merge server configuration */
+
+    ngx_http_v2_create_loc_conf,           /* create location configuration */
+    ngx_http_v2_merge_loc_conf             /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_v2_module = {
+    NGX_MODULE_V1,
+    &ngx_http_v2_module_ctx,               /* module context */
+    ngx_http_v2_commands,                  /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    ngx_http_v2_module_init,               /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_variable_t  ngx_http_v2_vars[] = {
+
+    { ngx_string("http2"), NULL,
+      ngx_http_v2_variable, 0, 0, 0 },
+
+      ngx_http_null_variable
+};
+
+
+static ngx_int_t
+ngx_http_v2_add_variables(ngx_conf_t *cf)
+{
+    ngx_http_variable_t  *var, *v;
+
+    for (v = ngx_http_v2_vars; v->name.len; v++) {
+        var = ngx_http_add_variable(cf, &v->name, v->flags);
+        if (var == NULL) {
+            return NGX_ERROR;
+        }
+
+        var->get_handler = v->get_handler;
+        var->data = v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+
+    if (r->stream) {
+#if (NGX_HTTP_SSL)
+
+        if (r->connection->ssl) {
+            v->len = sizeof("h2") - 1;
+            v->valid = 1;
+            v->no_cacheable = 0;
+            v->not_found = 0;
+            v->data = (u_char *) "h2";
+
+            return NGX_OK;
+        }
+
+#endif
+        v->len = sizeof("h2c") - 1;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = (u_char *) "h2c";
+
+        return NGX_OK;
+    }
+
+    *v = ngx_http_variable_null_value;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_module_init(ngx_cycle_t *cycle)
+{
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_v2_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_http_v2_main_conf_t  *h2mcf;
+
+    h2mcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v2_main_conf_t));
+    if (h2mcf == NULL) {
+        return NULL;
+    }
+
+    h2mcf->recv_buffer_size = NGX_CONF_UNSET_SIZE;
+
+    return h2mcf;
+}
+
+
+static char *
+ngx_http_v2_init_main_conf(ngx_conf_t *cf, void *conf)
+{
+    ngx_http_v2_main_conf_t *h2mcf = conf;
+
+    ngx_conf_init_size_value(h2mcf->recv_buffer_size, 256 * 1024);
+
+    return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_http_v2_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_http_v2_srv_conf_t  *h2scf;
+
+    h2scf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v2_srv_conf_t));
+    if (h2scf == NULL) {
+        return NULL;
+    }
+
+    h2scf->pool_size = NGX_CONF_UNSET_SIZE;
+
+    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;
+
+    return h2scf;
+}
+
+
+static char *
+ngx_http_v2_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_v2_srv_conf_t *prev = parent;
+    ngx_http_v2_srv_conf_t *conf = child;
+
+    ngx_conf_merge_size_value(conf->pool_size, prev->pool_size, 4096);
+
+    ngx_conf_merge_uint_value(conf->concurrent_streams,
+                              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);
+
+    return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_http_v2_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_v2_loc_conf_t  *h2lcf;
+
+    h2lcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v2_loc_conf_t));
+    if (h2lcf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     h2lcf->pushes = NULL;
+     */
+
+    h2lcf->chunk_size = NGX_CONF_UNSET_SIZE;
+
+    h2lcf->push_preload = NGX_CONF_UNSET;
+    h2lcf->push = NGX_CONF_UNSET;
+
+    return h2lcf;
+}
+
+
+static char *
+ngx_http_v2_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_v2_loc_conf_t *prev = parent;
+    ngx_http_v2_loc_conf_t *conf = child;
+
+    ngx_conf_merge_size_value(conf->chunk_size, prev->chunk_size, 8 * 1024);
+
+    ngx_conf_merge_value(conf->push, prev->push, 1);
+
+    if (conf->push && conf->pushes == NULL) {
+        conf->pushes = prev->pushes;
+    }
+
+    ngx_conf_merge_value(conf->push_preload, prev->push_preload, 0);
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_v2_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_v2_loc_conf_t *h2lcf = conf;
+
+    ngx_str_t                         *value;
+    ngx_http_complex_value_t          *cv;
+    ngx_http_compile_complex_value_t   ccv;
+
+    value = cf->args->elts;
+
+    if (ngx_strcmp(value[1].data, "off") == 0) {
+
+        if (h2lcf->pushes) {
+            return "\"off\" parameter cannot be used with URI";
+        }
+
+        if (h2lcf->push == 0) {
+            return "is duplicate";
+        }
+
+        h2lcf->push = 0;
+        return NGX_CONF_OK;
+    }
+
+    if (h2lcf->push == 0) {
+        return "URI cannot be used with \"off\" parameter";
+    }
+
+    h2lcf->push = 1;
+
+    if (h2lcf->pushes == NULL) {
+        h2lcf->pushes = ngx_array_create(cf->pool, 1,
+                                         sizeof(ngx_http_complex_value_t));
+        if (h2lcf->pushes == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    cv = ngx_array_push(h2lcf->pushes);
+    if (cv == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[1];
+    ccv.complex_value = cv;
+
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_v2_recv_buffer_size(ngx_conf_t *cf, void *post, void *data)
+{
+    size_t *sp = data;
+
+    if (*sp <= 2 * NGX_HTTP_V2_STATE_BUFFER_SIZE) {
+        return "value is too small";
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_v2_pool_size(ngx_conf_t *cf, void *post, void *data)
+{
+    size_t *sp = data;
+
+    if (*sp < NGX_MIN_POOL_SIZE) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "the pool size must be no less than %uz",
+                           NGX_MIN_POOL_SIZE);
+
+        return NGX_CONF_ERROR;
+    }
+
+    if (*sp % NGX_POOL_ALIGNMENT) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "the pool size must be a multiple of %uz",
+                           NGX_POOL_ALIGNMENT);
+
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_v2_preread_size(ngx_conf_t *cf, void *post, void *data)
+{
+    size_t *sp = data;
+
+    if (*sp > NGX_HTTP_V2_MAX_WINDOW) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "the maximum body preread buffer size is %uz",
+                           NGX_HTTP_V2_MAX_WINDOW);
+
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_v2_streams_index_mask(ngx_conf_t *cf, void *post, void *data)
+{
+    ngx_uint_t *np = data;
+
+    ngx_uint_t  mask;
+
+    mask = *np - 1;
+
+    if (*np == 0 || (*np & mask)) {
+        return "must be a power of two";
+    }
+
+    *np = mask;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_v2_chunk_size(ngx_conf_t *cf, void *post, void *data)
+{
+    size_t *sp = data;
+
+    if (*sp == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "the http2 chunk size cannot be zero");
+
+        return NGX_CONF_ERROR;
+    }
+
+    if (*sp > NGX_HTTP_V2_MAX_FRAME_SIZE) {
+        *sp = NGX_HTTP_V2_MAX_FRAME_SIZE;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_v2_spdy_deprecated(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    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);
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/http/v2/ngx_http_v2_module.h b/nginx/src/http/v2/ngx_http_v2_module.h
new file mode 100644 (file)
index 0000000..cdd2921
--- /dev/null
@@ -0,0 +1,50 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Valentin V. Bartenev
+ */
+
+
+#ifndef _NGX_HTTP_V2_MODULE_H_INCLUDED_
+#define _NGX_HTTP_V2_MODULE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    size_t                          recv_buffer_size;
+    u_char                         *recv_buffer;
+} ngx_http_v2_main_conf_t;
+
+
+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;
+} ngx_http_v2_srv_conf_t;
+
+
+typedef struct {
+    size_t                          chunk_size;
+
+    ngx_flag_t                      push_preload;
+
+    ngx_flag_t                      push;
+    ngx_array_t                    *pushes;
+} ngx_http_v2_loc_conf_t;
+
+
+extern ngx_module_t  ngx_http_v2_module;
+
+
+#endif /* _NGX_HTTP_V2_MODULE_H_INCLUDED_ */
diff --git a/nginx/src/http/v2/ngx_http_v2_table.c b/nginx/src/http/v2/ngx_http_v2_table.c
new file mode 100644 (file)
index 0000000..7d49803
--- /dev/null
@@ -0,0 +1,363 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Valentin V. Bartenev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_V2_TABLE_SIZE  4096
+
+
+static ngx_int_t ngx_http_v2_table_account(ngx_http_v2_connection_t *h2c,
+    size_t size);
+
+
+static ngx_http_v2_header_t  ngx_http_v2_static_table[] = {
+    { ngx_string(":authority"), ngx_string("") },
+    { ngx_string(":method"), ngx_string("GET") },
+    { ngx_string(":method"), ngx_string("POST") },
+    { ngx_string(":path"), ngx_string("/") },
+    { ngx_string(":path"), ngx_string("/index.html") },
+    { ngx_string(":scheme"), ngx_string("http") },
+    { ngx_string(":scheme"), ngx_string("https") },
+    { ngx_string(":status"), ngx_string("200") },
+    { ngx_string(":status"), ngx_string("204") },
+    { ngx_string(":status"), ngx_string("206") },
+    { ngx_string(":status"), ngx_string("304") },
+    { ngx_string(":status"), ngx_string("400") },
+    { ngx_string(":status"), ngx_string("404") },
+    { ngx_string(":status"), ngx_string("500") },
+    { ngx_string("accept-charset"), ngx_string("") },
+    { ngx_string("accept-encoding"), ngx_string("gzip, deflate") },
+    { ngx_string("accept-language"), ngx_string("") },
+    { ngx_string("accept-ranges"), ngx_string("") },
+    { ngx_string("accept"), ngx_string("") },
+    { ngx_string("access-control-allow-origin"), ngx_string("") },
+    { ngx_string("age"), ngx_string("") },
+    { ngx_string("allow"), ngx_string("") },
+    { ngx_string("authorization"), ngx_string("") },
+    { ngx_string("cache-control"), ngx_string("") },
+    { ngx_string("content-disposition"), ngx_string("") },
+    { ngx_string("content-encoding"), ngx_string("") },
+    { ngx_string("content-language"), ngx_string("") },
+    { ngx_string("content-length"), ngx_string("") },
+    { ngx_string("content-location"), ngx_string("") },
+    { ngx_string("content-range"), ngx_string("") },
+    { ngx_string("content-type"), ngx_string("") },
+    { ngx_string("cookie"), ngx_string("") },
+    { ngx_string("date"), ngx_string("") },
+    { ngx_string("etag"), ngx_string("") },
+    { ngx_string("expect"), ngx_string("") },
+    { ngx_string("expires"), ngx_string("") },
+    { ngx_string("from"), ngx_string("") },
+    { ngx_string("host"), ngx_string("") },
+    { ngx_string("if-match"), ngx_string("") },
+    { ngx_string("if-modified-since"), ngx_string("") },
+    { ngx_string("if-none-match"), ngx_string("") },
+    { ngx_string("if-range"), ngx_string("") },
+    { ngx_string("if-unmodified-since"), ngx_string("") },
+    { ngx_string("last-modified"), ngx_string("") },
+    { ngx_string("link"), ngx_string("") },
+    { ngx_string("location"), ngx_string("") },
+    { ngx_string("max-forwards"), ngx_string("") },
+    { ngx_string("proxy-authenticate"), ngx_string("") },
+    { ngx_string("proxy-authorization"), ngx_string("") },
+    { ngx_string("range"), ngx_string("") },
+    { ngx_string("referer"), ngx_string("") },
+    { ngx_string("refresh"), ngx_string("") },
+    { ngx_string("retry-after"), ngx_string("") },
+    { ngx_string("server"), ngx_string("") },
+    { ngx_string("set-cookie"), ngx_string("") },
+    { ngx_string("strict-transport-security"), ngx_string("") },
+    { ngx_string("transfer-encoding"), ngx_string("") },
+    { ngx_string("user-agent"), ngx_string("") },
+    { ngx_string("vary"), ngx_string("") },
+    { ngx_string("via"), ngx_string("") },
+    { ngx_string("www-authenticate"), ngx_string("") },
+};
+
+#define NGX_HTTP_V2_STATIC_TABLE_ENTRIES                                      \
+    (sizeof(ngx_http_v2_static_table)                                         \
+     / sizeof(ngx_http_v2_header_t))
+
+
+ngx_str_t *
+ngx_http_v2_get_static_name(ngx_uint_t index)
+{
+    return &ngx_http_v2_static_table[index - 1].name;
+}
+
+
+ngx_str_t *
+ngx_http_v2_get_static_value(ngx_uint_t index)
+{
+    return &ngx_http_v2_static_table[index - 1].value;
+}
+
+
+ngx_int_t
+ngx_http_v2_get_indexed_header(ngx_http_v2_connection_t *h2c, ngx_uint_t index,
+    ngx_uint_t name_only)
+{
+    u_char                *p;
+    size_t                 rest;
+    ngx_http_v2_header_t  *entry;
+
+    if (index == 0) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent invalid hpack table index 0");
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 get indexed %s: %ui",
+                   name_only ? "name" : "header", index);
+
+    index--;
+
+    if (index < NGX_HTTP_V2_STATIC_TABLE_ENTRIES) {
+        h2c->state.header = ngx_http_v2_static_table[index];
+        return NGX_OK;
+    }
+
+    index -= NGX_HTTP_V2_STATIC_TABLE_ENTRIES;
+
+    if (index < h2c->hpack.added - h2c->hpack.deleted) {
+        index = (h2c->hpack.added - index - 1) % h2c->hpack.allocated;
+        entry = h2c->hpack.entries[index];
+
+        p = ngx_pnalloc(h2c->state.pool, entry->name.len + 1);
+        if (p == NULL) {
+            return NGX_ERROR;
+        }
+
+        h2c->state.header.name.len = entry->name.len;
+        h2c->state.header.name.data = p;
+
+        rest = h2c->hpack.storage + NGX_HTTP_V2_TABLE_SIZE - entry->name.data;
+
+        if (entry->name.len > rest) {
+            p = ngx_cpymem(p, entry->name.data, rest);
+            p = ngx_cpymem(p, h2c->hpack.storage, entry->name.len - rest);
+
+        } else {
+            p = ngx_cpymem(p, entry->name.data, entry->name.len);
+        }
+
+        *p = '\0';
+
+        if (name_only) {
+            return NGX_OK;
+        }
+
+        p = ngx_pnalloc(h2c->state.pool, entry->value.len + 1);
+        if (p == NULL) {
+            return NGX_ERROR;
+        }
+
+        h2c->state.header.value.len = entry->value.len;
+        h2c->state.header.value.data = p;
+
+        rest = h2c->hpack.storage + NGX_HTTP_V2_TABLE_SIZE - entry->value.data;
+
+        if (entry->value.len > rest) {
+            p = ngx_cpymem(p, entry->value.data, rest);
+            p = ngx_cpymem(p, h2c->hpack.storage, entry->value.len - rest);
+
+        } else {
+            p = ngx_cpymem(p, entry->value.data, entry->value.len);
+        }
+
+        *p = '\0';
+
+        return NGX_OK;
+    }
+
+    ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                  "client sent out of bound hpack table index: %ui", index);
+
+    return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_http_v2_add_header(ngx_http_v2_connection_t *h2c,
+    ngx_http_v2_header_t *header)
+{
+    size_t                 avail;
+    ngx_uint_t             index;
+    ngx_http_v2_header_t  *entry, **entries;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 table add: \"%V: %V\"",
+                   &header->name, &header->value);
+
+    if (h2c->hpack.entries == NULL) {
+        h2c->hpack.allocated = 64;
+        h2c->hpack.size = NGX_HTTP_V2_TABLE_SIZE;
+        h2c->hpack.free = NGX_HTTP_V2_TABLE_SIZE;
+
+        h2c->hpack.entries = ngx_palloc(h2c->connection->pool,
+                                        sizeof(ngx_http_v2_header_t *)
+                                        * h2c->hpack.allocated);
+        if (h2c->hpack.entries == NULL) {
+            return NGX_ERROR;
+        }
+
+        h2c->hpack.storage = ngx_palloc(h2c->connection->pool,
+                                        h2c->hpack.free);
+        if (h2c->hpack.storage == NULL) {
+            return NGX_ERROR;
+        }
+
+        h2c->hpack.pos = h2c->hpack.storage;
+    }
+
+    if (ngx_http_v2_table_account(h2c, header->name.len + header->value.len)
+        != NGX_OK)
+    {
+        return NGX_OK;
+    }
+
+    if (h2c->hpack.reused == h2c->hpack.deleted) {
+        entry = ngx_palloc(h2c->connection->pool, sizeof(ngx_http_v2_header_t));
+        if (entry == NULL) {
+            return NGX_ERROR;
+        }
+
+    } else {
+        entry = h2c->hpack.entries[h2c->hpack.reused++ % h2c->hpack.allocated];
+    }
+
+    avail = h2c->hpack.storage + NGX_HTTP_V2_TABLE_SIZE - h2c->hpack.pos;
+
+    entry->name.len = header->name.len;
+    entry->name.data = h2c->hpack.pos;
+
+    if (avail >= header->name.len) {
+        h2c->hpack.pos = ngx_cpymem(h2c->hpack.pos, header->name.data,
+                                    header->name.len);
+    } else {
+        ngx_memcpy(h2c->hpack.pos, header->name.data, avail);
+        h2c->hpack.pos = ngx_cpymem(h2c->hpack.storage,
+                                    header->name.data + avail,
+                                    header->name.len - avail);
+        avail = NGX_HTTP_V2_TABLE_SIZE;
+    }
+
+    avail -= header->name.len;
+
+    entry->value.len = header->value.len;
+    entry->value.data = h2c->hpack.pos;
+
+    if (avail >= header->value.len) {
+        h2c->hpack.pos = ngx_cpymem(h2c->hpack.pos, header->value.data,
+                                    header->value.len);
+    } else {
+        ngx_memcpy(h2c->hpack.pos, header->value.data, avail);
+        h2c->hpack.pos = ngx_cpymem(h2c->hpack.storage,
+                                    header->value.data + avail,
+                                    header->value.len - avail);
+    }
+
+    if (h2c->hpack.allocated == h2c->hpack.added - h2c->hpack.deleted) {
+
+        entries = ngx_palloc(h2c->connection->pool,
+                             sizeof(ngx_http_v2_header_t *)
+                             * (h2c->hpack.allocated + 64));
+        if (entries == NULL) {
+            return NGX_ERROR;
+        }
+
+        index = h2c->hpack.deleted % h2c->hpack.allocated;
+
+        ngx_memcpy(entries, &h2c->hpack.entries[index],
+                   (h2c->hpack.allocated - index)
+                   * sizeof(ngx_http_v2_header_t *));
+
+        ngx_memcpy(&entries[h2c->hpack.allocated - index], h2c->hpack.entries,
+                   index * sizeof(ngx_http_v2_header_t *));
+
+        (void) ngx_pfree(h2c->connection->pool, h2c->hpack.entries);
+
+        h2c->hpack.entries = entries;
+
+        h2c->hpack.added = h2c->hpack.allocated;
+        h2c->hpack.deleted = 0;
+        h2c->hpack.reused = 0;
+        h2c->hpack.allocated += 64;
+    }
+
+    h2c->hpack.entries[h2c->hpack.added++ % h2c->hpack.allocated] = entry;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_table_account(ngx_http_v2_connection_t *h2c, size_t size)
+{
+    ngx_http_v2_header_t  *entry;
+
+    size += 32;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 table account: %uz free:%uz",
+                   size, h2c->hpack.free);
+
+    if (size <= h2c->hpack.free) {
+        h2c->hpack.free -= size;
+        return NGX_OK;
+    }
+
+    if (size > h2c->hpack.size) {
+        h2c->hpack.deleted = h2c->hpack.added;
+        h2c->hpack.free = h2c->hpack.size;
+        return NGX_DECLINED;
+    }
+
+    do {
+        entry = h2c->hpack.entries[h2c->hpack.deleted++ % h2c->hpack.allocated];
+        h2c->hpack.free += 32 + entry->name.len + entry->value.len;
+    } while (size > h2c->hpack.free);
+
+    h2c->hpack.free -= size;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v2_table_size(ngx_http_v2_connection_t *h2c, size_t size)
+{
+    ssize_t                needed;
+    ngx_http_v2_header_t  *entry;
+
+    if (size > NGX_HTTP_V2_TABLE_SIZE) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent invalid table size update: %uz", size);
+
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 new hpack table size: %uz was:%uz",
+                   size, h2c->hpack.size);
+
+    needed = h2c->hpack.size - size;
+
+    while (needed > (ssize_t) h2c->hpack.free) {
+        entry = h2c->hpack.entries[h2c->hpack.deleted++ % h2c->hpack.allocated];
+        h2c->hpack.free += 32 + entry->name.len + entry->value.len;
+    }
+
+    h2c->hpack.size = size;
+    h2c->hpack.free -= needed;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/mail/ngx_mail.c b/nginx/src/mail/ngx_mail.c
new file mode 100644 (file)
index 0000000..5fd5fa0
--- /dev/null
@@ -0,0 +1,512 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+
+
+static char *ngx_mail_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static ngx_int_t ngx_mail_add_ports(ngx_conf_t *cf, ngx_array_t *ports,
+    ngx_mail_listen_t *listen);
+static char *ngx_mail_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports);
+static ngx_int_t ngx_mail_add_addrs(ngx_conf_t *cf, ngx_mail_port_t *mport,
+    ngx_mail_conf_addr_t *addr);
+#if (NGX_HAVE_INET6)
+static ngx_int_t ngx_mail_add_addrs6(ngx_conf_t *cf, ngx_mail_port_t *mport,
+    ngx_mail_conf_addr_t *addr);
+#endif
+static ngx_int_t ngx_mail_cmp_conf_addrs(const void *one, const void *two);
+
+
+ngx_uint_t  ngx_mail_max_module;
+
+
+static ngx_command_t  ngx_mail_commands[] = {
+
+    { ngx_string("mail"),
+      NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+      ngx_mail_block,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_core_module_t  ngx_mail_module_ctx = {
+    ngx_string("mail"),
+    NULL,
+    NULL
+};
+
+
+ngx_module_t  ngx_mail_module = {
+    NGX_MODULE_V1,
+    &ngx_mail_module_ctx,                  /* module context */
+    ngx_mail_commands,                     /* module directives */
+    NGX_CORE_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 char *
+ngx_mail_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char                        *rv;
+    ngx_uint_t                   i, m, mi, s;
+    ngx_conf_t                   pcf;
+    ngx_array_t                  ports;
+    ngx_mail_listen_t           *listen;
+    ngx_mail_module_t           *module;
+    ngx_mail_conf_ctx_t         *ctx;
+    ngx_mail_core_srv_conf_t   **cscfp;
+    ngx_mail_core_main_conf_t   *cmcf;
+
+    if (*(ngx_mail_conf_ctx_t **) conf) {
+        return "is duplicate";
+    }
+
+    /* the main mail context */
+
+    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_mail_conf_ctx_t));
+    if (ctx == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    *(ngx_mail_conf_ctx_t **) conf = ctx;
+
+    /* count the number of the mail modules and set up their indices */
+
+    ngx_mail_max_module = ngx_count_modules(cf->cycle, NGX_MAIL_MODULE);
+
+
+    /* the mail main_conf context, it is the same in the all mail contexts */
+
+    ctx->main_conf = ngx_pcalloc(cf->pool,
+                                 sizeof(void *) * ngx_mail_max_module);
+    if (ctx->main_conf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+
+    /*
+     * the mail null srv_conf context, it is used to merge
+     * the server{}s' srv_conf's
+     */
+
+    ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_mail_max_module);
+    if (ctx->srv_conf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+
+    /*
+     * create the main_conf's and the null srv_conf's of the all mail modules
+     */
+
+    for (m = 0; cf->cycle->modules[m]; m++) {
+        if (cf->cycle->modules[m]->type != NGX_MAIL_MODULE) {
+            continue;
+        }
+
+        module = cf->cycle->modules[m]->ctx;
+        mi = cf->cycle->modules[m]->ctx_index;
+
+        if (module->create_main_conf) {
+            ctx->main_conf[mi] = module->create_main_conf(cf);
+            if (ctx->main_conf[mi] == NULL) {
+                return NGX_CONF_ERROR;
+            }
+        }
+
+        if (module->create_srv_conf) {
+            ctx->srv_conf[mi] = module->create_srv_conf(cf);
+            if (ctx->srv_conf[mi] == NULL) {
+                return NGX_CONF_ERROR;
+            }
+        }
+    }
+
+
+    /* parse inside the mail{} block */
+
+    pcf = *cf;
+    cf->ctx = ctx;
+
+    cf->module_type = NGX_MAIL_MODULE;
+    cf->cmd_type = NGX_MAIL_MAIN_CONF;
+    rv = ngx_conf_parse(cf, NULL);
+
+    if (rv != NGX_CONF_OK) {
+        *cf = pcf;
+        return rv;
+    }
+
+
+    /* init mail{} main_conf's, merge the server{}s' srv_conf's */
+
+    cmcf = ctx->main_conf[ngx_mail_core_module.ctx_index];
+    cscfp = cmcf->servers.elts;
+
+    for (m = 0; cf->cycle->modules[m]; m++) {
+        if (cf->cycle->modules[m]->type != NGX_MAIL_MODULE) {
+            continue;
+        }
+
+        module = cf->cycle->modules[m]->ctx;
+        mi = cf->cycle->modules[m]->ctx_index;
+
+        /* init mail{} main_conf's */
+
+        cf->ctx = ctx;
+
+        if (module->init_main_conf) {
+            rv = module->init_main_conf(cf, ctx->main_conf[mi]);
+            if (rv != NGX_CONF_OK) {
+                *cf = pcf;
+                return rv;
+            }
+        }
+
+        for (s = 0; s < cmcf->servers.nelts; s++) {
+
+            /* merge the server{}s' srv_conf's */
+
+            cf->ctx = cscfp[s]->ctx;
+
+            if (module->merge_srv_conf) {
+                rv = module->merge_srv_conf(cf,
+                                            ctx->srv_conf[mi],
+                                            cscfp[s]->ctx->srv_conf[mi]);
+                if (rv != NGX_CONF_OK) {
+                    *cf = pcf;
+                    return rv;
+                }
+            }
+        }
+    }
+
+    *cf = pcf;
+
+
+    if (ngx_array_init(&ports, cf->temp_pool, 4, sizeof(ngx_mail_conf_port_t))
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    listen = cmcf->listen.elts;
+
+    for (i = 0; i < cmcf->listen.nelts; i++) {
+        if (ngx_mail_add_ports(cf, &ports, &listen[i]) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    return ngx_mail_optimize_servers(cf, &ports);
+}
+
+
+static ngx_int_t
+ngx_mail_add_ports(ngx_conf_t *cf, ngx_array_t *ports,
+    ngx_mail_listen_t *listen)
+{
+    in_port_t              p;
+    ngx_uint_t             i;
+    struct sockaddr       *sa;
+    ngx_mail_conf_port_t  *port;
+    ngx_mail_conf_addr_t  *addr;
+
+    sa = &listen->sockaddr.sockaddr;
+    p = ngx_inet_get_port(sa);
+
+    port = ports->elts;
+    for (i = 0; i < ports->nelts; i++) {
+        if (p == port[i].port && sa->sa_family == port[i].family) {
+
+            /* a port is already in the port list */
+
+            port = &port[i];
+            goto found;
+        }
+    }
+
+    /* add a port to the port list */
+
+    port = ngx_array_push(ports);
+    if (port == NULL) {
+        return NGX_ERROR;
+    }
+
+    port->family = sa->sa_family;
+    port->port = p;
+
+    if (ngx_array_init(&port->addrs, cf->temp_pool, 2,
+                       sizeof(ngx_mail_conf_addr_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+found:
+
+    addr = ngx_array_push(&port->addrs);
+    if (addr == NULL) {
+        return NGX_ERROR;
+    }
+
+    addr->opt = *listen;
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_mail_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports)
+{
+    ngx_uint_t                 i, p, last, bind_wildcard;
+    ngx_listening_t           *ls;
+    ngx_mail_port_t           *mport;
+    ngx_mail_conf_port_t      *port;
+    ngx_mail_conf_addr_t      *addr;
+    ngx_mail_core_srv_conf_t  *cscf;
+
+    port = ports->elts;
+    for (p = 0; p < ports->nelts; p++) {
+
+        ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,
+                 sizeof(ngx_mail_conf_addr_t), ngx_mail_cmp_conf_addrs);
+
+        addr = port[p].addrs.elts;
+        last = port[p].addrs.nelts;
+
+        /*
+         * if there is the binding to the "*:port" then we need to bind()
+         * to the "*:port" only and ignore the other bindings
+         */
+
+        if (addr[last - 1].opt.wildcard) {
+            addr[last - 1].opt.bind = 1;
+            bind_wildcard = 1;
+
+        } else {
+            bind_wildcard = 0;
+        }
+
+        i = 0;
+
+        while (i < last) {
+
+            if (bind_wildcard && !addr[i].opt.bind) {
+                i++;
+                continue;
+            }
+
+            ls = ngx_create_listening(cf, &addr[i].opt.sockaddr.sockaddr,
+                                      addr[i].opt.socklen);
+            if (ls == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            ls->addr_ntop = 1;
+            ls->handler = ngx_mail_init_connection;
+            ls->pool_size = 256;
+
+            cscf = addr->opt.ctx->srv_conf[ngx_mail_core_module.ctx_index];
+
+            ls->logp = cscf->error_log;
+            ls->log.data = &ls->addr_text;
+            ls->log.handler = ngx_accept_log_error;
+
+            ls->backlog = addr[i].opt.backlog;
+            ls->rcvbuf = addr[i].opt.rcvbuf;
+            ls->sndbuf = addr[i].opt.sndbuf;
+
+            ls->keepalive = addr[i].opt.so_keepalive;
+#if (NGX_HAVE_KEEPALIVE_TUNABLE)
+            ls->keepidle = addr[i].opt.tcp_keepidle;
+            ls->keepintvl = addr[i].opt.tcp_keepintvl;
+            ls->keepcnt = addr[i].opt.tcp_keepcnt;
+#endif
+
+#if (NGX_HAVE_INET6)
+            ls->ipv6only = addr[i].opt.ipv6only;
+#endif
+
+            mport = ngx_palloc(cf->pool, sizeof(ngx_mail_port_t));
+            if (mport == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            ls->servers = mport;
+
+            mport->naddrs = i + 1;
+
+            switch (ls->sockaddr->sa_family) {
+#if (NGX_HAVE_INET6)
+            case AF_INET6:
+                if (ngx_mail_add_addrs6(cf, mport, addr) != NGX_OK) {
+                    return NGX_CONF_ERROR;
+                }
+                break;
+#endif
+            default: /* AF_INET */
+                if (ngx_mail_add_addrs(cf, mport, addr) != NGX_OK) {
+                    return NGX_CONF_ERROR;
+                }
+                break;
+            }
+
+            addr++;
+            last--;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_add_addrs(ngx_conf_t *cf, ngx_mail_port_t *mport,
+    ngx_mail_conf_addr_t *addr)
+{
+    u_char              *p;
+    size_t               len;
+    ngx_uint_t           i;
+    ngx_mail_in_addr_t  *addrs;
+    struct sockaddr_in  *sin;
+    u_char               buf[NGX_SOCKADDR_STRLEN];
+
+    mport->addrs = ngx_pcalloc(cf->pool,
+                               mport->naddrs * sizeof(ngx_mail_in_addr_t));
+    if (mport->addrs == NULL) {
+        return NGX_ERROR;
+    }
+
+    addrs = mport->addrs;
+
+    for (i = 0; i < mport->naddrs; i++) {
+
+        sin = &addr[i].opt.sockaddr.sockaddr_in;
+        addrs[i].addr = sin->sin_addr.s_addr;
+
+        addrs[i].conf.ctx = addr[i].opt.ctx;
+#if (NGX_MAIL_SSL)
+        addrs[i].conf.ssl = addr[i].opt.ssl;
+#endif
+
+        len = ngx_sock_ntop(&addr[i].opt.sockaddr.sockaddr, addr[i].opt.socklen,
+                            buf, NGX_SOCKADDR_STRLEN, 1);
+
+        p = ngx_pnalloc(cf->pool, len);
+        if (p == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_memcpy(p, buf, len);
+
+        addrs[i].conf.addr_text.len = len;
+        addrs[i].conf.addr_text.data = p;
+    }
+
+    return NGX_OK;
+}
+
+
+#if (NGX_HAVE_INET6)
+
+static ngx_int_t
+ngx_mail_add_addrs6(ngx_conf_t *cf, ngx_mail_port_t *mport,
+    ngx_mail_conf_addr_t *addr)
+{
+    u_char               *p;
+    size_t                len;
+    ngx_uint_t            i;
+    ngx_mail_in6_addr_t  *addrs6;
+    struct sockaddr_in6  *sin6;
+    u_char                buf[NGX_SOCKADDR_STRLEN];
+
+    mport->addrs = ngx_pcalloc(cf->pool,
+                               mport->naddrs * sizeof(ngx_mail_in6_addr_t));
+    if (mport->addrs == NULL) {
+        return NGX_ERROR;
+    }
+
+    addrs6 = mport->addrs;
+
+    for (i = 0; i < mport->naddrs; i++) {
+
+        sin6 = &addr[i].opt.sockaddr.sockaddr_in6;
+        addrs6[i].addr6 = sin6->sin6_addr;
+
+        addrs6[i].conf.ctx = addr[i].opt.ctx;
+#if (NGX_MAIL_SSL)
+        addrs6[i].conf.ssl = addr[i].opt.ssl;
+#endif
+
+        len = ngx_sock_ntop(&addr[i].opt.sockaddr.sockaddr, addr[i].opt.socklen,
+                            buf, NGX_SOCKADDR_STRLEN, 1);
+
+        p = ngx_pnalloc(cf->pool, len);
+        if (p == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_memcpy(p, buf, len);
+
+        addrs6[i].conf.addr_text.len = len;
+        addrs6[i].conf.addr_text.data = p;
+    }
+
+    return NGX_OK;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_mail_cmp_conf_addrs(const void *one, const void *two)
+{
+    ngx_mail_conf_addr_t  *first, *second;
+
+    first = (ngx_mail_conf_addr_t *) one;
+    second = (ngx_mail_conf_addr_t *) two;
+
+    if (first->opt.wildcard) {
+        /* a wildcard must be the last resort, shift it to the end */
+        return 1;
+    }
+
+    if (second->opt.wildcard) {
+        /* a wildcard must be the last resort, shift it to the end */
+        return -1;
+    }
+
+    if (first->opt.bind && !second->opt.bind) {
+        /* shift explicit bind()ed addresses to the start */
+        return -1;
+    }
+
+    if (!first->opt.bind && second->opt.bind) {
+        /* shift explicit bind()ed addresses to the start */
+        return 1;
+    }
+
+    /* do not sort by default */
+
+    return 0;
+}
diff --git a/nginx/src/mail/ngx_mail.h b/nginx/src/mail/ngx_mail.h
new file mode 100644 (file)
index 0000000..6ecfefc
--- /dev/null
@@ -0,0 +1,412 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_MAIL_H_INCLUDED_
+#define _NGX_MAIL_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_connect.h>
+
+#if (NGX_MAIL_SSL)
+#include <ngx_mail_ssl_module.h>
+#endif
+
+
+
+typedef struct {
+    void                  **main_conf;
+    void                  **srv_conf;
+} ngx_mail_conf_ctx_t;
+
+
+typedef struct {
+    ngx_sockaddr_t          sockaddr;
+    socklen_t               socklen;
+
+    /* server ctx */
+    ngx_mail_conf_ctx_t    *ctx;
+
+    unsigned                bind:1;
+    unsigned                wildcard:1;
+    unsigned                ssl:1;
+#if (NGX_HAVE_INET6)
+    unsigned                ipv6only:1;
+#endif
+    unsigned                so_keepalive:2;
+#if (NGX_HAVE_KEEPALIVE_TUNABLE)
+    int                     tcp_keepidle;
+    int                     tcp_keepintvl;
+    int                     tcp_keepcnt;
+#endif
+    int                     backlog;
+    int                     rcvbuf;
+    int                     sndbuf;
+} ngx_mail_listen_t;
+
+
+typedef struct {
+    ngx_mail_conf_ctx_t    *ctx;
+    ngx_str_t               addr_text;
+    ngx_uint_t              ssl;    /* unsigned   ssl:1; */
+} ngx_mail_addr_conf_t;
+
+typedef struct {
+    in_addr_t               addr;
+    ngx_mail_addr_conf_t    conf;
+} ngx_mail_in_addr_t;
+
+
+#if (NGX_HAVE_INET6)
+
+typedef struct {
+    struct in6_addr         addr6;
+    ngx_mail_addr_conf_t    conf;
+} ngx_mail_in6_addr_t;
+
+#endif
+
+
+typedef struct {
+    /* ngx_mail_in_addr_t or ngx_mail_in6_addr_t */
+    void                   *addrs;
+    ngx_uint_t              naddrs;
+} ngx_mail_port_t;
+
+
+typedef struct {
+    int                     family;
+    in_port_t               port;
+    ngx_array_t             addrs;       /* array of ngx_mail_conf_addr_t */
+} ngx_mail_conf_port_t;
+
+
+typedef struct {
+    ngx_mail_listen_t       opt;
+} ngx_mail_conf_addr_t;
+
+
+typedef struct {
+    ngx_array_t             servers;     /* ngx_mail_core_srv_conf_t */
+    ngx_array_t             listen;      /* ngx_mail_listen_t */
+} ngx_mail_core_main_conf_t;
+
+
+#define NGX_MAIL_POP3_PROTOCOL  0
+#define NGX_MAIL_IMAP_PROTOCOL  1
+#define NGX_MAIL_SMTP_PROTOCOL  2
+
+
+typedef struct ngx_mail_protocol_s  ngx_mail_protocol_t;
+
+
+typedef struct {
+    ngx_mail_protocol_t    *protocol;
+
+    ngx_msec_t              timeout;
+    ngx_msec_t              resolver_timeout;
+
+    ngx_str_t               server_name;
+
+    u_char                 *file_name;
+    ngx_uint_t              line;
+
+    ngx_resolver_t         *resolver;
+    ngx_log_t              *error_log;
+
+    /* server ctx */
+    ngx_mail_conf_ctx_t    *ctx;
+
+    ngx_uint_t              listen;  /* unsigned  listen:1; */
+} ngx_mail_core_srv_conf_t;
+
+
+typedef enum {
+    ngx_pop3_start = 0,
+    ngx_pop3_user,
+    ngx_pop3_passwd,
+    ngx_pop3_auth_login_username,
+    ngx_pop3_auth_login_password,
+    ngx_pop3_auth_plain,
+    ngx_pop3_auth_cram_md5,
+    ngx_pop3_auth_external
+} ngx_pop3_state_e;
+
+
+typedef enum {
+    ngx_imap_start = 0,
+    ngx_imap_auth_login_username,
+    ngx_imap_auth_login_password,
+    ngx_imap_auth_plain,
+    ngx_imap_auth_cram_md5,
+    ngx_imap_auth_external,
+    ngx_imap_login,
+    ngx_imap_user,
+    ngx_imap_passwd
+} ngx_imap_state_e;
+
+
+typedef enum {
+    ngx_smtp_start = 0,
+    ngx_smtp_auth_login_username,
+    ngx_smtp_auth_login_password,
+    ngx_smtp_auth_plain,
+    ngx_smtp_auth_cram_md5,
+    ngx_smtp_auth_external,
+    ngx_smtp_helo,
+    ngx_smtp_helo_xclient,
+    ngx_smtp_helo_from,
+    ngx_smtp_xclient,
+    ngx_smtp_xclient_from,
+    ngx_smtp_xclient_helo,
+    ngx_smtp_from,
+    ngx_smtp_to
+} ngx_smtp_state_e;
+
+
+typedef struct {
+    ngx_peer_connection_t   upstream;
+    ngx_buf_t              *buffer;
+} ngx_mail_proxy_ctx_t;
+
+
+typedef struct {
+    uint32_t                signature;         /* "MAIL" */
+
+    ngx_connection_t       *connection;
+
+    ngx_str_t               out;
+    ngx_buf_t              *buffer;
+
+    void                  **ctx;
+    void                  **main_conf;
+    void                  **srv_conf;
+
+    ngx_resolver_ctx_t     *resolver_ctx;
+
+    ngx_mail_proxy_ctx_t   *proxy;
+
+    ngx_uint_t              mail_state;
+
+    unsigned                protocol:3;
+    unsigned                blocked:1;
+    unsigned                quit:1;
+    unsigned                quoted:1;
+    unsigned                backslash:1;
+    unsigned                no_sync_literal:1;
+    unsigned                starttls:1;
+    unsigned                esmtp:1;
+    unsigned                auth_method:3;
+    unsigned                auth_wait:1;
+
+    ngx_str_t               login;
+    ngx_str_t               passwd;
+
+    ngx_str_t               salt;
+    ngx_str_t               tag;
+    ngx_str_t               tagged_line;
+    ngx_str_t               text;
+
+    ngx_str_t              *addr_text;
+    ngx_str_t               host;
+    ngx_str_t               smtp_helo;
+    ngx_str_t               smtp_from;
+    ngx_str_t               smtp_to;
+
+    ngx_str_t               cmd;
+
+    ngx_uint_t              command;
+    ngx_array_t             args;
+
+    ngx_uint_t              login_attempt;
+
+    /* used to parse POP3/IMAP/SMTP command */
+
+    ngx_uint_t              state;
+    u_char                 *cmd_start;
+    u_char                 *arg_start;
+    u_char                 *arg_end;
+    ngx_uint_t              literal_len;
+} ngx_mail_session_t;
+
+
+typedef struct {
+    ngx_str_t              *client;
+    ngx_mail_session_t     *session;
+} ngx_mail_log_ctx_t;
+
+
+#define NGX_POP3_USER          1
+#define NGX_POP3_PASS          2
+#define NGX_POP3_CAPA          3
+#define NGX_POP3_QUIT          4
+#define NGX_POP3_NOOP          5
+#define NGX_POP3_STLS          6
+#define NGX_POP3_APOP          7
+#define NGX_POP3_AUTH          8
+#define NGX_POP3_STAT          9
+#define NGX_POP3_LIST          10
+#define NGX_POP3_RETR          11
+#define NGX_POP3_DELE          12
+#define NGX_POP3_RSET          13
+#define NGX_POP3_TOP           14
+#define NGX_POP3_UIDL          15
+
+
+#define NGX_IMAP_LOGIN         1
+#define NGX_IMAP_LOGOUT        2
+#define NGX_IMAP_CAPABILITY    3
+#define NGX_IMAP_NOOP          4
+#define NGX_IMAP_STARTTLS      5
+
+#define NGX_IMAP_NEXT          6
+
+#define NGX_IMAP_AUTHENTICATE  7
+
+
+#define NGX_SMTP_HELO          1
+#define NGX_SMTP_EHLO          2
+#define NGX_SMTP_AUTH          3
+#define NGX_SMTP_QUIT          4
+#define NGX_SMTP_NOOP          5
+#define NGX_SMTP_MAIL          6
+#define NGX_SMTP_RSET          7
+#define NGX_SMTP_RCPT          8
+#define NGX_SMTP_DATA          9
+#define NGX_SMTP_VRFY          10
+#define NGX_SMTP_EXPN          11
+#define NGX_SMTP_HELP          12
+#define NGX_SMTP_STARTTLS      13
+
+
+#define NGX_MAIL_AUTH_PLAIN             0
+#define NGX_MAIL_AUTH_LOGIN             1
+#define NGX_MAIL_AUTH_LOGIN_USERNAME    2
+#define NGX_MAIL_AUTH_APOP              3
+#define NGX_MAIL_AUTH_CRAM_MD5          4
+#define NGX_MAIL_AUTH_EXTERNAL          5
+#define NGX_MAIL_AUTH_NONE              6
+
+
+#define NGX_MAIL_AUTH_PLAIN_ENABLED     0x0002
+#define NGX_MAIL_AUTH_LOGIN_ENABLED     0x0004
+#define NGX_MAIL_AUTH_APOP_ENABLED      0x0008
+#define NGX_MAIL_AUTH_CRAM_MD5_ENABLED  0x0010
+#define NGX_MAIL_AUTH_EXTERNAL_ENABLED  0x0020
+#define NGX_MAIL_AUTH_NONE_ENABLED      0x0040
+
+
+#define NGX_MAIL_PARSE_INVALID_COMMAND  20
+
+
+typedef void (*ngx_mail_init_session_pt)(ngx_mail_session_t *s,
+    ngx_connection_t *c);
+typedef void (*ngx_mail_init_protocol_pt)(ngx_event_t *rev);
+typedef void (*ngx_mail_auth_state_pt)(ngx_event_t *rev);
+typedef ngx_int_t (*ngx_mail_parse_command_pt)(ngx_mail_session_t *s);
+
+
+struct ngx_mail_protocol_s {
+    ngx_str_t                   name;
+    in_port_t                   port[4];
+    ngx_uint_t                  type;
+
+    ngx_mail_init_session_pt    init_session;
+    ngx_mail_init_protocol_pt   init_protocol;
+    ngx_mail_parse_command_pt   parse_command;
+    ngx_mail_auth_state_pt      auth_state;
+
+    ngx_str_t                   internal_server_error;
+    ngx_str_t                   cert_error;
+    ngx_str_t                   no_cert;
+};
+
+
+typedef struct {
+    ngx_mail_protocol_t        *protocol;
+
+    void                       *(*create_main_conf)(ngx_conf_t *cf);
+    char                       *(*init_main_conf)(ngx_conf_t *cf, void *conf);
+
+    void                       *(*create_srv_conf)(ngx_conf_t *cf);
+    char                       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev,
+                                                  void *conf);
+} ngx_mail_module_t;
+
+
+#define NGX_MAIL_MODULE         0x4C49414D     /* "MAIL" */
+
+#define NGX_MAIL_MAIN_CONF      0x02000000
+#define NGX_MAIL_SRV_CONF       0x04000000
+
+
+#define NGX_MAIL_MAIN_CONF_OFFSET  offsetof(ngx_mail_conf_ctx_t, main_conf)
+#define NGX_MAIL_SRV_CONF_OFFSET   offsetof(ngx_mail_conf_ctx_t, srv_conf)
+
+
+#define ngx_mail_get_module_ctx(s, module)     (s)->ctx[module.ctx_index]
+#define ngx_mail_set_ctx(s, c, module)         s->ctx[module.ctx_index] = c;
+#define ngx_mail_delete_ctx(s, module)         s->ctx[module.ctx_index] = NULL;
+
+
+#define ngx_mail_get_module_main_conf(s, module)                             \
+    (s)->main_conf[module.ctx_index]
+#define ngx_mail_get_module_srv_conf(s, module)  (s)->srv_conf[module.ctx_index]
+
+#define ngx_mail_conf_get_module_main_conf(cf, module)                       \
+    ((ngx_mail_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index]
+#define ngx_mail_conf_get_module_srv_conf(cf, module)                        \
+    ((ngx_mail_conf_ctx_t *) cf->ctx)->srv_conf[module.ctx_index]
+
+
+#if (NGX_MAIL_SSL)
+void ngx_mail_starttls_handler(ngx_event_t *rev);
+ngx_int_t ngx_mail_starttls_only(ngx_mail_session_t *s, ngx_connection_t *c);
+#endif
+
+
+void ngx_mail_init_connection(ngx_connection_t *c);
+
+ngx_int_t ngx_mail_salt(ngx_mail_session_t *s, ngx_connection_t *c,
+    ngx_mail_core_srv_conf_t *cscf);
+ngx_int_t ngx_mail_auth_plain(ngx_mail_session_t *s, ngx_connection_t *c,
+    ngx_uint_t n);
+ngx_int_t ngx_mail_auth_login_username(ngx_mail_session_t *s,
+    ngx_connection_t *c, ngx_uint_t n);
+ngx_int_t ngx_mail_auth_login_password(ngx_mail_session_t *s,
+    ngx_connection_t *c);
+ngx_int_t ngx_mail_auth_cram_md5_salt(ngx_mail_session_t *s,
+    ngx_connection_t *c, char *prefix, size_t len);
+ngx_int_t ngx_mail_auth_cram_md5(ngx_mail_session_t *s, ngx_connection_t *c);
+ngx_int_t ngx_mail_auth_external(ngx_mail_session_t *s, ngx_connection_t *c,
+    ngx_uint_t n);
+ngx_int_t ngx_mail_auth_parse(ngx_mail_session_t *s, ngx_connection_t *c);
+
+void ngx_mail_send(ngx_event_t *wev);
+ngx_int_t ngx_mail_read_command(ngx_mail_session_t *s, ngx_connection_t *c);
+void ngx_mail_auth(ngx_mail_session_t *s, ngx_connection_t *c);
+void ngx_mail_close_connection(ngx_connection_t *c);
+void ngx_mail_session_internal_server_error(ngx_mail_session_t *s);
+u_char *ngx_mail_log_error(ngx_log_t *log, u_char *buf, size_t len);
+
+
+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);
+/**/
+
+
+extern ngx_uint_t    ngx_mail_max_module;
+extern ngx_module_t  ngx_mail_core_module;
+
+
+#endif /* _NGX_MAIL_H_INCLUDED_ */
diff --git a/nginx/src/mail/ngx_mail_auth_http_module.c b/nginx/src/mail/ngx_mail_auth_http_module.c
new file mode 100644 (file)
index 0000000..6b57358
--- /dev/null
@@ -0,0 +1,1574 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_connect.h>
+#include <ngx_mail.h>
+
+
+typedef struct {
+    ngx_addr_t                     *peer;
+
+    ngx_msec_t                      timeout;
+    ngx_flag_t                      pass_client_cert;
+
+    ngx_str_t                       host_header;
+    ngx_str_t                       uri;
+    ngx_str_t                       header;
+
+    ngx_array_t                    *headers;
+
+    u_char                         *file;
+    ngx_uint_t                      line;
+} ngx_mail_auth_http_conf_t;
+
+
+typedef struct ngx_mail_auth_http_ctx_s  ngx_mail_auth_http_ctx_t;
+
+typedef void (*ngx_mail_auth_http_handler_pt)(ngx_mail_session_t *s,
+    ngx_mail_auth_http_ctx_t *ctx);
+
+struct ngx_mail_auth_http_ctx_s {
+    ngx_buf_t                      *request;
+    ngx_buf_t                      *response;
+    ngx_peer_connection_t           peer;
+
+    ngx_mail_auth_http_handler_pt   handler;
+
+    ngx_uint_t                      state;
+
+    u_char                         *header_name_start;
+    u_char                         *header_name_end;
+    u_char                         *header_start;
+    u_char                         *header_end;
+
+    ngx_str_t                       addr;
+    ngx_str_t                       port;
+    ngx_str_t                       err;
+    ngx_str_t                       errmsg;
+    ngx_str_t                       errcode;
+
+    time_t                          sleep;
+
+    ngx_pool_t                     *pool;
+};
+
+
+static void ngx_mail_auth_http_write_handler(ngx_event_t *wev);
+static void ngx_mail_auth_http_read_handler(ngx_event_t *rev);
+static void ngx_mail_auth_http_ignore_status_line(ngx_mail_session_t *s,
+    ngx_mail_auth_http_ctx_t *ctx);
+static void ngx_mail_auth_http_process_headers(ngx_mail_session_t *s,
+    ngx_mail_auth_http_ctx_t *ctx);
+static void ngx_mail_auth_sleep_handler(ngx_event_t *rev);
+static ngx_int_t ngx_mail_auth_http_parse_header_line(ngx_mail_session_t *s,
+    ngx_mail_auth_http_ctx_t *ctx);
+static void ngx_mail_auth_http_block_read(ngx_event_t *rev);
+static void ngx_mail_auth_http_dummy_handler(ngx_event_t *ev);
+static ngx_buf_t *ngx_mail_auth_http_create_request(ngx_mail_session_t *s,
+    ngx_pool_t *pool, ngx_mail_auth_http_conf_t *ahcf);
+static ngx_int_t ngx_mail_auth_http_escape(ngx_pool_t *pool, ngx_str_t *text,
+    ngx_str_t *escaped);
+
+static void *ngx_mail_auth_http_create_conf(ngx_conf_t *cf);
+static char *ngx_mail_auth_http_merge_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static char *ngx_mail_auth_http(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_mail_auth_http_header(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+
+static ngx_command_t  ngx_mail_auth_http_commands[] = {
+
+    { ngx_string("auth_http"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_mail_auth_http,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("auth_http_timeout"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_auth_http_conf_t, timeout),
+      NULL },
+
+    { ngx_string("auth_http_header"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE2,
+      ngx_mail_auth_http_header,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("auth_http_pass_client_cert"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_auth_http_conf_t, pass_client_cert),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_mail_module_t  ngx_mail_auth_http_module_ctx = {
+    NULL,                                  /* protocol */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_mail_auth_http_create_conf,        /* create server configuration */
+    ngx_mail_auth_http_merge_conf          /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_mail_auth_http_module = {
+    NGX_MODULE_V1,
+    &ngx_mail_auth_http_module_ctx,        /* module context */
+    ngx_mail_auth_http_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
+};
+
+
+static ngx_str_t   ngx_mail_auth_http_method[] = {
+    ngx_string("plain"),
+    ngx_string("plain"),
+    ngx_string("plain"),
+    ngx_string("apop"),
+    ngx_string("cram-md5"),
+    ngx_string("external"),
+    ngx_string("none")
+};
+
+static ngx_str_t   ngx_mail_smtp_errcode = ngx_string("535 5.7.0");
+
+
+void
+ngx_mail_auth_http_init(ngx_mail_session_t *s)
+{
+    ngx_int_t                   rc;
+    ngx_pool_t                 *pool;
+    ngx_mail_auth_http_ctx_t   *ctx;
+    ngx_mail_auth_http_conf_t  *ahcf;
+
+    s->connection->log->action = "in http auth state";
+
+    pool = ngx_create_pool(2048, s->connection->log);
+    if (pool == NULL) {
+        ngx_mail_session_internal_server_error(s);
+        return;
+    }
+
+    ctx = ngx_pcalloc(pool, sizeof(ngx_mail_auth_http_ctx_t));
+    if (ctx == NULL) {
+        ngx_destroy_pool(pool);
+        ngx_mail_session_internal_server_error(s);
+        return;
+    }
+
+    ctx->pool = pool;
+
+    ahcf = ngx_mail_get_module_srv_conf(s, ngx_mail_auth_http_module);
+
+    ctx->request = ngx_mail_auth_http_create_request(s, pool, ahcf);
+    if (ctx->request == NULL) {
+        ngx_destroy_pool(ctx->pool);
+        ngx_mail_session_internal_server_error(s);
+        return;
+    }
+
+    ngx_mail_set_ctx(s, ctx, ngx_mail_auth_http_module);
+
+    ctx->peer.sockaddr = ahcf->peer->sockaddr;
+    ctx->peer.socklen = ahcf->peer->socklen;
+    ctx->peer.name = &ahcf->peer->name;
+    ctx->peer.get = ngx_event_get_peer;
+    ctx->peer.log = s->connection->log;
+    ctx->peer.log_error = NGX_ERROR_ERR;
+
+    rc = ngx_event_connect_peer(&ctx->peer);
+
+    if (rc == NGX_ERROR || rc == NGX_BUSY || rc == NGX_DECLINED) {
+        if (ctx->peer.connection) {
+            ngx_close_connection(ctx->peer.connection);
+        }
+
+        ngx_destroy_pool(ctx->pool);
+        ngx_mail_session_internal_server_error(s);
+        return;
+    }
+
+    ctx->peer.connection->data = s;
+    ctx->peer.connection->pool = s->connection->pool;
+
+    s->connection->read->handler = ngx_mail_auth_http_block_read;
+    ctx->peer.connection->read->handler = ngx_mail_auth_http_read_handler;
+    ctx->peer.connection->write->handler = ngx_mail_auth_http_write_handler;
+
+    ctx->handler = ngx_mail_auth_http_ignore_status_line;
+
+    ngx_add_timer(ctx->peer.connection->read, ahcf->timeout);
+    ngx_add_timer(ctx->peer.connection->write, ahcf->timeout);
+
+    if (rc == NGX_OK) {
+        ngx_mail_auth_http_write_handler(ctx->peer.connection->write);
+        return;
+    }
+}
+
+
+static void
+ngx_mail_auth_http_write_handler(ngx_event_t *wev)
+{
+    ssize_t                     n, size;
+    ngx_connection_t           *c;
+    ngx_mail_session_t         *s;
+    ngx_mail_auth_http_ctx_t   *ctx;
+    ngx_mail_auth_http_conf_t  *ahcf;
+
+    c = wev->data;
+    s = c->data;
+
+    ctx = ngx_mail_get_module_ctx(s, ngx_mail_auth_http_module);
+
+    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, wev->log, 0,
+                   "mail auth http write handler");
+
+    if (wev->timedout) {
+        ngx_log_error(NGX_LOG_ERR, wev->log, NGX_ETIMEDOUT,
+                      "auth http server %V timed out", ctx->peer.name);
+        ngx_close_connection(c);
+        ngx_destroy_pool(ctx->pool);
+        ngx_mail_session_internal_server_error(s);
+        return;
+    }
+
+    size = ctx->request->last - ctx->request->pos;
+
+    n = ngx_send(c, ctx->request->pos, size);
+
+    if (n == NGX_ERROR) {
+        ngx_close_connection(c);
+        ngx_destroy_pool(ctx->pool);
+        ngx_mail_session_internal_server_error(s);
+        return;
+    }
+
+    if (n > 0) {
+        ctx->request->pos += n;
+
+        if (n == size) {
+            wev->handler = ngx_mail_auth_http_dummy_handler;
+
+            if (wev->timer_set) {
+                ngx_del_timer(wev);
+            }
+
+            if (ngx_handle_write_event(wev, 0) != NGX_OK) {
+                ngx_close_connection(c);
+                ngx_destroy_pool(ctx->pool);
+                ngx_mail_session_internal_server_error(s);
+            }
+
+            return;
+        }
+    }
+
+    if (!wev->timer_set) {
+        ahcf = ngx_mail_get_module_srv_conf(s, ngx_mail_auth_http_module);
+        ngx_add_timer(wev, ahcf->timeout);
+    }
+}
+
+
+static void
+ngx_mail_auth_http_read_handler(ngx_event_t *rev)
+{
+    ssize_t                     n, size;
+    ngx_connection_t          *c;
+    ngx_mail_session_t        *s;
+    ngx_mail_auth_http_ctx_t  *ctx;
+
+    c = rev->data;
+    s = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+                   "mail auth http read handler");
+
+    ctx = ngx_mail_get_module_ctx(s, ngx_mail_auth_http_module);
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_ERR, rev->log, NGX_ETIMEDOUT,
+                      "auth http server %V timed out", ctx->peer.name);
+        ngx_close_connection(c);
+        ngx_destroy_pool(ctx->pool);
+        ngx_mail_session_internal_server_error(s);
+        return;
+    }
+
+    if (ctx->response == NULL) {
+        ctx->response = ngx_create_temp_buf(ctx->pool, 1024);
+        if (ctx->response == NULL) {
+            ngx_close_connection(c);
+            ngx_destroy_pool(ctx->pool);
+            ngx_mail_session_internal_server_error(s);
+            return;
+        }
+    }
+
+    size = ctx->response->end - ctx->response->last;
+
+    n = ngx_recv(c, ctx->response->pos, size);
+
+    if (n > 0) {
+        ctx->response->last += n;
+
+        ctx->handler(s, ctx);
+        return;
+    }
+
+    if (n == NGX_AGAIN) {
+        return;
+    }
+
+    ngx_close_connection(c);
+    ngx_destroy_pool(ctx->pool);
+    ngx_mail_session_internal_server_error(s);
+}
+
+
+static void
+ngx_mail_auth_http_ignore_status_line(ngx_mail_session_t *s,
+    ngx_mail_auth_http_ctx_t *ctx)
+{
+    u_char  *p, ch;
+    enum  {
+        sw_start = 0,
+        sw_H,
+        sw_HT,
+        sw_HTT,
+        sw_HTTP,
+        sw_skip,
+        sw_almost_done
+    } state;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+                   "mail auth http process status line");
+
+    state = ctx->state;
+
+    for (p = ctx->response->pos; p < ctx->response->last; p++) {
+        ch = *p;
+
+        switch (state) {
+
+        /* "HTTP/" */
+        case sw_start:
+            if (ch == 'H') {
+                state = sw_H;
+                break;
+            }
+            goto next;
+
+        case sw_H:
+            if (ch == 'T') {
+                state = sw_HT;
+                break;
+            }
+            goto next;
+
+        case sw_HT:
+            if (ch == 'T') {
+                state = sw_HTT;
+                break;
+            }
+            goto next;
+
+        case sw_HTT:
+            if (ch == 'P') {
+                state = sw_HTTP;
+                break;
+            }
+            goto next;
+
+        case sw_HTTP:
+            if (ch == '/') {
+                state = sw_skip;
+                break;
+            }
+            goto next;
+
+        /* any text until end of line */
+        case sw_skip:
+            switch (ch) {
+            case CR:
+                state = sw_almost_done;
+
+                break;
+            case LF:
+                goto done;
+            }
+            break;
+
+        /* end of status line */
+        case sw_almost_done:
+            if (ch == LF) {
+                goto done;
+            }
+
+            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                          "auth http server %V sent invalid response",
+                          ctx->peer.name);
+            ngx_close_connection(ctx->peer.connection);
+            ngx_destroy_pool(ctx->pool);
+            ngx_mail_session_internal_server_error(s);
+            return;
+        }
+    }
+
+    ctx->response->pos = p;
+    ctx->state = state;
+
+    return;
+
+next:
+
+    p = ctx->response->start - 1;
+
+done:
+
+    ctx->response->pos = p + 1;
+    ctx->state = 0;
+    ctx->handler = ngx_mail_auth_http_process_headers;
+    ctx->handler(s, ctx);
+}
+
+
+static void
+ngx_mail_auth_http_process_headers(ngx_mail_session_t *s,
+    ngx_mail_auth_http_ctx_t *ctx)
+{
+    u_char      *p;
+    time_t       timer;
+    size_t       len, size;
+    ngx_int_t    rc, port, n;
+    ngx_addr_t  *peer;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+                   "mail auth http process headers");
+
+    for ( ;; ) {
+        rc = ngx_mail_auth_http_parse_header_line(s, ctx);
+
+        if (rc == NGX_OK) {
+
+#if (NGX_DEBUG)
+            {
+            ngx_str_t  key, value;
+
+            key.len = ctx->header_name_end - ctx->header_name_start;
+            key.data = ctx->header_name_start;
+            value.len = ctx->header_end - ctx->header_start;
+            value.data = ctx->header_start;
+
+            ngx_log_debug2(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+                           "mail auth http header: \"%V: %V\"",
+                           &key, &value);
+            }
+#endif
+
+            len = ctx->header_name_end - ctx->header_name_start;
+
+            if (len == sizeof("Auth-Status") - 1
+                && ngx_strncasecmp(ctx->header_name_start,
+                                   (u_char *) "Auth-Status",
+                                   sizeof("Auth-Status") - 1)
+                   == 0)
+            {
+                len = ctx->header_end - ctx->header_start;
+
+                if (len == 2
+                    && ctx->header_start[0] == 'O'
+                    && ctx->header_start[1] == 'K')
+                {
+                    continue;
+                }
+
+                if (len == 4
+                    && ctx->header_start[0] == 'W'
+                    && ctx->header_start[1] == 'A'
+                    && ctx->header_start[2] == 'I'
+                    && ctx->header_start[3] == 'T')
+                {
+                    s->auth_wait = 1;
+                    continue;
+                }
+
+                ctx->errmsg.len = len;
+                ctx->errmsg.data = ctx->header_start;
+
+                switch (s->protocol) {
+
+                case NGX_MAIL_POP3_PROTOCOL:
+                    size = sizeof("-ERR ") - 1 + len + sizeof(CRLF) - 1;
+                    break;
+
+                case NGX_MAIL_IMAP_PROTOCOL:
+                    size = s->tag.len + sizeof("NO ") - 1 + len
+                           + sizeof(CRLF) - 1;
+                    break;
+
+                default: /* NGX_MAIL_SMTP_PROTOCOL */
+                    ctx->err = ctx->errmsg;
+                    continue;
+                }
+
+                p = ngx_pnalloc(s->connection->pool, size);
+                if (p == NULL) {
+                    ngx_close_connection(ctx->peer.connection);
+                    ngx_destroy_pool(ctx->pool);
+                    ngx_mail_session_internal_server_error(s);
+                    return;
+                }
+
+                ctx->err.data = p;
+
+                switch (s->protocol) {
+
+                case NGX_MAIL_POP3_PROTOCOL:
+                    *p++ = '-'; *p++ = 'E'; *p++ = 'R'; *p++ = 'R'; *p++ = ' ';
+                    break;
+
+                case NGX_MAIL_IMAP_PROTOCOL:
+                    p = ngx_cpymem(p, s->tag.data, s->tag.len);
+                    *p++ = 'N'; *p++ = 'O'; *p++ = ' ';
+                    break;
+
+                default: /* NGX_MAIL_SMTP_PROTOCOL */
+                    break;
+                }
+
+                p = ngx_cpymem(p, ctx->header_start, len);
+                *p++ = CR; *p++ = LF;
+
+                ctx->err.len = p - ctx->err.data;
+
+                continue;
+            }
+
+            if (len == sizeof("Auth-Server") - 1
+                && ngx_strncasecmp(ctx->header_name_start,
+                                   (u_char *) "Auth-Server",
+                                   sizeof("Auth-Server") - 1)
+                    == 0)
+            {
+                ctx->addr.len = ctx->header_end - ctx->header_start;
+                ctx->addr.data = ctx->header_start;
+
+                continue;
+            }
+
+            if (len == sizeof("Auth-Port") - 1
+                && ngx_strncasecmp(ctx->header_name_start,
+                                   (u_char *) "Auth-Port",
+                                   sizeof("Auth-Port") - 1)
+                   == 0)
+            {
+                ctx->port.len = ctx->header_end - ctx->header_start;
+                ctx->port.data = ctx->header_start;
+
+                continue;
+            }
+
+            if (len == sizeof("Auth-User") - 1
+                && ngx_strncasecmp(ctx->header_name_start,
+                                   (u_char *) "Auth-User",
+                                   sizeof("Auth-User") - 1)
+                   == 0)
+            {
+                s->login.len = ctx->header_end - ctx->header_start;
+
+                s->login.data = ngx_pnalloc(s->connection->pool, s->login.len);
+                if (s->login.data == NULL) {
+                    ngx_close_connection(ctx->peer.connection);
+                    ngx_destroy_pool(ctx->pool);
+                    ngx_mail_session_internal_server_error(s);
+                    return;
+                }
+
+                ngx_memcpy(s->login.data, ctx->header_start, s->login.len);
+
+                continue;
+            }
+
+            if (len == sizeof("Auth-Pass") - 1
+                && ngx_strncasecmp(ctx->header_name_start,
+                                   (u_char *) "Auth-Pass",
+                                   sizeof("Auth-Pass") - 1)
+                   == 0)
+            {
+                s->passwd.len = ctx->header_end - ctx->header_start;
+
+                s->passwd.data = ngx_pnalloc(s->connection->pool,
+                                             s->passwd.len);
+                if (s->passwd.data == NULL) {
+                    ngx_close_connection(ctx->peer.connection);
+                    ngx_destroy_pool(ctx->pool);
+                    ngx_mail_session_internal_server_error(s);
+                    return;
+                }
+
+                ngx_memcpy(s->passwd.data, ctx->header_start, s->passwd.len);
+
+                continue;
+            }
+
+            if (len == sizeof("Auth-Wait") - 1
+                && ngx_strncasecmp(ctx->header_name_start,
+                                   (u_char *) "Auth-Wait",
+                                   sizeof("Auth-Wait") - 1)
+                   == 0)
+            {
+                n = ngx_atoi(ctx->header_start,
+                             ctx->header_end - ctx->header_start);
+
+                if (n != NGX_ERROR) {
+                    ctx->sleep = n;
+                }
+
+                continue;
+            }
+
+            if (len == sizeof("Auth-Error-Code") - 1
+                && ngx_strncasecmp(ctx->header_name_start,
+                                   (u_char *) "Auth-Error-Code",
+                                   sizeof("Auth-Error-Code") - 1)
+                   == 0)
+            {
+                ctx->errcode.len = ctx->header_end - ctx->header_start;
+
+                ctx->errcode.data = ngx_pnalloc(s->connection->pool,
+                                                ctx->errcode.len);
+                if (ctx->errcode.data == NULL) {
+                    ngx_close_connection(ctx->peer.connection);
+                    ngx_destroy_pool(ctx->pool);
+                    ngx_mail_session_internal_server_error(s);
+                    return;
+                }
+
+                ngx_memcpy(ctx->errcode.data, ctx->header_start,
+                           ctx->errcode.len);
+
+                continue;
+            }
+
+            /* ignore other headers */
+
+            continue;
+        }
+
+        if (rc == NGX_DONE) {
+            ngx_log_debug0(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+                           "mail auth http header done");
+
+            ngx_close_connection(ctx->peer.connection);
+
+            if (ctx->err.len) {
+
+                ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
+                              "client login failed: \"%V\"", &ctx->errmsg);
+
+                if (s->protocol == NGX_MAIL_SMTP_PROTOCOL) {
+
+                    if (ctx->errcode.len == 0) {
+                        ctx->errcode = ngx_mail_smtp_errcode;
+                    }
+
+                    ctx->err.len = ctx->errcode.len + ctx->errmsg.len
+                                   + sizeof(" " CRLF) - 1;
+
+                    p = ngx_pnalloc(s->connection->pool, ctx->err.len);
+                    if (p == NULL) {
+                        ngx_destroy_pool(ctx->pool);
+                        ngx_mail_session_internal_server_error(s);
+                        return;
+                    }
+
+                    ctx->err.data = p;
+
+                    p = ngx_cpymem(p, ctx->errcode.data, ctx->errcode.len);
+                    *p++ = ' ';
+                    p = ngx_cpymem(p, ctx->errmsg.data, ctx->errmsg.len);
+                    *p++ = CR; *p = LF;
+                }
+
+                s->out = ctx->err;
+                timer = ctx->sleep;
+
+                ngx_destroy_pool(ctx->pool);
+
+                if (timer == 0) {
+                    s->quit = 1;
+                    ngx_mail_send(s->connection->write);
+                    return;
+                }
+
+                ngx_add_timer(s->connection->read, (ngx_msec_t) (timer * 1000));
+
+                s->connection->read->handler = ngx_mail_auth_sleep_handler;
+
+                return;
+            }
+
+            if (s->auth_wait) {
+                timer = ctx->sleep;
+
+                ngx_destroy_pool(ctx->pool);
+
+                if (timer == 0) {
+                    ngx_mail_auth_http_init(s);
+                    return;
+                }
+
+                ngx_add_timer(s->connection->read, (ngx_msec_t) (timer * 1000));
+
+                s->connection->read->handler = ngx_mail_auth_sleep_handler;
+
+                return;
+            }
+
+            if (ctx->addr.len == 0 || ctx->port.len == 0) {
+                ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                              "auth http server %V did not send server or port",
+                              ctx->peer.name);
+                ngx_destroy_pool(ctx->pool);
+                ngx_mail_session_internal_server_error(s);
+                return;
+            }
+
+            if (s->passwd.data == NULL
+                && s->protocol != NGX_MAIL_SMTP_PROTOCOL)
+            {
+                ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                              "auth http server %V did not send password",
+                              ctx->peer.name);
+                ngx_destroy_pool(ctx->pool);
+                ngx_mail_session_internal_server_error(s);
+                return;
+            }
+
+            peer = ngx_pcalloc(s->connection->pool, sizeof(ngx_addr_t));
+            if (peer == NULL) {
+                ngx_destroy_pool(ctx->pool);
+                ngx_mail_session_internal_server_error(s);
+                return;
+            }
+
+            rc = ngx_parse_addr(s->connection->pool, peer,
+                                ctx->addr.data, ctx->addr.len);
+
+            switch (rc) {
+            case NGX_OK:
+                break;
+
+            case NGX_DECLINED:
+                ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                              "auth http server %V sent invalid server "
+                              "address:\"%V\"",
+                              ctx->peer.name, &ctx->addr);
+                /* fall through */
+
+            default:
+                ngx_destroy_pool(ctx->pool);
+                ngx_mail_session_internal_server_error(s);
+                return;
+            }
+
+            port = ngx_atoi(ctx->port.data, ctx->port.len);
+            if (port == NGX_ERROR || port < 1 || port > 65535) {
+                ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                              "auth http server %V sent invalid server "
+                              "port:\"%V\"",
+                              ctx->peer.name, &ctx->port);
+                ngx_destroy_pool(ctx->pool);
+                ngx_mail_session_internal_server_error(s);
+                return;
+            }
+
+            ngx_inet_set_port(peer->sockaddr, (in_port_t) port);
+
+            len = ctx->addr.len + 1 + ctx->port.len;
+
+            peer->name.len = len;
+
+            peer->name.data = ngx_pnalloc(s->connection->pool, len);
+            if (peer->name.data == NULL) {
+                ngx_destroy_pool(ctx->pool);
+                ngx_mail_session_internal_server_error(s);
+                return;
+            }
+
+            len = ctx->addr.len;
+
+            ngx_memcpy(peer->name.data, ctx->addr.data, len);
+
+            peer->name.data[len++] = ':';
+
+            ngx_memcpy(peer->name.data + len, ctx->port.data, ctx->port.len);
+
+            ngx_destroy_pool(ctx->pool);
+            ngx_mail_proxy_init(s, peer);
+
+            return;
+        }
+
+        if (rc == NGX_AGAIN ) {
+            return;
+        }
+
+        /* rc == NGX_ERROR */
+
+        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                      "auth http server %V sent invalid header in response",
+                      ctx->peer.name);
+        ngx_close_connection(ctx->peer.connection);
+        ngx_destroy_pool(ctx->pool);
+        ngx_mail_session_internal_server_error(s);
+
+        return;
+    }
+}
+
+
+static void
+ngx_mail_auth_sleep_handler(ngx_event_t *rev)
+{
+    ngx_connection_t          *c;
+    ngx_mail_session_t        *s;
+    ngx_mail_core_srv_conf_t  *cscf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, "mail auth sleep handler");
+
+    c = rev->data;
+    s = c->data;
+
+    if (rev->timedout) {
+
+        rev->timedout = 0;
+
+        if (s->auth_wait) {
+            s->auth_wait = 0;
+            ngx_mail_auth_http_init(s);
+            return;
+        }
+
+        cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+        rev->handler = cscf->protocol->auth_state;
+
+        s->mail_state = 0;
+        s->auth_method = NGX_MAIL_AUTH_PLAIN;
+
+        c->log->action = "in auth state";
+
+        ngx_mail_send(c->write);
+
+        if (c->destroyed) {
+            return;
+        }
+
+        ngx_add_timer(rev, cscf->timeout);
+
+        if (rev->ready) {
+            rev->handler(rev);
+            return;
+        }
+
+        if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+            ngx_mail_close_connection(c);
+        }
+
+        return;
+    }
+
+    if (rev->active) {
+        if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+            ngx_mail_close_connection(c);
+        }
+    }
+}
+
+
+static ngx_int_t
+ngx_mail_auth_http_parse_header_line(ngx_mail_session_t *s,
+    ngx_mail_auth_http_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;
+
+        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;
+            }
+
+            if (ch == CR) {
+                ctx->header_name_end = p;
+                ctx->header_start = p;
+                ctx->header_end = p;
+                state = sw_almost_done;
+                break;
+            }
+
+            if (ch == LF) {
+                ctx->header_name_end = p;
+                ctx->header_start = p;
+                ctx->header_end = p;
+                goto done;
+            }
+
+            return NGX_ERROR;
+
+        /* 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;
+
+        /* 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;
+
+        /* end of header line */
+        case sw_almost_done:
+            switch (ch) {
+            case LF:
+                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;
+            }
+        }
+    }
+
+    ctx->response->pos = p;
+    ctx->state = state;
+
+    return NGX_AGAIN;
+
+done:
+
+    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 void
+ngx_mail_auth_http_block_read(ngx_event_t *rev)
+{
+    ngx_connection_t          *c;
+    ngx_mail_session_t        *s;
+    ngx_mail_auth_http_ctx_t  *ctx;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+                   "mail auth http block read");
+
+    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+        c = rev->data;
+        s = c->data;
+
+        ctx = ngx_mail_get_module_ctx(s, ngx_mail_auth_http_module);
+
+        ngx_close_connection(ctx->peer.connection);
+        ngx_destroy_pool(ctx->pool);
+        ngx_mail_session_internal_server_error(s);
+    }
+}
+
+
+static void
+ngx_mail_auth_http_dummy_handler(ngx_event_t *ev)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, ev->log, 0,
+                   "mail auth http dummy handler");
+}
+
+
+static ngx_buf_t *
+ngx_mail_auth_http_create_request(ngx_mail_session_t *s, ngx_pool_t *pool,
+    ngx_mail_auth_http_conf_t *ahcf)
+{
+    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;
+    ngx_mail_ssl_conf_t       *sslcf;
+#endif
+    ngx_mail_core_srv_conf_t  *cscf;
+
+    if (ngx_mail_auth_http_escape(pool, &s->login, &login) != NGX_OK) {
+        return NULL;
+    }
+
+    if (ngx_mail_auth_http_escape(pool, &s->passwd, &passwd) != NGX_OK) {
+        return NULL;
+    }
+
+#if (NGX_MAIL_SSL)
+
+    c = s->connection;
+    sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+    if (c->ssl && sslcf->verify) {
+
+        /* certificate details */
+
+        if (ngx_ssl_get_client_verify(c, pool, &verify) != NGX_OK) {
+            return NULL;
+        }
+
+        if (ngx_ssl_get_subject_dn(c, pool, &subject) != NGX_OK) {
+            return NULL;
+        }
+
+        if (ngx_ssl_get_issuer_dn(c, pool, &issuer) != NGX_OK) {
+            return NULL;
+        }
+
+        if (ngx_ssl_get_serial_number(c, pool, &serial) != NGX_OK) {
+            return NULL;
+        }
+
+        if (ngx_ssl_get_fingerprint(c, pool, &fingerprint) != NGX_OK) {
+            return NULL;
+        }
+
+        if (ahcf->pass_client_cert) {
+
+            /* certificate itself, if configured */
+
+            if (ngx_ssl_get_raw_certificate(c, pool, &raw_cert) != NGX_OK) {
+                return NULL;
+            }
+
+            if (ngx_mail_auth_http_escape(pool, &raw_cert, &cert) != NGX_OK) {
+                return NULL;
+            }
+
+        } else {
+            ngx_str_null(&cert);
+        }
+
+    } else {
+        ngx_str_null(&verify);
+        ngx_str_null(&subject);
+        ngx_str_null(&issuer);
+        ngx_str_null(&serial);
+        ngx_str_null(&fingerprint);
+        ngx_str_null(&cert);
+    }
+
+#endif
+
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+    len = sizeof("GET ") - 1 + ahcf->uri.len + sizeof(" HTTP/1.0" CRLF) - 1
+          + sizeof("Host: ") - 1 + ahcf->host_header.len + sizeof(CRLF) - 1
+          + sizeof("Auth-Method: ") - 1
+                + ngx_mail_auth_http_method[s->auth_method].len
+                + sizeof(CRLF) - 1
+          + sizeof("Auth-User: ") - 1 + login.len + sizeof(CRLF) - 1
+          + sizeof("Auth-Pass: ") - 1 + passwd.len + sizeof(CRLF) - 1
+          + sizeof("Auth-Salt: ") - 1 + s->salt.len
+          + sizeof("Auth-Protocol: ") - 1 + cscf->protocol->name.len
+                + sizeof(CRLF) - 1
+          + sizeof("Auth-Login-Attempt: ") - 1 + NGX_INT_T_LEN
+                + sizeof(CRLF) - 1
+          + 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;
+
+    b = ngx_create_temp_buf(pool, len);
+    if (b == NULL) {
+        return NULL;
+    }
+
+    b->last = ngx_cpymem(b->last, "GET ", sizeof("GET ") - 1);
+    b->last = ngx_copy(b->last, ahcf->uri.data, ahcf->uri.len);
+    b->last = ngx_cpymem(b->last, " HTTP/1.0" CRLF,
+                         sizeof(" HTTP/1.0" CRLF) - 1);
+
+    b->last = ngx_cpymem(b->last, "Host: ", sizeof("Host: ") - 1);
+    b->last = ngx_copy(b->last, ahcf->host_header.data,
+                         ahcf->host_header.len);
+    *b->last++ = CR; *b->last++ = LF;
+
+    b->last = ngx_cpymem(b->last, "Auth-Method: ",
+                         sizeof("Auth-Method: ") - 1);
+    b->last = ngx_cpymem(b->last,
+                         ngx_mail_auth_http_method[s->auth_method].data,
+                         ngx_mail_auth_http_method[s->auth_method].len);
+    *b->last++ = CR; *b->last++ = LF;
+
+    b->last = ngx_cpymem(b->last, "Auth-User: ", sizeof("Auth-User: ") - 1);
+    b->last = ngx_copy(b->last, login.data, login.len);
+    *b->last++ = CR; *b->last++ = LF;
+
+    b->last = ngx_cpymem(b->last, "Auth-Pass: ", sizeof("Auth-Pass: ") - 1);
+    b->last = ngx_copy(b->last, passwd.data, passwd.len);
+    *b->last++ = CR; *b->last++ = LF;
+
+    if (s->auth_method != NGX_MAIL_AUTH_PLAIN && s->salt.len) {
+        b->last = ngx_cpymem(b->last, "Auth-Salt: ", sizeof("Auth-Salt: ") - 1);
+        b->last = ngx_copy(b->last, s->salt.data, s->salt.len);
+
+        s->passwd.data = NULL;
+    }
+
+    b->last = ngx_cpymem(b->last, "Auth-Protocol: ",
+                         sizeof("Auth-Protocol: ") - 1);
+    b->last = ngx_cpymem(b->last, cscf->protocol->name.data,
+                         cscf->protocol->name.len);
+    *b->last++ = CR; *b->last++ = LF;
+
+    b->last = ngx_sprintf(b->last, "Auth-Login-Attempt: %ui" CRLF,
+                          s->login_attempt);
+
+    b->last = ngx_cpymem(b->last, "Client-IP: ", sizeof("Client-IP: ") - 1);
+    b->last = ngx_copy(b->last, s->connection->addr_text.data,
+                       s->connection->addr_text.len);
+    *b->last++ = CR; *b->last++ = LF;
+
+    if (s->host.len) {
+        b->last = ngx_cpymem(b->last, "Client-Host: ",
+                             sizeof("Client-Host: ") - 1);
+        b->last = ngx_copy(b->last, s->host.data, s->host.len);
+        *b->last++ = CR; *b->last++ = LF;
+    }
+
+    if (s->auth_method == NGX_MAIL_AUTH_NONE) {
+
+        /* HELO, MAIL FROM, and RCPT TO can't contain CRLF, no need to escape */
+
+        b->last = ngx_cpymem(b->last, "Auth-SMTP-Helo: ",
+                             sizeof("Auth-SMTP-Helo: ") - 1);
+        b->last = ngx_copy(b->last, s->smtp_helo.data, s->smtp_helo.len);
+        *b->last++ = CR; *b->last++ = LF;
+
+        b->last = ngx_cpymem(b->last, "Auth-SMTP-From: ",
+                             sizeof("Auth-SMTP-From: ") - 1);
+        b->last = ngx_copy(b->last, s->smtp_from.data, s->smtp_from.len);
+        *b->last++ = CR; *b->last++ = LF;
+
+        b->last = ngx_cpymem(b->last, "Auth-SMTP-To: ",
+                             sizeof("Auth-SMTP-To: ") - 1);
+        b->last = ngx_copy(b->last, s->smtp_to.data, s->smtp_to.len);
+        *b->last++ = CR; *b->last++ = LF;
+
+    }
+
+#if (NGX_MAIL_SSL)
+
+    if (c->ssl) {
+        b->last = ngx_cpymem(b->last, "Auth-SSL: on" CRLF,
+                             sizeof("Auth-SSL: on" CRLF) - 1);
+
+        if (verify.len) {
+            b->last = ngx_cpymem(b->last, "Auth-SSL-Verify: ",
+                                 sizeof("Auth-SSL-Verify: ") - 1);
+            b->last = ngx_copy(b->last, verify.data, verify.len);
+            *b->last++ = CR; *b->last++ = LF;
+        }
+
+        if (subject.len) {
+            b->last = ngx_cpymem(b->last, "Auth-SSL-Subject: ",
+                                 sizeof("Auth-SSL-Subject: ") - 1);
+            b->last = ngx_copy(b->last, subject.data, subject.len);
+            *b->last++ = CR; *b->last++ = LF;
+        }
+
+        if (issuer.len) {
+            b->last = ngx_cpymem(b->last, "Auth-SSL-Issuer: ",
+                                 sizeof("Auth-SSL-Issuer: ") - 1);
+            b->last = ngx_copy(b->last, issuer.data, issuer.len);
+            *b->last++ = CR; *b->last++ = LF;
+        }
+
+        if (serial.len) {
+            b->last = ngx_cpymem(b->last, "Auth-SSL-Serial: ",
+                                 sizeof("Auth-SSL-Serial: ") - 1);
+            b->last = ngx_copy(b->last, serial.data, serial.len);
+            *b->last++ = CR; *b->last++ = LF;
+        }
+
+        if (fingerprint.len) {
+            b->last = ngx_cpymem(b->last, "Auth-SSL-Fingerprint: ",
+                                 sizeof("Auth-SSL-Fingerprint: ") - 1);
+            b->last = ngx_copy(b->last, fingerprint.data, fingerprint.len);
+            *b->last++ = CR; *b->last++ = LF;
+        }
+
+        if (cert.len) {
+            b->last = ngx_cpymem(b->last, "Auth-SSL-Cert: ",
+                                 sizeof("Auth-SSL-Cert: ") - 1);
+            b->last = ngx_copy(b->last, cert.data, cert.len);
+            *b->last++ = CR; *b->last++ = LF;
+        }
+    }
+
+#endif
+
+    if (ahcf->header.len) {
+        b->last = ngx_copy(b->last, ahcf->header.data, ahcf->header.len);
+    }
+
+    /* add "\r\n" at the header end */
+    *b->last++ = CR; *b->last++ = LF;
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+    ngx_log_debug2(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+                   "mail auth http header:%N\"%*s\"",
+                   (size_t) (b->last - b->pos), b->pos);
+#endif
+
+    return b;
+}
+
+
+static ngx_int_t
+ngx_mail_auth_http_escape(ngx_pool_t *pool, ngx_str_t *text, ngx_str_t *escaped)
+{
+    u_char     *p;
+    uintptr_t   n;
+
+    n = ngx_escape_uri(NULL, text->data, text->len, NGX_ESCAPE_MAIL_AUTH);
+
+    if (n == 0) {
+        *escaped = *text;
+        return NGX_OK;
+    }
+
+    escaped->len = text->len + n * 2;
+
+    p = ngx_pnalloc(pool, escaped->len);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    (void) ngx_escape_uri(p, text->data, text->len, NGX_ESCAPE_MAIL_AUTH);
+
+    escaped->data = p;
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_mail_auth_http_create_conf(ngx_conf_t *cf)
+{
+    ngx_mail_auth_http_conf_t  *ahcf;
+
+    ahcf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_auth_http_conf_t));
+    if (ahcf == NULL) {
+        return NULL;
+    }
+
+    ahcf->timeout = NGX_CONF_UNSET_MSEC;
+    ahcf->pass_client_cert = NGX_CONF_UNSET;
+
+    ahcf->file = cf->conf_file->file.name.data;
+    ahcf->line = cf->conf_file->line;
+
+    return ahcf;
+}
+
+
+static char *
+ngx_mail_auth_http_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_mail_auth_http_conf_t *prev = parent;
+    ngx_mail_auth_http_conf_t *conf = child;
+
+    u_char           *p;
+    size_t            len;
+    ngx_uint_t        i;
+    ngx_table_elt_t  *header;
+
+    if (conf->peer == NULL) {
+        conf->peer = prev->peer;
+        conf->host_header = prev->host_header;
+        conf->uri = prev->uri;
+
+        if (conf->peer == NULL) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "no \"auth_http\" is defined for server in %s:%ui",
+                          conf->file, conf->line);
+
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000);
+
+    ngx_conf_merge_value(conf->pass_client_cert, prev->pass_client_cert, 0);
+
+    if (conf->headers == NULL) {
+        conf->headers = prev->headers;
+        conf->header = prev->header;
+    }
+
+    if (conf->headers && conf->header.len == 0) {
+        len = 0;
+        header = conf->headers->elts;
+        for (i = 0; i < conf->headers->nelts; i++) {
+            len += header[i].key.len + 2 + header[i].value.len + 2;
+        }
+
+        p = ngx_pnalloc(cf->pool, len);
+        if (p == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        conf->header.len = len;
+        conf->header.data = p;
+
+        for (i = 0; i < conf->headers->nelts; i++) {
+            p = ngx_cpymem(p, header[i].key.data, header[i].key.len);
+            *p++ = ':'; *p++ = ' ';
+            p = ngx_cpymem(p, header[i].value.data, header[i].value.len);
+            *p++ = CR; *p++ = LF;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_auth_http(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_mail_auth_http_conf_t *ahcf = conf;
+
+    ngx_str_t  *value;
+    ngx_url_t   u;
+
+    value = cf->args->elts;
+
+    ngx_memzero(&u, sizeof(ngx_url_t));
+
+    u.url = value[1];
+    u.default_port = 80;
+    u.uri_part = 1;
+
+    if (ngx_strncmp(u.url.data, "http://", 7) == 0) {
+        u.url.len -= 7;
+        u.url.data += 7;
+    }
+
+    if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
+        if (u.err) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "%s in auth_http \"%V\"", u.err, &u.url);
+        }
+
+        return NGX_CONF_ERROR;
+    }
+
+    ahcf->peer = u.addrs;
+
+    if (u.family != AF_UNIX) {
+        ahcf->host_header = u.host;
+
+    } else {
+        ngx_str_set(&ahcf->host_header, "localhost");
+    }
+
+    ahcf->uri = u.uri;
+
+    if (ahcf->uri.len == 0) {
+        ngx_str_set(&ahcf->uri, "/");
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_auth_http_header(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_mail_auth_http_conf_t *ahcf = conf;
+
+    ngx_str_t        *value;
+    ngx_table_elt_t  *header;
+
+    if (ahcf->headers == NULL) {
+        ahcf->headers = ngx_array_create(cf->pool, 1, sizeof(ngx_table_elt_t));
+        if (ahcf->headers == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    header = ngx_array_push(ahcf->headers);
+    if (header == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    value = cf->args->elts;
+
+    header->key = value[1];
+    header->value = value[2];
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/mail/ngx_mail_core_module.c b/nginx/src/mail/ngx_mail_core_module.c
new file mode 100644 (file)
index 0000000..276b8ee
--- /dev/null
@@ -0,0 +1,678 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+
+
+static void *ngx_mail_core_create_main_conf(ngx_conf_t *cf);
+static void *ngx_mail_core_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_mail_core_merge_srv_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static char *ngx_mail_core_server(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_mail_core_listen(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_mail_core_protocol(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_mail_core_error_log(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_mail_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+
+static ngx_command_t  ngx_mail_core_commands[] = {
+
+    { ngx_string("server"),
+      NGX_MAIL_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+      ngx_mail_core_server,
+      0,
+      0,
+      NULL },
+
+    { ngx_string("listen"),
+      NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+      ngx_mail_core_listen,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("protocol"),
+      NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_mail_core_protocol,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("timeout"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_core_srv_conf_t, timeout),
+      NULL },
+
+    { ngx_string("server_name"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_core_srv_conf_t, server_name),
+      NULL },
+
+    { ngx_string("error_log"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+      ngx_mail_core_error_log,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("resolver"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+      ngx_mail_core_resolver,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("resolver_timeout"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_core_srv_conf_t, resolver_timeout),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_mail_module_t  ngx_mail_core_module_ctx = {
+    NULL,                                  /* protocol */
+
+    ngx_mail_core_create_main_conf,        /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_mail_core_create_srv_conf,         /* create server configuration */
+    ngx_mail_core_merge_srv_conf           /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_mail_core_module = {
+    NGX_MODULE_V1,
+    &ngx_mail_core_module_ctx,             /* module context */
+    ngx_mail_core_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
+};
+
+
+static void *
+ngx_mail_core_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_mail_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_core_main_conf_t));
+    if (cmcf == NULL) {
+        return NULL;
+    }
+
+    if (ngx_array_init(&cmcf->servers, cf->pool, 4,
+                       sizeof(ngx_mail_core_srv_conf_t *))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    if (ngx_array_init(&cmcf->listen, cf->pool, 4, sizeof(ngx_mail_listen_t))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    return cmcf;
+}
+
+
+static void *
+ngx_mail_core_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_mail_core_srv_conf_t  *cscf;
+
+    cscf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_core_srv_conf_t));
+    if (cscf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     cscf->protocol = NULL;
+     *     cscf->error_log = NULL;
+     */
+
+    cscf->timeout = NGX_CONF_UNSET_MSEC;
+    cscf->resolver_timeout = NGX_CONF_UNSET_MSEC;
+
+    cscf->resolver = NGX_CONF_UNSET_PTR;
+
+    cscf->file_name = cf->conf_file->file.name.data;
+    cscf->line = cf->conf_file->line;
+
+    return cscf;
+}
+
+
+static char *
+ngx_mail_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_mail_core_srv_conf_t *prev = parent;
+    ngx_mail_core_srv_conf_t *conf = child;
+
+    ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000);
+    ngx_conf_merge_msec_value(conf->resolver_timeout, prev->resolver_timeout,
+                              30000);
+
+
+    ngx_conf_merge_str_value(conf->server_name, prev->server_name, "");
+
+    if (conf->server_name.len == 0) {
+        conf->server_name = cf->cycle->hostname;
+    }
+
+    if (conf->protocol == NULL) {
+        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                      "unknown mail protocol for server in %s:%ui",
+                      conf->file_name, conf->line);
+        return NGX_CONF_ERROR;
+    }
+
+    if (conf->error_log == NULL) {
+        if (prev->error_log) {
+            conf->error_log = prev->error_log;
+        } else {
+            conf->error_log = &cf->cycle->new_log;
+        }
+    }
+
+    ngx_conf_merge_ptr_value(conf->resolver, prev->resolver, NULL);
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char                       *rv;
+    void                       *mconf;
+    ngx_uint_t                  m;
+    ngx_conf_t                  pcf;
+    ngx_mail_module_t          *module;
+    ngx_mail_conf_ctx_t        *ctx, *mail_ctx;
+    ngx_mail_core_srv_conf_t   *cscf, **cscfp;
+    ngx_mail_core_main_conf_t  *cmcf;
+
+    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_mail_conf_ctx_t));
+    if (ctx == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    mail_ctx = cf->ctx;
+    ctx->main_conf = mail_ctx->main_conf;
+
+    /* the server{}'s srv_conf */
+
+    ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_mail_max_module);
+    if (ctx->srv_conf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    for (m = 0; cf->cycle->modules[m]; m++) {
+        if (cf->cycle->modules[m]->type != NGX_MAIL_MODULE) {
+            continue;
+        }
+
+        module = cf->cycle->modules[m]->ctx;
+
+        if (module->create_srv_conf) {
+            mconf = module->create_srv_conf(cf);
+            if (mconf == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            ctx->srv_conf[cf->cycle->modules[m]->ctx_index] = mconf;
+        }
+    }
+
+    /* the server configuration context */
+
+    cscf = ctx->srv_conf[ngx_mail_core_module.ctx_index];
+    cscf->ctx = ctx;
+
+    cmcf = ctx->main_conf[ngx_mail_core_module.ctx_index];
+
+    cscfp = ngx_array_push(&cmcf->servers);
+    if (cscfp == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    *cscfp = cscf;
+
+
+    /* parse inside server{} */
+
+    pcf = *cf;
+    cf->ctx = ctx;
+    cf->cmd_type = NGX_MAIL_SRV_CONF;
+
+    rv = ngx_conf_parse(cf, NULL);
+
+    *cf = pcf;
+
+    if (rv == NGX_CONF_OK && !cscf->listen) {
+        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                      "no \"listen\" is defined for server in %s:%ui",
+                      cscf->file_name, cscf->line);
+        return NGX_CONF_ERROR;
+    }
+
+    return rv;
+}
+
+
+static char *
+ngx_mail_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_mail_core_srv_conf_t  *cscf = conf;
+
+    ngx_str_t                  *value, size;
+    ngx_url_t                   u;
+    ngx_uint_t                  i, m;
+    ngx_mail_listen_t          *ls;
+    ngx_mail_module_t          *module;
+    ngx_mail_core_main_conf_t  *cmcf;
+
+    cscf->listen = 1;
+
+    value = cf->args->elts;
+
+    ngx_memzero(&u, sizeof(ngx_url_t));
+
+    u.url = value[1];
+    u.listen = 1;
+
+    if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
+        if (u.err) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "%s in \"%V\" of the \"listen\" directive",
+                               u.err, &u.url);
+        }
+
+        return NGX_CONF_ERROR;
+    }
+
+    cmcf = ngx_mail_conf_get_module_main_conf(cf, ngx_mail_core_module);
+
+    ls = cmcf->listen.elts;
+
+    for (i = 0; i < cmcf->listen.nelts; i++) {
+
+        if (ngx_cmp_sockaddr(&ls[i].sockaddr.sockaddr, ls[i].socklen,
+                             (struct sockaddr *) &u.sockaddr, u.socklen, 1)
+            != NGX_OK)
+        {
+            continue;
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "duplicate \"%V\" address and port pair", &u.url);
+        return NGX_CONF_ERROR;
+    }
+
+    ls = ngx_array_push(&cmcf->listen);
+    if (ls == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memzero(ls, sizeof(ngx_mail_listen_t));
+
+    ngx_memcpy(&ls->sockaddr.sockaddr, &u.sockaddr, u.socklen);
+
+    ls->socklen = u.socklen;
+    ls->backlog = NGX_LISTEN_BACKLOG;
+    ls->rcvbuf = -1;
+    ls->sndbuf = -1;
+    ls->wildcard = u.wildcard;
+    ls->ctx = cf->ctx;
+
+#if (NGX_HAVE_INET6)
+    ls->ipv6only = 1;
+#endif
+
+    if (cscf->protocol == NULL) {
+        for (m = 0; cf->cycle->modules[m]; m++) {
+            if (cf->cycle->modules[m]->type != NGX_MAIL_MODULE) {
+                continue;
+            }
+
+            module = cf->cycle->modules[m]->ctx;
+
+            if (module->protocol == NULL) {
+                continue;
+            }
+
+            for (i = 0; module->protocol->port[i]; i++) {
+                if (module->protocol->port[i] == u.port) {
+                    cscf->protocol = module->protocol;
+                    break;
+                }
+            }
+        }
+    }
+
+    for (i = 2; i < cf->args->nelts; i++) {
+
+        if (ngx_strcmp(value[i].data, "bind") == 0) {
+            ls->bind = 1;
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "backlog=", 8) == 0) {
+            ls->backlog = ngx_atoi(value[i].data + 8, value[i].len - 8);
+            ls->bind = 1;
+
+            if (ls->backlog == NGX_ERROR || ls->backlog == 0) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid backlog \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "rcvbuf=", 7) == 0) {
+            size.len = value[i].len - 7;
+            size.data = value[i].data + 7;
+
+            ls->rcvbuf = ngx_parse_size(&size);
+            ls->bind = 1;
+
+            if (ls->rcvbuf == NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid rcvbuf \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "sndbuf=", 7) == 0) {
+            size.len = value[i].len - 7;
+            size.data = value[i].data + 7;
+
+            ls->sndbuf = ngx_parse_size(&size);
+            ls->bind = 1;
+
+            if (ls->sndbuf == NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid sndbuf \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "ipv6only=o", 10) == 0) {
+#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
+            size_t  len;
+            u_char  buf[NGX_SOCKADDR_STRLEN];
+
+            if (ls->sockaddr.sockaddr.sa_family == AF_INET6) {
+
+                if (ngx_strcmp(&value[i].data[10], "n") == 0) {
+                    ls->ipv6only = 1;
+
+                } else if (ngx_strcmp(&value[i].data[10], "ff") == 0) {
+                    ls->ipv6only = 0;
+
+                } else {
+                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                       "invalid ipv6only flags \"%s\"",
+                                       &value[i].data[9]);
+                    return NGX_CONF_ERROR;
+                }
+
+                ls->bind = 1;
+
+            } else {
+                len = ngx_sock_ntop(&ls->sockaddr.sockaddr, ls->socklen, buf,
+                                    NGX_SOCKADDR_STRLEN, 1);
+
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "ipv6only is not supported "
+                                   "on addr \"%*s\", ignored", len, buf);
+            }
+
+            continue;
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "bind ipv6only is not supported "
+                               "on this platform");
+            return NGX_CONF_ERROR;
+#endif
+        }
+
+        if (ngx_strcmp(value[i].data, "ssl") == 0) {
+#if (NGX_MAIL_SSL)
+            ls->ssl = 1;
+            continue;
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "the \"ssl\" parameter requires "
+                               "ngx_mail_ssl_module");
+            return NGX_CONF_ERROR;
+#endif
+        }
+
+        if (ngx_strncmp(value[i].data, "so_keepalive=", 13) == 0) {
+
+            if (ngx_strcmp(&value[i].data[13], "on") == 0) {
+                ls->so_keepalive = 1;
+
+            } else if (ngx_strcmp(&value[i].data[13], "off") == 0) {
+                ls->so_keepalive = 2;
+
+            } else {
+
+#if (NGX_HAVE_KEEPALIVE_TUNABLE)
+                u_char     *p, *end;
+                ngx_str_t   s;
+
+                end = value[i].data + value[i].len;
+                s.data = value[i].data + 13;
+
+                p = ngx_strlchr(s.data, end, ':');
+                if (p == NULL) {
+                    p = end;
+                }
+
+                if (p > s.data) {
+                    s.len = p - s.data;
+
+                    ls->tcp_keepidle = ngx_parse_time(&s, 1);
+                    if (ls->tcp_keepidle == (time_t) NGX_ERROR) {
+                        goto invalid_so_keepalive;
+                    }
+                }
+
+                s.data = (p < end) ? (p + 1) : end;
+
+                p = ngx_strlchr(s.data, end, ':');
+                if (p == NULL) {
+                    p = end;
+                }
+
+                if (p > s.data) {
+                    s.len = p - s.data;
+
+                    ls->tcp_keepintvl = ngx_parse_time(&s, 1);
+                    if (ls->tcp_keepintvl == (time_t) NGX_ERROR) {
+                        goto invalid_so_keepalive;
+                    }
+                }
+
+                s.data = (p < end) ? (p + 1) : end;
+
+                if (s.data < end) {
+                    s.len = end - s.data;
+
+                    ls->tcp_keepcnt = ngx_atoi(s.data, s.len);
+                    if (ls->tcp_keepcnt == NGX_ERROR) {
+                        goto invalid_so_keepalive;
+                    }
+                }
+
+                if (ls->tcp_keepidle == 0 && ls->tcp_keepintvl == 0
+                    && ls->tcp_keepcnt == 0)
+                {
+                    goto invalid_so_keepalive;
+                }
+
+                ls->so_keepalive = 1;
+
+#else
+
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "the \"so_keepalive\" parameter accepts "
+                                   "only \"on\" or \"off\" on this platform");
+                return NGX_CONF_ERROR;
+
+#endif
+            }
+
+            ls->bind = 1;
+
+            continue;
+
+#if (NGX_HAVE_KEEPALIVE_TUNABLE)
+        invalid_so_keepalive:
+
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid so_keepalive value: \"%s\"",
+                               &value[i].data[13]);
+            return NGX_CONF_ERROR;
+#endif
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "the invalid \"%V\" parameter", &value[i]);
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_core_protocol(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_mail_core_srv_conf_t  *cscf = conf;
+
+    ngx_str_t          *value;
+    ngx_uint_t          m;
+    ngx_mail_module_t  *module;
+
+    value = cf->args->elts;
+
+    for (m = 0; cf->cycle->modules[m]; m++) {
+        if (cf->cycle->modules[m]->type != NGX_MAIL_MODULE) {
+            continue;
+        }
+
+        module = cf->cycle->modules[m]->ctx;
+
+        if (module->protocol
+            && ngx_strcmp(module->protocol->name.data, value[1].data) == 0)
+        {
+            cscf->protocol = module->protocol;
+
+            return NGX_CONF_OK;
+        }
+    }
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "unknown protocol \"%V\"", &value[1]);
+    return NGX_CONF_ERROR;
+}
+
+
+static char *
+ngx_mail_core_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_mail_core_srv_conf_t  *cscf = conf;
+
+    return ngx_log_set_log(cf, &cscf->error_log);
+}
+
+
+static char *
+ngx_mail_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_mail_core_srv_conf_t  *cscf = conf;
+
+    ngx_str_t  *value;
+
+    value = cf->args->elts;
+
+    if (cscf->resolver != NGX_CONF_UNSET_PTR) {
+        return "is duplicate";
+    }
+
+    if (ngx_strcmp(value[1].data, "off") == 0) {
+        cscf->resolver = NULL;
+        return NGX_CONF_OK;
+    }
+
+    cscf->resolver = ngx_resolver_create(cf, &value[1], cf->args->nelts - 1);
+    if (cscf->resolver == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *
+ngx_mail_capabilities(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ngx_str_t    *c, *value;
+    ngx_uint_t    i;
+    ngx_array_t  *a;
+
+    a = (ngx_array_t *) (p + cmd->offset);
+
+    value = cf->args->elts;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+        c = ngx_array_push(a);
+        if (c == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        *c = value[i];
+    }
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/mail/ngx_mail_handler.c b/nginx/src/mail/ngx_mail_handler.c
new file mode 100644 (file)
index 0000000..bc3e6b9
--- /dev/null
@@ -0,0 +1,900 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+
+
+static void ngx_mail_init_session(ngx_connection_t *c);
+
+#if (NGX_MAIL_SSL)
+static void ngx_mail_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c);
+static void ngx_mail_ssl_handshake_handler(ngx_connection_t *c);
+static ngx_int_t ngx_mail_verify_cert(ngx_mail_session_t *s,
+    ngx_connection_t *c);
+#endif
+
+
+void
+ngx_mail_init_connection(ngx_connection_t *c)
+{
+    size_t                     len;
+    ngx_uint_t                 i;
+    ngx_mail_port_t           *port;
+    struct sockaddr           *sa;
+    struct sockaddr_in        *sin;
+    ngx_mail_log_ctx_t        *ctx;
+    ngx_mail_in_addr_t        *addr;
+    ngx_mail_session_t        *s;
+    ngx_mail_addr_conf_t      *addr_conf;
+    ngx_mail_core_srv_conf_t  *cscf;
+    u_char                     text[NGX_SOCKADDR_STRLEN];
+#if (NGX_HAVE_INET6)
+    struct sockaddr_in6       *sin6;
+    ngx_mail_in6_addr_t       *addr6;
+#endif
+
+
+    /* find the server configuration for the address:port */
+
+    port = c->listening->servers;
+
+    if (port->naddrs > 1) {
+
+        /*
+         * There are several addresses on this port and one of them
+         * is the "*:port" wildcard so getsockname() is needed to determine
+         * the server address.
+         *
+         * AcceptEx() already gave this address.
+         */
+
+        if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
+            ngx_mail_close_connection(c);
+            return;
+        }
+
+        sa = c->local_sockaddr;
+
+        switch (sa->sa_family) {
+
+#if (NGX_HAVE_INET6)
+        case AF_INET6:
+            sin6 = (struct sockaddr_in6 *) sa;
+
+            addr6 = port->addrs;
+
+            /* the last address is "*" */
+
+            for (i = 0; i < port->naddrs - 1; i++) {
+                if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) {
+                    break;
+                }
+            }
+
+            addr_conf = &addr6[i].conf;
+
+            break;
+#endif
+
+        default: /* AF_INET */
+            sin = (struct sockaddr_in *) sa;
+
+            addr = port->addrs;
+
+            /* the last address is "*" */
+
+            for (i = 0; i < port->naddrs - 1; i++) {
+                if (addr[i].addr == sin->sin_addr.s_addr) {
+                    break;
+                }
+            }
+
+            addr_conf = &addr[i].conf;
+
+            break;
+        }
+
+    } else {
+        switch (c->local_sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+        case AF_INET6:
+            addr6 = port->addrs;
+            addr_conf = &addr6[0].conf;
+            break;
+#endif
+
+        default: /* AF_INET */
+            addr = port->addrs;
+            addr_conf = &addr[0].conf;
+            break;
+        }
+    }
+
+    s = ngx_pcalloc(c->pool, sizeof(ngx_mail_session_t));
+    if (s == NULL) {
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    s->signature = NGX_MAIL_MODULE;
+
+    s->main_conf = addr_conf->ctx->main_conf;
+    s->srv_conf = addr_conf->ctx->srv_conf;
+
+    s->addr_text = &addr_conf->addr_text;
+
+    c->data = s;
+    s->connection = c;
+
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+    ngx_set_connection_log(c, cscf->error_log);
+
+    len = ngx_sock_ntop(c->sockaddr, c->socklen, text, NGX_SOCKADDR_STRLEN, 1);
+
+    ngx_log_error(NGX_LOG_INFO, c->log, 0, "*%uA client %*s connected to %V",
+                  c->number, len, text, s->addr_text);
+
+    ctx = ngx_palloc(c->pool, sizeof(ngx_mail_log_ctx_t));
+    if (ctx == NULL) {
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    ctx->client = &c->addr_text;
+    ctx->session = s;
+
+    c->log->connection = c->number;
+    c->log->handler = ngx_mail_log_error;
+    c->log->data = ctx;
+    c->log->action = "sending client greeting line";
+
+    c->log_error = NGX_ERROR_INFO;
+
+#if (NGX_MAIL_SSL)
+    {
+    ngx_mail_ssl_conf_t  *sslcf;
+
+    sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+    if (sslcf->enable) {
+        c->log->action = "SSL handshaking";
+
+        ngx_mail_ssl_init_connection(&sslcf->ssl, c);
+        return;
+    }
+
+    if (addr_conf->ssl) {
+
+        c->log->action = "SSL handshaking";
+
+        if (sslcf->ssl.ctx == NULL) {
+            ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                          "no \"ssl_certificate\" is defined "
+                          "in server listening on SSL port");
+            ngx_mail_close_connection(c);
+            return;
+        }
+
+        ngx_mail_ssl_init_connection(&sslcf->ssl, c);
+        return;
+    }
+
+    }
+#endif
+
+    ngx_mail_init_session(c);
+}
+
+
+#if (NGX_MAIL_SSL)
+
+void
+ngx_mail_starttls_handler(ngx_event_t *rev)
+{
+    ngx_connection_t     *c;
+    ngx_mail_session_t   *s;
+    ngx_mail_ssl_conf_t  *sslcf;
+
+    c = rev->data;
+    s = c->data;
+    s->starttls = 1;
+
+    c->log->action = "in starttls state";
+
+    sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+    ngx_mail_ssl_init_connection(&sslcf->ssl, c);
+}
+
+
+static void
+ngx_mail_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c)
+{
+    ngx_mail_session_t        *s;
+    ngx_mail_core_srv_conf_t  *cscf;
+
+    if (ngx_ssl_create_connection(ssl, c, 0) != NGX_OK) {
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    if (ngx_ssl_handshake(c) == NGX_AGAIN) {
+
+        s = c->data;
+
+        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;
+
+        return;
+    }
+
+    ngx_mail_ssl_handshake_handler(c);
+}
+
+
+static void
+ngx_mail_ssl_handshake_handler(ngx_connection_t *c)
+{
+    ngx_mail_session_t        *s;
+    ngx_mail_core_srv_conf_t  *cscf;
+
+    if (c->ssl->handshaked) {
+
+        s = c->data;
+
+        if (ngx_mail_verify_cert(s, c) != NGX_OK) {
+            return;
+        }
+
+        if (s->starttls) {
+            cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+            c->read->handler = cscf->protocol->init_protocol;
+            c->write->handler = ngx_mail_send;
+
+            cscf->protocol->init_protocol(c->read);
+
+            return;
+        }
+
+        c->read->ready = 0;
+
+        ngx_mail_init_session(c);
+        return;
+    }
+
+    ngx_mail_close_connection(c);
+}
+
+
+static ngx_int_t
+ngx_mail_verify_cert(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    long                       rc;
+    X509                      *cert;
+    ngx_mail_ssl_conf_t       *sslcf;
+    ngx_mail_core_srv_conf_t  *cscf;
+
+    sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+    if (!sslcf->verify) {
+        return NGX_OK;
+    }
+
+    rc = SSL_get_verify_result(c->ssl->connection);
+
+    if (rc != X509_V_OK
+        && (sslcf->verify != 3 || !ngx_ssl_verify_error_optional(rc)))
+    {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "client SSL certificate verify error: (%l:%s)",
+                      rc, X509_verify_cert_error_string(rc));
+
+        ngx_ssl_remove_cached_session(c->ssl->session_ctx,
+                                      (SSL_get0_session(c->ssl->connection)));
+
+        cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+        s->out = cscf->protocol->cert_error;
+        s->quit = 1;
+
+        c->write->handler = ngx_mail_send;
+
+        ngx_mail_send(s->connection->write);
+        return NGX_ERROR;
+    }
+
+    if (sslcf->verify == 1) {
+        cert = SSL_get_peer_certificate(c->ssl->connection);
+
+        if (cert == NULL) {
+            ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                          "client sent no required SSL certificate");
+
+            ngx_ssl_remove_cached_session(c->ssl->session_ctx,
+                                       (SSL_get0_session(c->ssl->connection)));
+
+            cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+            s->out = cscf->protocol->no_cert;
+            s->quit = 1;
+
+            c->write->handler = ngx_mail_send;
+
+            ngx_mail_send(s->connection->write);
+            return NGX_ERROR;
+        }
+
+        X509_free(cert);
+    }
+
+    return NGX_OK;
+}
+
+#endif
+
+
+static void
+ngx_mail_init_session(ngx_connection_t *c)
+{
+    ngx_mail_session_t        *s;
+    ngx_mail_core_srv_conf_t  *cscf;
+
+    s = c->data;
+
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+    s->protocol = cscf->protocol->type;
+
+    s->ctx = ngx_pcalloc(c->pool, sizeof(void *) * ngx_mail_max_module);
+    if (s->ctx == NULL) {
+        ngx_mail_session_internal_server_error(s);
+        return;
+    }
+
+    c->write->handler = ngx_mail_send;
+
+    cscf->protocol->init_session(s, c);
+}
+
+
+ngx_int_t
+ngx_mail_salt(ngx_mail_session_t *s, ngx_connection_t *c,
+    ngx_mail_core_srv_conf_t *cscf)
+{
+    s->salt.data = ngx_pnalloc(c->pool,
+                               sizeof(" <18446744073709551616.@>" CRLF) - 1
+                               + NGX_TIME_T_LEN
+                               + cscf->server_name.len);
+    if (s->salt.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    s->salt.len = ngx_sprintf(s->salt.data, "<%ul.%T@%V>" CRLF,
+                              ngx_random(), ngx_time(), &cscf->server_name)
+                  - s->salt.data;
+
+    return NGX_OK;
+}
+
+
+#if (NGX_MAIL_SSL)
+
+ngx_int_t
+ngx_mail_starttls_only(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_mail_ssl_conf_t  *sslcf;
+
+    if (c->ssl) {
+        return 0;
+    }
+
+    sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+    if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
+        return 1;
+    }
+
+    return 0;
+}
+
+#endif
+
+
+ngx_int_t
+ngx_mail_auth_plain(ngx_mail_session_t *s, ngx_connection_t *c, ngx_uint_t n)
+{
+    u_char     *p, *last;
+    ngx_str_t  *arg, plain;
+
+    arg = s->args.elts;
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth plain: \"%V\"", &arg[n]);
+#endif
+
+    plain.data = ngx_pnalloc(c->pool, ngx_base64_decoded_length(arg[n].len));
+    if (plain.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_decode_base64(&plain, &arg[n]) != NGX_OK) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+            "client sent invalid base64 encoding in AUTH PLAIN command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    p = plain.data;
+    last = p + plain.len;
+
+    while (p < last && *p++) { /* void */ }
+
+    if (p == last) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "client sent invalid login in AUTH PLAIN command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    s->login.data = p;
+
+    while (p < last && *p) { p++; }
+
+    if (p == last) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "client sent invalid password in AUTH PLAIN command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    s->login.len = p++ - s->login.data;
+
+    s->passwd.len = last - p;
+    s->passwd.data = p;
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+    ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth plain: \"%V\" \"%V\"", &s->login, &s->passwd);
+#endif
+
+    return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_mail_auth_login_username(ngx_mail_session_t *s, ngx_connection_t *c,
+    ngx_uint_t n)
+{
+    ngx_str_t  *arg;
+
+    arg = s->args.elts;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth login username: \"%V\"", &arg[n]);
+
+    s->login.data = ngx_pnalloc(c->pool, ngx_base64_decoded_length(arg[n].len));
+    if (s->login.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_decode_base64(&s->login, &arg[n]) != NGX_OK) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+            "client sent invalid base64 encoding in AUTH LOGIN command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth login username: \"%V\"", &s->login);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_mail_auth_login_password(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_str_t  *arg;
+
+    arg = s->args.elts;
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth login password: \"%V\"", &arg[0]);
+#endif
+
+    s->passwd.data = ngx_pnalloc(c->pool,
+                                 ngx_base64_decoded_length(arg[0].len));
+    if (s->passwd.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_decode_base64(&s->passwd, &arg[0]) != NGX_OK) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+            "client sent invalid base64 encoding in AUTH LOGIN command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth login password: \"%V\"", &s->passwd);
+#endif
+
+    return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_mail_auth_cram_md5_salt(ngx_mail_session_t *s, ngx_connection_t *c,
+    char *prefix, size_t len)
+{
+    u_char      *p;
+    ngx_str_t    salt;
+    ngx_uint_t   n;
+
+    p = ngx_pnalloc(c->pool, len + ngx_base64_encoded_length(s->salt.len) + 2);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    salt.data = ngx_cpymem(p, prefix, len);
+    s->salt.len -= 2;
+
+    ngx_encode_base64(&salt, &s->salt);
+
+    s->salt.len += 2;
+    n = len + salt.len;
+    p[n++] = CR; p[n++] = LF;
+
+    s->out.len = n;
+    s->out.data = p;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_mail_auth_cram_md5(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    u_char     *p, *last;
+    ngx_str_t  *arg;
+
+    arg = s->args.elts;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth cram-md5: \"%V\"", &arg[0]);
+
+    s->login.data = ngx_pnalloc(c->pool, ngx_base64_decoded_length(arg[0].len));
+    if (s->login.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_decode_base64(&s->login, &arg[0]) != NGX_OK) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+            "client sent invalid base64 encoding in AUTH CRAM-MD5 command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    p = s->login.data;
+    last = p + s->login.len;
+
+    while (p < last) {
+        if (*p++ == ' ') {
+            s->login.len = p - s->login.data - 1;
+            s->passwd.len = last - p;
+            s->passwd.data = p;
+            break;
+        }
+    }
+
+    if (s->passwd.len != 32) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+            "client sent invalid CRAM-MD5 hash in AUTH CRAM-MD5 command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth cram-md5: \"%V\" \"%V\"", &s->login, &s->passwd);
+
+    s->auth_method = NGX_MAIL_AUTH_CRAM_MD5;
+
+    return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_mail_auth_external(ngx_mail_session_t *s, ngx_connection_t *c,
+    ngx_uint_t n)
+{
+    ngx_str_t  *arg, external;
+
+    arg = s->args.elts;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth external: \"%V\"", &arg[n]);
+
+    external.data = ngx_pnalloc(c->pool, ngx_base64_decoded_length(arg[n].len));
+    if (external.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_decode_base64(&external, &arg[n]) != NGX_OK) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+            "client sent invalid base64 encoding in AUTH EXTERNAL command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    s->login.len = external.len;
+    s->login.data = external.data;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth external: \"%V\"", &s->login);
+
+    s->auth_method = NGX_MAIL_AUTH_EXTERNAL;
+
+    return NGX_DONE;
+}
+
+
+void
+ngx_mail_send(ngx_event_t *wev)
+{
+    ngx_int_t                  n;
+    ngx_connection_t          *c;
+    ngx_mail_session_t        *s;
+    ngx_mail_core_srv_conf_t  *cscf;
+
+    c = wev->data;
+    s = c->data;
+
+    if (wev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+        c->timedout = 1;
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    if (s->out.len == 0) {
+        if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+            ngx_mail_close_connection(c);
+        }
+
+        return;
+    }
+
+    n = c->send(c, s->out.data, s->out.len);
+
+    if (n > 0) {
+        s->out.data += n;
+        s->out.len -= n;
+
+        if (s->out.len != 0) {
+            goto again;
+        }
+
+        if (wev->timer_set) {
+            ngx_del_timer(wev);
+        }
+
+        if (s->quit) {
+            ngx_mail_close_connection(c);
+            return;
+        }
+
+        if (s->blocked) {
+            c->read->handler(c->read);
+        }
+
+        return;
+    }
+
+    if (n == NGX_ERROR) {
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    /* n == NGX_AGAIN */
+
+again:
+
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+    ngx_add_timer(c->write, cscf->timeout);
+
+    if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+        ngx_mail_close_connection(c);
+        return;
+    }
+}
+
+
+ngx_int_t
+ngx_mail_read_command(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ssize_t                    n;
+    ngx_int_t                  rc;
+    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 (n == NGX_ERROR || n == 0) {
+        ngx_mail_close_connection(c);
+        return NGX_ERROR;
+    }
+
+    if (n > 0) {
+        s->buffer->last += n;
+    }
+
+    if (n == NGX_AGAIN) {
+        if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+            ngx_mail_session_internal_server_error(s);
+            return NGX_ERROR;
+        }
+
+        if (s->buffer->pos == s->buffer->last) {
+            return NGX_AGAIN;
+        }
+    }
+
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+    rc = cscf->protocol->parse_command(s);
+
+    if (rc == NGX_AGAIN) {
+
+        if (s->buffer->last < s->buffer->end) {
+            return rc;
+        }
+
+        l.len = s->buffer->last - s->buffer->start;
+        l.data = s->buffer->start;
+
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "client sent too long command \"%V\"", &l);
+
+        s->quit = 1;
+
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    if (rc == NGX_IMAP_NEXT || rc == NGX_MAIL_PARSE_INVALID_COMMAND) {
+        return rc;
+    }
+
+    if (rc == NGX_ERROR) {
+        ngx_mail_close_connection(c);
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+void
+ngx_mail_auth(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    s->args.nelts = 0;
+
+    if (s->buffer->pos == s->buffer->last) {
+        s->buffer->pos = s->buffer->start;
+        s->buffer->last = s->buffer->start;
+    }
+
+    s->state = 0;
+
+    if (c->read->timer_set) {
+        ngx_del_timer(c->read);
+    }
+
+    s->login_attempt++;
+
+    ngx_mail_auth_http_init(s);
+}
+
+
+void
+ngx_mail_session_internal_server_error(ngx_mail_session_t *s)
+{
+    ngx_mail_core_srv_conf_t  *cscf;
+
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+    s->out = cscf->protocol->internal_server_error;
+    s->quit = 1;
+
+    ngx_mail_send(s->connection->write);
+}
+
+
+void
+ngx_mail_close_connection(ngx_connection_t *c)
+{
+    ngx_pool_t  *pool;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "close mail connection: %d", c->fd);
+
+#if (NGX_MAIL_SSL)
+
+    if (c->ssl) {
+        if (ngx_ssl_shutdown(c) == NGX_AGAIN) {
+            c->ssl->handler = ngx_mail_close_connection;
+            return;
+        }
+    }
+
+#endif
+
+#if (NGX_STAT_STUB)
+    (void) ngx_atomic_fetch_add(ngx_stat_active, -1);
+#endif
+
+    c->destroyed = 1;
+
+    pool = c->pool;
+
+    ngx_close_connection(c);
+
+    ngx_destroy_pool(pool);
+}
+
+
+u_char *
+ngx_mail_log_error(ngx_log_t *log, u_char *buf, size_t len)
+{
+    u_char              *p;
+    ngx_mail_session_t  *s;
+    ngx_mail_log_ctx_t  *ctx;
+
+    if (log->action) {
+        p = ngx_snprintf(buf, len, " while %s", log->action);
+        len -= p - buf;
+        buf = p;
+    }
+
+    ctx = log->data;
+
+    p = ngx_snprintf(buf, len, ", client: %V", ctx->client);
+    len -= p - buf;
+    buf = p;
+
+    s = ctx->session;
+
+    if (s == NULL) {
+        return p;
+    }
+
+    p = ngx_snprintf(buf, len, "%s, server: %V",
+                     s->starttls ? " using starttls" : "",
+                     s->addr_text);
+    len -= p - buf;
+    buf = p;
+
+    if (s->login.len == 0) {
+        return p;
+    }
+
+    p = ngx_snprintf(buf, len, ", login: \"%V\"", &s->login);
+    len -= p - buf;
+    buf = p;
+
+    if (s->proxy == NULL) {
+        return p;
+    }
+
+    p = ngx_snprintf(buf, len, ", upstream: %V", s->proxy->upstream.name);
+
+    return p;
+}
diff --git a/nginx/src/mail/ngx_mail_imap_handler.c b/nginx/src/mail/ngx_mail_imap_handler.c
new file mode 100644 (file)
index 0000000..3bf09ec
--- /dev/null
@@ -0,0 +1,472 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+#include <ngx_mail_imap_module.h>
+
+
+static ngx_int_t ngx_mail_imap_login(ngx_mail_session_t *s,
+    ngx_connection_t *c);
+static ngx_int_t ngx_mail_imap_authenticate(ngx_mail_session_t *s,
+    ngx_connection_t *c);
+static ngx_int_t ngx_mail_imap_capability(ngx_mail_session_t *s,
+    ngx_connection_t *c);
+static ngx_int_t ngx_mail_imap_starttls(ngx_mail_session_t *s,
+    ngx_connection_t *c);
+
+
+static u_char  imap_greeting[] = "* OK IMAP4 ready" CRLF;
+static u_char  imap_star[] = "* ";
+static u_char  imap_ok[] = "OK completed" CRLF;
+static u_char  imap_next[] = "+ OK" CRLF;
+static u_char  imap_plain_next[] = "+ " CRLF;
+static u_char  imap_username[] = "+ VXNlcm5hbWU6" CRLF;
+static u_char  imap_password[] = "+ UGFzc3dvcmQ6" CRLF;
+static u_char  imap_bye[] = "* BYE" CRLF;
+static u_char  imap_invalid_command[] = "BAD invalid command" CRLF;
+
+
+void
+ngx_mail_imap_init_session(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_mail_core_srv_conf_t  *cscf;
+
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+    ngx_str_set(&s->out, imap_greeting);
+
+    c->read->handler = ngx_mail_imap_init_protocol;
+
+    ngx_add_timer(c->read, cscf->timeout);
+
+    if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+        ngx_mail_close_connection(c);
+    }
+
+    ngx_mail_send(c->write);
+}
+
+
+void
+ngx_mail_imap_init_protocol(ngx_event_t *rev)
+{
+    ngx_connection_t          *c;
+    ngx_mail_session_t        *s;
+    ngx_mail_imap_srv_conf_t  *iscf;
+
+    c = rev->data;
+
+    c->log->action = "in auth state";
+
+    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;
+    }
+
+    s = c->data;
+
+    if (s->buffer == NULL) {
+        if (ngx_array_init(&s->args, c->pool, 2, sizeof(ngx_str_t))
+            == NGX_ERROR)
+        {
+            ngx_mail_session_internal_server_error(s);
+            return;
+        }
+
+        iscf = ngx_mail_get_module_srv_conf(s, ngx_mail_imap_module);
+
+        s->buffer = ngx_create_temp_buf(c->pool, iscf->client_buffer_size);
+        if (s->buffer == NULL) {
+            ngx_mail_session_internal_server_error(s);
+            return;
+        }
+    }
+
+    s->mail_state = ngx_imap_start;
+    c->read->handler = ngx_mail_imap_auth_state;
+
+    ngx_mail_imap_auth_state(rev);
+}
+
+
+void
+ngx_mail_imap_auth_state(ngx_event_t *rev)
+{
+    u_char              *p, *dst, *src, *end;
+    ngx_str_t           *arg;
+    ngx_int_t            rc;
+    ngx_uint_t           tag, i;
+    ngx_connection_t    *c;
+    ngx_mail_session_t  *s;
+
+    c = rev->data;
+    s = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "imap auth state");
+
+    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;
+    }
+
+    if (s->out.len) {
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "imap send handler busy");
+        s->blocked = 1;
+        return;
+    }
+
+    s->blocked = 0;
+
+    rc = ngx_mail_read_command(s, c);
+
+    if (rc == NGX_AGAIN || rc == NGX_ERROR) {
+        return;
+    }
+
+    tag = 1;
+    s->text.len = 0;
+    ngx_str_set(&s->out, imap_ok);
+
+    if (rc == NGX_OK) {
+
+        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:
+
+            switch (s->command) {
+
+            case NGX_IMAP_LOGIN:
+                rc = ngx_mail_imap_login(s, c);
+                break;
+
+            case NGX_IMAP_AUTHENTICATE:
+                rc = ngx_mail_imap_authenticate(s, c);
+                tag = (rc != NGX_OK);
+                break;
+
+            case NGX_IMAP_CAPABILITY:
+                rc = ngx_mail_imap_capability(s, c);
+                break;
+
+            case NGX_IMAP_LOGOUT:
+                s->quit = 1;
+                ngx_str_set(&s->text, imap_bye);
+                break;
+
+            case NGX_IMAP_NOOP:
+                break;
+
+            case NGX_IMAP_STARTTLS:
+                rc = ngx_mail_imap_starttls(s, c);
+                break;
+
+            default:
+                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
+                break;
+            }
+
+            break;
+
+        case ngx_imap_auth_login_username:
+            rc = ngx_mail_auth_login_username(s, c, 0);
+
+            tag = 0;
+            ngx_str_set(&s->out, imap_password);
+            s->mail_state = ngx_imap_auth_login_password;
+
+            break;
+
+        case ngx_imap_auth_login_password:
+            rc = ngx_mail_auth_login_password(s, c);
+            break;
+
+        case ngx_imap_auth_plain:
+            rc = ngx_mail_auth_plain(s, c, 0);
+            break;
+
+        case ngx_imap_auth_cram_md5:
+            rc = ngx_mail_auth_cram_md5(s, c);
+            break;
+
+        case ngx_imap_auth_external:
+            rc = ngx_mail_auth_external(s, c, 0);
+            break;
+        }
+
+    } else if (rc == NGX_IMAP_NEXT) {
+        tag = 0;
+        ngx_str_set(&s->out, imap_next);
+    }
+
+    switch (rc) {
+
+    case NGX_DONE:
+        ngx_mail_auth(s, c);
+        return;
+
+    case NGX_ERROR:
+        ngx_mail_session_internal_server_error(s);
+        return;
+
+    case NGX_MAIL_PARSE_INVALID_COMMAND:
+        s->state = 0;
+        ngx_str_set(&s->out, imap_invalid_command);
+        s->mail_state = ngx_imap_start;
+        break;
+    }
+
+    if (tag) {
+        if (s->tag.len == 0) {
+            ngx_str_set(&s->tag, imap_star);
+        }
+
+        if (s->tagged_line.len < s->tag.len + s->text.len + s->out.len) {
+            s->tagged_line.len = s->tag.len + s->text.len + s->out.len;
+            s->tagged_line.data = ngx_pnalloc(c->pool, s->tagged_line.len);
+            if (s->tagged_line.data == NULL) {
+                ngx_mail_close_connection(c);
+                return;
+            }
+        }
+
+        p = s->tagged_line.data;
+
+        if (s->text.len) {
+            p = ngx_cpymem(p, s->text.data, s->text.len);
+        }
+
+        p = ngx_cpymem(p, s->tag.data, s->tag.len);
+        ngx_memcpy(p, s->out.data, s->out.len);
+
+        s->out.len = s->text.len + s->tag.len + s->out.len;
+        s->out.data = s->tagged_line.data;
+    }
+
+    if (rc != NGX_IMAP_NEXT) {
+        s->args.nelts = 0;
+
+        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;
+
+        } else {
+            s->buffer->pos = s->buffer->start;
+            s->buffer->last = s->buffer->start;
+            s->tag.len = 0;
+        }
+    }
+
+    ngx_mail_send(c->write);
+}
+
+
+static ngx_int_t
+ngx_mail_imap_login(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_str_t  *arg;
+
+#if (NGX_MAIL_SSL)
+    if (ngx_mail_starttls_only(s, c)) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+#endif
+
+    arg = s->args.elts;
+
+    if (s->args.nelts != 2 || arg[0].len == 0) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    s->login.len = arg[0].len;
+    s->login.data = ngx_pnalloc(c->pool, s->login.len);
+    if (s->login.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(s->login.data, arg[0].data, s->login.len);
+
+    s->passwd.len = arg[1].len;
+    s->passwd.data = ngx_pnalloc(c->pool, s->passwd.len);
+    if (s->passwd.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(s->passwd.data, arg[1].data, s->passwd.len);
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+    ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "imap login:\"%V\" passwd:\"%V\"",
+                   &s->login, &s->passwd);
+#else
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "imap login:\"%V\"", &s->login);
+#endif
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_mail_imap_authenticate(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_int_t                  rc;
+    ngx_mail_core_srv_conf_t  *cscf;
+    ngx_mail_imap_srv_conf_t  *iscf;
+
+#if (NGX_MAIL_SSL)
+    if (ngx_mail_starttls_only(s, c)) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+#endif
+
+    iscf = ngx_mail_get_module_srv_conf(s, ngx_mail_imap_module);
+
+    rc = ngx_mail_auth_parse(s, c);
+
+    switch (rc) {
+
+    case NGX_MAIL_AUTH_LOGIN:
+
+        ngx_str_set(&s->out, imap_username);
+        s->mail_state = ngx_imap_auth_login_username;
+
+        return NGX_OK;
+
+    case NGX_MAIL_AUTH_LOGIN_USERNAME:
+
+        ngx_str_set(&s->out, imap_password);
+        s->mail_state = ngx_imap_auth_login_password;
+
+        return ngx_mail_auth_login_username(s, c, 1);
+
+    case NGX_MAIL_AUTH_PLAIN:
+
+        ngx_str_set(&s->out, imap_plain_next);
+        s->mail_state = ngx_imap_auth_plain;
+
+        return NGX_OK;
+
+    case NGX_MAIL_AUTH_CRAM_MD5:
+
+        if (!(iscf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED)) {
+            return NGX_MAIL_PARSE_INVALID_COMMAND;
+        }
+
+        if (s->salt.data == NULL) {
+            cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+            if (ngx_mail_salt(s, c, cscf) != NGX_OK) {
+                return NGX_ERROR;
+            }
+        }
+
+        if (ngx_mail_auth_cram_md5_salt(s, c, "+ ", 2) == NGX_OK) {
+            s->mail_state = ngx_imap_auth_cram_md5;
+            return NGX_OK;
+        }
+
+        return NGX_ERROR;
+
+    case NGX_MAIL_AUTH_EXTERNAL:
+
+        if (!(iscf->auth_methods & NGX_MAIL_AUTH_EXTERNAL_ENABLED)) {
+            return NGX_MAIL_PARSE_INVALID_COMMAND;
+        }
+
+        ngx_str_set(&s->out, imap_username);
+        s->mail_state = ngx_imap_auth_external;
+
+        return NGX_OK;
+    }
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_mail_imap_capability(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_mail_imap_srv_conf_t  *iscf;
+
+    iscf = ngx_mail_get_module_srv_conf(s, ngx_mail_imap_module);
+
+#if (NGX_MAIL_SSL)
+
+    if (c->ssl == NULL) {
+        ngx_mail_ssl_conf_t  *sslcf;
+
+        sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+        if (sslcf->starttls == NGX_MAIL_STARTTLS_ON) {
+            s->text = iscf->starttls_capability;
+            return NGX_OK;
+        }
+
+        if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
+            s->text = iscf->starttls_only_capability;
+            return NGX_OK;
+        }
+    }
+#endif
+
+    s->text = iscf->capability;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_imap_starttls(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+#if (NGX_MAIL_SSL)
+    ngx_mail_ssl_conf_t  *sslcf;
+
+    if (c->ssl == NULL) {
+        sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+        if (sslcf->starttls) {
+            c->read->handler = ngx_mail_starttls_handler;
+            return NGX_OK;
+        }
+    }
+
+#endif
+
+    return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
diff --git a/nginx/src/mail/ngx_mail_imap_module.c b/nginx/src/mail/ngx_mail_imap_module.c
new file mode 100644 (file)
index 0000000..1f187fd
--- /dev/null
@@ -0,0 +1,257 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+#include <ngx_mail_imap_module.h>
+
+
+static void *ngx_mail_imap_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_mail_imap_merge_srv_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+
+
+static ngx_str_t  ngx_mail_imap_default_capabilities[] = {
+    ngx_string("IMAP4"),
+    ngx_string("IMAP4rev1"),
+    ngx_string("UIDPLUS"),
+    ngx_null_string
+};
+
+
+static ngx_conf_bitmask_t  ngx_mail_imap_auth_methods[] = {
+    { ngx_string("plain"), NGX_MAIL_AUTH_PLAIN_ENABLED },
+    { ngx_string("login"), NGX_MAIL_AUTH_LOGIN_ENABLED },
+    { ngx_string("cram-md5"), NGX_MAIL_AUTH_CRAM_MD5_ENABLED },
+    { ngx_string("external"), NGX_MAIL_AUTH_EXTERNAL_ENABLED },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_str_t  ngx_mail_imap_auth_methods_names[] = {
+    ngx_string("AUTH=PLAIN"),
+    ngx_string("AUTH=LOGIN"),
+    ngx_null_string,  /* APOP */
+    ngx_string("AUTH=CRAM-MD5"),
+    ngx_string("AUTH=EXTERNAL"),
+    ngx_null_string   /* NONE */
+};
+
+
+static ngx_mail_protocol_t  ngx_mail_imap_protocol = {
+    ngx_string("imap"),
+    { 143, 993, 0, 0 },
+    NGX_MAIL_IMAP_PROTOCOL,
+
+    ngx_mail_imap_init_session,
+    ngx_mail_imap_init_protocol,
+    ngx_mail_imap_parse_command,
+    ngx_mail_imap_auth_state,
+
+    ngx_string("* BAD internal server error" CRLF),
+    ngx_string("* BYE SSL certificate error" CRLF),
+    ngx_string("* BYE No required SSL certificate" CRLF)
+};
+
+
+static ngx_command_t  ngx_mail_imap_commands[] = {
+
+    { ngx_string("imap_client_buffer"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_imap_srv_conf_t, client_buffer_size),
+      NULL },
+
+    { ngx_string("imap_capabilities"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+      ngx_mail_capabilities,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_imap_srv_conf_t, capabilities),
+      NULL },
+
+    { ngx_string("imap_auth"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_imap_srv_conf_t, auth_methods),
+      &ngx_mail_imap_auth_methods },
+
+      ngx_null_command
+};
+
+
+static ngx_mail_module_t  ngx_mail_imap_module_ctx = {
+    &ngx_mail_imap_protocol,               /* protocol */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_mail_imap_create_srv_conf,         /* create server configuration */
+    ngx_mail_imap_merge_srv_conf           /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_mail_imap_module = {
+    NGX_MODULE_V1,
+    &ngx_mail_imap_module_ctx,             /* module context */
+    ngx_mail_imap_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
+};
+
+
+static void *
+ngx_mail_imap_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_mail_imap_srv_conf_t  *iscf;
+
+    iscf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_imap_srv_conf_t));
+    if (iscf == NULL) {
+        return NULL;
+    }
+
+    iscf->client_buffer_size = NGX_CONF_UNSET_SIZE;
+
+    if (ngx_array_init(&iscf->capabilities, cf->pool, 4, sizeof(ngx_str_t))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    return iscf;
+}
+
+
+static char *
+ngx_mail_imap_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_mail_imap_srv_conf_t *prev = parent;
+    ngx_mail_imap_srv_conf_t *conf = child;
+
+    u_char      *p, *auth;
+    size_t       size;
+    ngx_str_t   *c, *d;
+    ngx_uint_t   i, m;
+
+    ngx_conf_merge_size_value(conf->client_buffer_size,
+                              prev->client_buffer_size,
+                              (size_t) ngx_pagesize);
+
+    ngx_conf_merge_bitmask_value(conf->auth_methods,
+                              prev->auth_methods,
+                              (NGX_CONF_BITMASK_SET
+                               |NGX_MAIL_AUTH_PLAIN_ENABLED));
+
+
+    if (conf->capabilities.nelts == 0) {
+        conf->capabilities = prev->capabilities;
+    }
+
+    if (conf->capabilities.nelts == 0) {
+
+        for (d = ngx_mail_imap_default_capabilities; d->len; d++) {
+            c = ngx_array_push(&conf->capabilities);
+            if (c == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            *c = *d;
+        }
+    }
+
+    size = sizeof("* CAPABILITY" CRLF) - 1;
+
+    c = conf->capabilities.elts;
+    for (i = 0; i < conf->capabilities.nelts; i++) {
+        size += 1 + c[i].len;
+    }
+
+    for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
+         m <= NGX_MAIL_AUTH_EXTERNAL_ENABLED;
+         m <<= 1, i++)
+    {
+        if (m & conf->auth_methods) {
+            size += 1 + ngx_mail_imap_auth_methods_names[i].len;
+        }
+    }
+
+    p = ngx_pnalloc(cf->pool, size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->capability.len = size;
+    conf->capability.data = p;
+
+    p = ngx_cpymem(p, "* CAPABILITY", sizeof("* CAPABILITY") - 1);
+
+    for (i = 0; i < conf->capabilities.nelts; i++) {
+        *p++ = ' ';
+        p = ngx_cpymem(p, c[i].data, c[i].len);
+    }
+
+    auth = p;
+
+    for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
+         m <= NGX_MAIL_AUTH_EXTERNAL_ENABLED;
+         m <<= 1, i++)
+    {
+        if (m & conf->auth_methods) {
+            *p++ = ' ';
+            p = ngx_cpymem(p, ngx_mail_imap_auth_methods_names[i].data,
+                           ngx_mail_imap_auth_methods_names[i].len);
+        }
+    }
+
+    *p++ = CR; *p = LF;
+
+
+    size += sizeof(" STARTTLS") - 1;
+
+    p = ngx_pnalloc(cf->pool, size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->starttls_capability.len = size;
+    conf->starttls_capability.data = p;
+
+    p = ngx_cpymem(p, conf->capability.data,
+                   conf->capability.len - (sizeof(CRLF) - 1));
+    p = ngx_cpymem(p, " STARTTLS", sizeof(" STARTTLS") - 1);
+    *p++ = CR; *p = LF;
+
+
+    size = (auth - conf->capability.data) + sizeof(CRLF) - 1
+            + sizeof(" STARTTLS LOGINDISABLED") - 1;
+
+    p = ngx_pnalloc(cf->pool, size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->starttls_only_capability.len = size;
+    conf->starttls_only_capability.data = p;
+
+    p = ngx_cpymem(p, conf->capability.data,
+                   auth - conf->capability.data);
+    p = ngx_cpymem(p, " STARTTLS LOGINDISABLED",
+                   sizeof(" STARTTLS LOGINDISABLED") - 1);
+    *p++ = CR; *p = LF;
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/mail/ngx_mail_imap_module.h b/nginx/src/mail/ngx_mail_imap_module.h
new file mode 100644 (file)
index 0000000..131b445
--- /dev/null
@@ -0,0 +1,39 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_MAIL_IMAP_MODULE_H_INCLUDED_
+#define _NGX_MAIL_IMAP_MODULE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_mail.h>
+
+
+typedef struct {
+    size_t       client_buffer_size;
+
+    ngx_str_t    capability;
+    ngx_str_t    starttls_capability;
+    ngx_str_t    starttls_only_capability;
+
+    ngx_uint_t   auth_methods;
+
+    ngx_array_t  capabilities;
+} ngx_mail_imap_srv_conf_t;
+
+
+void ngx_mail_imap_init_session(ngx_mail_session_t *s, ngx_connection_t *c);
+void ngx_mail_imap_init_protocol(ngx_event_t *rev);
+void ngx_mail_imap_auth_state(ngx_event_t *rev);
+ngx_int_t ngx_mail_imap_parse_command(ngx_mail_session_t *s);
+
+
+extern ngx_module_t  ngx_mail_imap_module;
+
+
+#endif /* _NGX_MAIL_IMAP_MODULE_H_INCLUDED_ */
diff --git a/nginx/src/mail/ngx_mail_parse.c b/nginx/src/mail/ngx_mail_parse.c
new file mode 100644 (file)
index 0000000..2c2cdff
--- /dev/null
@@ -0,0 +1,932 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+#include <ngx_mail_pop3_module.h>
+#include <ngx_mail_imap_module.h>
+#include <ngx_mail_smtp_module.h>
+
+
+ngx_int_t
+ngx_mail_pop3_parse_command(ngx_mail_session_t *s)
+{
+    u_char      ch, *p, *c, c0, c1, c2, c3;
+    ngx_str_t  *arg;
+    enum {
+        sw_start = 0,
+        sw_spaces_before_argument,
+        sw_argument,
+        sw_almost_done
+    } state;
+
+    state = s->state;
+
+    for (p = s->buffer->pos; p < s->buffer->last; p++) {
+        ch = *p;
+
+        switch (state) {
+
+        /* POP3 command */
+        case sw_start:
+            if (ch == ' ' || ch == CR || ch == LF) {
+                c = s->buffer->start;
+
+                if (p - c == 4) {
+
+                    c0 = ngx_toupper(c[0]);
+                    c1 = ngx_toupper(c[1]);
+                    c2 = ngx_toupper(c[2]);
+                    c3 = ngx_toupper(c[3]);
+
+                    if (c0 == 'U' && c1 == 'S' && c2 == 'E' && c3 == 'R')
+                    {
+                        s->command = NGX_POP3_USER;
+
+                    } else if (c0 == 'P' && c1 == 'A' && c2 == 'S' && c3 == 'S')
+                    {
+                        s->command = NGX_POP3_PASS;
+
+                    } else if (c0 == 'A' && c1 == 'P' && c2 == 'O' && c3 == 'P')
+                    {
+                        s->command = NGX_POP3_APOP;
+
+                    } else if (c0 == 'Q' && c1 == 'U' && c2 == 'I' && c3 == 'T')
+                    {
+                        s->command = NGX_POP3_QUIT;
+
+                    } else if (c0 == 'C' && c1 == 'A' && c2 == 'P' && c3 == 'A')
+                    {
+                        s->command = NGX_POP3_CAPA;
+
+                    } else if (c0 == 'A' && c1 == 'U' && c2 == 'T' && c3 == 'H')
+                    {
+                        s->command = NGX_POP3_AUTH;
+
+                    } else if (c0 == 'N' && c1 == 'O' && c2 == 'O' && c3 == 'P')
+                    {
+                        s->command = NGX_POP3_NOOP;
+#if (NGX_MAIL_SSL)
+                    } else if (c0 == 'S' && c1 == 'T' && c2 == 'L' && c3 == 'S')
+                    {
+                        s->command = NGX_POP3_STLS;
+#endif
+                    } else {
+                        goto invalid;
+                    }
+
+                } else {
+                    goto invalid;
+                }
+
+                switch (ch) {
+                case ' ':
+                    state = sw_spaces_before_argument;
+                    break;
+                case CR:
+                    state = sw_almost_done;
+                    break;
+                case LF:
+                    goto done;
+                }
+                break;
+            }
+
+            if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z')) {
+                goto invalid;
+            }
+
+            break;
+
+        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) {
+                    state = sw_argument;
+                    s->arg_start = p;
+                    break;
+                }
+                goto invalid;
+            }
+            break;
+
+        case sw_argument:
+            switch (ch) {
+
+            case ' ':
+
+                /*
+                 * the space should be considered as part of the at username
+                 * or password, but not of argument in other commands
+                 */
+
+                if (s->command == NGX_POP3_USER
+                    || s->command == NGX_POP3_PASS)
+                {
+                    break;
+                }
+
+                /* fall through */
+
+            case CR:
+            case LF:
+                arg = ngx_array_push(&s->args);
+                if (arg == NULL) {
+                    return NGX_ERROR;
+                }
+                arg->len = p - s->arg_start;
+                arg->data = s->arg_start;
+                s->arg_start = NULL;
+
+                switch (ch) {
+                case ' ':
+                    state = sw_spaces_before_argument;
+                    break;
+                case CR:
+                    state = sw_almost_done;
+                    break;
+                case LF:
+                    goto done;
+                }
+                break;
+
+            default:
+                break;
+            }
+            break;
+
+        case sw_almost_done:
+            switch (ch) {
+            case LF:
+                goto done;
+            default:
+                goto invalid;
+            }
+        }
+    }
+
+    s->buffer->pos = p;
+    s->state = state;
+
+    return NGX_AGAIN;
+
+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;
+
+    return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
+
+
+ngx_int_t
+ngx_mail_imap_parse_command(ngx_mail_session_t *s)
+{
+    u_char      ch, *p, *c;
+    ngx_str_t  *arg;
+    enum {
+        sw_start = 0,
+        sw_spaces_before_command,
+        sw_command,
+        sw_spaces_before_argument,
+        sw_argument,
+        sw_backslash,
+        sw_literal,
+        sw_no_sync_literal_argument,
+        sw_start_literal_argument,
+        sw_literal_argument,
+        sw_end_literal_argument,
+        sw_almost_done
+    } state;
+
+    state = s->state;
+
+    for (p = s->buffer->pos; p < s->buffer->last; p++) {
+        ch = *p;
+
+        switch (state) {
+
+        /* IMAP tag */
+        case sw_start:
+            switch (ch) {
+            case ' ':
+                s->tag.len = p - s->buffer->start + 1;
+                s->tag.data = s->buffer->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;
+            }
+            break;
+
+        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;
+            default:
+                s->cmd_start = p;
+                state = sw_command;
+                break;
+            }
+            break;
+
+        case sw_command:
+            if (ch == ' ' || ch == CR || ch == LF) {
+
+                c = s->cmd_start;
+
+                switch (p - c) {
+
+                case 4:
+                    if ((c[0] == 'N' || c[0] == 'n')
+                        && (c[1] == 'O'|| c[1] == 'o')
+                        && (c[2] == 'O'|| c[2] == 'o')
+                        && (c[3] == 'P'|| c[3] == 'p'))
+                    {
+                        s->command = NGX_IMAP_NOOP;
+
+                    } else {
+                        goto invalid;
+                    }
+                    break;
+
+                case 5:
+                    if ((c[0] == 'L'|| c[0] == 'l')
+                        && (c[1] == 'O'|| c[1] == 'o')
+                        && (c[2] == 'G'|| c[2] == 'g')
+                        && (c[3] == 'I'|| c[3] == 'i')
+                        && (c[4] == 'N'|| c[4] == 'n'))
+                    {
+                        s->command = NGX_IMAP_LOGIN;
+
+                    } else {
+                        goto invalid;
+                    }
+                    break;
+
+                case 6:
+                    if ((c[0] == 'L'|| c[0] == 'l')
+                        && (c[1] == 'O'|| c[1] == 'o')
+                        && (c[2] == 'G'|| c[2] == 'g')
+                        && (c[3] == 'O'|| c[3] == 'o')
+                        && (c[4] == 'U'|| c[4] == 'u')
+                        && (c[5] == 'T'|| c[5] == 't'))
+                    {
+                        s->command = NGX_IMAP_LOGOUT;
+
+                    } else {
+                        goto invalid;
+                    }
+                    break;
+
+#if (NGX_MAIL_SSL)
+                case 8:
+                    if ((c[0] == 'S'|| c[0] == 's')
+                        && (c[1] == 'T'|| c[1] == 't')
+                        && (c[2] == 'A'|| c[2] == 'a')
+                        && (c[3] == 'R'|| c[3] == 'r')
+                        && (c[4] == 'T'|| c[4] == 't')
+                        && (c[5] == 'T'|| c[5] == 't')
+                        && (c[6] == 'L'|| c[6] == 'l')
+                        && (c[7] == 'S'|| c[7] == 's'))
+                    {
+                        s->command = NGX_IMAP_STARTTLS;
+
+                    } else {
+                        goto invalid;
+                    }
+                    break;
+#endif
+
+                case 10:
+                    if ((c[0] == 'C'|| c[0] == 'c')
+                        && (c[1] == 'A'|| c[1] == 'a')
+                        && (c[2] == 'P'|| c[2] == 'p')
+                        && (c[3] == 'A'|| c[3] == 'a')
+                        && (c[4] == 'B'|| c[4] == 'b')
+                        && (c[5] == 'I'|| c[5] == 'i')
+                        && (c[6] == 'L'|| c[6] == 'l')
+                        && (c[7] == 'I'|| c[7] == 'i')
+                        && (c[8] == 'T'|| c[8] == 't')
+                        && (c[9] == 'Y'|| c[9] == 'y'))
+                    {
+                        s->command = NGX_IMAP_CAPABILITY;
+
+                    } else {
+                        goto invalid;
+                    }
+                    break;
+
+                case 12:
+                    if ((c[0] == 'A'|| c[0] == 'a')
+                        && (c[1] == 'U'|| c[1] == 'u')
+                        && (c[2] == 'T'|| c[2] == 't')
+                        && (c[3] == 'H'|| c[3] == 'h')
+                        && (c[4] == 'E'|| c[4] == 'e')
+                        && (c[5] == 'N'|| c[5] == 'n')
+                        && (c[6] == 'T'|| c[6] == 't')
+                        && (c[7] == 'I'|| c[7] == 'i')
+                        && (c[8] == 'C'|| c[8] == 'c')
+                        && (c[9] == 'A'|| c[9] == 'a')
+                        && (c[10] == 'T'|| c[10] == 't')
+                        && (c[11] == 'E'|| c[11] == 'e'))
+                    {
+                        s->command = NGX_IMAP_AUTHENTICATE;
+
+                    } else {
+                        goto invalid;
+                    }
+                    break;
+
+                default:
+                    goto invalid;
+                }
+
+                switch (ch) {
+                case ' ':
+                    state = sw_spaces_before_argument;
+                    break;
+                case CR:
+                    state = sw_almost_done;
+                    break;
+                case LF:
+                    goto done;
+                }
+                break;
+            }
+
+            if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z')) {
+                goto invalid;
+            }
+
+            break;
+
+        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;
+            case '"':
+                if (s->args.nelts <= 2) {
+                    s->quoted = 1;
+                    s->arg_start = p + 1;
+                    state = sw_argument;
+                    break;
+                }
+                goto invalid;
+            case '{':
+                if (s->args.nelts <= 2) {
+                    state = sw_literal;
+                    break;
+                }
+                goto invalid;
+            default:
+                if (s->args.nelts <= 2) {
+                    s->arg_start = p;
+                    state = sw_argument;
+                    break;
+                }
+                goto invalid;
+            }
+            break;
+
+        case sw_argument:
+            if (ch == ' ' && s->quoted) {
+                break;
+            }
+
+            switch (ch) {
+            case '"':
+                if (!s->quoted) {
+                    break;
+                }
+                s->quoted = 0;
+                /* fall through */
+            case ' ':
+            case CR:
+            case LF:
+                arg = ngx_array_push(&s->args);
+                if (arg == NULL) {
+                    return NGX_ERROR;
+                }
+                arg->len = p - s->arg_start;
+                arg->data = s->arg_start;
+                s->arg_start = NULL;
+
+                switch (ch) {
+                case '"':
+                case ' ':
+                    state = sw_spaces_before_argument;
+                    break;
+                case CR:
+                    state = sw_almost_done;
+                    break;
+                case LF:
+                    goto done;
+                }
+                break;
+            case '\\':
+                if (s->quoted) {
+                    s->backslash = 1;
+                    state = sw_backslash;
+                }
+                break;
+            }
+            break;
+
+        case sw_backslash:
+            switch (ch) {
+            case CR:
+            case LF:
+                goto invalid;
+            default:
+                state = sw_argument;
+            }
+            break;
+
+        case sw_literal:
+            if (ch >= '0' && ch <= '9') {
+                s->literal_len = s->literal_len * 10 + (ch - '0');
+                break;
+            }
+            if (ch == '}') {
+                state = sw_start_literal_argument;
+                break;
+            }
+            if (ch == '+') {
+                state = sw_no_sync_literal_argument;
+                break;
+            }
+            goto invalid;
+
+        case sw_no_sync_literal_argument:
+            if (ch == '}') {
+                s->no_sync_literal = 1;
+                state = sw_start_literal_argument;
+                break;
+            }
+            goto invalid;
+
+        case sw_start_literal_argument:
+            switch (ch) {
+            case CR:
+                break;
+            case LF:
+                s->buffer->pos = p + 1;
+                s->arg_start = p + 1;
+                if (s->no_sync_literal == 0) {
+                    s->state = sw_literal_argument;
+                    return NGX_IMAP_NEXT;
+                }
+                state = sw_literal_argument;
+                s->no_sync_literal = 0;
+                break;
+            default:
+                goto invalid;
+            }
+            break;
+
+        case sw_literal_argument:
+            if (s->literal_len && --s->literal_len) {
+                break;
+            }
+
+            arg = ngx_array_push(&s->args);
+            if (arg == NULL) {
+                return NGX_ERROR;
+            }
+            arg->len = p + 1 - s->arg_start;
+            arg->data = s->arg_start;
+            s->arg_start = NULL;
+            state = sw_end_literal_argument;
+
+            break;
+
+        case sw_end_literal_argument:
+            switch (ch) {
+            case '{':
+                if (s->args.nelts <= 2) {
+                    state = sw_literal;
+                    break;
+                }
+                goto invalid;
+            case CR:
+                state = sw_almost_done;
+                break;
+            case LF:
+                goto done;
+            default:
+                state = sw_spaces_before_argument;
+                break;
+            }
+            break;
+
+        case sw_almost_done:
+            switch (ch) {
+            case LF:
+                goto done;
+            default:
+                goto invalid;
+            }
+        }
+    }
+
+    s->buffer->pos = p;
+    s->state = state;
+
+    return NGX_AGAIN;
+
+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->quoted = 0;
+    s->no_sync_literal = 0;
+    s->literal_len = 0;
+
+    return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
+
+
+ngx_int_t
+ngx_mail_smtp_parse_command(ngx_mail_session_t *s)
+{
+    u_char      ch, *p, *c, c0, c1, c2, c3;
+    ngx_str_t  *arg;
+    enum {
+        sw_start = 0,
+        sw_command,
+        sw_invalid,
+        sw_spaces_before_argument,
+        sw_argument,
+        sw_almost_done
+    } state;
+
+    state = s->state;
+
+    for (p = s->buffer->pos; p < s->buffer->last; p++) {
+        ch = *p;
+
+        switch (state) {
+
+        /* SMTP command */
+        case sw_start:
+            s->cmd_start = p;
+            state = sw_command;
+
+            /* fall through */
+
+        case sw_command:
+            if (ch == ' ' || ch == CR || ch == LF) {
+                c = s->cmd_start;
+
+                if (p - c == 4) {
+
+                    c0 = ngx_toupper(c[0]);
+                    c1 = ngx_toupper(c[1]);
+                    c2 = ngx_toupper(c[2]);
+                    c3 = ngx_toupper(c[3]);
+
+                    if (c0 == 'H' && c1 == 'E' && c2 == 'L' && c3 == 'O')
+                    {
+                        s->command = NGX_SMTP_HELO;
+
+                    } else if (c0 == 'E' && c1 == 'H' && c2 == 'L' && c3 == 'O')
+                    {
+                        s->command = NGX_SMTP_EHLO;
+
+                    } else if (c0 == 'Q' && c1 == 'U' && c2 == 'I' && c3 == 'T')
+                    {
+                        s->command = NGX_SMTP_QUIT;
+
+                    } else if (c0 == 'A' && c1 == 'U' && c2 == 'T' && c3 == 'H')
+                    {
+                        s->command = NGX_SMTP_AUTH;
+
+                    } else if (c0 == 'N' && c1 == 'O' && c2 == 'O' && c3 == 'P')
+                    {
+                        s->command = NGX_SMTP_NOOP;
+
+                    } else if (c0 == 'M' && c1 == 'A' && c2 == 'I' && c3 == 'L')
+                    {
+                        s->command = NGX_SMTP_MAIL;
+
+                    } else if (c0 == 'R' && c1 == 'S' && c2 == 'E' && c3 == 'T')
+                    {
+                        s->command = NGX_SMTP_RSET;
+
+                    } else if (c0 == 'R' && c1 == 'C' && c2 == 'P' && c3 == 'T')
+                    {
+                        s->command = NGX_SMTP_RCPT;
+
+                    } else if (c0 == 'V' && c1 == 'R' && c2 == 'F' && c3 == 'Y')
+                    {
+                        s->command = NGX_SMTP_VRFY;
+
+                    } else if (c0 == 'E' && c1 == 'X' && c2 == 'P' && c3 == 'N')
+                    {
+                        s->command = NGX_SMTP_EXPN;
+
+                    } else if (c0 == 'H' && c1 == 'E' && c2 == 'L' && c3 == 'P')
+                    {
+                        s->command = NGX_SMTP_HELP;
+
+                    } else {
+                        goto invalid;
+                    }
+#if (NGX_MAIL_SSL)
+                } else if (p - c == 8) {
+
+                    if ((c[0] == 'S'|| c[0] == 's')
+                        && (c[1] == 'T'|| c[1] == 't')
+                        && (c[2] == 'A'|| c[2] == 'a')
+                        && (c[3] == 'R'|| c[3] == 'r')
+                        && (c[4] == 'T'|| c[4] == 't')
+                        && (c[5] == 'T'|| c[5] == 't')
+                        && (c[6] == 'L'|| c[6] == 'l')
+                        && (c[7] == 'S'|| c[7] == 's'))
+                    {
+                        s->command = NGX_SMTP_STARTTLS;
+
+                    } else {
+                        goto invalid;
+                    }
+#endif
+                } else {
+                    goto invalid;
+                }
+
+                s->cmd.data = s->cmd_start;
+                s->cmd.len = p - s->cmd_start;
+
+                switch (ch) {
+                case ' ':
+                    state = sw_spaces_before_argument;
+                    break;
+                case CR:
+                    state = sw_almost_done;
+                    break;
+                case LF:
+                    goto done;
+                }
+                break;
+            }
+
+            if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z')) {
+                goto invalid;
+            }
+
+            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 <= 10) {
+                    state = sw_argument;
+                    s->arg_start = p;
+                    break;
+                }
+                goto invalid;
+            }
+            break;
+
+        case sw_argument:
+            switch (ch) {
+            case ' ':
+            case CR:
+            case LF:
+                arg = ngx_array_push(&s->args);
+                if (arg == NULL) {
+                    return NGX_ERROR;
+                }
+                arg->len = p - s->arg_start;
+                arg->data = s->arg_start;
+                s->arg_start = NULL;
+
+                switch (ch) {
+                case ' ':
+                    state = sw_spaces_before_argument;
+                    break;
+                case CR:
+                    state = sw_almost_done;
+                    break;
+                case LF:
+                    goto done;
+                }
+                break;
+
+            default:
+                break;
+            }
+            break;
+
+        case sw_almost_done:
+            switch (ch) {
+            case LF:
+                goto done;
+            default:
+                goto invalid;
+            }
+        }
+    }
+
+    s->buffer->pos = p;
+    s->state = state;
+
+    return NGX_AGAIN;
+
+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;
+
+invalid:
+
+    s->state = sw_invalid;
+    s->arg_start = NULL;
+
+    /* skip invalid command till LF */
+
+    for (p = s->buffer->pos; p < s->buffer->last; p++) {
+        if (*p == LF) {
+            s->state = sw_start;
+            p++;
+            break;
+        }
+    }
+
+    s->buffer->pos = p;
+
+    return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
+
+
+ngx_int_t
+ngx_mail_auth_parse(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_str_t                 *arg;
+
+#if (NGX_MAIL_SSL)
+    if (ngx_mail_starttls_only(s, c)) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+#endif
+
+    if (s->args.nelts == 0) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    arg = s->args.elts;
+
+    if (arg[0].len == 5) {
+
+        if (ngx_strncasecmp(arg[0].data, (u_char *) "LOGIN", 5) == 0) {
+
+            if (s->args.nelts == 1) {
+                return NGX_MAIL_AUTH_LOGIN;
+            }
+
+            if (s->args.nelts == 2) {
+                return NGX_MAIL_AUTH_LOGIN_USERNAME;
+            }
+
+            return NGX_MAIL_PARSE_INVALID_COMMAND;
+        }
+
+        if (ngx_strncasecmp(arg[0].data, (u_char *) "PLAIN", 5) == 0) {
+
+            if (s->args.nelts == 1) {
+                return NGX_MAIL_AUTH_PLAIN;
+            }
+
+            if (s->args.nelts == 2) {
+                return ngx_mail_auth_plain(s, c, 1);
+            }
+        }
+
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    if (arg[0].len == 8) {
+
+        if (ngx_strncasecmp(arg[0].data, (u_char *) "CRAM-MD5", 8) == 0) {
+
+            if (s->args.nelts != 1) {
+                return NGX_MAIL_PARSE_INVALID_COMMAND;
+            }
+
+            return NGX_MAIL_AUTH_CRAM_MD5;
+        }
+
+        if (ngx_strncasecmp(arg[0].data, (u_char *) "EXTERNAL", 8) == 0) {
+
+            if (s->args.nelts == 1) {
+                return NGX_MAIL_AUTH_EXTERNAL;
+            }
+
+            if (s->args.nelts == 2) {
+                return ngx_mail_auth_external(s, c, 1);
+            }
+        }
+
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
diff --git a/nginx/src/mail/ngx_mail_pop3_handler.c b/nginx/src/mail/ngx_mail_pop3_handler.c
new file mode 100644 (file)
index 0000000..9310c27
--- /dev/null
@@ -0,0 +1,515 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+#include <ngx_mail_pop3_module.h>
+
+
+static ngx_int_t ngx_mail_pop3_user(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_pop3_pass(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_pop3_capa(ngx_mail_session_t *s, ngx_connection_t *c,
+    ngx_int_t stls);
+static ngx_int_t ngx_mail_pop3_stls(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_pop3_apop(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_pop3_auth(ngx_mail_session_t *s, ngx_connection_t *c);
+
+
+static u_char  pop3_greeting[] = "+OK POP3 ready" CRLF;
+static u_char  pop3_ok[] = "+OK" CRLF;
+static u_char  pop3_next[] = "+ " CRLF;
+static u_char  pop3_username[] = "+ VXNlcm5hbWU6" CRLF;
+static u_char  pop3_password[] = "+ UGFzc3dvcmQ6" CRLF;
+static u_char  pop3_invalid_command[] = "-ERR invalid command" CRLF;
+
+
+void
+ngx_mail_pop3_init_session(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    u_char                    *p;
+    ngx_mail_core_srv_conf_t  *cscf;
+    ngx_mail_pop3_srv_conf_t  *pscf;
+
+    pscf = ngx_mail_get_module_srv_conf(s, ngx_mail_pop3_module);
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+    if (pscf->auth_methods
+        & (NGX_MAIL_AUTH_APOP_ENABLED|NGX_MAIL_AUTH_CRAM_MD5_ENABLED))
+    {
+        if (ngx_mail_salt(s, c, cscf) != NGX_OK) {
+            ngx_mail_session_internal_server_error(s);
+            return;
+        }
+
+        s->out.data = ngx_pnalloc(c->pool, sizeof(pop3_greeting) + s->salt.len);
+        if (s->out.data == NULL) {
+            ngx_mail_session_internal_server_error(s);
+            return;
+        }
+
+        p = ngx_cpymem(s->out.data, pop3_greeting, sizeof(pop3_greeting) - 3);
+        *p++ = ' ';
+        p = ngx_cpymem(p, s->salt.data, s->salt.len);
+
+        s->out.len = p - s->out.data;
+
+    } else {
+        ngx_str_set(&s->out, pop3_greeting);
+    }
+
+    c->read->handler = ngx_mail_pop3_init_protocol;
+
+    ngx_add_timer(c->read, cscf->timeout);
+
+    if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+        ngx_mail_close_connection(c);
+    }
+
+    ngx_mail_send(c->write);
+}
+
+
+void
+ngx_mail_pop3_init_protocol(ngx_event_t *rev)
+{
+    ngx_connection_t    *c;
+    ngx_mail_session_t  *s;
+
+    c = rev->data;
+
+    c->log->action = "in auth state";
+
+    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;
+    }
+
+    s = c->data;
+
+    if (s->buffer == NULL) {
+        if (ngx_array_init(&s->args, c->pool, 2, sizeof(ngx_str_t))
+            == NGX_ERROR)
+        {
+            ngx_mail_session_internal_server_error(s);
+            return;
+        }
+
+        s->buffer = ngx_create_temp_buf(c->pool, 128);
+        if (s->buffer == NULL) {
+            ngx_mail_session_internal_server_error(s);
+            return;
+        }
+    }
+
+    s->mail_state = ngx_pop3_start;
+    c->read->handler = ngx_mail_pop3_auth_state;
+
+    ngx_mail_pop3_auth_state(rev);
+}
+
+
+void
+ngx_mail_pop3_auth_state(ngx_event_t *rev)
+{
+    ngx_int_t            rc;
+    ngx_connection_t    *c;
+    ngx_mail_session_t  *s;
+
+    c = rev->data;
+    s = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "pop3 auth state");
+
+    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;
+    }
+
+    if (s->out.len) {
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "pop3 send handler busy");
+        s->blocked = 1;
+        return;
+    }
+
+    s->blocked = 0;
+
+    rc = ngx_mail_read_command(s, c);
+
+    if (rc == NGX_AGAIN || rc == NGX_ERROR) {
+        return;
+    }
+
+    ngx_str_set(&s->out, pop3_ok);
+
+    if (rc == NGX_OK) {
+        switch (s->mail_state) {
+
+        case ngx_pop3_start:
+
+            switch (s->command) {
+
+            case NGX_POP3_USER:
+                rc = ngx_mail_pop3_user(s, c);
+                break;
+
+            case NGX_POP3_CAPA:
+                rc = ngx_mail_pop3_capa(s, c, 1);
+                break;
+
+            case NGX_POP3_APOP:
+                rc = ngx_mail_pop3_apop(s, c);
+                break;
+
+            case NGX_POP3_AUTH:
+                rc = ngx_mail_pop3_auth(s, c);
+                break;
+
+            case NGX_POP3_QUIT:
+                s->quit = 1;
+                break;
+
+            case NGX_POP3_NOOP:
+                break;
+
+            case NGX_POP3_STLS:
+                rc = ngx_mail_pop3_stls(s, c);
+                break;
+
+            default:
+                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
+                break;
+            }
+
+            break;
+
+        case ngx_pop3_user:
+
+            switch (s->command) {
+
+            case NGX_POP3_PASS:
+                rc = ngx_mail_pop3_pass(s, c);
+                break;
+
+            case NGX_POP3_CAPA:
+                rc = ngx_mail_pop3_capa(s, c, 0);
+                break;
+
+            case NGX_POP3_QUIT:
+                s->quit = 1;
+                break;
+
+            case NGX_POP3_NOOP:
+                break;
+
+            default:
+                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
+                break;
+            }
+
+            break;
+
+        /* suppress warnings */
+        case ngx_pop3_passwd:
+            break;
+
+        case ngx_pop3_auth_login_username:
+            rc = ngx_mail_auth_login_username(s, c, 0);
+
+            ngx_str_set(&s->out, pop3_password);
+            s->mail_state = ngx_pop3_auth_login_password;
+            break;
+
+        case ngx_pop3_auth_login_password:
+            rc = ngx_mail_auth_login_password(s, c);
+            break;
+
+        case ngx_pop3_auth_plain:
+            rc = ngx_mail_auth_plain(s, c, 0);
+            break;
+
+        case ngx_pop3_auth_cram_md5:
+            rc = ngx_mail_auth_cram_md5(s, c);
+            break;
+
+        case ngx_pop3_auth_external:
+            rc = ngx_mail_auth_external(s, c, 0);
+            break;
+        }
+    }
+
+    switch (rc) {
+
+    case NGX_DONE:
+        ngx_mail_auth(s, c);
+        return;
+
+    case NGX_ERROR:
+        ngx_mail_session_internal_server_error(s);
+        return;
+
+    case NGX_MAIL_PARSE_INVALID_COMMAND:
+        s->mail_state = ngx_pop3_start;
+        s->state = 0;
+
+        ngx_str_set(&s->out, pop3_invalid_command);
+
+        /* fall through */
+
+    case NGX_OK:
+
+        s->args.nelts = 0;
+        s->buffer->pos = s->buffer->start;
+        s->buffer->last = s->buffer->start;
+
+        if (s->state) {
+            s->arg_start = s->buffer->start;
+        }
+
+        ngx_mail_send(c->write);
+    }
+}
+
+static ngx_int_t
+ngx_mail_pop3_user(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_str_t  *arg;
+
+#if (NGX_MAIL_SSL)
+    if (ngx_mail_starttls_only(s, c)) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+#endif
+
+    if (s->args.nelts != 1) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    arg = s->args.elts;
+    s->login.len = arg[0].len;
+    s->login.data = ngx_pnalloc(c->pool, s->login.len);
+    if (s->login.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(s->login.data, arg[0].data, s->login.len);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "pop3 login: \"%V\"", &s->login);
+
+    s->mail_state = ngx_pop3_user;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_pop3_pass(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_str_t  *arg;
+
+    if (s->args.nelts != 1) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    arg = s->args.elts;
+    s->passwd.len = arg[0].len;
+    s->passwd.data = ngx_pnalloc(c->pool, s->passwd.len);
+    if (s->passwd.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(s->passwd.data, arg[0].data, s->passwd.len);
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "pop3 passwd: \"%V\"", &s->passwd);
+#endif
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_mail_pop3_capa(ngx_mail_session_t *s, ngx_connection_t *c, ngx_int_t stls)
+{
+    ngx_mail_pop3_srv_conf_t  *pscf;
+
+    pscf = ngx_mail_get_module_srv_conf(s, ngx_mail_pop3_module);
+
+#if (NGX_MAIL_SSL)
+
+    if (stls && c->ssl == NULL) {
+        ngx_mail_ssl_conf_t  *sslcf;
+
+        sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+        if (sslcf->starttls == NGX_MAIL_STARTTLS_ON) {
+            s->out = pscf->starttls_capability;
+            return NGX_OK;
+        }
+
+        if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
+            s->out = pscf->starttls_only_capability;
+            return NGX_OK;
+        }
+    }
+
+#endif
+
+    s->out = pscf->capability;
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_pop3_stls(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+#if (NGX_MAIL_SSL)
+    ngx_mail_ssl_conf_t  *sslcf;
+
+    if (c->ssl == NULL) {
+        sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+        if (sslcf->starttls) {
+            c->read->handler = ngx_mail_starttls_handler;
+            return NGX_OK;
+        }
+    }
+
+#endif
+
+    return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
+
+
+static ngx_int_t
+ngx_mail_pop3_apop(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_str_t                 *arg;
+    ngx_mail_pop3_srv_conf_t  *pscf;
+
+#if (NGX_MAIL_SSL)
+    if (ngx_mail_starttls_only(s, c)) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+#endif
+
+    if (s->args.nelts != 2) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    pscf = ngx_mail_get_module_srv_conf(s, ngx_mail_pop3_module);
+
+    if (!(pscf->auth_methods & NGX_MAIL_AUTH_APOP_ENABLED)) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    arg = s->args.elts;
+
+    s->login.len = arg[0].len;
+    s->login.data = ngx_pnalloc(c->pool, s->login.len);
+    if (s->login.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(s->login.data, arg[0].data, s->login.len);
+
+    s->passwd.len = arg[1].len;
+    s->passwd.data = ngx_pnalloc(c->pool, s->passwd.len);
+    if (s->passwd.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(s->passwd.data, arg[1].data, s->passwd.len);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "pop3 apop: \"%V\" \"%V\"", &s->login, &s->passwd);
+
+    s->auth_method = NGX_MAIL_AUTH_APOP;
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_mail_pop3_auth(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_int_t                  rc;
+    ngx_mail_pop3_srv_conf_t  *pscf;
+
+#if (NGX_MAIL_SSL)
+    if (ngx_mail_starttls_only(s, c)) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+#endif
+
+    pscf = ngx_mail_get_module_srv_conf(s, ngx_mail_pop3_module);
+
+    if (s->args.nelts == 0) {
+        s->out = pscf->auth_capability;
+        s->state = 0;
+
+        return NGX_OK;
+    }
+
+    rc = ngx_mail_auth_parse(s, c);
+
+    switch (rc) {
+
+    case NGX_MAIL_AUTH_LOGIN:
+
+        ngx_str_set(&s->out, pop3_username);
+        s->mail_state = ngx_pop3_auth_login_username;
+
+        return NGX_OK;
+
+    case NGX_MAIL_AUTH_LOGIN_USERNAME:
+
+        ngx_str_set(&s->out, pop3_password);
+        s->mail_state = ngx_pop3_auth_login_password;
+
+        return ngx_mail_auth_login_username(s, c, 1);
+
+    case NGX_MAIL_AUTH_PLAIN:
+
+        ngx_str_set(&s->out, pop3_next);
+        s->mail_state = ngx_pop3_auth_plain;
+
+        return NGX_OK;
+
+    case NGX_MAIL_AUTH_CRAM_MD5:
+
+        if (!(pscf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED)) {
+            return NGX_MAIL_PARSE_INVALID_COMMAND;
+        }
+
+        if (ngx_mail_auth_cram_md5_salt(s, c, "+ ", 2) == NGX_OK) {
+            s->mail_state = ngx_pop3_auth_cram_md5;
+            return NGX_OK;
+        }
+
+        return NGX_ERROR;
+
+    case NGX_MAIL_AUTH_EXTERNAL:
+
+        if (!(pscf->auth_methods & NGX_MAIL_AUTH_EXTERNAL_ENABLED)) {
+            return NGX_MAIL_PARSE_INVALID_COMMAND;
+        }
+
+        ngx_str_set(&s->out, pop3_username);
+        s->mail_state = ngx_pop3_auth_external;
+
+        return NGX_OK;
+    }
+
+    return rc;
+}
diff --git a/nginx/src/mail/ngx_mail_pop3_module.c b/nginx/src/mail/ngx_mail_pop3_module.c
new file mode 100644 (file)
index 0000000..a673070
--- /dev/null
@@ -0,0 +1,322 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+#include <ngx_mail_pop3_module.h>
+
+
+static void *ngx_mail_pop3_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_mail_pop3_merge_srv_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+
+
+static ngx_str_t  ngx_mail_pop3_default_capabilities[] = {
+    ngx_string("TOP"),
+    ngx_string("USER"),
+    ngx_string("UIDL"),
+    ngx_null_string
+};
+
+
+static ngx_conf_bitmask_t  ngx_mail_pop3_auth_methods[] = {
+    { ngx_string("plain"), NGX_MAIL_AUTH_PLAIN_ENABLED },
+    { ngx_string("apop"), NGX_MAIL_AUTH_APOP_ENABLED },
+    { ngx_string("cram-md5"), NGX_MAIL_AUTH_CRAM_MD5_ENABLED },
+    { ngx_string("external"), NGX_MAIL_AUTH_EXTERNAL_ENABLED },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_str_t  ngx_mail_pop3_auth_methods_names[] = {
+    ngx_string("PLAIN"),
+    ngx_string("LOGIN"),
+    ngx_null_string,  /* APOP */
+    ngx_string("CRAM-MD5"),
+    ngx_string("EXTERNAL"),
+    ngx_null_string   /* NONE */
+};
+
+
+static ngx_mail_protocol_t  ngx_mail_pop3_protocol = {
+    ngx_string("pop3"),
+    { 110, 995, 0, 0 },
+    NGX_MAIL_POP3_PROTOCOL,
+
+    ngx_mail_pop3_init_session,
+    ngx_mail_pop3_init_protocol,
+    ngx_mail_pop3_parse_command,
+    ngx_mail_pop3_auth_state,
+
+    ngx_string("-ERR internal server error" CRLF),
+    ngx_string("-ERR SSL certificate error" CRLF),
+    ngx_string("-ERR No required SSL certificate" CRLF)
+};
+
+
+static ngx_command_t  ngx_mail_pop3_commands[] = {
+
+    { ngx_string("pop3_capabilities"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+      ngx_mail_capabilities,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_pop3_srv_conf_t, capabilities),
+      NULL },
+
+    { ngx_string("pop3_auth"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_pop3_srv_conf_t, auth_methods),
+      &ngx_mail_pop3_auth_methods },
+
+      ngx_null_command
+};
+
+
+static ngx_mail_module_t  ngx_mail_pop3_module_ctx = {
+    &ngx_mail_pop3_protocol,               /* protocol */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_mail_pop3_create_srv_conf,         /* create server configuration */
+    ngx_mail_pop3_merge_srv_conf           /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_mail_pop3_module = {
+    NGX_MODULE_V1,
+    &ngx_mail_pop3_module_ctx,             /* module context */
+    ngx_mail_pop3_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
+};
+
+
+static void *
+ngx_mail_pop3_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_mail_pop3_srv_conf_t  *pscf;
+
+    pscf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_pop3_srv_conf_t));
+    if (pscf == NULL) {
+        return NULL;
+    }
+
+    if (ngx_array_init(&pscf->capabilities, cf->pool, 4, sizeof(ngx_str_t))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    return pscf;
+}
+
+
+static char *
+ngx_mail_pop3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_mail_pop3_srv_conf_t *prev = parent;
+    ngx_mail_pop3_srv_conf_t *conf = child;
+
+    u_char      *p;
+    size_t       size, stls_only_size;
+    ngx_str_t   *c, *d;
+    ngx_uint_t   i, m;
+
+    ngx_conf_merge_bitmask_value(conf->auth_methods,
+                                 prev->auth_methods,
+                                 (NGX_CONF_BITMASK_SET
+                                  |NGX_MAIL_AUTH_PLAIN_ENABLED));
+
+    if (conf->auth_methods & NGX_MAIL_AUTH_PLAIN_ENABLED) {
+        conf->auth_methods |= NGX_MAIL_AUTH_LOGIN_ENABLED;
+    }
+
+    if (conf->capabilities.nelts == 0) {
+        conf->capabilities = prev->capabilities;
+    }
+
+    if (conf->capabilities.nelts == 0) {
+
+        for (d = ngx_mail_pop3_default_capabilities; d->len; d++) {
+            c = ngx_array_push(&conf->capabilities);
+            if (c == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            *c = *d;
+        }
+    }
+
+    size = sizeof("+OK Capability list follows" CRLF) - 1
+           + sizeof("." CRLF) - 1;
+
+    stls_only_size = size + sizeof("STLS" CRLF) - 1;
+
+    c = conf->capabilities.elts;
+    for (i = 0; i < conf->capabilities.nelts; i++) {
+        size += c[i].len + sizeof(CRLF) - 1;
+
+        if (ngx_strcasecmp(c[i].data, (u_char *) "USER") == 0) {
+            continue;
+        }
+
+        stls_only_size += c[i].len + sizeof(CRLF) - 1;
+    }
+
+    size += sizeof("SASL") - 1 + sizeof(CRLF) - 1;
+
+    for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
+         m <= NGX_MAIL_AUTH_EXTERNAL_ENABLED;
+         m <<= 1, i++)
+    {
+        if (ngx_mail_pop3_auth_methods_names[i].len == 0) {
+            continue;
+        }
+
+        if (m & conf->auth_methods) {
+            size += 1 + ngx_mail_pop3_auth_methods_names[i].len;
+        }
+    }
+
+    p = ngx_pnalloc(cf->pool, size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->capability.len = size;
+    conf->capability.data = p;
+
+    p = ngx_cpymem(p, "+OK Capability list follows" CRLF,
+                   sizeof("+OK Capability list follows" CRLF) - 1);
+
+    for (i = 0; i < conf->capabilities.nelts; i++) {
+        p = ngx_cpymem(p, c[i].data, c[i].len);
+        *p++ = CR; *p++ = LF;
+    }
+
+    p = ngx_cpymem(p, "SASL", sizeof("SASL") - 1);
+
+    for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
+         m <= NGX_MAIL_AUTH_EXTERNAL_ENABLED;
+         m <<= 1, i++)
+    {
+        if (ngx_mail_pop3_auth_methods_names[i].len == 0) {
+            continue;
+        }
+
+        if (m & conf->auth_methods) {
+            *p++ = ' ';
+            p = ngx_cpymem(p, ngx_mail_pop3_auth_methods_names[i].data,
+                           ngx_mail_pop3_auth_methods_names[i].len);
+        }
+    }
+
+    *p++ = CR; *p++ = LF;
+
+    *p++ = '.'; *p++ = CR; *p = LF;
+
+
+    size += sizeof("STLS" CRLF) - 1;
+
+    p = ngx_pnalloc(cf->pool, size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->starttls_capability.len = size;
+    conf->starttls_capability.data = p;
+
+    p = ngx_cpymem(p, conf->capability.data,
+                   conf->capability.len - (sizeof("." CRLF) - 1));
+
+    p = ngx_cpymem(p, "STLS" CRLF, sizeof("STLS" CRLF) - 1);
+    *p++ = '.'; *p++ = CR; *p = LF;
+
+
+    size = sizeof("+OK methods supported:" CRLF) - 1
+           + sizeof("." CRLF) - 1;
+
+    for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
+         m <= NGX_MAIL_AUTH_EXTERNAL_ENABLED;
+         m <<= 1, i++)
+    {
+        if (ngx_mail_pop3_auth_methods_names[i].len == 0) {
+            continue;
+        }
+
+        if (m & conf->auth_methods) {
+            size += ngx_mail_pop3_auth_methods_names[i].len
+                    + sizeof(CRLF) - 1;
+        }
+    }
+
+    p = ngx_pnalloc(cf->pool, size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->auth_capability.data = p;
+    conf->auth_capability.len = size;
+
+    p = ngx_cpymem(p, "+OK methods supported:" CRLF,
+                   sizeof("+OK methods supported:" CRLF) - 1);
+
+    for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
+         m <= NGX_MAIL_AUTH_EXTERNAL_ENABLED;
+         m <<= 1, i++)
+    {
+        if (ngx_mail_pop3_auth_methods_names[i].len == 0) {
+            continue;
+        }
+
+        if (m & conf->auth_methods) {
+            p = ngx_cpymem(p, ngx_mail_pop3_auth_methods_names[i].data,
+                           ngx_mail_pop3_auth_methods_names[i].len);
+            *p++ = CR; *p++ = LF;
+        }
+    }
+
+    *p++ = '.'; *p++ = CR; *p = LF;
+
+
+    p = ngx_pnalloc(cf->pool, stls_only_size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->starttls_only_capability.len = stls_only_size;
+    conf->starttls_only_capability.data = p;
+
+    p = ngx_cpymem(p, "+OK Capability list follows" CRLF,
+                   sizeof("+OK Capability list follows" CRLF) - 1);
+
+    for (i = 0; i < conf->capabilities.nelts; i++) {
+        if (ngx_strcasecmp(c[i].data, (u_char *) "USER") == 0) {
+            continue;
+        }
+
+        p = ngx_cpymem(p, c[i].data, c[i].len);
+        *p++ = CR; *p++ = LF;
+    }
+
+    p = ngx_cpymem(p, "STLS" CRLF, sizeof("STLS" CRLF) - 1);
+    *p++ = '.'; *p++ = CR; *p = LF;
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/mail/ngx_mail_pop3_module.h b/nginx/src/mail/ngx_mail_pop3_module.h
new file mode 100644 (file)
index 0000000..86947a7
--- /dev/null
@@ -0,0 +1,38 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_MAIL_POP3_MODULE_H_INCLUDED_
+#define _NGX_MAIL_POP3_MODULE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_mail.h>
+
+
+typedef struct {
+    ngx_str_t    capability;
+    ngx_str_t    starttls_capability;
+    ngx_str_t    starttls_only_capability;
+    ngx_str_t    auth_capability;
+
+    ngx_uint_t   auth_methods;
+
+    ngx_array_t  capabilities;
+} ngx_mail_pop3_srv_conf_t;
+
+
+void ngx_mail_pop3_init_session(ngx_mail_session_t *s, ngx_connection_t *c);
+void ngx_mail_pop3_init_protocol(ngx_event_t *rev);
+void ngx_mail_pop3_auth_state(ngx_event_t *rev);
+ngx_int_t ngx_mail_pop3_parse_command(ngx_mail_session_t *s);
+
+
+extern ngx_module_t  ngx_mail_pop3_module;
+
+
+#endif /* _NGX_MAIL_POP3_MODULE_H_INCLUDED_ */
diff --git a/nginx/src/mail/ngx_mail_proxy_module.c b/nginx/src/mail/ngx_mail_proxy_module.c
new file mode 100644 (file)
index 0000000..1c86e54
--- /dev/null
@@ -0,0 +1,1126 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_connect.h>
+#include <ngx_mail.h>
+
+
+typedef struct {
+    ngx_flag_t  enable;
+    ngx_flag_t  pass_error_message;
+    ngx_flag_t  xclient;
+    size_t      buffer_size;
+    ngx_msec_t  timeout;
+} ngx_mail_proxy_conf_t;
+
+
+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 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);
+static void ngx_mail_proxy_upstream_error(ngx_mail_session_t *s);
+static void ngx_mail_proxy_internal_server_error(ngx_mail_session_t *s);
+static void ngx_mail_proxy_close_session(ngx_mail_session_t *s);
+static void *ngx_mail_proxy_create_conf(ngx_conf_t *cf);
+static char *ngx_mail_proxy_merge_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+
+
+static ngx_command_t  ngx_mail_proxy_commands[] = {
+
+    { ngx_string("proxy"),
+      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, enable),
+      NULL },
+
+    { ngx_string("proxy_buffer"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_proxy_conf_t, buffer_size),
+      NULL },
+
+    { ngx_string("proxy_timeout"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_proxy_conf_t, timeout),
+      NULL },
+
+    { ngx_string("proxy_pass_error_message"),
+      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, pass_error_message),
+      NULL },
+
+    { ngx_string("xclient"),
+      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, xclient),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_mail_module_t  ngx_mail_proxy_module_ctx = {
+    NULL,                                  /* protocol */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_mail_proxy_create_conf,            /* create server configuration */
+    ngx_mail_proxy_merge_conf              /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_mail_proxy_module = {
+    NGX_MODULE_V1,
+    &ngx_mail_proxy_module_ctx,            /* module context */
+    ngx_mail_proxy_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
+};
+
+
+static u_char  smtp_auth_ok[] = "235 2.0.0 OK" CRLF;
+
+
+void
+ngx_mail_proxy_init(ngx_mail_session_t *s, ngx_addr_t *peer)
+{
+    ngx_int_t                  rc;
+    ngx_mail_proxy_ctx_t      *p;
+    ngx_mail_proxy_conf_t     *pcf;
+    ngx_mail_core_srv_conf_t  *cscf;
+
+    s->connection->log->action = "connecting to upstream";
+
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+    p = ngx_pcalloc(s->connection->pool, sizeof(ngx_mail_proxy_ctx_t));
+    if (p == NULL) {
+        ngx_mail_session_internal_server_error(s);
+        return;
+    }
+
+    s->proxy = p;
+
+    p->upstream.sockaddr = peer->sockaddr;
+    p->upstream.socklen = peer->socklen;
+    p->upstream.name = &peer->name;
+    p->upstream.get = ngx_event_get_peer;
+    p->upstream.log = s->connection->log;
+    p->upstream.log_error = NGX_ERROR_ERR;
+
+    rc = ngx_event_connect_peer(&p->upstream);
+
+    if (rc == NGX_ERROR || rc == NGX_BUSY || rc == NGX_DECLINED) {
+        ngx_mail_proxy_internal_server_error(s);
+        return;
+    }
+
+    ngx_add_timer(p->upstream.connection->read, cscf->timeout);
+
+    p->upstream.connection->data = s;
+    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;
+
+    pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+
+    s->proxy->buffer = ngx_create_temp_buf(s->connection->pool,
+                                           pcf->buffer_size);
+    if (s->proxy->buffer == NULL) {
+        ngx_mail_proxy_internal_server_error(s);
+        return;
+    }
+
+    s->out.len = 0;
+
+    switch (s->protocol) {
+
+    case NGX_MAIL_POP3_PROTOCOL:
+        p->upstream.connection->read->handler = ngx_mail_proxy_pop3_handler;
+        s->mail_state = ngx_pop3_start;
+        break;
+
+    case NGX_MAIL_IMAP_PROTOCOL:
+        p->upstream.connection->read->handler = ngx_mail_proxy_imap_handler;
+        s->mail_state = ngx_imap_start;
+        break;
+
+    default: /* NGX_MAIL_SMTP_PROTOCOL */
+        p->upstream.connection->read->handler = ngx_mail_proxy_smtp_handler;
+        s->mail_state = ngx_smtp_start;
+        break;
+    }
+}
+
+
+static void
+ngx_mail_proxy_block_read(ngx_event_t *rev)
+{
+    ngx_connection_t    *c;
+    ngx_mail_session_t  *s;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, "mail proxy block read");
+
+    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+        c = rev->data;
+        s = c->data;
+
+        ngx_mail_proxy_close_session(s);
+    }
+}
+
+
+static void
+ngx_mail_proxy_pop3_handler(ngx_event_t *rev)
+{
+    u_char                 *p;
+    ngx_int_t               rc;
+    ngx_str_t               line;
+    ngx_connection_t       *c;
+    ngx_mail_session_t     *s;
+    ngx_mail_proxy_conf_t  *pcf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+                   "mail proxy pop3 auth handler");
+
+    c = rev->data;
+    s = c->data;
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+                      "upstream timed out");
+        c->timedout = 1;
+        ngx_mail_proxy_internal_server_error(s);
+        return;
+    }
+
+    rc = ngx_mail_proxy_read_response(s, 0);
+
+    if (rc == NGX_AGAIN) {
+        return;
+    }
+
+    if (rc == NGX_ERROR) {
+        ngx_mail_proxy_upstream_error(s);
+        return;
+    }
+
+    switch (s->mail_state) {
+
+    case ngx_pop3_start:
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, "mail proxy send user");
+
+        s->connection->log->action = "sending user name to upstream";
+
+        line.len = sizeof("USER ")  - 1 + s->login.len + 2;
+        line.data = ngx_pnalloc(c->pool, line.len);
+        if (line.data == NULL) {
+            ngx_mail_proxy_internal_server_error(s);
+            return;
+        }
+
+        p = ngx_cpymem(line.data, "USER ", sizeof("USER ") - 1);
+        p = ngx_cpymem(p, s->login.data, s->login.len);
+        *p++ = CR; *p = LF;
+
+        s->mail_state = ngx_pop3_user;
+        break;
+
+    case ngx_pop3_user:
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, "mail proxy send pass");
+
+        s->connection->log->action = "sending password to upstream";
+
+        line.len = sizeof("PASS ")  - 1 + s->passwd.len + 2;
+        line.data = ngx_pnalloc(c->pool, line.len);
+        if (line.data == NULL) {
+            ngx_mail_proxy_internal_server_error(s);
+            return;
+        }
+
+        p = ngx_cpymem(line.data, "PASS ", sizeof("PASS ") - 1);
+        p = ngx_cpymem(p, s->passwd.data, s->passwd.len);
+        *p++ = CR; *p = LF;
+
+        s->mail_state = ngx_pop3_passwd;
+        break;
+
+    case ngx_pop3_passwd:
+        s->connection->read->handler = ngx_mail_proxy_handler;
+        s->connection->write->handler = ngx_mail_proxy_handler;
+        rev->handler = ngx_mail_proxy_handler;
+        c->write->handler = ngx_mail_proxy_handler;
+
+        pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+        ngx_add_timer(s->connection->read, pcf->timeout);
+        ngx_del_timer(c->read);
+
+        c->log->action = NULL;
+        ngx_log_error(NGX_LOG_INFO, c->log, 0, "client logged in");
+
+        ngx_mail_proxy_handler(s->connection->write);
+
+        return;
+
+    default:
+#if (NGX_SUPPRESS_WARN)
+        ngx_str_null(&line);
+#endif
+        break;
+    }
+
+    if (c->send(c, line.data, line.len) < (ssize_t) line.len) {
+        /*
+         * we treat the incomplete sending as NGX_ERROR
+         * because it is very strange here
+         */
+        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_imap_handler(ngx_event_t *rev)
+{
+    u_char                 *p;
+    ngx_int_t               rc;
+    ngx_str_t               line;
+    ngx_connection_t       *c;
+    ngx_mail_session_t     *s;
+    ngx_mail_proxy_conf_t  *pcf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+                   "mail proxy imap auth handler");
+
+    c = rev->data;
+    s = c->data;
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+                      "upstream timed out");
+        c->timedout = 1;
+        ngx_mail_proxy_internal_server_error(s);
+        return;
+    }
+
+    rc = ngx_mail_proxy_read_response(s, s->mail_state);
+
+    if (rc == NGX_AGAIN) {
+        return;
+    }
+
+    if (rc == NGX_ERROR) {
+        ngx_mail_proxy_upstream_error(s);
+        return;
+    }
+
+    switch (s->mail_state) {
+
+    case ngx_imap_start:
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+                       "mail proxy send login");
+
+        s->connection->log->action = "sending LOGIN command to upstream";
+
+        line.len = s->tag.len + sizeof("LOGIN ") - 1
+                   + 1 + NGX_SIZE_T_LEN + 1 + 2;
+        line.data = ngx_pnalloc(c->pool, line.len);
+        if (line.data == NULL) {
+            ngx_mail_proxy_internal_server_error(s);
+            return;
+        }
+
+        line.len = ngx_sprintf(line.data, "%VLOGIN {%uz}" CRLF,
+                               &s->tag, s->login.len)
+                   - line.data;
+
+        s->mail_state = ngx_imap_login;
+        break;
+
+    case ngx_imap_login:
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, "mail proxy send user");
+
+        s->connection->log->action = "sending user name to upstream";
+
+        line.len = s->login.len + 1 + 1 + NGX_SIZE_T_LEN + 1 + 2;
+        line.data = ngx_pnalloc(c->pool, line.len);
+        if (line.data == NULL) {
+            ngx_mail_proxy_internal_server_error(s);
+            return;
+        }
+
+        line.len = ngx_sprintf(line.data, "%V {%uz}" CRLF,
+                               &s->login, s->passwd.len)
+                   - line.data;
+
+        s->mail_state = ngx_imap_user;
+        break;
+
+    case ngx_imap_user:
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+                       "mail proxy send passwd");
+
+        s->connection->log->action = "sending password to upstream";
+
+        line.len = s->passwd.len + 2;
+        line.data = ngx_pnalloc(c->pool, line.len);
+        if (line.data == NULL) {
+            ngx_mail_proxy_internal_server_error(s);
+            return;
+        }
+
+        p = ngx_cpymem(line.data, s->passwd.data, s->passwd.len);
+        *p++ = CR; *p = LF;
+
+        s->mail_state = ngx_imap_passwd;
+        break;
+
+    case ngx_imap_passwd:
+        s->connection->read->handler = ngx_mail_proxy_handler;
+        s->connection->write->handler = ngx_mail_proxy_handler;
+        rev->handler = ngx_mail_proxy_handler;
+        c->write->handler = ngx_mail_proxy_handler;
+
+        pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+        ngx_add_timer(s->connection->read, pcf->timeout);
+        ngx_del_timer(c->read);
+
+        c->log->action = NULL;
+        ngx_log_error(NGX_LOG_INFO, c->log, 0, "client logged in");
+
+        ngx_mail_proxy_handler(s->connection->write);
+
+        return;
+
+    default:
+#if (NGX_SUPPRESS_WARN)
+        ngx_str_null(&line);
+#endif
+        break;
+    }
+
+    if (c->send(c, line.data, line.len) < (ssize_t) line.len) {
+        /*
+         * we treat the incomplete sending as NGX_ERROR
+         * because it is very strange here
+         */
+        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_smtp_handler(ngx_event_t *rev)
+{
+    u_char                    *p;
+    ngx_int_t                  rc;
+    ngx_str_t                  line;
+    ngx_buf_t                 *b;
+    ngx_connection_t          *c;
+    ngx_mail_session_t        *s;
+    ngx_mail_proxy_conf_t     *pcf;
+    ngx_mail_core_srv_conf_t  *cscf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+                   "mail proxy smtp auth handler");
+
+    c = rev->data;
+    s = c->data;
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+                      "upstream timed out");
+        c->timedout = 1;
+        ngx_mail_proxy_internal_server_error(s);
+        return;
+    }
+
+    rc = ngx_mail_proxy_read_response(s, s->mail_state);
+
+    if (rc == NGX_AGAIN) {
+        return;
+    }
+
+    if (rc == NGX_ERROR) {
+        ngx_mail_proxy_upstream_error(s);
+        return;
+    }
+
+    switch (s->mail_state) {
+
+    case ngx_smtp_start:
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, "mail proxy send ehlo");
+
+        s->connection->log->action = "sending HELO/EHLO to upstream";
+
+        cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+        line.len = sizeof("HELO ")  - 1 + cscf->server_name.len + 2;
+        line.data = ngx_pnalloc(c->pool, line.len);
+        if (line.data == NULL) {
+            ngx_mail_proxy_internal_server_error(s);
+            return;
+        }
+
+        pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+
+        p = ngx_cpymem(line.data,
+                       ((s->esmtp || pcf->xclient) ? "EHLO " : "HELO "),
+                       sizeof("HELO ") - 1);
+
+        p = ngx_cpymem(p, cscf->server_name.data, cscf->server_name.len);
+        *p++ = CR; *p = LF;
+
+        if (pcf->xclient) {
+            s->mail_state = ngx_smtp_helo_xclient;
+
+        } else if (s->auth_method == NGX_MAIL_AUTH_NONE) {
+            s->mail_state = ngx_smtp_helo_from;
+
+        } else {
+            s->mail_state = ngx_smtp_helo;
+        }
+
+        break;
+
+    case ngx_smtp_helo_xclient:
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+                       "mail proxy send xclient");
+
+        s->connection->log->action = "sending XCLIENT to upstream";
+
+        line.len = sizeof("XCLIENT ADDR= LOGIN= NAME="
+                          CRLF) - 1
+                   + s->connection->addr_text.len + s->login.len + s->host.len;
+
+#if (NGX_HAVE_INET6)
+        if (s->connection->sockaddr->sa_family == AF_INET6) {
+            line.len += sizeof("IPV6:") - 1;
+        }
+#endif
+
+        line.data = ngx_pnalloc(c->pool, line.len);
+        if (line.data == NULL) {
+            ngx_mail_proxy_internal_server_error(s);
+            return;
+        }
+
+        p = ngx_cpymem(line.data, "XCLIENT ADDR=", sizeof("XCLIENT ADDR=") - 1);
+
+#if (NGX_HAVE_INET6)
+        if (s->connection->sockaddr->sa_family == AF_INET6) {
+            p = ngx_cpymem(p, "IPV6:", sizeof("IPV6:") - 1);
+        }
+#endif
+
+        p = ngx_copy(p, s->connection->addr_text.data,
+                     s->connection->addr_text.len);
+
+        if (s->login.len) {
+            p = ngx_cpymem(p, " LOGIN=", sizeof(" LOGIN=") - 1);
+            p = ngx_copy(p, s->login.data, s->login.len);
+        }
+
+        p = ngx_cpymem(p, " NAME=", sizeof(" NAME=") - 1);
+        p = ngx_copy(p, s->host.data, s->host.len);
+
+        *p++ = CR; *p++ = LF;
+
+        line.len = p - line.data;
+
+        if (s->smtp_helo.len) {
+            s->mail_state = ngx_smtp_xclient_helo;
+
+        } else if (s->auth_method == NGX_MAIL_AUTH_NONE) {
+            s->mail_state = ngx_smtp_xclient_from;
+
+        } else {
+            s->mail_state = ngx_smtp_xclient;
+        }
+
+        break;
+
+    case ngx_smtp_xclient_helo:
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+                       "mail proxy send client ehlo");
+
+        s->connection->log->action = "sending client HELO/EHLO to upstream";
+
+        line.len = sizeof("HELO " CRLF) - 1 + s->smtp_helo.len;
+
+        line.data = ngx_pnalloc(c->pool, line.len);
+        if (line.data == NULL) {
+            ngx_mail_proxy_internal_server_error(s);
+            return;
+        }
+
+        line.len = ngx_sprintf(line.data,
+                       ((s->esmtp) ? "EHLO %V" CRLF : "HELO %V" CRLF),
+                       &s->smtp_helo)
+                   - line.data;
+
+        s->mail_state = (s->auth_method == NGX_MAIL_AUTH_NONE) ?
+                            ngx_smtp_helo_from : ngx_smtp_helo;
+
+        break;
+
+    case ngx_smtp_helo_from:
+    case ngx_smtp_xclient_from:
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+                       "mail proxy send mail from");
+
+        s->connection->log->action = "sending MAIL FROM to upstream";
+
+        line.len = s->smtp_from.len + sizeof(CRLF) - 1;
+        line.data = ngx_pnalloc(c->pool, line.len);
+        if (line.data == NULL) {
+            ngx_mail_proxy_internal_server_error(s);
+            return;
+        }
+
+        p = ngx_cpymem(line.data, s->smtp_from.data, s->smtp_from.len);
+        *p++ = CR; *p = LF;
+
+        s->mail_state = ngx_smtp_from;
+
+        break;
+
+    case ngx_smtp_from:
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+                       "mail proxy send rcpt to");
+
+        s->connection->log->action = "sending RCPT TO to upstream";
+
+        line.len = s->smtp_to.len + sizeof(CRLF) - 1;
+        line.data = ngx_pnalloc(c->pool, line.len);
+        if (line.data == NULL) {
+            ngx_mail_proxy_internal_server_error(s);
+            return;
+        }
+
+        p = ngx_cpymem(line.data, s->smtp_to.data, s->smtp_to.len);
+        *p++ = CR; *p = LF;
+
+        s->mail_state = ngx_smtp_to;
+
+        break;
+
+    case ngx_smtp_helo:
+    case ngx_smtp_xclient:
+    case ngx_smtp_to:
+
+        b = s->proxy->buffer;
+
+        if (s->auth_method == NGX_MAIL_AUTH_NONE) {
+            b->pos = b->start;
+
+        } else {
+            ngx_memcpy(b->start, smtp_auth_ok, sizeof(smtp_auth_ok) - 1);
+            b->last = b->start + sizeof(smtp_auth_ok) - 1;
+        }
+
+        s->connection->read->handler = ngx_mail_proxy_handler;
+        s->connection->write->handler = ngx_mail_proxy_handler;
+        rev->handler = ngx_mail_proxy_handler;
+        c->write->handler = ngx_mail_proxy_handler;
+
+        pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+        ngx_add_timer(s->connection->read, pcf->timeout);
+        ngx_del_timer(c->read);
+
+        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);
+        }
+
+        return;
+
+    default:
+#if (NGX_SUPPRESS_WARN)
+        ngx_str_null(&line);
+#endif
+        break;
+    }
+
+    if (c->send(c, line.data, line.len) < (ssize_t) line.len) {
+        /*
+         * we treat the incomplete sending as NGX_ERROR
+         * because it is very strange here
+         */
+        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_connection_t    *c;
+    ngx_mail_session_t  *s;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, wev->log, 0, "mail proxy dummy handler");
+
+    if (ngx_handle_write_event(wev, 0) != NGX_OK) {
+        c = wev->data;
+        s = c->data;
+
+        ngx_mail_proxy_close_session(s);
+    }
+}
+
+
+static ngx_int_t
+ngx_mail_proxy_read_response(ngx_mail_session_t *s, ngx_uint_t state)
+{
+    u_char                 *p, *m;
+    ssize_t                 n;
+    ngx_buf_t              *b;
+    ngx_mail_proxy_conf_t  *pcf;
+
+    s->connection->log->action = "reading response from upstream";
+
+    b = s->proxy->buffer;
+
+    n = s->proxy->upstream.connection->recv(s->proxy->upstream.connection,
+                                            b->last, b->end - b->last);
+
+    if (n == NGX_ERROR || n == 0) {
+        return NGX_ERROR;
+    }
+
+    if (n == NGX_AGAIN) {
+        return NGX_AGAIN;
+    }
+
+    b->last += n;
+
+    if (b->last - b->pos < 4) {
+        return NGX_AGAIN;
+    }
+
+    if (*(b->last - 2) != CR || *(b->last - 1) != LF) {
+        if (b->last == b->end) {
+            *(b->last - 1) = '\0';
+            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                          "upstream sent too long response line: \"%s\"",
+                          b->pos);
+            return NGX_ERROR;
+        }
+
+        return NGX_AGAIN;
+    }
+
+    p = b->pos;
+
+    switch (s->protocol) {
+
+    case NGX_MAIL_POP3_PROTOCOL:
+        if (p[0] == '+' && p[1] == 'O' && p[2] == 'K') {
+            return NGX_OK;
+        }
+        break;
+
+    case NGX_MAIL_IMAP_PROTOCOL:
+        switch (state) {
+
+        case ngx_imap_start:
+            if (p[0] == '*' && p[1] == ' ' && p[2] == 'O' && p[3] == 'K') {
+                return NGX_OK;
+            }
+            break;
+
+        case ngx_imap_login:
+        case ngx_imap_user:
+            if (p[0] == '+') {
+                return NGX_OK;
+            }
+            break;
+
+        case ngx_imap_passwd:
+            if (ngx_strncmp(p, s->tag.data, s->tag.len) == 0) {
+                p += s->tag.len;
+                if (p[0] == 'O' && p[1] == 'K') {
+                    return NGX_OK;
+                }
+            }
+            break;
+        }
+
+        break;
+
+    default: /* NGX_MAIL_SMTP_PROTOCOL */
+
+        if (p[3] == '-') {
+            /* multiline reply, check if we got last line */
+
+            m = b->last - (sizeof(CRLF "200" CRLF) - 1);
+
+            while (m > p) {
+                if (m[0] == CR && m[1] == LF) {
+                    break;
+                }
+
+                m--;
+            }
+
+            if (m <= p || m[5] == '-') {
+                return NGX_AGAIN;
+            }
+        }
+
+        switch (state) {
+
+        case ngx_smtp_start:
+            if (p[0] == '2' && p[1] == '2' && p[2] == '0') {
+                return NGX_OK;
+            }
+            break;
+
+        case ngx_smtp_helo:
+        case ngx_smtp_helo_xclient:
+        case ngx_smtp_helo_from:
+        case ngx_smtp_from:
+            if (p[0] == '2' && p[1] == '5' && p[2] == '0') {
+                return NGX_OK;
+            }
+            break;
+
+        case ngx_smtp_xclient:
+        case ngx_smtp_xclient_from:
+        case ngx_smtp_xclient_helo:
+            if (p[0] == '2' && (p[1] == '2' || p[1] == '5') && p[2] == '0') {
+                return NGX_OK;
+            }
+            break;
+
+        case ngx_smtp_to:
+            return NGX_OK;
+        }
+
+        break;
+    }
+
+    pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+
+    if (pcf->pass_error_message == 0) {
+        *(b->last - 2) = '\0';
+        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                      "upstream sent invalid response: \"%s\"", p);
+        return NGX_ERROR;
+    }
+
+    s->out.len = b->last - p - 2;
+    s->out.data = p;
+
+    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
+                  "upstream sent invalid response: \"%V\"", &s->out);
+
+    s->out.len = b->last - b->pos;
+    s->out.data = b->pos;
+
+    return NGX_ERROR;
+}
+
+
+static void
+ngx_mail_proxy_handler(ngx_event_t *ev)
+{
+    char                   *action, *recv_action, *send_action;
+    size_t                  size;
+    ssize_t                 n;
+    ngx_buf_t              *b;
+    ngx_uint_t              do_write;
+    ngx_connection_t       *c, *src, *dst;
+    ngx_mail_session_t     *s;
+    ngx_mail_proxy_conf_t  *pcf;
+
+    c = ev->data;
+    s = c->data;
+
+    if (ev->timedout || c->close) {
+        c->log->action = "proxying";
+
+        if (c->close) {
+            ngx_log_error(NGX_LOG_INFO, c->log, 0, "shutdown timeout");
+
+        } else if (c == s->connection) {
+            ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+                          "client timed out");
+            c->timedout = 1;
+
+        } else {
+            ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+                          "upstream timed out");
+        }
+
+        ngx_mail_proxy_close_session(s);
+        return;
+    }
+
+    if (c == s->connection) {
+        if (ev->write) {
+            recv_action = "proxying and reading from upstream";
+            send_action = "proxying and sending to client";
+            src = s->proxy->upstream.connection;
+            dst = c;
+            b = s->proxy->buffer;
+
+        } else {
+            recv_action = "proxying and reading from client";
+            send_action = "proxying and sending to upstream";
+            src = c;
+            dst = s->proxy->upstream.connection;
+            b = s->buffer;
+        }
+
+    } else {
+        if (ev->write) {
+            recv_action = "proxying and reading from client";
+            send_action = "proxying and sending to upstream";
+            src = s->connection;
+            dst = c;
+            b = s->buffer;
+
+        } else {
+            recv_action = "proxying and reading from upstream";
+            send_action = "proxying and sending to client";
+            src = c;
+            dst = s->connection;
+            b = s->proxy->buffer;
+        }
+    }
+
+    do_write = ev->write ? 1 : 0;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_MAIL, ev->log, 0,
+                   "mail proxy handler: %ui, #%d > #%d",
+                   do_write, src->fd, dst->fd);
+
+    for ( ;; ) {
+
+        if (do_write) {
+
+            size = b->last - b->pos;
+
+            if (size && dst->write->ready) {
+                c->log->action = send_action;
+
+                n = dst->send(dst, b->pos, size);
+
+                if (n == NGX_ERROR) {
+                    ngx_mail_proxy_close_session(s);
+                    return;
+                }
+
+                if (n > 0) {
+                    b->pos += n;
+
+                    if (b->pos == b->last) {
+                        b->pos = b->start;
+                        b->last = b->start;
+                    }
+                }
+            }
+        }
+
+        size = b->end - b->last;
+
+        if (size && src->read->ready) {
+            c->log->action = recv_action;
+
+            n = src->recv(src, b->last, size);
+
+            if (n == NGX_AGAIN || n == 0) {
+                break;
+            }
+
+            if (n > 0) {
+                do_write = 1;
+                b->last += n;
+
+                continue;
+            }
+
+            if (n == NGX_ERROR) {
+                src->read->eof = 1;
+            }
+        }
+
+        break;
+    }
+
+    c->log->action = "proxying";
+
+    if ((s->connection->read->eof && s->buffer->pos == s->buffer->last)
+        || (s->proxy->upstream.connection->read->eof
+            && s->proxy->buffer->pos == s->proxy->buffer->last)
+        || (s->connection->read->eof
+            && s->proxy->upstream.connection->read->eof))
+    {
+        action = c->log->action;
+        c->log->action = NULL;
+        ngx_log_error(NGX_LOG_INFO, c->log, 0, "proxied session done");
+        c->log->action = action;
+
+        ngx_mail_proxy_close_session(s);
+        return;
+    }
+
+    if (ngx_handle_write_event(dst->write, 0) != NGX_OK) {
+        ngx_mail_proxy_close_session(s);
+        return;
+    }
+
+    if (ngx_handle_read_event(dst->read, 0) != NGX_OK) {
+        ngx_mail_proxy_close_session(s);
+        return;
+    }
+
+    if (ngx_handle_write_event(src->write, 0) != NGX_OK) {
+        ngx_mail_proxy_close_session(s);
+        return;
+    }
+
+    if (ngx_handle_read_event(src->read, 0) != NGX_OK) {
+        ngx_mail_proxy_close_session(s);
+        return;
+    }
+
+    if (c == s->connection) {
+        pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+        ngx_add_timer(c->read, pcf->timeout);
+    }
+}
+
+
+static void
+ngx_mail_proxy_upstream_error(ngx_mail_session_t *s)
+{
+    if (s->proxy->upstream.connection) {
+        ngx_log_debug1(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+                       "close mail proxy connection: %d",
+                       s->proxy->upstream.connection->fd);
+
+        ngx_close_connection(s->proxy->upstream.connection);
+    }
+
+    if (s->out.len == 0) {
+        ngx_mail_session_internal_server_error(s);
+        return;
+    }
+
+    s->quit = 1;
+    ngx_mail_send(s->connection->write);
+}
+
+
+static void
+ngx_mail_proxy_internal_server_error(ngx_mail_session_t *s)
+{
+    if (s->proxy->upstream.connection) {
+        ngx_log_debug1(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+                       "close mail proxy connection: %d",
+                       s->proxy->upstream.connection->fd);
+
+        ngx_close_connection(s->proxy->upstream.connection);
+    }
+
+    ngx_mail_session_internal_server_error(s);
+}
+
+
+static void
+ngx_mail_proxy_close_session(ngx_mail_session_t *s)
+{
+    if (s->proxy->upstream.connection) {
+        ngx_log_debug1(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+                       "close mail proxy connection: %d",
+                       s->proxy->upstream.connection->fd);
+
+        ngx_close_connection(s->proxy->upstream.connection);
+    }
+
+    ngx_mail_close_connection(s->connection);
+}
+
+
+static void *
+ngx_mail_proxy_create_conf(ngx_conf_t *cf)
+{
+    ngx_mail_proxy_conf_t  *pcf;
+
+    pcf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_proxy_conf_t));
+    if (pcf == NULL) {
+        return NULL;
+    }
+
+    pcf->enable = NGX_CONF_UNSET;
+    pcf->pass_error_message = NGX_CONF_UNSET;
+    pcf->xclient = NGX_CONF_UNSET;
+    pcf->buffer_size = NGX_CONF_UNSET_SIZE;
+    pcf->timeout = NGX_CONF_UNSET_MSEC;
+
+    return pcf;
+}
+
+
+static char *
+ngx_mail_proxy_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_mail_proxy_conf_t *prev = parent;
+    ngx_mail_proxy_conf_t *conf = 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_size_value(conf->buffer_size, prev->buffer_size,
+                              (size_t) ngx_pagesize);
+    ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 24 * 60 * 60000);
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/mail/ngx_mail_smtp_handler.c b/nginx/src/mail/ngx_mail_smtp_handler.c
new file mode 100644 (file)
index 0000000..939fb1a
--- /dev/null
@@ -0,0 +1,872 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+#include <ngx_mail_smtp_module.h>
+
+
+static void ngx_mail_smtp_resolve_addr_handler(ngx_resolver_ctx_t *ctx);
+static void ngx_mail_smtp_resolve_name(ngx_event_t *rev);
+static void ngx_mail_smtp_resolve_name_handler(ngx_resolver_ctx_t *ctx);
+static void ngx_mail_smtp_greeting(ngx_mail_session_t *s, ngx_connection_t *c);
+static void ngx_mail_smtp_invalid_pipelining(ngx_event_t *rev);
+static ngx_int_t ngx_mail_smtp_create_buffer(ngx_mail_session_t *s,
+    ngx_connection_t *c);
+
+static ngx_int_t ngx_mail_smtp_helo(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_smtp_auth(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_smtp_mail(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_smtp_starttls(ngx_mail_session_t *s,
+    ngx_connection_t *c);
+static ngx_int_t ngx_mail_smtp_rset(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_smtp_rcpt(ngx_mail_session_t *s, ngx_connection_t *c);
+
+static ngx_int_t ngx_mail_smtp_discard_command(ngx_mail_session_t *s,
+    ngx_connection_t *c, char *err);
+static void ngx_mail_smtp_log_rejected_command(ngx_mail_session_t *s,
+    ngx_connection_t *c, char *err);
+
+
+static u_char  smtp_ok[] = "250 2.0.0 OK" CRLF;
+static u_char  smtp_bye[] = "221 2.0.0 Bye" CRLF;
+static u_char  smtp_starttls[] = "220 2.0.0 Start TLS" CRLF;
+static u_char  smtp_next[] = "334 " CRLF;
+static u_char  smtp_username[] = "334 VXNlcm5hbWU6" CRLF;
+static u_char  smtp_password[] = "334 UGFzc3dvcmQ6" CRLF;
+static u_char  smtp_invalid_command[] = "500 5.5.1 Invalid command" CRLF;
+static u_char  smtp_invalid_pipelining[] =
+    "503 5.5.0 Improper use of SMTP command pipelining" CRLF;
+static u_char  smtp_invalid_argument[] = "501 5.5.4 Invalid argument" CRLF;
+static u_char  smtp_auth_required[] = "530 5.7.1 Authentication required" CRLF;
+static u_char  smtp_bad_sequence[] = "503 5.5.1 Bad sequence of commands" CRLF;
+
+
+static ngx_str_t  smtp_unavailable = ngx_string("[UNAVAILABLE]");
+static ngx_str_t  smtp_tempunavail = ngx_string("[TEMPUNAVAIL]");
+
+
+void
+ngx_mail_smtp_init_session(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_resolver_ctx_t        *ctx;
+    ngx_mail_core_srv_conf_t  *cscf;
+
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+    if (cscf->resolver == NULL) {
+        s->host = smtp_unavailable;
+        ngx_mail_smtp_greeting(s, c);
+        return;
+    }
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+    if (c->sockaddr->sa_family == AF_UNIX) {
+        s->host = smtp_tempunavail;
+        ngx_mail_smtp_greeting(s, c);
+        return;
+    }
+#endif
+
+    c->log->action = "in resolving client address";
+
+    ctx = ngx_resolve_start(cscf->resolver, NULL);
+    if (ctx == NULL) {
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    ctx->addr.sockaddr = c->sockaddr;
+    ctx->addr.socklen = c->socklen;
+    ctx->handler = ngx_mail_smtp_resolve_addr_handler;
+    ctx->data = s;
+    ctx->timeout = cscf->resolver_timeout;
+
+    if (ngx_resolve_addr(ctx) != NGX_OK) {
+        ngx_mail_close_connection(c);
+    }
+}
+
+
+static void
+ngx_mail_smtp_resolve_addr_handler(ngx_resolver_ctx_t *ctx)
+{
+    ngx_connection_t    *c;
+    ngx_mail_session_t  *s;
+
+    s = ctx->data;
+    c = s->connection;
+
+    if (ctx->state) {
+        ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                      "%V could not be resolved (%i: %s)",
+                      &c->addr_text, ctx->state,
+                      ngx_resolver_strerror(ctx->state));
+
+        if (ctx->state == NGX_RESOLVE_NXDOMAIN) {
+            s->host = smtp_unavailable;
+
+        } else {
+            s->host = smtp_tempunavail;
+        }
+
+        ngx_resolve_addr_done(ctx);
+
+        ngx_mail_smtp_greeting(s, s->connection);
+
+        return;
+    }
+
+    c->log->action = "in resolving client hostname";
+
+    s->host.data = ngx_pstrdup(c->pool, &ctx->name);
+    if (s->host.data == NULL) {
+        ngx_resolve_addr_done(ctx);
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    s->host.len = ctx->name.len;
+
+    ngx_resolve_addr_done(ctx);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "address resolved: %V", &s->host);
+
+    c->read->handler = ngx_mail_smtp_resolve_name;
+
+    ngx_post_event(c->read, &ngx_posted_events);
+}
+
+
+static void
+ngx_mail_smtp_resolve_name(ngx_event_t *rev)
+{
+    ngx_connection_t          *c;
+    ngx_mail_session_t        *s;
+    ngx_resolver_ctx_t        *ctx;
+    ngx_mail_core_srv_conf_t  *cscf;
+
+    c = rev->data;
+    s = c->data;
+
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+    ctx = ngx_resolve_start(cscf->resolver, NULL);
+    if (ctx == NULL) {
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    ctx->name = s->host;
+    ctx->handler = ngx_mail_smtp_resolve_name_handler;
+    ctx->data = s;
+    ctx->timeout = cscf->resolver_timeout;
+
+    if (ngx_resolve_name(ctx) != NGX_OK) {
+        ngx_mail_close_connection(c);
+    }
+}
+
+
+static void
+ngx_mail_smtp_resolve_name_handler(ngx_resolver_ctx_t *ctx)
+{
+    ngx_uint_t           i;
+    ngx_connection_t    *c;
+    ngx_mail_session_t  *s;
+
+    s = ctx->data;
+    c = s->connection;
+
+    if (ctx->state) {
+        ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                      "\"%V\" could not be resolved (%i: %s)",
+                      &ctx->name, ctx->state,
+                      ngx_resolver_strerror(ctx->state));
+
+        if (ctx->state == NGX_RESOLVE_NXDOMAIN) {
+            s->host = smtp_unavailable;
+
+        } else {
+            s->host = smtp_tempunavail;
+        }
+
+    } else {
+
+#if (NGX_DEBUG)
+        {
+        u_char     text[NGX_SOCKADDR_STRLEN];
+        ngx_str_t  addr;
+
+        addr.data = text;
+
+        for (i = 0; i < ctx->naddrs; i++) {
+            addr.len = ngx_sock_ntop(ctx->addrs[i].sockaddr,
+                                     ctx->addrs[i].socklen,
+                                     text, NGX_SOCKADDR_STRLEN, 0);
+
+            ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                           "name was resolved to %V", &addr);
+        }
+        }
+#endif
+
+        for (i = 0; i < ctx->naddrs; i++) {
+            if (ngx_cmp_sockaddr(ctx->addrs[i].sockaddr, ctx->addrs[i].socklen,
+                                 c->sockaddr, c->socklen, 0)
+                == NGX_OK)
+            {
+                goto found;
+            }
+        }
+
+        s->host = smtp_unavailable;
+    }
+
+found:
+
+    ngx_resolve_name_done(ctx);
+
+    ngx_mail_smtp_greeting(s, c);
+}
+
+
+static void
+ngx_mail_smtp_greeting(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_msec_t                 timeout;
+    ngx_mail_core_srv_conf_t  *cscf;
+    ngx_mail_smtp_srv_conf_t  *sscf;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "smtp greeting for \"%V\"", &s->host);
+
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+    sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+    timeout = sscf->greeting_delay ? sscf->greeting_delay : cscf->timeout;
+    ngx_add_timer(c->read, timeout);
+
+    if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+        ngx_mail_close_connection(c);
+    }
+
+    if (sscf->greeting_delay) {
+         c->read->handler = ngx_mail_smtp_invalid_pipelining;
+         return;
+    }
+
+    c->read->handler = ngx_mail_smtp_init_protocol;
+
+    s->out = sscf->greeting;
+
+    ngx_mail_send(c->write);
+}
+
+
+static void
+ngx_mail_smtp_invalid_pipelining(ngx_event_t *rev)
+{
+    ngx_connection_t          *c;
+    ngx_mail_session_t        *s;
+    ngx_mail_core_srv_conf_t  *cscf;
+    ngx_mail_smtp_srv_conf_t  *sscf;
+
+    c = rev->data;
+    s = c->data;
+
+    c->log->action = "in delay pipelining state";
+
+    if (rev->timedout) {
+
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "delay greeting");
+
+        rev->timedout = 0;
+
+        cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+        c->read->handler = ngx_mail_smtp_init_protocol;
+
+        ngx_add_timer(c->read, cscf->timeout);
+
+        if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+            ngx_mail_close_connection(c);
+            return;
+        }
+
+        sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+        s->out = sscf->greeting;
+
+    } else {
+
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "invalid pipelining");
+
+        if (s->buffer == NULL) {
+            if (ngx_mail_smtp_create_buffer(s, c) != NGX_OK) {
+                return;
+            }
+        }
+
+        if (ngx_mail_smtp_discard_command(s, c,
+                                "client was rejected before greeting: \"%V\"")
+            != NGX_OK)
+        {
+            return;
+        }
+
+        ngx_str_set(&s->out, smtp_invalid_pipelining);
+        s->quit = 1;
+    }
+
+    ngx_mail_send(c->write);
+}
+
+
+void
+ngx_mail_smtp_init_protocol(ngx_event_t *rev)
+{
+    ngx_connection_t    *c;
+    ngx_mail_session_t  *s;
+
+    c = rev->data;
+
+    c->log->action = "in auth state";
+
+    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;
+    }
+
+    s = c->data;
+
+    if (s->buffer == NULL) {
+        if (ngx_mail_smtp_create_buffer(s, c) != NGX_OK) {
+            return;
+        }
+    }
+
+    s->mail_state = ngx_smtp_start;
+    c->read->handler = ngx_mail_smtp_auth_state;
+
+    ngx_mail_smtp_auth_state(rev);
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_create_buffer(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_mail_smtp_srv_conf_t  *sscf;
+
+    if (ngx_array_init(&s->args, c->pool, 2, sizeof(ngx_str_t)) == NGX_ERROR) {
+        ngx_mail_session_internal_server_error(s);
+        return NGX_ERROR;
+    }
+
+    sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+    s->buffer = ngx_create_temp_buf(c->pool, sscf->client_buffer_size);
+    if (s->buffer == NULL) {
+        ngx_mail_session_internal_server_error(s);
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+void
+ngx_mail_smtp_auth_state(ngx_event_t *rev)
+{
+    ngx_int_t            rc;
+    ngx_connection_t    *c;
+    ngx_mail_session_t  *s;
+
+    c = rev->data;
+    s = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "smtp auth state");
+
+    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;
+    }
+
+    if (s->out.len) {
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "smtp send handler busy");
+        s->blocked = 1;
+        return;
+    }
+
+    s->blocked = 0;
+
+    rc = ngx_mail_read_command(s, c);
+
+    if (rc == NGX_AGAIN || rc == NGX_ERROR) {
+        return;
+    }
+
+    ngx_str_set(&s->out, smtp_ok);
+
+    if (rc == NGX_OK) {
+        switch (s->mail_state) {
+
+        case ngx_smtp_start:
+
+            switch (s->command) {
+
+            case NGX_SMTP_HELO:
+            case NGX_SMTP_EHLO:
+                rc = ngx_mail_smtp_helo(s, c);
+                break;
+
+            case NGX_SMTP_AUTH:
+                rc = ngx_mail_smtp_auth(s, c);
+                break;
+
+            case NGX_SMTP_QUIT:
+                s->quit = 1;
+                ngx_str_set(&s->out, smtp_bye);
+                break;
+
+            case NGX_SMTP_MAIL:
+                rc = ngx_mail_smtp_mail(s, c);
+                break;
+
+            case NGX_SMTP_RCPT:
+                rc = ngx_mail_smtp_rcpt(s, c);
+                break;
+
+            case NGX_SMTP_RSET:
+                rc = ngx_mail_smtp_rset(s, c);
+                break;
+
+            case NGX_SMTP_NOOP:
+                break;
+
+            case NGX_SMTP_STARTTLS:
+                rc = ngx_mail_smtp_starttls(s, c);
+                ngx_str_set(&s->out, smtp_starttls);
+                break;
+
+            default:
+                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
+                break;
+            }
+
+            break;
+
+        case ngx_smtp_auth_login_username:
+            rc = ngx_mail_auth_login_username(s, c, 0);
+
+            ngx_str_set(&s->out, smtp_password);
+            s->mail_state = ngx_smtp_auth_login_password;
+            break;
+
+        case ngx_smtp_auth_login_password:
+            rc = ngx_mail_auth_login_password(s, c);
+            break;
+
+        case ngx_smtp_auth_plain:
+            rc = ngx_mail_auth_plain(s, c, 0);
+            break;
+
+        case ngx_smtp_auth_cram_md5:
+            rc = ngx_mail_auth_cram_md5(s, c);
+            break;
+
+        case ngx_smtp_auth_external:
+            rc = ngx_mail_auth_external(s, c, 0);
+            break;
+        }
+    }
+
+    if (s->buffer->pos < s->buffer->last) {
+        s->blocked = 1;
+    }
+
+    switch (rc) {
+
+    case NGX_DONE:
+        ngx_mail_auth(s, c);
+        return;
+
+    case NGX_ERROR:
+        ngx_mail_session_internal_server_error(s);
+        return;
+
+    case NGX_MAIL_PARSE_INVALID_COMMAND:
+        s->mail_state = ngx_smtp_start;
+        s->state = 0;
+        ngx_str_set(&s->out, smtp_invalid_command);
+
+        /* fall through */
+
+    case NGX_OK:
+        s->args.nelts = 0;
+
+        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->pos;
+        }
+
+        ngx_mail_send(c->write);
+    }
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_helo(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_str_t                 *arg;
+    ngx_mail_smtp_srv_conf_t  *sscf;
+
+    if (s->args.nelts != 1) {
+        ngx_str_set(&s->out, smtp_invalid_argument);
+        s->state = 0;
+        return NGX_OK;
+    }
+
+    arg = s->args.elts;
+
+    s->smtp_helo.len = arg[0].len;
+
+    s->smtp_helo.data = ngx_pnalloc(c->pool, arg[0].len);
+    if (s->smtp_helo.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(s->smtp_helo.data, arg[0].data, arg[0].len);
+
+    ngx_str_null(&s->smtp_from);
+    ngx_str_null(&s->smtp_to);
+
+    sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+    if (s->command == NGX_SMTP_HELO) {
+        s->out = sscf->server_name;
+
+    } else {
+        s->esmtp = 1;
+
+#if (NGX_MAIL_SSL)
+
+        if (c->ssl == NULL) {
+            ngx_mail_ssl_conf_t  *sslcf;
+
+            sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+            if (sslcf->starttls == NGX_MAIL_STARTTLS_ON) {
+                s->out = sscf->starttls_capability;
+                return NGX_OK;
+            }
+
+            if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
+                s->out = sscf->starttls_only_capability;
+                return NGX_OK;
+            }
+        }
+#endif
+
+        s->out = sscf->capability;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_auth(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_int_t                  rc;
+    ngx_mail_core_srv_conf_t  *cscf;
+    ngx_mail_smtp_srv_conf_t  *sscf;
+
+#if (NGX_MAIL_SSL)
+    if (ngx_mail_starttls_only(s, c)) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+#endif
+
+    if (s->args.nelts == 0) {
+        ngx_str_set(&s->out, smtp_invalid_argument);
+        s->state = 0;
+        return NGX_OK;
+    }
+
+    sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+    rc = ngx_mail_auth_parse(s, c);
+
+    switch (rc) {
+
+    case NGX_MAIL_AUTH_LOGIN:
+
+        ngx_str_set(&s->out, smtp_username);
+        s->mail_state = ngx_smtp_auth_login_username;
+
+        return NGX_OK;
+
+    case NGX_MAIL_AUTH_LOGIN_USERNAME:
+
+        ngx_str_set(&s->out, smtp_password);
+        s->mail_state = ngx_smtp_auth_login_password;
+
+        return ngx_mail_auth_login_username(s, c, 1);
+
+    case NGX_MAIL_AUTH_PLAIN:
+
+        ngx_str_set(&s->out, smtp_next);
+        s->mail_state = ngx_smtp_auth_plain;
+
+        return NGX_OK;
+
+    case NGX_MAIL_AUTH_CRAM_MD5:
+
+        if (!(sscf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED)) {
+            return NGX_MAIL_PARSE_INVALID_COMMAND;
+        }
+
+        if (s->salt.data == NULL) {
+            cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+            if (ngx_mail_salt(s, c, cscf) != NGX_OK) {
+                return NGX_ERROR;
+            }
+        }
+
+        if (ngx_mail_auth_cram_md5_salt(s, c, "334 ", 4) == NGX_OK) {
+            s->mail_state = ngx_smtp_auth_cram_md5;
+            return NGX_OK;
+        }
+
+        return NGX_ERROR;
+
+    case NGX_MAIL_AUTH_EXTERNAL:
+
+        if (!(sscf->auth_methods & NGX_MAIL_AUTH_EXTERNAL_ENABLED)) {
+            return NGX_MAIL_PARSE_INVALID_COMMAND;
+        }
+
+        ngx_str_set(&s->out, smtp_username);
+        s->mail_state = ngx_smtp_auth_external;
+
+        return NGX_OK;
+    }
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_mail(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_str_t                 *arg, cmd;
+    ngx_mail_smtp_srv_conf_t  *sscf;
+
+    sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+    if (!(sscf->auth_methods & NGX_MAIL_AUTH_NONE_ENABLED)) {
+        ngx_mail_smtp_log_rejected_command(s, c, "client was rejected: \"%V\"");
+        ngx_str_set(&s->out, smtp_auth_required);
+        return NGX_OK;
+    }
+
+    /* auth none */
+
+    if (s->smtp_from.len) {
+        ngx_str_set(&s->out, smtp_bad_sequence);
+        return NGX_OK;
+    }
+
+    if (s->args.nelts == 0) {
+        ngx_str_set(&s->out, smtp_invalid_argument);
+        return NGX_OK;
+    }
+
+    arg = s->args.elts;
+    arg += s->args.nelts - 1;
+
+    cmd.len = arg->data + arg->len - s->cmd.data;
+    cmd.data = s->cmd.data;
+
+    s->smtp_from.len = cmd.len;
+
+    s->smtp_from.data = ngx_pnalloc(c->pool, cmd.len);
+    if (s->smtp_from.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(s->smtp_from.data, cmd.data, cmd.len);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "smtp mail from:\"%V\"", &s->smtp_from);
+
+    ngx_str_set(&s->out, smtp_ok);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_rcpt(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_str_t  *arg, cmd;
+
+    if (s->smtp_from.len == 0) {
+        ngx_str_set(&s->out, smtp_bad_sequence);
+        return NGX_OK;
+    }
+
+    if (s->args.nelts == 0) {
+        ngx_str_set(&s->out, smtp_invalid_argument);
+        return NGX_OK;
+    }
+
+    arg = s->args.elts;
+    arg += s->args.nelts - 1;
+
+    cmd.len = arg->data + arg->len - s->cmd.data;
+    cmd.data = s->cmd.data;
+
+    s->smtp_to.len = cmd.len;
+
+    s->smtp_to.data = ngx_pnalloc(c->pool, cmd.len);
+    if (s->smtp_to.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(s->smtp_to.data, cmd.data, cmd.len);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "smtp rcpt to:\"%V\"", &s->smtp_to);
+
+    s->auth_method = NGX_MAIL_AUTH_NONE;
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_rset(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_str_null(&s->smtp_from);
+    ngx_str_null(&s->smtp_to);
+    ngx_str_set(&s->out, smtp_ok);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_starttls(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+#if (NGX_MAIL_SSL)
+    ngx_mail_ssl_conf_t  *sslcf;
+
+    if (c->ssl == NULL) {
+        sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+        if (sslcf->starttls) {
+
+            /*
+             * RFC3207 requires us to discard any knowledge
+             * obtained from client before STARTTLS.
+             */
+
+            ngx_str_null(&s->smtp_helo);
+            ngx_str_null(&s->smtp_from);
+            ngx_str_null(&s->smtp_to);
+
+            s->buffer->pos = s->buffer->start;
+            s->buffer->last = s->buffer->start;
+
+            c->read->handler = ngx_mail_starttls_handler;
+            return NGX_OK;
+        }
+    }
+
+#endif
+
+    return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_discard_command(ngx_mail_session_t *s, ngx_connection_t *c,
+    char *err)
+{
+    ssize_t    n;
+
+    n = c->recv(c, s->buffer->last, s->buffer->end - s->buffer->last);
+
+    if (n == NGX_ERROR || n == 0) {
+        ngx_mail_close_connection(c);
+        return NGX_ERROR;
+    }
+
+    if (n > 0) {
+        s->buffer->last += n;
+    }
+
+    if (n == NGX_AGAIN) {
+        if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+            ngx_mail_session_internal_server_error(s);
+            return NGX_ERROR;
+        }
+
+        return NGX_AGAIN;
+    }
+
+    ngx_mail_smtp_log_rejected_command(s, c, err);
+
+    s->buffer->pos = s->buffer->start;
+    s->buffer->last = s->buffer->start;
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_mail_smtp_log_rejected_command(ngx_mail_session_t *s, ngx_connection_t *c,
+    char *err)
+{
+    u_char      ch;
+    ngx_str_t   cmd;
+    ngx_uint_t  i;
+
+    if (c->log->log_level < NGX_LOG_INFO) {
+        return;
+    }
+
+    cmd.len = s->buffer->last - s->buffer->start;
+    cmd.data = s->buffer->start;
+
+    for (i = 0; i < cmd.len; i++) {
+        ch = cmd.data[i];
+
+        if (ch != CR && ch != LF) {
+            continue;
+        }
+
+        cmd.data[i] = '_';
+    }
+
+    cmd.len = i;
+
+    ngx_log_error(NGX_LOG_INFO, c->log, 0, err, &cmd);
+}
diff --git a/nginx/src/mail/ngx_mail_smtp_module.c b/nginx/src/mail/ngx_mail_smtp_module.c
new file mode 100644 (file)
index 0000000..3b5a2d8
--- /dev/null
@@ -0,0 +1,311 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+#include <ngx_mail_smtp_module.h>
+
+
+static void *ngx_mail_smtp_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_mail_smtp_merge_srv_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+
+
+static ngx_conf_bitmask_t  ngx_mail_smtp_auth_methods[] = {
+    { ngx_string("plain"), NGX_MAIL_AUTH_PLAIN_ENABLED },
+    { ngx_string("login"), NGX_MAIL_AUTH_LOGIN_ENABLED },
+    { ngx_string("cram-md5"), NGX_MAIL_AUTH_CRAM_MD5_ENABLED },
+    { ngx_string("external"), NGX_MAIL_AUTH_EXTERNAL_ENABLED },
+    { ngx_string("none"), NGX_MAIL_AUTH_NONE_ENABLED },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_str_t  ngx_mail_smtp_auth_methods_names[] = {
+    ngx_string("PLAIN"),
+    ngx_string("LOGIN"),
+    ngx_null_string,  /* APOP */
+    ngx_string("CRAM-MD5"),
+    ngx_string("EXTERNAL"),
+    ngx_null_string   /* NONE */
+};
+
+
+static ngx_mail_protocol_t  ngx_mail_smtp_protocol = {
+    ngx_string("smtp"),
+    { 25, 465, 587, 0 },
+    NGX_MAIL_SMTP_PROTOCOL,
+
+    ngx_mail_smtp_init_session,
+    ngx_mail_smtp_init_protocol,
+    ngx_mail_smtp_parse_command,
+    ngx_mail_smtp_auth_state,
+
+    ngx_string("451 4.3.2 Internal server error" CRLF),
+    ngx_string("421 4.7.1 SSL certificate error" CRLF),
+    ngx_string("421 4.7.1 No required SSL certificate" CRLF)
+};
+
+
+static ngx_command_t  ngx_mail_smtp_commands[] = {
+
+    { ngx_string("smtp_client_buffer"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_smtp_srv_conf_t, client_buffer_size),
+      NULL },
+
+    { ngx_string("smtp_greeting_delay"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_smtp_srv_conf_t, greeting_delay),
+      NULL },
+
+    { ngx_string("smtp_capabilities"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+      ngx_mail_capabilities,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_smtp_srv_conf_t, capabilities),
+      NULL },
+
+    { ngx_string("smtp_auth"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_smtp_srv_conf_t, auth_methods),
+      &ngx_mail_smtp_auth_methods },
+
+      ngx_null_command
+};
+
+
+static ngx_mail_module_t  ngx_mail_smtp_module_ctx = {
+    &ngx_mail_smtp_protocol,               /* protocol */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_mail_smtp_create_srv_conf,         /* create server configuration */
+    ngx_mail_smtp_merge_srv_conf           /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_mail_smtp_module = {
+    NGX_MODULE_V1,
+    &ngx_mail_smtp_module_ctx,             /* module context */
+    ngx_mail_smtp_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
+};
+
+
+static void *
+ngx_mail_smtp_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_mail_smtp_srv_conf_t  *sscf;
+
+    sscf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_smtp_srv_conf_t));
+    if (sscf == NULL) {
+        return NULL;
+    }
+
+    sscf->client_buffer_size = NGX_CONF_UNSET_SIZE;
+    sscf->greeting_delay = NGX_CONF_UNSET_MSEC;
+
+    if (ngx_array_init(&sscf->capabilities, cf->pool, 4, sizeof(ngx_str_t))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    return sscf;
+}
+
+
+static char *
+ngx_mail_smtp_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_mail_smtp_srv_conf_t *prev = parent;
+    ngx_mail_smtp_srv_conf_t *conf = child;
+
+    u_char                    *p, *auth, *last;
+    size_t                     size;
+    ngx_str_t                 *c;
+    ngx_uint_t                 i, m, auth_enabled;
+    ngx_mail_core_srv_conf_t  *cscf;
+
+    ngx_conf_merge_size_value(conf->client_buffer_size,
+                              prev->client_buffer_size,
+                              (size_t) ngx_pagesize);
+
+    ngx_conf_merge_msec_value(conf->greeting_delay,
+                              prev->greeting_delay, 0);
+
+    ngx_conf_merge_bitmask_value(conf->auth_methods,
+                              prev->auth_methods,
+                              (NGX_CONF_BITMASK_SET
+                               |NGX_MAIL_AUTH_PLAIN_ENABLED
+                               |NGX_MAIL_AUTH_LOGIN_ENABLED));
+
+
+    cscf = ngx_mail_conf_get_module_srv_conf(cf, ngx_mail_core_module);
+
+    size = sizeof("220  ESMTP ready" CRLF) - 1 + cscf->server_name.len;
+
+    p = ngx_pnalloc(cf->pool, size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->greeting.len = size;
+    conf->greeting.data = p;
+
+    *p++ = '2'; *p++ = '2'; *p++ = '0'; *p++ = ' ';
+    p = ngx_cpymem(p, cscf->server_name.data, cscf->server_name.len);
+    ngx_memcpy(p, " ESMTP ready" CRLF, sizeof(" ESMTP ready" CRLF) - 1);
+
+
+    size = sizeof("250 " CRLF) - 1 + cscf->server_name.len;
+
+    p = ngx_pnalloc(cf->pool, size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->server_name.len = size;
+    conf->server_name.data = p;
+
+    *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = ' ';
+    p = ngx_cpymem(p, cscf->server_name.data, cscf->server_name.len);
+    *p++ = CR; *p = LF;
+
+
+    if (conf->capabilities.nelts == 0) {
+        conf->capabilities = prev->capabilities;
+    }
+
+    size = sizeof("250-") - 1 + cscf->server_name.len + sizeof(CRLF) - 1;
+
+    c = conf->capabilities.elts;
+    for (i = 0; i < conf->capabilities.nelts; i++) {
+        size += sizeof("250 ") - 1 + c[i].len + sizeof(CRLF) - 1;
+    }
+
+    auth_enabled = 0;
+
+    for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
+         m <= NGX_MAIL_AUTH_EXTERNAL_ENABLED;
+         m <<= 1, i++)
+    {
+        if (m & conf->auth_methods) {
+            size += 1 + ngx_mail_smtp_auth_methods_names[i].len;
+            auth_enabled = 1;
+        }
+    }
+
+    if (auth_enabled) {
+        size += sizeof("250 AUTH") - 1 + sizeof(CRLF) - 1;
+    }
+
+    p = ngx_pnalloc(cf->pool, size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->capability.len = size;
+    conf->capability.data = p;
+
+    last = p;
+
+    *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = '-';
+    p = ngx_cpymem(p, cscf->server_name.data, cscf->server_name.len);
+    *p++ = CR; *p++ = LF;
+
+    for (i = 0; i < conf->capabilities.nelts; i++) {
+        last = p;
+        *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = '-';
+        p = ngx_cpymem(p, c[i].data, c[i].len);
+        *p++ = CR; *p++ = LF;
+    }
+
+    auth = p;
+
+    if (auth_enabled) {
+        last = p;
+
+        *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = ' ';
+        *p++ = 'A'; *p++ = 'U'; *p++ = 'T'; *p++ = 'H';
+
+        for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
+             m <= NGX_MAIL_AUTH_EXTERNAL_ENABLED;
+             m <<= 1, i++)
+        {
+            if (m & conf->auth_methods) {
+                *p++ = ' ';
+                p = ngx_cpymem(p, ngx_mail_smtp_auth_methods_names[i].data,
+                               ngx_mail_smtp_auth_methods_names[i].len);
+            }
+        }
+
+        *p++ = CR; *p = LF;
+
+    } else {
+        last[3] = ' ';
+    }
+
+    size += sizeof("250 STARTTLS" CRLF) - 1;
+
+    p = ngx_pnalloc(cf->pool, size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->starttls_capability.len = size;
+    conf->starttls_capability.data = p;
+
+    p = ngx_cpymem(p, conf->capability.data, conf->capability.len);
+
+    ngx_memcpy(p, "250 STARTTLS" CRLF, sizeof("250 STARTTLS" CRLF) - 1);
+
+    p = conf->starttls_capability.data
+        + (last - conf->capability.data) + 3;
+    *p = '-';
+
+    size = (auth - conf->capability.data)
+            + sizeof("250 STARTTLS" CRLF) - 1;
+
+    p = ngx_pnalloc(cf->pool, size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->starttls_only_capability.len = size;
+    conf->starttls_only_capability.data = p;
+
+    p = ngx_cpymem(p, conf->capability.data, auth - conf->capability.data);
+
+    ngx_memcpy(p, "250 STARTTLS" CRLF, sizeof("250 STARTTLS" CRLF) - 1);
+
+    if (last < auth) {
+        p = conf->starttls_only_capability.data
+            + (last - conf->capability.data) + 3;
+        *p = '-';
+    }
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/mail/ngx_mail_smtp_module.h b/nginx/src/mail/ngx_mail_smtp_module.h
new file mode 100644 (file)
index 0000000..04ffab6
--- /dev/null
@@ -0,0 +1,45 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_MAIL_SMTP_MODULE_H_INCLUDED_
+#define _NGX_MAIL_SMTP_MODULE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_mail.h>
+#include <ngx_mail_smtp_module.h>
+
+
+typedef struct {
+    ngx_msec_t   greeting_delay;
+
+    size_t       client_buffer_size;
+
+    ngx_str_t    capability;
+    ngx_str_t    starttls_capability;
+    ngx_str_t    starttls_only_capability;
+
+    ngx_str_t    server_name;
+    ngx_str_t    greeting;
+
+    ngx_uint_t   auth_methods;
+
+    ngx_array_t  capabilities;
+} ngx_mail_smtp_srv_conf_t;
+
+
+void ngx_mail_smtp_init_session(ngx_mail_session_t *s, ngx_connection_t *c);
+void ngx_mail_smtp_init_protocol(ngx_event_t *rev);
+void ngx_mail_smtp_auth_state(ngx_event_t *rev);
+ngx_int_t ngx_mail_smtp_parse_command(ngx_mail_session_t *s);
+
+
+extern ngx_module_t  ngx_mail_smtp_module;
+
+
+#endif /* _NGX_MAIL_SMTP_MODULE_H_INCLUDED_ */
diff --git a/nginx/src/mail/ngx_mail_ssl_module.c b/nginx/src/mail/ngx_mail_ssl_module.c
new file mode 100644 (file)
index 0000000..aebd179
--- /dev/null
@@ -0,0 +1,662 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_mail.h>
+
+
+#define NGX_DEFAULT_CIPHERS     "HIGH:!aNULL:!MD5"
+#define NGX_DEFAULT_ECDH_CURVE  "auto"
+
+
+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);
+
+static char *ngx_mail_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_mail_ssl_starttls(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_mail_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_mail_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+
+static ngx_conf_enum_t  ngx_mail_starttls_state[] = {
+    { ngx_string("off"), NGX_MAIL_STARTTLS_OFF },
+    { ngx_string("on"), NGX_MAIL_STARTTLS_ON },
+    { ngx_string("only"), NGX_MAIL_STARTTLS_ONLY },
+    { ngx_null_string, 0 }
+};
+
+
+
+static ngx_conf_bitmask_t  ngx_mail_ssl_protocols[] = {
+    { ngx_string("SSLv2"), NGX_SSL_SSLv2 },
+    { ngx_string("SSLv3"), NGX_SSL_SSLv3 },
+    { ngx_string("TLSv1"), NGX_SSL_TLSv1 },
+    { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 },
+    { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 },
+    { ngx_string("TLSv1.3"), NGX_SSL_TLSv1_3 },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_enum_t  ngx_mail_ssl_verify[] = {
+    { ngx_string("off"), 0 },
+    { ngx_string("on"), 1 },
+    { ngx_string("optional"), 2 },
+    { ngx_string("optional_no_ca"), 3 },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t  ngx_mail_ssl_commands[] = {
+
+    { ngx_string("ssl"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG,
+      ngx_mail_ssl_enable,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, enable),
+      NULL },
+
+    { ngx_string("starttls"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_mail_ssl_starttls,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, starttls),
+      ngx_mail_starttls_state },
+
+    { ngx_string("ssl_certificate"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, certificates),
+      NULL },
+
+    { ngx_string("ssl_certificate_key"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, certificate_keys),
+      NULL },
+
+    { ngx_string("ssl_password_file"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_mail_ssl_password_file,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("ssl_dhparam"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, dhparam),
+      NULL },
+
+    { ngx_string("ssl_ecdh_curve"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, ecdh_curve),
+      NULL },
+
+    { ngx_string("ssl_protocols"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, protocols),
+      &ngx_mail_ssl_protocols },
+
+    { ngx_string("ssl_ciphers"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, ciphers),
+      NULL },
+
+    { ngx_string("ssl_prefer_server_ciphers"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, prefer_server_ciphers),
+      NULL },
+
+    { ngx_string("ssl_session_cache"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE12,
+      ngx_mail_ssl_session_cache,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("ssl_session_tickets"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, session_tickets),
+      NULL },
+
+    { ngx_string("ssl_session_ticket_key"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, session_ticket_keys),
+      NULL },
+
+    { ngx_string("ssl_session_timeout"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_sec_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, session_timeout),
+      NULL },
+
+    { ngx_string("ssl_verify_client"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, verify),
+      &ngx_mail_ssl_verify },
+
+    { ngx_string("ssl_verify_depth"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, verify_depth),
+      NULL },
+
+    { ngx_string("ssl_client_certificate"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, client_certificate),
+      NULL },
+
+    { ngx_string("ssl_trusted_certificate"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, trusted_certificate),
+      NULL },
+
+    { ngx_string("ssl_crl"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, crl),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_mail_module_t  ngx_mail_ssl_module_ctx = {
+    NULL,                                  /* protocol */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_mail_ssl_create_conf,              /* create server configuration */
+    ngx_mail_ssl_merge_conf                /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_mail_ssl_module = {
+    NGX_MODULE_V1,
+    &ngx_mail_ssl_module_ctx,              /* module context */
+    ngx_mail_ssl_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
+};
+
+
+static ngx_str_t ngx_mail_ssl_sess_id_ctx = ngx_string("MAIL");
+
+
+static void *
+ngx_mail_ssl_create_conf(ngx_conf_t *cf)
+{
+    ngx_mail_ssl_conf_t  *scf;
+
+    scf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_ssl_conf_t));
+    if (scf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     scf->protocols = 0;
+     *     scf->dhparam = { 0, NULL };
+     *     scf->ecdh_curve = { 0, NULL };
+     *     scf->client_certificate = { 0, NULL };
+     *     scf->trusted_certificate = { 0, NULL };
+     *     scf->crl = { 0, NULL };
+     *     scf->ciphers = { 0, NULL };
+     *     scf->shm_zone = NULL;
+     */
+
+    scf->enable = NGX_CONF_UNSET;
+    scf->starttls = NGX_CONF_UNSET_UINT;
+    scf->certificates = NGX_CONF_UNSET_PTR;
+    scf->certificate_keys = NGX_CONF_UNSET_PTR;
+    scf->passwords = NGX_CONF_UNSET_PTR;
+    scf->prefer_server_ciphers = NGX_CONF_UNSET;
+    scf->verify = NGX_CONF_UNSET_UINT;
+    scf->verify_depth = NGX_CONF_UNSET_UINT;
+    scf->builtin_session_cache = NGX_CONF_UNSET;
+    scf->session_timeout = NGX_CONF_UNSET;
+    scf->session_tickets = NGX_CONF_UNSET;
+    scf->session_ticket_keys = NGX_CONF_UNSET_PTR;
+
+    return scf;
+}
+
+
+static char *
+ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_mail_ssl_conf_t *prev = parent;
+    ngx_mail_ssl_conf_t *conf = child;
+
+    char                *mode;
+    ngx_pool_cleanup_t  *cln;
+
+    ngx_conf_merge_value(conf->enable, prev->enable, 0);
+    ngx_conf_merge_uint_value(conf->starttls, prev->starttls,
+                         NGX_MAIL_STARTTLS_OFF);
+
+    ngx_conf_merge_value(conf->session_timeout,
+                         prev->session_timeout, 300);
+
+    ngx_conf_merge_value(conf->prefer_server_ciphers,
+                         prev->prefer_server_ciphers, 0);
+
+    ngx_conf_merge_bitmask_value(conf->protocols, prev->protocols,
+                         (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1
+                          |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2));
+
+    ngx_conf_merge_uint_value(conf->verify, prev->verify, 0);
+    ngx_conf_merge_uint_value(conf->verify_depth, prev->verify_depth, 1);
+
+    ngx_conf_merge_ptr_value(conf->certificates, prev->certificates, NULL);
+    ngx_conf_merge_ptr_value(conf->certificate_keys, prev->certificate_keys,
+                         NULL);
+
+    ngx_conf_merge_ptr_value(conf->passwords, prev->passwords, NULL);
+
+    ngx_conf_merge_str_value(conf->dhparam, prev->dhparam, "");
+
+    ngx_conf_merge_str_value(conf->ecdh_curve, prev->ecdh_curve,
+                         NGX_DEFAULT_ECDH_CURVE);
+
+    ngx_conf_merge_str_value(conf->client_certificate,
+                         prev->client_certificate, "");
+    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->ciphers, prev->ciphers, NGX_DEFAULT_CIPHERS);
+
+
+    conf->ssl.log = cf->log;
+
+    if (conf->enable) {
+        mode = "ssl";
+
+    } else if (conf->starttls != NGX_MAIL_STARTTLS_OFF) {
+        mode = "starttls";
+
+    } else {
+        mode = "";
+    }
+
+    if (conf->file == NULL) {
+        conf->file = prev->file;
+        conf->line = prev->line;
+    }
+
+    if (*mode) {
+
+        if (conf->certificates == NULL) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "no \"ssl_certificate\" is defined for "
+                          "the \"%s\" directive in %s:%ui",
+                          mode, conf->file, conf->line);
+            return NGX_CONF_ERROR;
+        }
+
+        if (conf->certificate_keys == NULL) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "no \"ssl_certificate_key\" is defined for "
+                          "the \"%s\" directive in %s:%ui",
+                          mode, conf->file, conf->line);
+            return NGX_CONF_ERROR;
+        }
+
+        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->certificates == NULL) {
+            return NGX_CONF_OK;
+        }
+
+        if (conf->certificate_keys == NULL
+            || 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\"",
+                          ((ngx_str_t *) conf->certificates->elts)
+                          + conf->certificates->nelts - 1);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (ngx_ssl_create(&conf->ssl, conf->protocols, NULL) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    cln = ngx_pool_cleanup_add(cf->pool, 0);
+    if (cln == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    cln->handler = ngx_ssl_cleanup_ctx;
+    cln->data = &conf->ssl;
+
+    if (ngx_ssl_certificates(cf, &conf->ssl, conf->certificates,
+                             conf->certificate_keys, conf->passwords)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    if (conf->verify) {
+
+        if (conf->client_certificate.len == 0 && conf->verify != 3) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "no ssl_client_certificate for ssl_client_verify");
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_ssl_client_certificate(cf, &conf->ssl,
+                                       &conf->client_certificate,
+                                       conf->verify_depth)
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_ssl_trusted_certificate(cf, &conf->ssl,
+                                        &conf->trusted_certificate,
+                                        conf->verify_depth)
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_ssl_crl(cf, &conf->ssl, &conf->crl) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    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;
+    }
+
+    if (ngx_ssl_ecdh_curve(cf, &conf->ssl, &conf->ecdh_curve) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_conf_merge_value(conf->builtin_session_cache,
+                         prev->builtin_session_cache, NGX_SSL_NONE_SCACHE);
+
+    if (conf->shm_zone == NULL) {
+        conf->shm_zone = prev->shm_zone;
+    }
+
+    if (ngx_ssl_session_cache(&conf->ssl, &ngx_mail_ssl_sess_id_ctx,
+                              conf->builtin_session_cache,
+                              conf->shm_zone, conf->session_timeout)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_conf_merge_value(conf->session_tickets,
+                         prev->session_tickets, 1);
+
+#ifdef SSL_OP_NO_TICKET
+    if (!conf->session_tickets) {
+        SSL_CTX_set_options(conf->ssl.ctx, SSL_OP_NO_TICKET);
+    }
+#endif
+
+    ngx_conf_merge_ptr_value(conf->session_ticket_keys,
+                         prev->session_ticket_keys, NULL);
+
+    if (ngx_ssl_session_ticket_keys(cf, &conf->ssl, conf->session_ticket_keys)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_mail_ssl_conf_t  *scf = conf;
+
+    char  *rv;
+
+    rv = ngx_conf_set_flag_slot(cf, cmd, conf);
+
+    if (rv != NGX_CONF_OK) {
+        return rv;
+    }
+
+    if (scf->enable && (ngx_int_t) scf->starttls > NGX_MAIL_STARTTLS_OFF) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"starttls\" directive conflicts with \"ssl on\"");
+        return NGX_CONF_ERROR;
+    }
+
+    scf->file = cf->conf_file->file.name.data;
+    scf->line = cf->conf_file->line;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_ssl_starttls(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_mail_ssl_conf_t  *scf = conf;
+
+    char  *rv;
+
+    rv = ngx_conf_set_enum_slot(cf, cmd, conf);
+
+    if (rv != NGX_CONF_OK) {
+        return rv;
+    }
+
+    if (scf->enable == 1 && (ngx_int_t) scf->starttls > NGX_MAIL_STARTTLS_OFF) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"ssl\" directive conflicts with \"starttls\"");
+        return NGX_CONF_ERROR;
+    }
+
+    scf->file = cf->conf_file->file.name.data;
+    scf->line = cf->conf_file->line;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_mail_ssl_conf_t  *scf = conf;
+
+    ngx_str_t  *value;
+
+    if (scf->passwords != NGX_CONF_UNSET_PTR) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    scf->passwords = ngx_ssl_read_password_file(cf, &value[1]);
+
+    if (scf->passwords == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_mail_ssl_conf_t  *scf = conf;
+
+    size_t       len;
+    ngx_str_t   *value, name, size;
+    ngx_int_t    n;
+    ngx_uint_t   i, j;
+
+    value = cf->args->elts;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+
+        if (ngx_strcmp(value[i].data, "off") == 0) {
+            scf->builtin_session_cache = NGX_SSL_NO_SCACHE;
+            continue;
+        }
+
+        if (ngx_strcmp(value[i].data, "none") == 0) {
+            scf->builtin_session_cache = NGX_SSL_NONE_SCACHE;
+            continue;
+        }
+
+        if (ngx_strcmp(value[i].data, "builtin") == 0) {
+            scf->builtin_session_cache = NGX_SSL_DFLT_BUILTIN_SCACHE;
+            continue;
+        }
+
+        if (value[i].len > sizeof("builtin:") - 1
+            && ngx_strncmp(value[i].data, "builtin:", sizeof("builtin:") - 1)
+               == 0)
+        {
+            n = ngx_atoi(value[i].data + sizeof("builtin:") - 1,
+                         value[i].len - (sizeof("builtin:") - 1));
+
+            if (n == NGX_ERROR) {
+                goto invalid;
+            }
+
+            scf->builtin_session_cache = n;
+
+            continue;
+        }
+
+        if (value[i].len > sizeof("shared:") - 1
+            && ngx_strncmp(value[i].data, "shared:", sizeof("shared:") - 1)
+               == 0)
+        {
+            len = 0;
+
+            for (j = sizeof("shared:") - 1; j < value[i].len; j++) {
+                if (value[i].data[j] == ':') {
+                    break;
+                }
+
+                len++;
+            }
+
+            if (len == 0) {
+                goto invalid;
+            }
+
+            name.len = len;
+            name.data = value[i].data + sizeof("shared:") - 1;
+
+            size.len = value[i].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,
+                                   "session cache \"%V\" is too small",
+                                   &value[i]);
+
+                return NGX_CONF_ERROR;
+            }
+
+            scf->shm_zone = ngx_shared_memory_add(cf, &name, n,
+                                                   &ngx_mail_ssl_module);
+            if (scf->shm_zone == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            scf->shm_zone->init = ngx_ssl_session_cache_init;
+
+            continue;
+        }
+
+        goto invalid;
+    }
+
+    if (scf->shm_zone && scf->builtin_session_cache == NGX_CONF_UNSET) {
+        scf->builtin_session_cache = NGX_SSL_NO_BUILTIN_SCACHE;
+    }
+
+    return NGX_CONF_OK;
+
+invalid:
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "invalid session cache \"%V\"", &value[i]);
+
+    return NGX_CONF_ERROR;
+}
diff --git a/nginx/src/mail/ngx_mail_ssl_module.h b/nginx/src/mail/ngx_mail_ssl_module.h
new file mode 100644 (file)
index 0000000..26628d5
--- /dev/null
@@ -0,0 +1,64 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_MAIL_SSL_H_INCLUDED_
+#define _NGX_MAIL_SSL_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_mail.h>
+
+
+#define NGX_MAIL_STARTTLS_OFF   0
+#define NGX_MAIL_STARTTLS_ON    1
+#define NGX_MAIL_STARTTLS_ONLY  2
+
+
+typedef struct {
+    ngx_flag_t       enable;
+    ngx_flag_t       prefer_server_ciphers;
+
+    ngx_ssl_t        ssl;
+
+    ngx_uint_t       starttls;
+    ngx_uint_t       protocols;
+
+    ngx_uint_t       verify;
+    ngx_uint_t       verify_depth;
+
+    ssize_t          builtin_session_cache;
+
+    time_t           session_timeout;
+
+    ngx_array_t     *certificates;
+    ngx_array_t     *certificate_keys;
+
+    ngx_str_t        dhparam;
+    ngx_str_t        ecdh_curve;
+    ngx_str_t        client_certificate;
+    ngx_str_t        trusted_certificate;
+    ngx_str_t        crl;
+
+    ngx_str_t        ciphers;
+
+    ngx_array_t     *passwords;
+
+    ngx_shm_zone_t  *shm_zone;
+
+    ngx_flag_t       session_tickets;
+    ngx_array_t     *session_ticket_keys;
+
+    u_char          *file;
+    ngx_uint_t       line;
+} ngx_mail_ssl_conf_t;
+
+
+extern ngx_module_t  ngx_mail_ssl_module;
+
+
+#endif /* _NGX_MAIL_SSL_H_INCLUDED_ */
diff --git a/nginx/src/misc/ngx_cpp_test_module.cpp b/nginx/src/misc/ngx_cpp_test_module.cpp
new file mode 100644 (file)
index 0000000..5d2f08d
--- /dev/null
@@ -0,0 +1,29 @@
+
+// stub module to test header files' C++ compatibility
+
+extern "C" {
+  #include <ngx_config.h>
+  #include <ngx_core.h>
+  #include <ngx_event.h>
+  #include <ngx_event_connect.h>
+  #include <ngx_event_pipe.h>
+
+  #include <ngx_http.h>
+
+  #include <ngx_mail.h>
+  #include <ngx_mail_pop3_module.h>
+  #include <ngx_mail_imap_module.h>
+  #include <ngx_mail_smtp_module.h>
+}
+
+// nginx header files should go before other, because they define 64-bit off_t
+// #include <string>
+
+
+void ngx_cpp_test_handler(void *data);
+
+void
+ngx_cpp_test_handler(void *data)
+{
+    return;
+}
diff --git a/nginx/src/misc/ngx_google_perftools_module.c b/nginx/src/misc/ngx_google_perftools_module.c
new file mode 100644 (file)
index 0000000..381f3d1
--- /dev/null
@@ -0,0 +1,126 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+/*
+ * declare Profiler interface here because
+ * <google/profiler.h> is C++ header file
+ */
+
+int ProfilerStart(u_char* fname);
+void ProfilerStop(void);
+void ProfilerRegisterThread(void);
+
+
+static void *ngx_google_perftools_create_conf(ngx_cycle_t *cycle);
+static ngx_int_t ngx_google_perftools_worker(ngx_cycle_t *cycle);
+
+
+typedef struct {
+    ngx_str_t  profiles;
+} ngx_google_perftools_conf_t;
+
+
+static ngx_command_t  ngx_google_perftools_commands[] = {
+
+    { ngx_string("google_perftools_profiles"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      0,
+      offsetof(ngx_google_perftools_conf_t, profiles),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_core_module_t  ngx_google_perftools_module_ctx = {
+    ngx_string("google_perftools"),
+    ngx_google_perftools_create_conf,
+    NULL
+};
+
+
+ngx_module_t  ngx_google_perftools_module = {
+    NGX_MODULE_V1,
+    &ngx_google_perftools_module_ctx,      /* module context */
+    ngx_google_perftools_commands,         /* module directives */
+    NGX_CORE_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    ngx_google_perftools_worker,           /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static void *
+ngx_google_perftools_create_conf(ngx_cycle_t *cycle)
+{
+    ngx_google_perftools_conf_t  *gptcf;
+
+    gptcf = ngx_pcalloc(cycle->pool, sizeof(ngx_google_perftools_conf_t));
+    if (gptcf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc()
+     *
+     *     gptcf->profiles = { 0, NULL };
+     */
+
+    return gptcf;
+}
+
+
+static ngx_int_t
+ngx_google_perftools_worker(ngx_cycle_t *cycle)
+{
+    u_char                       *profile;
+    ngx_google_perftools_conf_t  *gptcf;
+
+    gptcf = (ngx_google_perftools_conf_t *)
+                ngx_get_conf(cycle->conf_ctx, ngx_google_perftools_module);
+
+    if (gptcf->profiles.len == 0) {
+        return NGX_OK;
+    }
+
+    profile = ngx_alloc(gptcf->profiles.len + NGX_INT_T_LEN + 2, cycle->log);
+    if (profile == NULL) {
+        return NGX_OK;
+    }
+
+    if (getenv("CPUPROFILE")) {
+        /* disable inherited Profiler enabled in master process */
+        ProfilerStop();
+    }
+
+    ngx_sprintf(profile, "%V.%d%Z", &gptcf->profiles, ngx_pid);
+
+    if (ProfilerStart(profile)) {
+        /* start ITIMER_PROF timer */
+        ProfilerRegisterThread();
+
+    } else {
+        ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_errno,
+                      "ProfilerStart(%s) failed", profile);
+    }
+
+    ngx_free(profile);
+
+    return NGX_OK;
+}
+
+
+/* ProfilerStop() is called on Profiler destruction */
diff --git a/nginx/src/os/unix/ngx_alloc.c b/nginx/src/os/unix/ngx_alloc.c
new file mode 100644 (file)
index 0000000..5c2f787
--- /dev/null
@@ -0,0 +1,90 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+ngx_uint_t  ngx_pagesize;
+ngx_uint_t  ngx_pagesize_shift;
+ngx_uint_t  ngx_cacheline_size;
+
+
+void *
+ngx_alloc(size_t size, ngx_log_t *log)
+{
+    void  *p;
+
+    p = malloc(size);
+    if (p == NULL) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                      "malloc(%uz) failed", size);
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);
+
+    return p;
+}
+
+
+void *
+ngx_calloc(size_t size, ngx_log_t *log)
+{
+    void  *p;
+
+    p = ngx_alloc(size, log);
+
+    if (p) {
+        ngx_memzero(p, size);
+    }
+
+    return p;
+}
+
+
+#if (NGX_HAVE_POSIX_MEMALIGN)
+
+void *
+ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
+{
+    void  *p;
+    int    err;
+
+    err = posix_memalign(&p, alignment, size);
+
+    if (err) {
+        ngx_log_error(NGX_LOG_EMERG, log, err,
+                      "posix_memalign(%uz, %uz) failed", alignment, size);
+        p = NULL;
+    }
+
+    ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
+                   "posix_memalign: %p:%uz @%uz", p, size, alignment);
+
+    return p;
+}
+
+#elif (NGX_HAVE_MEMALIGN)
+
+void *
+ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
+{
+    void  *p;
+
+    p = memalign(alignment, size);
+    if (p == NULL) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                      "memalign(%uz, %uz) failed", alignment, size);
+    }
+
+    ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
+                   "memalign: %p:%uz @%uz", p, size, alignment);
+
+    return p;
+}
+
+#endif
diff --git a/nginx/src/os/unix/ngx_alloc.h b/nginx/src/os/unix/ngx_alloc.h
new file mode 100644 (file)
index 0000000..655db25
--- /dev/null
@@ -0,0 +1,45 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_ALLOC_H_INCLUDED_
+#define _NGX_ALLOC_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+void *ngx_alloc(size_t size, ngx_log_t *log);
+void *ngx_calloc(size_t size, ngx_log_t *log);
+
+#define ngx_free          free
+
+
+/*
+ * Linux has memalign() or posix_memalign()
+ * Solaris has memalign()
+ * FreeBSD 7.0 has posix_memalign(), besides, early version's malloc()
+ * aligns allocations bigger than page size at the page boundary
+ */
+
+#if (NGX_HAVE_POSIX_MEMALIGN || NGX_HAVE_MEMALIGN)
+
+void *ngx_memalign(size_t alignment, size_t size, ngx_log_t *log);
+
+#else
+
+#define ngx_memalign(alignment, size, log)  ngx_alloc(size, log)
+
+#endif
+
+
+extern ngx_uint_t  ngx_pagesize;
+extern ngx_uint_t  ngx_pagesize_shift;
+extern ngx_uint_t  ngx_cacheline_size;
+
+
+#endif /* _NGX_ALLOC_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_atomic.h b/nginx/src/os/unix/ngx_atomic.h
new file mode 100644 (file)
index 0000000..74b8b7f
--- /dev/null
@@ -0,0 +1,313 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_ATOMIC_H_INCLUDED_
+#define _NGX_ATOMIC_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#if (NGX_HAVE_LIBATOMIC)
+
+#define AO_REQUIRE_CAS
+#include <atomic_ops.h>
+
+#define NGX_HAVE_ATOMIC_OPS  1
+
+typedef long                        ngx_atomic_int_t;
+typedef AO_t                        ngx_atomic_uint_t;
+typedef volatile ngx_atomic_uint_t  ngx_atomic_t;
+
+#if (NGX_PTR_SIZE == 8)
+#define NGX_ATOMIC_T_LEN            (sizeof("-9223372036854775808") - 1)
+#else
+#define NGX_ATOMIC_T_LEN            (sizeof("-2147483648") - 1)
+#endif
+
+#define ngx_atomic_cmp_set(lock, old, new)                                    \
+    AO_compare_and_swap(lock, old, new)
+#define ngx_atomic_fetch_add(value, add)                                      \
+    AO_fetch_and_add(value, add)
+#define ngx_memory_barrier()        AO_nop()
+#define ngx_cpu_pause()
+
+
+#elif (NGX_DARWIN_ATOMIC)
+
+/*
+ * use Darwin 8 atomic(3) and barrier(3) operations
+ * optimized at run-time for UP and SMP
+ */
+
+#include <libkern/OSAtomic.h>
+
+/* "bool" conflicts with perl's CORE/handy.h */
+#if 0
+#undef bool
+#endif
+
+
+#define NGX_HAVE_ATOMIC_OPS  1
+
+#if (NGX_PTR_SIZE == 8)
+
+typedef int64_t                     ngx_atomic_int_t;
+typedef uint64_t                    ngx_atomic_uint_t;
+#define NGX_ATOMIC_T_LEN            (sizeof("-9223372036854775808") - 1)
+
+#define ngx_atomic_cmp_set(lock, old, new)                                    \
+    OSAtomicCompareAndSwap64Barrier(old, new, (int64_t *) lock)
+
+#define ngx_atomic_fetch_add(value, add)                                      \
+    (OSAtomicAdd64(add, (int64_t *) value) - add)
+
+#else
+
+typedef int32_t                     ngx_atomic_int_t;
+typedef uint32_t                    ngx_atomic_uint_t;
+#define NGX_ATOMIC_T_LEN            (sizeof("-2147483648") - 1)
+
+#define ngx_atomic_cmp_set(lock, old, new)                                    \
+    OSAtomicCompareAndSwap32Barrier(old, new, (int32_t *) lock)
+
+#define ngx_atomic_fetch_add(value, add)                                      \
+    (OSAtomicAdd32(add, (int32_t *) value) - add)
+
+#endif
+
+#define ngx_memory_barrier()        OSMemoryBarrier()
+
+#define ngx_cpu_pause()
+
+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;
+typedef uint32_t                    ngx_atomic_uint_t;
+typedef volatile ngx_atomic_uint_t  ngx_atomic_t;
+#define NGX_ATOMIC_T_LEN            (sizeof("-2147483648") - 1)
+
+
+#if ( __SUNPRO_C )
+
+#define NGX_HAVE_ATOMIC_OPS  1
+
+ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+    ngx_atomic_uint_t set);
+
+ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add);
+
+/*
+ * Sun Studio 12 exits with segmentation fault on '__asm ("pause")',
+ * so ngx_cpu_pause is declared in src/os/unix/ngx_sunpro_x86.il
+ */
+
+void
+ngx_cpu_pause(void);
+
+/* the code in src/os/unix/ngx_sunpro_x86.il */
+
+#define ngx_memory_barrier()        __asm (".volatile"); __asm (".nonvolatile")
+
+
+#else /* ( __GNUC__ || __INTEL_COMPILER ) */
+
+#define NGX_HAVE_ATOMIC_OPS  1
+
+#include "ngx_gcc_atomic_x86.h"
+
+#endif
+
+
+#elif ( __amd64__ || __amd64 )
+
+typedef int64_t                     ngx_atomic_int_t;
+typedef uint64_t                    ngx_atomic_uint_t;
+typedef volatile ngx_atomic_uint_t  ngx_atomic_t;
+#define NGX_ATOMIC_T_LEN            (sizeof("-9223372036854775808") - 1)
+
+
+#if ( __SUNPRO_C )
+
+#define NGX_HAVE_ATOMIC_OPS  1
+
+ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+    ngx_atomic_uint_t set);
+
+ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add);
+
+/*
+ * Sun Studio 12 exits with segmentation fault on '__asm ("pause")',
+ * so ngx_cpu_pause is declared in src/os/unix/ngx_sunpro_amd64.il
+ */
+
+void
+ngx_cpu_pause(void);
+
+/* the code in src/os/unix/ngx_sunpro_amd64.il */
+
+#define ngx_memory_barrier()        __asm (".volatile"); __asm (".nonvolatile")
+
+
+#else /* ( __GNUC__ || __INTEL_COMPILER ) */
+
+#define NGX_HAVE_ATOMIC_OPS  1
+
+#include "ngx_gcc_atomic_amd64.h"
+
+#endif
+
+
+#elif ( __sparc__ || __sparc || __sparcv9 )
+
+#if (NGX_PTR_SIZE == 8)
+
+typedef int64_t                     ngx_atomic_int_t;
+typedef uint64_t                    ngx_atomic_uint_t;
+#define NGX_ATOMIC_T_LEN            (sizeof("-9223372036854775808") - 1)
+
+#else
+
+typedef int32_t                     ngx_atomic_int_t;
+typedef uint32_t                    ngx_atomic_uint_t;
+#define NGX_ATOMIC_T_LEN            (sizeof("-2147483648") - 1)
+
+#endif
+
+typedef volatile ngx_atomic_uint_t  ngx_atomic_t;
+
+
+#if ( __SUNPRO_C )
+
+#define NGX_HAVE_ATOMIC_OPS  1
+
+#include "ngx_sunpro_atomic_sparc64.h"
+
+
+#else /* ( __GNUC__ || __INTEL_COMPILER ) */
+
+#define NGX_HAVE_ATOMIC_OPS  1
+
+#include "ngx_gcc_atomic_sparc64.h"
+
+#endif
+
+
+#elif ( __powerpc__ || __POWERPC__ )
+
+#define NGX_HAVE_ATOMIC_OPS  1
+
+#if (NGX_PTR_SIZE == 8)
+
+typedef int64_t                     ngx_atomic_int_t;
+typedef uint64_t                    ngx_atomic_uint_t;
+#define NGX_ATOMIC_T_LEN            (sizeof("-9223372036854775808") - 1)
+
+#else
+
+typedef int32_t                     ngx_atomic_int_t;
+typedef uint32_t                    ngx_atomic_uint_t;
+#define NGX_ATOMIC_T_LEN            (sizeof("-2147483648") - 1)
+
+#endif
+
+typedef volatile ngx_atomic_uint_t  ngx_atomic_t;
+
+
+#include "ngx_gcc_atomic_ppc.h"
+
+#endif
+
+
+#if !(NGX_HAVE_ATOMIC_OPS)
+
+#define NGX_HAVE_ATOMIC_OPS  0
+
+typedef int32_t                     ngx_atomic_int_t;
+typedef uint32_t                    ngx_atomic_uint_t;
+typedef volatile ngx_atomic_uint_t  ngx_atomic_t;
+#define NGX_ATOMIC_T_LEN            (sizeof("-2147483648") - 1)
+
+
+static ngx_inline ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+    ngx_atomic_uint_t set)
+{
+    if (*lock == old) {
+        *lock = set;
+        return 1;
+    }
+
+    return 0;
+}
+
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+    ngx_atomic_int_t  old;
+
+    old = *value;
+    *value += add;
+
+    return old;
+}
+
+#define ngx_memory_barrier()
+#define ngx_cpu_pause()
+
+#endif
+
+
+void ngx_spinlock(ngx_atomic_t *lock, ngx_atomic_int_t value, ngx_uint_t spin);
+
+#define ngx_trylock(lock)  (*(lock) == 0 && ngx_atomic_cmp_set(lock, 0, 1))
+#define ngx_unlock(lock)    *(lock) = 0
+
+
+#endif /* _NGX_ATOMIC_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_channel.c b/nginx/src/os/unix/ngx_channel.c
new file mode 100644 (file)
index 0000000..1efa066
--- /dev/null
@@ -0,0 +1,253 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_channel.h>
+
+
+ngx_int_t
+ngx_write_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size,
+    ngx_log_t *log)
+{
+    ssize_t             n;
+    ngx_err_t           err;
+    struct iovec        iov[1];
+    struct msghdr       msg;
+
+#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
+
+    union {
+        struct cmsghdr  cm;
+        char            space[CMSG_SPACE(sizeof(int))];
+    } cmsg;
+
+    if (ch->fd == -1) {
+        msg.msg_control = NULL;
+        msg.msg_controllen = 0;
+
+    } else {
+        msg.msg_control = (caddr_t) &cmsg;
+        msg.msg_controllen = sizeof(cmsg);
+
+        ngx_memzero(&cmsg, sizeof(cmsg));
+
+        cmsg.cm.cmsg_len = CMSG_LEN(sizeof(int));
+        cmsg.cm.cmsg_level = SOL_SOCKET;
+        cmsg.cm.cmsg_type = SCM_RIGHTS;
+
+        /*
+         * We have to use ngx_memcpy() instead of simple
+         *   *(int *) CMSG_DATA(&cmsg.cm) = ch->fd;
+         * because some gcc 4.4 with -O2/3/s optimization issues the warning:
+         *   dereferencing type-punned pointer will break strict-aliasing rules
+         *
+         * Fortunately, gcc with -O1 compiles this ngx_memcpy()
+         * in the same simple assignment as in the code above
+         */
+
+        ngx_memcpy(CMSG_DATA(&cmsg.cm), &ch->fd, sizeof(int));
+    }
+
+    msg.msg_flags = 0;
+
+#else
+
+    if (ch->fd == -1) {
+        msg.msg_accrights = NULL;
+        msg.msg_accrightslen = 0;
+
+    } else {
+        msg.msg_accrights = (caddr_t) &ch->fd;
+        msg.msg_accrightslen = sizeof(int);
+    }
+
+#endif
+
+    iov[0].iov_base = (char *) ch;
+    iov[0].iov_len = size;
+
+    msg.msg_name = NULL;
+    msg.msg_namelen = 0;
+    msg.msg_iov = iov;
+    msg.msg_iovlen = 1;
+
+    n = sendmsg(s, &msg, 0);
+
+    if (n == -1) {
+        err = ngx_errno;
+        if (err == NGX_EAGAIN) {
+            return NGX_AGAIN;
+        }
+
+        ngx_log_error(NGX_LOG_ALERT, log, err, "sendmsg() failed");
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_read_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size, ngx_log_t *log)
+{
+    ssize_t             n;
+    ngx_err_t           err;
+    struct iovec        iov[1];
+    struct msghdr       msg;
+
+#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
+    union {
+        struct cmsghdr  cm;
+        char            space[CMSG_SPACE(sizeof(int))];
+    } cmsg;
+#else
+    int                 fd;
+#endif
+
+    iov[0].iov_base = (char *) ch;
+    iov[0].iov_len = size;
+
+    msg.msg_name = NULL;
+    msg.msg_namelen = 0;
+    msg.msg_iov = iov;
+    msg.msg_iovlen = 1;
+
+#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
+    msg.msg_control = (caddr_t) &cmsg;
+    msg.msg_controllen = sizeof(cmsg);
+#else
+    msg.msg_accrights = (caddr_t) &fd;
+    msg.msg_accrightslen = sizeof(int);
+#endif
+
+    n = recvmsg(s, &msg, 0);
+
+    if (n == -1) {
+        err = ngx_errno;
+        if (err == NGX_EAGAIN) {
+            return NGX_AGAIN;
+        }
+
+        ngx_log_error(NGX_LOG_ALERT, log, err, "recvmsg() failed");
+        return NGX_ERROR;
+    }
+
+    if (n == 0) {
+        ngx_log_debug0(NGX_LOG_DEBUG_CORE, log, 0, "recvmsg() returned zero");
+        return NGX_ERROR;
+    }
+
+    if ((size_t) n < sizeof(ngx_channel_t)) {
+        ngx_log_error(NGX_LOG_ALERT, log, 0,
+                      "recvmsg() returned not enough data: %z", n);
+        return NGX_ERROR;
+    }
+
+#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
+
+    if (ch->command == NGX_CMD_OPEN_CHANNEL) {
+
+        if (cmsg.cm.cmsg_len < (socklen_t) CMSG_LEN(sizeof(int))) {
+            ngx_log_error(NGX_LOG_ALERT, log, 0,
+                          "recvmsg() returned too small ancillary data");
+            return NGX_ERROR;
+        }
+
+        if (cmsg.cm.cmsg_level != SOL_SOCKET || cmsg.cm.cmsg_type != SCM_RIGHTS)
+        {
+            ngx_log_error(NGX_LOG_ALERT, log, 0,
+                          "recvmsg() returned invalid ancillary data "
+                          "level %d or type %d",
+                          cmsg.cm.cmsg_level, cmsg.cm.cmsg_type);
+            return NGX_ERROR;
+        }
+
+        /* ch->fd = *(int *) CMSG_DATA(&cmsg.cm); */
+
+        ngx_memcpy(&ch->fd, CMSG_DATA(&cmsg.cm), sizeof(int));
+    }
+
+    if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) {
+        ngx_log_error(NGX_LOG_ALERT, log, 0,
+                      "recvmsg() truncated data");
+    }
+
+#else
+
+    if (ch->command == NGX_CMD_OPEN_CHANNEL) {
+        if (msg.msg_accrightslen != sizeof(int)) {
+            ngx_log_error(NGX_LOG_ALERT, log, 0,
+                          "recvmsg() returned no ancillary data");
+            return NGX_ERROR;
+        }
+
+        ch->fd = fd;
+    }
+
+#endif
+
+    return n;
+}
+
+
+ngx_int_t
+ngx_add_channel_event(ngx_cycle_t *cycle, ngx_fd_t fd, ngx_int_t event,
+    ngx_event_handler_pt handler)
+{
+    ngx_event_t       *ev, *rev, *wev;
+    ngx_connection_t  *c;
+
+    c = ngx_get_connection(fd, cycle->log);
+
+    if (c == NULL) {
+        return NGX_ERROR;
+    }
+
+    c->pool = cycle->pool;
+
+    rev = c->read;
+    wev = c->write;
+
+    rev->log = cycle->log;
+    wev->log = cycle->log;
+
+    rev->channel = 1;
+    wev->channel = 1;
+
+    ev = (event == NGX_READ_EVENT) ? rev : wev;
+
+    ev->handler = handler;
+
+    if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
+        if (ngx_add_conn(c) == NGX_ERROR) {
+            ngx_free_connection(c);
+            return NGX_ERROR;
+        }
+
+    } else {
+        if (ngx_add_event(ev, event, 0) == NGX_ERROR) {
+            ngx_free_connection(c);
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+void
+ngx_close_channel(ngx_fd_t *fd, ngx_log_t *log)
+{
+    if (close(fd[0]) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "close() channel failed");
+    }
+
+    if (close(fd[1]) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "close() channel failed");
+    }
+}
diff --git a/nginx/src/os/unix/ngx_channel.h b/nginx/src/os/unix/ngx_channel.h
new file mode 100644 (file)
index 0000000..362cc64
--- /dev/null
@@ -0,0 +1,34 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_CHANNEL_H_INCLUDED_
+#define _NGX_CHANNEL_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+typedef struct {
+    ngx_uint_t  command;
+    ngx_pid_t   pid;
+    ngx_int_t   slot;
+    ngx_fd_t    fd;
+} ngx_channel_t;
+
+
+ngx_int_t ngx_write_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size,
+    ngx_log_t *log);
+ngx_int_t ngx_read_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size,
+    ngx_log_t *log);
+ngx_int_t ngx_add_channel_event(ngx_cycle_t *cycle, ngx_fd_t fd,
+    ngx_int_t event, ngx_event_handler_pt handler);
+void ngx_close_channel(ngx_fd_t *fd, ngx_log_t *log);
+
+
+#endif /* _NGX_CHANNEL_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_daemon.c b/nginx/src/os/unix/ngx_daemon.c
new file mode 100644 (file)
index 0000000..385c49b
--- /dev/null
@@ -0,0 +1,71 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+ngx_int_t
+ngx_daemon(ngx_log_t *log)
+{
+    int  fd;
+
+    switch (fork()) {
+    case -1:
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "fork() failed");
+        return NGX_ERROR;
+
+    case 0:
+        break;
+
+    default:
+        exit(0);
+    }
+
+    ngx_parent = ngx_pid;
+    ngx_pid = ngx_getpid();
+
+    if (setsid() == -1) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "setsid() failed");
+        return NGX_ERROR;
+    }
+
+    umask(0);
+
+    fd = open("/dev/null", O_RDWR);
+    if (fd == -1) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                      "open(\"/dev/null\") failed");
+        return NGX_ERROR;
+    }
+
+    if (dup2(fd, STDIN_FILENO) == -1) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDIN) failed");
+        return NGX_ERROR;
+    }
+
+    if (dup2(fd, STDOUT_FILENO) == -1) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDOUT) failed");
+        return NGX_ERROR;
+    }
+
+#if 0
+    if (dup2(fd, STDERR_FILENO) == -1) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDERR) failed");
+        return NGX_ERROR;
+    }
+#endif
+
+    if (fd > STDERR_FILENO) {
+        if (close(fd) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "close() failed");
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
diff --git a/nginx/src/os/unix/ngx_darwin.h b/nginx/src/os/unix/ngx_darwin.h
new file mode 100644 (file)
index 0000000..4d01b26
--- /dev/null
@@ -0,0 +1,23 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_DARWIN_H_INCLUDED_
+#define _NGX_DARWIN_H_INCLUDED_
+
+
+void ngx_debug_init(void);
+ngx_chain_t *ngx_darwin_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in,
+    off_t limit);
+
+extern int       ngx_darwin_kern_osreldate;
+extern int       ngx_darwin_hw_ncpu;
+extern u_long    ngx_darwin_net_inet_tcp_sendspace;
+
+extern ngx_uint_t  ngx_debug_malloc;
+
+
+#endif /* _NGX_DARWIN_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_darwin_config.h b/nginx/src/os/unix/ngx_darwin_config.h
new file mode 100644 (file)
index 0000000..0dfe633
--- /dev/null
@@ -0,0 +1,100 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_DARWIN_CONFIG_H_INCLUDED_
+#define _NGX_DARWIN_CONFIG_H_INCLUDED_
+
+
+#define __APPLE_USE_RFC_3542    /* IPV6_PKTINFO */
+
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stddef.h>             /* offsetof() */
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <dirent.h>
+#include <glob.h>
+#include <sys/mount.h>          /* statfs() */
+
+#include <sys/filio.h>          /* FIONBIO */
+#include <sys/ioctl.h>
+#include <sys/uio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sched.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>        /* TCP_NODELAY */
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/un.h>
+
+#include <sys/sysctl.h>
+#include <xlocale.h>
+
+#include <dlfcn.h>
+
+
+#ifndef IOV_MAX
+#define IOV_MAX   64
+#endif
+
+
+#include <ngx_auto_config.h>
+
+
+#if (NGX_HAVE_POSIX_SEM)
+#include <semaphore.h>
+#endif
+
+
+#if (NGX_HAVE_POLL)
+#include <poll.h>
+#endif
+
+
+#if (NGX_HAVE_KQUEUE)
+#include <sys/event.h>
+#endif
+
+
+#define NGX_LISTEN_BACKLOG  -1
+
+
+#ifndef NGX_HAVE_INHERITED_NONBLOCK
+#define NGX_HAVE_INHERITED_NONBLOCK  1
+#endif
+
+
+#ifndef NGX_HAVE_CASELESS_FILESYSTEM
+#define NGX_HAVE_CASELESS_FILESYSTEM  1
+#endif
+
+
+#define NGX_HAVE_OS_SPECIFIC_INIT    1
+#define NGX_HAVE_DEBUG_MALLOC        1
+
+
+extern char **environ;
+
+
+#endif /* _NGX_DARWIN_CONFIG_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_darwin_init.c b/nginx/src/os/unix/ngx_darwin_init.c
new file mode 100644 (file)
index 0000000..aabe02f
--- /dev/null
@@ -0,0 +1,198 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+char    ngx_darwin_kern_ostype[16];
+char    ngx_darwin_kern_osrelease[128];
+int     ngx_darwin_hw_ncpu;
+int     ngx_darwin_kern_ipc_somaxconn;
+u_long  ngx_darwin_net_inet_tcp_sendspace;
+
+ngx_uint_t  ngx_debug_malloc;
+
+
+static ngx_os_io_t ngx_darwin_io = {
+    ngx_unix_recv,
+    ngx_readv_chain,
+    ngx_udp_unix_recv,
+    ngx_unix_send,
+    ngx_udp_unix_send,
+    ngx_udp_unix_sendmsg_chain,
+#if (NGX_HAVE_SENDFILE)
+    ngx_darwin_sendfile_chain,
+    NGX_IO_SENDFILE
+#else
+    ngx_writev_chain,
+    0
+#endif
+};
+
+
+typedef struct {
+    char        *name;
+    void        *value;
+    size_t       size;
+    ngx_uint_t   exists;
+} sysctl_t;
+
+
+sysctl_t sysctls[] = {
+    { "hw.ncpu",
+      &ngx_darwin_hw_ncpu,
+      sizeof(ngx_darwin_hw_ncpu), 0 },
+
+    { "net.inet.tcp.sendspace",
+      &ngx_darwin_net_inet_tcp_sendspace,
+      sizeof(ngx_darwin_net_inet_tcp_sendspace), 0 },
+
+    { "kern.ipc.somaxconn",
+      &ngx_darwin_kern_ipc_somaxconn,
+      sizeof(ngx_darwin_kern_ipc_somaxconn), 0 },
+
+    { NULL, NULL, 0, 0 }
+};
+
+
+void
+ngx_debug_init(void)
+{
+#if (NGX_DEBUG_MALLOC)
+
+    /*
+     * MacOSX 10.6, 10.7:  MallocScribble fills freed memory with 0x55
+     *                     and fills allocated memory with 0xAA.
+     * MacOSX 10.4, 10.5:  MallocScribble fills freed memory with 0x55,
+     *                     MallocPreScribble fills allocated memory with 0xAA.
+     * MacOSX 10.3:        MallocScribble fills freed memory with 0x55,
+     *                     and no way to fill allocated memory.
+     */
+
+    setenv("MallocScribble", "1", 0);
+
+    ngx_debug_malloc = 1;
+
+#else
+
+    if (getenv("MallocScribble")) {
+        ngx_debug_malloc = 1;
+    }
+
+#endif
+}
+
+
+ngx_int_t
+ngx_os_specific_init(ngx_log_t *log)
+{
+    size_t      size;
+    ngx_err_t   err;
+    ngx_uint_t  i;
+
+    size = sizeof(ngx_darwin_kern_ostype);
+    if (sysctlbyname("kern.ostype", ngx_darwin_kern_ostype, &size, NULL, 0)
+        == -1)
+    {
+        err = ngx_errno;
+
+        if (err != NGX_ENOENT) {
+
+            ngx_log_error(NGX_LOG_ALERT, log, err,
+                          "sysctlbyname(kern.ostype) failed");
+
+            if (err != NGX_ENOMEM) {
+                return NGX_ERROR;
+            }
+
+            ngx_darwin_kern_ostype[size - 1] = '\0';
+        }
+    }
+
+    size = sizeof(ngx_darwin_kern_osrelease);
+    if (sysctlbyname("kern.osrelease", ngx_darwin_kern_osrelease, &size,
+                     NULL, 0)
+        == -1)
+    {
+        err = ngx_errno;
+
+        if (err != NGX_ENOENT) {
+
+            ngx_log_error(NGX_LOG_ALERT, log, err,
+                          "sysctlbyname(kern.osrelease) failed");
+
+            if (err != NGX_ENOMEM) {
+                return NGX_ERROR;
+            }
+
+            ngx_darwin_kern_osrelease[size - 1] = '\0';
+        }
+    }
+
+    for (i = 0; sysctls[i].name; i++) {
+        size = sysctls[i].size;
+
+        if (sysctlbyname(sysctls[i].name, sysctls[i].value, &size, NULL, 0)
+            == 0)
+        {
+            sysctls[i].exists = 1;
+            continue;
+        }
+
+        err = ngx_errno;
+
+        if (err == NGX_ENOENT) {
+            continue;
+        }
+
+        ngx_log_error(NGX_LOG_ALERT, log, err,
+                      "sysctlbyname(%s) failed", sysctls[i].name);
+        return NGX_ERROR;
+    }
+
+    ngx_ncpu = ngx_darwin_hw_ncpu;
+
+    if (ngx_darwin_kern_ipc_somaxconn > 32767) {
+        ngx_log_error(NGX_LOG_ALERT, log, 0,
+                      "sysctl kern.ipc.somaxconn must be less than 32768");
+        return NGX_ERROR;
+    }
+
+    ngx_tcp_nodelay_and_tcp_nopush = 1;
+
+    ngx_os_io = ngx_darwin_io;
+
+    return NGX_OK;
+}
+
+
+void
+ngx_os_specific_status(ngx_log_t *log)
+{
+    u_long      value;
+    ngx_uint_t  i;
+
+    if (ngx_darwin_kern_ostype[0]) {
+        ngx_log_error(NGX_LOG_NOTICE, log, 0, "OS: %s %s",
+                      ngx_darwin_kern_ostype, ngx_darwin_kern_osrelease);
+    }
+
+    for (i = 0; sysctls[i].name; i++) {
+        if (sysctls[i].exists) {
+            if (sysctls[i].size == sizeof(long)) {
+                value = *(long *) sysctls[i].value;
+
+            } else {
+                value = *(int *) sysctls[i].value;
+            }
+
+            ngx_log_error(NGX_LOG_NOTICE, log, 0, "%s: %l",
+                          sysctls[i].name, value);
+        }
+    }
+}
diff --git a/nginx/src/os/unix/ngx_darwin_sendfile_chain.c b/nginx/src/os/unix/ngx_darwin_sendfile_chain.c
new file mode 100644 (file)
index 0000000..2a76c15
--- /dev/null
@@ -0,0 +1,206 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+/*
+ * It seems that Darwin 9.4 (Mac OS X 1.5) sendfile() has the same
+ * old bug as early FreeBSD sendfile() syscall:
+ * http://bugs.freebsd.org/33771
+ *
+ * Besides sendfile() has another bug: if one calls sendfile()
+ * with both a header and a trailer, then sendfile() ignores a file part
+ * at all and sends only the header and the trailer together.
+ * For this reason we send a trailer only if there is no a header.
+ *
+ * Although sendfile() allows to pass a header or a trailer,
+ * it may send the header or the trailer and a part of the file
+ * in different packets.  And FreeBSD workaround (TCP_NOPUSH option)
+ * does not help.
+ */
+
+
+ngx_chain_t *
+ngx_darwin_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
+{
+    int              rc;
+    off_t            send, prev_send, sent;
+    off_t            file_size;
+    ssize_t          n;
+    ngx_uint_t       eintr;
+    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];
+
+    wev = c->write;
+
+    if (!wev->ready) {
+        return in;
+    }
+
+#if (NGX_HAVE_KQUEUE)
+
+    if ((ngx_event_flags & NGX_USE_KQUEUE_EVENT) && wev->pending_eof) {
+        (void) ngx_connection_error(c, wev->kq_errno,
+                               "kevent() reported about an closed connection");
+        wev->error = 1;
+        return NGX_CHAIN_ERROR;
+    }
+
+#endif
+
+    /* the maximum limit size is the maximum size_t value - the page size */
+
+    if (limit == 0 || limit > (off_t) (NGX_MAX_SIZE_T_VALUE - ngx_pagesize)) {
+        limit = NGX_MAX_SIZE_T_VALUE - ngx_pagesize;
+    }
+
+    send = 0;
+
+    header.iovs = headers;
+    header.nalloc = NGX_IOVS_PREALLOCATE;
+
+    trailer.iovs = trailers;
+    trailer.nalloc = NGX_IOVS_PREALLOCATE;
+
+    for ( ;; ) {
+        eintr = 0;
+        prev_send = send;
+
+        /* create the header iovec and coalesce the neighbouring bufs */
+
+        cl = ngx_output_chain_to_iovec(&header, in, limit - send, c->log);
+
+        if (cl == NGX_CHAIN_ERROR) {
+            return NGX_CHAIN_ERROR;
+        }
+
+        send += header.size;
+
+        if (cl && cl->buf->in_file && send < limit) {
+            file = cl->buf;
+
+            /* coalesce the neighbouring file bufs */
+
+            file_size = ngx_chain_coalesce_file(&cl, limit - send);
+
+            send += file_size;
+
+            if (header.count == 0 && send < limit) {
+
+                /*
+                 * create the trailer iovec and coalesce the neighbouring bufs
+                 */
+
+                cl = ngx_output_chain_to_iovec(&trailer, cl, limit - send,
+                                               c->log);
+                if (cl == NGX_CHAIN_ERROR) {
+                    return NGX_CHAIN_ERROR;
+                }
+
+                send += trailer.size;
+
+            } else {
+                trailer.count = 0;
+            }
+
+            /*
+             * sendfile() returns EINVAL if sf_hdtr's count is 0,
+             * but corresponding pointer is not NULL
+             */
+
+            hdtr.headers = header.count ? header.iovs : NULL;
+            hdtr.hdr_cnt = header.count;
+            hdtr.trailers = trailer.count ? trailer.iovs : NULL;
+            hdtr.trl_cnt = trailer.count;
+
+            sent = header.size + file_size;
+
+            ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                           "sendfile: @%O %O h:%uz",
+                           file->file_pos, sent, header.size);
+
+            rc = sendfile(file->file->fd, c->fd, file->file_pos,
+                          &sent, &hdtr, 0);
+
+            if (rc == -1) {
+                err = ngx_errno;
+
+                switch (err) {
+                case NGX_EAGAIN:
+                    break;
+
+                case NGX_EINTR:
+                    eintr = 1;
+                    break;
+
+                default:
+                    wev->error = 1;
+                    (void) ngx_connection_error(c, err, "sendfile() failed");
+                    return NGX_CHAIN_ERROR;
+                }
+
+                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, err,
+                               "sendfile() sent only %O bytes", sent);
+            }
+
+            if (rc == 0 && sent == 0) {
+
+                /*
+                 * if rc and sent equal to 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,
+                              "sendfile() reported that \"%s\" was truncated",
+                              file->file->name.data);
+
+                return NGX_CHAIN_ERROR;
+            }
+
+            ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                           "sendfile: %d, @%O %O:%O",
+                           rc, file->file_pos, sent, file_size + header.size);
+
+        } else {
+            n = ngx_writev(c, &header);
+
+            if (n == NGX_ERROR) {
+                return NGX_CHAIN_ERROR;
+            }
+
+            sent = (n == NGX_AGAIN) ? 0 : n;
+        }
+
+        c->sent += sent;
+
+        in = ngx_chain_update_sent(in, sent);
+
+        if (eintr) {
+            send = prev_send + sent;
+            continue;
+        }
+
+        if (send - prev_send != sent) {
+            wev->ready = 0;
+            return in;
+        }
+
+        if (send >= limit || in == NULL) {
+            return in;
+        }
+    }
+}
diff --git a/nginx/src/os/unix/ngx_dlopen.c b/nginx/src/os/unix/ngx_dlopen.c
new file mode 100644 (file)
index 0000000..a0efc69
--- /dev/null
@@ -0,0 +1,28 @@
+
+/*
+ * Copyright (C) Maxim Dounin
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#if (NGX_HAVE_DLOPEN)
+
+char *
+ngx_dlerror(void)
+{
+    char  *err;
+
+    err = (char *) dlerror();
+
+    if (err == NULL) {
+        return "";
+    }
+
+    return err;
+}
+
+#endif
diff --git a/nginx/src/os/unix/ngx_dlopen.h b/nginx/src/os/unix/ngx_dlopen.h
new file mode 100644 (file)
index 0000000..7a3159f
--- /dev/null
@@ -0,0 +1,31 @@
+
+/*
+ * Copyright (C) Maxim Dounin
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_DLOPEN_H_INCLUDED_
+#define _NGX_DLOPEN_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#define ngx_dlopen(path)           dlopen((char *) path, RTLD_NOW | RTLD_GLOBAL)
+#define ngx_dlopen_n               "dlopen()"
+
+#define ngx_dlsym(handle, symbol)  dlsym(handle, symbol)
+#define ngx_dlsym_n                "dlsym()"
+
+#define ngx_dlclose(handle)        dlclose(handle)
+#define ngx_dlclose_n              "dlclose()"
+
+
+#if (NGX_HAVE_DLOPEN)
+char *ngx_dlerror(void);
+#endif
+
+
+#endif /* _NGX_DLOPEN_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_errno.c b/nginx/src/os/unix/ngx_errno.c
new file mode 100644 (file)
index 0000000..e787b23
--- /dev/null
@@ -0,0 +1,87 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/*
+ * The strerror() messages are copied because:
+ *
+ * 1) strerror() and strerror_r() functions are not Async-Signal-Safe,
+ *    therefore, they cannot be used in signal handlers;
+ *
+ * 2) a direct sys_errlist[] array may be used instead of these functions,
+ *    but Linux linker warns about its usage:
+ *
+ * warning: `sys_errlist' is deprecated; use `strerror' or `strerror_r' instead
+ * warning: `sys_nerr' is deprecated; use `strerror' or `strerror_r' instead
+ *
+ *    causing false bug reports.
+ */
+
+
+static ngx_str_t  *ngx_sys_errlist;
+static ngx_str_t   ngx_unknown_error = ngx_string("Unknown error");
+
+
+u_char *
+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;
+    size = ngx_min(size, msg->len);
+
+    return ngx_cpymem(errstr, msg->data, size);
+}
+
+
+ngx_int_t
+ngx_strerror_init(void)
+{
+    char       *msg;
+    u_char     *p;
+    size_t      len;
+    ngx_err_t   err;
+
+    /*
+     * 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);
+
+    ngx_sys_errlist = malloc(len);
+    if (ngx_sys_errlist == NULL) {
+        goto failed;
+    }
+
+    for (err = 0; err < NGX_SYS_NERR; err++) {
+        msg = strerror(err);
+        len = ngx_strlen(msg);
+
+        p = malloc(len);
+        if (p == NULL) {
+            goto failed;
+        }
+
+        ngx_memcpy(p, msg, len);
+        ngx_sys_errlist[err].len = len;
+        ngx_sys_errlist[err].data = p;
+    }
+
+    return NGX_OK;
+
+failed:
+
+    err = errno;
+    ngx_log_stderr(0, "malloc(%uz) failed (%d: %s)", len, err, strerror(err));
+
+    return NGX_ERROR;
+}
diff --git a/nginx/src/os/unix/ngx_errno.h b/nginx/src/os/unix/ngx_errno.h
new file mode 100644 (file)
index 0000000..7d6ca76
--- /dev/null
@@ -0,0 +1,79 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_ERRNO_H_INCLUDED_
+#define _NGX_ERRNO_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef int               ngx_err_t;
+
+#define NGX_EPERM         EPERM
+#define NGX_ENOENT        ENOENT
+#define NGX_ENOPATH       ENOENT
+#define NGX_ESRCH         ESRCH
+#define NGX_EINTR         EINTR
+#define NGX_ECHILD        ECHILD
+#define NGX_ENOMEM        ENOMEM
+#define NGX_EACCES        EACCES
+#define NGX_EBUSY         EBUSY
+#define NGX_EEXIST        EEXIST
+#define NGX_EEXIST_FILE   EEXIST
+#define NGX_EXDEV         EXDEV
+#define NGX_ENOTDIR       ENOTDIR
+#define NGX_EISDIR        EISDIR
+#define NGX_EINVAL        EINVAL
+#define NGX_ENFILE        ENFILE
+#define NGX_EMFILE        EMFILE
+#define NGX_ENOSPC        ENOSPC
+#define NGX_EPIPE         EPIPE
+#define NGX_EINPROGRESS   EINPROGRESS
+#define NGX_ENOPROTOOPT   ENOPROTOOPT
+#define NGX_EOPNOTSUPP    EOPNOTSUPP
+#define NGX_EADDRINUSE    EADDRINUSE
+#define NGX_ECONNABORTED  ECONNABORTED
+#define NGX_ECONNRESET    ECONNRESET
+#define NGX_ENOTCONN      ENOTCONN
+#define NGX_ETIMEDOUT     ETIMEDOUT
+#define NGX_ECONNREFUSED  ECONNREFUSED
+#define NGX_ENAMETOOLONG  ENAMETOOLONG
+#define NGX_ENETDOWN      ENETDOWN
+#define NGX_ENETUNREACH   ENETUNREACH
+#define NGX_EHOSTDOWN     EHOSTDOWN
+#define NGX_EHOSTUNREACH  EHOSTUNREACH
+#define NGX_ENOSYS        ENOSYS
+#define NGX_ECANCELED     ECANCELED
+#define NGX_EILSEQ        EILSEQ
+#define NGX_ENOMOREFILES  0
+#define NGX_ELOOP         ELOOP
+#define NGX_EBADF         EBADF
+
+#if (NGX_HAVE_OPENAT)
+#define NGX_EMLINK        EMLINK
+#endif
+
+#if (__hpux__)
+#define NGX_EAGAIN        EWOULDBLOCK
+#else
+#define NGX_EAGAIN        EAGAIN
+#endif
+
+
+#define ngx_errno                  errno
+#define ngx_socket_errno           errno
+#define ngx_set_errno(err)         errno = err
+#define ngx_set_socket_errno(err)  errno = err
+
+
+u_char *ngx_strerror(ngx_err_t err, u_char *errstr, size_t size);
+ngx_int_t ngx_strerror_init(void);
+
+
+#endif /* _NGX_ERRNO_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_file_aio_read.c b/nginx/src/os/unix/ngx_file_aio_read.c
new file mode 100644 (file)
index 0000000..aedc3c9
--- /dev/null
@@ -0,0 +1,216 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+/*
+ * FreeBSD file AIO features and quirks:
+ *
+ *    if an asked data are already in VM cache, then aio_error() returns 0,
+ *    and the data are already copied in buffer;
+ *
+ *    aio_read() preread in VM cache as minimum 16K (probably BKVASIZE);
+ *    the first AIO preload may be up to 128K;
+ *
+ *    aio_read/aio_error() may return EINPROGRESS for just written data;
+ *
+ *    kqueue EVFILT_AIO filter is level triggered only: an event repeats
+ *    until aio_return() will be called;
+ *
+ *    aio_cancel() cannot cancel file AIO: it returns AIO_NOTCANCELED always.
+ */
+
+
+extern int  ngx_kqueue;
+
+
+static ssize_t ngx_file_aio_result(ngx_file_t *file, ngx_event_aio_t *aio,
+    ngx_event_t *ev);
+static void ngx_file_aio_event_handler(ngx_event_t *ev);
+
+
+ngx_int_t
+ngx_file_aio_init(ngx_file_t *file, ngx_pool_t *pool)
+{
+    ngx_event_aio_t  *aio;
+
+    aio = ngx_pcalloc(pool, sizeof(ngx_event_aio_t));
+    if (aio == NULL) {
+        return NGX_ERROR;
+    }
+
+    aio->file = file;
+    aio->fd = file->fd;
+    aio->event.data = aio;
+    aio->event.ready = 1;
+    aio->event.log = file->log;
+
+    file->aio = aio;
+
+    return NGX_OK;
+}
+
+
+ssize_t
+ngx_file_aio_read(ngx_file_t *file, u_char *buf, size_t size, off_t offset,
+    ngx_pool_t *pool)
+{
+    int               n;
+    ngx_event_t      *ev;
+    ngx_event_aio_t  *aio;
+
+    if (!ngx_file_aio) {
+        return ngx_read_file(file, buf, size, offset);
+    }
+
+    if (file->aio == NULL && ngx_file_aio_init(file, pool) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    aio = file->aio;
+    ev = &aio->event;
+
+    if (!ev->ready) {
+        ngx_log_error(NGX_LOG_ALERT, file->log, 0,
+                      "second aio post for \"%V\"", &file->name);
+        return NGX_AGAIN;
+    }
+
+    ngx_log_debug4(NGX_LOG_DEBUG_CORE, file->log, 0,
+                   "aio complete:%d @%O:%uz %V",
+                   ev->complete, offset, size, &file->name);
+
+    if (ev->complete) {
+        ev->complete = 0;
+        ngx_set_errno(aio->err);
+
+        if (aio->err == 0) {
+            return aio->nbytes;
+        }
+
+        ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+                      "aio read \"%s\" failed", file->name.data);
+
+        return NGX_ERROR;
+    }
+
+    ngx_memzero(&aio->aiocb, sizeof(struct aiocb));
+
+    aio->aiocb.aio_fildes = file->fd;
+    aio->aiocb.aio_offset = offset;
+    aio->aiocb.aio_buf = buf;
+    aio->aiocb.aio_nbytes = size;
+#if (NGX_HAVE_KQUEUE)
+    aio->aiocb.aio_sigevent.sigev_notify_kqueue = ngx_kqueue;
+    aio->aiocb.aio_sigevent.sigev_notify = SIGEV_KEVENT;
+    aio->aiocb.aio_sigevent.sigev_value.sigval_ptr = ev;
+#endif
+    ev->handler = ngx_file_aio_event_handler;
+
+    n = aio_read(&aio->aiocb);
+
+    if (n == -1) {
+        n = ngx_errno;
+
+        if (n == NGX_EAGAIN) {
+            return ngx_read_file(file, buf, size, offset);
+        }
+
+        ngx_log_error(NGX_LOG_CRIT, file->log, n,
+                      "aio_read(\"%V\") failed", &file->name);
+
+        if (n == NGX_ENOSYS) {
+            ngx_file_aio = 0;
+            return ngx_read_file(file, buf, size, offset);
+        }
+
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_CORE, file->log, 0,
+                   "aio_read: fd:%d %d", file->fd, n);
+
+    ev->active = 1;
+    ev->ready = 0;
+    ev->complete = 0;
+
+    return ngx_file_aio_result(aio->file, aio, ev);
+}
+
+
+static ssize_t
+ngx_file_aio_result(ngx_file_t *file, ngx_event_aio_t *aio, ngx_event_t *ev)
+{
+    int        n;
+    ngx_err_t  err;
+
+    n = aio_error(&aio->aiocb);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_CORE, file->log, 0,
+                   "aio_error: fd:%d %d", file->fd, n);
+
+    if (n == -1) {
+        err = ngx_errno;
+        aio->err = err;
+
+        ngx_log_error(NGX_LOG_ALERT, file->log, err,
+                      "aio_error(\"%V\") failed", &file->name);
+        return NGX_ERROR;
+    }
+
+    if (n == NGX_EINPROGRESS) {
+        if (ev->ready) {
+            ev->ready = 0;
+            ngx_log_error(NGX_LOG_ALERT, file->log, n,
+                          "aio_read(\"%V\") still in progress",
+                          &file->name);
+        }
+
+        return NGX_AGAIN;
+    }
+
+    n = aio_return(&aio->aiocb);
+
+    if (n == -1) {
+        err = ngx_errno;
+        aio->err = err;
+        ev->ready = 1;
+
+        ngx_log_error(NGX_LOG_CRIT, file->log, err,
+                      "aio_return(\"%V\") failed", &file->name);
+        return NGX_ERROR;
+    }
+
+    aio->err = 0;
+    aio->nbytes = n;
+    ev->ready = 1;
+    ev->active = 0;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_CORE, file->log, 0,
+                   "aio_return: fd:%d %d", file->fd, n);
+
+    return n;
+}
+
+
+static void
+ngx_file_aio_event_handler(ngx_event_t *ev)
+{
+    ngx_event_aio_t  *aio;
+
+    aio = ev->data;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_CORE, ev->log, 0,
+                   "aio event handler fd:%d %V", aio->fd, &aio->file->name);
+
+    if (ngx_file_aio_result(aio->file, aio, ev) != NGX_AGAIN) {
+        aio->handler(ev);
+    }
+}
diff --git a/nginx/src/os/unix/ngx_files.c b/nginx/src/os/unix/ngx_files.c
new file mode 100644 (file)
index 0000000..482d327
--- /dev/null
@@ -0,0 +1,907 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#if (NGX_THREADS)
+#include <ngx_thread_pool.h>
+static void ngx_thread_read_handler(void *data, ngx_log_t *log);
+static void ngx_thread_write_chain_to_file_handler(void *data, ngx_log_t *log);
+#endif
+
+static ngx_chain_t *ngx_chain_to_iovec(ngx_iovec_t *vec, ngx_chain_t *cl);
+static ssize_t ngx_writev_file(ngx_file_t *file, ngx_iovec_t *vec,
+    off_t offset);
+
+
+#if (NGX_HAVE_FILE_AIO)
+
+ngx_uint_t  ngx_file_aio = 1;
+
+#endif
+
+
+ssize_t
+ngx_read_file(ngx_file_t *file, u_char *buf, size_t size, off_t offset)
+{
+    ssize_t  n;
+
+    ngx_log_debug4(NGX_LOG_DEBUG_CORE, file->log, 0,
+                   "read: %d, %p, %uz, %O", file->fd, buf, size, offset);
+
+#if (NGX_HAVE_PREAD)
+
+    n = pread(file->fd, buf, size, offset);
+
+    if (n == -1) {
+        ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+                      "pread() \"%s\" failed", file->name.data);
+        return NGX_ERROR;
+    }
+
+#else
+
+    if (file->sys_offset != offset) {
+        if (lseek(file->fd, offset, SEEK_SET) == -1) {
+            ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+                          "lseek() \"%s\" failed", file->name.data);
+            return NGX_ERROR;
+        }
+
+        file->sys_offset = offset;
+    }
+
+    n = read(file->fd, buf, size);
+
+    if (n == -1) {
+        ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+                      "read() \"%s\" failed", file->name.data);
+        return NGX_ERROR;
+    }
+
+    file->sys_offset += n;
+
+#endif
+
+    file->offset += n;
+
+    return n;
+}
+
+
+#if (NGX_THREADS)
+
+typedef struct {
+    ngx_fd_t       fd;
+    ngx_uint_t     write;   /* unsigned  write:1; */
+
+    u_char        *buf;
+    size_t         size;
+    ngx_chain_t   *chain;
+    off_t          offset;
+
+    size_t         nbytes;
+    ngx_err_t      err;
+} ngx_thread_file_ctx_t;
+
+
+ssize_t
+ngx_thread_read(ngx_file_t *file, u_char *buf, size_t size, off_t offset,
+    ngx_pool_t *pool)
+{
+    ngx_thread_task_t      *task;
+    ngx_thread_file_ctx_t  *ctx;
+
+    ngx_log_debug4(NGX_LOG_DEBUG_CORE, file->log, 0,
+                   "thread read: %d, %p, %uz, %O",
+                   file->fd, buf, size, offset);
+
+    task = file->thread_task;
+
+    if (task == NULL) {
+        task = ngx_thread_task_alloc(pool, sizeof(ngx_thread_file_ctx_t));
+        if (task == NULL) {
+            return NGX_ERROR;
+        }
+
+        file->thread_task = task;
+    }
+
+    ctx = task->ctx;
+
+    if (task->event.complete) {
+        task->event.complete = 0;
+
+        if (ctx->write) {
+            ngx_log_error(NGX_LOG_ALERT, file->log, 0,
+                          "invalid thread call, read instead of write");
+            return NGX_ERROR;
+        }
+
+        if (ctx->err) {
+            ngx_log_error(NGX_LOG_CRIT, file->log, ctx->err,
+                          "pread() \"%s\" failed", file->name.data);
+            return NGX_ERROR;
+        }
+
+        return ctx->nbytes;
+    }
+
+    task->handler = ngx_thread_read_handler;
+
+    ctx->write = 0;
+
+    ctx->fd = file->fd;
+    ctx->buf = buf;
+    ctx->size = size;
+    ctx->offset = offset;
+
+    if (file->thread_handler(task, file) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    return NGX_AGAIN;
+}
+
+
+#if (NGX_HAVE_PREAD)
+
+static void
+ngx_thread_read_handler(void *data, ngx_log_t *log)
+{
+    ngx_thread_file_ctx_t *ctx = data;
+
+    ssize_t  n;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_CORE, log, 0, "thread read handler");
+
+    n = pread(ctx->fd, ctx->buf, ctx->size, ctx->offset);
+
+    if (n == -1) {
+        ctx->err = ngx_errno;
+
+    } else {
+        ctx->nbytes = n;
+        ctx->err = 0;
+    }
+
+#if 0
+    ngx_time_update();
+#endif
+
+    ngx_log_debug4(NGX_LOG_DEBUG_CORE, log, 0,
+                   "pread: %z (err: %d) of %uz @%O",
+                   n, ctx->err, ctx->size, ctx->offset);
+}
+
+#else
+
+#error pread() is required!
+
+#endif
+
+#endif /* NGX_THREADS */
+
+
+ssize_t
+ngx_write_file(ngx_file_t *file, u_char *buf, size_t size, off_t offset)
+{
+    ssize_t    n, written;
+    ngx_err_t  err;
+
+    ngx_log_debug4(NGX_LOG_DEBUG_CORE, file->log, 0,
+                   "write: %d, %p, %uz, %O", file->fd, buf, size, offset);
+
+    written = 0;
+
+#if (NGX_HAVE_PWRITE)
+
+    for ( ;; ) {
+        n = pwrite(file->fd, buf + written, size, offset);
+
+        if (n == -1) {
+            err = ngx_errno;
+
+            if (err == NGX_EINTR) {
+                ngx_log_debug0(NGX_LOG_DEBUG_CORE, file->log, err,
+                               "pwrite() was interrupted");
+                continue;
+            }
+
+            ngx_log_error(NGX_LOG_CRIT, file->log, err,
+                          "pwrite() \"%s\" failed", file->name.data);
+            return NGX_ERROR;
+        }
+
+        file->offset += n;
+        written += n;
+
+        if ((size_t) n == size) {
+            return written;
+        }
+
+        offset += n;
+        size -= n;
+    }
+
+#else
+
+    if (file->sys_offset != offset) {
+        if (lseek(file->fd, offset, SEEK_SET) == -1) {
+            ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+                          "lseek() \"%s\" failed", file->name.data);
+            return NGX_ERROR;
+        }
+
+        file->sys_offset = offset;
+    }
+
+    for ( ;; ) {
+        n = write(file->fd, buf + written, size);
+
+        if (n == -1) {
+            err = ngx_errno;
+
+            if (err == NGX_EINTR) {
+                ngx_log_debug0(NGX_LOG_DEBUG_CORE, file->log, err,
+                               "write() was interrupted");
+                continue;
+            }
+
+            ngx_log_error(NGX_LOG_CRIT, file->log, err,
+                          "write() \"%s\" failed", file->name.data);
+            return NGX_ERROR;
+        }
+
+        file->sys_offset += n;
+        file->offset += n;
+        written += n;
+
+        if ((size_t) n == size) {
+            return written;
+        }
+
+        size -= n;
+    }
+#endif
+}
+
+
+ngx_fd_t
+ngx_open_tempfile(u_char *name, ngx_uint_t persistent, ngx_uint_t access)
+{
+    ngx_fd_t  fd;
+
+    fd = open((const char *) name, O_CREAT|O_EXCL|O_RDWR,
+              access ? access : 0600);
+
+    if (fd != -1 && !persistent) {
+        (void) unlink((const char *) name);
+    }
+
+    return fd;
+}
+
+
+ssize_t
+ngx_write_chain_to_file(ngx_file_t *file, ngx_chain_t *cl, off_t offset,
+    ngx_pool_t *pool)
+{
+    ssize_t        total, n;
+    ngx_iovec_t    vec;
+    struct iovec   iovs[NGX_IOVS_PREALLOCATE];
+
+    /* use pwrite() if there is the only buf in a chain */
+
+    if (cl->next == NULL) {
+        return ngx_write_file(file, cl->buf->pos,
+                              (size_t) (cl->buf->last - cl->buf->pos),
+                              offset);
+    }
+
+    total = 0;
+
+    vec.iovs = iovs;
+    vec.nalloc = NGX_IOVS_PREALLOCATE;
+
+    do {
+        /* create the iovec and coalesce the neighbouring bufs */
+        cl = ngx_chain_to_iovec(&vec, cl);
+
+        /* use pwrite() if there is the only iovec buffer */
+
+        if (vec.count == 1) {
+            n = ngx_write_file(file, (u_char *) iovs[0].iov_base,
+                               iovs[0].iov_len, offset);
+
+            if (n == NGX_ERROR) {
+                return n;
+            }
+
+            return total + n;
+        }
+
+        n = ngx_writev_file(file, &vec, offset);
+
+        if (n == NGX_ERROR) {
+            return n;
+        }
+
+        offset += n;
+        total += n;
+
+    } while (cl);
+
+    return total;
+}
+
+
+static ngx_chain_t *
+ngx_chain_to_iovec(ngx_iovec_t *vec, ngx_chain_t *cl)
+{
+    size_t         total, size;
+    u_char        *prev;
+    ngx_uint_t     n;
+    struct iovec  *iov;
+
+    iov = NULL;
+    prev = NULL;
+    total = 0;
+    n = 0;
+
+    for ( /* void */ ; cl; cl = cl->next) {
+
+        if (ngx_buf_special(cl->buf)) {
+            continue;
+        }
+
+        size = cl->buf->last - cl->buf->pos;
+
+        if (prev == cl->buf->pos) {
+            iov->iov_len += size;
+
+        } else {
+            if (n == vec->nalloc) {
+                break;
+            }
+
+            iov = &vec->iovs[n++];
+
+            iov->iov_base = (void *) cl->buf->pos;
+            iov->iov_len = size;
+        }
+
+        prev = cl->buf->pos + size;
+        total += size;
+    }
+
+    vec->count = n;
+    vec->size = total;
+
+    return cl;
+}
+
+
+static ssize_t
+ngx_writev_file(ngx_file_t *file, ngx_iovec_t *vec, off_t offset)
+{
+    ssize_t    n;
+    ngx_err_t  err;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_CORE, file->log, 0,
+                   "writev: %d, %uz, %O", file->fd, vec->size, offset);
+
+#if (NGX_HAVE_PWRITEV)
+
+eintr:
+
+    n = pwritev(file->fd, vec->iovs, vec->count, offset);
+
+    if (n == -1) {
+        err = ngx_errno;
+
+        if (err == NGX_EINTR) {
+            ngx_log_debug0(NGX_LOG_DEBUG_CORE, file->log, err,
+                           "pwritev() was interrupted");
+            goto eintr;
+        }
+
+        ngx_log_error(NGX_LOG_CRIT, file->log, err,
+                      "pwritev() \"%s\" failed", file->name.data);
+        return NGX_ERROR;
+    }
+
+    if ((size_t) n != vec->size) {
+        ngx_log_error(NGX_LOG_CRIT, file->log, 0,
+                      "pwritev() \"%s\" has written only %z of %uz",
+                      file->name.data, n, vec->size);
+        return NGX_ERROR;
+    }
+
+#else
+
+    if (file->sys_offset != offset) {
+        if (lseek(file->fd, offset, SEEK_SET) == -1) {
+            ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+                          "lseek() \"%s\" failed", file->name.data);
+            return NGX_ERROR;
+        }
+
+        file->sys_offset = offset;
+    }
+
+eintr:
+
+    n = writev(file->fd, vec->iovs, vec->count);
+
+    if (n == -1) {
+        err = ngx_errno;
+
+        if (err == NGX_EINTR) {
+            ngx_log_debug0(NGX_LOG_DEBUG_CORE, file->log, err,
+                           "writev() was interrupted");
+            goto eintr;
+        }
+
+        ngx_log_error(NGX_LOG_CRIT, file->log, err,
+                      "writev() \"%s\" failed", file->name.data);
+        return NGX_ERROR;
+    }
+
+    if ((size_t) n != vec->size) {
+        ngx_log_error(NGX_LOG_CRIT, file->log, 0,
+                      "writev() \"%s\" has written only %z of %uz",
+                      file->name.data, n, vec->size);
+        return NGX_ERROR;
+    }
+
+    file->sys_offset += n;
+
+#endif
+
+    file->offset += n;
+
+    return n;
+}
+
+
+#if (NGX_THREADS)
+
+ssize_t
+ngx_thread_write_chain_to_file(ngx_file_t *file, ngx_chain_t *cl, off_t offset,
+    ngx_pool_t *pool)
+{
+    ngx_thread_task_t      *task;
+    ngx_thread_file_ctx_t  *ctx;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_CORE, file->log, 0,
+                   "thread write chain: %d, %p, %O",
+                   file->fd, cl, offset);
+
+    task = file->thread_task;
+
+    if (task == NULL) {
+        task = ngx_thread_task_alloc(pool,
+                                     sizeof(ngx_thread_file_ctx_t));
+        if (task == NULL) {
+            return NGX_ERROR;
+        }
+
+        file->thread_task = task;
+    }
+
+    ctx = task->ctx;
+
+    if (task->event.complete) {
+        task->event.complete = 0;
+
+        if (!ctx->write) {
+            ngx_log_error(NGX_LOG_ALERT, file->log, 0,
+                          "invalid thread call, write instead of read");
+            return NGX_ERROR;
+        }
+
+        if (ctx->err || ctx->nbytes == 0) {
+            ngx_log_error(NGX_LOG_CRIT, file->log, ctx->err,
+                          "pwritev() \"%s\" failed", file->name.data);
+            return NGX_ERROR;
+        }
+
+        file->offset += ctx->nbytes;
+        return ctx->nbytes;
+    }
+
+    task->handler = ngx_thread_write_chain_to_file_handler;
+
+    ctx->write = 1;
+
+    ctx->fd = file->fd;
+    ctx->chain = cl;
+    ctx->offset = offset;
+
+    if (file->thread_handler(task, file) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    return NGX_AGAIN;
+}
+
+
+static void
+ngx_thread_write_chain_to_file_handler(void *data, ngx_log_t *log)
+{
+    ngx_thread_file_ctx_t *ctx = data;
+
+#if (NGX_HAVE_PWRITEV)
+
+    off_t          offset;
+    ssize_t        n;
+    ngx_err_t      err;
+    ngx_chain_t   *cl;
+    ngx_iovec_t    vec;
+    struct iovec   iovs[NGX_IOVS_PREALLOCATE];
+
+    vec.iovs = iovs;
+    vec.nalloc = NGX_IOVS_PREALLOCATE;
+
+    cl = ctx->chain;
+    offset = ctx->offset;
+
+    ctx->nbytes = 0;
+    ctx->err = 0;
+
+    do {
+        /* create the iovec and coalesce the neighbouring bufs */
+        cl = ngx_chain_to_iovec(&vec, cl);
+
+eintr:
+
+        n = pwritev(ctx->fd, iovs, vec.count, offset);
+
+        if (n == -1) {
+            err = ngx_errno;
+
+            if (err == NGX_EINTR) {
+                ngx_log_debug0(NGX_LOG_DEBUG_CORE, log, err,
+                               "pwritev() was interrupted");
+                goto eintr;
+            }
+
+            ctx->err = err;
+            return;
+        }
+
+        if ((size_t) n != vec.size) {
+            ctx->nbytes = 0;
+            return;
+        }
+
+        ctx->nbytes += n;
+        offset += n;
+    } while (cl);
+
+#else
+
+    ctx->err = NGX_ENOSYS;
+    return;
+
+#endif
+}
+
+#endif /* NGX_THREADS */
+
+
+ngx_int_t
+ngx_set_file_time(u_char *name, ngx_fd_t fd, time_t s)
+{
+    struct timeval  tv[2];
+
+    tv[0].tv_sec = ngx_time();
+    tv[0].tv_usec = 0;
+    tv[1].tv_sec = s;
+    tv[1].tv_usec = 0;
+
+    if (utimes((char *) name, tv) != -1) {
+        return NGX_OK;
+    }
+
+    return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_create_file_mapping(ngx_file_mapping_t *fm)
+{
+    fm->fd = ngx_open_file(fm->name, NGX_FILE_RDWR, NGX_FILE_TRUNCATE,
+                           NGX_FILE_DEFAULT_ACCESS);
+
+    if (fm->fd == NGX_INVALID_FILE) {
+        ngx_log_error(NGX_LOG_CRIT, fm->log, ngx_errno,
+                      ngx_open_file_n " \"%s\" failed", fm->name);
+        return NGX_ERROR;
+    }
+
+    if (ftruncate(fm->fd, fm->size) == -1) {
+        ngx_log_error(NGX_LOG_CRIT, fm->log, ngx_errno,
+                      "ftruncate() \"%s\" failed", fm->name);
+        goto failed;
+    }
+
+    fm->addr = mmap(NULL, fm->size, PROT_READ|PROT_WRITE, MAP_SHARED,
+                    fm->fd, 0);
+    if (fm->addr != MAP_FAILED) {
+        return NGX_OK;
+    }
+
+    ngx_log_error(NGX_LOG_CRIT, fm->log, ngx_errno,
+                  "mmap(%uz) \"%s\" failed", fm->size, fm->name);
+
+failed:
+
+    if (ngx_close_file(fm->fd) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, fm->log, ngx_errno,
+                      ngx_close_file_n " \"%s\" failed", fm->name);
+    }
+
+    return NGX_ERROR;
+}
+
+
+void
+ngx_close_file_mapping(ngx_file_mapping_t *fm)
+{
+    if (munmap(fm->addr, fm->size) == -1) {
+        ngx_log_error(NGX_LOG_CRIT, fm->log, ngx_errno,
+                      "munmap(%uz) \"%s\" failed", fm->size, fm->name);
+    }
+
+    if (ngx_close_file(fm->fd) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, fm->log, ngx_errno,
+                      ngx_close_file_n " \"%s\" failed", fm->name);
+    }
+}
+
+
+ngx_int_t
+ngx_open_dir(ngx_str_t *name, ngx_dir_t *dir)
+{
+    dir->dir = opendir((const char *) name->data);
+
+    if (dir->dir == NULL) {
+        return NGX_ERROR;
+    }
+
+    dir->valid_info = 0;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_read_dir(ngx_dir_t *dir)
+{
+    dir->de = readdir(dir->dir);
+
+    if (dir->de) {
+#if (NGX_HAVE_D_TYPE)
+        dir->type = dir->de->d_type;
+#else
+        dir->type = 0;
+#endif
+        return NGX_OK;
+    }
+
+    return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_open_glob(ngx_glob_t *gl)
+{
+    int  n;
+
+    n = glob((char *) gl->pattern, 0, NULL, &gl->pglob);
+
+    if (n == 0) {
+        return NGX_OK;
+    }
+
+#ifdef GLOB_NOMATCH
+
+    if (n == GLOB_NOMATCH && gl->test) {
+        return NGX_OK;
+    }
+
+#endif
+
+    return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_read_glob(ngx_glob_t *gl, ngx_str_t *name)
+{
+    size_t  count;
+
+#ifdef GLOB_NOMATCH
+    count = (size_t) gl->pglob.gl_pathc;
+#else
+    count = (size_t) gl->pglob.gl_matchc;
+#endif
+
+    if (gl->n < count) {
+
+        name->len = (size_t) ngx_strlen(gl->pglob.gl_pathv[gl->n]);
+        name->data = (u_char *) gl->pglob.gl_pathv[gl->n];
+        gl->n++;
+
+        return NGX_OK;
+    }
+
+    return NGX_DONE;
+}
+
+
+void
+ngx_close_glob(ngx_glob_t *gl)
+{
+    globfree(&gl->pglob);
+}
+
+
+ngx_err_t
+ngx_trylock_fd(ngx_fd_t fd)
+{
+    struct flock  fl;
+
+    ngx_memzero(&fl, sizeof(struct flock));
+    fl.l_type = F_WRLCK;
+    fl.l_whence = SEEK_SET;
+
+    if (fcntl(fd, F_SETLK, &fl) == -1) {
+        return ngx_errno;
+    }
+
+    return 0;
+}
+
+
+ngx_err_t
+ngx_lock_fd(ngx_fd_t fd)
+{
+    struct flock  fl;
+
+    ngx_memzero(&fl, sizeof(struct flock));
+    fl.l_type = F_WRLCK;
+    fl.l_whence = SEEK_SET;
+
+    if (fcntl(fd, F_SETLKW, &fl) == -1) {
+        return ngx_errno;
+    }
+
+    return 0;
+}
+
+
+ngx_err_t
+ngx_unlock_fd(ngx_fd_t fd)
+{
+    struct flock  fl;
+
+    ngx_memzero(&fl, sizeof(struct flock));
+    fl.l_type = F_UNLCK;
+    fl.l_whence = SEEK_SET;
+
+    if (fcntl(fd, F_SETLK, &fl) == -1) {
+        return  ngx_errno;
+    }
+
+    return 0;
+}
+
+
+#if (NGX_HAVE_POSIX_FADVISE) && !(NGX_HAVE_F_READAHEAD)
+
+ngx_int_t
+ngx_read_ahead(ngx_fd_t fd, size_t n)
+{
+    int  err;
+
+    err = posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);
+
+    if (err == 0) {
+        return 0;
+    }
+
+    ngx_set_errno(err);
+    return NGX_FILE_ERROR;
+}
+
+#endif
+
+
+#if (NGX_HAVE_O_DIRECT)
+
+ngx_int_t
+ngx_directio_on(ngx_fd_t fd)
+{
+    int  flags;
+
+    flags = fcntl(fd, F_GETFL);
+
+    if (flags == -1) {
+        return NGX_FILE_ERROR;
+    }
+
+    return fcntl(fd, F_SETFL, flags | O_DIRECT);
+}
+
+
+ngx_int_t
+ngx_directio_off(ngx_fd_t fd)
+{
+    int  flags;
+
+    flags = fcntl(fd, F_GETFL);
+
+    if (flags == -1) {
+        return NGX_FILE_ERROR;
+    }
+
+    return fcntl(fd, F_SETFL, flags & ~O_DIRECT);
+}
+
+#endif
+
+
+#if (NGX_HAVE_STATFS)
+
+size_t
+ngx_fs_bsize(u_char *name)
+{
+    struct statfs  fs;
+
+    if (statfs((char *) name, &fs) == -1) {
+        return 512;
+    }
+
+    if ((fs.f_bsize % 512) != 0) {
+        return 512;
+    }
+
+    return (size_t) fs.f_bsize;
+}
+
+#elif (NGX_HAVE_STATVFS)
+
+size_t
+ngx_fs_bsize(u_char *name)
+{
+    struct statvfs  fs;
+
+    if (statvfs((char *) name, &fs) == -1) {
+        return 512;
+    }
+
+    if ((fs.f_frsize % 512) != 0) {
+        return 512;
+    }
+
+    return (size_t) fs.f_frsize;
+}
+
+#else
+
+size_t
+ngx_fs_bsize(u_char *name)
+{
+    return 512;
+}
+
+#endif
diff --git a/nginx/src/os/unix/ngx_files.h b/nginx/src/os/unix/ngx_files.h
new file mode 100644 (file)
index 0000000..07872b1
--- /dev/null
@@ -0,0 +1,395 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_FILES_H_INCLUDED_
+#define _NGX_FILES_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef int                      ngx_fd_t;
+typedef struct stat              ngx_file_info_t;
+typedef ino_t                    ngx_file_uniq_t;
+
+
+typedef struct {
+    u_char                      *name;
+    size_t                       size;
+    void                        *addr;
+    ngx_fd_t                     fd;
+    ngx_log_t                   *log;
+} ngx_file_mapping_t;
+
+
+typedef struct {
+    DIR                         *dir;
+    struct dirent               *de;
+    struct stat                  info;
+
+    unsigned                     type:8;
+    unsigned                     valid_info:1;
+} ngx_dir_t;
+
+
+typedef struct {
+    size_t                       n;
+    glob_t                       pglob;
+    u_char                      *pattern;
+    ngx_log_t                   *log;
+    ngx_uint_t                   test;
+} ngx_glob_t;
+
+
+#define NGX_INVALID_FILE         -1
+#define NGX_FILE_ERROR           -1
+
+
+
+#ifdef __CYGWIN__
+
+#ifndef NGX_HAVE_CASELESS_FILESYSTEM
+#define NGX_HAVE_CASELESS_FILESYSTEM  1
+#endif
+
+#define ngx_open_file(name, mode, create, access)                            \
+    open((const char *) name, mode|create|O_BINARY, access)
+
+#else
+
+#define ngx_open_file(name, mode, create, access)                            \
+    open((const char *) name, mode|create, access)
+
+#endif
+
+#define ngx_open_file_n          "open()"
+
+#define NGX_FILE_RDONLY          O_RDONLY
+#define NGX_FILE_WRONLY          O_WRONLY
+#define NGX_FILE_RDWR            O_RDWR
+#define NGX_FILE_CREATE_OR_OPEN  O_CREAT
+#define NGX_FILE_OPEN            0
+#define NGX_FILE_TRUNCATE        (O_CREAT|O_TRUNC)
+#define NGX_FILE_APPEND          (O_WRONLY|O_APPEND)
+#define NGX_FILE_NONBLOCK        O_NONBLOCK
+
+#if (NGX_HAVE_OPENAT)
+#define NGX_FILE_NOFOLLOW        O_NOFOLLOW
+
+#if defined(O_DIRECTORY)
+#define NGX_FILE_DIRECTORY       O_DIRECTORY
+#else
+#define NGX_FILE_DIRECTORY       0
+#endif
+
+#if defined(O_SEARCH)
+#define NGX_FILE_SEARCH          (O_SEARCH|NGX_FILE_DIRECTORY)
+
+#elif defined(O_EXEC)
+#define NGX_FILE_SEARCH          (O_EXEC|NGX_FILE_DIRECTORY)
+
+#elif (NGX_HAVE_O_PATH)
+#define NGX_FILE_SEARCH          (O_PATH|O_RDONLY|NGX_FILE_DIRECTORY)
+
+#else
+#define NGX_FILE_SEARCH          (O_RDONLY|NGX_FILE_DIRECTORY)
+#endif
+
+#endif /* NGX_HAVE_OPENAT */
+
+#define NGX_FILE_DEFAULT_ACCESS  0644
+#define NGX_FILE_OWNER_ACCESS    0600
+
+
+#define ngx_close_file           close
+#define ngx_close_file_n         "close()"
+
+
+#define ngx_delete_file(name)    unlink((const char *) name)
+#define ngx_delete_file_n        "unlink()"
+
+
+ngx_fd_t ngx_open_tempfile(u_char *name, ngx_uint_t persistent,
+    ngx_uint_t access);
+#define ngx_open_tempfile_n      "open()"
+
+
+ssize_t ngx_read_file(ngx_file_t *file, u_char *buf, size_t size, off_t offset);
+#if (NGX_HAVE_PREAD)
+#define ngx_read_file_n          "pread()"
+#else
+#define ngx_read_file_n          "read()"
+#endif
+
+ssize_t ngx_write_file(ngx_file_t *file, u_char *buf, size_t size,
+    off_t offset);
+
+ssize_t ngx_write_chain_to_file(ngx_file_t *file, ngx_chain_t *ce,
+    off_t offset, ngx_pool_t *pool);
+
+
+#define ngx_read_fd              read
+#define ngx_read_fd_n            "read()"
+
+/*
+ * we use inlined function instead of simple #define
+ * because glibc 2.3 sets warn_unused_result attribute for write()
+ * and in this case gcc 4.3 ignores (void) cast
+ */
+static ngx_inline ssize_t
+ngx_write_fd(ngx_fd_t fd, void *buf, size_t n)
+{
+    return write(fd, buf, n);
+}
+
+#define ngx_write_fd_n           "write()"
+
+
+#define ngx_write_console        ngx_write_fd
+
+
+#define ngx_linefeed(p)          *p++ = LF;
+#define NGX_LINEFEED_SIZE        1
+#define NGX_LINEFEED             "\x0a"
+
+
+#define ngx_rename_file(o, n)    rename((const char *) o, (const char *) n)
+#define ngx_rename_file_n        "rename()"
+
+
+#define ngx_change_file_access(n, a) chmod((const char *) n, a)
+#define ngx_change_file_access_n "chmod()"
+
+
+ngx_int_t ngx_set_file_time(u_char *name, ngx_fd_t fd, time_t s);
+#define ngx_set_file_time_n      "utimes()"
+
+
+#define ngx_file_info(file, sb)  stat((const char *) file, sb)
+#define ngx_file_info_n          "stat()"
+
+#define ngx_fd_info(fd, sb)      fstat(fd, sb)
+#define ngx_fd_info_n            "fstat()"
+
+#define ngx_link_info(file, sb)  lstat((const char *) file, sb)
+#define ngx_link_info_n          "lstat()"
+
+#define ngx_is_dir(sb)           (S_ISDIR((sb)->st_mode))
+#define ngx_is_file(sb)          (S_ISREG((sb)->st_mode))
+#define ngx_is_link(sb)          (S_ISLNK((sb)->st_mode))
+#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_mtime(sb)       (sb)->st_mtime
+#define ngx_file_uniq(sb)        (sb)->st_ino
+
+
+ngx_int_t ngx_create_file_mapping(ngx_file_mapping_t *fm);
+void ngx_close_file_mapping(ngx_file_mapping_t *fm);
+
+
+#define ngx_realpath(p, r)       (u_char *) realpath((char *) p, (char *) r)
+#define ngx_realpath_n           "realpath()"
+#define ngx_getcwd(buf, size)    (getcwd((char *) buf, size) != NULL)
+#define ngx_getcwd_n             "getcwd()"
+#define ngx_path_separator(c)    ((c) == '/')
+
+
+#if defined(PATH_MAX)
+
+#define NGX_HAVE_MAX_PATH        1
+#define NGX_MAX_PATH             PATH_MAX
+
+#else
+
+#define NGX_MAX_PATH             4096
+
+#endif
+
+
+#define NGX_DIR_MASK_LEN         0
+
+
+ngx_int_t ngx_open_dir(ngx_str_t *name, ngx_dir_t *dir);
+#define ngx_open_dir_n           "opendir()"
+
+
+#define ngx_close_dir(d)         closedir((d)->dir)
+#define ngx_close_dir_n          "closedir()"
+
+
+ngx_int_t ngx_read_dir(ngx_dir_t *dir);
+#define ngx_read_dir_n           "readdir()"
+
+
+#define ngx_create_dir(name, access) mkdir((const char *) name, access)
+#define ngx_create_dir_n         "mkdir()"
+
+
+#define ngx_delete_dir(name)     rmdir((const char *) name)
+#define ngx_delete_dir_n         "rmdir()"
+
+
+#define ngx_dir_access(a)        (a | (a & 0444) >> 2)
+
+
+#define ngx_de_name(dir)         ((u_char *) (dir)->de->d_name)
+#if (NGX_HAVE_D_NAMLEN)
+#define ngx_de_namelen(dir)      (dir)->de->d_namlen
+#else
+#define ngx_de_namelen(dir)      ngx_strlen((dir)->de->d_name)
+#endif
+
+static ngx_inline ngx_int_t
+ngx_de_info(u_char *name, ngx_dir_t *dir)
+{
+    dir->type = 0;
+    return stat((const char *) name, &dir->info);
+}
+
+#define ngx_de_info_n            "stat()"
+#define ngx_de_link_info(name, dir)  lstat((const char *) name, &(dir)->info)
+#define ngx_de_link_info_n       "lstat()"
+
+#if (NGX_HAVE_D_TYPE)
+
+/*
+ * some file systems (e.g. XFS on Linux and CD9660 on FreeBSD)
+ * do not set dirent.d_type
+ */
+
+#define ngx_de_is_dir(dir)                                                   \
+    (((dir)->type) ? ((dir)->type == DT_DIR) : (S_ISDIR((dir)->info.st_mode)))
+#define ngx_de_is_file(dir)                                                  \
+    (((dir)->type) ? ((dir)->type == DT_REG) : (S_ISREG((dir)->info.st_mode)))
+#define ngx_de_is_link(dir)                                                  \
+    (((dir)->type) ? ((dir)->type == DT_LNK) : (S_ISLNK((dir)->info.st_mode)))
+
+#else
+
+#define ngx_de_is_dir(dir)       (S_ISDIR((dir)->info.st_mode))
+#define ngx_de_is_file(dir)      (S_ISREG((dir)->info.st_mode))
+#define ngx_de_is_link(dir)      (S_ISLNK((dir)->info.st_mode))
+
+#endif
+
+#define ngx_de_access(dir)       (((dir)->info.st_mode) & 0777)
+#define ngx_de_size(dir)         (dir)->info.st_size
+#define ngx_de_fs_size(dir)                                                  \
+    ngx_max((dir)->info.st_size, (dir)->info.st_blocks * 512)
+#define ngx_de_mtime(dir)        (dir)->info.st_mtime
+
+
+ngx_int_t ngx_open_glob(ngx_glob_t *gl);
+#define ngx_open_glob_n          "glob()"
+ngx_int_t ngx_read_glob(ngx_glob_t *gl, ngx_str_t *name);
+void ngx_close_glob(ngx_glob_t *gl);
+
+
+ngx_err_t ngx_trylock_fd(ngx_fd_t fd);
+ngx_err_t ngx_lock_fd(ngx_fd_t fd);
+ngx_err_t ngx_unlock_fd(ngx_fd_t fd);
+
+#define ngx_trylock_fd_n         "fcntl(F_SETLK, F_WRLCK)"
+#define ngx_lock_fd_n            "fcntl(F_SETLKW, F_WRLCK)"
+#define ngx_unlock_fd_n          "fcntl(F_SETLK, F_UNLCK)"
+
+
+#if (NGX_HAVE_F_READAHEAD)
+
+#define NGX_HAVE_READ_AHEAD      1
+
+#define ngx_read_ahead(fd, n)    fcntl(fd, F_READAHEAD, (int) n)
+#define ngx_read_ahead_n         "fcntl(fd, F_READAHEAD)"
+
+#elif (NGX_HAVE_POSIX_FADVISE)
+
+#define NGX_HAVE_READ_AHEAD      1
+
+ngx_int_t ngx_read_ahead(ngx_fd_t fd, size_t n);
+#define ngx_read_ahead_n         "posix_fadvise(POSIX_FADV_SEQUENTIAL)"
+
+#else
+
+#define ngx_read_ahead(fd, n)    0
+#define ngx_read_ahead_n         "ngx_read_ahead_n"
+
+#endif
+
+
+#if (NGX_HAVE_O_DIRECT)
+
+ngx_int_t ngx_directio_on(ngx_fd_t fd);
+#define ngx_directio_on_n        "fcntl(O_DIRECT)"
+
+ngx_int_t ngx_directio_off(ngx_fd_t fd);
+#define ngx_directio_off_n       "fcntl(!O_DIRECT)"
+
+#elif (NGX_HAVE_F_NOCACHE)
+
+#define ngx_directio_on(fd)      fcntl(fd, F_NOCACHE, 1)
+#define ngx_directio_on_n        "fcntl(F_NOCACHE, 1)"
+
+#elif (NGX_HAVE_DIRECTIO)
+
+#define ngx_directio_on(fd)      directio(fd, DIRECTIO_ON)
+#define ngx_directio_on_n        "directio(DIRECTIO_ON)"
+
+#else
+
+#define ngx_directio_on(fd)      0
+#define ngx_directio_on_n        "ngx_directio_on_n"
+
+#endif
+
+size_t ngx_fs_bsize(u_char *name);
+
+
+#if (NGX_HAVE_OPENAT)
+
+#define ngx_openat_file(fd, name, mode, create, access)                      \
+    openat(fd, (const char *) name, mode|create, access)
+
+#define ngx_openat_file_n        "openat()"
+
+#define ngx_file_at_info(fd, name, sb, flag)                                 \
+    fstatat(fd, (const char *) name, sb, flag)
+
+#define ngx_file_at_info_n       "fstatat()"
+
+#define NGX_AT_FDCWD             (ngx_fd_t) AT_FDCWD
+
+#endif
+
+
+#define ngx_stdout               STDOUT_FILENO
+#define ngx_stderr               STDERR_FILENO
+#define ngx_set_stderr(fd)       dup2(fd, STDERR_FILENO)
+#define ngx_set_stderr_n         "dup2(STDERR_FILENO)"
+
+
+#if (NGX_HAVE_FILE_AIO)
+
+ngx_int_t ngx_file_aio_init(ngx_file_t *file, ngx_pool_t *pool);
+ssize_t ngx_file_aio_read(ngx_file_t *file, u_char *buf, size_t size,
+    off_t offset, ngx_pool_t *pool);
+
+extern ngx_uint_t  ngx_file_aio;
+
+#endif
+
+#if (NGX_THREADS)
+ssize_t ngx_thread_read(ngx_file_t *file, u_char *buf, size_t size,
+    off_t offset, ngx_pool_t *pool);
+ssize_t ngx_thread_write_chain_to_file(ngx_file_t *file, ngx_chain_t *cl,
+    off_t offset, ngx_pool_t *pool);
+#endif
+
+
+#endif /* _NGX_FILES_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_freebsd.h b/nginx/src/os/unix/ngx_freebsd.h
new file mode 100644 (file)
index 0000000..4f93da5
--- /dev/null
@@ -0,0 +1,25 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_FREEBSD_H_INCLUDED_
+#define _NGX_FREEBSD_H_INCLUDED_
+
+
+void ngx_debug_init(void);
+ngx_chain_t *ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in,
+    off_t limit);
+
+extern int         ngx_freebsd_kern_osreldate;
+extern int         ngx_freebsd_hw_ncpu;
+extern u_long      ngx_freebsd_net_inet_tcp_sendspace;
+
+extern ngx_uint_t  ngx_freebsd_sendfile_nbytes_bug;
+extern ngx_uint_t  ngx_freebsd_use_tcp_nopush;
+extern ngx_uint_t  ngx_debug_malloc;
+
+
+#endif /* _NGX_FREEBSD_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_freebsd_config.h b/nginx/src/os/unix/ngx_freebsd_config.h
new file mode 100644 (file)
index 0000000..b7da48c
--- /dev/null
@@ -0,0 +1,123 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_FREEBSD_CONFIG_H_INCLUDED_
+#define _NGX_FREEBSD_CONFIG_H_INCLUDED_
+
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stddef.h>             /* offsetof() */
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <dirent.h>
+#include <glob.h>
+#include <time.h>
+#include <sys/param.h>          /* ALIGN() */
+#include <sys/mount.h>          /* statfs() */
+
+#include <sys/filio.h>          /* FIONBIO */
+#include <sys/uio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sched.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>        /* TCP_NODELAY, TCP_NOPUSH */
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/un.h>
+
+#include <libutil.h>            /* setproctitle() before 4.1 */
+#include <osreldate.h>
+#include <sys/sysctl.h>
+
+#include <dlfcn.h>
+
+
+#if __FreeBSD_version < 400017
+
+/*
+ * FreeBSD 3.x has no CMSG_SPACE() and CMSG_LEN() and has the broken CMSG_DATA()
+ */
+
+#undef  CMSG_SPACE
+#define CMSG_SPACE(l)       (ALIGN(sizeof(struct cmsghdr)) + ALIGN(l))
+
+#undef  CMSG_LEN
+#define CMSG_LEN(l)         (ALIGN(sizeof(struct cmsghdr)) + (l))
+
+#undef  CMSG_DATA
+#define CMSG_DATA(cmsg)     ((u_char *)(cmsg) + ALIGN(sizeof(struct cmsghdr)))
+
+#endif
+
+
+#include <ngx_auto_config.h>
+
+
+#if (NGX_HAVE_POSIX_SEM)
+#include <semaphore.h>
+#endif
+
+
+#if (NGX_HAVE_POLL)
+#include <poll.h>
+#endif
+
+
+#if (NGX_HAVE_KQUEUE)
+#include <sys/event.h>
+#endif
+
+
+#if (NGX_HAVE_FILE_AIO)
+#include <aio.h>
+typedef struct aiocb  ngx_aiocb_t;
+#endif
+
+
+#define NGX_LISTEN_BACKLOG        -1
+
+
+#ifdef __DragonFly__
+#define NGX_KEEPALIVE_FACTOR      1000
+#endif
+
+
+#ifndef IOV_MAX
+#define IOV_MAX   1024
+#endif
+
+
+#ifndef NGX_HAVE_INHERITED_NONBLOCK
+#define NGX_HAVE_INHERITED_NONBLOCK  1
+#endif
+
+
+#define NGX_HAVE_OS_SPECIFIC_INIT    1
+#define NGX_HAVE_DEBUG_MALLOC        1
+
+
+extern char **environ;
+extern char  *malloc_options;
+
+
+#endif /* _NGX_FREEBSD_CONFIG_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_freebsd_init.c b/nginx/src/os/unix/ngx_freebsd_init.c
new file mode 100644 (file)
index 0000000..1823f02
--- /dev/null
@@ -0,0 +1,262 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/* FreeBSD 3.0 at least */
+char    ngx_freebsd_kern_ostype[16];
+char    ngx_freebsd_kern_osrelease[128];
+int     ngx_freebsd_kern_osreldate;
+int     ngx_freebsd_hw_ncpu;
+int     ngx_freebsd_kern_ipc_somaxconn;
+u_long  ngx_freebsd_net_inet_tcp_sendspace;
+
+/* FreeBSD 4.9 */
+int     ngx_freebsd_machdep_hlt_logical_cpus;
+
+
+ngx_uint_t  ngx_freebsd_sendfile_nbytes_bug;
+ngx_uint_t  ngx_freebsd_use_tcp_nopush;
+
+ngx_uint_t  ngx_debug_malloc;
+
+
+static ngx_os_io_t ngx_freebsd_io = {
+    ngx_unix_recv,
+    ngx_readv_chain,
+    ngx_udp_unix_recv,
+    ngx_unix_send,
+    ngx_udp_unix_send,
+    ngx_udp_unix_sendmsg_chain,
+#if (NGX_HAVE_SENDFILE)
+    ngx_freebsd_sendfile_chain,
+    NGX_IO_SENDFILE
+#else
+    ngx_writev_chain,
+    0
+#endif
+};
+
+
+typedef struct {
+    char        *name;
+    void        *value;
+    size_t       size;
+    ngx_uint_t   exists;
+} sysctl_t;
+
+
+sysctl_t sysctls[] = {
+    { "hw.ncpu",
+      &ngx_freebsd_hw_ncpu,
+      sizeof(ngx_freebsd_hw_ncpu), 0 },
+
+    { "machdep.hlt_logical_cpus",
+      &ngx_freebsd_machdep_hlt_logical_cpus,
+      sizeof(ngx_freebsd_machdep_hlt_logical_cpus), 0 },
+
+    { "net.inet.tcp.sendspace",
+      &ngx_freebsd_net_inet_tcp_sendspace,
+      sizeof(ngx_freebsd_net_inet_tcp_sendspace), 0 },
+
+    { "kern.ipc.somaxconn",
+      &ngx_freebsd_kern_ipc_somaxconn,
+      sizeof(ngx_freebsd_kern_ipc_somaxconn), 0 },
+
+    { NULL, NULL, 0, 0 }
+};
+
+
+void
+ngx_debug_init(void)
+{
+#if (NGX_DEBUG_MALLOC)
+
+#if __FreeBSD_version >= 500014 && __FreeBSD_version < 1000011
+    _malloc_options = "J";
+#elif __FreeBSD_version < 500014
+    malloc_options = "J";
+#endif
+
+    ngx_debug_malloc = 1;
+
+#else
+    char  *mo;
+
+    mo = getenv("MALLOC_OPTIONS");
+
+    if (mo && ngx_strchr(mo, 'J')) {
+        ngx_debug_malloc = 1;
+    }
+#endif
+}
+
+
+ngx_int_t
+ngx_os_specific_init(ngx_log_t *log)
+{
+    int         version;
+    size_t      size;
+    ngx_err_t   err;
+    ngx_uint_t  i;
+
+    size = sizeof(ngx_freebsd_kern_ostype);
+    if (sysctlbyname("kern.ostype",
+                     ngx_freebsd_kern_ostype, &size, NULL, 0) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "sysctlbyname(kern.ostype) failed");
+
+        if (ngx_errno != NGX_ENOMEM) {
+            return NGX_ERROR;
+        }
+
+        ngx_freebsd_kern_ostype[size - 1] = '\0';
+    }
+
+    size = sizeof(ngx_freebsd_kern_osrelease);
+    if (sysctlbyname("kern.osrelease",
+                     ngx_freebsd_kern_osrelease, &size, NULL, 0) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "sysctlbyname(kern.osrelease) failed");
+
+        if (ngx_errno != NGX_ENOMEM) {
+            return NGX_ERROR;
+        }
+
+        ngx_freebsd_kern_osrelease[size - 1] = '\0';
+    }
+
+
+    size = sizeof(int);
+    if (sysctlbyname("kern.osreldate",
+                     &ngx_freebsd_kern_osreldate, &size, NULL, 0) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "sysctlbyname(kern.osreldate) failed");
+        return NGX_ERROR;
+    }
+
+    version = ngx_freebsd_kern_osreldate;
+
+
+#if (NGX_HAVE_SENDFILE)
+
+    /*
+     * The determination of the sendfile() "nbytes bug" is complex enough.
+     * There are two sendfile() syscalls: a new #393 has no bug while
+     * an old #336 has the bug in some versions and has not in others.
+     * Besides libc_r wrapper also emulates the bug in some versions.
+     * There is no way to say exactly if syscall #336 in FreeBSD circa 4.6
+     * has the bug.  We use the algorithm that is correct at least for
+     * RELEASEs and for syscalls only (not libc_r wrapper).
+     *
+     * 4.6.1-RELEASE and below have the bug
+     * 4.6.2-RELEASE and above have the new syscall
+     *
+     * We detect the new sendfile() syscall available at the compile time
+     * to allow an old binary to run correctly on an updated FreeBSD system.
+     */
+
+#if (__FreeBSD__ == 4 && __FreeBSD_version >= 460102) \
+    || __FreeBSD_version == 460002 || __FreeBSD_version >= 500039
+
+    /* a new syscall without the bug */
+
+    ngx_freebsd_sendfile_nbytes_bug = 0;
+
+#else
+
+    /* an old syscall that may have the bug */
+
+    ngx_freebsd_sendfile_nbytes_bug = 1;
+
+#endif
+
+#endif /* NGX_HAVE_SENDFILE */
+
+
+    if ((version < 500000 && version >= 440003) || version >= 500017) {
+        ngx_freebsd_use_tcp_nopush = 1;
+    }
+
+
+    for (i = 0; sysctls[i].name; i++) {
+        size = sysctls[i].size;
+
+        if (sysctlbyname(sysctls[i].name, sysctls[i].value, &size, NULL, 0)
+            == 0)
+        {
+            sysctls[i].exists = 1;
+            continue;
+        }
+
+        err = ngx_errno;
+
+        if (err == NGX_ENOENT) {
+            continue;
+        }
+
+        ngx_log_error(NGX_LOG_ALERT, log, err,
+                      "sysctlbyname(%s) failed", sysctls[i].name);
+        return NGX_ERROR;
+    }
+
+    if (ngx_freebsd_machdep_hlt_logical_cpus) {
+        ngx_ncpu = ngx_freebsd_hw_ncpu / 2;
+
+    } else {
+        ngx_ncpu = ngx_freebsd_hw_ncpu;
+    }
+
+    if (version < 600008 && ngx_freebsd_kern_ipc_somaxconn > 32767) {
+        ngx_log_error(NGX_LOG_ALERT, log, 0,
+                      "sysctl kern.ipc.somaxconn must be less than 32768");
+        return NGX_ERROR;
+    }
+
+    ngx_tcp_nodelay_and_tcp_nopush = 1;
+
+    ngx_os_io = ngx_freebsd_io;
+
+    return NGX_OK;
+}
+
+
+void
+ngx_os_specific_status(ngx_log_t *log)
+{
+    u_long      value;
+    ngx_uint_t  i;
+
+    ngx_log_error(NGX_LOG_NOTICE, log, 0, "OS: %s %s",
+                  ngx_freebsd_kern_ostype, ngx_freebsd_kern_osrelease);
+
+#ifdef __DragonFly_version
+    ngx_log_error(NGX_LOG_NOTICE, log, 0,
+                  "kern.osreldate: %d, built on %d",
+                  ngx_freebsd_kern_osreldate, __DragonFly_version);
+#else
+    ngx_log_error(NGX_LOG_NOTICE, log, 0,
+                  "kern.osreldate: %d, built on %d",
+                  ngx_freebsd_kern_osreldate, __FreeBSD_version);
+#endif
+
+    for (i = 0; sysctls[i].name; i++) {
+        if (sysctls[i].exists) {
+            if (sysctls[i].size == sizeof(long)) {
+                value = *(long *) sysctls[i].value;
+
+            } else {
+                value = *(int *) sysctls[i].value;
+            }
+
+            ngx_log_error(NGX_LOG_NOTICE, log, 0, "%s: %l",
+                          sysctls[i].name, value);
+        }
+    }
+}
diff --git a/nginx/src/os/unix/ngx_freebsd_sendfile_chain.c b/nginx/src/os/unix/ngx_freebsd_sendfile_chain.c
new file mode 100644 (file)
index 0000000..3d415bd
--- /dev/null
@@ -0,0 +1,333 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+/*
+ * Although FreeBSD sendfile() allows to pass a header and a trailer,
+ * it cannot send a header with a part of the file in one packet until
+ * FreeBSD 5.3.  Besides, over the fast ethernet connection sendfile()
+ * may send the partially filled packets, i.e. the 8 file pages may be sent
+ * as the 11 full 1460-bytes packets, then one incomplete 324-bytes packet,
+ * and then again the 11 full 1460-bytes packets.
+ *
+ * Therefore we use the TCP_NOPUSH option (similar to Linux's TCP_CORK)
+ * to postpone the sending - it not only sends a header and the first part of
+ * the file in one packet, but also sends the file pages in the full packets.
+ *
+ * But until FreeBSD 4.5 turning TCP_NOPUSH off does not flush a pending
+ * data that less than MSS, so that data may be sent with 5 second delay.
+ * So we do not use TCP_NOPUSH on FreeBSD prior to 4.5, although it can be used
+ * for non-keepalive HTTP connections.
+ */
+
+
+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;
+#endif
+
+    wev = c->write;
+
+    if (!wev->ready) {
+        return in;
+    }
+
+#if (NGX_HAVE_KQUEUE)
+
+    if ((ngx_event_flags & NGX_USE_KQUEUE_EVENT) && wev->pending_eof) {
+        (void) ngx_connection_error(c, wev->kq_errno,
+                               "kevent() reported about an closed connection");
+        wev->error = 1;
+        return NGX_CHAIN_ERROR;
+    }
+
+#endif
+
+    /* the maximum limit size is the maximum size_t value - the page size */
+
+    if (limit == 0 || limit > (off_t) (NGX_MAX_SIZE_T_VALUE - ngx_pagesize)) {
+        limit = NGX_MAX_SIZE_T_VALUE - ngx_pagesize;
+    }
+
+    send = 0;
+    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;
+
+    trailer.iovs = trailers;
+    trailer.nalloc = NGX_IOVS_PREALLOCATE;
+
+    for ( ;; ) {
+        eintr = 0;
+#if (NGX_HAVE_AIO_SENDFILE)
+        ebusy = 0;
+#endif
+        prev_send = send;
+
+        /* create the header iovec and coalesce the neighbouring bufs */
+
+        cl = ngx_output_chain_to_iovec(&header, in, limit - send, c->log);
+
+        if (cl == NGX_CHAIN_ERROR) {
+            return NGX_CHAIN_ERROR;
+        }
+
+        send += header.size;
+
+        if (cl && cl->buf->in_file && send < limit) {
+            file = cl->buf;
+
+            /* coalesce the neighbouring file bufs */
+
+            file_size = (size_t) ngx_chain_coalesce_file(&cl, limit - send);
+
+            send += file_size;
+
+            if (send < limit) {
+
+                /*
+                 * create the trailer iovec and coalesce the neighbouring bufs
+                 */
+
+                cl = ngx_output_chain_to_iovec(&trailer, cl, limit - send,
+                                               c->log);
+                if (cl == NGX_CHAIN_ERROR) {
+                    return NGX_CHAIN_ERROR;
+                }
+
+                send += trailer.size;
+
+            } else {
+                trailer.count = 0;
+            }
+
+            if (ngx_freebsd_use_tcp_nopush
+                && c->tcp_nopush == NGX_TCP_NOPUSH_UNSET)
+            {
+                if (ngx_tcp_nopush(c->fd) == -1) {
+                    err = ngx_socket_errno;
+
+                    /*
+                     * there is a tiny chance to be interrupted, however,
+                     * we continue a processing without the TCP_NOPUSH
+                     */
+
+                    if (err != NGX_EINTR) {
+                        wev->error = 1;
+                        (void) ngx_connection_error(c, err,
+                                                    ngx_tcp_nopush_n " failed");
+                        return NGX_CHAIN_ERROR;
+                    }
+
+                } else {
+                    c->tcp_nopush = NGX_TCP_NOPUSH_SET;
+
+                    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                                   "tcp_nopush");
+                }
+            }
+
+            /*
+             * sendfile() does unneeded work if sf_hdtr's count is 0,
+             * but corresponding pointer is not NULL
+             */
+
+            hdtr.headers = header.count ? header.iovs : NULL;
+            hdtr.hdr_cnt = header.count;
+            hdtr.trailers = trailer.count ? trailer.iovs : NULL;
+            hdtr.trl_cnt = trailer.count;
+
+            /*
+             * the "nbytes bug" of the old sendfile() syscall:
+             * http://bugs.freebsd.org/33771
+             */
+
+            if (!ngx_freebsd_sendfile_nbytes_bug) {
+                header.size = 0;
+            }
+
+            sent = 0;
+
+#if (NGX_HAVE_AIO_SENDFILE)
+            aio = file->file->aio;
+            flags = (aio && aio->preload_handler) ? SF_NODISKIO : 0;
+#endif
+
+            rc = sendfile(file->file->fd, c->fd, file->file_pos,
+                          file_size + header.size, &hdtr, &sent, flags);
+
+            if (rc == -1) {
+                err = ngx_errno;
+
+                switch (err) {
+                case NGX_EAGAIN:
+                    eagain = 1;
+                    break;
+
+                case NGX_EINTR:
+                    eintr = 1;
+                    break;
+
+#if (NGX_HAVE_AIO_SENDFILE)
+                case NGX_EBUSY:
+                    ebusy = 1;
+                    break;
+#endif
+
+                default:
+                    wev->error = 1;
+                    (void) ngx_connection_error(c, err, "sendfile() failed");
+                    return NGX_CHAIN_ERROR;
+                }
+
+                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, err,
+                               "sendfile() sent only %O bytes", sent);
+
+            /*
+             * sendfile() in FreeBSD 3.x-4.x may return value >= 0
+             * on success, although only 0 is documented
+             */
+
+            } else if (rc >= 0 && sent == 0) {
+
+                /*
+                 * if rc is OK and sent equal to 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,
+                         "sendfile() reported that \"%s\" was truncated at %O",
+                         file->file->name.data, file->file_pos);
+
+                return NGX_CHAIN_ERROR;
+            }
+
+            ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                           "sendfile: %d, @%O %O:%uz",
+                           rc, file->file_pos, sent, file_size + header.size);
+
+        } else {
+            n = ngx_writev(c, &header);
+
+            if (n == NGX_ERROR) {
+                return NGX_CHAIN_ERROR;
+            }
+
+            sent = (n == NGX_AGAIN) ? 0 : n;
+        }
+
+        c->sent += sent;
+
+        in = ngx_chain_update_sent(in, sent);
+
+#if (NGX_HAVE_AIO_SENDFILE)
+
+        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;
+                }
+
+            } else {
+                c->busy_count = 0;
+            }
+
+            n = aio->preload_handler(file);
+
+            if (n > 0) {
+                send = prev_send + sent;
+                continue;
+            }
+
+            return in;
+        }
+
+        if (flags == SF_NODISKIO) {
+            c->busy_count = 0;
+        }
+
+#endif
+
+        if (eagain) {
+
+            /*
+             * sendfile() may return EAGAIN, even if it has sent a whole file
+             * part, it indicates that the successive sendfile() call would
+             * return EAGAIN right away and would not send anything.
+             * We use it as a hint.
+             */
+
+            wev->ready = 0;
+            return in;
+        }
+
+        if (eintr) {
+            send = prev_send + sent;
+            continue;
+        }
+
+        if (send - prev_send != sent) {
+            wev->ready = 0;
+            return in;
+        }
+
+        if (send >= limit || in == NULL) {
+            return in;
+        }
+    }
+}
diff --git a/nginx/src/os/unix/ngx_gcc_atomic_amd64.h b/nginx/src/os/unix/ngx_gcc_atomic_amd64.h
new file mode 100644 (file)
index 0000000..159a297
--- /dev/null
@@ -0,0 +1,82 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#if (NGX_SMP)
+#define NGX_SMP_LOCK  "lock;"
+#else
+#define NGX_SMP_LOCK
+#endif
+
+
+/*
+ * "cmpxchgq  r, [m]":
+ *
+ *     if (rax == [m]) {
+ *         zf = 1;
+ *         [m] = r;
+ *     } else {
+ *         zf = 0;
+ *         rax = [m];
+ *     }
+ *
+ *
+ * The "r" is any register, %rax (%r0) - %r16.
+ * The "=a" and "a" are the %rax register.
+ * Although we can return result in any register, we use "a" because it is
+ * used in cmpxchgq anyway.  The result is actually in %al but not in $rax,
+ * however as the code is inlined gcc can test %al as well as %rax.
+ *
+ * The "cc" means that flags were changed.
+ */
+
+static ngx_inline ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+    ngx_atomic_uint_t set)
+{
+    u_char  res;
+
+    __asm__ volatile (
+
+         NGX_SMP_LOCK
+    "    cmpxchgq  %3, %1;   "
+    "    sete      %0;       "
+
+    : "=a" (res) : "m" (*lock), "a" (old), "r" (set) : "cc", "memory");
+
+    return res;
+}
+
+
+/*
+ * "xaddq  r, [m]":
+ *
+ *     temp = [m];
+ *     [m] += r;
+ *     r = temp;
+ *
+ *
+ * The "+r" is any register, %rax (%r0) - %r16.
+ * The "cc" means that flags were changed.
+ */
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+    __asm__ volatile (
+
+         NGX_SMP_LOCK
+    "    xaddq  %0, %1;   "
+
+    : "+r" (add) : "m" (*value) : "cc", "memory");
+
+    return add;
+}
+
+
+#define ngx_memory_barrier()    __asm__ volatile ("" ::: "memory")
+
+#define ngx_cpu_pause()         __asm__ ("pause")
diff --git a/nginx/src/os/unix/ngx_gcc_atomic_ppc.h b/nginx/src/os/unix/ngx_gcc_atomic_ppc.h
new file mode 100644 (file)
index 0000000..45afc4b
--- /dev/null
@@ -0,0 +1,155 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+/*
+ * The ppc assembler treats ";" as comment, so we have to use "\n".
+ * The minus in "bne-" is a hint for the branch prediction unit that
+ * this branch is unlikely to be taken.
+ * The "1b" means the nearest backward label "1" and the "1f" means
+ * the nearest forward label "1".
+ *
+ * The "b" means that the base registers can be used only, i.e.
+ * any register except r0.  The r0 register always has a zero value and
+ * could not be used in "addi  r0, r0, 1".
+ * The "=&b" means that no input registers can be used.
+ *
+ * "sync"    read and write barriers
+ * "isync"   read barrier, is faster than "sync"
+ * "eieio"   write barrier, is faster than "sync"
+ * "lwsync"  write barrier, is faster than "eieio" on ppc64
+ */
+
+#if (NGX_PTR_SIZE == 8)
+
+static ngx_inline ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+    ngx_atomic_uint_t set)
+{
+    ngx_atomic_uint_t  res, temp;
+
+    __asm__ volatile (
+
+    "    li      %0, 0       \n" /* preset "0" to "res"                      */
+    "    lwsync              \n" /* write barrier                            */
+    "1:                      \n"
+    "    ldarx   %1, 0, %2   \n" /* load from [lock] into "temp"             */
+                                 /*   and store reservation                  */
+    "    cmpd    %1, %3      \n" /* compare "temp" and "old"                 */
+    "    bne-    2f          \n" /* not equal                                */
+    "    stdcx.  %4, 0, %2   \n" /* store "set" into [lock] if reservation   */
+                                 /*   is not cleared                         */
+    "    bne-    1b          \n" /* the reservation was cleared              */
+    "    isync               \n" /* read barrier                             */
+    "    li      %0, 1       \n" /* set "1" to "res"                         */
+    "2:                      \n"
+
+    : "=&b" (res), "=&b" (temp)
+    : "b" (lock), "b" (old), "b" (set)
+    : "cc", "memory");
+
+    return res;
+}
+
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+    ngx_atomic_uint_t  res, temp;
+
+    __asm__ volatile (
+
+    "    lwsync              \n" /* write barrier                            */
+    "1:  ldarx   %0, 0, %2   \n" /* load from [value] into "res"             */
+                                 /*   and store reservation                  */
+    "    add     %1, %0, %3  \n" /* "res" + "add" store in "temp"            */
+    "    stdcx.  %1, 0, %2   \n" /* store "temp" into [value] if reservation */
+                                 /*   is not cleared                         */
+    "    bne-    1b          \n" /* try again if reservation was cleared     */
+    "    isync               \n" /* read barrier                             */
+
+    : "=&b" (res), "=&b" (temp)
+    : "b" (value), "b" (add)
+    : "cc", "memory");
+
+    return res;
+}
+
+
+#if (NGX_SMP)
+#define ngx_memory_barrier()                                                  \
+    __asm__ volatile ("isync  \n  lwsync  \n" ::: "memory")
+#else
+#define ngx_memory_barrier()   __asm__ volatile ("" ::: "memory")
+#endif
+
+#else
+
+static ngx_inline ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+    ngx_atomic_uint_t set)
+{
+    ngx_atomic_uint_t  res, temp;
+
+    __asm__ volatile (
+
+    "    li      %0, 0       \n" /* preset "0" to "res"                      */
+    "    eieio               \n" /* write barrier                            */
+    "1:                      \n"
+    "    lwarx   %1, 0, %2   \n" /* load from [lock] into "temp"             */
+                                 /*   and store reservation                  */
+    "    cmpw    %1, %3      \n" /* compare "temp" and "old"                 */
+    "    bne-    2f          \n" /* not equal                                */
+    "    stwcx.  %4, 0, %2   \n" /* store "set" into [lock] if reservation   */
+                                 /*   is not cleared                         */
+    "    bne-    1b          \n" /* the reservation was cleared              */
+    "    isync               \n" /* read barrier                             */
+    "    li      %0, 1       \n" /* set "1" to "res"                         */
+    "2:                      \n"
+
+    : "=&b" (res), "=&b" (temp)
+    : "b" (lock), "b" (old), "b" (set)
+    : "cc", "memory");
+
+    return res;
+}
+
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+    ngx_atomic_uint_t  res, temp;
+
+    __asm__ volatile (
+
+    "    eieio               \n" /* write barrier                            */
+    "1:  lwarx   %0, 0, %2   \n" /* load from [value] into "res"             */
+                                 /*   and store reservation                  */
+    "    add     %1, %0, %3  \n" /* "res" + "add" store in "temp"            */
+    "    stwcx.  %1, 0, %2   \n" /* store "temp" into [value] if reservation */
+                                 /*   is not cleared                         */
+    "    bne-    1b          \n" /* try again if reservation was cleared     */
+    "    isync               \n" /* read barrier                             */
+
+    : "=&b" (res), "=&b" (temp)
+    : "b" (value), "b" (add)
+    : "cc", "memory");
+
+    return res;
+}
+
+
+#if (NGX_SMP)
+#define ngx_memory_barrier()                                                  \
+    __asm__ volatile ("isync  \n  eieio  \n" ::: "memory")
+#else
+#define ngx_memory_barrier()   __asm__ volatile ("" ::: "memory")
+#endif
+
+#endif
+
+
+#define ngx_cpu_pause()
diff --git a/nginx/src/os/unix/ngx_gcc_atomic_sparc64.h b/nginx/src/os/unix/ngx_gcc_atomic_sparc64.h
new file mode 100644 (file)
index 0000000..a84db35
--- /dev/null
@@ -0,0 +1,82 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+/*
+ * "casa   [r1] 0x80, r2, r0"  and
+ * "casxa  [r1] 0x80, r2, r0"  do the following:
+ *
+ *     if ([r1] == r2) {
+ *         swap(r0, [r1]);
+ *     } else {
+ *         r0 = [r1];
+ *     }
+ *
+ * so "r0 == r2" means that the operation was successful.
+ *
+ *
+ * The "r" means the general register.
+ * The "+r" means the general register used for both input and output.
+ */
+
+
+#if (NGX_PTR_SIZE == 4)
+#define NGX_CASA  "casa"
+#else
+#define NGX_CASA  "casxa"
+#endif
+
+
+static ngx_inline ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+    ngx_atomic_uint_t set)
+{
+    __asm__ volatile (
+
+    NGX_CASA " [%1] 0x80, %2, %0"
+
+    : "+r" (set) : "r" (lock), "r" (old) : "memory");
+
+    return (set == old);
+}
+
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+    ngx_atomic_uint_t  old, res;
+
+    old = *value;
+
+    for ( ;; ) {
+
+        res = old + add;
+
+        __asm__ volatile (
+
+        NGX_CASA " [%1] 0x80, %2, %0"
+
+        : "+r" (res) : "r" (value), "r" (old) : "memory");
+
+        if (res == old) {
+            return res;
+        }
+
+        old = res;
+    }
+}
+
+
+#if (NGX_SMP)
+#define ngx_memory_barrier()                                                  \
+            __asm__ volatile (                                                \
+            "membar #LoadLoad | #LoadStore | #StoreStore | #StoreLoad"        \
+            ::: "memory")
+#else
+#define ngx_memory_barrier()   __asm__ volatile ("" ::: "memory")
+#endif
+
+#define ngx_cpu_pause()
diff --git a/nginx/src/os/unix/ngx_gcc_atomic_x86.h b/nginx/src/os/unix/ngx_gcc_atomic_x86.h
new file mode 100644 (file)
index 0000000..54e01ae
--- /dev/null
@@ -0,0 +1,127 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#if (NGX_SMP)
+#define NGX_SMP_LOCK  "lock;"
+#else
+#define NGX_SMP_LOCK
+#endif
+
+
+/*
+ * "cmpxchgl  r, [m]":
+ *
+ *     if (eax == [m]) {
+ *         zf = 1;
+ *         [m] = r;
+ *     } else {
+ *         zf = 0;
+ *         eax = [m];
+ *     }
+ *
+ *
+ * The "r" means the general register.
+ * The "=a" and "a" are the %eax register.
+ * Although we can return result in any register, we use "a" because it is
+ * used in cmpxchgl anyway.  The result is actually in %al but not in %eax,
+ * however, as the code is inlined gcc can test %al as well as %eax,
+ * and icc adds "movzbl %al, %eax" by itself.
+ *
+ * The "cc" means that flags were changed.
+ */
+
+static ngx_inline ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+    ngx_atomic_uint_t set)
+{
+    u_char  res;
+
+    __asm__ volatile (
+
+         NGX_SMP_LOCK
+    "    cmpxchgl  %3, %1;   "
+    "    sete      %0;       "
+
+    : "=a" (res) : "m" (*lock), "a" (old), "r" (set) : "cc", "memory");
+
+    return res;
+}
+
+
+/*
+ * "xaddl  r, [m]":
+ *
+ *     temp = [m];
+ *     [m] += r;
+ *     r = temp;
+ *
+ *
+ * The "+r" means the general register.
+ * The "cc" means that flags were changed.
+ */
+
+
+#if !(( __GNUC__ == 2 && __GNUC_MINOR__ <= 7 ) || ( __INTEL_COMPILER >= 800 ))
+
+/*
+ * icc 8.1 and 9.0 compile broken code with -march=pentium4 option:
+ * ngx_atomic_fetch_add() always return the input "add" value,
+ * so we use the gcc 2.7 version.
+ *
+ * icc 8.1 and 9.0 with -march=pentiumpro option or icc 7.1 compile
+ * correct code.
+ */
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+    __asm__ volatile (
+
+         NGX_SMP_LOCK
+    "    xaddl  %0, %1;   "
+
+    : "+r" (add) : "m" (*value) : "cc", "memory");
+
+    return add;
+}
+
+
+#else
+
+/*
+ * gcc 2.7 does not support "+r", so we have to use the fixed
+ * %eax ("=a" and "a") and this adds two superfluous instructions in the end
+ * of code, something like this: "mov %eax, %edx / mov %edx, %eax".
+ */
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+    ngx_atomic_uint_t  old;
+
+    __asm__ volatile (
+
+         NGX_SMP_LOCK
+    "    xaddl  %2, %1;   "
+
+    : "=a" (old) : "m" (*value), "a" (add) : "cc", "memory");
+
+    return old;
+}
+
+#endif
+
+
+/*
+ * on x86 the write operations go in a program order, so we need only
+ * to disable the gcc reorder optimizations
+ */
+
+#define ngx_memory_barrier()    __asm__ volatile ("" ::: "memory")
+
+/* old "as" does not support "pause" opcode */
+#define ngx_cpu_pause()         __asm__ (".byte 0xf3, 0x90")
diff --git a/nginx/src/os/unix/ngx_linux.h b/nginx/src/os/unix/ngx_linux.h
new file mode 100644 (file)
index 0000000..13d654e
--- /dev/null
@@ -0,0 +1,16 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_LINUX_H_INCLUDED_
+#define _NGX_LINUX_H_INCLUDED_
+
+
+ngx_chain_t *ngx_linux_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in,
+    off_t limit);
+
+
+#endif /* _NGX_LINUX_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_linux_aio_read.c b/nginx/src/os/unix/ngx_linux_aio_read.c
new file mode 100644 (file)
index 0000000..9f0a6c1
--- /dev/null
@@ -0,0 +1,148 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+extern int            ngx_eventfd;
+extern aio_context_t  ngx_aio_ctx;
+
+
+static void ngx_file_aio_event_handler(ngx_event_t *ev);
+
+
+static int
+io_submit(aio_context_t ctx, long n, struct iocb **paiocb)
+{
+    return syscall(SYS_io_submit, ctx, n, paiocb);
+}
+
+
+ngx_int_t
+ngx_file_aio_init(ngx_file_t *file, ngx_pool_t *pool)
+{
+    ngx_event_aio_t  *aio;
+
+    aio = ngx_pcalloc(pool, sizeof(ngx_event_aio_t));
+    if (aio == NULL) {
+        return NGX_ERROR;
+    }
+
+    aio->file = file;
+    aio->fd = file->fd;
+    aio->event.data = aio;
+    aio->event.ready = 1;
+    aio->event.log = file->log;
+
+    file->aio = aio;
+
+    return NGX_OK;
+}
+
+
+ssize_t
+ngx_file_aio_read(ngx_file_t *file, u_char *buf, size_t size, off_t offset,
+    ngx_pool_t *pool)
+{
+    ngx_err_t         err;
+    struct iocb      *piocb[1];
+    ngx_event_t      *ev;
+    ngx_event_aio_t  *aio;
+
+    if (!ngx_file_aio) {
+        return ngx_read_file(file, buf, size, offset);
+    }
+
+    if (file->aio == NULL && ngx_file_aio_init(file, pool) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    aio = file->aio;
+    ev = &aio->event;
+
+    if (!ev->ready) {
+        ngx_log_error(NGX_LOG_ALERT, file->log, 0,
+                      "second aio post for \"%V\"", &file->name);
+        return NGX_AGAIN;
+    }
+
+    ngx_log_debug4(NGX_LOG_DEBUG_CORE, file->log, 0,
+                   "aio complete:%d @%O:%uz %V",
+                   ev->complete, offset, size, &file->name);
+
+    if (ev->complete) {
+        ev->active = 0;
+        ev->complete = 0;
+
+        if (aio->res >= 0) {
+            ngx_set_errno(0);
+            return aio->res;
+        }
+
+        ngx_set_errno(-aio->res);
+
+        ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+                      "aio read \"%s\" failed", file->name.data);
+
+        return NGX_ERROR;
+    }
+
+    ngx_memzero(&aio->aiocb, sizeof(struct iocb));
+
+    aio->aiocb.aio_data = (uint64_t) (uintptr_t) ev;
+    aio->aiocb.aio_lio_opcode = IOCB_CMD_PREAD;
+    aio->aiocb.aio_fildes = file->fd;
+    aio->aiocb.aio_buf = (uint64_t) (uintptr_t) buf;
+    aio->aiocb.aio_nbytes = size;
+    aio->aiocb.aio_offset = offset;
+    aio->aiocb.aio_flags = IOCB_FLAG_RESFD;
+    aio->aiocb.aio_resfd = ngx_eventfd;
+
+    ev->handler = ngx_file_aio_event_handler;
+
+    piocb[0] = &aio->aiocb;
+
+    if (io_submit(ngx_aio_ctx, 1, piocb) == 1) {
+        ev->active = 1;
+        ev->ready = 0;
+        ev->complete = 0;
+
+        return NGX_AGAIN;
+    }
+
+    err = ngx_errno;
+
+    if (err == NGX_EAGAIN) {
+        return ngx_read_file(file, buf, size, offset);
+    }
+
+    ngx_log_error(NGX_LOG_CRIT, file->log, err,
+                  "io_submit(\"%V\") failed", &file->name);
+
+    if (err == NGX_ENOSYS) {
+        ngx_file_aio = 0;
+        return ngx_read_file(file, buf, size, offset);
+    }
+
+    return NGX_ERROR;
+}
+
+
+static void
+ngx_file_aio_event_handler(ngx_event_t *ev)
+{
+    ngx_event_aio_t  *aio;
+
+    aio = ev->data;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_CORE, ev->log, 0,
+                   "aio event handler fd:%d %V", aio->fd, &aio->file->name);
+
+    aio->handler(ev);
+}
diff --git a/nginx/src/os/unix/ngx_linux_config.h b/nginx/src/os/unix/ngx_linux_config.h
new file mode 100644 (file)
index 0000000..3036cae
--- /dev/null
@@ -0,0 +1,128 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_LINUX_CONFIG_H_INCLUDED_
+#define _NGX_LINUX_CONFIG_H_INCLUDED_
+
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE             /* pread(), pwrite(), gethostname() */
+#endif
+
+#define _FILE_OFFSET_BITS  64
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stddef.h>             /* offsetof() */
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <dirent.h>
+#include <glob.h>
+#include <sys/vfs.h>            /* statfs() */
+
+#include <sys/uio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sched.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>        /* TCP_NODELAY, TCP_CORK */
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/un.h>
+
+#include <time.h>               /* tzset() */
+#include <malloc.h>             /* memalign() */
+#include <limits.h>             /* IOV_MAX */
+#include <sys/ioctl.h>
+#include <crypt.h>
+#include <sys/utsname.h>        /* uname() */
+
+#include <dlfcn.h>
+
+
+#include <ngx_auto_config.h>
+
+
+#if (NGX_HAVE_POSIX_SEM)
+#include <semaphore.h>
+#endif
+
+
+#if (NGX_HAVE_SYS_PRCTL_H)
+#include <sys/prctl.h>
+#endif
+
+
+#if (NGX_HAVE_SENDFILE64)
+#include <sys/sendfile.h>
+#else
+extern ssize_t sendfile(int s, int fd, int32_t *offset, size_t size);
+#define NGX_SENDFILE_LIMIT  0x80000000
+#endif
+
+
+#if (NGX_HAVE_POLL)
+#include <poll.h>
+#endif
+
+
+#if (NGX_HAVE_EPOLL)
+#include <sys/epoll.h>
+#endif
+
+
+#if (NGX_HAVE_SYS_EVENTFD_H)
+#include <sys/eventfd.h>
+#endif
+#include <sys/syscall.h>
+#if (NGX_HAVE_FILE_AIO)
+#include <linux/aio_abi.h>
+typedef struct iocb  ngx_aiocb_t;
+#endif
+
+
+#if (NGX_HAVE_CAPABILITIES)
+#include <linux/capability.h>
+#endif
+
+
+#define NGX_LISTEN_BACKLOG        511
+
+
+#ifndef NGX_HAVE_SO_SNDLOWAT
+/* setsockopt(SO_SNDLOWAT) returns ENOPROTOOPT */
+#define NGX_HAVE_SO_SNDLOWAT         0
+#endif
+
+
+#ifndef NGX_HAVE_INHERITED_NONBLOCK
+#define NGX_HAVE_INHERITED_NONBLOCK  0
+#endif
+
+
+#define NGX_HAVE_OS_SPECIFIC_INIT    1
+#define ngx_debug_init()
+
+
+extern char **environ;
+
+
+#endif /* _NGX_LINUX_CONFIG_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_linux_init.c b/nginx/src/os/unix/ngx_linux_init.c
new file mode 100644 (file)
index 0000000..a8cf6a0
--- /dev/null
@@ -0,0 +1,60 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+u_char  ngx_linux_kern_ostype[50];
+u_char  ngx_linux_kern_osrelease[50];
+
+
+static ngx_os_io_t ngx_linux_io = {
+    ngx_unix_recv,
+    ngx_readv_chain,
+    ngx_udp_unix_recv,
+    ngx_unix_send,
+    ngx_udp_unix_send,
+    ngx_udp_unix_sendmsg_chain,
+#if (NGX_HAVE_SENDFILE)
+    ngx_linux_sendfile_chain,
+    NGX_IO_SENDFILE
+#else
+    ngx_writev_chain,
+    0
+#endif
+};
+
+
+ngx_int_t
+ngx_os_specific_init(ngx_log_t *log)
+{
+    struct utsname  u;
+
+    if (uname(&u) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "uname() failed");
+        return NGX_ERROR;
+    }
+
+    (void) ngx_cpystrn(ngx_linux_kern_ostype, (u_char *) u.sysname,
+                       sizeof(ngx_linux_kern_ostype));
+
+    (void) ngx_cpystrn(ngx_linux_kern_osrelease, (u_char *) u.release,
+                       sizeof(ngx_linux_kern_osrelease));
+
+    ngx_os_io = ngx_linux_io;
+
+    return NGX_OK;
+}
+
+
+void
+ngx_os_specific_status(ngx_log_t *log)
+{
+    ngx_log_error(NGX_LOG_NOTICE, log, 0, "OS: %s %s",
+                  ngx_linux_kern_ostype, ngx_linux_kern_osrelease);
+}
diff --git a/nginx/src/os/unix/ngx_linux_sendfile_chain.c b/nginx/src/os/unix/ngx_linux_sendfile_chain.c
new file mode 100644 (file)
index 0000000..158f433
--- /dev/null
@@ -0,0 +1,442 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+static ssize_t ngx_linux_sendfile(ngx_connection_t *c, ngx_buf_t *file,
+    size_t size);
+
+#if (NGX_THREADS)
+#include <ngx_thread_pool.h>
+
+#if !(NGX_HAVE_SENDFILE64)
+#error sendfile64() is required!
+#endif
+
+static ssize_t ngx_linux_sendfile_thread(ngx_connection_t *c, ngx_buf_t *file,
+    size_t size);
+static void ngx_linux_sendfile_thread_handler(void *data, ngx_log_t *log);
+#endif
+
+
+/*
+ * On Linux up to 2.4.21 sendfile() (syscall #187) works with 32-bit
+ * offsets only, and the including <sys/sendfile.h> breaks the compiling,
+ * if off_t is 64 bit wide.  So we use own sendfile() definition, where offset
+ * parameter is int32_t, and use sendfile() for the file parts below 2G only,
+ * see src/os/unix/ngx_linux_config.h
+ *
+ * Linux 2.4.21 has the new sendfile64() syscall #239.
+ *
+ * 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.
+ */
+
+#define NGX_SENDFILE_MAXSIZE  2147483647L
+
+
+ngx_chain_t *
+ngx_linux_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
+{
+    int            tcp_nodelay;
+    off_t          send, prev_send;
+    size_t         file_size, sent;
+    ssize_t        n;
+    ngx_err_t      err;
+    ngx_buf_t     *file;
+    ngx_event_t   *wev;
+    ngx_chain_t   *cl;
+    ngx_iovec_t    header;
+    struct iovec   headers[NGX_IOVS_PREALLOCATE];
+
+    wev = c->write;
+
+    if (!wev->ready) {
+        return in;
+    }
+
+
+    /* the maximum limit size is 2G-1 - the page size */
+
+    if (limit == 0 || limit > (off_t) (NGX_SENDFILE_MAXSIZE - ngx_pagesize)) {
+        limit = NGX_SENDFILE_MAXSIZE - ngx_pagesize;
+    }
+
+
+    send = 0;
+
+    header.iovs = headers;
+    header.nalloc = NGX_IOVS_PREALLOCATE;
+
+    for ( ;; ) {
+        prev_send = send;
+
+        /* create the iovec and coalesce the neighbouring bufs */
+
+        cl = ngx_output_chain_to_iovec(&header, in, limit - send, c->log);
+
+        if (cl == NGX_CHAIN_ERROR) {
+            return NGX_CHAIN_ERROR;
+        }
+
+        send += header.size;
+
+        /* set TCP_CORK if there is a header before a file */
+
+        if (c->tcp_nopush == NGX_TCP_NOPUSH_UNSET
+            && header.count != 0
+            && cl
+            && cl->buf->in_file)
+        {
+            /* the TCP_CORK and TCP_NODELAY are mutually exclusive */
+
+            if (c->tcp_nodelay == NGX_TCP_NODELAY_SET) {
+
+                tcp_nodelay = 0;
+
+                if (ngxvcl_kvfd_setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY,
+                               (const void *) &tcp_nodelay, sizeof(int)) == -1)
+                {
+                    err = ngx_socket_errno;
+
+                    /*
+                     * there is a tiny chance to be interrupted, however,
+                     * we continue a processing with the TCP_NODELAY
+                     * and without the TCP_CORK
+                     */
+
+                    if (err != NGX_EINTR) {
+                        wev->error = 1;
+                        ngx_connection_error(c, err,
+                                             "setsockopt(TCP_NODELAY) failed");
+                        return NGX_CHAIN_ERROR;
+                    }
+
+                } else {
+                    c->tcp_nodelay = NGX_TCP_NODELAY_UNSET;
+
+                    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                                   "no tcp_nodelay");
+                }
+            }
+
+            if (c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) {
+
+                if (ngx_tcp_nopush(c->fd) == -1) {
+                    err = ngx_socket_errno;
+
+                    /*
+                     * there is a tiny chance to be interrupted, however,
+                     * we continue a processing without the TCP_CORK
+                     */
+
+                    if (err != NGX_EINTR) {
+                        wev->error = 1;
+                        ngx_connection_error(c, err,
+                                             ngx_tcp_nopush_n " failed");
+                        return NGX_CHAIN_ERROR;
+                    }
+
+                } else {
+                    c->tcp_nopush = NGX_TCP_NOPUSH_SET;
+
+                    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                                   "tcp_nopush");
+                }
+            }
+        }
+
+        /* get the file buf */
+
+        if (header.count == 0 && cl && cl->buf->in_file && send < limit) {
+            file = cl->buf;
+
+            /* coalesce the neighbouring file bufs */
+
+            file_size = (size_t) ngx_chain_coalesce_file(&cl, limit - send);
+
+            send += file_size;
+#if 1
+            if (file_size == 0) {
+                ngx_debug_point();
+                return NGX_CHAIN_ERROR;
+            }
+#endif
+
+            n = ngx_linux_sendfile(c, file, file_size);
+
+            if (n == NGX_ERROR) {
+                return NGX_CHAIN_ERROR;
+            }
+
+            if (n == NGX_DONE) {
+                /* thread task posted */
+                return in;
+            }
+
+            sent = (n == NGX_AGAIN) ? 0 : n;
+
+        } else {
+            n = ngx_writev(c, &header);
+
+            if (n == NGX_ERROR) {
+                return NGX_CHAIN_ERROR;
+            }
+
+            sent = (n == NGX_AGAIN) ? 0 : n;
+        }
+
+        c->sent += sent;
+
+        in = ngx_chain_update_sent(in, sent);
+
+        if (n == NGX_AGAIN) {
+            wev->ready = 0;
+            return in;
+        }
+
+        if ((size_t) (send - prev_send) != sent) {
+
+            /*
+             * sendfile() on Linux 4.3+ might be interrupted at any time,
+             * and provides no indication if it was interrupted or not,
+             * so we have to retry till an explicit EAGAIN
+             *
+             * sendfile() in threads can also report less bytes written
+             * than we are prepared to send now, since it was started in
+             * some point in the past, so we again have to retry
+             */
+
+            send = prev_send + sent;
+            continue;
+        }
+
+        if (send >= limit || in == NULL) {
+            return in;
+        }
+    }
+}
+
+
+static ssize_t
+ngx_linux_sendfile(ngx_connection_t *c, ngx_buf_t *file, size_t size)
+{
+#if (NGX_HAVE_SENDFILE64)
+    off_t      offset;
+#else
+    int32_t    offset;
+#endif
+    ssize_t    n;
+    ngx_err_t  err;
+
+#if (NGX_THREADS)
+
+    if (file->file->thread_handler) {
+        return ngx_linux_sendfile_thread(c, file, size);
+    }
+
+#endif
+
+#if (NGX_HAVE_SENDFILE64)
+    offset = file->file_pos;
+#else
+    offset = (int32_t) file->file_pos;
+#endif
+
+eintr:
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "sendfile: @%O %uz", file->file_pos, size);
+
+    n = ngxvcl_sendfile(c->fd, file->file->fd, &offset, size);
+
+    if (n == -1) {
+        err = ngx_errno;
+
+        switch (err) {
+        case NGX_EAGAIN:
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+                           "sendfile() is not ready");
+            return NGX_AGAIN;
+
+        case NGX_EINTR:
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+                           "sendfile() was interrupted");
+            goto eintr;
+
+        default:
+            c->write->error = 1;
+            ngx_connection_error(c, err, "sendfile() failed");
+            return NGX_ERROR;
+        }
+    }
+
+    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,
+                      "sendfile() reported that \"%s\" was truncated at %O",
+                      file->file->name.data, file->file_pos);
+
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "sendfile: %z of %uz @%O",
+                   n, size, file->file_pos);
+
+    return n;
+}
+
+
+#if (NGX_THREADS)
+
+typedef struct {
+    ngx_buf_t     *file;
+    ngx_socket_t   socket;
+    size_t         size;
+
+    size_t         sent;
+    ngx_err_t      err;
+} ngx_linux_sendfile_ctx_t;
+
+
+static ssize_t
+ngx_linux_sendfile_thread(ngx_connection_t *c, ngx_buf_t *file, size_t size)
+{
+    ngx_event_t               *wev;
+    ngx_thread_task_t         *task;
+    ngx_linux_sendfile_ctx_t  *ctx;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_CORE, c->log, 0,
+                   "linux sendfile thread: %d, %uz, %O",
+                   file->file->fd, size, file->file_pos);
+
+    task = c->sendfile_task;
+
+    if (task == NULL) {
+        task = ngx_thread_task_alloc(c->pool, sizeof(ngx_linux_sendfile_ctx_t));
+        if (task == NULL) {
+            return NGX_ERROR;
+        }
+
+        task->handler = ngx_linux_sendfile_thread_handler;
+
+        c->sendfile_task = task;
+    }
+
+    ctx = task->ctx;
+    wev = c->write;
+
+    if (task->event.complete) {
+        task->event.complete = 0;
+
+        if (ctx->err == NGX_EAGAIN) {
+            /*
+             * if wev->complete is set, this means that a write event
+             * happened while we were waiting for the thread task, so
+             * we have to retry sending even on EAGAIN
+             */
+
+            if (wev->complete) {
+                return 0;
+            }
+
+            return NGX_AGAIN;
+        }
+
+        if (ctx->err) {
+            wev->error = 1;
+            ngx_connection_error(c, ctx->err, "sendfile() failed");
+            return NGX_ERROR;
+        }
+
+        if (ctx->sent == 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,
+                          "sendfile() reported that \"%s\" was truncated at %O",
+                          file->file->name.data, file->file_pos);
+
+            return NGX_ERROR;
+        }
+
+        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;
+
+    wev->complete = 0;
+
+    if (file->file->thread_handler(task, file->file) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    return NGX_DONE;
+}
+
+
+static void
+ngx_linux_sendfile_thread_handler(void *data, ngx_log_t *log)
+{
+    ngx_linux_sendfile_ctx_t *ctx = data;
+
+    off_t       offset;
+    ssize_t     n;
+    ngx_buf_t  *file;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_CORE, log, 0, "linux sendfile thread handler");
+
+    file = ctx->file;
+    offset = file->file_pos;
+
+again:
+
+    n = ngxvcl_sendfile(ctx->socket, file->file->fd, &offset, ctx->size);
+
+    if (n == -1) {
+        ctx->err = ngx_errno;
+
+    } else {
+        ctx->sent = n;
+        ctx->err = 0;
+    }
+
+#if 0
+    ngx_time_update();
+#endif
+
+    ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0,
+                   "sendfile: %z (err: %d) of %uz @%O",
+                   n, ctx->err, ctx->size, file->file_pos);
+
+    if (ctx->err == NGX_EINTR) {
+        goto again;
+    }
+}
+
+#endif /* NGX_THREADS */
diff --git a/nginx/src/os/unix/ngx_os.h b/nginx/src/os/unix/ngx_os.h
new file mode 100644 (file)
index 0000000..3b32819
--- /dev/null
@@ -0,0 +1,102 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_OS_H_INCLUDED_
+#define _NGX_OS_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#define NGX_IO_SENDFILE    1
+
+
+typedef ssize_t (*ngx_recv_pt)(ngx_connection_t *c, u_char *buf, size_t size);
+typedef ssize_t (*ngx_recv_chain_pt)(ngx_connection_t *c, ngx_chain_t *in,
+    off_t limit);
+typedef ssize_t (*ngx_send_pt)(ngx_connection_t *c, u_char *buf, size_t size);
+typedef ngx_chain_t *(*ngx_send_chain_pt)(ngx_connection_t *c, ngx_chain_t *in,
+    off_t limit);
+
+typedef struct {
+    ngx_recv_pt        recv;
+    ngx_recv_chain_pt  recv_chain;
+    ngx_recv_pt        udp_recv;
+    ngx_send_pt        send;
+    ngx_send_pt        udp_send;
+    ngx_send_chain_pt  udp_send_chain;
+    ngx_send_chain_pt  send_chain;
+    ngx_uint_t         flags;
+} ngx_os_io_t;
+
+
+ngx_int_t ngx_os_init(ngx_log_t *log);
+void ngx_os_status(ngx_log_t *log);
+ngx_int_t ngx_os_specific_init(ngx_log_t *log);
+void ngx_os_specific_status(ngx_log_t *log);
+ngx_int_t ngx_daemon(ngx_log_t *log);
+ngx_int_t ngx_os_signal_process(ngx_cycle_t *cycle, char *sig, ngx_pid_t pid);
+
+
+ssize_t ngx_unix_recv(ngx_connection_t *c, u_char *buf, size_t size);
+ssize_t ngx_readv_chain(ngx_connection_t *c, ngx_chain_t *entry, off_t limit);
+ssize_t ngx_udp_unix_recv(ngx_connection_t *c, u_char *buf, size_t size);
+ssize_t ngx_unix_send(ngx_connection_t *c, u_char *buf, size_t size);
+ngx_chain_t *ngx_writev_chain(ngx_connection_t *c, ngx_chain_t *in,
+    off_t limit);
+ssize_t ngx_udp_unix_send(ngx_connection_t *c, u_char *buf, size_t size);
+ngx_chain_t *ngx_udp_unix_sendmsg_chain(ngx_connection_t *c, ngx_chain_t *in,
+    off_t limit);
+
+
+#if (IOV_MAX > 64)
+#define NGX_IOVS_PREALLOCATE  64
+#else
+#define NGX_IOVS_PREALLOCATE  IOV_MAX
+#endif
+
+
+typedef struct {
+    struct iovec  *iovs;
+    ngx_uint_t     count;
+    size_t         size;
+    ngx_uint_t     nalloc;
+} ngx_iovec_t;
+
+ngx_chain_t *ngx_output_chain_to_iovec(ngx_iovec_t *vec, ngx_chain_t *in,
+    size_t limit, ngx_log_t *log);
+
+
+ssize_t ngx_writev(ngx_connection_t *c, ngx_iovec_t *vec);
+
+
+extern ngx_os_io_t  ngx_os_io;
+extern ngx_int_t    ngx_ncpu;
+extern ngx_int_t    ngx_max_sockets;
+extern ngx_uint_t   ngx_inherited_nonblocking;
+extern ngx_uint_t   ngx_tcp_nodelay_and_tcp_nopush;
+
+
+#if (NGX_FREEBSD)
+#include <ngx_freebsd.h>
+
+
+#elif (NGX_LINUX)
+#include <ngx_linux.h>
+
+
+#elif (NGX_SOLARIS)
+#include <ngx_solaris.h>
+
+
+#elif (NGX_DARWIN)
+#include <ngx_darwin.h>
+#endif
+
+
+#endif /* _NGX_OS_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_posix_config.h b/nginx/src/os/unix/ngx_posix_config.h
new file mode 100644 (file)
index 0000000..2a8c413
--- /dev/null
@@ -0,0 +1,151 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_POSIX_CONFIG_H_INCLUDED_
+#define _NGX_POSIX_CONFIG_H_INCLUDED_
+
+
+#if (NGX_HPUX)
+#define _XOPEN_SOURCE
+#define _XOPEN_SOURCE_EXTENDED  1
+#define _HPUX_ALT_XOPEN_SOCKET_API
+#endif
+
+
+#if (NGX_TRU64)
+#define _REENTRANT
+#endif
+
+
+#if (NGX_GNU_HURD)
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE             /* accept4() */
+#endif
+#define _FILE_OFFSET_BITS       64
+#endif
+
+
+#ifdef __CYGWIN__
+#define timezonevar             /* timezone is variable */
+#define NGX_BROKEN_SCM_RIGHTS   1
+#endif
+
+
+#include <sys/types.h>
+#include <sys/time.h>
+#if (NGX_HAVE_UNISTD_H)
+#include <unistd.h>
+#endif
+#if (NGX_HAVE_INTTYPES_H)
+#include <inttypes.h>
+#endif
+#include <stdarg.h>
+#include <stddef.h>             /* offsetof() */
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <dirent.h>
+#include <glob.h>
+#include <time.h>
+#if (NGX_HAVE_SYS_PARAM_H)
+#include <sys/param.h>          /* statfs() */
+#endif
+#if (NGX_HAVE_SYS_MOUNT_H)
+#include <sys/mount.h>          /* statfs() */
+#endif
+#if (NGX_HAVE_SYS_STATVFS_H)
+#include <sys/statvfs.h>        /* statvfs() */
+#endif
+
+#if (NGX_HAVE_SYS_FILIO_H)
+#include <sys/filio.h>          /* FIONBIO */
+#endif
+#include <sys/ioctl.h>          /* FIONBIO */
+
+#include <sys/uio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sched.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>        /* TCP_NODELAY */
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/un.h>
+
+#if (NGX_HAVE_LIMITS_H)
+#include <limits.h>             /* IOV_MAX */
+#endif
+
+#ifdef __CYGWIN__
+#include <malloc.h>             /* memalign() */
+#endif
+
+#if (NGX_HAVE_CRYPT_H)
+#include <crypt.h>
+#endif
+
+
+#ifndef IOV_MAX
+#define IOV_MAX   16
+#endif
+
+
+#include <ngx_auto_config.h>
+
+
+#if (NGX_HAVE_DLOPEN)
+#include <dlfcn.h>
+#endif
+
+
+#if (NGX_HAVE_POSIX_SEM)
+#include <semaphore.h>
+#endif
+
+
+#if (NGX_HAVE_POLL)
+#include <poll.h>
+#endif
+
+
+#if (NGX_HAVE_KQUEUE)
+#include <sys/event.h>
+#endif
+
+
+#if (NGX_HAVE_DEVPOLL) && !(NGX_TEST_BUILD_DEVPOLL)
+#include <sys/ioctl.h>
+#include <sys/devpoll.h>
+#endif
+
+
+#if (NGX_HAVE_FILE_AIO)
+#include <aio.h>
+typedef struct aiocb  ngx_aiocb_t;
+#endif
+
+
+#define NGX_LISTEN_BACKLOG  511
+
+#define ngx_debug_init()
+
+
+extern char **environ;
+
+
+#endif /* _NGX_POSIX_CONFIG_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_posix_init.c b/nginx/src/os/unix/ngx_posix_init.c
new file mode 100644 (file)
index 0000000..7824735
--- /dev/null
@@ -0,0 +1,144 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <nginx.h>
+
+
+ngx_int_t   ngx_ncpu;
+ngx_int_t   ngx_max_sockets;
+ngx_uint_t  ngx_inherited_nonblocking;
+ngx_uint_t  ngx_tcp_nodelay_and_tcp_nopush;
+
+
+struct rlimit  rlmt;
+
+
+ngx_os_io_t ngx_os_io = {
+    ngx_unix_recv,
+    ngx_readv_chain,
+    ngx_udp_unix_recv,
+    ngx_unix_send,
+    ngx_udp_unix_send,
+    ngx_udp_unix_sendmsg_chain,
+    ngx_writev_chain,
+    0
+};
+
+
+ngx_int_t
+ngx_os_init(ngx_log_t *log)
+{
+    ngx_time_t  *tp;
+    ngx_uint_t   n;
+#if (NGX_HAVE_LEVEL1_DCACHE_LINESIZE)
+    long         size;
+#endif
+
+#if (NGX_HAVE_OS_SPECIFIC_INIT)
+    if (ngx_os_specific_init(log) != NGX_OK) {
+        return NGX_ERROR;
+    }
+#endif
+
+    if (ngx_init_setproctitle(log) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    ngx_pagesize = getpagesize();
+    ngx_cacheline_size = NGX_CPU_CACHE_LINE;
+
+    for (n = ngx_pagesize; n >>= 1; ngx_pagesize_shift++) { /* void */ }
+
+#if (NGX_HAVE_SC_NPROCESSORS_ONLN)
+    if (ngx_ncpu == 0) {
+        ngx_ncpu = sysconf(_SC_NPROCESSORS_ONLN);
+    }
+#endif
+
+    if (ngx_ncpu < 1) {
+        ngx_ncpu = 1;
+    }
+
+#if (NGX_HAVE_LEVEL1_DCACHE_LINESIZE)
+    size = sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
+    if (size > 0) {
+        ngx_cacheline_size = size;
+    }
+#endif
+
+    ngx_cpuinfo();
+
+    if (getrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, errno,
+                      "getrlimit(RLIMIT_NOFILE) failed");
+        return NGX_ERROR;
+    }
+
+    ngx_max_sockets = (ngx_int_t) rlmt.rlim_cur;
+
+#if (NGX_HAVE_INHERITED_NONBLOCK || NGX_HAVE_ACCEPT4)
+    ngx_inherited_nonblocking = 1;
+#else
+    ngx_inherited_nonblocking = 0;
+#endif
+
+    tp = ngx_timeofday();
+    srandom(((unsigned) ngx_pid << 16) ^ tp->sec ^ tp->msec);
+
+    return NGX_OK;
+}
+
+
+void
+ngx_os_status(ngx_log_t *log)
+{
+    ngx_log_error(NGX_LOG_NOTICE, log, 0, NGINX_VER_BUILD);
+
+#ifdef NGX_COMPILER
+    ngx_log_error(NGX_LOG_NOTICE, log, 0, "built by " NGX_COMPILER);
+#endif
+
+#if (NGX_HAVE_OS_SPECIFIC_INIT)
+    ngx_os_specific_status(log);
+#endif
+
+    ngx_log_error(NGX_LOG_NOTICE, log, 0,
+                  "getrlimit(RLIMIT_NOFILE): %r:%r",
+                  rlmt.rlim_cur, rlmt.rlim_max);
+}
+
+
+#if 0
+
+ngx_int_t
+ngx_posix_post_conf_init(ngx_log_t *log)
+{
+    ngx_fd_t  pp[2];
+
+    if (pipe(pp) == -1) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "pipe() failed");
+        return NGX_ERROR;
+    }
+
+    if (dup2(pp[1], STDERR_FILENO) == -1) {
+        ngx_log_error(NGX_LOG_EMERG, log, errno, "dup2(STDERR) failed");
+        return NGX_ERROR;
+    }
+
+    if (pp[1] > STDERR_FILENO) {
+        if (close(pp[1]) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, log, errno, "close() failed");
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+#endif
diff --git a/nginx/src/os/unix/ngx_process.c b/nginx/src/os/unix/ngx_process.c
new file mode 100644 (file)
index 0000000..1568023
--- /dev/null
@@ -0,0 +1,648 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_channel.h>
+
+
+typedef struct {
+    int     signo;
+    char   *signame;
+    char   *name;
+    void  (*handler)(int signo, siginfo_t *siginfo, void *ucontext);
+} ngx_signal_t;
+
+
+
+static void ngx_execute_proc(ngx_cycle_t *cycle, void *data);
+static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext);
+static void ngx_process_get_status(void);
+static void ngx_unlock_mutexes(ngx_pid_t pid);
+
+
+int              ngx_argc;
+char           **ngx_argv;
+char           **ngx_os_argv;
+
+ngx_int_t        ngx_process_slot;
+ngx_socket_t     ngx_channel;
+ngx_int_t        ngx_last_process;
+ngx_process_t    ngx_processes[NGX_MAX_PROCESSES];
+
+
+ngx_signal_t  signals[] = {
+    { ngx_signal_value(NGX_RECONFIGURE_SIGNAL),
+      "SIG" ngx_value(NGX_RECONFIGURE_SIGNAL),
+      "reload",
+      ngx_signal_handler },
+
+    { ngx_signal_value(NGX_REOPEN_SIGNAL),
+      "SIG" ngx_value(NGX_REOPEN_SIGNAL),
+      "reopen",
+      ngx_signal_handler },
+
+    { ngx_signal_value(NGX_NOACCEPT_SIGNAL),
+      "SIG" ngx_value(NGX_NOACCEPT_SIGNAL),
+      "",
+      ngx_signal_handler },
+
+    { ngx_signal_value(NGX_TERMINATE_SIGNAL),
+      "SIG" ngx_value(NGX_TERMINATE_SIGNAL),
+      "stop",
+      ngx_signal_handler },
+
+    { ngx_signal_value(NGX_SHUTDOWN_SIGNAL),
+      "SIG" ngx_value(NGX_SHUTDOWN_SIGNAL),
+      "quit",
+      ngx_signal_handler },
+
+    { ngx_signal_value(NGX_CHANGEBIN_SIGNAL),
+      "SIG" ngx_value(NGX_CHANGEBIN_SIGNAL),
+      "",
+      ngx_signal_handler },
+
+    { SIGALRM, "SIGALRM", "", ngx_signal_handler },
+
+    { SIGINT, "SIGINT", "", ngx_signal_handler },
+
+    { SIGIO, "SIGIO", "", ngx_signal_handler },
+
+    { SIGCHLD, "SIGCHLD", "", ngx_signal_handler },
+
+    { SIGSYS, "SIGSYS, SIG_IGN", "", NULL },
+
+    { SIGPIPE, "SIGPIPE, SIG_IGN", "", NULL },
+
+    { 0, NULL, "", NULL }
+};
+
+
+ngx_pid_t
+ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
+    char *name, ngx_int_t respawn)
+{
+    u_long     on;
+    ngx_pid_t  pid;
+    ngx_int_t  s;
+
+    if (respawn >= 0) {
+        s = respawn;
+
+    } else {
+        for (s = 0; s < ngx_last_process; s++) {
+            if (ngx_processes[s].pid == -1) {
+                break;
+            }
+        }
+
+        if (s == NGX_MAX_PROCESSES) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                          "no more than %d processes can be spawned",
+                          NGX_MAX_PROCESSES);
+            return NGX_INVALID_PID;
+        }
+    }
+
+
+    if (respawn != NGX_PROCESS_DETACHED) {
+
+        /* Solaris 9 still has no AF_LOCAL */
+
+        if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1)
+        {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "socketpair() failed while spawning \"%s\"", name);
+            return NGX_INVALID_PID;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+                       "channel %d:%d",
+                       ngx_processes[s].channel[0],
+                       ngx_processes[s].channel[1]);
+
+        if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          ngx_nonblocking_n " failed while spawning \"%s\"",
+                          name);
+            ngx_close_channel(ngx_processes[s].channel, cycle->log);
+            return NGX_INVALID_PID;
+        }
+
+        if (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          ngx_nonblocking_n " failed while spawning \"%s\"",
+                          name);
+            ngx_close_channel(ngx_processes[s].channel, cycle->log);
+            return NGX_INVALID_PID;
+        }
+
+        on = 1;
+        if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "ioctl(FIOASYNC) failed while spawning \"%s\"", name);
+            ngx_close_channel(ngx_processes[s].channel, cycle->log);
+            return NGX_INVALID_PID;
+        }
+
+        if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "fcntl(F_SETOWN) failed while spawning \"%s\"", name);
+            ngx_close_channel(ngx_processes[s].channel, cycle->log);
+            return NGX_INVALID_PID;
+        }
+
+        if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
+                           name);
+            ngx_close_channel(ngx_processes[s].channel, cycle->log);
+            return NGX_INVALID_PID;
+        }
+
+        if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
+                           name);
+            ngx_close_channel(ngx_processes[s].channel, cycle->log);
+            return NGX_INVALID_PID;
+        }
+
+        ngx_channel = ngx_processes[s].channel[1];
+
+    } else {
+        ngx_processes[s].channel[0] = -1;
+        ngx_processes[s].channel[1] = -1;
+    }
+
+    ngx_process_slot = s;
+
+
+    pid = fork();
+
+    switch (pid) {
+
+    case -1:
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "fork() failed while spawning \"%s\"", name);
+        ngx_close_channel(ngx_processes[s].channel, cycle->log);
+        return NGX_INVALID_PID;
+
+    case 0:
+        ngx_parent = ngx_pid;
+        ngx_pid = ngx_getpid();
+        proc(cycle, data);
+        break;
+
+    default:
+        break;
+    }
+
+    ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start %s %P", name, pid);
+
+    ngx_processes[s].pid = pid;
+    ngx_processes[s].exited = 0;
+
+    if (respawn >= 0) {
+        return pid;
+    }
+
+    ngx_processes[s].proc = proc;
+    ngx_processes[s].data = data;
+    ngx_processes[s].name = name;
+    ngx_processes[s].exiting = 0;
+
+    switch (respawn) {
+
+    case NGX_PROCESS_NORESPAWN:
+        ngx_processes[s].respawn = 0;
+        ngx_processes[s].just_spawn = 0;
+        ngx_processes[s].detached = 0;
+        break;
+
+    case NGX_PROCESS_JUST_SPAWN:
+        ngx_processes[s].respawn = 0;
+        ngx_processes[s].just_spawn = 1;
+        ngx_processes[s].detached = 0;
+        break;
+
+    case NGX_PROCESS_RESPAWN:
+        ngx_processes[s].respawn = 1;
+        ngx_processes[s].just_spawn = 0;
+        ngx_processes[s].detached = 0;
+        break;
+
+    case NGX_PROCESS_JUST_RESPAWN:
+        ngx_processes[s].respawn = 1;
+        ngx_processes[s].just_spawn = 1;
+        ngx_processes[s].detached = 0;
+        break;
+
+    case NGX_PROCESS_DETACHED:
+        ngx_processes[s].respawn = 0;
+        ngx_processes[s].just_spawn = 0;
+        ngx_processes[s].detached = 1;
+        break;
+    }
+
+    if (s == ngx_last_process) {
+        ngx_last_process++;
+    }
+
+    return pid;
+}
+
+
+ngx_pid_t
+ngx_execute(ngx_cycle_t *cycle, ngx_exec_ctx_t *ctx)
+{
+    return ngx_spawn_process(cycle, ngx_execute_proc, ctx, ctx->name,
+                             NGX_PROCESS_DETACHED);
+}
+
+
+static void
+ngx_execute_proc(ngx_cycle_t *cycle, void *data)
+{
+    ngx_exec_ctx_t  *ctx = data;
+
+    if (execve(ctx->path, ctx->argv, ctx->envp) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "execve() failed while executing %s \"%s\"",
+                      ctx->name, ctx->path);
+    }
+
+    exit(1);
+}
+
+
+ngx_int_t
+ngx_init_signals(ngx_log_t *log)
+{
+    ngx_signal_t      *sig;
+    struct sigaction   sa;
+
+    for (sig = signals; sig->signo != 0; sig++) {
+        ngx_memzero(&sa, sizeof(struct sigaction));
+
+        if (sig->handler) {
+            sa.sa_sigaction = sig->handler;
+            sa.sa_flags = SA_SIGINFO;
+
+        } else {
+            sa.sa_handler = SIG_IGN;
+        }
+
+        sigemptyset(&sa.sa_mask);
+        if (sigaction(sig->signo, &sa, NULL) == -1) {
+#if (NGX_VALGRIND)
+            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                          "sigaction(%s) failed, ignored", sig->signame);
+#else
+            ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                          "sigaction(%s) failed", sig->signame);
+            return NGX_ERROR;
+#endif
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext)
+{
+    char            *action;
+    ngx_int_t        ignore;
+    ngx_err_t        err;
+    ngx_signal_t    *sig;
+
+    ignore = 0;
+
+    err = ngx_errno;
+
+    for (sig = signals; sig->signo != 0; sig++) {
+        if (sig->signo == signo) {
+            break;
+        }
+    }
+
+    ngx_time_sigsafe_update();
+
+    action = "";
+
+    switch (ngx_process) {
+
+    case NGX_PROCESS_MASTER:
+    case NGX_PROCESS_SINGLE:
+        switch (signo) {
+
+        case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
+            ngx_quit = 1;
+            action = ", shutting down";
+            break;
+
+        case ngx_signal_value(NGX_TERMINATE_SIGNAL):
+        case SIGINT:
+            ngx_terminate = 1;
+            action = ", exiting";
+            break;
+
+        case ngx_signal_value(NGX_NOACCEPT_SIGNAL):
+            if (ngx_daemonized) {
+                ngx_noaccept = 1;
+                action = ", stop accepting connections";
+            }
+            break;
+
+        case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
+            ngx_reconfigure = 1;
+            action = ", reconfiguring";
+            break;
+
+        case ngx_signal_value(NGX_REOPEN_SIGNAL):
+            ngx_reopen = 1;
+            action = ", reopening logs";
+            break;
+
+        case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
+            if (ngx_getppid() == ngx_parent || ngx_new_binary > 0) {
+
+                /*
+                 * Ignore the signal in the new binary if its parent is
+                 * not changed, i.e. the old binary's process is still
+                 * running.  Or ignore the signal in the old binary's
+                 * process if the new binary's process is already running.
+                 */
+
+                action = ", ignoring";
+                ignore = 1;
+                break;
+            }
+
+            ngx_change_binary = 1;
+            action = ", changing binary";
+            break;
+
+        case SIGALRM:
+            ngx_sigalrm = 1;
+            break;
+
+        case SIGIO:
+            ngx_sigio = 1;
+            break;
+
+        case SIGCHLD:
+            ngx_reap = 1;
+            break;
+        }
+
+        break;
+
+    case NGX_PROCESS_WORKER:
+    case NGX_PROCESS_HELPER:
+        switch (signo) {
+
+        case ngx_signal_value(NGX_NOACCEPT_SIGNAL):
+            if (!ngx_daemonized) {
+                break;
+            }
+            ngx_debug_quit = 1;
+            /* fall through */
+        case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
+            ngx_quit = 1;
+            action = ", shutting down";
+            break;
+
+        case ngx_signal_value(NGX_TERMINATE_SIGNAL):
+        case SIGINT:
+            ngx_terminate = 1;
+            action = ", exiting";
+            break;
+
+        case ngx_signal_value(NGX_REOPEN_SIGNAL):
+            ngx_reopen = 1;
+            action = ", reopening logs";
+            break;
+
+        case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
+        case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
+        case SIGIO:
+            action = ", ignoring";
+            break;
+        }
+
+        break;
+    }
+
+    if (siginfo && siginfo->si_pid) {
+        ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
+                      "signal %d (%s) received from %P%s",
+                      signo, sig->signame, siginfo->si_pid, action);
+
+    } else {
+        ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
+                      "signal %d (%s) received%s",
+                      signo, sig->signame, action);
+    }
+
+    if (ignore) {
+        ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, 0,
+                      "the changing binary signal is ignored: "
+                      "you should shutdown or terminate "
+                      "before either old or new binary's process");
+    }
+
+    if (signo == SIGCHLD) {
+        ngx_process_get_status();
+    }
+
+    ngx_set_errno(err);
+}
+
+
+static void
+ngx_process_get_status(void)
+{
+    int              status;
+    char            *process;
+    ngx_pid_t        pid;
+    ngx_err_t        err;
+    ngx_int_t        i;
+    ngx_uint_t       one;
+
+    one = 0;
+
+    for ( ;; ) {
+        pid = waitpid(-1, &status, WNOHANG);
+
+        if (pid == 0) {
+            return;
+        }
+
+        if (pid == -1) {
+            err = ngx_errno;
+
+            if (err == NGX_EINTR) {
+                continue;
+            }
+
+            if (err == NGX_ECHILD && one) {
+                return;
+            }
+
+            /*
+             * Solaris always calls the signal handler for each exited process
+             * despite waitpid() may be already called for this process.
+             *
+             * When several processes exit at the same time FreeBSD may
+             * erroneously call the signal handler for exited process
+             * despite waitpid() may be already called for this process.
+             */
+
+            if (err == NGX_ECHILD) {
+                ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, err,
+                              "waitpid() failed");
+                return;
+            }
+
+            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, err,
+                          "waitpid() failed");
+            return;
+        }
+
+
+        one = 1;
+        process = "unknown process";
+
+        for (i = 0; i < ngx_last_process; i++) {
+            if (ngx_processes[i].pid == pid) {
+                ngx_processes[i].status = status;
+                ngx_processes[i].exited = 1;
+                process = ngx_processes[i].name;
+                break;
+            }
+        }
+
+        if (WTERMSIG(status)) {
+#ifdef WCOREDUMP
+            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+                          "%s %P exited on signal %d%s",
+                          process, pid, WTERMSIG(status),
+                          WCOREDUMP(status) ? " (core dumped)" : "");
+#else
+            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+                          "%s %P exited on signal %d",
+                          process, pid, WTERMSIG(status));
+#endif
+
+        } else {
+            ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
+                          "%s %P exited with code %d",
+                          process, pid, WEXITSTATUS(status));
+        }
+
+        if (WEXITSTATUS(status) == 2 && ngx_processes[i].respawn) {
+            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+                          "%s %P exited with fatal code %d "
+                          "and cannot be respawned",
+                          process, pid, WEXITSTATUS(status));
+            ngx_processes[i].respawn = 0;
+        }
+
+        ngx_unlock_mutexes(pid);
+    }
+}
+
+
+static void
+ngx_unlock_mutexes(ngx_pid_t pid)
+{
+    ngx_uint_t        i;
+    ngx_shm_zone_t   *shm_zone;
+    ngx_list_part_t  *part;
+    ngx_slab_pool_t  *sp;
+
+    /*
+     * unlock the accept mutex if the abnormally exited process
+     * held it
+     */
+
+    if (ngx_accept_mutex_ptr) {
+        (void) ngx_shmtx_force_unlock(&ngx_accept_mutex, pid);
+    }
+
+    /*
+     * unlock shared memory mutexes if held by the abnormally exited
+     * process
+     */
+
+    part = (ngx_list_part_t *) &ngx_cycle->shared_memory.part;
+    shm_zone = part->elts;
+
+    for (i = 0; /* void */ ; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+            part = part->next;
+            shm_zone = part->elts;
+            i = 0;
+        }
+
+        sp = (ngx_slab_pool_t *) shm_zone[i].shm.addr;
+
+        if (ngx_shmtx_force_unlock(&sp->mutex, pid)) {
+            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+                          "shared memory zone \"%V\" was locked by %P",
+                          &shm_zone[i].shm.name, pid);
+        }
+    }
+}
+
+
+void
+ngx_debug_point(void)
+{
+    ngx_core_conf_t  *ccf;
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
+                                           ngx_core_module);
+
+    switch (ccf->debug_points) {
+
+    case NGX_DEBUG_POINTS_STOP:
+        raise(SIGSTOP);
+        break;
+
+    case NGX_DEBUG_POINTS_ABORT:
+        ngx_abort();
+    }
+}
+
+
+ngx_int_t
+ngx_os_signal_process(ngx_cycle_t *cycle, char *name, ngx_pid_t pid)
+{
+    ngx_signal_t  *sig;
+
+    for (sig = signals; sig->signo != 0; sig++) {
+        if (ngx_strcmp(name, sig->name) == 0) {
+            if (kill(pid, sig->signo) != -1) {
+                return 0;
+            }
+
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "kill(%P, %d) failed", pid, sig->signo);
+        }
+    }
+
+    return 1;
+}
diff --git a/nginx/src/os/unix/ngx_process.h b/nginx/src/os/unix/ngx_process.h
new file mode 100644 (file)
index 0000000..3986639
--- /dev/null
@@ -0,0 +1,90 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_PROCESS_H_INCLUDED_
+#define _NGX_PROCESS_H_INCLUDED_
+
+
+#include <ngx_setaffinity.h>
+#include <ngx_setproctitle.h>
+
+
+typedef pid_t       ngx_pid_t;
+
+#define NGX_INVALID_PID  -1
+
+typedef void (*ngx_spawn_proc_pt) (ngx_cycle_t *cycle, void *data);
+
+typedef struct {
+    ngx_pid_t           pid;
+    int                 status;
+    ngx_socket_t        channel[2];
+
+    ngx_spawn_proc_pt   proc;
+    void               *data;
+    char               *name;
+
+    unsigned            respawn:1;
+    unsigned            just_spawn:1;
+    unsigned            detached:1;
+    unsigned            exiting:1;
+    unsigned            exited:1;
+} ngx_process_t;
+
+
+typedef struct {
+    char         *path;
+    char         *name;
+    char *const  *argv;
+    char *const  *envp;
+} ngx_exec_ctx_t;
+
+
+#define NGX_MAX_PROCESSES         1024
+
+#define NGX_PROCESS_NORESPAWN     -1
+#define NGX_PROCESS_JUST_SPAWN    -2
+#define NGX_PROCESS_RESPAWN       -3
+#define NGX_PROCESS_JUST_RESPAWN  -4
+#define NGX_PROCESS_DETACHED      -5
+
+
+#define ngx_getpid   getpid
+#define ngx_getppid  getppid
+
+#ifndef ngx_log_pid
+#define ngx_log_pid  ngx_pid
+#endif
+
+
+ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle,
+    ngx_spawn_proc_pt proc, void *data, char *name, ngx_int_t respawn);
+ngx_pid_t ngx_execute(ngx_cycle_t *cycle, ngx_exec_ctx_t *ctx);
+ngx_int_t ngx_init_signals(ngx_log_t *log);
+void ngx_debug_point(void);
+
+
+#if (NGX_HAVE_SCHED_YIELD)
+#define ngx_sched_yield()  sched_yield()
+#else
+#define ngx_sched_yield()  usleep(1)
+#endif
+
+
+extern int            ngx_argc;
+extern char         **ngx_argv;
+extern char         **ngx_os_argv;
+
+extern ngx_pid_t      ngx_pid;
+extern ngx_pid_t      ngx_parent;
+extern ngx_socket_t   ngx_channel;
+extern ngx_int_t      ngx_process_slot;
+extern ngx_int_t      ngx_last_process;
+extern ngx_process_t  ngx_processes[NGX_MAX_PROCESSES];
+
+
+#endif /* _NGX_PROCESS_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_process_cycle.c b/nginx/src/os/unix/ngx_process_cycle.c
new file mode 100644 (file)
index 0000000..3236904
--- /dev/null
@@ -0,0 +1,1232 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_channel.h>
+
+
+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_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);
+static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data);
+static void ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker);
+static void ngx_worker_process_exit(ngx_cycle_t *cycle);
+static void ngx_channel_handler(ngx_event_t *ev);
+static void ngx_cache_manager_process_cycle(ngx_cycle_t *cycle, void *data);
+static void ngx_cache_manager_process_handler(ngx_event_t *ev);
+static void ngx_cache_loader_process_handler(ngx_event_t *ev);
+
+
+ngx_uint_t    ngx_process;
+ngx_uint_t    ngx_worker;
+ngx_pid_t     ngx_pid;
+ngx_pid_t     ngx_parent;
+
+sig_atomic_t  ngx_reap;
+sig_atomic_t  ngx_sigio;
+sig_atomic_t  ngx_sigalrm;
+sig_atomic_t  ngx_terminate;
+sig_atomic_t  ngx_quit;
+sig_atomic_t  ngx_debug_quit;
+ngx_uint_t    ngx_exiting;
+sig_atomic_t  ngx_reconfigure;
+sig_atomic_t  ngx_reopen;
+
+sig_atomic_t  ngx_change_binary;
+ngx_pid_t     ngx_new_binary;
+ngx_uint_t    ngx_inherited;
+ngx_uint_t    ngx_daemonized;
+
+sig_atomic_t  ngx_noaccept;
+ngx_uint_t    ngx_noaccepting;
+ngx_uint_t    ngx_restart;
+
+
+static u_char  master_process[] = "master process";
+
+
+static ngx_cache_manager_ctx_t  ngx_cache_manager_ctx = {
+    ngx_cache_manager_process_handler, "cache manager process", 0
+};
+
+static ngx_cache_manager_ctx_t  ngx_cache_loader_ctx = {
+    ngx_cache_loader_process_handler, "cache loader process", 60000
+};
+
+
+static ngx_cycle_t      ngx_exit_cycle;
+static ngx_log_t        ngx_exit_log;
+static ngx_open_file_t  ngx_exit_log_file;
+
+
+void
+ngx_master_process_cycle(ngx_cycle_t *cycle)
+{
+    char              *title;
+    u_char            *p;
+    size_t             size;
+    ngx_int_t          i;
+    ngx_uint_t         n, sigio;
+    sigset_t           set;
+    struct itimerval   itv;
+    ngx_uint_t         live;
+    ngx_msec_t         delay;
+    ngx_listening_t   *ls;
+    ngx_core_conf_t   *ccf;
+
+    sigemptyset(&set);
+    sigaddset(&set, SIGCHLD);
+    sigaddset(&set, SIGALRM);
+    sigaddset(&set, SIGIO);
+    sigaddset(&set, SIGINT);
+    sigaddset(&set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL));
+    sigaddset(&set, ngx_signal_value(NGX_REOPEN_SIGNAL));
+    sigaddset(&set, ngx_signal_value(NGX_NOACCEPT_SIGNAL));
+    sigaddset(&set, ngx_signal_value(NGX_TERMINATE_SIGNAL));
+    sigaddset(&set, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
+    sigaddset(&set, ngx_signal_value(NGX_CHANGEBIN_SIGNAL));
+
+    if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "sigprocmask() failed");
+    }
+
+    sigemptyset(&set);
+
+
+    size = sizeof(master_process);
+
+    for (i = 0; i < ngx_argc; i++) {
+        size += ngx_strlen(ngx_argv[i]) + 1;
+    }
+
+    title = ngx_pnalloc(cycle->pool, size);
+    if (title == NULL) {
+        /* fatal */
+        exit(2);
+    }
+
+    p = ngx_cpymem(title, master_process, sizeof(master_process) - 1);
+    for (i = 0; i < ngx_argc; i++) {
+        *p++ = ' ';
+        p = ngx_cpystrn(p, (u_char *) ngx_argv[i], size);
+    }
+
+    ngx_setproctitle(title);
+
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+    ngx_start_worker_processes(cycle, ccf->worker_processes,
+                               NGX_PROCESS_RESPAWN);
+    ngx_start_cache_manager_processes(cycle, 0);
+
+    ngx_new_binary = 0;
+    delay = 0;
+    sigio = 0;
+    live = 1;
+
+    for ( ;; ) {
+        if (delay) {
+            if (ngx_sigalrm) {
+                sigio = 0;
+                delay *= 2;
+                ngx_sigalrm = 0;
+            }
+
+            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                           "termination cycle: %M", delay);
+
+            itv.it_interval.tv_sec = 0;
+            itv.it_interval.tv_usec = 0;
+            itv.it_value.tv_sec = delay / 1000;
+            itv.it_value.tv_usec = (delay % 1000 ) * 1000;
+
+            if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                              "setitimer() failed");
+            }
+        }
+
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "sigsuspend");
+
+        sigsuspend(&set);
+
+        ngx_time_update();
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "wake up, sigio %i", sigio);
+
+        if (ngx_reap) {
+            ngx_reap = 0;
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");
+
+            live = ngx_reap_children(cycle);
+        }
+
+        if (!live && (ngx_terminate || ngx_quit)) {
+            ngx_master_process_exit(cycle);
+        }
+
+        if (ngx_terminate) {
+            if (delay == 0) {
+                delay = 50;
+            }
+
+            if (sigio) {
+                sigio--;
+                continue;
+            }
+
+            sigio = ccf->worker_processes + 2 /* cache processes */;
+
+            if (delay > 1000) {
+                ngx_signal_worker_processes(cycle, SIGKILL);
+            } else {
+                ngx_signal_worker_processes(cycle,
+                                       ngx_signal_value(NGX_TERMINATE_SIGNAL));
+            }
+
+            continue;
+        }
+
+        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;
+
+            continue;
+        }
+
+        if (ngx_reconfigure) {
+            ngx_reconfigure = 0;
+
+            if (ngx_new_binary) {
+                ngx_start_worker_processes(cycle, ccf->worker_processes,
+                                           NGX_PROCESS_RESPAWN);
+                ngx_start_cache_manager_processes(cycle, 0);
+                ngx_noaccepting = 0;
+
+                continue;
+            }
+
+            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring");
+
+            cycle = ngx_init_cycle(cycle);
+            if (cycle == NULL) {
+                cycle = (ngx_cycle_t *) ngx_cycle;
+                continue;
+            }
+
+            ngx_cycle = cycle;
+            ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
+                                                   ngx_core_module);
+            ngx_start_worker_processes(cycle, ccf->worker_processes,
+                                       NGX_PROCESS_JUST_RESPAWN);
+            ngx_start_cache_manager_processes(cycle, 1);
+
+            /* allow new processes to start */
+            ngx_msleep(100);
+
+            live = 1;
+            ngx_signal_worker_processes(cycle,
+                                        ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
+        }
+
+        if (ngx_restart) {
+            ngx_restart = 0;
+            ngx_start_worker_processes(cycle, ccf->worker_processes,
+                                       NGX_PROCESS_RESPAWN);
+            ngx_start_cache_manager_processes(cycle, 0);
+            live = 1;
+        }
+
+        if (ngx_reopen) {
+            ngx_reopen = 0;
+            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
+            ngx_reopen_files(cycle, ccf->user);
+            ngx_signal_worker_processes(cycle,
+                                        ngx_signal_value(NGX_REOPEN_SIGNAL));
+        }
+
+        if (ngx_change_binary) {
+            ngx_change_binary = 0;
+            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "changing binary");
+            ngx_new_binary = ngx_exec_new_binary(cycle, ngx_argv);
+        }
+
+        if (ngx_noaccept) {
+            ngx_noaccept = 0;
+            ngx_noaccepting = 1;
+            ngx_signal_worker_processes(cycle,
+                                        ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
+        }
+    }
+}
+
+
+void
+ngx_single_process_cycle(ngx_cycle_t *cycle)
+{
+    ngx_uint_t  i;
+
+    if (ngx_set_environment(cycle, NULL) == NULL) {
+        /* fatal */
+        exit(2);
+    }
+
+    for (i = 0; cycle->modules[i]; i++) {
+        if (cycle->modules[i]->init_process) {
+            if (cycle->modules[i]->init_process(cycle) == NGX_ERROR) {
+                /* fatal */
+                exit(2);
+            }
+        }
+    }
+
+    for ( ;; ) {
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
+
+        ngx_process_events_and_timers(cycle);
+
+        if (ngx_terminate || ngx_quit) {
+
+            for (i = 0; cycle->modules[i]; i++) {
+                if (cycle->modules[i]->exit_process) {
+                    cycle->modules[i]->exit_process(cycle);
+                }
+            }
+
+            ngx_master_process_exit(cycle);
+        }
+
+        if (ngx_reconfigure) {
+            ngx_reconfigure = 0;
+            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring");
+
+            cycle = ngx_init_cycle(cycle);
+            if (cycle == NULL) {
+                cycle = (ngx_cycle_t *) ngx_cycle;
+                continue;
+            }
+
+            ngx_cycle = cycle;
+        }
+
+        if (ngx_reopen) {
+            ngx_reopen = 0;
+            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
+            ngx_reopen_files(cycle, (ngx_uid_t) -1);
+        }
+    }
+}
+
+
+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_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start worker processes");
+
+    ngx_memzero(&ch, sizeof(ngx_channel_t));
+
+    ch.command = NGX_CMD_OPEN_CHANNEL;
+
+    ngxvcl_wait_kep_and_vep();
+    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_sleep(3);
+    ngxvcl_wait_vep_only();
+}
+
+
+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;
+
+    manager = 0;
+    loader = 0;
+
+    path = ngx_cycle->paths.elts;
+    for (i = 0; i < ngx_cycle->paths.nelts; i++) {
+
+        if (path[i]->manager) {
+            manager = 1;
+        }
+
+        if (path[i]->loader) {
+            loader = 1;
+        }
+    }
+
+    if (manager == 0) {
+        return;
+    }
+
+    ngx_spawn_process(cycle, ngx_cache_manager_process_cycle,
+                      &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);
+
+    if (loader == 0) {
+        return;
+    }
+
+    ngx_spawn_process(cycle, ngx_cache_manager_process_cycle,
+                      &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);
+}
+
+
+static void
+ngx_pass_open_channel(ngx_cycle_t *cycle, ngx_channel_t *ch)
+{
+    ngx_int_t  i;
+
+    for (i = 0; i < ngx_last_process; i++) {
+
+        if (i == ngx_process_slot
+            || ngx_processes[i].pid == -1
+            || ngx_processes[i].channel[0] == -1)
+        {
+            continue;
+        }
+
+        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,
+                      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);
+    }
+}
+
+
+static void
+ngx_signal_worker_processes(ngx_cycle_t *cycle, int signo)
+{
+    ngx_int_t      i;
+    ngx_err_t      err;
+    ngx_channel_t  ch;
+
+    ngx_memzero(&ch, sizeof(ngx_channel_t));
+
+#if (NGX_BROKEN_SCM_RIGHTS)
+
+    ch.command = 0;
+
+#else
+
+    switch (signo) {
+
+    case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
+        ch.command = NGX_CMD_QUIT;
+        break;
+
+    case ngx_signal_value(NGX_TERMINATE_SIGNAL):
+        ch.command = NGX_CMD_TERMINATE;
+        break;
+
+    case ngx_signal_value(NGX_REOPEN_SIGNAL):
+        ch.command = NGX_CMD_REOPEN;
+        break;
+
+    default:
+        ch.command = 0;
+    }
+
+#endif
+
+    ch.fd = -1;
+
+
+    for (i = 0; i < ngx_last_process; i++) {
+
+        ngx_log_debug7(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "child: %i %P e:%d t:%d d:%d r:%d j:%d",
+                       i,
+                       ngx_processes[i].pid,
+                       ngx_processes[i].exiting,
+                       ngx_processes[i].exited,
+                       ngx_processes[i].detached,
+                       ngx_processes[i].respawn,
+                       ngx_processes[i].just_spawn);
+
+        if (ngx_processes[i].detached || ngx_processes[i].pid == -1) {
+            continue;
+        }
+
+        if (ngx_processes[i].just_spawn) {
+            ngx_processes[i].just_spawn = 0;
+            continue;
+        }
+
+        if (ngx_processes[i].exiting
+            && signo == ngx_signal_value(NGX_SHUTDOWN_SIGNAL))
+        {
+            continue;
+        }
+
+        if (ch.command) {
+            if (ngx_write_channel(ngx_processes[i].channel[0],
+                                  &ch, sizeof(ngx_channel_t), cycle->log)
+                == NGX_OK)
+            {
+                if (signo != ngx_signal_value(NGX_REOPEN_SIGNAL)) {
+                    ngx_processes[i].exiting = 1;
+                }
+
+                continue;
+            }
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+                       "kill (%P, %d)", ngx_processes[i].pid, signo);
+
+        if (kill(ngx_processes[i].pid, signo) == -1) {
+            err = ngx_errno;
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+                          "kill(%P, %d) failed", ngx_processes[i].pid, signo);
+
+            if (err == NGX_ESRCH) {
+                ngx_processes[i].exited = 1;
+                ngx_processes[i].exiting = 0;
+                ngx_reap = 1;
+            }
+
+            continue;
+        }
+
+        if (signo != ngx_signal_value(NGX_REOPEN_SIGNAL)) {
+            ngx_processes[i].exiting = 1;
+        }
+    }
+}
+
+
+static ngx_uint_t
+ngx_reap_children(ngx_cycle_t *cycle)
+{
+    ngx_int_t         i, n;
+    ngx_uint_t        live;
+    ngx_channel_t     ch;
+    ngx_core_conf_t  *ccf;
+
+    ngx_memzero(&ch, sizeof(ngx_channel_t));
+
+    ch.command = NGX_CMD_CLOSE_CHANNEL;
+    ch.fd = -1;
+
+    live = 0;
+    for (i = 0; i < ngx_last_process; i++) {
+
+        ngx_log_debug7(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "child: %i %P e:%d t:%d d:%d r:%d j:%d",
+                       i,
+                       ngx_processes[i].pid,
+                       ngx_processes[i].exiting,
+                       ngx_processes[i].exited,
+                       ngx_processes[i].detached,
+                       ngx_processes[i].respawn,
+                       ngx_processes[i].just_spawn);
+
+        if (ngx_processes[i].pid == -1) {
+            continue;
+        }
+
+        if (ngx_processes[i].exited) {
+
+            if (!ngx_processes[i].detached) {
+                ngx_close_channel(ngx_processes[i].channel, cycle->log);
+
+                ngx_processes[i].channel[0] = -1;
+                ngx_processes[i].channel[1] = -1;
+
+                ch.pid = ngx_processes[i].pid;
+                ch.slot = i;
+
+                for (n = 0; n < ngx_last_process; n++) {
+                    if (ngx_processes[n].exited
+                        || ngx_processes[n].pid == -1
+                        || ngx_processes[n].channel[0] == -1)
+                    {
+                        continue;
+                    }
+
+                    ngx_log_debug3(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+                                   "pass close channel s:%i pid:%P to:%P",
+                                   ch.slot, ch.pid, ngx_processes[n].pid);
+
+                    /* TODO: NGX_AGAIN */
+
+                    ngx_write_channel(ngx_processes[n].channel[0],
+                                      &ch, sizeof(ngx_channel_t), cycle->log);
+                }
+            }
+
+            if (ngx_processes[i].respawn
+                && !ngx_processes[i].exiting
+                && !ngx_terminate
+                && !ngx_quit)
+            {
+                if (ngx_spawn_process(cycle, ngx_processes[i].proc,
+                                      ngx_processes[i].data,
+                                      ngx_processes[i].name, i)
+                    == NGX_INVALID_PID)
+                {
+                    ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                                  "could not respawn %s",
+                                  ngx_processes[i].name);
+                    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);
+
+                live = 1;
+
+                continue;
+            }
+
+            if (ngx_processes[i].pid == ngx_new_binary) {
+
+                ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
+                                                       ngx_core_module);
+
+                if (ngx_rename_file((char *) ccf->oldpid.data,
+                                    (char *) ccf->pid.data)
+                    == NGX_FILE_ERROR)
+                {
+                    ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                                  ngx_rename_file_n " %s back to %s failed "
+                                  "after the new binary process \"%s\" exited",
+                                  ccf->oldpid.data, ccf->pid.data, ngx_argv[0]);
+                }
+
+                ngx_new_binary = 0;
+                if (ngx_noaccepting) {
+                    ngx_restart = 1;
+                    ngx_noaccepting = 0;
+                }
+            }
+
+            if (i == ngx_last_process - 1) {
+                ngx_last_process--;
+
+            } else {
+                ngx_processes[i].pid = -1;
+            }
+
+        } else if (ngx_processes[i].exiting || !ngx_processes[i].detached) {
+            live = 1;
+        }
+    }
+
+    return live;
+}
+
+
+static void
+ngx_master_process_exit(ngx_cycle_t *cycle)
+{
+    ngx_uint_t  i;
+
+    ngx_delete_pidfile(cycle);
+
+    ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exit");
+
+    for (i = 0; cycle->modules[i]; i++) {
+        if (cycle->modules[i]->exit_master) {
+            cycle->modules[i]->exit_master(cycle);
+        }
+    }
+
+    ngx_close_listening_sockets(cycle);
+
+    /*
+     * Copy ngx_cycle->log related data to the special static exit cycle,
+     * log, and log file structures enough to allow a signal handler to log.
+     * The handler may be called when standard ngx_cycle->log allocated from
+     * ngx_cycle->pool is already destroyed.
+     */
+
+
+    ngx_exit_log = *ngx_log_get_file_log(ngx_cycle->log);
+
+    ngx_exit_log_file.fd = ngx_exit_log.file->fd;
+    ngx_exit_log.file = &ngx_exit_log_file;
+    ngx_exit_log.next = NULL;
+    ngx_exit_log.writer = NULL;
+
+    ngx_exit_cycle.log = &ngx_exit_log;
+    ngx_exit_cycle.files = ngx_cycle->files;
+    ngx_exit_cycle.files_n = ngx_cycle->files_n;
+    ngx_cycle = &ngx_exit_cycle;
+
+    ngx_destroy_pool(cycle->pool);
+
+    exit(0);
+}
+
+
+static void
+ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
+{
+    ngx_int_t worker = (intptr_t) data;
+
+    ngx_process = NGX_PROCESS_WORKER;
+    ngx_worker = worker;
+
+    ngx_worker_process_init(cycle, worker);
+
+    ngx_setproctitle("worker process");
+
+    for ( ;; ) {
+
+        if (ngx_exiting) {
+            if (ngx_event_no_timers_left() == NGX_OK) {
+                ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
+                ngx_worker_process_exit(cycle);
+            }
+        }
+
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
+
+        ngx_process_events_and_timers(cycle);
+
+        if (ngx_terminate) {
+            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
+            ngx_worker_process_exit(cycle);
+        }
+
+        if (ngx_quit) {
+            ngx_quit = 0;
+            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
+                          "gracefully shutting down");
+            ngx_setproctitle("worker process is shutting down");
+
+            if (!ngx_exiting) {
+                ngx_exiting = 1;
+                ngx_set_shutdown_timer(cycle);
+                ngx_close_listening_sockets(cycle);
+                ngx_close_idle_connections(cycle);
+            }
+        }
+
+        if (ngx_reopen) {
+            ngx_reopen = 0;
+            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
+            ngx_reopen_files(cycle, -1);
+        }
+    }
+}
+
+
+static void
+ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker)
+{
+    sigset_t          set;
+    ngx_int_t         n;
+    ngx_time_t       *tp;
+    ngx_uint_t        i;
+    ngx_cpuset_t     *cpu_affinity;
+    struct rlimit     rlmt;
+    ngx_core_conf_t  *ccf;
+    ngx_listening_t  *ls;
+
+    if (ngx_set_environment(cycle, NULL) == NULL) {
+        /* fatal */
+        exit(2);
+    }
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+    if (worker >= 0 && ccf->priority != 0) {
+        if (setpriority(PRIO_PROCESS, 0, ccf->priority) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "setpriority(%d) failed", ccf->priority);
+        }
+    }
+
+    if (ccf->rlimit_nofile != NGX_CONF_UNSET) {
+        rlmt.rlim_cur = (rlim_t) ccf->rlimit_nofile;
+        rlmt.rlim_max = (rlim_t) ccf->rlimit_nofile;
+
+        if (setrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "setrlimit(RLIMIT_NOFILE, %i) failed",
+                          ccf->rlimit_nofile);
+        }
+    }
+
+    if (ccf->rlimit_core != NGX_CONF_UNSET) {
+        rlmt.rlim_cur = (rlim_t) ccf->rlimit_core;
+        rlmt.rlim_max = (rlim_t) ccf->rlimit_core;
+
+        if (setrlimit(RLIMIT_CORE, &rlmt) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "setrlimit(RLIMIT_CORE, %O) failed",
+                          ccf->rlimit_core);
+        }
+    }
+
+    if (geteuid() == 0) {
+        if (setgid(ccf->group) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          "setgid(%d) failed", ccf->group);
+            /* fatal */
+            exit(2);
+        }
+
+        if (initgroups(ccf->username, ccf->group) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          "initgroups(%s, %d) failed",
+                          ccf->username, ccf->group);
+        }
+
+#if (NGX_HAVE_PR_SET_KEEPCAPS && NGX_HAVE_CAPABILITIES)
+        if (ccf->transparent && ccf->user) {
+            if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) {
+                ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                              "prctl(PR_SET_KEEPCAPS, 1) failed");
+                /* fatal */
+                exit(2);
+            }
+        }
+#endif
+
+        if (setuid(ccf->user) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          "setuid(%d) failed", ccf->user);
+            /* fatal */
+            exit(2);
+        }
+
+#if (NGX_HAVE_CAPABILITIES)
+        if (ccf->transparent && ccf->user) {
+            struct __user_cap_data_struct    data;
+            struct __user_cap_header_struct  header;
+
+            ngx_memzero(&header, sizeof(struct __user_cap_header_struct));
+            ngx_memzero(&data, sizeof(struct __user_cap_data_struct));
+
+            header.version = _LINUX_CAPABILITY_VERSION_1;
+            data.effective = CAP_TO_MASK(CAP_NET_RAW);
+            data.permitted = data.effective;
+
+            if (syscall(SYS_capset, &header, &data) == -1) {
+                ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                              "capset() failed");
+                /* fatal */
+                exit(2);
+            }
+        }
+#endif
+    }
+
+    if (worker >= 0) {
+        cpu_affinity = ngx_get_cpu_affinity(worker);
+
+        if (cpu_affinity) {
+            ngx_setaffinity(cpu_affinity, cycle->log);
+        }
+    }
+
+#if (NGX_HAVE_PR_SET_DUMPABLE)
+
+    /* allow coredump after setuid() in Linux 2.4.x */
+
+    if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "prctl(PR_SET_DUMPABLE) failed");
+    }
+
+#endif
+
+    if (ccf->working_directory.len) {
+        if (chdir((char *) ccf->working_directory.data) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "chdir(\"%s\") failed", ccf->working_directory.data);
+            /* fatal */
+            exit(2);
+        }
+    }
+
+    sigemptyset(&set);
+
+    if (sigprocmask(SIG_SETMASK, &set, NULL) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "sigprocmask() failed");
+    }
+
+    tp = ngx_timeofday();
+    srandom(((unsigned) ngx_pid << 16) ^ tp->sec ^ tp->msec);
+
+    /*
+     * disable deleting previous events for the listening sockets because
+     * in the worker processes there are no events at all at this point
+     */
+    ls = cycle->listening.elts;
+    for (i = 0; i < cycle->listening.nelts; i++) {
+        ls[i].previous = NULL;
+    }
+
+    for (i = 0; cycle->modules[i]; i++) {
+        if (cycle->modules[i]->init_process) {
+            if (cycle->modules[i]->init_process(cycle) == NGX_ERROR) {
+                /* fatal */
+                exit(2);
+            }
+        }
+    }
+
+    for (n = 0; n < ngx_last_process; n++) {
+
+        if (ngx_processes[n].pid == -1) {
+            continue;
+        }
+
+        if (n == ngx_process_slot) {
+            continue;
+        }
+
+        if (ngx_processes[n].channel[1] == -1) {
+            continue;
+        }
+
+        if (close(ngx_processes[n].channel[1]) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "close() channel failed");
+        }
+    }
+
+    if (close(ngx_processes[ngx_process_slot].channel[0]) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "close() channel failed");
+    }
+
+#if 0
+    ngx_last_process = 0;
+#endif
+
+    if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,
+                              ngx_channel_handler)
+        == NGX_ERROR)
+    {
+        /* fatal */
+        exit(2);
+    }
+}
+
+
+static void
+ngx_worker_process_exit(ngx_cycle_t *cycle)
+{
+    ngx_uint_t         i;
+    ngx_connection_t  *c;
+
+    for (i = 0; cycle->modules[i]; i++) {
+        if (cycle->modules[i]->exit_process) {
+            cycle->modules[i]->exit_process(cycle);
+        }
+    }
+
+    if (ngx_exiting) {
+        c = cycle->connections;
+        for (i = 0; i < cycle->connection_n; i++) {
+            if (c[i].fd != -1
+                && c[i].read
+                && !c[i].read->accept
+                && !c[i].read->channel
+                && !c[i].read->resolver)
+            {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                              "*%uA open socket #%d left in connection %ui",
+                              c[i].number, c[i].fd, i);
+                ngx_debug_quit = 1;
+            }
+        }
+
+        if (ngx_debug_quit) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "aborting");
+            ngx_debug_point();
+        }
+    }
+
+    /*
+     * Copy ngx_cycle->log related data to the special static exit cycle,
+     * log, and log file structures enough to allow a signal handler to log.
+     * The handler may be called when standard ngx_cycle->log allocated from
+     * ngx_cycle->pool is already destroyed.
+     */
+
+    ngx_exit_log = *ngx_log_get_file_log(ngx_cycle->log);
+
+    ngx_exit_log_file.fd = ngx_exit_log.file->fd;
+    ngx_exit_log.file = &ngx_exit_log_file;
+    ngx_exit_log.next = NULL;
+    ngx_exit_log.writer = NULL;
+
+    ngx_exit_cycle.log = &ngx_exit_log;
+    ngx_exit_cycle.files = ngx_cycle->files;
+    ngx_exit_cycle.files_n = ngx_cycle->files_n;
+    ngx_cycle = &ngx_exit_cycle;
+
+    ngx_destroy_pool(cycle->pool);
+
+    ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0, "exit");
+
+    exit(0);
+}
+
+
+static void
+ngx_channel_handler(ngx_event_t *ev)
+{
+    ngx_int_t          n;
+    ngx_channel_t      ch;
+    ngx_connection_t  *c;
+
+    if (ev->timedout) {
+        ev->timedout = 0;
+        return;
+    }
+
+    c = ev->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_CORE, ev->log, 0, "channel handler");
+
+    for ( ;; ) {
+
+        n = ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0, "channel: %i", n);
+
+        if (n == NGX_ERROR) {
+
+            if (ngx_event_flags & NGX_USE_EPOLL_EVENT) {
+                ngx_del_conn(c, 0);
+            }
+
+            ngx_close_connection(c);
+            return;
+        }
+
+        if (ngx_event_flags & NGX_USE_EVENTPORT_EVENT) {
+            if (ngx_add_event(ev, NGX_READ_EVENT, 0) == NGX_ERROR) {
+                return;
+            }
+        }
+
+        if (n == NGX_AGAIN) {
+            return;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0,
+                       "channel command: %ui", ch.command);
+
+        switch (ch.command) {
+
+        case NGX_CMD_QUIT:
+            ngx_quit = 1;
+            break;
+
+        case NGX_CMD_TERMINATE:
+            ngx_terminate = 1;
+            break;
+
+        case NGX_CMD_REOPEN:
+            ngx_reopen = 1;
+            break;
+
+        case NGX_CMD_OPEN_CHANNEL:
+
+            ngx_log_debug3(NGX_LOG_DEBUG_CORE, ev->log, 0,
+                           "get channel s:%i pid:%P fd:%d",
+                           ch.slot, ch.pid, ch.fd);
+
+            ngx_processes[ch.slot].pid = ch.pid;
+            ngx_processes[ch.slot].channel[0] = ch.fd;
+            break;
+
+        case NGX_CMD_CLOSE_CHANNEL:
+
+            ngx_log_debug4(NGX_LOG_DEBUG_CORE, ev->log, 0,
+                           "close channel s:%i pid:%P our:%P fd:%d",
+                           ch.slot, ch.pid, ngx_processes[ch.slot].pid,
+                           ngx_processes[ch.slot].channel[0]);
+
+            if (close(ngx_processes[ch.slot].channel[0]) == -1) {
+                ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+                              "close() channel failed");
+            }
+
+            ngx_processes[ch.slot].channel[0] = -1;
+            break;
+        }
+    }
+}
+
+
+static void
+ngx_cache_manager_process_cycle(ngx_cycle_t *cycle, void *data)
+{
+    ngx_cache_manager_ctx_t *ctx = data;
+
+    void         *ident[4];
+    ngx_event_t   ev;
+
+    /*
+     * Set correct process type since closing listening Unix domain socket
+     * in a master process also removes the Unix domain socket file.
+     */
+    ngx_process = NGX_PROCESS_HELPER;
+
+    ngx_close_listening_sockets(cycle);
+
+    /* Set a moderate number of connections for a helper process. */
+    cycle->connection_n = 512;
+
+    ngx_worker_process_init(cycle, -1);
+
+    ngx_memzero(&ev, sizeof(ngx_event_t));
+    ev.handler = ctx->handler;
+    ev.data = ident;
+    ev.log = cycle->log;
+    ident[3] = (void *) -1;
+
+    ngx_use_accept_mutex = 0;
+
+    ngx_setproctitle(ctx->name);
+
+    ngx_add_timer(&ev, ctx->delay);
+
+    for ( ;; ) {
+
+        if (ngx_terminate || ngx_quit) {
+            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
+            exit(0);
+        }
+
+        if (ngx_reopen) {
+            ngx_reopen = 0;
+            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
+            ngx_reopen_files(cycle, -1);
+        }
+
+        ngx_process_events_and_timers(cycle);
+    }
+}
+
+
+static void
+ngx_cache_manager_process_handler(ngx_event_t *ev)
+{
+    ngx_uint_t    i;
+    ngx_msec_t    next, n;
+    ngx_path_t  **path;
+
+    next = 60 * 60 * 1000;
+
+    path = ngx_cycle->paths.elts;
+    for (i = 0; i < ngx_cycle->paths.nelts; i++) {
+
+        if (path[i]->manager) {
+            n = path[i]->manager(path[i]->data);
+
+            next = (n <= next) ? n : next;
+
+            ngx_time_update();
+        }
+    }
+
+    if (next == 0) {
+        next = 1;
+    }
+
+    ngx_add_timer(ev, next);
+}
+
+
+static void
+ngx_cache_loader_process_handler(ngx_event_t *ev)
+{
+    ngx_uint_t     i;
+    ngx_path_t   **path;
+    ngx_cycle_t   *cycle;
+
+    cycle = (ngx_cycle_t *) ngx_cycle;
+
+    path = cycle->paths.elts;
+    for (i = 0; i < cycle->paths.nelts; i++) {
+
+        if (ngx_terminate || ngx_quit) {
+            break;
+        }
+
+        if (path[i]->loader) {
+            path[i]->loader(path[i]->data);
+            ngx_time_update();
+        }
+    }
+
+    exit(0);
+}
diff --git a/nginx/src/os/unix/ngx_process_cycle.h b/nginx/src/os/unix/ngx_process_cycle.h
new file mode 100644 (file)
index 0000000..69495d5
--- /dev/null
@@ -0,0 +1,61 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_PROCESS_CYCLE_H_INCLUDED_
+#define _NGX_PROCESS_CYCLE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#define NGX_CMD_OPEN_CHANNEL   1
+#define NGX_CMD_CLOSE_CHANNEL  2
+#define NGX_CMD_QUIT           3
+#define NGX_CMD_TERMINATE      4
+#define NGX_CMD_REOPEN         5
+
+
+#define NGX_PROCESS_SINGLE     0
+#define NGX_PROCESS_MASTER     1
+#define NGX_PROCESS_SIGNALLER  2
+#define NGX_PROCESS_WORKER     3
+#define NGX_PROCESS_HELPER     4
+
+
+typedef struct {
+    ngx_event_handler_pt       handler;
+    char                      *name;
+    ngx_msec_t                 delay;
+} ngx_cache_manager_ctx_t;
+
+
+void ngx_master_process_cycle(ngx_cycle_t *cycle);
+void ngx_single_process_cycle(ngx_cycle_t *cycle);
+
+
+extern ngx_uint_t      ngx_process;
+extern ngx_uint_t      ngx_worker;
+extern ngx_pid_t       ngx_pid;
+extern ngx_pid_t       ngx_new_binary;
+extern ngx_uint_t      ngx_inherited;
+extern ngx_uint_t      ngx_daemonized;
+extern ngx_uint_t      ngx_exiting;
+
+extern sig_atomic_t    ngx_reap;
+extern sig_atomic_t    ngx_sigio;
+extern sig_atomic_t    ngx_sigalrm;
+extern sig_atomic_t    ngx_quit;
+extern sig_atomic_t    ngx_debug_quit;
+extern sig_atomic_t    ngx_terminate;
+extern sig_atomic_t    ngx_noaccept;
+extern sig_atomic_t    ngx_reconfigure;
+extern sig_atomic_t    ngx_reopen;
+extern sig_atomic_t    ngx_change_binary;
+
+
+#endif /* _NGX_PROCESS_CYCLE_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_readv_chain.c b/nginx/src/os/unix/ngx_readv_chain.c
new file mode 100644 (file)
index 0000000..098b524
--- /dev/null
@@ -0,0 +1,214 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+ssize_t
+ngx_readv_chain(ngx_connection_t *c, ngx_chain_t *chain, off_t limit)
+{
+    u_char        *prev;
+    ssize_t        n, size;
+    ngx_err_t      err;
+    ngx_array_t    vec;
+    ngx_event_t   *rev;
+    struct iovec  *iov, iovs[NGX_IOVS_PREALLOCATE];
+
+    rev = c->read;
+
+#if (NGX_HAVE_KQUEUE)
+
+    if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "readv: eof:%d, avail:%d, err:%d",
+                       rev->pending_eof, rev->available, rev->kq_errno);
+
+        if (rev->available == 0) {
+            if (rev->pending_eof) {
+                rev->ready = 0;
+                rev->eof = 1;
+
+                ngx_log_error(NGX_LOG_INFO, c->log, rev->kq_errno,
+                              "kevent() reported about an closed connection");
+
+                if (rev->kq_errno) {
+                    rev->error = 1;
+                    ngx_set_socket_errno(rev->kq_errno);
+                    return NGX_ERROR;
+                }
+
+                return 0;
+
+            } else {
+                return NGX_AGAIN;
+            }
+        }
+    }
+
+#endif
+
+#if (NGX_HAVE_EPOLLRDHUP)
+
+    if (ngx_event_flags & NGX_USE_EPOLL_EVENT) {
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "readv: eof:%d, avail:%d",
+                       rev->pending_eof, rev->available);
+
+        if (!rev->available && !rev->pending_eof) {
+            return NGX_AGAIN;
+        }
+    }
+
+#endif
+
+    prev = NULL;
+    iov = NULL;
+    size = 0;
+
+    vec.elts = iovs;
+    vec.nelts = 0;
+    vec.size = sizeof(struct iovec);
+    vec.nalloc = NGX_IOVS_PREALLOCATE;
+    vec.pool = c->pool;
+
+    /* coalesce the neighbouring bufs */
+
+    while (chain) {
+        n = chain->buf->end - chain->buf->last;
+
+        if (limit) {
+            if (size >= limit) {
+                break;
+            }
+
+            if (size + n > limit) {
+                n = (ssize_t) (limit - size);
+            }
+        }
+
+        if (prev == chain->buf->last) {
+            iov->iov_len += n;
+
+        } else {
+            if (vec.nelts >= IOV_MAX) {
+                break;
+            }
+
+            iov = ngx_array_push(&vec);
+            if (iov == NULL) {
+                return NGX_ERROR;
+            }
+
+            iov->iov_base = (void *) chain->buf->last;
+            iov->iov_len = n;
+        }
+
+        size += n;
+        prev = chain->buf->end;
+        chain = chain->next;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "readv: %ui, last:%uz", vec.nelts, iov->iov_len);
+
+    do {
+        n = ngxvcl_readv(c->fd, (struct iovec *) vec.elts, vec.nelts);
+
+        if (n == 0) {
+            rev->ready = 0;
+            rev->eof = 1;
+
+#if (NGX_HAVE_KQUEUE)
+
+            /*
+             * on FreeBSD readv() may return 0 on closed socket
+             * even if kqueue reported about available data
+             */
+
+            if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+                rev->available = 0;
+            }
+
+#endif
+
+            return 0;
+        }
+
+        if (n > 0) {
+
+#if (NGX_HAVE_KQUEUE)
+
+            if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+                rev->available -= n;
+
+                /*
+                 * rev->available may be negative here because some additional
+                 * bytes may be received between kevent() and readv()
+                 */
+
+                if (rev->available <= 0) {
+                    if (!rev->pending_eof) {
+                        rev->ready = 0;
+                    }
+
+                    rev->available = 0;
+                }
+
+                return n;
+            }
+
+#endif
+
+#if (NGX_HAVE_EPOLLRDHUP)
+
+            if ((ngx_event_flags & NGX_USE_EPOLL_EVENT)
+                && ngx_use_epoll_rdhup)
+            {
+                if (n < size) {
+                    if (!rev->pending_eof) {
+                        rev->ready = 0;
+                    }
+
+                    rev->available = 0;
+                }
+
+                return n;
+            }
+
+#endif
+
+            if (n < size && !(ngx_event_flags & NGX_USE_GREEDY_EVENT)) {
+                rev->ready = 0;
+            }
+
+            return n;
+        }
+
+        err = ngx_socket_errno;
+
+        if (err == NGX_EAGAIN || err == NGX_EINTR) {
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+                           "readv() not ready");
+            n = NGX_AGAIN;
+
+        } else {
+            n = ngx_connection_error(c, err, "readv() failed");
+            break;
+        }
+
+    } while (err == NGX_EINTR);
+
+    rev->ready = 0;
+
+    if (n == NGX_ERROR) {
+        c->read->error = 1;
+    }
+
+    return n;
+}
diff --git a/nginx/src/os/unix/ngx_recv.c b/nginx/src/os/unix/ngx_recv.c
new file mode 100644 (file)
index 0000000..9aaa809
--- /dev/null
@@ -0,0 +1,167 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+ssize_t
+ngx_unix_recv(ngx_connection_t *c, u_char *buf, size_t size)
+{
+    ssize_t       n;
+    ngx_err_t     err;
+    ngx_event_t  *rev;
+
+    rev = c->read;
+
+#if (NGX_HAVE_KQUEUE)
+
+    if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "recv: eof:%d, avail:%d, err:%d",
+                       rev->pending_eof, rev->available, rev->kq_errno);
+
+        if (rev->available == 0) {
+            if (rev->pending_eof) {
+                rev->ready = 0;
+                rev->eof = 1;
+
+                if (rev->kq_errno) {
+                    rev->error = 1;
+                    ngx_set_socket_errno(rev->kq_errno);
+
+                    return ngx_connection_error(c, rev->kq_errno,
+                               "kevent() reported about an closed connection");
+                }
+
+                return 0;
+
+            } else {
+                rev->ready = 0;
+                return NGX_AGAIN;
+            }
+        }
+    }
+
+#endif
+
+#if (NGX_HAVE_EPOLLRDHUP)
+
+    if (ngx_event_flags & NGX_USE_EPOLL_EVENT) {
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "recv: eof:%d, avail:%d",
+                       rev->pending_eof, rev->available);
+
+        if (!rev->available && !rev->pending_eof) {
+            rev->ready = 0;
+            return NGX_AGAIN;
+        }
+    }
+
+#endif
+
+    do {
+        n = ngxvcl_recv(c->fd, buf, size, 0);
+
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "recv: fd:%d %z of %uz", c->fd, n, size);
+
+        if (n == 0) {
+            rev->ready = 0;
+            rev->eof = 1;
+
+#if (NGX_HAVE_KQUEUE)
+
+            /*
+             * on FreeBSD recv() may return 0 on closed socket
+             * even if kqueue reported about available data
+             */
+
+            if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+                rev->available = 0;
+            }
+
+#endif
+
+            return 0;
+        }
+
+        if (n > 0) {
+
+#if (NGX_HAVE_KQUEUE)
+
+            if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+                rev->available -= n;
+
+                /*
+                 * rev->available may be negative here because some additional
+                 * bytes may be received between kevent() and recv()
+                 */
+
+                if (rev->available <= 0) {
+                    if (!rev->pending_eof) {
+                        rev->ready = 0;
+                    }
+
+                    rev->available = 0;
+                }
+
+                return n;
+            }
+
+#endif
+
+#if (NGX_HAVE_EPOLLRDHUP)
+
+            if ((ngx_event_flags & NGX_USE_EPOLL_EVENT)
+                && ngx_use_epoll_rdhup)
+            {
+                if ((size_t) n < size) {
+                    if (!rev->pending_eof) {
+                        rev->ready = 0;
+                    }
+
+                    rev->available = 0;
+                }
+
+                return n;
+            }
+
+#endif
+
+            if ((size_t) n < size
+                && !(ngx_event_flags & NGX_USE_GREEDY_EVENT))
+            {
+                rev->ready = 0;
+            }
+
+            return n;
+        }
+
+        err = ngx_socket_errno;
+
+        if (err == NGX_EAGAIN || err == NGX_EINTR) {
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+                           "recv() not ready");
+            n = NGX_AGAIN;
+
+        } else {
+            n = ngx_connection_error(c, err, "recv() failed");
+            break;
+        }
+
+    } while (err == NGX_EINTR);
+
+    rev->ready = 0;
+
+    if (n == NGX_ERROR) {
+        rev->error = 1;
+    }
+
+    return n;
+}
diff --git a/nginx/src/os/unix/ngx_send.c b/nginx/src/os/unix/ngx_send.c
new file mode 100644 (file)
index 0000000..5b91df6
--- /dev/null
@@ -0,0 +1,73 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+ssize_t
+ngx_unix_send(ngx_connection_t *c, u_char *buf, size_t size)
+{
+    ssize_t       n;
+    ngx_err_t     err;
+    ngx_event_t  *wev;
+
+    wev = c->write;
+
+#if (NGX_HAVE_KQUEUE)
+
+    if ((ngx_event_flags & NGX_USE_KQUEUE_EVENT) && wev->pending_eof) {
+        (void) ngx_connection_error(c, wev->kq_errno,
+                               "kevent() reported about an closed connection");
+        wev->error = 1;
+        return NGX_ERROR;
+    }
+
+#endif
+
+    for ( ;; ) {
+        n = ngxvcl_send(c->fd, buf, size, 0);
+
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "send: fd:%d %z of %uz", c->fd, n, size);
+
+        if (n > 0) {
+            if (n < (ssize_t) size) {
+                wev->ready = 0;
+            }
+
+            c->sent += n;
+
+            return n;
+        }
+
+        err = ngx_socket_errno;
+
+        if (n == 0) {
+            ngx_log_error(NGX_LOG_ALERT, c->log, err, "send() returned zero");
+            wev->ready = 0;
+            return n;
+        }
+
+        if (err == NGX_EAGAIN || err == NGX_EINTR) {
+            wev->ready = 0;
+
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+                           "send() not ready");
+
+            if (err == NGX_EAGAIN) {
+                return NGX_AGAIN;
+            }
+
+        } else {
+            wev->error = 1;
+            (void) ngx_connection_error(c, err, "send() failed");
+            return NGX_ERROR;
+        }
+    }
+}
diff --git a/nginx/src/os/unix/ngx_setaffinity.c b/nginx/src/os/unix/ngx_setaffinity.c
new file mode 100644 (file)
index 0000000..34ec390
--- /dev/null
@@ -0,0 +1,53 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#if (NGX_HAVE_CPUSET_SETAFFINITY)
+
+void
+ngx_setaffinity(ngx_cpuset_t *cpu_affinity, ngx_log_t *log)
+{
+    ngx_uint_t  i;
+
+    for (i = 0; i < CPU_SETSIZE; i++) {
+        if (CPU_ISSET(i, cpu_affinity)) {
+            ngx_log_error(NGX_LOG_NOTICE, log, 0,
+                          "cpuset_setaffinity(): using cpu #%ui", i);
+        }
+    }
+
+    if (cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, -1,
+                           sizeof(cpuset_t), cpu_affinity) == -1)
+    {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "cpuset_setaffinity() failed");
+    }
+}
+
+#elif (NGX_HAVE_SCHED_SETAFFINITY)
+
+void
+ngx_setaffinity(ngx_cpuset_t *cpu_affinity, ngx_log_t *log)
+{
+    ngx_uint_t  i;
+
+    for (i = 0; i < CPU_SETSIZE; i++) {
+        if (CPU_ISSET(i, cpu_affinity)) {
+            ngx_log_error(NGX_LOG_NOTICE, log, 0,
+                          "sched_setaffinity(): using cpu #%ui", i);
+        }
+    }
+
+    if (sched_setaffinity(0, sizeof(cpu_set_t), cpu_affinity) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "sched_setaffinity() failed");
+    }
+}
+
+#endif
diff --git a/nginx/src/os/unix/ngx_setaffinity.h b/nginx/src/os/unix/ngx_setaffinity.h
new file mode 100644 (file)
index 0000000..a4139ed
--- /dev/null
@@ -0,0 +1,37 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ */
+
+#ifndef _NGX_SETAFFINITY_H_INCLUDED_
+#define _NGX_SETAFFINITY_H_INCLUDED_
+
+
+#if (NGX_HAVE_SCHED_SETAFFINITY || NGX_HAVE_CPUSET_SETAFFINITY)
+
+#define NGX_HAVE_CPU_AFFINITY 1
+
+#if (NGX_HAVE_SCHED_SETAFFINITY)
+
+typedef cpu_set_t  ngx_cpuset_t;
+
+#elif (NGX_HAVE_CPUSET_SETAFFINITY)
+
+#include <sys/cpuset.h>
+
+typedef cpuset_t  ngx_cpuset_t;
+
+#endif
+
+void ngx_setaffinity(ngx_cpuset_t *cpu_affinity, ngx_log_t *log);
+
+#else
+
+#define ngx_setaffinity(cpu_affinity, log)
+
+typedef uint64_t  ngx_cpuset_t;
+
+#endif
+
+
+#endif /* _NGX_SETAFFINITY_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_setproctitle.c b/nginx/src/os/unix/ngx_setproctitle.c
new file mode 100644 (file)
index 0000000..91afa51
--- /dev/null
@@ -0,0 +1,135 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#if (NGX_SETPROCTITLE_USES_ENV)
+
+/*
+ * To change the process title in Linux and Solaris we have to set argv[1]
+ * to NULL and to copy the title to the same place where the argv[0] points to.
+ * However, argv[0] may be too small to hold a new title.  Fortunately, Linux
+ * and Solaris store argv[] and environ[] one after another.  So we should
+ * ensure that is the continuous memory and then we allocate the new memory
+ * for environ[] and copy it.  After this we could use the memory starting
+ * from argv[0] for our process title.
+ *
+ * The Solaris's standard /bin/ps does not show the changed process title.
+ * You have to use "/usr/ucb/ps -w" instead.  Besides, the UCB ps does not
+ * show a new title if its length less than the origin command line length.
+ * To avoid it we append to a new title the origin command line in the
+ * parenthesis.
+ */
+
+extern char **environ;
+
+static char *ngx_os_argv_last;
+
+ngx_int_t
+ngx_init_setproctitle(ngx_log_t *log)
+{
+    u_char      *p;
+    size_t       size;
+    ngx_uint_t   i;
+
+    size = 0;
+
+    for (i = 0; environ[i]; i++) {
+        size += ngx_strlen(environ[i]) + 1;
+    }
+
+    p = ngx_alloc(size, log);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_os_argv_last = ngx_os_argv[0];
+
+    for (i = 0; ngx_os_argv[i]; i++) {
+        if (ngx_os_argv_last == ngx_os_argv[i]) {
+            ngx_os_argv_last = ngx_os_argv[i] + ngx_strlen(ngx_os_argv[i]) + 1;
+        }
+    }
+
+    for (i = 0; environ[i]; i++) {
+        if (ngx_os_argv_last == environ[i]) {
+
+            size = ngx_strlen(environ[i]) + 1;
+            ngx_os_argv_last = environ[i] + size;
+
+            ngx_cpystrn(p, (u_char *) environ[i], size);
+            environ[i] = (char *) p;
+            p += size;
+        }
+    }
+
+    ngx_os_argv_last--;
+
+    return NGX_OK;
+}
+
+
+void
+ngx_setproctitle(char *title)
+{
+    u_char     *p;
+
+#if (NGX_SOLARIS)
+
+    ngx_int_t   i;
+    size_t      size;
+
+#endif
+
+    ngx_os_argv[1] = NULL;
+
+    p = ngx_cpystrn((u_char *) ngx_os_argv[0], (u_char *) "nginx: ",
+                    ngx_os_argv_last - ngx_os_argv[0]);
+
+    p = ngx_cpystrn(p, (u_char *) title, ngx_os_argv_last - (char *) p);
+
+#if (NGX_SOLARIS)
+
+    size = 0;
+
+    for (i = 0; i < ngx_argc; i++) {
+        size += ngx_strlen(ngx_argv[i]) + 1;
+    }
+
+    if (size > (size_t) ((char *) p - ngx_os_argv[0])) {
+
+        /*
+         * ngx_setproctitle() is too rare operation so we use
+         * the non-optimized copies
+         */
+
+        p = ngx_cpystrn(p, (u_char *) " (", ngx_os_argv_last - (char *) p);
+
+        for (i = 0; i < ngx_argc; i++) {
+            p = ngx_cpystrn(p, (u_char *) ngx_argv[i],
+                            ngx_os_argv_last - (char *) p);
+            p = ngx_cpystrn(p, (u_char *) " ", ngx_os_argv_last - (char *) p);
+        }
+
+        if (*(p - 1) == ' ') {
+            *(p - 1) = ')';
+        }
+    }
+
+#endif
+
+    if (ngx_os_argv_last - (char *) p) {
+        ngx_memset(p, NGX_SETPROCTITLE_PAD, ngx_os_argv_last - (char *) p);
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
+                   "setproctitle: \"%s\"", ngx_os_argv[0]);
+}
+
+#endif /* NGX_SETPROCTITLE_USES_ENV */
diff --git a/nginx/src/os/unix/ngx_setproctitle.h b/nginx/src/os/unix/ngx_setproctitle.h
new file mode 100644 (file)
index 0000000..c363662
--- /dev/null
@@ -0,0 +1,52 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_SETPROCTITLE_H_INCLUDED_
+#define _NGX_SETPROCTITLE_H_INCLUDED_
+
+
+#if (NGX_HAVE_SETPROCTITLE)
+
+/* FreeBSD, NetBSD, OpenBSD */
+
+#define ngx_init_setproctitle(log) NGX_OK
+#define ngx_setproctitle(title)    setproctitle("%s", title)
+
+
+#else /* !NGX_HAVE_SETPROCTITLE */
+
+#if !defined NGX_SETPROCTITLE_USES_ENV
+
+#if (NGX_SOLARIS)
+
+#define NGX_SETPROCTITLE_USES_ENV  1
+#define NGX_SETPROCTITLE_PAD       ' '
+
+ngx_int_t ngx_init_setproctitle(ngx_log_t *log);
+void ngx_setproctitle(char *title);
+
+#elif (NGX_LINUX) || (NGX_DARWIN)
+
+#define NGX_SETPROCTITLE_USES_ENV  1
+#define NGX_SETPROCTITLE_PAD       '\0'
+
+ngx_int_t ngx_init_setproctitle(ngx_log_t *log);
+void ngx_setproctitle(char *title);
+
+#else
+
+#define ngx_init_setproctitle(log) NGX_OK
+#define ngx_setproctitle(title)
+
+#endif /* OSes */
+
+#endif /* NGX_SETPROCTITLE_USES_ENV */
+
+#endif /* NGX_HAVE_SETPROCTITLE */
+
+
+#endif /* _NGX_SETPROCTITLE_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_shmem.c b/nginx/src/os/unix/ngx_shmem.c
new file mode 100644 (file)
index 0000000..3ec7cbf
--- /dev/null
@@ -0,0 +1,126 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#if (NGX_HAVE_MAP_ANON)
+
+ngx_int_t
+ngx_shm_alloc(ngx_shm_t *shm)
+{
+    shm->addr = (u_char *) mmap(NULL, shm->size,
+                                PROT_READ|PROT_WRITE,
+                                MAP_ANON|MAP_SHARED, -1, 0);
+
+    if (shm->addr == MAP_FAILED) {
+        ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
+                      "mmap(MAP_ANON|MAP_SHARED, %uz) failed", shm->size);
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+void
+ngx_shm_free(ngx_shm_t *shm)
+{
+    if (munmap((void *) shm->addr, shm->size) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
+                      "munmap(%p, %uz) failed", shm->addr, shm->size);
+    }
+}
+
+#elif (NGX_HAVE_MAP_DEVZERO)
+
+ngx_int_t
+ngx_shm_alloc(ngx_shm_t *shm)
+{
+    ngx_fd_t  fd;
+
+    fd = open("/dev/zero", O_RDWR);
+
+    if (fd == -1) {
+        ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
+                      "open(\"/dev/zero\") failed");
+        return NGX_ERROR;
+    }
+
+    shm->addr = (u_char *) mmap(NULL, shm->size, PROT_READ|PROT_WRITE,
+                                MAP_SHARED, fd, 0);
+
+    if (shm->addr == MAP_FAILED) {
+        ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
+                      "mmap(/dev/zero, MAP_SHARED, %uz) failed", shm->size);
+    }
+
+    if (close(fd) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
+                      "close(\"/dev/zero\") failed");
+    }
+
+    return (shm->addr == MAP_FAILED) ? NGX_ERROR : NGX_OK;
+}
+
+
+void
+ngx_shm_free(ngx_shm_t *shm)
+{
+    if (munmap((void *) shm->addr, shm->size) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
+                      "munmap(%p, %uz) failed", shm->addr, shm->size);
+    }
+}
+
+#elif (NGX_HAVE_SYSVSHM)
+
+#include <sys/ipc.h>
+#include <sys/shm.h>
+
+
+ngx_int_t
+ngx_shm_alloc(ngx_shm_t *shm)
+{
+    int  id;
+
+    id = shmget(IPC_PRIVATE, shm->size, (SHM_R|SHM_W|IPC_CREAT));
+
+    if (id == -1) {
+        ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
+                      "shmget(%uz) failed", shm->size);
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, shm->log, 0, "shmget id: %d", id);
+
+    shm->addr = shmat(id, NULL, 0);
+
+    if (shm->addr == (void *) -1) {
+        ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "shmat() failed");
+    }
+
+    if (shmctl(id, IPC_RMID, NULL) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
+                      "shmctl(IPC_RMID) failed");
+    }
+
+    return (shm->addr == (void *) -1) ? NGX_ERROR : NGX_OK;
+}
+
+
+void
+ngx_shm_free(ngx_shm_t *shm)
+{
+    if (shmdt(shm->addr) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
+                      "shmdt(%p) failed", shm->addr);
+    }
+}
+
+#endif
diff --git a/nginx/src/os/unix/ngx_shmem.h b/nginx/src/os/unix/ngx_shmem.h
new file mode 100644 (file)
index 0000000..566a7d3
--- /dev/null
@@ -0,0 +1,29 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_SHMEM_H_INCLUDED_
+#define _NGX_SHMEM_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef struct {
+    u_char      *addr;
+    size_t       size;
+    ngx_str_t    name;
+    ngx_log_t   *log;
+    ngx_uint_t   exists;   /* unsigned  exists:1;  */
+} ngx_shm_t;
+
+
+ngx_int_t ngx_shm_alloc(ngx_shm_t *shm);
+void ngx_shm_free(ngx_shm_t *shm);
+
+
+#endif /* _NGX_SHMEM_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_socket.c b/nginx/src/os/unix/ngx_socket.c
new file mode 100644 (file)
index 0000000..b3d5fe6
--- /dev/null
@@ -0,0 +1,116 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/*
+ * ioctl(FIONBIO) sets a non-blocking mode with the single syscall
+ * while fcntl(F_SETFL, O_NONBLOCK) needs to learn the current state
+ * using fcntl(F_GETFL).
+ *
+ * ioctl() and fcntl() are syscalls at least in FreeBSD 2.x, Linux 2.2
+ * and Solaris 7.
+ *
+ * ioctl() in Linux 2.4 and 2.6 uses BKL, however, fcntl(F_SETFL) uses it too.
+ */
+
+
+#if (NGX_HAVE_FIONBIO)
+
+int
+ngx_nonblocking(ngx_socket_t s)
+{
+    int  nb;
+
+    nb = 1;
+
+    return ngxvcl_kvfd_ioctl(s, FIONBIO, &nb);
+}
+
+
+int
+ngx_blocking(ngx_socket_t s)
+{
+    int  nb;
+
+    nb = 0;
+
+    return ngxvcl_kvfd_ioctl(s, FIONBIO, &nb);
+}
+
+#endif
+
+
+#if (NGX_FREEBSD)
+
+int
+ngx_tcp_nopush(ngx_socket_t s)
+{
+    int  tcp_nopush;
+
+    tcp_nopush = 1;
+
+    return ngxvcl_kvfd_setsockopt(s, IPPROTO_TCP, TCP_NOPUSH,
+                      (const void *) &tcp_nopush, sizeof(int));
+}
+
+
+int
+ngx_tcp_push(ngx_socket_t s)
+{
+    int  tcp_nopush;
+
+    tcp_nopush = 0;
+
+    return ngxvcl_kvfd_setsockopt(s, IPPROTO_TCP, TCP_NOPUSH,
+                      (const void *) &tcp_nopush, sizeof(int));
+}
+
+#elif (NGX_LINUX)
+
+
+int
+ngx_tcp_nopush(ngx_socket_t s)
+{
+    int  cork;
+
+    cork = 1;
+
+    return ngxvcl_kvfd_setsockopt(s, IPPROTO_TCP, TCP_CORK,
+                      (const void *) &cork, sizeof(int));
+}
+
+
+int
+ngx_tcp_push(ngx_socket_t s)
+{
+    int  cork;
+
+    cork = 0;
+
+    return ngxvcl_kvfd_setsockopt(s, IPPROTO_TCP, TCP_CORK,
+                      (const void *) &cork, sizeof(int));
+}
+
+#else
+
+int
+ngx_tcp_nopush(ngx_socket_t s)
+{
+    return 0;
+}
+
+
+int
+ngx_tcp_push(ngx_socket_t s)
+{
+    return 0;
+}
+
+#endif
diff --git a/nginx/src/os/unix/ngx_socket.h b/nginx/src/os/unix/ngx_socket.h
new file mode 100644 (file)
index 0000000..bcdde38
--- /dev/null
@@ -0,0 +1,64 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_SOCKET_H_INCLUDED_
+#define _NGX_SOCKET_H_INCLUDED_
+
+
+#include <ngx_config.h>
+
+
+#define NGX_WRITE_SHUTDOWN SHUT_WR
+
+typedef int  ngx_socket_t;
+
+#define ngx_socket          ngxvcl_socket
+#define ngx_socket_n        "socket()"
+
+
+#if (NGX_HAVE_FIONBIO)
+
+int ngx_nonblocking(ngx_socket_t s);
+int ngx_blocking(ngx_socket_t s);
+
+#define ngx_nonblocking_n   "ioctl(FIONBIO)"
+#define ngx_blocking_n      "ioctl(!FIONBIO)"
+
+#else
+
+#define ngx_nonblocking(s)  ngxvcl_kvfd_fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK)
+#define ngx_nonblocking_n   "fcntl(O_NONBLOCK)"
+
+#define ngx_blocking(s)     ngxvcl_kvfd_fcntl(s, F_SETFL, fcntl(s, F_GETFL) & ~O_NONBLOCK)
+#define ngx_blocking_n      "fcntl(!O_NONBLOCK)"
+
+#endif
+
+int ngx_tcp_nopush(ngx_socket_t s);
+int ngx_tcp_push(ngx_socket_t s);
+
+#if (NGX_LINUX)
+
+#define ngx_tcp_nopush_n   "setsockopt(TCP_CORK)"
+#define ngx_tcp_push_n     "setsockopt(!TCP_CORK)"
+
+#else
+
+#define ngx_tcp_nopush_n   "setsockopt(TCP_NOPUSH)"
+#define ngx_tcp_push_n     "setsockopt(!TCP_NOPUSH)"
+
+#endif
+
+
+#define ngx_shutdown_socket    ngxvcl_shutdown
+#define ngx_shutdown_socket_n  "shutdown()"
+
+#define ngx_close_socket    ngxvcl_close
+#define ngx_close_socket_n  "close() socket"
+
+
+#endif /* _NGX_SOCKET_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_solaris.h b/nginx/src/os/unix/ngx_solaris.h
new file mode 100644 (file)
index 0000000..7b167d8
--- /dev/null
@@ -0,0 +1,16 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_SOLARIS_H_INCLUDED_
+#define _NGX_SOLARIS_H_INCLUDED_
+
+
+ngx_chain_t *ngx_solaris_sendfilev_chain(ngx_connection_t *c, ngx_chain_t *in,
+    off_t limit);
+
+
+#endif /* _NGX_SOLARIS_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_solaris_config.h b/nginx/src/os/unix/ngx_solaris_config.h
new file mode 100644 (file)
index 0000000..ffa01c8
--- /dev/null
@@ -0,0 +1,112 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_SOLARIS_CONFIG_H_INCLUDED_
+#define _NGX_SOLARIS_CONFIG_H_INCLUDED_
+
+
+#ifndef _REENTRANT
+#define _REENTRANT
+#endif
+
+#define _FILE_OFFSET_BITS  64   /* must be before <sys/types.h> */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stddef.h>             /* offsetof() */
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <dirent.h>
+#include <glob.h>
+#include <time.h>
+#include <sys/statvfs.h>        /* statvfs() */
+
+#include <sys/filio.h>          /* FIONBIO */
+#include <sys/uio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sched.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>        /* TCP_NODELAY */
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/un.h>
+
+#include <sys/systeminfo.h>
+#include <limits.h>             /* IOV_MAX */
+#include <inttypes.h>
+#include <crypt.h>
+
+#include <dlfcn.h>
+
+#define NGX_ALIGNMENT  _MAX_ALIGNMENT
+
+#include <ngx_auto_config.h>
+
+
+#if (NGX_HAVE_POSIX_SEM)
+#include <semaphore.h>
+#endif
+
+
+#if (NGX_HAVE_POLL)
+#include <poll.h>
+#endif
+
+
+#if (NGX_HAVE_DEVPOLL)
+#include <sys/ioctl.h>
+#include <sys/devpoll.h>
+#endif
+
+
+#if (NGX_HAVE_EVENTPORT)
+#include <port.h>
+#endif
+
+
+#if (NGX_HAVE_SENDFILE)
+#include <sys/sendfile.h>
+#endif
+
+
+#define NGX_LISTEN_BACKLOG           511
+
+
+#ifndef NGX_HAVE_INHERITED_NONBLOCK
+#define NGX_HAVE_INHERITED_NONBLOCK  1
+#endif
+
+
+#ifndef NGX_HAVE_SO_SNDLOWAT
+/* setsockopt(SO_SNDLOWAT) returns ENOPROTOOPT */
+#define NGX_HAVE_SO_SNDLOWAT         0
+#endif
+
+
+#define NGX_HAVE_OS_SPECIFIC_INIT    1
+#define ngx_debug_init()
+
+
+extern char **environ;
+
+
+#endif /* _NGX_SOLARIS_CONFIG_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_solaris_init.c b/nginx/src/os/unix/ngx_solaris_init.c
new file mode 100644 (file)
index 0000000..65d7875
--- /dev/null
@@ -0,0 +1,77 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+char ngx_solaris_sysname[20];
+char ngx_solaris_release[10];
+char ngx_solaris_version[50];
+
+
+static ngx_os_io_t ngx_solaris_io = {
+    ngx_unix_recv,
+    ngx_readv_chain,
+    ngx_udp_unix_recv,
+    ngx_unix_send,
+    ngx_udp_unix_send,
+    ngx_udp_unix_sendmsg_chain,
+#if (NGX_HAVE_SENDFILE)
+    ngx_solaris_sendfilev_chain,
+    NGX_IO_SENDFILE
+#else
+    ngx_writev_chain,
+    0
+#endif
+};
+
+
+ngx_int_t
+ngx_os_specific_init(ngx_log_t *log)
+{
+    if (sysinfo(SI_SYSNAME, ngx_solaris_sysname, sizeof(ngx_solaris_sysname))
+        == -1)
+    {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "sysinfo(SI_SYSNAME) failed");
+        return NGX_ERROR;
+    }
+
+    if (sysinfo(SI_RELEASE, ngx_solaris_release, sizeof(ngx_solaris_release))
+        == -1)
+    {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "sysinfo(SI_RELEASE) failed");
+        return NGX_ERROR;
+    }
+
+    if (sysinfo(SI_VERSION, ngx_solaris_version, sizeof(ngx_solaris_version))
+        == -1)
+    {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "sysinfo(SI_SYSNAME) failed");
+        return NGX_ERROR;
+    }
+
+
+    ngx_os_io = ngx_solaris_io;
+
+    return NGX_OK;
+}
+
+
+void
+ngx_os_specific_status(ngx_log_t *log)
+{
+
+    ngx_log_error(NGX_LOG_NOTICE, log, 0, "OS: %s %s",
+                  ngx_solaris_sysname, ngx_solaris_release);
+
+    ngx_log_error(NGX_LOG_NOTICE, log, 0, "version: %s",
+                  ngx_solaris_version);
+}
diff --git a/nginx/src/os/unix/ngx_solaris_sendfilev_chain.c b/nginx/src/os/unix/ngx_solaris_sendfilev_chain.c
new file mode 100644 (file)
index 0000000..39bcafa
--- /dev/null
@@ -0,0 +1,228 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if (NGX_TEST_BUILD_SOLARIS_SENDFILEV)
+
+/* Solaris declarations */
+
+typedef struct sendfilevec {
+    int     sfv_fd;
+    u_int   sfv_flag;
+    off_t   sfv_off;
+    size_t  sfv_len;
+} sendfilevec_t;
+
+#define SFV_FD_SELF  -2
+
+static ssize_t sendfilev(int fd, const struct sendfilevec *vec,
+    int sfvcnt, size_t *xferred)
+{
+    return -1;
+}
+
+ngx_chain_t *ngx_solaris_sendfilev_chain(ngx_connection_t *c, ngx_chain_t *in,
+    off_t limit);
+
+#endif
+
+
+#define NGX_SENDFILEVECS  NGX_IOVS_PREALLOCATE
+
+
+ngx_chain_t *
+ngx_solaris_sendfilev_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
+{
+    int             fd;
+    u_char         *prev;
+    off_t           size, send, prev_send, aligned, fprev;
+    size_t          sent;
+    ssize_t         n;
+    ngx_int_t       eintr;
+    ngx_err_t       err;
+    ngx_buf_t      *file;
+    ngx_uint_t      nsfv;
+    sendfilevec_t  *sfv, sfvs[NGX_SENDFILEVECS];
+    ngx_event_t    *wev;
+    ngx_chain_t    *cl;
+
+    wev = c->write;
+
+    if (!wev->ready) {
+        return in;
+    }
+
+    if (!c->sendfile) {
+        return ngx_writev_chain(c, in, limit);
+    }
+
+
+    /* the maximum limit size is the maximum size_t value - the page size */
+
+    if (limit == 0 || limit > (off_t) (NGX_MAX_SIZE_T_VALUE - ngx_pagesize)) {
+        limit = NGX_MAX_SIZE_T_VALUE - ngx_pagesize;
+    }
+
+
+    send = 0;
+
+    for ( ;; ) {
+        fd = SFV_FD_SELF;
+        prev = NULL;
+        fprev = 0;
+        file = NULL;
+        sfv = NULL;
+        eintr = 0;
+        sent = 0;
+        prev_send = send;
+
+        nsfv = 0;
+
+        /* create the sendfilevec and coalesce the neighbouring bufs */
+
+        for (cl = in; cl && send < limit; cl = cl->next) {
+
+            if (ngx_buf_special(cl->buf)) {
+                continue;
+            }
+
+            if (ngx_buf_in_memory_only(cl->buf)) {
+                fd = SFV_FD_SELF;
+
+                size = cl->buf->last - cl->buf->pos;
+
+                if (send + size > limit) {
+                    size = limit - send;
+                }
+
+                if (prev == cl->buf->pos) {
+                    sfv->sfv_len += (size_t) size;
+
+                } else {
+                    if (nsfv == NGX_SENDFILEVECS) {
+                        break;
+                    }
+
+                    sfv = &sfvs[nsfv++];
+
+                    sfv->sfv_fd = SFV_FD_SELF;
+                    sfv->sfv_flag = 0;
+                    sfv->sfv_off = (off_t) (uintptr_t) cl->buf->pos;
+                    sfv->sfv_len = (size_t) size;
+                }
+
+                prev = cl->buf->pos + (size_t) size;
+                send += size;
+
+            } else {
+                prev = NULL;
+
+                size = cl->buf->file_last - cl->buf->file_pos;
+
+                if (send + size > limit) {
+                    size = limit - send;
+
+                    aligned = (cl->buf->file_pos + size + ngx_pagesize - 1)
+                               & ~((off_t) ngx_pagesize - 1);
+
+                    if (aligned <= cl->buf->file_last) {
+                        size = aligned - cl->buf->file_pos;
+                    }
+                }
+
+                if (fd == cl->buf->file->fd && fprev == cl->buf->file_pos) {
+                    sfv->sfv_len += (size_t) size;
+
+                } else {
+                    if (nsfv == NGX_SENDFILEVECS) {
+                        break;
+                    }
+
+                    sfv = &sfvs[nsfv++];
+
+                    fd = cl->buf->file->fd;
+                    sfv->sfv_fd = fd;
+                    sfv->sfv_flag = 0;
+                    sfv->sfv_off = cl->buf->file_pos;
+                    sfv->sfv_len = (size_t) size;
+                }
+
+                file = cl->buf;
+                fprev = cl->buf->file_pos + size;
+                send += size;
+            }
+        }
+
+        n = sendfilev(c->fd, sfvs, nsfv, &sent);
+
+        if (n == -1) {
+            err = ngx_errno;
+
+            switch (err) {
+            case NGX_EAGAIN:
+                break;
+
+            case NGX_EINTR:
+                eintr = 1;
+                break;
+
+            default:
+                wev->error = 1;
+                ngx_connection_error(c, err, "sendfilev() failed");
+                return NGX_CHAIN_ERROR;
+            }
+
+            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, err,
+                          "sendfilev() sent only %uz bytes", sent);
+
+        } else if (n == 0 && sent == 0) {
+
+            /*
+             * sendfilev() is documented to return -1 with errno
+             * set to EINVAL if svf_len is greater than the file size,
+             * but at least Solaris 11 returns 0 instead
+             */
+
+            if (file) {
+                ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                        "sendfilev() reported that \"%s\" was truncated at %O",
+                        file->file->name.data, file->file_pos);
+
+            } else {
+                ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                              "sendfilev() returned 0 with memory buffers");
+            }
+
+            return NGX_CHAIN_ERROR;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "sendfilev: %z %z", n, sent);
+
+        c->sent += sent;
+
+        in = ngx_chain_update_sent(in, sent);
+
+        if (eintr) {
+            send = prev_send + sent;
+            continue;
+        }
+
+        if (send - prev_send != (off_t) sent) {
+            wev->ready = 0;
+            return in;
+        }
+
+        if (send >= limit || in == NULL) {
+            return in;
+        }
+    }
+}
diff --git a/nginx/src/os/unix/ngx_sunpro_amd64.il b/nginx/src/os/unix/ngx_sunpro_amd64.il
new file mode 100644 (file)
index 0000000..07f3210
--- /dev/null
@@ -0,0 +1,43 @@
+/
+/ Copyright (C) Igor Sysoev
+/ Copyright (C) Nginx, Inc.
+/
+
+/ ngx_atomic_uint_t ngx_atomic_cmp_set(ngx_atomic_t *lock,
+/     ngx_atomic_uint_t old, ngx_atomic_uint_t set);
+/
+/ the arguments are passed in %rdi, %rsi, %rdx
+/ the result is returned in the %rax
+
+        .inline ngx_atomic_cmp_set,0
+        movq      %rsi, %rax
+        lock
+        cmpxchgq  %rdx, (%rdi)
+        setz      %al
+        movzbq    %al, %rax
+        .end
+
+
+/ ngx_atomic_int_t ngx_atomic_fetch_add(ngx_atomic_t *value,
+/     ngx_atomic_int_t add);
+/
+/ the arguments are passed in %rdi, %rsi
+/ the result is returned in the %rax
+
+        .inline ngx_atomic_fetch_add,0
+        movq      %rsi, %rax
+        lock
+        xaddq     %rax, (%rdi)
+        .end
+
+
+/ ngx_cpu_pause()
+/
+/ the "rep; nop" is used instead of "pause" to avoid the "[ PAUSE ]" hardware
+/ capability added by linker because Solaris/amd64 does not know about it:
+/
+/ ld.so.1: nginx: fatal: hardware capability unsupported: 0x2000 [ PAUSE ]
+
+        .inline ngx_cpu_pause,0
+        rep; nop
+        .end
diff --git a/nginx/src/os/unix/ngx_sunpro_atomic_sparc64.h b/nginx/src/os/unix/ngx_sunpro_atomic_sparc64.h
new file mode 100644 (file)
index 0000000..5f28055
--- /dev/null
@@ -0,0 +1,61 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#if (NGX_PTR_SIZE == 4)
+#define NGX_CASA  ngx_casa
+#else
+#define NGX_CASA  ngx_casxa
+#endif
+
+
+ngx_atomic_uint_t
+ngx_casa(ngx_atomic_uint_t set, ngx_atomic_uint_t old, ngx_atomic_t *lock);
+
+ngx_atomic_uint_t
+ngx_casxa(ngx_atomic_uint_t set, ngx_atomic_uint_t old, ngx_atomic_t *lock);
+
+/* the code in src/os/unix/ngx_sunpro_sparc64.il */
+
+
+static ngx_inline ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+    ngx_atomic_uint_t set)
+{
+    set = NGX_CASA(set, old, lock);
+
+    return (set == old);
+}
+
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+    ngx_atomic_uint_t  old, res;
+
+    old = *value;
+
+    for ( ;; ) {
+
+        res = old + add;
+
+        res = NGX_CASA(res, old, value);
+
+        if (res == old) {
+            return res;
+        }
+
+        old = res;
+    }
+}
+
+
+#define ngx_memory_barrier()                                                  \
+        __asm (".volatile");                                                  \
+        __asm ("membar #LoadLoad | #LoadStore | #StoreStore | #StoreLoad");   \
+        __asm (".nonvolatile")
+
+#define ngx_cpu_pause()
diff --git a/nginx/src/os/unix/ngx_sunpro_sparc64.il b/nginx/src/os/unix/ngx_sunpro_sparc64.il
new file mode 100644 (file)
index 0000000..bdeef61
--- /dev/null
@@ -0,0 +1,36 @@
+/
+/ Copyright (C) Igor Sysoev
+/ Copyright (C) Nginx, Inc.
+/
+
+
+/  "casa   [%o2] 0x80, %o1, %o0"  and
+/  "casxa  [%o2] 0x80, %o1, %o0"  do the following:
+/
+/       if ([%o2] == %o1) {
+/           swap(%o0, [%o2]);
+/       } else {
+/           %o0 = [%o2];
+/       }
+
+
+/ ngx_atomic_uint_t ngx_casa(ngx_atomic_uint_t set, ngx_atomic_uint_t old,
+/      ngx_atomic_t *lock);
+/
+/ the arguments are passed in the %o0, %o1, %o2
+/ the result is returned in the %o0
+
+        .inline ngx_casa,0
+        casa    [%o2] 0x80, %o1, %o0
+        .end
+
+
+/ ngx_atomic_uint_t ngx_casxa(ngx_atomic_uint_t set, ngx_atomic_uint_t old,
+/      ngx_atomic_t *lock);
+/
+/ the arguments are passed in the %o0, %o1, %o2
+/ the result is returned in the %o0
+
+        .inline ngx_casxa,0
+        casxa   [%o2] 0x80, %o1, %o0
+        .end
diff --git a/nginx/src/os/unix/ngx_sunpro_x86.il b/nginx/src/os/unix/ngx_sunpro_x86.il
new file mode 100644 (file)
index 0000000..d7e127c
--- /dev/null
@@ -0,0 +1,44 @@
+/
+/ Copyright (C) Igor Sysoev
+/ Copyright (C) Nginx, Inc.
+/
+
+/ ngx_atomic_uint_t ngx_atomic_cmp_set(ngx_atomic_t *lock,
+/     ngx_atomic_uint_t old, ngx_atomic_uint_t set);
+/
+/ the arguments are passed on stack (%esp), 4(%esp), 8(%esp)
+
+        .inline ngx_atomic_cmp_set,0
+        movl      (%esp), %ecx
+        movl      4(%esp), %eax
+        movl      8(%esp), %edx
+        lock
+        cmpxchgl  %edx, (%ecx)
+        setz      %al
+        movzbl    %al, %eax
+        .end
+
+
+/ ngx_atomic_int_t ngx_atomic_fetch_add(ngx_atomic_t *value,
+/     ngx_atomic_int_t add);
+/
+/ the arguments are passed on stack (%esp), 4(%esp)
+
+        .inline ngx_atomic_fetch_add,0
+        movl      (%esp), %ecx
+        movl      4(%esp), %eax
+        lock
+        xaddl     %eax, (%ecx)
+        .end
+
+
+/ ngx_cpu_pause()
+/
+/ the "rep; nop" is used instead of "pause" to avoid the "[ PAUSE ]" hardware
+/ capability added by linker because Solaris/i386 does not know about it:
+/
+/ ld.so.1: nginx: fatal: hardware capability unsupported: 0x2000  [ PAUSE ]
+
+        .inline ngx_cpu_pause,0
+        rep; nop
+        .end
diff --git a/nginx/src/os/unix/ngx_thread.h b/nginx/src/os/unix/ngx_thread.h
new file mode 100644 (file)
index 0000000..1b52dd7
--- /dev/null
@@ -0,0 +1,71 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_THREAD_H_INCLUDED_
+#define _NGX_THREAD_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+#if (NGX_THREADS)
+
+#include <pthread.h>
+
+
+typedef pthread_mutex_t  ngx_thread_mutex_t;
+
+ngx_int_t ngx_thread_mutex_create(ngx_thread_mutex_t *mtx, ngx_log_t *log);
+ngx_int_t ngx_thread_mutex_destroy(ngx_thread_mutex_t *mtx, ngx_log_t *log);
+ngx_int_t ngx_thread_mutex_lock(ngx_thread_mutex_t *mtx, ngx_log_t *log);
+ngx_int_t ngx_thread_mutex_unlock(ngx_thread_mutex_t *mtx, ngx_log_t *log);
+
+
+typedef pthread_cond_t  ngx_thread_cond_t;
+
+ngx_int_t ngx_thread_cond_create(ngx_thread_cond_t *cond, ngx_log_t *log);
+ngx_int_t ngx_thread_cond_destroy(ngx_thread_cond_t *cond, ngx_log_t *log);
+ngx_int_t ngx_thread_cond_signal(ngx_thread_cond_t *cond, ngx_log_t *log);
+ngx_int_t ngx_thread_cond_wait(ngx_thread_cond_t *cond, ngx_thread_mutex_t *mtx,
+    ngx_log_t *log);
+
+
+#if (NGX_LINUX)
+
+typedef pid_t      ngx_tid_t;
+#define NGX_TID_T_FMT         "%P"
+
+#elif (NGX_FREEBSD)
+
+typedef uint32_t   ngx_tid_t;
+#define NGX_TID_T_FMT         "%uD"
+
+#elif (NGX_DARWIN)
+
+typedef uint64_t   ngx_tid_t;
+#define NGX_TID_T_FMT         "%uA"
+
+#else
+
+typedef uint64_t   ngx_tid_t;
+#define NGX_TID_T_FMT         "%uA"
+
+#endif
+
+ngx_tid_t ngx_thread_tid(void);
+
+#define ngx_log_tid           ngx_thread_tid()
+
+#else
+
+#define ngx_log_tid           0
+#define NGX_TID_T_FMT         "%d"
+
+#endif
+
+
+#endif /* _NGX_THREAD_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_thread_cond.c b/nginx/src/os/unix/ngx_thread_cond.c
new file mode 100644 (file)
index 0000000..2ad51b8
--- /dev/null
@@ -0,0 +1,76 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+ngx_int_t
+ngx_thread_cond_create(ngx_thread_cond_t *cond, ngx_log_t *log)
+{
+    ngx_err_t  err;
+
+    err = pthread_cond_init(cond, NULL);
+    if (err == 0) {
+        return NGX_OK;
+    }
+
+    ngx_log_error(NGX_LOG_EMERG, log, err, "pthread_cond_init() failed");
+    return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_thread_cond_destroy(ngx_thread_cond_t *cond, ngx_log_t *log)
+{
+    ngx_err_t  err;
+
+    err = pthread_cond_destroy(cond);
+    if (err == 0) {
+        return NGX_OK;
+    }
+
+    ngx_log_error(NGX_LOG_EMERG, log, err, "pthread_cond_destroy() failed");
+    return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_thread_cond_signal(ngx_thread_cond_t *cond, ngx_log_t *log)
+{
+    ngx_err_t  err;
+
+    err = pthread_cond_signal(cond);
+    if (err == 0) {
+        return NGX_OK;
+    }
+
+    ngx_log_error(NGX_LOG_EMERG, log, err, "pthread_cond_signal() failed");
+    return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_thread_cond_wait(ngx_thread_cond_t *cond, ngx_thread_mutex_t *mtx,
+    ngx_log_t *log)
+{
+    ngx_err_t  err;
+
+    err = pthread_cond_wait(cond, mtx);
+
+#if 0
+    ngx_time_update();
+#endif
+
+    if (err == 0) {
+        return NGX_OK;
+    }
+
+    ngx_log_error(NGX_LOG_ALERT, log, err, "pthread_cond_wait() failed");
+
+    return NGX_ERROR;
+}
diff --git a/nginx/src/os/unix/ngx_thread_id.c b/nginx/src/os/unix/ngx_thread_id.c
new file mode 100644 (file)
index 0000000..5174f1a
--- /dev/null
@@ -0,0 +1,70 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_thread_pool.h>
+
+
+#if (NGX_LINUX)
+
+/*
+ * Linux thread id is a pid of thread created by clone(2),
+ * glibc does not provide a wrapper for gettid().
+ */
+
+ngx_tid_t
+ngx_thread_tid(void)
+{
+    return syscall(SYS_gettid);
+}
+
+#elif (NGX_FREEBSD) && (__FreeBSD_version >= 900031)
+
+#include <pthread_np.h>
+
+ngx_tid_t
+ngx_thread_tid(void)
+{
+    return pthread_getthreadid_np();
+}
+
+#elif (NGX_DARWIN)
+
+/*
+ * MacOSX thread has two thread ids:
+ *
+ * 1) MacOSX 10.6 (Snow Leoprad) has pthread_threadid_np() returning
+ *    an uint64_t value, which is obtained using the __thread_selfid()
+ *    syscall.  It is a number above 300,000.
+ */
+
+ngx_tid_t
+ngx_thread_tid(void)
+{
+    uint64_t  tid;
+
+    (void) pthread_threadid_np(NULL, &tid);
+    return tid;
+}
+
+/*
+ * 2) Kernel thread mach_port_t returned by pthread_mach_thread_np().
+ *    It is a number in range 100-100,000.
+ *
+ * return pthread_mach_thread_np(pthread_self());
+ */
+
+#else
+
+ngx_tid_t
+ngx_thread_tid(void)
+{
+    return (uint64_t) (uintptr_t) pthread_self();
+}
+
+#endif
diff --git a/nginx/src/os/unix/ngx_thread_mutex.c b/nginx/src/os/unix/ngx_thread_mutex.c
new file mode 100644 (file)
index 0000000..4886f49
--- /dev/null
@@ -0,0 +1,165 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/*
+ * All modern pthread mutex implementations try to acquire a lock
+ * atomically in userland before going to sleep in kernel.  Some
+ * spins before the sleeping.
+ *
+ * In Solaris since version 8 all mutex types spin before sleeping.
+ * The default spin count is 1000.  It can be overridden using
+ * _THREAD_ADAPTIVE_SPIN=100 environment variable.
+ *
+ * In MacOSX all mutex types spin to acquire a lock protecting a mutex's
+ * internals.  If the mutex is busy, thread calls Mach semaphore_wait().
+ *
+ *
+ * PTHREAD_MUTEX_NORMAL lacks deadlock detection and is the fastest
+ * mutex type.
+ *
+ *   Linux:    No spinning.  The internal name PTHREAD_MUTEX_TIMED_NP
+ *             remains from the times when pthread_mutex_timedlock() was
+ *             non-standard extension.  Alias name: PTHREAD_MUTEX_FAST_NP.
+ *   FreeBSD:  No spinning.
+ *
+ *
+ * PTHREAD_MUTEX_ERRORCHECK is usually as fast as PTHREAD_MUTEX_NORMAL
+ * yet has lightweight deadlock detection.
+ *
+ *   Linux:    No spinning.  The internal name: PTHREAD_MUTEX_ERRORCHECK_NP.
+ *   FreeBSD:  No spinning.
+ *
+ *
+ * PTHREAD_MUTEX_RECURSIVE allows recursive locking.
+ *
+ *   Linux:    No spinning.  The internal name: PTHREAD_MUTEX_RECURSIVE_NP.
+ *   FreeBSD:  No spinning.
+ *
+ *
+ * PTHREAD_MUTEX_ADAPTIVE_NP spins on SMP systems before sleeping.
+ *
+ *   Linux:    No deadlock detection.  Dynamically changes a spin count
+ *             for each mutex from 10 to 100 based on spin count taken
+ *             previously.
+ *   FreeBSD:  Deadlock detection.  The default spin count is 2000.
+ *             It can be overridden using LIBPTHREAD_SPINLOOPS environment
+ *             variable or by pthread_mutex_setspinloops_np().  If a lock
+ *             is still busy, sched_yield() can be called on both UP and
+ *             SMP systems.  The default yield loop count is zero, but
+ *             it can be set by LIBPTHREAD_YIELDLOOPS environment
+ *             variable or by pthread_mutex_setyieldloops_np().
+ *   Solaris:  No PTHREAD_MUTEX_ADAPTIVE_NP.
+ *   MacOSX:   No PTHREAD_MUTEX_ADAPTIVE_NP.
+ *
+ *
+ * PTHREAD_MUTEX_ELISION_NP is a Linux extension to elide locks using
+ * Intel Restricted Transactional Memory.  It is the most suitable for
+ * rwlock pattern access because it allows simultaneous reads without lock.
+ * Supported since glibc 2.18.
+ *
+ *
+ * PTHREAD_MUTEX_DEFAULT is default mutex type.
+ *
+ *   Linux:    PTHREAD_MUTEX_NORMAL.
+ *   FreeBSD:  PTHREAD_MUTEX_ERRORCHECK.
+ *   Solaris:  PTHREAD_MUTEX_NORMAL.
+ *   MacOSX:   PTHREAD_MUTEX_NORMAL.
+ */
+
+
+ngx_int_t
+ngx_thread_mutex_create(ngx_thread_mutex_t *mtx, ngx_log_t *log)
+{
+    ngx_err_t            err;
+    pthread_mutexattr_t  attr;
+
+    err = pthread_mutexattr_init(&attr);
+    if (err != 0) {
+        ngx_log_error(NGX_LOG_EMERG, log, err,
+                      "pthread_mutexattr_init() failed");
+        return NGX_ERROR;
+    }
+
+    err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
+    if (err != 0) {
+        ngx_log_error(NGX_LOG_EMERG, log, err,
+                      "pthread_mutexattr_settype"
+                      "(PTHREAD_MUTEX_ERRORCHECK) failed");
+        return NGX_ERROR;
+    }
+
+    err = pthread_mutex_init(mtx, &attr);
+    if (err != 0) {
+        ngx_log_error(NGX_LOG_EMERG, log, err,
+                      "pthread_mutex_init() failed");
+        return NGX_ERROR;
+    }
+
+    err = pthread_mutexattr_destroy(&attr);
+    if (err != 0) {
+        ngx_log_error(NGX_LOG_ALERT, log, err,
+                      "pthread_mutexattr_destroy() failed");
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_thread_mutex_destroy(ngx_thread_mutex_t *mtx, ngx_log_t *log)
+{
+    ngx_err_t  err;
+
+    err = pthread_mutex_destroy(mtx);
+    if (err != 0) {
+        ngx_log_error(NGX_LOG_ALERT, log, err,
+                      "pthread_mutex_destroy() failed");
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_thread_mutex_lock(ngx_thread_mutex_t *mtx, ngx_log_t *log)
+{
+    ngx_err_t  err;
+
+    err = pthread_mutex_lock(mtx);
+    if (err == 0) {
+        return NGX_OK;
+    }
+
+    ngx_log_error(NGX_LOG_ALERT, log, err, "pthread_mutex_lock() failed");
+
+    return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_thread_mutex_unlock(ngx_thread_mutex_t *mtx, ngx_log_t *log)
+{
+    ngx_err_t  err;
+
+    err = pthread_mutex_unlock(mtx);
+
+#if 0
+    ngx_time_update();
+#endif
+
+    if (err == 0) {
+        return NGX_OK;
+    }
+
+    ngx_log_error(NGX_LOG_ALERT, log, err, "pthread_mutex_unlock() failed");
+
+    return NGX_ERROR;
+}
diff --git a/nginx/src/os/unix/ngx_time.c b/nginx/src/os/unix/ngx_time.c
new file mode 100644 (file)
index 0000000..cc760b2
--- /dev/null
@@ -0,0 +1,104 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/*
+ * FreeBSD does not test /etc/localtime change, however, we can workaround it
+ * by calling tzset() with TZ and then without TZ to update timezone.
+ * The trick should work since FreeBSD 2.1.0.
+ *
+ * Linux does not test /etc/localtime change in localtime(),
+ * but may stat("/etc/localtime") several times in every strftime(),
+ * therefore we use it to update timezone.
+ *
+ * Solaris does not test /etc/TIMEZONE change too and no workaround available.
+ */
+
+void
+ngx_timezone_update(void)
+{
+#if (NGX_FREEBSD)
+
+    if (getenv("TZ")) {
+        return;
+    }
+
+    putenv("TZ=UTC");
+
+    tzset();
+
+    unsetenv("TZ");
+
+    tzset();
+
+#elif (NGX_LINUX)
+    time_t      s;
+    struct tm  *t;
+    char        buf[4];
+
+    s = time(0);
+
+    t = localtime(&s);
+
+    strftime(buf, 4, "%H", t);
+
+#endif
+}
+
+
+void
+ngx_localtime(time_t s, ngx_tm_t *tm)
+{
+#if (NGX_HAVE_LOCALTIME_R)
+    (void) localtime_r(&s, tm);
+
+#else
+    ngx_tm_t  *t;
+
+    t = localtime(&s);
+    *tm = *t;
+
+#endif
+
+    tm->ngx_tm_mon++;
+    tm->ngx_tm_year += 1900;
+}
+
+
+void
+ngx_libc_localtime(time_t s, struct tm *tm)
+{
+#if (NGX_HAVE_LOCALTIME_R)
+    (void) localtime_r(&s, tm);
+
+#else
+    struct tm  *t;
+
+    t = localtime(&s);
+    *tm = *t;
+
+#endif
+}
+
+
+void
+ngx_libc_gmtime(time_t s, struct tm *tm)
+{
+#if (NGX_HAVE_LOCALTIME_R)
+    (void) gmtime_r(&s, tm);
+
+#else
+    struct tm  *t;
+
+    t = gmtime(&s);
+    *tm = *t;
+
+#endif
+}
diff --git a/nginx/src/os/unix/ngx_time.h b/nginx/src/os/unix/ngx_time.h
new file mode 100644 (file)
index 0000000..c128c9a
--- /dev/null
@@ -0,0 +1,66 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_TIME_H_INCLUDED_
+#define _NGX_TIME_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef ngx_rbtree_key_t      ngx_msec_t;
+typedef ngx_rbtree_key_int_t  ngx_msec_int_t;
+
+typedef struct tm             ngx_tm_t;
+
+#define ngx_tm_sec            tm_sec
+#define ngx_tm_min            tm_min
+#define ngx_tm_hour           tm_hour
+#define ngx_tm_mday           tm_mday
+#define ngx_tm_mon            tm_mon
+#define ngx_tm_year           tm_year
+#define ngx_tm_wday           tm_wday
+#define ngx_tm_isdst          tm_isdst
+
+#define ngx_tm_sec_t          int
+#define ngx_tm_min_t          int
+#define ngx_tm_hour_t         int
+#define ngx_tm_mday_t         int
+#define ngx_tm_mon_t          int
+#define ngx_tm_year_t         int
+#define ngx_tm_wday_t         int
+
+
+#if (NGX_HAVE_GMTOFF)
+#define ngx_tm_gmtoff         tm_gmtoff
+#define ngx_tm_zone           tm_zone
+#endif
+
+
+#if (NGX_SOLARIS)
+
+#define ngx_timezone(isdst) (- (isdst ? altzone : timezone) / 60)
+
+#else
+
+#define ngx_timezone(isdst) (- (isdst ? timezone + 3600 : timezone) / 60)
+
+#endif
+
+
+void ngx_timezone_update(void);
+void ngx_localtime(time_t s, ngx_tm_t *tm);
+void ngx_libc_localtime(time_t s, struct tm *tm);
+void ngx_libc_gmtime(time_t s, struct tm *tm);
+
+#define ngx_gettimeofday(tp)  (void) gettimeofday(tp, NULL);
+#define ngx_msleep(ms)        (void) usleep(ms * 1000)
+#define ngx_sleep(s)          (void) sleep(s)
+
+
+#endif /* _NGX_TIME_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_udp_recv.c b/nginx/src/os/unix/ngx_udp_recv.c
new file mode 100644 (file)
index 0000000..ced9261
--- /dev/null
@@ -0,0 +1,72 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+ssize_t
+ngx_udp_unix_recv(ngx_connection_t *c, u_char *buf, size_t size)
+{
+    ssize_t       n;
+    ngx_err_t     err;
+    ngx_event_t  *rev;
+
+    rev = c->read;
+
+    do {
+        n = ngxvcl_recv(c->fd, buf, size, 0);
+
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "recv: fd:%d %z of %uz", c->fd, n, size);
+
+        if (n >= 0) {
+
+#if (NGX_HAVE_KQUEUE)
+
+            if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+                rev->available -= n;
+
+                /*
+                 * rev->available may be negative here because some additional
+                 * bytes may be received between kevent() and recv()
+                 */
+
+                if (rev->available <= 0) {
+                    rev->ready = 0;
+                    rev->available = 0;
+                }
+            }
+
+#endif
+
+            return n;
+        }
+
+        err = ngx_socket_errno;
+
+        if (err == NGX_EAGAIN || err == NGX_EINTR) {
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+                           "recv() not ready");
+            n = NGX_AGAIN;
+
+        } else {
+            n = ngx_connection_error(c, err, "recv() failed");
+            break;
+        }
+
+    } while (err == NGX_EINTR);
+
+    rev->ready = 0;
+
+    if (n == NGX_ERROR) {
+        rev->error = 1;
+    }
+
+    return n;
+}
diff --git a/nginx/src/os/unix/ngx_udp_send.c b/nginx/src/os/unix/ngx_udp_send.c
new file mode 100644 (file)
index 0000000..ad1af9d
--- /dev/null
@@ -0,0 +1,56 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+ssize_t
+ngx_udp_unix_send(ngx_connection_t *c, u_char *buf, size_t size)
+{
+    ssize_t       n;
+    ngx_err_t     err;
+    ngx_event_t  *wev;
+
+    wev = c->write;
+
+    for ( ;; ) {
+        n = ngxvcl_sendto(c->fd, buf, size, 0, c->sockaddr, c->socklen);
+
+        ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "sendto: fd:%d %z of %uz to \"%V\"",
+                       c->fd, n, size, &c->addr_text);
+
+        if (n >= 0) {
+            if ((size_t) n != size) {
+                wev->error = 1;
+                (void) ngx_connection_error(c, 0, "sendto() incomplete");
+                return NGX_ERROR;
+            }
+
+            c->sent += n;
+
+            return n;
+        }
+
+        err = ngx_socket_errno;
+
+        if (err == NGX_EAGAIN) {
+            wev->ready = 0;
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, NGX_EAGAIN,
+                           "sendto() not ready");
+            return NGX_AGAIN;
+        }
+
+        if (err != NGX_EINTR) {
+            wev->error = 1;
+            (void) ngx_connection_error(c, err, "sendto() failed");
+            return NGX_ERROR;
+        }
+    }
+}
diff --git a/nginx/src/os/unix/ngx_udp_sendmsg_chain.c b/nginx/src/os/unix/ngx_udp_sendmsg_chain.c
new file mode 100644 (file)
index 0000000..7a04f0d
--- /dev/null
@@ -0,0 +1,335 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+static ngx_chain_t *ngx_udp_output_chain_to_iovec(ngx_iovec_t *vec,
+    ngx_chain_t *in, ngx_log_t *log);
+static ssize_t ngx_sendmsg(ngx_connection_t *c, ngx_iovec_t *vec);
+
+
+ngx_chain_t *
+ngx_udp_unix_sendmsg_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
+{
+    ssize_t        n;
+    off_t          send;
+    ngx_chain_t   *cl;
+    ngx_event_t   *wev;
+    ngx_iovec_t    vec;
+    struct iovec   iovs[NGX_IOVS_PREALLOCATE];
+
+    wev = c->write;
+
+    if (!wev->ready) {
+        return in;
+    }
+
+#if (NGX_HAVE_KQUEUE)
+
+    if ((ngx_event_flags & NGX_USE_KQUEUE_EVENT) && wev->pending_eof) {
+        (void) ngx_connection_error(c, wev->kq_errno,
+                               "kevent() reported about an closed connection");
+        wev->error = 1;
+        return NGX_CHAIN_ERROR;
+    }
+
+#endif
+
+    /* the maximum limit size is the maximum size_t value - the page size */
+
+    if (limit == 0 || limit > (off_t) (NGX_MAX_SIZE_T_VALUE - ngx_pagesize)) {
+        limit = NGX_MAX_SIZE_T_VALUE - ngx_pagesize;
+    }
+
+    send = 0;
+
+    vec.iovs = iovs;
+    vec.nalloc = NGX_IOVS_PREALLOCATE;
+
+    for ( ;; ) {
+
+        /* create the iovec and coalesce the neighbouring bufs */
+
+        cl = ngx_udp_output_chain_to_iovec(&vec, in, c->log);
+
+        if (cl == NGX_CHAIN_ERROR) {
+            return NGX_CHAIN_ERROR;
+        }
+
+        if (cl && cl->buf->in_file) {
+            ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                          "file buf in sendmsg "
+                          "t:%d r:%d f:%d %p %p-%p %p %O-%O",
+                          cl->buf->temporary,
+                          cl->buf->recycled,
+                          cl->buf->in_file,
+                          cl->buf->start,
+                          cl->buf->pos,
+                          cl->buf->last,
+                          cl->buf->file,
+                          cl->buf->file_pos,
+                          cl->buf->file_last);
+
+            ngx_debug_point();
+
+            return NGX_CHAIN_ERROR;
+        }
+
+        if (cl == in) {
+            return in;
+        }
+
+        send += vec.size;
+
+        n = ngx_sendmsg(c, &vec);
+
+        if (n == NGX_ERROR) {
+            return NGX_CHAIN_ERROR;
+        }
+
+        if (n == NGX_AGAIN) {
+            wev->ready = 0;
+            return in;
+        }
+
+        c->sent += n;
+
+        in = ngx_chain_update_sent(in, n);
+
+        if (send >= limit || in == NULL) {
+            return in;
+        }
+    }
+}
+
+
+static ngx_chain_t *
+ngx_udp_output_chain_to_iovec(ngx_iovec_t *vec, ngx_chain_t *in, ngx_log_t *log)
+{
+    size_t         total, size;
+    u_char        *prev;
+    ngx_uint_t     n, flush;
+    ngx_chain_t   *cl;
+    struct iovec  *iov;
+
+    cl = in;
+    iov = NULL;
+    prev = NULL;
+    total = 0;
+    n = 0;
+    flush = 0;
+
+    for ( /* void */ ; in && !flush; in = in->next) {
+
+        if (in->buf->flush || in->buf->last_buf) {
+            flush = 1;
+        }
+
+        if (ngx_buf_special(in->buf)) {
+            continue;
+        }
+
+        if (in->buf->in_file) {
+            break;
+        }
+
+        if (!ngx_buf_in_memory(in->buf)) {
+            ngx_log_error(NGX_LOG_ALERT, log, 0,
+                          "bad buf in output chain "
+                          "t:%d r:%d f:%d %p %p-%p %p %O-%O",
+                          in->buf->temporary,
+                          in->buf->recycled,
+                          in->buf->in_file,
+                          in->buf->start,
+                          in->buf->pos,
+                          in->buf->last,
+                          in->buf->file,
+                          in->buf->file_pos,
+                          in->buf->file_last);
+
+            ngx_debug_point();
+
+            return NGX_CHAIN_ERROR;
+        }
+
+        size = in->buf->last - in->buf->pos;
+
+        if (prev == in->buf->pos) {
+            iov->iov_len += size;
+
+        } else {
+            if (n == vec->nalloc) {
+                ngx_log_error(NGX_LOG_ALERT, log, 0,
+                              "too many parts in a datagram");
+                return NGX_CHAIN_ERROR;
+            }
+
+            iov = &vec->iovs[n++];
+
+            iov->iov_base = (void *) in->buf->pos;
+            iov->iov_len = size;
+        }
+
+        prev = in->buf->pos + size;
+        total += size;
+    }
+
+    if (!flush) {
+#if (NGX_SUPPRESS_WARN)
+        vec->size = 0;
+        vec->count = 0;
+#endif
+        return cl;
+    }
+
+    vec->count = n;
+    vec->size = total;
+
+    return in;
+}
+
+
+static ssize_t
+ngx_sendmsg(ngx_connection_t *c, ngx_iovec_t *vec)
+{
+    ssize_t        n;
+    ngx_err_t      err;
+    struct msghdr  msg;
+
+#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
+
+#if (NGX_HAVE_IP_SENDSRCADDR)
+    u_char         msg_control[CMSG_SPACE(sizeof(struct in_addr))];
+#elif (NGX_HAVE_IP_PKTINFO)
+    u_char         msg_control[CMSG_SPACE(sizeof(struct in_pktinfo))];
+#endif
+
+#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO)
+    u_char         msg_control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+#endif
+
+#endif
+
+    ngx_memzero(&msg, sizeof(struct msghdr));
+
+    if (c->socklen) {
+        msg.msg_name = c->sockaddr;
+        msg.msg_namelen = c->socklen;
+    }
+
+    msg.msg_iov = vec->iovs;
+    msg.msg_iovlen = vec->count;
+
+#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
+
+    if (c->listening && c->listening->wildcard && c->local_sockaddr) {
+
+#if (NGX_HAVE_IP_SENDSRCADDR)
+
+        if (c->local_sockaddr->sa_family == AF_INET) {
+            struct cmsghdr      *cmsg;
+            struct in_addr      *addr;
+            struct sockaddr_in  *sin;
+
+            msg.msg_control = &msg_control;
+            msg.msg_controllen = sizeof(msg_control);
+
+            cmsg = CMSG_FIRSTHDR(&msg);
+            cmsg->cmsg_level = IPPROTO_IP;
+            cmsg->cmsg_type = IP_SENDSRCADDR;
+            cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
+
+            sin = (struct sockaddr_in *) c->local_sockaddr;
+
+            addr = (struct in_addr *) CMSG_DATA(cmsg);
+            *addr = sin->sin_addr;
+        }
+
+#elif (NGX_HAVE_IP_PKTINFO)
+
+        if (c->local_sockaddr->sa_family == AF_INET) {
+            struct cmsghdr      *cmsg;
+            struct in_pktinfo   *pkt;
+            struct sockaddr_in  *sin;
+
+            msg.msg_control = &msg_control;
+            msg.msg_controllen = sizeof(msg_control);
+
+            cmsg = CMSG_FIRSTHDR(&msg);
+            cmsg->cmsg_level = IPPROTO_IP;
+            cmsg->cmsg_type = IP_PKTINFO;
+            cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
+
+            sin = (struct sockaddr_in *) c->local_sockaddr;
+
+            pkt = (struct in_pktinfo *) CMSG_DATA(cmsg);
+            ngx_memzero(pkt, sizeof(struct in_pktinfo));
+            pkt->ipi_spec_dst = sin->sin_addr;
+        }
+
+#endif
+
+#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO)
+
+        if (c->local_sockaddr->sa_family == AF_INET6) {
+            struct cmsghdr       *cmsg;
+            struct in6_pktinfo   *pkt6;
+            struct sockaddr_in6  *sin6;
+
+            msg.msg_control = &msg_control6;
+            msg.msg_controllen = sizeof(msg_control6);
+
+            cmsg = CMSG_FIRSTHDR(&msg);
+            cmsg->cmsg_level = IPPROTO_IPV6;
+            cmsg->cmsg_type = IPV6_PKTINFO;
+            cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+
+            sin6 = (struct sockaddr_in6 *) c->local_sockaddr;
+
+            pkt6 = (struct in6_pktinfo *) CMSG_DATA(cmsg);
+            ngx_memzero(pkt6, sizeof(struct in6_pktinfo));
+            pkt6->ipi6_addr = sin6->sin6_addr;
+        }
+
+#endif
+    }
+
+#endif
+
+eintr:
+
+    n = ngxvcl_sendmsg(c->fd, &msg, 0);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "sendmsg: %z of %uz", n, vec->size);
+
+    if (n == -1) {
+        err = ngx_errno;
+
+        switch (err) {
+        case NGX_EAGAIN:
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+                           "sendmsg() not ready");
+            return NGX_AGAIN;
+
+        case NGX_EINTR:
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+                           "sendmsg() was interrupted");
+            goto eintr;
+
+        default:
+            c->write->error = 1;
+            ngx_connection_error(c, err, "sendmsg() failed");
+            return NGX_ERROR;
+        }
+    }
+
+    return n;
+}
diff --git a/nginx/src/os/unix/ngx_user.c b/nginx/src/os/unix/ngx_user.c
new file mode 100644 (file)
index 0000000..b3d81d0
--- /dev/null
@@ -0,0 +1,76 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#if (NGX_CRYPT)
+
+#if (NGX_HAVE_GNU_CRYPT_R)
+
+ngx_int_t
+ngx_libc_crypt(ngx_pool_t *pool, u_char *key, u_char *salt, u_char **encrypted)
+{
+    char               *value;
+    size_t              len;
+    struct crypt_data   cd;
+
+    cd.initialized = 0;
+
+    value = crypt_r((char *) key, (char *) salt, &cd);
+
+    if (value) {
+        len = ngx_strlen(value) + 1;
+
+        *encrypted = ngx_pnalloc(pool, len);
+        if (*encrypted == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_memcpy(*encrypted, value, len);
+        return NGX_OK;
+    }
+
+    ngx_log_error(NGX_LOG_CRIT, pool->log, ngx_errno, "crypt_r() failed");
+
+    return NGX_ERROR;
+}
+
+#else
+
+ngx_int_t
+ngx_libc_crypt(ngx_pool_t *pool, u_char *key, u_char *salt, u_char **encrypted)
+{
+    char       *value;
+    size_t      len;
+    ngx_err_t   err;
+
+    value = crypt((char *) key, (char *) salt);
+
+    if (value) {
+        len = ngx_strlen(value) + 1;
+
+        *encrypted = ngx_pnalloc(pool, len);
+        if (*encrypted == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_memcpy(*encrypted, value, len);
+        return NGX_OK;
+    }
+
+    err = ngx_errno;
+
+    ngx_log_error(NGX_LOG_CRIT, pool->log, err, "crypt() failed");
+
+    return NGX_ERROR;
+}
+
+#endif
+
+#endif /* NGX_CRYPT */
diff --git a/nginx/src/os/unix/ngx_user.h b/nginx/src/os/unix/ngx_user.h
new file mode 100644 (file)
index 0000000..6e82204
--- /dev/null
@@ -0,0 +1,24 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_USER_H_INCLUDED_
+#define _NGX_USER_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef uid_t  ngx_uid_t;
+typedef gid_t  ngx_gid_t;
+
+
+ngx_int_t ngx_libc_crypt(ngx_pool_t *pool, u_char *key, u_char *salt,
+    u_char **encrypted);
+
+
+#endif /* _NGX_USER_H_INCLUDED_ */
diff --git a/nginx/src/os/unix/ngx_writev_chain.c b/nginx/src/os/unix/ngx_writev_chain.c
new file mode 100644 (file)
index 0000000..9edc838
--- /dev/null
@@ -0,0 +1,216 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+ngx_chain_t *
+ngx_writev_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
+{
+    ssize_t        n, sent;
+    off_t          send, prev_send;
+    ngx_chain_t   *cl;
+    ngx_event_t   *wev;
+    ngx_iovec_t    vec;
+    struct iovec   iovs[NGX_IOVS_PREALLOCATE];
+
+    wev = c->write;
+
+    if (!wev->ready) {
+        return in;
+    }
+
+#if (NGX_HAVE_KQUEUE)
+
+    if ((ngx_event_flags & NGX_USE_KQUEUE_EVENT) && wev->pending_eof) {
+        (void) ngx_connection_error(c, wev->kq_errno,
+                               "kevent() reported about an closed connection");
+        wev->error = 1;
+        return NGX_CHAIN_ERROR;
+    }
+
+#endif
+
+    /* the maximum limit size is the maximum size_t value - the page size */
+
+    if (limit == 0 || limit > (off_t) (NGX_MAX_SIZE_T_VALUE - ngx_pagesize)) {
+        limit = NGX_MAX_SIZE_T_VALUE - ngx_pagesize;
+    }
+
+    send = 0;
+
+    vec.iovs = iovs;
+    vec.nalloc = NGX_IOVS_PREALLOCATE;
+
+    for ( ;; ) {
+        prev_send = send;
+
+        /* create the iovec and coalesce the neighbouring bufs */
+
+        cl = ngx_output_chain_to_iovec(&vec, in, limit - send, c->log);
+
+        if (cl == NGX_CHAIN_ERROR) {
+            return NGX_CHAIN_ERROR;
+        }
+
+        if (cl && cl->buf->in_file) {
+            ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                          "file buf in writev "
+                          "t:%d r:%d f:%d %p %p-%p %p %O-%O",
+                          cl->buf->temporary,
+                          cl->buf->recycled,
+                          cl->buf->in_file,
+                          cl->buf->start,
+                          cl->buf->pos,
+                          cl->buf->last,
+                          cl->buf->file,
+                          cl->buf->file_pos,
+                          cl->buf->file_last);
+
+            ngx_debug_point();
+
+            return NGX_CHAIN_ERROR;
+        }
+
+        send += vec.size;
+
+        n = ngx_writev(c, &vec);
+
+        if (n == NGX_ERROR) {
+            return NGX_CHAIN_ERROR;
+        }
+
+        sent = (n == NGX_AGAIN) ? 0 : n;
+
+        c->sent += sent;
+
+        in = ngx_chain_update_sent(in, sent);
+
+        if (send - prev_send != sent) {
+            wev->ready = 0;
+            return in;
+        }
+
+        if (send >= limit || in == NULL) {
+            return in;
+        }
+    }
+}
+
+
+ngx_chain_t *
+ngx_output_chain_to_iovec(ngx_iovec_t *vec, ngx_chain_t *in, size_t limit,
+    ngx_log_t *log)
+{
+    size_t         total, size;
+    u_char        *prev;
+    ngx_uint_t     n;
+    struct iovec  *iov;
+
+    iov = NULL;
+    prev = NULL;
+    total = 0;
+    n = 0;
+
+    for ( /* void */ ; in && total < limit; in = in->next) {
+
+        if (ngx_buf_special(in->buf)) {
+            continue;
+        }
+
+        if (in->buf->in_file) {
+            break;
+        }
+
+        if (!ngx_buf_in_memory(in->buf)) {
+            ngx_log_error(NGX_LOG_ALERT, log, 0,
+                          "bad buf in output chain "
+                          "t:%d r:%d f:%d %p %p-%p %p %O-%O",
+                          in->buf->temporary,
+                          in->buf->recycled,
+                          in->buf->in_file,
+                          in->buf->start,
+                          in->buf->pos,
+                          in->buf->last,
+                          in->buf->file,
+                          in->buf->file_pos,
+                          in->buf->file_last);
+
+            ngx_debug_point();
+
+            return NGX_CHAIN_ERROR;
+        }
+
+        size = in->buf->last - in->buf->pos;
+
+        if (size > limit - total) {
+            size = limit - total;
+        }
+
+        if (prev == in->buf->pos) {
+            iov->iov_len += size;
+
+        } else {
+            if (n == vec->nalloc) {
+                break;
+            }
+
+            iov = &vec->iovs[n++];
+
+            iov->iov_base = (void *) in->buf->pos;
+            iov->iov_len = size;
+        }
+
+        prev = in->buf->pos + size;
+        total += size;
+    }
+
+    vec->count = n;
+    vec->size = total;
+
+    return in;
+}
+
+
+ssize_t
+ngx_writev(ngx_connection_t *c, ngx_iovec_t *vec)
+{
+    ssize_t    n;
+    ngx_err_t  err;
+
+eintr:
+
+    n = ngxvcl_writev(c->fd, vec->iovs, vec->count);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "writev: %z of %uz", n, vec->size);
+
+    if (n == -1) {
+        err = ngx_errno;
+
+        switch (err) {
+        case NGX_EAGAIN:
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+                           "writev() not ready");
+            return NGX_AGAIN;
+
+        case NGX_EINTR:
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+                           "writev() was interrupted");
+            goto eintr;
+
+        default:
+            c->write->error = 1;
+            ngx_connection_error(c, err, "writev() failed");
+            return NGX_ERROR;
+        }
+    }
+
+    return n;
+}
diff --git a/nginx/src/stream/ngx_stream.c b/nginx/src/stream/ngx_stream.c
new file mode 100644 (file)
index 0000000..0efbda8
--- /dev/null
@@ -0,0 +1,685 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_stream.h>
+
+
+static char *ngx_stream_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static ngx_int_t ngx_stream_init_phases(ngx_conf_t *cf,
+    ngx_stream_core_main_conf_t *cmcf);
+static ngx_int_t ngx_stream_init_phase_handlers(ngx_conf_t *cf,
+    ngx_stream_core_main_conf_t *cmcf);
+static ngx_int_t ngx_stream_add_ports(ngx_conf_t *cf, ngx_array_t *ports,
+    ngx_stream_listen_t *listen);
+static char *ngx_stream_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports);
+static ngx_int_t ngx_stream_add_addrs(ngx_conf_t *cf, ngx_stream_port_t *stport,
+    ngx_stream_conf_addr_t *addr);
+#if (NGX_HAVE_INET6)
+static ngx_int_t ngx_stream_add_addrs6(ngx_conf_t *cf,
+    ngx_stream_port_t *stport, ngx_stream_conf_addr_t *addr);
+#endif
+static ngx_int_t ngx_stream_cmp_conf_addrs(const void *one, const void *two);
+
+
+ngx_uint_t  ngx_stream_max_module;
+
+
+ngx_stream_filter_pt  ngx_stream_top_filter;
+
+
+static ngx_command_t  ngx_stream_commands[] = {
+
+    { ngx_string("stream"),
+      NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+      ngx_stream_block,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_core_module_t  ngx_stream_module_ctx = {
+    ngx_string("stream"),
+    NULL,
+    NULL
+};
+
+
+ngx_module_t  ngx_stream_module = {
+    NGX_MODULE_V1,
+    &ngx_stream_module_ctx,                /* module context */
+    ngx_stream_commands,                   /* module directives */
+    NGX_CORE_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 char *
+ngx_stream_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char                          *rv;
+    ngx_uint_t                     i, m, mi, s;
+    ngx_conf_t                     pcf;
+    ngx_array_t                    ports;
+    ngx_stream_listen_t           *listen;
+    ngx_stream_module_t           *module;
+    ngx_stream_conf_ctx_t         *ctx;
+    ngx_stream_core_srv_conf_t   **cscfp;
+    ngx_stream_core_main_conf_t   *cmcf;
+
+    if (*(ngx_stream_conf_ctx_t **) conf) {
+        return "is duplicate";
+    }
+
+    /* the main stream context */
+
+    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_stream_conf_ctx_t));
+    if (ctx == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    *(ngx_stream_conf_ctx_t **) conf = ctx;
+
+    /* count the number of the stream modules and set up their indices */
+
+    ngx_stream_max_module = ngx_count_modules(cf->cycle, NGX_STREAM_MODULE);
+
+
+    /* the stream main_conf context, it's the same in the all stream contexts */
+
+    ctx->main_conf = ngx_pcalloc(cf->pool,
+                                 sizeof(void *) * ngx_stream_max_module);
+    if (ctx->main_conf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+
+    /*
+     * the stream null srv_conf context, it is used to merge
+     * the server{}s' srv_conf's
+     */
+
+    ctx->srv_conf = ngx_pcalloc(cf->pool,
+                                sizeof(void *) * ngx_stream_max_module);
+    if (ctx->srv_conf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+
+    /*
+     * create the main_conf's and the null srv_conf's of the all stream modules
+     */
+
+    for (m = 0; cf->cycle->modules[m]; m++) {
+        if (cf->cycle->modules[m]->type != NGX_STREAM_MODULE) {
+            continue;
+        }
+
+        module = cf->cycle->modules[m]->ctx;
+        mi = cf->cycle->modules[m]->ctx_index;
+
+        if (module->create_main_conf) {
+            ctx->main_conf[mi] = module->create_main_conf(cf);
+            if (ctx->main_conf[mi] == NULL) {
+                return NGX_CONF_ERROR;
+            }
+        }
+
+        if (module->create_srv_conf) {
+            ctx->srv_conf[mi] = module->create_srv_conf(cf);
+            if (ctx->srv_conf[mi] == NULL) {
+                return NGX_CONF_ERROR;
+            }
+        }
+    }
+
+
+    pcf = *cf;
+    cf->ctx = ctx;
+
+    for (m = 0; cf->cycle->modules[m]; m++) {
+        if (cf->cycle->modules[m]->type != NGX_STREAM_MODULE) {
+            continue;
+        }
+
+        module = cf->cycle->modules[m]->ctx;
+
+        if (module->preconfiguration) {
+            if (module->preconfiguration(cf) != NGX_OK) {
+                return NGX_CONF_ERROR;
+            }
+        }
+    }
+
+
+    /* parse inside the stream{} block */
+
+    cf->module_type = NGX_STREAM_MODULE;
+    cf->cmd_type = NGX_STREAM_MAIN_CONF;
+    rv = ngx_conf_parse(cf, NULL);
+
+    if (rv != NGX_CONF_OK) {
+        *cf = pcf;
+        return rv;
+    }
+
+
+    /* init stream{} main_conf's, merge the server{}s' srv_conf's */
+
+    cmcf = ctx->main_conf[ngx_stream_core_module.ctx_index];
+    cscfp = cmcf->servers.elts;
+
+    for (m = 0; cf->cycle->modules[m]; m++) {
+        if (cf->cycle->modules[m]->type != NGX_STREAM_MODULE) {
+            continue;
+        }
+
+        module = cf->cycle->modules[m]->ctx;
+        mi = cf->cycle->modules[m]->ctx_index;
+
+        /* init stream{} main_conf's */
+
+        cf->ctx = ctx;
+
+        if (module->init_main_conf) {
+            rv = module->init_main_conf(cf, ctx->main_conf[mi]);
+            if (rv != NGX_CONF_OK) {
+                *cf = pcf;
+                return rv;
+            }
+        }
+
+        for (s = 0; s < cmcf->servers.nelts; s++) {
+
+            /* merge the server{}s' srv_conf's */
+
+            cf->ctx = cscfp[s]->ctx;
+
+            if (module->merge_srv_conf) {
+                rv = module->merge_srv_conf(cf,
+                                            ctx->srv_conf[mi],
+                                            cscfp[s]->ctx->srv_conf[mi]);
+                if (rv != NGX_CONF_OK) {
+                    *cf = pcf;
+                    return rv;
+                }
+            }
+        }
+    }
+
+    if (ngx_stream_init_phases(cf, cmcf) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    for (m = 0; cf->cycle->modules[m]; m++) {
+        if (cf->cycle->modules[m]->type != NGX_STREAM_MODULE) {
+            continue;
+        }
+
+        module = cf->cycle->modules[m]->ctx;
+
+        if (module->postconfiguration) {
+            if (module->postconfiguration(cf) != NGX_OK) {
+                return NGX_CONF_ERROR;
+            }
+        }
+    }
+
+    if (ngx_stream_variables_init_vars(cf) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    *cf = pcf;
+
+    if (ngx_stream_init_phase_handlers(cf, cmcf) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (ngx_array_init(&ports, cf->temp_pool, 4, sizeof(ngx_stream_conf_port_t))
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    listen = cmcf->listen.elts;
+
+    for (i = 0; i < cmcf->listen.nelts; i++) {
+        if (ngx_stream_add_ports(cf, &ports, &listen[i]) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    return ngx_stream_optimize_servers(cf, &ports);
+}
+
+
+static ngx_int_t
+ngx_stream_init_phases(ngx_conf_t *cf, ngx_stream_core_main_conf_t *cmcf)
+{
+    if (ngx_array_init(&cmcf->phases[NGX_STREAM_POST_ACCEPT_PHASE].handlers,
+                       cf->pool, 1, sizeof(ngx_stream_handler_pt))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&cmcf->phases[NGX_STREAM_PREACCESS_PHASE].handlers,
+                       cf->pool, 1, sizeof(ngx_stream_handler_pt))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&cmcf->phases[NGX_STREAM_ACCESS_PHASE].handlers,
+                       cf->pool, 1, sizeof(ngx_stream_handler_pt))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&cmcf->phases[NGX_STREAM_SSL_PHASE].handlers,
+                       cf->pool, 1, sizeof(ngx_stream_handler_pt))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&cmcf->phases[NGX_STREAM_PREREAD_PHASE].handlers,
+                       cf->pool, 1, sizeof(ngx_stream_handler_pt))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&cmcf->phases[NGX_STREAM_LOG_PHASE].handlers,
+                       cf->pool, 1, sizeof(ngx_stream_handler_pt))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_init_phase_handlers(ngx_conf_t *cf,
+    ngx_stream_core_main_conf_t *cmcf)
+{
+    ngx_int_t                     j;
+    ngx_uint_t                    i, n;
+    ngx_stream_handler_pt        *h;
+    ngx_stream_phase_handler_t   *ph;
+    ngx_stream_phase_handler_pt   checker;
+
+    n = 1 /* content phase */;
+
+    for (i = 0; i < NGX_STREAM_LOG_PHASE; i++) {
+        n += cmcf->phases[i].handlers.nelts;
+    }
+
+    ph = ngx_pcalloc(cf->pool,
+                     n * sizeof(ngx_stream_phase_handler_t) + sizeof(void *));
+    if (ph == NULL) {
+        return NGX_ERROR;
+    }
+
+    cmcf->phase_engine.handlers = ph;
+    n = 0;
+
+    for (i = 0; i < NGX_STREAM_LOG_PHASE; i++) {
+        h = cmcf->phases[i].handlers.elts;
+
+        switch (i) {
+
+        case NGX_STREAM_PREREAD_PHASE:
+            checker = ngx_stream_core_preread_phase;
+            break;
+
+        case NGX_STREAM_CONTENT_PHASE:
+            ph->checker = ngx_stream_core_content_phase;
+            n++;
+            ph++;
+
+            continue;
+
+        default:
+            checker = ngx_stream_core_generic_phase;
+        }
+
+        n += cmcf->phases[i].handlers.nelts;
+
+        for (j = cmcf->phases[i].handlers.nelts - 1; j >= 0; j--) {
+            ph->checker = checker;
+            ph->handler = h[j];
+            ph->next = n;
+            ph++;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_add_ports(ngx_conf_t *cf, ngx_array_t *ports,
+    ngx_stream_listen_t *listen)
+{
+    in_port_t                p;
+    ngx_uint_t               i;
+    struct sockaddr         *sa;
+    ngx_stream_conf_port_t  *port;
+    ngx_stream_conf_addr_t  *addr;
+
+    sa = &listen->sockaddr.sockaddr;
+    p = ngx_inet_get_port(sa);
+
+    port = ports->elts;
+    for (i = 0; i < ports->nelts; i++) {
+
+        if (p == port[i].port
+            && listen->type == port[i].type
+            && sa->sa_family == port[i].family)
+        {
+            /* a port is already in the port list */
+
+            port = &port[i];
+            goto found;
+        }
+    }
+
+    /* add a port to the port list */
+
+    port = ngx_array_push(ports);
+    if (port == NULL) {
+        return NGX_ERROR;
+    }
+
+    port->family = sa->sa_family;
+    port->type = listen->type;
+    port->port = p;
+
+    if (ngx_array_init(&port->addrs, cf->temp_pool, 2,
+                       sizeof(ngx_stream_conf_addr_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+found:
+
+    addr = ngx_array_push(&port->addrs);
+    if (addr == NULL) {
+        return NGX_ERROR;
+    }
+
+    addr->opt = *listen;
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_stream_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports)
+{
+    ngx_uint_t                   i, p, last, bind_wildcard;
+    ngx_listening_t             *ls;
+    ngx_stream_port_t           *stport;
+    ngx_stream_conf_port_t      *port;
+    ngx_stream_conf_addr_t      *addr;
+    ngx_stream_core_srv_conf_t  *cscf;
+
+    port = ports->elts;
+    for (p = 0; p < ports->nelts; p++) {
+
+        ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,
+                 sizeof(ngx_stream_conf_addr_t), ngx_stream_cmp_conf_addrs);
+
+        addr = port[p].addrs.elts;
+        last = port[p].addrs.nelts;
+
+        /*
+         * if there is the binding to the "*:port" then we need to bind()
+         * to the "*:port" only and ignore the other bindings
+         */
+
+        if (addr[last - 1].opt.wildcard) {
+            addr[last - 1].opt.bind = 1;
+            bind_wildcard = 1;
+
+        } else {
+            bind_wildcard = 0;
+        }
+
+        i = 0;
+
+        while (i < last) {
+
+            if (bind_wildcard && !addr[i].opt.bind) {
+                i++;
+                continue;
+            }
+
+            ls = ngx_create_listening(cf, &addr[i].opt.sockaddr.sockaddr,
+                                      addr[i].opt.socklen);
+            if (ls == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            ls->addr_ntop = 1;
+            ls->handler = ngx_stream_init_connection;
+            ls->pool_size = 256;
+            ls->type = addr[i].opt.type;
+
+            cscf = addr->opt.ctx->srv_conf[ngx_stream_core_module.ctx_index];
+
+            ls->logp = cscf->error_log;
+            ls->log.data = &ls->addr_text;
+            ls->log.handler = ngx_accept_log_error;
+
+            ls->backlog = addr[i].opt.backlog;
+            ls->rcvbuf = addr[i].opt.rcvbuf;
+            ls->sndbuf = addr[i].opt.sndbuf;
+
+            ls->wildcard = addr[i].opt.wildcard;
+
+            ls->keepalive = addr[i].opt.so_keepalive;
+#if (NGX_HAVE_KEEPALIVE_TUNABLE)
+            ls->keepidle = addr[i].opt.tcp_keepidle;
+            ls->keepintvl = addr[i].opt.tcp_keepintvl;
+            ls->keepcnt = addr[i].opt.tcp_keepcnt;
+#endif
+
+#if (NGX_HAVE_INET6)
+            ls->ipv6only = addr[i].opt.ipv6only;
+#endif
+
+#if (NGX_HAVE_REUSEPORT)
+            ls->reuseport = addr[i].opt.reuseport;
+#endif
+
+            stport = ngx_palloc(cf->pool, sizeof(ngx_stream_port_t));
+            if (stport == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            ls->servers = stport;
+
+            stport->naddrs = i + 1;
+
+            switch (ls->sockaddr->sa_family) {
+#if (NGX_HAVE_INET6)
+            case AF_INET6:
+                if (ngx_stream_add_addrs6(cf, stport, addr) != NGX_OK) {
+                    return NGX_CONF_ERROR;
+                }
+                break;
+#endif
+            default: /* AF_INET */
+                if (ngx_stream_add_addrs(cf, stport, addr) != NGX_OK) {
+                    return NGX_CONF_ERROR;
+                }
+                break;
+            }
+
+            if (ngx_clone_listening(cf, ls) != NGX_OK) {
+                return NGX_CONF_ERROR;
+            }
+
+            addr++;
+            last--;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_add_addrs(ngx_conf_t *cf, ngx_stream_port_t *stport,
+    ngx_stream_conf_addr_t *addr)
+{
+    u_char                *p;
+    size_t                 len;
+    ngx_uint_t             i;
+    struct sockaddr_in    *sin;
+    ngx_stream_in_addr_t  *addrs;
+    u_char                 buf[NGX_SOCKADDR_STRLEN];
+
+    stport->addrs = ngx_pcalloc(cf->pool,
+                                stport->naddrs * sizeof(ngx_stream_in_addr_t));
+    if (stport->addrs == NULL) {
+        return NGX_ERROR;
+    }
+
+    addrs = stport->addrs;
+
+    for (i = 0; i < stport->naddrs; i++) {
+
+        sin = &addr[i].opt.sockaddr.sockaddr_in;
+        addrs[i].addr = sin->sin_addr.s_addr;
+
+        addrs[i].conf.ctx = addr[i].opt.ctx;
+#if (NGX_STREAM_SSL)
+        addrs[i].conf.ssl = addr[i].opt.ssl;
+#endif
+        addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
+
+        len = ngx_sock_ntop(&addr[i].opt.sockaddr.sockaddr, addr[i].opt.socklen,
+                            buf, NGX_SOCKADDR_STRLEN, 1);
+
+        p = ngx_pnalloc(cf->pool, len);
+        if (p == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_memcpy(p, buf, len);
+
+        addrs[i].conf.addr_text.len = len;
+        addrs[i].conf.addr_text.data = p;
+    }
+
+    return NGX_OK;
+}
+
+
+#if (NGX_HAVE_INET6)
+
+static ngx_int_t
+ngx_stream_add_addrs6(ngx_conf_t *cf, ngx_stream_port_t *stport,
+    ngx_stream_conf_addr_t *addr)
+{
+    u_char                 *p;
+    size_t                  len;
+    ngx_uint_t              i;
+    struct sockaddr_in6    *sin6;
+    ngx_stream_in6_addr_t  *addrs6;
+    u_char                  buf[NGX_SOCKADDR_STRLEN];
+
+    stport->addrs = ngx_pcalloc(cf->pool,
+                                stport->naddrs * sizeof(ngx_stream_in6_addr_t));
+    if (stport->addrs == NULL) {
+        return NGX_ERROR;
+    }
+
+    addrs6 = stport->addrs;
+
+    for (i = 0; i < stport->naddrs; i++) {
+
+        sin6 = &addr[i].opt.sockaddr.sockaddr_in6;
+        addrs6[i].addr6 = sin6->sin6_addr;
+
+        addrs6[i].conf.ctx = addr[i].opt.ctx;
+#if (NGX_STREAM_SSL)
+        addrs6[i].conf.ssl = addr[i].opt.ssl;
+#endif
+        addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
+
+        len = ngx_sock_ntop(&addr[i].opt.sockaddr.sockaddr, addr[i].opt.socklen,
+                            buf, NGX_SOCKADDR_STRLEN, 1);
+
+        p = ngx_pnalloc(cf->pool, len);
+        if (p == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_memcpy(p, buf, len);
+
+        addrs6[i].conf.addr_text.len = len;
+        addrs6[i].conf.addr_text.data = p;
+    }
+
+    return NGX_OK;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_stream_cmp_conf_addrs(const void *one, const void *two)
+{
+    ngx_stream_conf_addr_t  *first, *second;
+
+    first = (ngx_stream_conf_addr_t *) one;
+    second = (ngx_stream_conf_addr_t *) two;
+
+    if (first->opt.wildcard) {
+        /* a wildcard must be the last resort, shift it to the end */
+        return 1;
+    }
+
+    if (second->opt.wildcard) {
+        /* a wildcard must be the last resort, shift it to the end */
+        return -1;
+    }
+
+    if (first->opt.bind && !second->opt.bind) {
+        /* shift explicit bind()ed addresses to the start */
+        return -1;
+    }
+
+    if (!first->opt.bind && second->opt.bind) {
+        /* shift explicit bind()ed addresses to the start */
+        return 1;
+    }
+
+    /* do not sort by default */
+
+    return 0;
+}
diff --git a/nginx/src/stream/ngx_stream.h b/nginx/src/stream/ngx_stream.h
new file mode 100644 (file)
index 0000000..09d2459
--- /dev/null
@@ -0,0 +1,306 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_STREAM_H_INCLUDED_
+#define _NGX_STREAM_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+#if (NGX_STREAM_SSL)
+#include <ngx_stream_ssl_module.h>
+#endif
+
+
+typedef struct ngx_stream_session_s  ngx_stream_session_t;
+
+
+#include <ngx_stream_variables.h>
+#include <ngx_stream_script.h>
+#include <ngx_stream_upstream.h>
+#include <ngx_stream_upstream_round_robin.h>
+
+
+#define NGX_STREAM_OK                        200
+#define NGX_STREAM_BAD_REQUEST               400
+#define NGX_STREAM_FORBIDDEN                 403
+#define NGX_STREAM_INTERNAL_SERVER_ERROR     500
+#define NGX_STREAM_BAD_GATEWAY               502
+#define NGX_STREAM_SERVICE_UNAVAILABLE       503
+
+
+typedef struct {
+    void                         **main_conf;
+    void                         **srv_conf;
+} ngx_stream_conf_ctx_t;
+
+
+typedef struct {
+    ngx_sockaddr_t                 sockaddr;
+    socklen_t                      socklen;
+
+    /* server ctx */
+    ngx_stream_conf_ctx_t         *ctx;
+
+    unsigned                       bind:1;
+    unsigned                       wildcard:1;
+    unsigned                       ssl:1;
+#if (NGX_HAVE_INET6)
+    unsigned                       ipv6only:1;
+#endif
+    unsigned                       reuseport:1;
+    unsigned                       so_keepalive:2;
+    unsigned                       proxy_protocol:1;
+#if (NGX_HAVE_KEEPALIVE_TUNABLE)
+    int                            tcp_keepidle;
+    int                            tcp_keepintvl;
+    int                            tcp_keepcnt;
+#endif
+    int                            backlog;
+    int                            rcvbuf;
+    int                            sndbuf;
+    int                            type;
+} ngx_stream_listen_t;
+
+
+typedef struct {
+    ngx_stream_conf_ctx_t         *ctx;
+    ngx_str_t                      addr_text;
+    unsigned                       ssl:1;
+    unsigned                       proxy_protocol:1;
+} ngx_stream_addr_conf_t;
+
+typedef struct {
+    in_addr_t                      addr;
+    ngx_stream_addr_conf_t         conf;
+} ngx_stream_in_addr_t;
+
+
+#if (NGX_HAVE_INET6)
+
+typedef struct {
+    struct in6_addr                addr6;
+    ngx_stream_addr_conf_t         conf;
+} ngx_stream_in6_addr_t;
+
+#endif
+
+
+typedef struct {
+    /* ngx_stream_in_addr_t or ngx_stream_in6_addr_t */
+    void                          *addrs;
+    ngx_uint_t                     naddrs;
+} ngx_stream_port_t;
+
+
+typedef struct {
+    int                            family;
+    int                            type;
+    in_port_t                      port;
+    ngx_array_t                    addrs; /* array of ngx_stream_conf_addr_t */
+} ngx_stream_conf_port_t;
+
+
+typedef struct {
+    ngx_stream_listen_t            opt;
+} ngx_stream_conf_addr_t;
+
+
+typedef enum {
+    NGX_STREAM_POST_ACCEPT_PHASE = 0,
+    NGX_STREAM_PREACCESS_PHASE,
+    NGX_STREAM_ACCESS_PHASE,
+    NGX_STREAM_SSL_PHASE,
+    NGX_STREAM_PREREAD_PHASE,
+    NGX_STREAM_CONTENT_PHASE,
+    NGX_STREAM_LOG_PHASE
+} ngx_stream_phases;
+
+
+typedef struct ngx_stream_phase_handler_s  ngx_stream_phase_handler_t;
+
+typedef ngx_int_t (*ngx_stream_phase_handler_pt)(ngx_stream_session_t *s,
+    ngx_stream_phase_handler_t *ph);
+typedef ngx_int_t (*ngx_stream_handler_pt)(ngx_stream_session_t *s);
+typedef void (*ngx_stream_content_handler_pt)(ngx_stream_session_t *s);
+
+
+struct ngx_stream_phase_handler_s {
+    ngx_stream_phase_handler_pt    checker;
+    ngx_stream_handler_pt          handler;
+    ngx_uint_t                     next;
+};
+
+
+typedef struct {
+    ngx_stream_phase_handler_t    *handlers;
+} ngx_stream_phase_engine_t;
+
+
+typedef struct {
+    ngx_array_t                    handlers;
+} ngx_stream_phase_t;
+
+
+typedef struct {
+    ngx_array_t                    servers;     /* ngx_stream_core_srv_conf_t */
+    ngx_array_t                    listen;      /* ngx_stream_listen_t */
+
+    ngx_stream_phase_engine_t      phase_engine;
+
+    ngx_hash_t                     variables_hash;
+
+    ngx_array_t                    variables;        /* ngx_stream_variable_t */
+    ngx_array_t                    prefix_variables; /* ngx_stream_variable_t */
+    ngx_uint_t                     ncaptures;
+
+    ngx_uint_t                     variables_hash_max_size;
+    ngx_uint_t                     variables_hash_bucket_size;
+
+    ngx_hash_keys_arrays_t        *variables_keys;
+
+    ngx_stream_phase_t             phases[NGX_STREAM_LOG_PHASE + 1];
+} ngx_stream_core_main_conf_t;
+
+
+typedef struct {
+    ngx_stream_content_handler_pt  handler;
+
+    ngx_stream_conf_ctx_t         *ctx;
+
+    u_char                        *file_name;
+    ngx_uint_t                     line;
+
+    ngx_flag_t                     tcp_nodelay;
+    size_t                         preread_buffer_size;
+    ngx_msec_t                     preread_timeout;
+
+    ngx_log_t                     *error_log;
+
+    ngx_msec_t                     resolver_timeout;
+    ngx_resolver_t                *resolver;
+
+    ngx_msec_t                     proxy_protocol_timeout;
+
+    ngx_uint_t                     listen;  /* unsigned  listen:1; */
+} ngx_stream_core_srv_conf_t;
+
+
+struct ngx_stream_session_s {
+    uint32_t                       signature;         /* "STRM" */
+
+    ngx_connection_t              *connection;
+
+    off_t                          received;
+    time_t                         start_sec;
+    ngx_msec_t                     start_msec;
+
+    ngx_log_handler_pt             log_handler;
+
+    void                         **ctx;
+    void                         **main_conf;
+    void                         **srv_conf;
+
+    ngx_stream_upstream_t         *upstream;
+    ngx_array_t                   *upstream_states;
+                                           /* of ngx_stream_upstream_state_t */
+    ngx_stream_variable_value_t   *variables;
+
+#if (NGX_PCRE)
+    ngx_uint_t                     ncaptures;
+    int                           *captures;
+    u_char                        *captures_data;
+#endif
+
+    ngx_int_t                      phase_handler;
+    ngx_uint_t                     status;
+
+    unsigned                       ssl:1;
+
+    unsigned                       stat_processing:1;
+
+    unsigned                       health_check:1;
+};
+
+
+typedef struct {
+    ngx_int_t                    (*preconfiguration)(ngx_conf_t *cf);
+    ngx_int_t                    (*postconfiguration)(ngx_conf_t *cf);
+
+    void                        *(*create_main_conf)(ngx_conf_t *cf);
+    char                        *(*init_main_conf)(ngx_conf_t *cf, void *conf);
+
+    void                        *(*create_srv_conf)(ngx_conf_t *cf);
+    char                        *(*merge_srv_conf)(ngx_conf_t *cf, void *prev,
+                                                   void *conf);
+} ngx_stream_module_t;
+
+
+#define NGX_STREAM_MODULE       0x4d525453     /* "STRM" */
+
+#define NGX_STREAM_MAIN_CONF    0x02000000
+#define NGX_STREAM_SRV_CONF     0x04000000
+#define NGX_STREAM_UPS_CONF     0x08000000
+
+
+#define NGX_STREAM_MAIN_CONF_OFFSET  offsetof(ngx_stream_conf_ctx_t, main_conf)
+#define NGX_STREAM_SRV_CONF_OFFSET   offsetof(ngx_stream_conf_ctx_t, srv_conf)
+
+
+#define ngx_stream_get_module_ctx(s, module)   (s)->ctx[module.ctx_index]
+#define ngx_stream_set_ctx(s, c, module)       s->ctx[module.ctx_index] = c;
+#define ngx_stream_delete_ctx(s, module)       s->ctx[module.ctx_index] = NULL;
+
+
+#define ngx_stream_get_module_main_conf(s, module)                             \
+    (s)->main_conf[module.ctx_index]
+#define ngx_stream_get_module_srv_conf(s, module)                              \
+    (s)->srv_conf[module.ctx_index]
+
+#define ngx_stream_conf_get_module_main_conf(cf, module)                       \
+    ((ngx_stream_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index]
+#define ngx_stream_conf_get_module_srv_conf(cf, module)                        \
+    ((ngx_stream_conf_ctx_t *) cf->ctx)->srv_conf[module.ctx_index]
+
+#define ngx_stream_cycle_get_module_main_conf(cycle, module)                   \
+    (cycle->conf_ctx[ngx_stream_module.index] ?                                \
+        ((ngx_stream_conf_ctx_t *) cycle->conf_ctx[ngx_stream_module.index])   \
+            ->main_conf[module.ctx_index]:                                     \
+        NULL)
+
+
+#define NGX_STREAM_WRITE_BUFFERED  0x10
+
+
+void ngx_stream_core_run_phases(ngx_stream_session_t *s);
+ngx_int_t ngx_stream_core_generic_phase(ngx_stream_session_t *s,
+    ngx_stream_phase_handler_t *ph);
+ngx_int_t ngx_stream_core_preread_phase(ngx_stream_session_t *s,
+    ngx_stream_phase_handler_t *ph);
+ngx_int_t ngx_stream_core_content_phase(ngx_stream_session_t *s,
+    ngx_stream_phase_handler_t *ph);
+
+
+void ngx_stream_init_connection(ngx_connection_t *c);
+void ngx_stream_session_handler(ngx_event_t *rev);
+void ngx_stream_finalize_session(ngx_stream_session_t *s, ngx_uint_t rc);
+
+
+extern ngx_module_t  ngx_stream_module;
+extern ngx_uint_t    ngx_stream_max_module;
+extern ngx_module_t  ngx_stream_core_module;
+
+
+typedef ngx_int_t (*ngx_stream_filter_pt)(ngx_stream_session_t *s,
+    ngx_chain_t *chain, ngx_uint_t from_upstream);
+
+
+extern ngx_stream_filter_pt  ngx_stream_top_filter;
+
+
+#endif /* _NGX_STREAM_H_INCLUDED_ */
diff --git a/nginx/src/stream/ngx_stream_access_module.c b/nginx/src/stream/ngx_stream_access_module.c
new file mode 100644 (file)
index 0000000..a3020d4
--- /dev/null
@@ -0,0 +1,453 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+typedef struct {
+    in_addr_t         mask;
+    in_addr_t         addr;
+    ngx_uint_t        deny;      /* unsigned  deny:1; */
+} ngx_stream_access_rule_t;
+
+#if (NGX_HAVE_INET6)
+
+typedef struct {
+    struct in6_addr   addr;
+    struct in6_addr   mask;
+    ngx_uint_t        deny;      /* unsigned  deny:1; */
+} ngx_stream_access_rule6_t;
+
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+
+typedef struct {
+    ngx_uint_t        deny;      /* unsigned  deny:1; */
+} ngx_stream_access_rule_un_t;
+
+#endif
+
+typedef struct {
+    ngx_array_t      *rules;     /* array of ngx_stream_access_rule_t */
+#if (NGX_HAVE_INET6)
+    ngx_array_t      *rules6;    /* array of ngx_stream_access_rule6_t */
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+    ngx_array_t      *rules_un;  /* array of ngx_stream_access_rule_un_t */
+#endif
+} ngx_stream_access_srv_conf_t;
+
+
+static ngx_int_t ngx_stream_access_handler(ngx_stream_session_t *s);
+static ngx_int_t ngx_stream_access_inet(ngx_stream_session_t *s,
+    ngx_stream_access_srv_conf_t *ascf, in_addr_t addr);
+#if (NGX_HAVE_INET6)
+static ngx_int_t ngx_stream_access_inet6(ngx_stream_session_t *s,
+    ngx_stream_access_srv_conf_t *ascf, u_char *p);
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+static ngx_int_t ngx_stream_access_unix(ngx_stream_session_t *s,
+    ngx_stream_access_srv_conf_t *ascf);
+#endif
+static ngx_int_t ngx_stream_access_found(ngx_stream_session_t *s,
+    ngx_uint_t deny);
+static char *ngx_stream_access_rule(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static void *ngx_stream_access_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_stream_access_merge_srv_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+static ngx_int_t ngx_stream_access_init(ngx_conf_t *cf);
+
+
+static ngx_command_t  ngx_stream_access_commands[] = {
+
+    { ngx_string("allow"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_stream_access_rule,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("deny"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_stream_access_rule,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+
+static ngx_stream_module_t  ngx_stream_access_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_stream_access_init,                /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_stream_access_create_srv_conf,     /* create server configuration */
+    ngx_stream_access_merge_srv_conf       /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_stream_access_module = {
+    NGX_MODULE_V1,
+    &ngx_stream_access_module_ctx,         /* module context */
+    ngx_stream_access_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_access_handler(ngx_stream_session_t *s)
+{
+    struct sockaddr_in            *sin;
+    ngx_stream_access_srv_conf_t  *ascf;
+#if (NGX_HAVE_INET6)
+    u_char                        *p;
+    in_addr_t                      addr;
+    struct sockaddr_in6           *sin6;
+#endif
+
+    ascf = ngx_stream_get_module_srv_conf(s, ngx_stream_access_module);
+
+    switch (s->connection->sockaddr->sa_family) {
+
+    case AF_INET:
+        if (ascf->rules) {
+            sin = (struct sockaddr_in *) s->connection->sockaddr;
+            return ngx_stream_access_inet(s, ascf, sin->sin_addr.s_addr);
+        }
+        break;
+
+#if (NGX_HAVE_INET6)
+
+    case AF_INET6:
+        sin6 = (struct sockaddr_in6 *) s->connection->sockaddr;
+        p = sin6->sin6_addr.s6_addr;
+
+        if (ascf->rules && IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+            addr = p[12] << 24;
+            addr += p[13] << 16;
+            addr += p[14] << 8;
+            addr += p[15];
+            return ngx_stream_access_inet(s, ascf, htonl(addr));
+        }
+
+        if (ascf->rules6) {
+            return ngx_stream_access_inet6(s, ascf, p);
+        }
+
+        break;
+
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+
+    case AF_UNIX:
+        if (ascf->rules_un) {
+            return ngx_stream_access_unix(s, ascf);
+        }
+
+        break;
+
+#endif
+    }
+
+    return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_stream_access_inet(ngx_stream_session_t *s,
+    ngx_stream_access_srv_conf_t *ascf, in_addr_t addr)
+{
+    ngx_uint_t                 i;
+    ngx_stream_access_rule_t  *rule;
+
+    rule = ascf->rules->elts;
+    for (i = 0; i < ascf->rules->nelts; i++) {
+
+        ngx_log_debug3(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                       "access: %08XD %08XD %08XD",
+                       addr, rule[i].mask, rule[i].addr);
+
+        if ((addr & rule[i].mask) == rule[i].addr) {
+            return ngx_stream_access_found(s, rule[i].deny);
+        }
+    }
+
+    return NGX_DECLINED;
+}
+
+
+#if (NGX_HAVE_INET6)
+
+static ngx_int_t
+ngx_stream_access_inet6(ngx_stream_session_t *s,
+    ngx_stream_access_srv_conf_t *ascf, u_char *p)
+{
+    ngx_uint_t                  n;
+    ngx_uint_t                  i;
+    ngx_stream_access_rule6_t  *rule6;
+
+    rule6 = ascf->rules6->elts;
+    for (i = 0; i < ascf->rules6->nelts; i++) {
+
+#if (NGX_DEBUG)
+        {
+        size_t  cl, ml, al;
+        u_char  ct[NGX_INET6_ADDRSTRLEN];
+        u_char  mt[NGX_INET6_ADDRSTRLEN];
+        u_char  at[NGX_INET6_ADDRSTRLEN];
+
+        cl = ngx_inet6_ntop(p, ct, NGX_INET6_ADDRSTRLEN);
+        ml = ngx_inet6_ntop(rule6[i].mask.s6_addr, mt, NGX_INET6_ADDRSTRLEN);
+        al = ngx_inet6_ntop(rule6[i].addr.s6_addr, at, NGX_INET6_ADDRSTRLEN);
+
+        ngx_log_debug6(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                       "access: %*s %*s %*s", cl, ct, ml, mt, al, at);
+        }
+#endif
+
+        for (n = 0; n < 16; n++) {
+            if ((p[n] & rule6[i].mask.s6_addr[n]) != rule6[i].addr.s6_addr[n]) {
+                goto next;
+            }
+        }
+
+        return ngx_stream_access_found(s, rule6[i].deny);
+
+    next:
+        continue;
+    }
+
+    return NGX_DECLINED;
+}
+
+#endif
+
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+
+static ngx_int_t
+ngx_stream_access_unix(ngx_stream_session_t *s,
+    ngx_stream_access_srv_conf_t *ascf)
+{
+    ngx_uint_t                    i;
+    ngx_stream_access_rule_un_t  *rule_un;
+
+    rule_un = ascf->rules_un->elts;
+    for (i = 0; i < ascf->rules_un->nelts; i++) {
+
+        /* TODO: check path */
+        if (1) {
+            return ngx_stream_access_found(s, rule_un[i].deny);
+        }
+    }
+
+    return NGX_DECLINED;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_stream_access_found(ngx_stream_session_t *s, ngx_uint_t deny)
+{
+    if (deny) {
+        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                      "access forbidden by rule");
+        return NGX_STREAM_FORBIDDEN;
+    }
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_stream_access_rule(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_access_srv_conf_t *ascf = conf;
+
+    ngx_int_t                     rc;
+    ngx_uint_t                    all;
+    ngx_str_t                    *value;
+    ngx_cidr_t                    cidr;
+    ngx_stream_access_rule_t     *rule;
+#if (NGX_HAVE_INET6)
+    ngx_stream_access_rule6_t    *rule6;
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+    ngx_stream_access_rule_un_t  *rule_un;
+#endif
+
+    all = 0;
+    ngx_memzero(&cidr, sizeof(ngx_cidr_t));
+
+    value = cf->args->elts;
+
+    if (value[1].len == 3 && ngx_strcmp(value[1].data, "all") == 0) {
+        all = 1;
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+    } else if (value[1].len == 5 && ngx_strcmp(value[1].data, "unix:") == 0) {
+        cidr.family = AF_UNIX;
+#endif
+
+    } else {
+        rc = ngx_ptocidr(&value[1], &cidr);
+
+        if (rc == NGX_ERROR) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                         "invalid parameter \"%V\"", &value[1]);
+            return NGX_CONF_ERROR;
+        }
+
+        if (rc == NGX_DONE) {
+            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                         "low address bits of %V are meaningless", &value[1]);
+        }
+    }
+
+    if (cidr.family == AF_INET || all) {
+
+        if (ascf->rules == NULL) {
+            ascf->rules = ngx_array_create(cf->pool, 4,
+                                           sizeof(ngx_stream_access_rule_t));
+            if (ascf->rules == NULL) {
+                return NGX_CONF_ERROR;
+            }
+        }
+
+        rule = ngx_array_push(ascf->rules);
+        if (rule == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        rule->mask = cidr.u.in.mask;
+        rule->addr = cidr.u.in.addr;
+        rule->deny = (value[0].data[0] == 'd') ? 1 : 0;
+    }
+
+#if (NGX_HAVE_INET6)
+    if (cidr.family == AF_INET6 || all) {
+
+        if (ascf->rules6 == NULL) {
+            ascf->rules6 = ngx_array_create(cf->pool, 4,
+                                            sizeof(ngx_stream_access_rule6_t));
+            if (ascf->rules6 == NULL) {
+                return NGX_CONF_ERROR;
+            }
+        }
+
+        rule6 = ngx_array_push(ascf->rules6);
+        if (rule6 == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        rule6->mask = cidr.u.in6.mask;
+        rule6->addr = cidr.u.in6.addr;
+        rule6->deny = (value[0].data[0] == 'd') ? 1 : 0;
+    }
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+    if (cidr.family == AF_UNIX || all) {
+
+        if (ascf->rules_un == NULL) {
+            ascf->rules_un = ngx_array_create(cf->pool, 1,
+                                          sizeof(ngx_stream_access_rule_un_t));
+            if (ascf->rules_un == NULL) {
+                return NGX_CONF_ERROR;
+            }
+        }
+
+        rule_un = ngx_array_push(ascf->rules_un);
+        if (rule_un == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        rule_un->deny = (value[0].data[0] == 'd') ? 1 : 0;
+    }
+#endif
+
+    return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_stream_access_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_stream_access_srv_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_access_srv_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    return conf;
+}
+
+
+static char *
+ngx_stream_access_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_stream_access_srv_conf_t  *prev = parent;
+    ngx_stream_access_srv_conf_t  *conf = child;
+
+    if (conf->rules == NULL
+#if (NGX_HAVE_INET6)
+        && conf->rules6 == NULL
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+        && conf->rules_un == NULL
+#endif
+    ) {
+        conf->rules = prev->rules;
+#if (NGX_HAVE_INET6)
+        conf->rules6 = prev->rules6;
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+        conf->rules_un = prev->rules_un;
+#endif
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_access_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_ACCESS_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_stream_access_handler;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/stream/ngx_stream_core_module.c b/nginx/src/stream/ngx_stream_core_module.c
new file mode 100644 (file)
index 0000000..272708d
--- /dev/null
@@ -0,0 +1,909 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+static ngx_int_t ngx_stream_core_preconfiguration(ngx_conf_t *cf);
+static void *ngx_stream_core_create_main_conf(ngx_conf_t *cf);
+static char *ngx_stream_core_init_main_conf(ngx_conf_t *cf, void *conf);
+static void *ngx_stream_core_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_stream_core_merge_srv_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static char *ngx_stream_core_error_log(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_stream_core_server(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_stream_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+
+static ngx_command_t  ngx_stream_core_commands[] = {
+
+    { ngx_string("variables_hash_max_size"),
+      NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_STREAM_MAIN_CONF_OFFSET,
+      offsetof(ngx_stream_core_main_conf_t, variables_hash_max_size),
+      NULL },
+
+    { ngx_string("variables_hash_bucket_size"),
+      NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_STREAM_MAIN_CONF_OFFSET,
+      offsetof(ngx_stream_core_main_conf_t, variables_hash_bucket_size),
+      NULL },
+
+    { ngx_string("server"),
+      NGX_STREAM_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+      ngx_stream_core_server,
+      0,
+      0,
+      NULL },
+
+    { ngx_string("listen"),
+      NGX_STREAM_SRV_CONF|NGX_CONF_1MORE,
+      ngx_stream_core_listen,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("error_log"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE,
+      ngx_stream_core_error_log,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("resolver"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE,
+      ngx_stream_core_resolver,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("resolver_timeout"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_core_srv_conf_t, resolver_timeout),
+      NULL },
+
+    { ngx_string("proxy_protocol_timeout"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_core_srv_conf_t, proxy_protocol_timeout),
+      NULL },
+
+    { ngx_string("tcp_nodelay"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_core_srv_conf_t, tcp_nodelay),
+      NULL },
+
+    { ngx_string("preread_buffer_size"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_core_srv_conf_t, preread_buffer_size),
+      NULL },
+
+    { ngx_string("preread_timeout"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_core_srv_conf_t, preread_timeout),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_stream_module_t  ngx_stream_core_module_ctx = {
+    ngx_stream_core_preconfiguration,      /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    ngx_stream_core_create_main_conf,      /* create main configuration */
+    ngx_stream_core_init_main_conf,        /* init main configuration */
+
+    ngx_stream_core_create_srv_conf,       /* create server configuration */
+    ngx_stream_core_merge_srv_conf         /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_stream_core_module = {
+    NGX_MODULE_V1,
+    &ngx_stream_core_module_ctx,           /* module context */
+    ngx_stream_core_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
+};
+
+
+void
+ngx_stream_core_run_phases(ngx_stream_session_t *s)
+{
+    ngx_int_t                     rc;
+    ngx_stream_phase_handler_t   *ph;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module);
+
+    ph = cmcf->phase_engine.handlers;
+
+    while (ph[s->phase_handler].checker) {
+
+        rc = ph[s->phase_handler].checker(s, &ph[s->phase_handler]);
+
+        if (rc == NGX_OK) {
+            return;
+        }
+    }
+}
+
+
+ngx_int_t
+ngx_stream_core_generic_phase(ngx_stream_session_t *s,
+    ngx_stream_phase_handler_t *ph)
+{
+    ngx_int_t  rc;
+
+    /*
+     * generic phase checker,
+     * used by all phases, except for preread and content
+     */
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                   "generic phase: %ui", s->phase_handler);
+
+    rc = ph->handler(s);
+
+    if (rc == NGX_OK) {
+        s->phase_handler = ph->next;
+        return NGX_AGAIN;
+    }
+
+    if (rc == NGX_DECLINED) {
+        s->phase_handler++;
+        return NGX_AGAIN;
+    }
+
+    if (rc == NGX_AGAIN || rc == NGX_DONE) {
+        return NGX_OK;
+    }
+
+    if (rc == NGX_ERROR) {
+        rc = NGX_STREAM_INTERNAL_SERVER_ERROR;
+    }
+
+    ngx_stream_finalize_session(s, rc);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_stream_core_preread_phase(ngx_stream_session_t *s,
+    ngx_stream_phase_handler_t *ph)
+{
+    size_t                       size;
+    ssize_t                      n;
+    ngx_int_t                    rc;
+    ngx_connection_t            *c;
+    ngx_stream_core_srv_conf_t  *cscf;
+
+    c = s->connection;
+
+    c->log->action = "prereading client data";
+
+    cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module);
+
+    if (c->read->timedout) {
+        rc = NGX_STREAM_OK;
+
+    } else if (c->read->timer_set) {
+        rc = NGX_AGAIN;
+
+    } else {
+        rc = ph->handler(s);
+    }
+
+    while (rc == NGX_AGAIN) {
+
+        if (c->buffer == NULL) {
+            c->buffer = ngx_create_temp_buf(c->pool, cscf->preread_buffer_size);
+            if (c->buffer == NULL) {
+                rc = NGX_ERROR;
+                break;
+            }
+        }
+
+        size = c->buffer->end - c->buffer->last;
+
+        if (size == 0) {
+            ngx_log_error(NGX_LOG_ERR, c->log, 0, "preread buffer full");
+            rc = NGX_STREAM_BAD_REQUEST;
+            break;
+        }
+
+        if (c->read->eof) {
+            rc = NGX_STREAM_OK;
+            break;
+        }
+
+        if (!c->read->ready) {
+            if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+                rc = NGX_ERROR;
+                break;
+            }
+
+            if (!c->read->timer_set) {
+                ngx_add_timer(c->read, cscf->preread_timeout);
+            }
+
+            c->read->handler = ngx_stream_session_handler;
+
+            return NGX_OK;
+        }
+
+        n = c->recv(c, c->buffer->last, size);
+
+        if (n == NGX_ERROR) {
+            rc = NGX_STREAM_OK;
+            break;
+        }
+
+        if (n > 0) {
+            c->buffer->last += n;
+        }
+
+        rc = ph->handler(s);
+    }
+
+    if (c->read->timer_set) {
+        ngx_del_timer(c->read);
+    }
+
+    if (rc == NGX_OK) {
+        s->phase_handler = ph->next;
+        return NGX_AGAIN;
+    }
+
+    if (rc == NGX_DECLINED) {
+        s->phase_handler++;
+        return NGX_AGAIN;
+    }
+
+    if (rc == NGX_DONE) {
+        return NGX_OK;
+    }
+
+    if (rc == NGX_ERROR) {
+        rc = NGX_STREAM_INTERNAL_SERVER_ERROR;
+    }
+
+    ngx_stream_finalize_session(s, rc);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_stream_core_content_phase(ngx_stream_session_t *s,
+    ngx_stream_phase_handler_t *ph)
+{
+    ngx_connection_t            *c;
+    ngx_stream_core_srv_conf_t  *cscf;
+
+    c = s->connection;
+
+    c->log->action = NULL;
+
+    cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module);
+
+    if (c->type == SOCK_STREAM
+        && cscf->tcp_nodelay
+        && ngx_tcp_nodelay(c) != NGX_OK)
+    {
+        ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return NGX_OK;
+    }
+
+    cscf->handler(s);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_core_preconfiguration(ngx_conf_t *cf)
+{
+    return ngx_stream_variables_add_core_vars(cf);
+}
+
+
+static void *
+ngx_stream_core_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_core_main_conf_t));
+    if (cmcf == NULL) {
+        return NULL;
+    }
+
+    if (ngx_array_init(&cmcf->servers, cf->pool, 4,
+                       sizeof(ngx_stream_core_srv_conf_t *))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    if (ngx_array_init(&cmcf->listen, cf->pool, 4, sizeof(ngx_stream_listen_t))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    cmcf->variables_hash_max_size = NGX_CONF_UNSET_UINT;
+    cmcf->variables_hash_bucket_size = NGX_CONF_UNSET_UINT;
+
+    return cmcf;
+}
+
+
+static char *
+ngx_stream_core_init_main_conf(ngx_conf_t *cf, void *conf)
+{
+    ngx_stream_core_main_conf_t *cmcf = conf;
+
+    ngx_conf_init_uint_value(cmcf->variables_hash_max_size, 1024);
+    ngx_conf_init_uint_value(cmcf->variables_hash_bucket_size, 64);
+
+    cmcf->variables_hash_bucket_size =
+               ngx_align(cmcf->variables_hash_bucket_size, ngx_cacheline_size);
+
+    if (cmcf->ncaptures) {
+        cmcf->ncaptures = (cmcf->ncaptures + 1) * 3;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_stream_core_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_stream_core_srv_conf_t  *cscf;
+
+    cscf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_core_srv_conf_t));
+    if (cscf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     cscf->handler = NULL;
+     *     cscf->error_log = NULL;
+     */
+
+    cscf->file_name = cf->conf_file->file.name.data;
+    cscf->line = cf->conf_file->line;
+    cscf->resolver_timeout = NGX_CONF_UNSET_MSEC;
+    cscf->proxy_protocol_timeout = NGX_CONF_UNSET_MSEC;
+    cscf->tcp_nodelay = NGX_CONF_UNSET;
+    cscf->preread_buffer_size = NGX_CONF_UNSET_SIZE;
+    cscf->preread_timeout = NGX_CONF_UNSET_MSEC;
+
+    return cscf;
+}
+
+
+static char *
+ngx_stream_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_stream_core_srv_conf_t *prev = parent;
+    ngx_stream_core_srv_conf_t *conf = child;
+
+    ngx_conf_merge_msec_value(conf->resolver_timeout,
+                              prev->resolver_timeout, 30000);
+
+    if (conf->resolver == NULL) {
+
+        if (prev->resolver == NULL) {
+
+            /*
+             * create dummy resolver in stream {} context
+             * to inherit it in all servers
+             */
+
+            prev->resolver = ngx_resolver_create(cf, NULL, 0);
+            if (prev->resolver == NULL) {
+                return NGX_CONF_ERROR;
+            }
+        }
+
+        conf->resolver = prev->resolver;
+    }
+
+    if (conf->handler == NULL) {
+        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                      "no handler for server in %s:%ui",
+                      conf->file_name, conf->line);
+        return NGX_CONF_ERROR;
+    }
+
+    if (conf->error_log == NULL) {
+        if (prev->error_log) {
+            conf->error_log = prev->error_log;
+        } else {
+            conf->error_log = &cf->cycle->new_log;
+        }
+    }
+
+    ngx_conf_merge_msec_value(conf->proxy_protocol_timeout,
+                              prev->proxy_protocol_timeout, 30000);
+
+    ngx_conf_merge_value(conf->tcp_nodelay, prev->tcp_nodelay, 1);
+
+    ngx_conf_merge_size_value(conf->preread_buffer_size,
+                              prev->preread_buffer_size, 16384);
+
+    ngx_conf_merge_msec_value(conf->preread_timeout,
+                              prev->preread_timeout, 30000);
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_stream_core_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_core_srv_conf_t  *cscf = conf;
+
+    return ngx_log_set_log(cf, &cscf->error_log);
+}
+
+
+static char *
+ngx_stream_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char                         *rv;
+    void                         *mconf;
+    ngx_uint_t                    m;
+    ngx_conf_t                    pcf;
+    ngx_stream_module_t          *module;
+    ngx_stream_conf_ctx_t        *ctx, *stream_ctx;
+    ngx_stream_core_srv_conf_t   *cscf, **cscfp;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_stream_conf_ctx_t));
+    if (ctx == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    stream_ctx = cf->ctx;
+    ctx->main_conf = stream_ctx->main_conf;
+
+    /* the server{}'s srv_conf */
+
+    ctx->srv_conf = ngx_pcalloc(cf->pool,
+                                sizeof(void *) * ngx_stream_max_module);
+    if (ctx->srv_conf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    for (m = 0; cf->cycle->modules[m]; m++) {
+        if (cf->cycle->modules[m]->type != NGX_STREAM_MODULE) {
+            continue;
+        }
+
+        module = cf->cycle->modules[m]->ctx;
+
+        if (module->create_srv_conf) {
+            mconf = module->create_srv_conf(cf);
+            if (mconf == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            ctx->srv_conf[cf->cycle->modules[m]->ctx_index] = mconf;
+        }
+    }
+
+    /* the server configuration context */
+
+    cscf = ctx->srv_conf[ngx_stream_core_module.ctx_index];
+    cscf->ctx = ctx;
+
+    cmcf = ctx->main_conf[ngx_stream_core_module.ctx_index];
+
+    cscfp = ngx_array_push(&cmcf->servers);
+    if (cscfp == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    *cscfp = cscf;
+
+
+    /* parse inside server{} */
+
+    pcf = *cf;
+    cf->ctx = ctx;
+    cf->cmd_type = NGX_STREAM_SRV_CONF;
+
+    rv = ngx_conf_parse(cf, NULL);
+
+    *cf = pcf;
+
+    if (rv == NGX_CONF_OK && !cscf->listen) {
+        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                      "no \"listen\" is defined for server in %s:%ui",
+                      cscf->file_name, cscf->line);
+        return NGX_CONF_ERROR;
+    }
+
+    return rv;
+}
+
+
+static char *
+ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_core_srv_conf_t  *cscf = conf;
+
+    ngx_str_t                    *value, size;
+    ngx_url_t                     u;
+    ngx_uint_t                    i, backlog;
+    ngx_stream_listen_t          *ls, *als;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    cscf->listen = 1;
+
+    value = cf->args->elts;
+
+    ngx_memzero(&u, sizeof(ngx_url_t));
+
+    u.url = value[1];
+    u.listen = 1;
+
+    if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
+        if (u.err) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "%s in \"%V\" of the \"listen\" directive",
+                               u.err, &u.url);
+        }
+
+        return NGX_CONF_ERROR;
+    }
+
+    cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module);
+
+    ls = ngx_array_push(&cmcf->listen);
+    if (ls == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memzero(ls, sizeof(ngx_stream_listen_t));
+
+    ngx_memcpy(&ls->sockaddr.sockaddr, &u.sockaddr, u.socklen);
+
+    ls->socklen = u.socklen;
+    ls->backlog = NGX_LISTEN_BACKLOG;
+    ls->rcvbuf = -1;
+    ls->sndbuf = -1;
+    ls->type = SOCK_STREAM;
+    ls->wildcard = u.wildcard;
+    ls->ctx = cf->ctx;
+
+#if (NGX_HAVE_INET6)
+    ls->ipv6only = 1;
+#endif
+
+    backlog = 0;
+
+    for (i = 2; i < cf->args->nelts; i++) {
+
+#if !(NGX_WIN32)
+        if (ngx_strcmp(value[i].data, "udp") == 0) {
+            ls->type = SOCK_DGRAM;
+            continue;
+        }
+#endif
+
+        if (ngx_strcmp(value[i].data, "bind") == 0) {
+            ls->bind = 1;
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "backlog=", 8) == 0) {
+            ls->backlog = ngx_atoi(value[i].data + 8, value[i].len - 8);
+            ls->bind = 1;
+
+            if (ls->backlog == NGX_ERROR || ls->backlog == 0) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid backlog \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            backlog = 1;
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "rcvbuf=", 7) == 0) {
+            size.len = value[i].len - 7;
+            size.data = value[i].data + 7;
+
+            ls->rcvbuf = ngx_parse_size(&size);
+            ls->bind = 1;
+
+            if (ls->rcvbuf == NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid rcvbuf \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "sndbuf=", 7) == 0) {
+            size.len = value[i].len - 7;
+            size.data = value[i].data + 7;
+
+            ls->sndbuf = ngx_parse_size(&size);
+            ls->bind = 1;
+
+            if (ls->sndbuf == NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid sndbuf \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "ipv6only=o", 10) == 0) {
+#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
+            size_t  len;
+            u_char  buf[NGX_SOCKADDR_STRLEN];
+
+            if (ls->sockaddr.sockaddr.sa_family == AF_INET6) {
+
+                if (ngx_strcmp(&value[i].data[10], "n") == 0) {
+                    ls->ipv6only = 1;
+
+                } else if (ngx_strcmp(&value[i].data[10], "ff") == 0) {
+                    ls->ipv6only = 0;
+
+                } else {
+                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                       "invalid ipv6only flags \"%s\"",
+                                       &value[i].data[9]);
+                    return NGX_CONF_ERROR;
+                }
+
+                ls->bind = 1;
+
+            } else {
+                len = ngx_sock_ntop(&ls->sockaddr.sockaddr, ls->socklen, buf,
+                                    NGX_SOCKADDR_STRLEN, 1);
+
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "ipv6only is not supported "
+                                   "on addr \"%*s\", ignored", len, buf);
+            }
+
+            continue;
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "bind ipv6only is not supported "
+                               "on this platform");
+            return NGX_CONF_ERROR;
+#endif
+        }
+
+        if (ngx_strcmp(value[i].data, "reuseport") == 0) {
+#if (NGX_HAVE_REUSEPORT)
+            ls->reuseport = 1;
+            ls->bind = 1;
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "reuseport is not supported "
+                               "on this platform, ignored");
+#endif
+            continue;
+        }
+
+        if (ngx_strcmp(value[i].data, "ssl") == 0) {
+#if (NGX_STREAM_SSL)
+            ls->ssl = 1;
+            continue;
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "the \"ssl\" parameter requires "
+                               "ngx_stream_ssl_module");
+            return NGX_CONF_ERROR;
+#endif
+        }
+
+        if (ngx_strncmp(value[i].data, "so_keepalive=", 13) == 0) {
+
+            if (ngx_strcmp(&value[i].data[13], "on") == 0) {
+                ls->so_keepalive = 1;
+
+            } else if (ngx_strcmp(&value[i].data[13], "off") == 0) {
+                ls->so_keepalive = 2;
+
+            } else {
+
+#if (NGX_HAVE_KEEPALIVE_TUNABLE)
+                u_char     *p, *end;
+                ngx_str_t   s;
+
+                end = value[i].data + value[i].len;
+                s.data = value[i].data + 13;
+
+                p = ngx_strlchr(s.data, end, ':');
+                if (p == NULL) {
+                    p = end;
+                }
+
+                if (p > s.data) {
+                    s.len = p - s.data;
+
+                    ls->tcp_keepidle = ngx_parse_time(&s, 1);
+                    if (ls->tcp_keepidle == (time_t) NGX_ERROR) {
+                        goto invalid_so_keepalive;
+                    }
+                }
+
+                s.data = (p < end) ? (p + 1) : end;
+
+                p = ngx_strlchr(s.data, end, ':');
+                if (p == NULL) {
+                    p = end;
+                }
+
+                if (p > s.data) {
+                    s.len = p - s.data;
+
+                    ls->tcp_keepintvl = ngx_parse_time(&s, 1);
+                    if (ls->tcp_keepintvl == (time_t) NGX_ERROR) {
+                        goto invalid_so_keepalive;
+                    }
+                }
+
+                s.data = (p < end) ? (p + 1) : end;
+
+                if (s.data < end) {
+                    s.len = end - s.data;
+
+                    ls->tcp_keepcnt = ngx_atoi(s.data, s.len);
+                    if (ls->tcp_keepcnt == NGX_ERROR) {
+                        goto invalid_so_keepalive;
+                    }
+                }
+
+                if (ls->tcp_keepidle == 0 && ls->tcp_keepintvl == 0
+                    && ls->tcp_keepcnt == 0)
+                {
+                    goto invalid_so_keepalive;
+                }
+
+                ls->so_keepalive = 1;
+
+#else
+
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "the \"so_keepalive\" parameter accepts "
+                                   "only \"on\" or \"off\" on this platform");
+                return NGX_CONF_ERROR;
+
+#endif
+            }
+
+            ls->bind = 1;
+
+            continue;
+
+#if (NGX_HAVE_KEEPALIVE_TUNABLE)
+        invalid_so_keepalive:
+
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid so_keepalive value: \"%s\"",
+                               &value[i].data[13]);
+            return NGX_CONF_ERROR;
+#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;
+    }
+
+    if (ls->type == SOCK_DGRAM) {
+        if (backlog) {
+            return "\"backlog\" parameter is incompatible with \"udp\"";
+        }
+
+#if (NGX_STREAM_SSL)
+        if (ls->ssl) {
+            return "\"ssl\" parameter is incompatible with \"udp\"";
+        }
+#endif
+
+        if (ls->so_keepalive) {
+            return "\"so_keepalive\" parameter is incompatible with \"udp\"";
+        }
+
+        if (ls->proxy_protocol) {
+            return "\"proxy_protocol\" parameter is incompatible with \"udp\"";
+        }
+    }
+
+    als = cmcf->listen.elts;
+
+    for (i = 0; i < cmcf->listen.nelts - 1; i++) {
+        if (ls->type != als[i].type) {
+            continue;
+        }
+
+        if (ngx_cmp_sockaddr(&als[i].sockaddr.sockaddr, als[i].socklen,
+                             &ls->sockaddr.sockaddr, ls->socklen, 1)
+            != NGX_OK)
+        {
+            continue;
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "duplicate \"%V\" address and port pair", &u.url);
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_stream_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_core_srv_conf_t  *cscf = conf;
+
+    ngx_str_t  *value;
+
+    if (cscf->resolver) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    cscf->resolver = ngx_resolver_create(cf, &value[1], cf->args->nelts - 1);
+    if (cscf->resolver == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/stream/ngx_stream_geo_module.c b/nginx/src/stream/ngx_stream_geo_module.c
new file mode 100644 (file)
index 0000000..83f7fb4
--- /dev/null
@@ -0,0 +1,1596 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+typedef struct {
+    ngx_stream_variable_value_t       *value;
+    u_short                            start;
+    u_short                            end;
+} ngx_stream_geo_range_t;
+
+
+typedef struct {
+    ngx_radix_tree_t                  *tree;
+#if (NGX_HAVE_INET6)
+    ngx_radix_tree_t                  *tree6;
+#endif
+} ngx_stream_geo_trees_t;
+
+
+typedef struct {
+    ngx_stream_geo_range_t           **low;
+    ngx_stream_variable_value_t       *default_value;
+} ngx_stream_geo_high_ranges_t;
+
+
+typedef struct {
+    ngx_str_node_t                     sn;
+    ngx_stream_variable_value_t       *value;
+    size_t                             offset;
+} ngx_stream_geo_variable_value_node_t;
+
+
+typedef struct {
+    ngx_stream_variable_value_t       *value;
+    ngx_str_t                         *net;
+    ngx_stream_geo_high_ranges_t       high;
+    ngx_radix_tree_t                  *tree;
+#if (NGX_HAVE_INET6)
+    ngx_radix_tree_t                  *tree6;
+#endif
+    ngx_rbtree_t                       rbtree;
+    ngx_rbtree_node_t                  sentinel;
+    ngx_pool_t                        *pool;
+    ngx_pool_t                        *temp_pool;
+
+    size_t                             data_size;
+
+    ngx_str_t                          include_name;
+    ngx_uint_t                         includes;
+    ngx_uint_t                         entries;
+
+    unsigned                           ranges:1;
+    unsigned                           outside_entries:1;
+    unsigned                           allow_binary_include:1;
+    unsigned                           binary_include:1;
+} ngx_stream_geo_conf_ctx_t;
+
+
+typedef struct {
+    union {
+        ngx_stream_geo_trees_t         trees;
+        ngx_stream_geo_high_ranges_t   high;
+    } u;
+
+    ngx_int_t                          index;
+} ngx_stream_geo_ctx_t;
+
+
+static ngx_int_t ngx_stream_geo_addr(ngx_stream_session_t *s,
+    ngx_stream_geo_ctx_t *ctx, ngx_addr_t *addr);
+
+static char *ngx_stream_geo_block(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_stream_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf);
+static char *ngx_stream_geo_range(ngx_conf_t *cf,
+    ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *value);
+static char *ngx_stream_geo_add_range(ngx_conf_t *cf,
+    ngx_stream_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end);
+static ngx_uint_t ngx_stream_geo_delete_range(ngx_conf_t *cf,
+    ngx_stream_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end);
+static char *ngx_stream_geo_cidr(ngx_conf_t *cf,
+    ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *value);
+static char *ngx_stream_geo_cidr_add(ngx_conf_t *cf,
+    ngx_stream_geo_conf_ctx_t *ctx, ngx_cidr_t *cidr, ngx_str_t *value,
+    ngx_str_t *net);
+static ngx_stream_variable_value_t *ngx_stream_geo_value(ngx_conf_t *cf,
+    ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *value);
+static ngx_int_t ngx_stream_geo_cidr_value(ngx_conf_t *cf, ngx_str_t *net,
+    ngx_cidr_t *cidr);
+static char *ngx_stream_geo_include(ngx_conf_t *cf,
+    ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *name);
+static ngx_int_t ngx_stream_geo_include_binary_base(ngx_conf_t *cf,
+    ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *name);
+static void ngx_stream_geo_create_binary_base(ngx_stream_geo_conf_ctx_t *ctx);
+static u_char *ngx_stream_geo_copy_values(u_char *base, u_char *p,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
+
+
+static ngx_command_t  ngx_stream_geo_commands[] = {
+
+    { ngx_string("geo"),
+      NGX_STREAM_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12,
+      ngx_stream_geo_block,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_stream_module_t  ngx_stream_geo_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL                                   /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_stream_geo_module = {
+    NGX_MODULE_V1,
+    &ngx_stream_geo_module_ctx,            /* module context */
+    ngx_stream_geo_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
+};
+
+
+typedef struct {
+    u_char    GEORNG[6];
+    u_char    version;
+    u_char    ptr_size;
+    uint32_t  endianness;
+    uint32_t  crc32;
+} ngx_stream_geo_header_t;
+
+
+static ngx_stream_geo_header_t  ngx_stream_geo_header = {
+    { 'G', 'E', 'O', 'R', 'N', 'G' }, 0, sizeof(void *), 0x12345678, 0
+};
+
+
+/* geo range is AF_INET only */
+
+static ngx_int_t
+ngx_stream_geo_cidr_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    ngx_stream_geo_ctx_t *ctx = (ngx_stream_geo_ctx_t *) data;
+
+    in_addr_t                     inaddr;
+    ngx_addr_t                    addr;
+    struct sockaddr_in           *sin;
+    ngx_stream_variable_value_t  *vv;
+#if (NGX_HAVE_INET6)
+    u_char                       *p;
+    struct in6_addr              *inaddr6;
+#endif
+
+    if (ngx_stream_geo_addr(s, ctx, &addr) != NGX_OK) {
+        vv = (ngx_stream_variable_value_t *)
+                  ngx_radix32tree_find(ctx->u.trees.tree, INADDR_NONE);
+        goto done;
+    }
+
+    switch (addr.sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+    case AF_INET6:
+        inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr;
+        p = inaddr6->s6_addr;
+
+        if (IN6_IS_ADDR_V4MAPPED(inaddr6)) {
+            inaddr = p[12] << 24;
+            inaddr += p[13] << 16;
+            inaddr += p[14] << 8;
+            inaddr += p[15];
+
+            vv = (ngx_stream_variable_value_t *)
+                      ngx_radix32tree_find(ctx->u.trees.tree, inaddr);
+
+        } else {
+            vv = (ngx_stream_variable_value_t *)
+                      ngx_radix128tree_find(ctx->u.trees.tree6, p);
+        }
+
+        break;
+#endif
+
+    default: /* AF_INET */
+        sin = (struct sockaddr_in *) addr.sockaddr;
+        inaddr = ntohl(sin->sin_addr.s_addr);
+
+        vv = (ngx_stream_variable_value_t *)
+                  ngx_radix32tree_find(ctx->u.trees.tree, inaddr);
+
+        break;
+    }
+
+done:
+
+    *v = *vv;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                   "stream geo: %v", v);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_geo_range_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    ngx_stream_geo_ctx_t *ctx = (ngx_stream_geo_ctx_t *) data;
+
+    in_addr_t                inaddr;
+    ngx_addr_t               addr;
+    ngx_uint_t               n;
+    struct sockaddr_in      *sin;
+    ngx_stream_geo_range_t  *range;
+#if (NGX_HAVE_INET6)
+    u_char                  *p;
+    struct in6_addr         *inaddr6;
+#endif
+
+    *v = *ctx->u.high.default_value;
+
+    if (ngx_stream_geo_addr(s, ctx, &addr) == NGX_OK) {
+
+        switch (addr.sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+        case AF_INET6:
+            inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr;
+
+            if (IN6_IS_ADDR_V4MAPPED(inaddr6)) {
+                p = inaddr6->s6_addr;
+
+                inaddr = p[12] << 24;
+                inaddr += p[13] << 16;
+                inaddr += p[14] << 8;
+                inaddr += p[15];
+
+            } else {
+                inaddr = INADDR_NONE;
+            }
+
+            break;
+#endif
+
+        default: /* AF_INET */
+            sin = (struct sockaddr_in *) addr.sockaddr;
+            inaddr = ntohl(sin->sin_addr.s_addr);
+            break;
+        }
+
+    } else {
+        inaddr = INADDR_NONE;
+    }
+
+    if (ctx->u.high.low) {
+        range = ctx->u.high.low[inaddr >> 16];
+
+        if (range) {
+            n = inaddr & 0xffff;
+            do {
+                if (n >= (ngx_uint_t) range->start
+                    && n <= (ngx_uint_t) range->end)
+                {
+                    *v = *range->value;
+                    break;
+                }
+            } while ((++range)->value);
+        }
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                   "stream geo: %v", v);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_geo_addr(ngx_stream_session_t *s, ngx_stream_geo_ctx_t *ctx,
+    ngx_addr_t *addr)
+{
+    ngx_stream_variable_value_t  *v;
+
+    if (ctx->index == -1) {
+        ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                       "stream geo started: %V", &s->connection->addr_text);
+
+        addr->sockaddr = s->connection->sockaddr;
+        addr->socklen = s->connection->socklen;
+        /* addr->name = s->connection->addr_text; */
+
+        return NGX_OK;
+    }
+
+    v = ngx_stream_get_flushed_variable(s, ctx->index);
+
+    if (v == NULL || v->not_found) {
+        ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                       "stream geo not found");
+
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                   "stream geo started: %v", v);
+
+    if (ngx_parse_addr(s->connection->pool, addr, v->data, v->len) == NGX_OK) {
+        return NGX_OK;
+    }
+
+    return NGX_ERROR;
+}
+
+
+static char *
+ngx_stream_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char                       *rv;
+    size_t                      len;
+    ngx_str_t                  *value, name;
+    ngx_uint_t                  i;
+    ngx_conf_t                  save;
+    ngx_pool_t                 *pool;
+    ngx_array_t                *a;
+    ngx_stream_variable_t      *var;
+    ngx_stream_geo_ctx_t       *geo;
+    ngx_stream_geo_conf_ctx_t   ctx;
+#if (NGX_HAVE_INET6)
+    static struct in6_addr      zero;
+#endif
+
+    value = cf->args->elts;
+
+    geo = ngx_palloc(cf->pool, sizeof(ngx_stream_geo_ctx_t));
+    if (geo == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    name = value[1];
+
+    if (name.data[0] != '$') {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid variable name \"%V\"", &name);
+        return NGX_CONF_ERROR;
+    }
+
+    name.len--;
+    name.data++;
+
+    if (cf->args->nelts == 3) {
+
+        geo->index = ngx_stream_get_variable_index(cf, &name);
+        if (geo->index == NGX_ERROR) {
+            return NGX_CONF_ERROR;
+        }
+
+        name = value[2];
+
+        if (name.data[0] != '$') {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid variable name \"%V\"", &name);
+            return NGX_CONF_ERROR;
+        }
+
+        name.len--;
+        name.data++;
+
+    } else {
+        geo->index = -1;
+    }
+
+    var = ngx_stream_add_variable(cf, &name, NGX_STREAM_VAR_CHANGEABLE);
+    if (var == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
+    if (pool == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memzero(&ctx, sizeof(ngx_stream_geo_conf_ctx_t));
+
+    ctx.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
+    if (ctx.temp_pool == NULL) {
+        ngx_destroy_pool(pool);
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_rbtree_init(&ctx.rbtree, &ctx.sentinel, ngx_str_rbtree_insert_value);
+
+    ctx.pool = cf->pool;
+    ctx.data_size = sizeof(ngx_stream_geo_header_t)
+                  + sizeof(ngx_stream_variable_value_t)
+                  + 0x10000 * sizeof(ngx_stream_geo_range_t *);
+    ctx.allow_binary_include = 1;
+
+    save = *cf;
+    cf->pool = pool;
+    cf->ctx = &ctx;
+    cf->handler = ngx_stream_geo;
+    cf->handler_conf = conf;
+
+    rv = ngx_conf_parse(cf, NULL);
+
+    *cf = save;
+
+    if (rv != NGX_CONF_OK) {
+        goto failed;
+    }
+
+    if (ctx.ranges) {
+
+        if (ctx.high.low && !ctx.binary_include) {
+            for (i = 0; i < 0x10000; i++) {
+                a = (ngx_array_t *) ctx.high.low[i];
+
+                if (a == NULL) {
+                    continue;
+                }
+
+                if (a->nelts == 0) {
+                    ctx.high.low[i] = NULL;
+                    continue;
+                }
+
+                len = a->nelts * sizeof(ngx_stream_geo_range_t);
+
+                ctx.high.low[i] = ngx_palloc(cf->pool, len + sizeof(void *));
+                if (ctx.high.low[i] == NULL) {
+                    goto failed;
+                }
+
+                ngx_memcpy(ctx.high.low[i], a->elts, len);
+                ctx.high.low[i][a->nelts].value = NULL;
+                ctx.data_size += len + sizeof(void *);
+            }
+
+            if (ctx.allow_binary_include
+                && !ctx.outside_entries
+                && ctx.entries > 100000
+                && ctx.includes == 1)
+            {
+                ngx_stream_geo_create_binary_base(&ctx);
+            }
+        }
+
+        if (ctx.high.default_value == NULL) {
+            ctx.high.default_value = &ngx_stream_variable_null_value;
+        }
+
+        geo->u.high = ctx.high;
+
+        var->get_handler = ngx_stream_geo_range_variable;
+        var->data = (uintptr_t) geo;
+
+    } else {
+        if (ctx.tree == NULL) {
+            ctx.tree = ngx_radix_tree_create(cf->pool, -1);
+            if (ctx.tree == NULL) {
+                goto failed;
+            }
+        }
+
+        geo->u.trees.tree = ctx.tree;
+
+#if (NGX_HAVE_INET6)
+        if (ctx.tree6 == NULL) {
+            ctx.tree6 = ngx_radix_tree_create(cf->pool, -1);
+            if (ctx.tree6 == NULL) {
+                goto failed;
+            }
+        }
+
+        geo->u.trees.tree6 = ctx.tree6;
+#endif
+
+        var->get_handler = ngx_stream_geo_cidr_variable;
+        var->data = (uintptr_t) geo;
+
+        if (ngx_radix32tree_insert(ctx.tree, 0, 0,
+                                   (uintptr_t) &ngx_stream_variable_null_value)
+            == NGX_ERROR)
+        {
+            goto failed;
+        }
+
+        /* NGX_BUSY is okay (default was set explicitly) */
+
+#if (NGX_HAVE_INET6)
+        if (ngx_radix128tree_insert(ctx.tree6, zero.s6_addr, zero.s6_addr,
+                                    (uintptr_t) &ngx_stream_variable_null_value)
+            == NGX_ERROR)
+        {
+            goto failed;
+        }
+#endif
+    }
+
+    ngx_destroy_pool(ctx.temp_pool);
+    ngx_destroy_pool(pool);
+
+    return NGX_CONF_OK;
+
+failed:
+
+    ngx_destroy_pool(ctx.temp_pool);
+    ngx_destroy_pool(pool);
+
+    return NGX_CONF_ERROR;
+}
+
+
+static char *
+ngx_stream_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
+{
+    char                       *rv;
+    ngx_str_t                  *value;
+    ngx_stream_geo_conf_ctx_t  *ctx;
+
+    ctx = cf->ctx;
+
+    value = cf->args->elts;
+
+    if (cf->args->nelts == 1) {
+
+        if (ngx_strcmp(value[0].data, "ranges") == 0) {
+
+            if (ctx->tree
+#if (NGX_HAVE_INET6)
+                || ctx->tree6
+#endif
+               )
+            {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "the \"ranges\" directive must be "
+                                   "the first directive inside \"geo\" block");
+                goto failed;
+            }
+
+            ctx->ranges = 1;
+
+            rv = NGX_CONF_OK;
+
+            goto done;
+        }
+    }
+
+    if (cf->args->nelts != 2) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid number of the geo parameters");
+        goto failed;
+    }
+
+    if (ngx_strcmp(value[0].data, "include") == 0) {
+
+        rv = ngx_stream_geo_include(cf, ctx, &value[1]);
+
+        goto done;
+    }
+
+    if (ctx->ranges) {
+        rv = ngx_stream_geo_range(cf, ctx, value);
+
+    } else {
+        rv = ngx_stream_geo_cidr(cf, ctx, value);
+    }
+
+done:
+
+    ngx_reset_pool(cf->pool);
+
+    return rv;
+
+failed:
+
+    ngx_reset_pool(cf->pool);
+
+    return NGX_CONF_ERROR;
+}
+
+
+static char *
+ngx_stream_geo_range(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx,
+    ngx_str_t *value)
+{
+    u_char      *p, *last;
+    in_addr_t    start, end;
+    ngx_str_t   *net;
+    ngx_uint_t   del;
+
+    if (ngx_strcmp(value[0].data, "default") == 0) {
+
+        if (ctx->high.default_value) {
+            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                "duplicate default geo range value: \"%V\", old value: \"%v\"",
+                &value[1], ctx->high.default_value);
+        }
+
+        ctx->high.default_value = ngx_stream_geo_value(cf, ctx, &value[1]);
+        if (ctx->high.default_value == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        return NGX_CONF_OK;
+    }
+
+    if (ctx->binary_include) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+            "binary geo range base \"%s\" cannot be mixed with usual entries",
+            ctx->include_name.data);
+        return NGX_CONF_ERROR;
+    }
+
+    if (ctx->high.low == NULL) {
+        ctx->high.low = ngx_pcalloc(ctx->pool,
+                                    0x10000 * sizeof(ngx_stream_geo_range_t *));
+        if (ctx->high.low == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    ctx->entries++;
+    ctx->outside_entries = 1;
+
+    if (ngx_strcmp(value[0].data, "delete") == 0) {
+        net = &value[1];
+        del = 1;
+
+    } else {
+        net = &value[0];
+        del = 0;
+    }
+
+    last = net->data + net->len;
+
+    p = ngx_strlchr(net->data, last, '-');
+
+    if (p == NULL) {
+        goto invalid;
+    }
+
+    start = ngx_inet_addr(net->data, p - net->data);
+
+    if (start == INADDR_NONE) {
+        goto invalid;
+    }
+
+    start = ntohl(start);
+
+    p++;
+
+    end = ngx_inet_addr(p, last - p);
+
+    if (end == INADDR_NONE) {
+        goto invalid;
+    }
+
+    end = ntohl(end);
+
+    if (start > end) {
+        goto invalid;
+    }
+
+    if (del) {
+        if (ngx_stream_geo_delete_range(cf, ctx, start, end)) {
+            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                               "no address range \"%V\" to delete", net);
+        }
+
+        return NGX_CONF_OK;
+    }
+
+    ctx->value = ngx_stream_geo_value(cf, ctx, &value[1]);
+
+    if (ctx->value == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ctx->net = net;
+
+    return ngx_stream_geo_add_range(cf, ctx, start, end);
+
+invalid:
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid range \"%V\"", net);
+
+    return NGX_CONF_ERROR;
+}
+
+
+/* the add procedure is optimized to add a growing up sequence */
+
+static char *
+ngx_stream_geo_add_range(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx,
+    in_addr_t start, in_addr_t end)
+{
+    in_addr_t                n;
+    ngx_uint_t               h, i, s, e;
+    ngx_array_t             *a;
+    ngx_stream_geo_range_t  *range;
+
+    for (n = start; n <= end; n = (n + 0x10000) & 0xffff0000) {
+
+        h = n >> 16;
+
+        if (n == start) {
+            s = n & 0xffff;
+        } else {
+            s = 0;
+        }
+
+        if ((n | 0xffff) > end) {
+            e = end & 0xffff;
+
+        } else {
+            e = 0xffff;
+        }
+
+        a = (ngx_array_t *) ctx->high.low[h];
+
+        if (a == NULL) {
+            a = ngx_array_create(ctx->temp_pool, 64,
+                                 sizeof(ngx_stream_geo_range_t));
+            if (a == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            ctx->high.low[h] = (ngx_stream_geo_range_t *) a;
+        }
+
+        i = a->nelts;
+        range = a->elts;
+
+        while (i) {
+
+            i--;
+
+            if (e < (ngx_uint_t) range[i].start) {
+                continue;
+            }
+
+            if (s > (ngx_uint_t) range[i].end) {
+
+                /* add after the range */
+
+                range = ngx_array_push(a);
+                if (range == NULL) {
+                    return NGX_CONF_ERROR;
+                }
+
+                range = a->elts;
+
+                ngx_memmove(&range[i + 2], &range[i + 1],
+                           (a->nelts - 2 - i) * sizeof(ngx_stream_geo_range_t));
+
+                range[i + 1].start = (u_short) s;
+                range[i + 1].end = (u_short) e;
+                range[i + 1].value = ctx->value;
+
+                goto next;
+            }
+
+            if (s == (ngx_uint_t) range[i].start
+                && e == (ngx_uint_t) range[i].end)
+            {
+                ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                    "duplicate range \"%V\", value: \"%v\", old value: \"%v\"",
+                    ctx->net, ctx->value, range[i].value);
+
+                range[i].value = ctx->value;
+
+                goto next;
+            }
+
+            if (s > (ngx_uint_t) range[i].start
+                && e < (ngx_uint_t) range[i].end)
+            {
+                /* split the range and insert the new one */
+
+                range = ngx_array_push(a);
+                if (range == NULL) {
+                    return NGX_CONF_ERROR;
+                }
+
+                range = ngx_array_push(a);
+                if (range == NULL) {
+                    return NGX_CONF_ERROR;
+                }
+
+                range = a->elts;
+
+                ngx_memmove(&range[i + 3], &range[i + 1],
+                           (a->nelts - 3 - i) * sizeof(ngx_stream_geo_range_t));
+
+                range[i + 2].start = (u_short) (e + 1);
+                range[i + 2].end = range[i].end;
+                range[i + 2].value = range[i].value;
+
+                range[i + 1].start = (u_short) s;
+                range[i + 1].end = (u_short) e;
+                range[i + 1].value = ctx->value;
+
+                range[i].end = (u_short) (s - 1);
+
+                goto next;
+            }
+
+            if (s == (ngx_uint_t) range[i].start
+                && e < (ngx_uint_t) range[i].end)
+            {
+                /* shift the range start and insert the new range */
+
+                range = ngx_array_push(a);
+                if (range == NULL) {
+                    return NGX_CONF_ERROR;
+                }
+
+                range = a->elts;
+
+                ngx_memmove(&range[i + 1], &range[i],
+                           (a->nelts - 1 - i) * sizeof(ngx_stream_geo_range_t));
+
+                range[i + 1].start = (u_short) (e + 1);
+
+                range[i].start = (u_short) s;
+                range[i].end = (u_short) e;
+                range[i].value = ctx->value;
+
+                goto next;
+            }
+
+            if (s > (ngx_uint_t) range[i].start
+                && e == (ngx_uint_t) range[i].end)
+            {
+                /* shift the range end and insert the new range */
+
+                range = ngx_array_push(a);
+                if (range == NULL) {
+                    return NGX_CONF_ERROR;
+                }
+
+                range = a->elts;
+
+                ngx_memmove(&range[i + 2], &range[i + 1],
+                           (a->nelts - 2 - i) * sizeof(ngx_stream_geo_range_t));
+
+                range[i + 1].start = (u_short) s;
+                range[i + 1].end = (u_short) e;
+                range[i + 1].value = ctx->value;
+
+                range[i].end = (u_short) (s - 1);
+
+                goto next;
+            }
+
+            s = (ngx_uint_t) range[i].start;
+            e = (ngx_uint_t) range[i].end;
+
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                         "range \"%V\" overlaps \"%d.%d.%d.%d-%d.%d.%d.%d\"",
+                         ctx->net,
+                         h >> 8, h & 0xff, s >> 8, s & 0xff,
+                         h >> 8, h & 0xff, e >> 8, e & 0xff);
+
+            return NGX_CONF_ERROR;
+        }
+
+        /* add the first range */
+
+        range = ngx_array_push(a);
+        if (range == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        range = a->elts;
+
+        ngx_memmove(&range[1], &range[0],
+                    (a->nelts - 1) * sizeof(ngx_stream_geo_range_t));
+
+        range[0].start = (u_short) s;
+        range[0].end = (u_short) e;
+        range[0].value = ctx->value;
+
+    next:
+
+        if (h == 0xffff) {
+            break;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_uint_t
+ngx_stream_geo_delete_range(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx,
+    in_addr_t start, in_addr_t end)
+{
+    in_addr_t                n;
+    ngx_uint_t               h, i, s, e, warn;
+    ngx_array_t             *a;
+    ngx_stream_geo_range_t  *range;
+
+    warn = 0;
+
+    for (n = start; n <= end; n = (n + 0x10000) & 0xffff0000) {
+
+        h = n >> 16;
+
+        if (n == start) {
+            s = n & 0xffff;
+        } else {
+            s = 0;
+        }
+
+        if ((n | 0xffff) > end) {
+            e = end & 0xffff;
+
+        } else {
+            e = 0xffff;
+        }
+
+        a = (ngx_array_t *) ctx->high.low[h];
+
+        if (a == NULL || a->nelts == 0) {
+            warn = 1;
+            goto next;
+        }
+
+        range = a->elts;
+        for (i = 0; i < a->nelts; i++) {
+
+            if (s == (ngx_uint_t) range[i].start
+                && e == (ngx_uint_t) range[i].end)
+            {
+                ngx_memmove(&range[i], &range[i + 1],
+                           (a->nelts - 1 - i) * sizeof(ngx_stream_geo_range_t));
+
+                a->nelts--;
+
+                break;
+            }
+
+            if (i == a->nelts - 1) {
+                warn = 1;
+            }
+        }
+
+    next:
+
+        if (h == 0xffff) {
+            break;
+        }
+    }
+
+    return warn;
+}
+
+
+static char *
+ngx_stream_geo_cidr(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx,
+    ngx_str_t *value)
+{
+    char        *rv;
+    ngx_int_t    rc, del;
+    ngx_str_t   *net;
+    ngx_cidr_t   cidr;
+
+    if (ctx->tree == NULL) {
+        ctx->tree = ngx_radix_tree_create(ctx->pool, -1);
+        if (ctx->tree == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+#if (NGX_HAVE_INET6)
+    if (ctx->tree6 == NULL) {
+        ctx->tree6 = ngx_radix_tree_create(ctx->pool, -1);
+        if (ctx->tree6 == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+#endif
+
+    if (ngx_strcmp(value[0].data, "default") == 0) {
+        cidr.family = AF_INET;
+        cidr.u.in.addr = 0;
+        cidr.u.in.mask = 0;
+
+        rv = ngx_stream_geo_cidr_add(cf, ctx, &cidr, &value[1], &value[0]);
+
+        if (rv != NGX_CONF_OK) {
+            return rv;
+        }
+
+#if (NGX_HAVE_INET6)
+        cidr.family = AF_INET6;
+        ngx_memzero(&cidr.u.in6, sizeof(ngx_in6_cidr_t));
+
+        rv = ngx_stream_geo_cidr_add(cf, ctx, &cidr, &value[1], &value[0]);
+
+        if (rv != NGX_CONF_OK) {
+            return rv;
+        }
+#endif
+
+        return NGX_CONF_OK;
+    }
+
+    if (ngx_strcmp(value[0].data, "delete") == 0) {
+        net = &value[1];
+        del = 1;
+
+    } else {
+        net = &value[0];
+        del = 0;
+    }
+
+    if (ngx_stream_geo_cidr_value(cf, net, &cidr) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (cidr.family == AF_INET) {
+        cidr.u.in.addr = ntohl(cidr.u.in.addr);
+        cidr.u.in.mask = ntohl(cidr.u.in.mask);
+    }
+
+    if (del) {
+        switch (cidr.family) {
+
+#if (NGX_HAVE_INET6)
+        case AF_INET6:
+            rc = ngx_radix128tree_delete(ctx->tree6,
+                                         cidr.u.in6.addr.s6_addr,
+                                         cidr.u.in6.mask.s6_addr);
+            break;
+#endif
+
+        default: /* AF_INET */
+            rc = ngx_radix32tree_delete(ctx->tree, cidr.u.in.addr,
+                                        cidr.u.in.mask);
+            break;
+        }
+
+        if (rc != NGX_OK) {
+            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                               "no network \"%V\" to delete", net);
+        }
+
+        return NGX_CONF_OK;
+    }
+
+    return ngx_stream_geo_cidr_add(cf, ctx, &cidr, &value[1], net);
+}
+
+
+static char *
+ngx_stream_geo_cidr_add(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx,
+    ngx_cidr_t *cidr, ngx_str_t *value, ngx_str_t *net)
+{
+    ngx_int_t                     rc;
+    ngx_stream_variable_value_t  *val, *old;
+
+    val = ngx_stream_geo_value(cf, ctx, value);
+
+    if (val == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    switch (cidr->family) {
+
+#if (NGX_HAVE_INET6)
+    case AF_INET6:
+        rc = ngx_radix128tree_insert(ctx->tree6, cidr->u.in6.addr.s6_addr,
+                                     cidr->u.in6.mask.s6_addr,
+                                     (uintptr_t) val);
+
+        if (rc == NGX_OK) {
+            return NGX_CONF_OK;
+        }
+
+        if (rc == NGX_ERROR) {
+            return NGX_CONF_ERROR;
+        }
+
+        /* rc == NGX_BUSY */
+
+        old = (ngx_stream_variable_value_t *)
+                   ngx_radix128tree_find(ctx->tree6,
+                                         cidr->u.in6.addr.s6_addr);
+
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+              "duplicate network \"%V\", value: \"%v\", old value: \"%v\"",
+              net, val, old);
+
+        rc = ngx_radix128tree_delete(ctx->tree6,
+                                     cidr->u.in6.addr.s6_addr,
+                                     cidr->u.in6.mask.s6_addr);
+
+        if (rc == NGX_ERROR) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree");
+            return NGX_CONF_ERROR;
+        }
+
+        rc = ngx_radix128tree_insert(ctx->tree6, cidr->u.in6.addr.s6_addr,
+                                     cidr->u.in6.mask.s6_addr,
+                                     (uintptr_t) val);
+
+        break;
+#endif
+
+    default: /* AF_INET */
+        rc = ngx_radix32tree_insert(ctx->tree, cidr->u.in.addr,
+                                    cidr->u.in.mask, (uintptr_t) val);
+
+        if (rc == NGX_OK) {
+            return NGX_CONF_OK;
+        }
+
+        if (rc == NGX_ERROR) {
+            return NGX_CONF_ERROR;
+        }
+
+        /* rc == NGX_BUSY */
+
+        old = (ngx_stream_variable_value_t *)
+                   ngx_radix32tree_find(ctx->tree, cidr->u.in.addr);
+
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+              "duplicate network \"%V\", value: \"%v\", old value: \"%v\"",
+              net, val, old);
+
+        rc = ngx_radix32tree_delete(ctx->tree,
+                                    cidr->u.in.addr, cidr->u.in.mask);
+
+        if (rc == NGX_ERROR) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree");
+            return NGX_CONF_ERROR;
+        }
+
+        rc = ngx_radix32tree_insert(ctx->tree, cidr->u.in.addr,
+                                    cidr->u.in.mask, (uintptr_t) val);
+
+        break;
+    }
+
+    if (rc == NGX_OK) {
+        return NGX_CONF_OK;
+    }
+
+    return NGX_CONF_ERROR;
+}
+
+
+static ngx_stream_variable_value_t *
+ngx_stream_geo_value(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx,
+    ngx_str_t *value)
+{
+    uint32_t                               hash;
+    ngx_stream_variable_value_t           *val;
+    ngx_stream_geo_variable_value_node_t  *gvvn;
+
+    hash = ngx_crc32_long(value->data, value->len);
+
+    gvvn = (ngx_stream_geo_variable_value_node_t *)
+               ngx_str_rbtree_lookup(&ctx->rbtree, value, hash);
+
+    if (gvvn) {
+        return gvvn->value;
+    }
+
+    val = ngx_palloc(ctx->pool, sizeof(ngx_stream_variable_value_t));
+    if (val == NULL) {
+        return NULL;
+    }
+
+    val->len = value->len;
+    val->data = ngx_pstrdup(ctx->pool, value);
+    if (val->data == NULL) {
+        return NULL;
+    }
+
+    val->valid = 1;
+    val->no_cacheable = 0;
+    val->not_found = 0;
+
+    gvvn = ngx_palloc(ctx->temp_pool,
+                      sizeof(ngx_stream_geo_variable_value_node_t));
+    if (gvvn == NULL) {
+        return NULL;
+    }
+
+    gvvn->sn.node.key = hash;
+    gvvn->sn.str.len = val->len;
+    gvvn->sn.str.data = val->data;
+    gvvn->value = val;
+    gvvn->offset = 0;
+
+    ngx_rbtree_insert(&ctx->rbtree, &gvvn->sn.node);
+
+    ctx->data_size += ngx_align(sizeof(ngx_stream_variable_value_t)
+                                + value->len, sizeof(void *));
+
+    return val;
+}
+
+
+static ngx_int_t
+ngx_stream_geo_cidr_value(ngx_conf_t *cf, ngx_str_t *net, ngx_cidr_t *cidr)
+{
+    ngx_int_t  rc;
+
+    if (ngx_strcmp(net->data, "255.255.255.255") == 0) {
+        cidr->family = AF_INET;
+        cidr->u.in.addr = 0xffffffff;
+        cidr->u.in.mask = 0xffffffff;
+
+        return NGX_OK;
+    }
+
+    rc = ngx_ptocidr(net, cidr);
+
+    if (rc == NGX_ERROR) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid network \"%V\"", net);
+        return NGX_ERROR;
+    }
+
+    if (rc == NGX_DONE) {
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                           "low address bits of %V are meaningless", net);
+    }
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_stream_geo_include(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx,
+    ngx_str_t *name)
+{
+    char       *rv;
+    ngx_str_t   file;
+
+    file.len = name->len + 4;
+    file.data = ngx_pnalloc(ctx->temp_pool, name->len + 5);
+    if (file.data == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_sprintf(file.data, "%V.bin%Z", name);
+
+    if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (ctx->ranges) {
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data);
+
+        switch (ngx_stream_geo_include_binary_base(cf, ctx, &file)) {
+        case NGX_OK:
+            return NGX_CONF_OK;
+        case NGX_ERROR:
+            return NGX_CONF_ERROR;
+        default:
+            break;
+        }
+    }
+
+    file.len -= 4;
+    file.data[file.len] = '\0';
+
+    ctx->include_name = file;
+
+    if (ctx->outside_entries) {
+        ctx->allow_binary_include = 0;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data);
+
+    rv = ngx_conf_parse(cf, &file);
+
+    ctx->includes++;
+    ctx->outside_entries = 0;
+
+    return rv;
+}
+
+
+static ngx_int_t
+ngx_stream_geo_include_binary_base(ngx_conf_t *cf,
+    ngx_stream_geo_conf_ctx_t *ctx, ngx_str_t *name)
+{
+    u_char                       *base, ch;
+    time_t                        mtime;
+    size_t                        size, len;
+    ssize_t                       n;
+    uint32_t                      crc32;
+    ngx_err_t                     err;
+    ngx_int_t                     rc;
+    ngx_uint_t                    i;
+    ngx_file_t                    file;
+    ngx_file_info_t               fi;
+    ngx_stream_geo_range_t       *range, **ranges;
+    ngx_stream_geo_header_t      *header;
+    ngx_stream_variable_value_t  *vv;
+
+    ngx_memzero(&file, sizeof(ngx_file_t));
+    file.name = *name;
+    file.log = cf->log;
+
+    file.fd = ngx_open_file(name->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
+
+    if (file.fd == NGX_INVALID_FILE) {
+        err = ngx_errno;
+        if (err != NGX_ENOENT) {
+            ngx_conf_log_error(NGX_LOG_CRIT, cf, err,
+                               ngx_open_file_n " \"%s\" failed", name->data);
+        }
+        return NGX_DECLINED;
+    }
+
+    if (ctx->outside_entries) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+            "binary geo range base \"%s\" cannot be mixed with usual entries",
+            name->data);
+        rc = NGX_ERROR;
+        goto done;
+    }
+
+    if (ctx->binary_include) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+            "second binary geo range base \"%s\" cannot be mixed with \"%s\"",
+            name->data, ctx->include_name.data);
+        rc = NGX_ERROR;
+        goto done;
+    }
+
+    if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) {
+        ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
+                           ngx_fd_info_n " \"%s\" failed", name->data);
+        goto failed;
+    }
+
+    size = (size_t) ngx_file_size(&fi);
+    mtime = ngx_file_mtime(&fi);
+
+    ch = name->data[name->len - 4];
+    name->data[name->len - 4] = '\0';
+
+    if (ngx_file_info(name->data, &fi) == NGX_FILE_ERROR) {
+        ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
+                           ngx_file_info_n " \"%s\" failed", name->data);
+        goto failed;
+    }
+
+    name->data[name->len - 4] = ch;
+
+    if (mtime < ngx_file_mtime(&fi)) {
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                           "stale binary geo range base \"%s\"", name->data);
+        goto failed;
+    }
+
+    base = ngx_palloc(ctx->pool, size);
+    if (base == NULL) {
+        goto failed;
+    }
+
+    n = ngx_read_file(&file, base, size, 0);
+
+    if (n == NGX_ERROR) {
+        ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
+                           ngx_read_file_n " \"%s\" failed", name->data);
+        goto failed;
+    }
+
+    if ((size_t) n != size) {
+        ngx_conf_log_error(NGX_LOG_CRIT, cf, 0,
+            ngx_read_file_n " \"%s\" returned only %z bytes instead of %z",
+            name->data, n, size);
+        goto failed;
+    }
+
+    header = (ngx_stream_geo_header_t *) base;
+
+    if (size < 16 || ngx_memcmp(&ngx_stream_geo_header, header, 12) != 0) {
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+             "incompatible binary geo range base \"%s\"", name->data);
+        goto failed;
+    }
+
+    ngx_crc32_init(crc32);
+
+    vv = (ngx_stream_variable_value_t *)
+            (base + sizeof(ngx_stream_geo_header_t));
+
+    while (vv->data) {
+        len = ngx_align(sizeof(ngx_stream_variable_value_t) + vv->len,
+                        sizeof(void *));
+        ngx_crc32_update(&crc32, (u_char *) vv, len);
+        vv->data += (size_t) base;
+        vv = (ngx_stream_variable_value_t *) ((u_char *) vv + len);
+    }
+    ngx_crc32_update(&crc32, (u_char *) vv,
+                     sizeof(ngx_stream_variable_value_t));
+    vv++;
+
+    ranges = (ngx_stream_geo_range_t **) vv;
+
+    for (i = 0; i < 0x10000; i++) {
+        ngx_crc32_update(&crc32, (u_char *) &ranges[i], sizeof(void *));
+        if (ranges[i]) {
+            ranges[i] = (ngx_stream_geo_range_t *)
+                            ((u_char *) ranges[i] + (size_t) base);
+        }
+    }
+
+    range = (ngx_stream_geo_range_t *) &ranges[0x10000];
+
+    while ((u_char *) range < base + size) {
+        while (range->value) {
+            ngx_crc32_update(&crc32, (u_char *) range,
+                             sizeof(ngx_stream_geo_range_t));
+            range->value = (ngx_stream_variable_value_t *)
+                               ((u_char *) range->value + (size_t) base);
+            range++;
+        }
+        ngx_crc32_update(&crc32, (u_char *) range, sizeof(void *));
+        range = (ngx_stream_geo_range_t *) ((u_char *) range + sizeof(void *));
+    }
+
+    ngx_crc32_final(crc32);
+
+    if (crc32 != header->crc32) {
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                  "CRC32 mismatch in binary geo range base \"%s\"", name->data);
+        goto failed;
+    }
+
+    ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0,
+                       "using binary geo range base \"%s\"", name->data);
+
+    ctx->include_name = *name;
+    ctx->binary_include = 1;
+    ctx->high.low = ranges;
+    rc = NGX_OK;
+
+    goto done;
+
+failed:
+
+    rc = NGX_DECLINED;
+
+done:
+
+    if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
+                      ngx_close_file_n " \"%s\" failed", name->data);
+    }
+
+    return rc;
+}
+
+
+static void
+ngx_stream_geo_create_binary_base(ngx_stream_geo_conf_ctx_t *ctx)
+{
+    u_char                                *p;
+    uint32_t                               hash;
+    ngx_str_t                              s;
+    ngx_uint_t                             i;
+    ngx_file_mapping_t                     fm;
+    ngx_stream_geo_range_t                *r, *range, **ranges;
+    ngx_stream_geo_header_t               *header;
+    ngx_stream_geo_variable_value_node_t  *gvvn;
+
+    fm.name = ngx_pnalloc(ctx->temp_pool, ctx->include_name.len + 5);
+    if (fm.name == NULL) {
+        return;
+    }
+
+    ngx_sprintf(fm.name, "%V.bin%Z", &ctx->include_name);
+
+    fm.size = ctx->data_size;
+    fm.log = ctx->pool->log;
+
+    ngx_log_error(NGX_LOG_NOTICE, fm.log, 0,
+                  "creating binary geo range base \"%s\"", fm.name);
+
+    if (ngx_create_file_mapping(&fm) != NGX_OK) {
+        return;
+    }
+
+    p = ngx_cpymem(fm.addr, &ngx_stream_geo_header,
+                   sizeof(ngx_stream_geo_header_t));
+
+    p = ngx_stream_geo_copy_values(fm.addr, p, ctx->rbtree.root,
+                                   ctx->rbtree.sentinel);
+
+    p += sizeof(ngx_stream_variable_value_t);
+
+    ranges = (ngx_stream_geo_range_t **) p;
+
+    p += 0x10000 * sizeof(ngx_stream_geo_range_t *);
+
+    for (i = 0; i < 0x10000; i++) {
+        r = ctx->high.low[i];
+        if (r == NULL) {
+            continue;
+        }
+
+        range = (ngx_stream_geo_range_t *) p;
+        ranges[i] = (ngx_stream_geo_range_t *) (p - (u_char *) fm.addr);
+
+        do {
+            s.len = r->value->len;
+            s.data = r->value->data;
+            hash = ngx_crc32_long(s.data, s.len);
+            gvvn = (ngx_stream_geo_variable_value_node_t *)
+                        ngx_str_rbtree_lookup(&ctx->rbtree, &s, hash);
+
+            range->value = (ngx_stream_variable_value_t *) gvvn->offset;
+            range->start = r->start;
+            range->end = r->end;
+            range++;
+
+        } while ((++r)->value);
+
+        range->value = NULL;
+
+        p = (u_char *) range + sizeof(void *);
+    }
+
+    header = fm.addr;
+    header->crc32 = ngx_crc32_long((u_char *) fm.addr
+                                       + sizeof(ngx_stream_geo_header_t),
+                                   fm.size - sizeof(ngx_stream_geo_header_t));
+
+    ngx_close_file_mapping(&fm);
+}
+
+
+static u_char *
+ngx_stream_geo_copy_values(u_char *base, u_char *p, ngx_rbtree_node_t *node,
+    ngx_rbtree_node_t *sentinel)
+{
+    ngx_stream_variable_value_t           *vv;
+    ngx_stream_geo_variable_value_node_t  *gvvn;
+
+    if (node == sentinel) {
+        return p;
+    }
+
+    gvvn = (ngx_stream_geo_variable_value_node_t *) node;
+    gvvn->offset = p - base;
+
+    vv = (ngx_stream_variable_value_t *) p;
+    *vv = *gvvn->value;
+    p += sizeof(ngx_stream_variable_value_t);
+    vv->data = (u_char *) (p - base);
+
+    p = ngx_cpymem(p, gvvn->sn.str.data, gvvn->sn.str.len);
+
+    p = ngx_align_ptr(p, sizeof(void *));
+
+    p = ngx_stream_geo_copy_values(base, p, node->left, sentinel);
+
+    return ngx_stream_geo_copy_values(base, p, node->right, sentinel);
+}
diff --git a/nginx/src/stream/ngx_stream_geoip_module.c b/nginx/src/stream/ngx_stream_geoip_module.c
new file mode 100644 (file)
index 0000000..6507b71
--- /dev/null
@@ -0,0 +1,814 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+#include <GeoIP.h>
+#include <GeoIPCity.h>
+
+
+#define NGX_GEOIP_COUNTRY_CODE   0
+#define NGX_GEOIP_COUNTRY_CODE3  1
+#define NGX_GEOIP_COUNTRY_NAME   2
+
+
+typedef struct {
+    GeoIP        *country;
+    GeoIP        *org;
+    GeoIP        *city;
+#if (NGX_HAVE_GEOIP_V6)
+    unsigned      country_v6:1;
+    unsigned      org_v6:1;
+    unsigned      city_v6:1;
+#endif
+} ngx_stream_geoip_conf_t;
+
+
+typedef struct {
+    ngx_str_t    *name;
+    uintptr_t     data;
+} ngx_stream_geoip_var_t;
+
+
+typedef const char *(*ngx_stream_geoip_variable_handler_pt)(GeoIP *,
+    u_long addr);
+
+
+ngx_stream_geoip_variable_handler_pt ngx_stream_geoip_country_functions[] = {
+    GeoIP_country_code_by_ipnum,
+    GeoIP_country_code3_by_ipnum,
+    GeoIP_country_name_by_ipnum,
+};
+
+
+#if (NGX_HAVE_GEOIP_V6)
+
+typedef const char *(*ngx_stream_geoip_variable_handler_v6_pt)(GeoIP *,
+    geoipv6_t addr);
+
+
+ngx_stream_geoip_variable_handler_v6_pt
+    ngx_stream_geoip_country_v6_functions[] =
+{
+    GeoIP_country_code_by_ipnum_v6,
+    GeoIP_country_code3_by_ipnum_v6,
+    GeoIP_country_name_by_ipnum_v6,
+};
+
+#endif
+
+
+static ngx_int_t ngx_stream_geoip_country_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_geoip_org_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_geoip_city_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_geoip_region_name_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_geoip_city_float_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_geoip_city_int_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static GeoIPRecord *ngx_stream_geoip_get_city_record(ngx_stream_session_t *s);
+
+static ngx_int_t ngx_stream_geoip_add_variables(ngx_conf_t *cf);
+static void *ngx_stream_geoip_create_conf(ngx_conf_t *cf);
+static char *ngx_stream_geoip_country(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_stream_geoip_org(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_stream_geoip_city(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static void ngx_stream_geoip_cleanup(void *data);
+
+
+static ngx_command_t  ngx_stream_geoip_commands[] = {
+
+    { ngx_string("geoip_country"),
+      NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE12,
+      ngx_stream_geoip_country,
+      NGX_STREAM_MAIN_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("geoip_org"),
+      NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE12,
+      ngx_stream_geoip_org,
+      NGX_STREAM_MAIN_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("geoip_city"),
+      NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE12,
+      ngx_stream_geoip_city,
+      NGX_STREAM_MAIN_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_stream_module_t  ngx_stream_geoip_module_ctx = {
+    ngx_stream_geoip_add_variables,        /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    ngx_stream_geoip_create_conf,          /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL                                   /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_stream_geoip_module = {
+    NGX_MODULE_V1,
+    &ngx_stream_geoip_module_ctx,          /* module context */
+    ngx_stream_geoip_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_stream_variable_t  ngx_stream_geoip_vars[] = {
+
+    { ngx_string("geoip_country_code"), NULL,
+      ngx_stream_geoip_country_variable,
+      NGX_GEOIP_COUNTRY_CODE, 0, 0 },
+
+    { ngx_string("geoip_country_code3"), NULL,
+      ngx_stream_geoip_country_variable,
+      NGX_GEOIP_COUNTRY_CODE3, 0, 0 },
+
+    { ngx_string("geoip_country_name"), NULL,
+      ngx_stream_geoip_country_variable,
+      NGX_GEOIP_COUNTRY_NAME, 0, 0 },
+
+    { ngx_string("geoip_org"), NULL,
+      ngx_stream_geoip_org_variable,
+      0, 0, 0 },
+
+    { ngx_string("geoip_city_continent_code"), NULL,
+      ngx_stream_geoip_city_variable,
+      offsetof(GeoIPRecord, continent_code), 0, 0 },
+
+    { ngx_string("geoip_city_country_code"), NULL,
+      ngx_stream_geoip_city_variable,
+      offsetof(GeoIPRecord, country_code), 0, 0 },
+
+    { ngx_string("geoip_city_country_code3"), NULL,
+      ngx_stream_geoip_city_variable,
+      offsetof(GeoIPRecord, country_code3), 0, 0 },
+
+    { ngx_string("geoip_city_country_name"), NULL,
+      ngx_stream_geoip_city_variable,
+      offsetof(GeoIPRecord, country_name), 0, 0 },
+
+    { ngx_string("geoip_region"), NULL,
+      ngx_stream_geoip_city_variable,
+      offsetof(GeoIPRecord, region), 0, 0 },
+
+    { ngx_string("geoip_region_name"), NULL,
+      ngx_stream_geoip_region_name_variable,
+      0, 0, 0 },
+
+    { ngx_string("geoip_city"), NULL,
+      ngx_stream_geoip_city_variable,
+      offsetof(GeoIPRecord, city), 0, 0 },
+
+    { ngx_string("geoip_postal_code"), NULL,
+      ngx_stream_geoip_city_variable,
+      offsetof(GeoIPRecord, postal_code), 0, 0 },
+
+    { ngx_string("geoip_latitude"), NULL,
+      ngx_stream_geoip_city_float_variable,
+      offsetof(GeoIPRecord, latitude), 0, 0 },
+
+    { ngx_string("geoip_longitude"), NULL,
+      ngx_stream_geoip_city_float_variable,
+      offsetof(GeoIPRecord, longitude), 0, 0 },
+
+    { ngx_string("geoip_dma_code"), NULL,
+      ngx_stream_geoip_city_int_variable,
+      offsetof(GeoIPRecord, dma_code), 0, 0 },
+
+    { ngx_string("geoip_area_code"), NULL,
+      ngx_stream_geoip_city_int_variable,
+      offsetof(GeoIPRecord, area_code), 0, 0 },
+
+      ngx_stream_null_variable
+};
+
+
+static u_long
+ngx_stream_geoip_addr(ngx_stream_session_t *s, ngx_stream_geoip_conf_t *gcf)
+{
+    ngx_addr_t           addr;
+    struct sockaddr_in  *sin;
+
+    addr.sockaddr = s->connection->sockaddr;
+    addr.socklen = s->connection->socklen;
+    /* addr.name = s->connection->addr_text; */
+
+#if (NGX_HAVE_INET6)
+
+    if (addr.sockaddr->sa_family == AF_INET6) {
+        u_char           *p;
+        in_addr_t         inaddr;
+        struct in6_addr  *inaddr6;
+
+        inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr;
+
+        if (IN6_IS_ADDR_V4MAPPED(inaddr6)) {
+            p = inaddr6->s6_addr;
+
+            inaddr = p[12] << 24;
+            inaddr += p[13] << 16;
+            inaddr += p[14] << 8;
+            inaddr += p[15];
+
+            return inaddr;
+        }
+    }
+
+#endif
+
+    if (addr.sockaddr->sa_family != AF_INET) {
+        return INADDR_NONE;
+    }
+
+    sin = (struct sockaddr_in *) addr.sockaddr;
+    return ntohl(sin->sin_addr.s_addr);
+}
+
+
+#if (NGX_HAVE_GEOIP_V6)
+
+static geoipv6_t
+ngx_stream_geoip_addr_v6(ngx_stream_session_t *s, ngx_stream_geoip_conf_t *gcf)
+{
+    ngx_addr_t            addr;
+    in_addr_t             addr4;
+    struct in6_addr       addr6;
+    struct sockaddr_in   *sin;
+    struct sockaddr_in6  *sin6;
+
+    addr.sockaddr = s->connection->sockaddr;
+    addr.socklen = s->connection->socklen;
+    /* addr.name = s->connection->addr_text; */
+
+    switch (addr.sockaddr->sa_family) {
+
+    case AF_INET:
+        /* Produce IPv4-mapped IPv6 address. */
+        sin = (struct sockaddr_in *) addr.sockaddr;
+        addr4 = ntohl(sin->sin_addr.s_addr);
+
+        ngx_memzero(&addr6, sizeof(struct in6_addr));
+        addr6.s6_addr[10] = 0xff;
+        addr6.s6_addr[11] = 0xff;
+        addr6.s6_addr[12] = addr4 >> 24;
+        addr6.s6_addr[13] = addr4 >> 16;
+        addr6.s6_addr[14] = addr4 >> 8;
+        addr6.s6_addr[15] = addr4;
+        return addr6;
+
+    case AF_INET6:
+        sin6 = (struct sockaddr_in6 *) addr.sockaddr;
+        return sin6->sin6_addr;
+
+    default:
+        return in6addr_any;
+    }
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_stream_geoip_country_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    ngx_stream_geoip_variable_handler_pt     handler =
+        ngx_stream_geoip_country_functions[data];
+#if (NGX_HAVE_GEOIP_V6)
+    ngx_stream_geoip_variable_handler_v6_pt  handler_v6 =
+        ngx_stream_geoip_country_v6_functions[data];
+#endif
+
+    const char               *val;
+    ngx_stream_geoip_conf_t  *gcf;
+
+    gcf = ngx_stream_get_module_main_conf(s, ngx_stream_geoip_module);
+
+    if (gcf->country == NULL) {
+        goto not_found;
+    }
+
+#if (NGX_HAVE_GEOIP_V6)
+    val = gcf->country_v6
+              ? handler_v6(gcf->country, ngx_stream_geoip_addr_v6(s, gcf))
+              : handler(gcf->country, ngx_stream_geoip_addr(s, gcf));
+#else
+    val = handler(gcf->country, ngx_stream_geoip_addr(s, gcf));
+#endif
+
+    if (val == NULL) {
+        goto not_found;
+    }
+
+    v->len = ngx_strlen(val);
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = (u_char *) val;
+
+    return NGX_OK;
+
+not_found:
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_geoip_org_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    size_t                    len;
+    char                     *val;
+    ngx_stream_geoip_conf_t  *gcf;
+
+    gcf = ngx_stream_get_module_main_conf(s, ngx_stream_geoip_module);
+
+    if (gcf->org == NULL) {
+        goto not_found;
+    }
+
+#if (NGX_HAVE_GEOIP_V6)
+    val = gcf->org_v6
+              ? GeoIP_name_by_ipnum_v6(gcf->org,
+                                       ngx_stream_geoip_addr_v6(s, gcf))
+              : GeoIP_name_by_ipnum(gcf->org,
+                                    ngx_stream_geoip_addr(s, gcf));
+#else
+    val = GeoIP_name_by_ipnum(gcf->org, ngx_stream_geoip_addr(s, gcf));
+#endif
+
+    if (val == NULL) {
+        goto not_found;
+    }
+
+    len = ngx_strlen(val);
+    v->data = ngx_pnalloc(s->connection->pool, len);
+    if (v->data == NULL) {
+        ngx_free(val);
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(v->data, val, len);
+
+    v->len = len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    ngx_free(val);
+
+    return NGX_OK;
+
+not_found:
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_geoip_city_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    char         *val;
+    size_t        len;
+    GeoIPRecord  *gr;
+
+    gr = ngx_stream_geoip_get_city_record(s);
+    if (gr == NULL) {
+        goto not_found;
+    }
+
+    val = *(char **) ((char *) gr + data);
+    if (val == NULL) {
+        goto no_value;
+    }
+
+    len = ngx_strlen(val);
+    v->data = ngx_pnalloc(s->connection->pool, len);
+    if (v->data == NULL) {
+        GeoIPRecord_delete(gr);
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(v->data, val, len);
+
+    v->len = len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    GeoIPRecord_delete(gr);
+
+    return NGX_OK;
+
+no_value:
+
+    GeoIPRecord_delete(gr);
+
+not_found:
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_geoip_region_name_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    size_t        len;
+    const char   *val;
+    GeoIPRecord  *gr;
+
+    gr = ngx_stream_geoip_get_city_record(s);
+    if (gr == NULL) {
+        goto not_found;
+    }
+
+    val = GeoIP_region_name_by_code(gr->country_code, gr->region);
+
+    GeoIPRecord_delete(gr);
+
+    if (val == NULL) {
+        goto not_found;
+    }
+
+    len = ngx_strlen(val);
+    v->data = ngx_pnalloc(s->connection->pool, len);
+    if (v->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(v->data, val, len);
+
+    v->len = len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    return NGX_OK;
+
+not_found:
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_geoip_city_float_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    float         val;
+    GeoIPRecord  *gr;
+
+    gr = ngx_stream_geoip_get_city_record(s);
+    if (gr == NULL) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    v->data = ngx_pnalloc(s->connection->pool, NGX_INT64_LEN + 5);
+    if (v->data == NULL) {
+        GeoIPRecord_delete(gr);
+        return NGX_ERROR;
+    }
+
+    val = *(float *) ((char *) gr + data);
+
+    v->len = ngx_sprintf(v->data, "%.4f", val) - v->data;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    GeoIPRecord_delete(gr);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_geoip_city_int_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    int           val;
+    GeoIPRecord  *gr;
+
+    gr = ngx_stream_geoip_get_city_record(s);
+    if (gr == NULL) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    v->data = ngx_pnalloc(s->connection->pool, NGX_INT64_LEN);
+    if (v->data == NULL) {
+        GeoIPRecord_delete(gr);
+        return NGX_ERROR;
+    }
+
+    val = *(int *) ((char *) gr + data);
+
+    v->len = ngx_sprintf(v->data, "%d", val) - v->data;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    GeoIPRecord_delete(gr);
+
+    return NGX_OK;
+}
+
+
+static GeoIPRecord *
+ngx_stream_geoip_get_city_record(ngx_stream_session_t *s)
+{
+    ngx_stream_geoip_conf_t  *gcf;
+
+    gcf = ngx_stream_get_module_main_conf(s, ngx_stream_geoip_module);
+
+    if (gcf->city) {
+#if (NGX_HAVE_GEOIP_V6)
+        return gcf->city_v6
+                   ? GeoIP_record_by_ipnum_v6(gcf->city,
+                                              ngx_stream_geoip_addr_v6(s, gcf))
+                   : GeoIP_record_by_ipnum(gcf->city,
+                                           ngx_stream_geoip_addr(s, gcf));
+#else
+        return GeoIP_record_by_ipnum(gcf->city, ngx_stream_geoip_addr(s, gcf));
+#endif
+    }
+
+    return NULL;
+}
+
+
+static ngx_int_t
+ngx_stream_geoip_add_variables(ngx_conf_t *cf)
+{
+    ngx_stream_variable_t  *var, *v;
+
+    for (v = ngx_stream_geoip_vars; v->name.len; v++) {
+        var = ngx_stream_add_variable(cf, &v->name, v->flags);
+        if (var == NULL) {
+            return NGX_ERROR;
+        }
+
+        var->get_handler = v->get_handler;
+        var->data = v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_stream_geoip_create_conf(ngx_conf_t *cf)
+{
+    ngx_pool_cleanup_t       *cln;
+    ngx_stream_geoip_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_geoip_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    cln = ngx_pool_cleanup_add(cf->pool, 0);
+    if (cln == NULL) {
+        return NULL;
+    }
+
+    cln->handler = ngx_stream_geoip_cleanup;
+    cln->data = conf;
+
+    return conf;
+}
+
+
+static char *
+ngx_stream_geoip_country(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_geoip_conf_t  *gcf = conf;
+
+    ngx_str_t  *value;
+
+    if (gcf->country) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    gcf->country = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE);
+
+    if (gcf->country == NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "GeoIP_open(\"%V\") failed", &value[1]);
+
+        return NGX_CONF_ERROR;
+    }
+
+    if (cf->args->nelts == 3) {
+        if (ngx_strcmp(value[2].data, "utf8") == 0) {
+            GeoIP_set_charset(gcf->country, GEOIP_CHARSET_UTF8);
+
+        } else {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid parameter \"%V\"", &value[2]);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    switch (gcf->country->databaseType) {
+
+    case GEOIP_COUNTRY_EDITION:
+
+        return NGX_CONF_OK;
+
+#if (NGX_HAVE_GEOIP_V6)
+    case GEOIP_COUNTRY_EDITION_V6:
+
+        gcf->country_v6 = 1;
+        return NGX_CONF_OK;
+#endif
+
+    default:
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid GeoIP database \"%V\" type:%d",
+                           &value[1], gcf->country->databaseType);
+        return NGX_CONF_ERROR;
+    }
+}
+
+
+static char *
+ngx_stream_geoip_org(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_geoip_conf_t  *gcf = conf;
+
+    ngx_str_t  *value;
+
+    if (gcf->org) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    gcf->org = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE);
+
+    if (gcf->org == NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "GeoIP_open(\"%V\") failed", &value[1]);
+
+        return NGX_CONF_ERROR;
+    }
+
+    if (cf->args->nelts == 3) {
+        if (ngx_strcmp(value[2].data, "utf8") == 0) {
+            GeoIP_set_charset(gcf->org, GEOIP_CHARSET_UTF8);
+
+        } else {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid parameter \"%V\"", &value[2]);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    switch (gcf->org->databaseType) {
+
+    case GEOIP_ISP_EDITION:
+    case GEOIP_ORG_EDITION:
+    case GEOIP_DOMAIN_EDITION:
+    case GEOIP_ASNUM_EDITION:
+
+        return NGX_CONF_OK;
+
+#if (NGX_HAVE_GEOIP_V6)
+    case GEOIP_ISP_EDITION_V6:
+    case GEOIP_ORG_EDITION_V6:
+    case GEOIP_DOMAIN_EDITION_V6:
+    case GEOIP_ASNUM_EDITION_V6:
+
+        gcf->org_v6 = 1;
+        return NGX_CONF_OK;
+#endif
+
+    default:
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid GeoIP database \"%V\" type:%d",
+                           &value[1], gcf->org->databaseType);
+        return NGX_CONF_ERROR;
+    }
+}
+
+
+static char *
+ngx_stream_geoip_city(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_geoip_conf_t  *gcf = conf;
+
+    ngx_str_t  *value;
+
+    if (gcf->city) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    gcf->city = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE);
+
+    if (gcf->city == NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "GeoIP_open(\"%V\") failed", &value[1]);
+
+        return NGX_CONF_ERROR;
+    }
+
+    if (cf->args->nelts == 3) {
+        if (ngx_strcmp(value[2].data, "utf8") == 0) {
+            GeoIP_set_charset(gcf->city, GEOIP_CHARSET_UTF8);
+
+        } else {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid parameter \"%V\"", &value[2]);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    switch (gcf->city->databaseType) {
+
+    case GEOIP_CITY_EDITION_REV0:
+    case GEOIP_CITY_EDITION_REV1:
+
+        return NGX_CONF_OK;
+
+#if (NGX_HAVE_GEOIP_V6)
+    case GEOIP_CITY_EDITION_REV0_V6:
+    case GEOIP_CITY_EDITION_REV1_V6:
+
+        gcf->city_v6 = 1;
+        return NGX_CONF_OK;
+#endif
+
+    default:
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid GeoIP City database \"%V\" type:%d",
+                           &value[1], gcf->city->databaseType);
+        return NGX_CONF_ERROR;
+    }
+}
+
+
+static void
+ngx_stream_geoip_cleanup(void *data)
+{
+    ngx_stream_geoip_conf_t  *gcf = data;
+
+    if (gcf->country) {
+        GeoIP_delete(gcf->country);
+    }
+
+    if (gcf->org) {
+        GeoIP_delete(gcf->org);
+    }
+
+    if (gcf->city) {
+        GeoIP_delete(gcf->city);
+    }
+}
diff --git a/nginx/src/stream/ngx_stream_handler.c b/nginx/src/stream/ngx_stream_handler.c
new file mode 100644 (file)
index 0000000..105b9f5
--- /dev/null
@@ -0,0 +1,385 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_stream.h>
+
+
+static void ngx_stream_log_session(ngx_stream_session_t *s);
+static void ngx_stream_close_connection(ngx_connection_t *c);
+static u_char *ngx_stream_log_error(ngx_log_t *log, u_char *buf, size_t len);
+static void ngx_stream_proxy_protocol_handler(ngx_event_t *rev);
+
+
+void
+ngx_stream_init_connection(ngx_connection_t *c)
+{
+    u_char                        text[NGX_SOCKADDR_STRLEN];
+    size_t                        len;
+    ngx_uint_t                    i;
+    ngx_time_t                   *tp;
+    ngx_event_t                  *rev;
+    struct sockaddr              *sa;
+    ngx_stream_port_t            *port;
+    struct sockaddr_in           *sin;
+    ngx_stream_in_addr_t         *addr;
+    ngx_stream_session_t         *s;
+    ngx_stream_addr_conf_t       *addr_conf;
+#if (NGX_HAVE_INET6)
+    struct sockaddr_in6          *sin6;
+    ngx_stream_in6_addr_t        *addr6;
+#endif
+    ngx_stream_core_srv_conf_t   *cscf;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    /* find the server configuration for the address:port */
+
+    port = c->listening->servers;
+
+    if (port->naddrs > 1) {
+
+        /*
+         * There are several addresses on this port and one of them
+         * is the "*:port" wildcard so getsockname() is needed to determine
+         * the server address.
+         *
+         * AcceptEx() and recvmsg() already gave this address.
+         */
+
+        if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
+            ngx_stream_close_connection(c);
+            return;
+        }
+
+        sa = c->local_sockaddr;
+
+        switch (sa->sa_family) {
+
+#if (NGX_HAVE_INET6)
+        case AF_INET6:
+            sin6 = (struct sockaddr_in6 *) sa;
+
+            addr6 = port->addrs;
+
+            /* the last address is "*" */
+
+            for (i = 0; i < port->naddrs - 1; i++) {
+                if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) {
+                    break;
+                }
+            }
+
+            addr_conf = &addr6[i].conf;
+
+            break;
+#endif
+
+        default: /* AF_INET */
+            sin = (struct sockaddr_in *) sa;
+
+            addr = port->addrs;
+
+            /* the last address is "*" */
+
+            for (i = 0; i < port->naddrs - 1; i++) {
+                if (addr[i].addr == sin->sin_addr.s_addr) {
+                    break;
+                }
+            }
+
+            addr_conf = &addr[i].conf;
+
+            break;
+        }
+
+    } else {
+        switch (c->local_sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+        case AF_INET6:
+            addr6 = port->addrs;
+            addr_conf = &addr6[0].conf;
+            break;
+#endif
+
+        default: /* AF_INET */
+            addr = port->addrs;
+            addr_conf = &addr[0].conf;
+            break;
+        }
+    }
+
+    s = ngx_pcalloc(c->pool, sizeof(ngx_stream_session_t));
+    if (s == NULL) {
+        ngx_stream_close_connection(c);
+        return;
+    }
+
+    s->signature = NGX_STREAM_MODULE;
+    s->main_conf = addr_conf->ctx->main_conf;
+    s->srv_conf = addr_conf->ctx->srv_conf;
+
+#if (NGX_STREAM_SSL)
+    s->ssl = addr_conf->ssl;
+#endif
+
+    if (c->buffer) {
+        s->received += c->buffer->last - c->buffer->pos;
+    }
+
+    s->connection = c;
+    c->data = s;
+
+    cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module);
+
+    ngx_set_connection_log(c, cscf->error_log);
+
+    len = ngx_sock_ntop(c->sockaddr, c->socklen, text, NGX_SOCKADDR_STRLEN, 1);
+
+    ngx_log_error(NGX_LOG_INFO, c->log, 0, "*%uA %sclient %*s connected to %V",
+                  c->number, c->type == SOCK_DGRAM ? "udp " : "",
+                  len, text, &addr_conf->addr_text);
+
+    c->log->connection = c->number;
+    c->log->handler = ngx_stream_log_error;
+    c->log->data = s;
+    c->log->action = "initializing session";
+    c->log_error = NGX_ERROR_INFO;
+
+    s->ctx = ngx_pcalloc(c->pool, sizeof(void *) * ngx_stream_max_module);
+    if (s->ctx == NULL) {
+        ngx_stream_close_connection(c);
+        return;
+    }
+
+    cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module);
+
+    s->variables = ngx_pcalloc(s->connection->pool,
+                               cmcf->variables.nelts
+                               * sizeof(ngx_stream_variable_value_t));
+
+    if (s->variables == NULL) {
+        ngx_stream_close_connection(c);
+        return;
+    }
+
+    tp = ngx_timeofday();
+    s->start_sec = tp->sec;
+    s->start_msec = tp->msec;
+
+    rev = c->read;
+    rev->handler = ngx_stream_session_handler;
+
+    if (addr_conf->proxy_protocol) {
+        c->log->action = "reading PROXY protocol";
+
+        rev->handler = ngx_stream_proxy_protocol_handler;
+
+        if (!rev->ready) {
+            ngx_add_timer(rev, cscf->proxy_protocol_timeout);
+
+            if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+                ngx_stream_finalize_session(s,
+                                            NGX_STREAM_INTERNAL_SERVER_ERROR);
+            }
+
+            return;
+        }
+    }
+
+    if (ngx_use_accept_mutex) {
+        ngx_post_event(rev, &ngx_posted_events);
+        return;
+    }
+
+    rev->handler(rev);
+}
+
+
+static void
+ngx_stream_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_stream_session_t        *s;
+    ngx_stream_core_srv_conf_t  *cscf;
+
+    c = rev->data;
+    s = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0,
+                   "stream PROXY protocol handler");
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+        ngx_stream_finalize_session(s, NGX_STREAM_OK);
+        return;
+    }
+
+    n = ngxvcl_recv(c->fd, (char *) buf, sizeof(buf), MSG_PEEK);
+
+    err = ngx_socket_errno;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "recv(): %z", n);
+
+    if (n == -1) {
+        if (err == NGX_EAGAIN) {
+            rev->ready = 0;
+
+            if (!rev->timer_set) {
+                cscf = ngx_stream_get_module_srv_conf(s,
+                                                      ngx_stream_core_module);
+
+                ngx_add_timer(rev, cscf->proxy_protocol_timeout);
+            }
+
+            if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+                ngx_stream_finalize_session(s,
+                                            NGX_STREAM_INTERNAL_SERVER_ERROR);
+            }
+
+            return;
+        }
+
+        ngx_connection_error(c, err, "recv() failed");
+
+        ngx_stream_finalize_session(s, NGX_STREAM_OK);
+        return;
+    }
+
+    if (rev->timer_set) {
+        ngx_del_timer(rev);
+    }
+
+    p = ngx_proxy_protocol_read(c, buf, buf + n);
+
+    if (p == NULL) {
+        ngx_stream_finalize_session(s, NGX_STREAM_BAD_REQUEST);
+        return;
+    }
+
+    size = p - buf;
+
+    if (c->recv(c, buf, size) != (ssize_t) size) {
+        ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    c->log->action = "initializing session";
+
+    ngx_stream_session_handler(rev);
+}
+
+
+void
+ngx_stream_session_handler(ngx_event_t *rev)
+{
+    ngx_connection_t      *c;
+    ngx_stream_session_t  *s;
+
+    c = rev->data;
+    s = c->data;
+
+    ngx_stream_core_run_phases(s);
+}
+
+
+void
+ngx_stream_finalize_session(ngx_stream_session_t *s, ngx_uint_t rc)
+{
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                   "finalize stream session: %i", rc);
+
+    s->status = rc;
+
+    ngx_stream_log_session(s);
+
+    ngx_stream_close_connection(s->connection);
+}
+
+
+static void
+ngx_stream_log_session(ngx_stream_session_t *s)
+{
+    ngx_uint_t                    i, n;
+    ngx_stream_handler_pt        *log_handler;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module);
+
+    log_handler = cmcf->phases[NGX_STREAM_LOG_PHASE].handlers.elts;
+    n = cmcf->phases[NGX_STREAM_LOG_PHASE].handlers.nelts;
+
+    for (i = 0; i < n; i++) {
+        log_handler[i](s);
+    }
+}
+
+
+static void
+ngx_stream_close_connection(ngx_connection_t *c)
+{
+    ngx_pool_t  *pool;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0,
+                   "close stream connection: %d", c->fd);
+
+#if (NGX_STREAM_SSL)
+
+    if (c->ssl) {
+        if (ngx_ssl_shutdown(c) == NGX_AGAIN) {
+            c->ssl->handler = ngx_stream_close_connection;
+            return;
+        }
+    }
+
+#endif
+
+#if (NGX_STAT_STUB)
+    (void) ngx_atomic_fetch_add(ngx_stat_active, -1);
+#endif
+
+    pool = c->pool;
+
+    ngx_close_connection(c);
+
+    ngx_destroy_pool(pool);
+}
+
+
+static u_char *
+ngx_stream_log_error(ngx_log_t *log, u_char *buf, size_t len)
+{
+    u_char                *p;
+    ngx_stream_session_t  *s;
+
+    if (log->action) {
+        p = ngx_snprintf(buf, len, " while %s", log->action);
+        len -= p - buf;
+        buf = p;
+    }
+
+    s = log->data;
+
+    p = ngx_snprintf(buf, len, ", %sclient: %V, server: %V",
+                     s->connection->type == SOCK_DGRAM ? "udp " : "",
+                     &s->connection->addr_text,
+                     &s->connection->listening->addr_text);
+    len -= p - buf;
+    buf = p;
+
+    if (s->log_handler) {
+        p = s->log_handler(log, buf, len);
+    }
+
+    return p;
+}
diff --git a/nginx/src/stream/ngx_stream_limit_conn_module.c b/nginx/src/stream/ngx_stream_limit_conn_module.c
new file mode 100644 (file)
index 0000000..b64a426
--- /dev/null
@@ -0,0 +1,646 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+typedef struct {
+    u_char                       color;
+    u_char                       len;
+    u_short                      conn;
+    u_char                       data[1];
+} ngx_stream_limit_conn_node_t;
+
+
+typedef struct {
+    ngx_shm_zone_t              *shm_zone;
+    ngx_rbtree_node_t           *node;
+} ngx_stream_limit_conn_cleanup_t;
+
+
+typedef struct {
+    ngx_rbtree_t                *rbtree;
+    ngx_stream_complex_value_t   key;
+} ngx_stream_limit_conn_ctx_t;
+
+
+typedef struct {
+    ngx_shm_zone_t              *shm_zone;
+    ngx_uint_t                   conn;
+} ngx_stream_limit_conn_limit_t;
+
+
+typedef struct {
+    ngx_array_t                  limits;
+    ngx_uint_t                   log_level;
+} ngx_stream_limit_conn_conf_t;
+
+
+static ngx_rbtree_node_t *ngx_stream_limit_conn_lookup(ngx_rbtree_t *rbtree,
+    ngx_str_t *key, uint32_t hash);
+static void ngx_stream_limit_conn_cleanup(void *data);
+static ngx_inline void ngx_stream_limit_conn_cleanup_all(ngx_pool_t *pool);
+
+static void *ngx_stream_limit_conn_create_conf(ngx_conf_t *cf);
+static char *ngx_stream_limit_conn_merge_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static char *ngx_stream_limit_conn_zone(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_stream_limit_conn(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static ngx_int_t ngx_stream_limit_conn_init(ngx_conf_t *cf);
+
+
+static ngx_conf_enum_t  ngx_stream_limit_conn_log_levels[] = {
+    { ngx_string("info"), NGX_LOG_INFO },
+    { ngx_string("notice"), NGX_LOG_NOTICE },
+    { ngx_string("warn"), NGX_LOG_WARN },
+    { ngx_string("error"), NGX_LOG_ERR },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t  ngx_stream_limit_conn_commands[] = {
+
+    { ngx_string("limit_conn_zone"),
+      NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE2,
+      ngx_stream_limit_conn_zone,
+      0,
+      0,
+      NULL },
+
+    { ngx_string("limit_conn"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE2,
+      ngx_stream_limit_conn,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("limit_conn_log_level"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_limit_conn_conf_t, log_level),
+      &ngx_stream_limit_conn_log_levels },
+
+      ngx_null_command
+};
+
+
+static ngx_stream_module_t  ngx_stream_limit_conn_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_stream_limit_conn_init,            /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_stream_limit_conn_create_conf,     /* create server configuration */
+    ngx_stream_limit_conn_merge_conf       /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_stream_limit_conn_module = {
+    NGX_MODULE_V1,
+    &ngx_stream_limit_conn_module_ctx,     /* module context */
+    ngx_stream_limit_conn_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_limit_conn_handler(ngx_stream_session_t *s)
+{
+    size_t                            n;
+    uint32_t                          hash;
+    ngx_str_t                         key;
+    ngx_uint_t                        i;
+    ngx_slab_pool_t                  *shpool;
+    ngx_rbtree_node_t                *node;
+    ngx_pool_cleanup_t               *cln;
+    ngx_stream_limit_conn_ctx_t      *ctx;
+    ngx_stream_limit_conn_node_t     *lc;
+    ngx_stream_limit_conn_conf_t     *lccf;
+    ngx_stream_limit_conn_limit_t    *limits;
+    ngx_stream_limit_conn_cleanup_t  *lccln;
+
+    lccf = ngx_stream_get_module_srv_conf(s, ngx_stream_limit_conn_module);
+    limits = lccf->limits.elts;
+
+    for (i = 0; i < lccf->limits.nelts; i++) {
+        ctx = limits[i].shm_zone->data;
+
+        if (ngx_stream_complex_value(s, &ctx->key, &key) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        if (key.len == 0) {
+            continue;
+        }
+
+        if (key.len > 255) {
+            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                          "the value of the \"%V\" key "
+                          "is more than 255 bytes: \"%V\"",
+                          &ctx->key.value, &key);
+            continue;
+        }
+
+        hash = ngx_crc32_short(key.data, key.len);
+
+        shpool = (ngx_slab_pool_t *) limits[i].shm_zone->shm.addr;
+
+        ngx_shmtx_lock(&shpool->mutex);
+
+        node = ngx_stream_limit_conn_lookup(ctx->rbtree, &key, hash);
+
+        if (node == NULL) {
+
+            n = offsetof(ngx_rbtree_node_t, color)
+                + offsetof(ngx_stream_limit_conn_node_t, data)
+                + key.len;
+
+            node = ngx_slab_alloc_locked(shpool, n);
+
+            if (node == NULL) {
+                ngx_shmtx_unlock(&shpool->mutex);
+                ngx_stream_limit_conn_cleanup_all(s->connection->pool);
+                return NGX_STREAM_SERVICE_UNAVAILABLE;
+            }
+
+            lc = (ngx_stream_limit_conn_node_t *) &node->color;
+
+            node->key = hash;
+            lc->len = (u_char) key.len;
+            lc->conn = 1;
+            ngx_memcpy(lc->data, key.data, key.len);
+
+            ngx_rbtree_insert(ctx->rbtree, node);
+
+        } else {
+
+            lc = (ngx_stream_limit_conn_node_t *) &node->color;
+
+            if ((ngx_uint_t) lc->conn >= limits[i].conn) {
+
+                ngx_shmtx_unlock(&shpool->mutex);
+
+                ngx_log_error(lccf->log_level, s->connection->log, 0,
+                              "limiting connections by zone \"%V\"",
+                              &limits[i].shm_zone->shm.name);
+
+                ngx_stream_limit_conn_cleanup_all(s->connection->pool);
+                return NGX_STREAM_SERVICE_UNAVAILABLE;
+            }
+
+            lc->conn++;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                       "limit conn: %08Xi %d", node->key, lc->conn);
+
+        ngx_shmtx_unlock(&shpool->mutex);
+
+        cln = ngx_pool_cleanup_add(s->connection->pool,
+                                   sizeof(ngx_stream_limit_conn_cleanup_t));
+        if (cln == NULL) {
+            return NGX_ERROR;
+        }
+
+        cln->handler = ngx_stream_limit_conn_cleanup;
+        lccln = cln->data;
+
+        lccln->shm_zone = limits[i].shm_zone;
+        lccln->node = node;
+    }
+
+    return NGX_DECLINED;
+}
+
+
+static void
+ngx_stream_limit_conn_rbtree_insert_value(ngx_rbtree_node_t *temp,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
+{
+    ngx_rbtree_node_t             **p;
+    ngx_stream_limit_conn_node_t   *lcn, *lcnt;
+
+    for ( ;; ) {
+
+        if (node->key < temp->key) {
+
+            p = &temp->left;
+
+        } else if (node->key > temp->key) {
+
+            p = &temp->right;
+
+        } else { /* node->key == temp->key */
+
+            lcn = (ngx_stream_limit_conn_node_t *) &node->color;
+            lcnt = (ngx_stream_limit_conn_node_t *) &temp->color;
+
+            p = (ngx_memn2cmp(lcn->data, lcnt->data, lcn->len, lcnt->len) < 0)
+                ? &temp->left : &temp->right;
+        }
+
+        if (*p == sentinel) {
+            break;
+        }
+
+        temp = *p;
+    }
+
+    *p = node;
+    node->parent = temp;
+    node->left = sentinel;
+    node->right = sentinel;
+    ngx_rbt_red(node);
+}
+
+
+static ngx_rbtree_node_t *
+ngx_stream_limit_conn_lookup(ngx_rbtree_t *rbtree, ngx_str_t *key,
+    uint32_t hash)
+{
+    ngx_int_t                      rc;
+    ngx_rbtree_node_t             *node, *sentinel;
+    ngx_stream_limit_conn_node_t  *lcn;
+
+    node = rbtree->root;
+    sentinel = rbtree->sentinel;
+
+    while (node != sentinel) {
+
+        if (hash < node->key) {
+            node = node->left;
+            continue;
+        }
+
+        if (hash > node->key) {
+            node = node->right;
+            continue;
+        }
+
+        /* hash == node->key */
+
+        lcn = (ngx_stream_limit_conn_node_t *) &node->color;
+
+        rc = ngx_memn2cmp(key->data, lcn->data, key->len, (size_t) lcn->len);
+
+        if (rc == 0) {
+            return node;
+        }
+
+        node = (rc < 0) ? node->left : node->right;
+    }
+
+    return NULL;
+}
+
+
+static void
+ngx_stream_limit_conn_cleanup(void *data)
+{
+    ngx_stream_limit_conn_cleanup_t  *lccln = data;
+
+    ngx_slab_pool_t               *shpool;
+    ngx_rbtree_node_t             *node;
+    ngx_stream_limit_conn_ctx_t   *ctx;
+    ngx_stream_limit_conn_node_t  *lc;
+
+    ctx = lccln->shm_zone->data;
+    shpool = (ngx_slab_pool_t *) lccln->shm_zone->shm.addr;
+    node = lccln->node;
+    lc = (ngx_stream_limit_conn_node_t *) &node->color;
+
+    ngx_shmtx_lock(&shpool->mutex);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_STREAM, lccln->shm_zone->shm.log, 0,
+                   "limit conn cleanup: %08Xi %d", node->key, lc->conn);
+
+    lc->conn--;
+
+    if (lc->conn == 0) {
+        ngx_rbtree_delete(ctx->rbtree, node);
+        ngx_slab_free_locked(shpool, node);
+    }
+
+    ngx_shmtx_unlock(&shpool->mutex);
+}
+
+
+static ngx_inline void
+ngx_stream_limit_conn_cleanup_all(ngx_pool_t *pool)
+{
+    ngx_pool_cleanup_t  *cln;
+
+    cln = pool->cleanup;
+
+    while (cln && cln->handler == ngx_stream_limit_conn_cleanup) {
+        ngx_stream_limit_conn_cleanup(cln->data);
+        cln = cln->next;
+    }
+
+    pool->cleanup = cln;
+}
+
+
+static ngx_int_t
+ngx_stream_limit_conn_init_zone(ngx_shm_zone_t *shm_zone, void *data)
+{
+    ngx_stream_limit_conn_ctx_t  *octx = data;
+
+    size_t                        len;
+    ngx_slab_pool_t              *shpool;
+    ngx_rbtree_node_t            *sentinel;
+    ngx_stream_limit_conn_ctx_t  *ctx;
+
+    ctx = shm_zone->data;
+
+    if (octx) {
+        if (ctx->key.value.len != octx->key.value.len
+            || ngx_strncmp(ctx->key.value.data, octx->key.value.data,
+                           ctx->key.value.len)
+               != 0)
+        {
+            ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
+                          "limit_conn_zone \"%V\" uses the \"%V\" key "
+                          "while previously it used the \"%V\" key",
+                          &shm_zone->shm.name, &ctx->key.value,
+                          &octx->key.value);
+            return NGX_ERROR;
+        }
+
+        ctx->rbtree = octx->rbtree;
+
+        return NGX_OK;
+    }
+
+    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+    if (shm_zone->shm.exists) {
+        ctx->rbtree = shpool->data;
+
+        return NGX_OK;
+    }
+
+    ctx->rbtree = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_t));
+    if (ctx->rbtree == NULL) {
+        return NGX_ERROR;
+    }
+
+    shpool->data = ctx->rbtree;
+
+    sentinel = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_node_t));
+    if (sentinel == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_rbtree_init(ctx->rbtree, sentinel,
+                    ngx_stream_limit_conn_rbtree_insert_value);
+
+    len = sizeof(" in limit_conn_zone \"\"") + shm_zone->shm.name.len;
+
+    shpool->log_ctx = ngx_slab_alloc(shpool, len);
+    if (shpool->log_ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_sprintf(shpool->log_ctx, " in limit_conn_zone \"%V\"%Z",
+                &shm_zone->shm.name);
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_stream_limit_conn_create_conf(ngx_conf_t *cf)
+{
+    ngx_stream_limit_conn_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_limit_conn_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->limits.elts = NULL;
+     */
+
+    conf->log_level = NGX_CONF_UNSET_UINT;
+
+    return conf;
+}
+
+
+static char *
+ngx_stream_limit_conn_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_stream_limit_conn_conf_t *prev = parent;
+    ngx_stream_limit_conn_conf_t *conf = child;
+
+    if (conf->limits.elts == NULL) {
+        conf->limits = prev->limits;
+    }
+
+    ngx_conf_merge_uint_value(conf->log_level, prev->log_level, NGX_LOG_ERR);
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_stream_limit_conn_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    u_char                              *p;
+    ssize_t                              size;
+    ngx_str_t                           *value, name, s;
+    ngx_uint_t                           i;
+    ngx_shm_zone_t                      *shm_zone;
+    ngx_stream_limit_conn_ctx_t         *ctx;
+    ngx_stream_compile_complex_value_t   ccv;
+
+    value = cf->args->elts;
+
+    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_stream_limit_conn_ctx_t));
+    if (ctx == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[1];
+    ccv.complex_value = &ctx->key;
+
+    if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    size = 0;
+    name.len = 0;
+
+    for (i = 2; i < cf->args->nelts; i++) {
+
+        if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
+
+            name.data = value[i].data + 5;
+
+            p = (u_char *) ngx_strchr(name.data, ':');
+
+            if (p == NULL) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid zone size \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            name.len = p - name.data;
+
+            s.data = p + 1;
+            s.len = value[i].data + value[i].len - s.data;
+
+            size = ngx_parse_size(&s);
+
+            if (size == NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid zone size \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            if (size < (ssize_t) (8 * ngx_pagesize)) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "zone \"%V\" is too small", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[i]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (name.len == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"%V\" must have \"zone\" parameter",
+                           &cmd->name);
+        return NGX_CONF_ERROR;
+    }
+
+    shm_zone = ngx_shared_memory_add(cf, &name, size,
+                                     &ngx_stream_limit_conn_module);
+    if (shm_zone == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (shm_zone->data) {
+        ctx = shm_zone->data;
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "%V \"%V\" is already bound to key \"%V\"",
+                           &cmd->name, &name, &ctx->key.value);
+        return NGX_CONF_ERROR;
+    }
+
+    shm_zone->init = ngx_stream_limit_conn_init_zone;
+    shm_zone->data = ctx;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_stream_limit_conn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_shm_zone_t                 *shm_zone;
+    ngx_stream_limit_conn_conf_t   *lccf = conf;
+    ngx_stream_limit_conn_limit_t  *limit, *limits;
+
+    ngx_str_t   *value;
+    ngx_int_t    n;
+    ngx_uint_t   i;
+
+    value = cf->args->elts;
+
+    shm_zone = ngx_shared_memory_add(cf, &value[1], 0,
+                                     &ngx_stream_limit_conn_module);
+    if (shm_zone == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    limits = lccf->limits.elts;
+
+    if (limits == NULL) {
+        if (ngx_array_init(&lccf->limits, cf->pool, 1,
+                           sizeof(ngx_stream_limit_conn_limit_t))
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    for (i = 0; i < lccf->limits.nelts; i++) {
+        if (shm_zone == limits[i].shm_zone) {
+            return "is duplicate";
+        }
+    }
+
+    n = ngx_atoi(value[2].data, value[2].len);
+    if (n <= 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid number of connections \"%V\"", &value[2]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (n > 65535) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "connection limit must be less 65536");
+        return NGX_CONF_ERROR;
+    }
+
+    limit = ngx_array_push(&lccf->limits);
+    if (limit == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    limit->conn = n;
+    limit->shm_zone = shm_zone;
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_limit_conn_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_limit_conn_handler;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/stream/ngx_stream_log_module.c b/nginx/src/stream/ngx_stream_log_module.c
new file mode 100644 (file)
index 0000000..0ff7f42
--- /dev/null
@@ -0,0 +1,1596 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+#if (NGX_ZLIB)
+#include <zlib.h>
+#endif
+
+
+typedef struct ngx_stream_log_op_s  ngx_stream_log_op_t;
+
+typedef u_char *(*ngx_stream_log_op_run_pt) (ngx_stream_session_t *s,
+    u_char *buf, ngx_stream_log_op_t *op);
+
+typedef size_t (*ngx_stream_log_op_getlen_pt) (ngx_stream_session_t *s,
+    uintptr_t data);
+
+
+struct ngx_stream_log_op_s {
+    size_t                       len;
+    ngx_stream_log_op_getlen_pt  getlen;
+    ngx_stream_log_op_run_pt     run;
+    uintptr_t                    data;
+};
+
+
+typedef struct {
+    ngx_str_t                    name;
+    ngx_array_t                 *flushes;
+    ngx_array_t                 *ops;        /* array of ngx_stream_log_op_t */
+} ngx_stream_log_fmt_t;
+
+
+typedef struct {
+    ngx_array_t                  formats;    /* array of ngx_stream_log_fmt_t */
+} ngx_stream_log_main_conf_t;
+
+
+typedef struct {
+    u_char                      *start;
+    u_char                      *pos;
+    u_char                      *last;
+
+    ngx_event_t                 *event;
+    ngx_msec_t                   flush;
+    ngx_int_t                    gzip;
+} ngx_stream_log_buf_t;
+
+
+typedef struct {
+    ngx_array_t                 *lengths;
+    ngx_array_t                 *values;
+} ngx_stream_log_script_t;
+
+
+typedef struct {
+    ngx_open_file_t             *file;
+    ngx_stream_log_script_t     *script;
+    time_t                       disk_full_time;
+    time_t                       error_log_time;
+    ngx_syslog_peer_t           *syslog_peer;
+    ngx_stream_log_fmt_t        *format;
+    ngx_stream_complex_value_t  *filter;
+} ngx_stream_log_t;
+
+
+typedef struct {
+    ngx_array_t                 *logs;       /* array of ngx_stream_log_t */
+
+    ngx_open_file_cache_t       *open_file_cache;
+    time_t                       open_file_cache_valid;
+    ngx_uint_t                   open_file_cache_min_uses;
+
+    ngx_uint_t                   off;        /* unsigned  off:1 */
+} ngx_stream_log_srv_conf_t;
+
+
+typedef struct {
+    ngx_str_t                    name;
+    size_t                       len;
+    ngx_stream_log_op_run_pt     run;
+} ngx_stream_log_var_t;
+
+
+#define NGX_STREAM_LOG_ESCAPE_DEFAULT  0
+#define NGX_STREAM_LOG_ESCAPE_JSON     1
+#define NGX_STREAM_LOG_ESCAPE_NONE     2
+
+
+static void ngx_stream_log_write(ngx_stream_session_t *s, ngx_stream_log_t *log,
+    u_char *buf, size_t len);
+static ssize_t ngx_stream_log_script_write(ngx_stream_session_t *s,
+    ngx_stream_log_script_t *script, u_char **name, u_char *buf, size_t len);
+
+#if (NGX_ZLIB)
+static ssize_t ngx_stream_log_gzip(ngx_fd_t fd, u_char *buf, size_t len,
+    ngx_int_t level, ngx_log_t *log);
+
+static void *ngx_stream_log_gzip_alloc(void *opaque, u_int items, u_int size);
+static void ngx_stream_log_gzip_free(void *opaque, void *address);
+#endif
+
+static void ngx_stream_log_flush(ngx_open_file_t *file, ngx_log_t *log);
+static void ngx_stream_log_flush_handler(ngx_event_t *ev);
+
+static ngx_int_t ngx_stream_log_variable_compile(ngx_conf_t *cf,
+    ngx_stream_log_op_t *op, ngx_str_t *value, ngx_uint_t escape);
+static size_t ngx_stream_log_variable_getlen(ngx_stream_session_t *s,
+    uintptr_t data);
+static u_char *ngx_stream_log_variable(ngx_stream_session_t *s, u_char *buf,
+    ngx_stream_log_op_t *op);
+static uintptr_t ngx_stream_log_escape(u_char *dst, u_char *src, size_t size);
+static size_t ngx_stream_log_json_variable_getlen(ngx_stream_session_t *s,
+    uintptr_t data);
+static u_char *ngx_stream_log_json_variable(ngx_stream_session_t *s,
+    u_char *buf, ngx_stream_log_op_t *op);
+static size_t ngx_stream_log_unescaped_variable_getlen(ngx_stream_session_t *s,
+    uintptr_t data);
+static u_char *ngx_stream_log_unescaped_variable(ngx_stream_session_t *s,
+    u_char *buf, ngx_stream_log_op_t *op);
+
+
+static void *ngx_stream_log_create_main_conf(ngx_conf_t *cf);
+static void *ngx_stream_log_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_stream_log_merge_srv_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static char *ngx_stream_log_set_log(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_stream_log_set_format(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_stream_log_compile_format(ngx_conf_t *cf,
+    ngx_array_t *flushes, ngx_array_t *ops, ngx_array_t *args, ngx_uint_t s);
+static char *ngx_stream_log_open_file_cache(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static ngx_int_t ngx_stream_log_init(ngx_conf_t *cf);
+
+
+static ngx_command_t  ngx_stream_log_commands[] = {
+
+    { ngx_string("log_format"),
+      NGX_STREAM_MAIN_CONF|NGX_CONF_2MORE,
+      ngx_stream_log_set_format,
+      NGX_STREAM_MAIN_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("access_log"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE,
+      ngx_stream_log_set_log,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("open_log_file_cache"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1234,
+      ngx_stream_log_open_file_cache,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_stream_module_t  ngx_stream_log_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_stream_log_init,                   /* postconfiguration */
+
+    ngx_stream_log_create_main_conf,       /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_stream_log_create_srv_conf,        /* create server configuration */
+    ngx_stream_log_merge_srv_conf          /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_stream_log_module = {
+    NGX_MODULE_V1,
+    &ngx_stream_log_module_ctx,            /* module context */
+    ngx_stream_log_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_log_handler(ngx_stream_session_t *s)
+{
+    u_char                     *line, *p;
+    size_t                      len, size;
+    ssize_t                     n;
+    ngx_str_t                   val;
+    ngx_uint_t                  i, l;
+    ngx_stream_log_t           *log;
+    ngx_stream_log_op_t        *op;
+    ngx_stream_log_buf_t       *buffer;
+    ngx_stream_log_srv_conf_t  *lscf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                   "stream log handler");
+
+    lscf = ngx_stream_get_module_srv_conf(s, ngx_stream_log_module);
+
+    if (lscf->off || lscf->logs == NULL) {
+        return NGX_OK;
+    }
+
+    log = lscf->logs->elts;
+    for (l = 0; l < lscf->logs->nelts; l++) {
+
+        if (log[l].filter) {
+            if (ngx_stream_complex_value(s, log[l].filter, &val) != NGX_OK) {
+                return NGX_ERROR;
+            }
+
+            if (val.len == 0 || (val.len == 1 && val.data[0] == '0')) {
+                continue;
+            }
+        }
+
+        if (ngx_time() == log[l].disk_full_time) {
+
+            /*
+             * on FreeBSD writing to a full filesystem with enabled softupdates
+             * may block process for much longer time than writing to non-full
+             * filesystem, so we skip writing to a log for one second
+             */
+
+            continue;
+        }
+
+        ngx_stream_script_flush_no_cacheable_variables(s,
+                                                       log[l].format->flushes);
+
+        len = 0;
+        op = log[l].format->ops->elts;
+        for (i = 0; i < log[l].format->ops->nelts; i++) {
+            if (op[i].len == 0) {
+                len += op[i].getlen(s, op[i].data);
+
+            } else {
+                len += op[i].len;
+            }
+        }
+
+        if (log[l].syslog_peer) {
+
+            /* length of syslog's PRI and HEADER message parts */
+            len += sizeof("<255>Jan 01 00:00:00 ") - 1
+                   + ngx_cycle->hostname.len + 1
+                   + log[l].syslog_peer->tag.len + 2;
+
+            goto alloc_line;
+        }
+
+        len += NGX_LINEFEED_SIZE;
+
+        buffer = log[l].file ? log[l].file->data : NULL;
+
+        if (buffer) {
+
+            if (len > (size_t) (buffer->last - buffer->pos)) {
+
+                ngx_stream_log_write(s, &log[l], buffer->start,
+                                     buffer->pos - buffer->start);
+
+                buffer->pos = buffer->start;
+            }
+
+            if (len <= (size_t) (buffer->last - buffer->pos)) {
+
+                p = buffer->pos;
+
+                if (buffer->event && p == buffer->start) {
+                    ngx_add_timer(buffer->event, buffer->flush);
+                }
+
+                for (i = 0; i < log[l].format->ops->nelts; i++) {
+                    p = op[i].run(s, p, &op[i]);
+                }
+
+                ngx_linefeed(p);
+
+                buffer->pos = p;
+
+                continue;
+            }
+
+            if (buffer->event && buffer->event->timer_set) {
+                ngx_del_timer(buffer->event);
+            }
+        }
+
+    alloc_line:
+
+        line = ngx_pnalloc(s->connection->pool, len);
+        if (line == NULL) {
+            return NGX_ERROR;
+        }
+
+        p = line;
+
+        if (log[l].syslog_peer) {
+            p = ngx_syslog_add_header(log[l].syslog_peer, line);
+        }
+
+        for (i = 0; i < log[l].format->ops->nelts; i++) {
+            p = op[i].run(s, p, &op[i]);
+        }
+
+        if (log[l].syslog_peer) {
+
+            size = p - line;
+
+            n = ngx_syslog_send(log[l].syslog_peer, line, size);
+
+            if (n < 0) {
+                ngx_log_error(NGX_LOG_WARN, s->connection->log, 0,
+                              "send() to syslog failed");
+
+            } else if ((size_t) n != size) {
+                ngx_log_error(NGX_LOG_WARN, s->connection->log, 0,
+                              "send() to syslog has written only %z of %uz",
+                              n, size);
+            }
+
+            continue;
+        }
+
+        ngx_linefeed(p);
+
+        ngx_stream_log_write(s, &log[l], line, p - line);
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_stream_log_write(ngx_stream_session_t *s, ngx_stream_log_t *log,
+    u_char *buf, size_t len)
+{
+    u_char                *name;
+    time_t                 now;
+    ssize_t                n;
+    ngx_err_t              err;
+#if (NGX_ZLIB)
+    ngx_stream_log_buf_t  *buffer;
+#endif
+
+    if (log->script == NULL) {
+        name = log->file->name.data;
+
+#if (NGX_ZLIB)
+        buffer = log->file->data;
+
+        if (buffer && buffer->gzip) {
+            n = ngx_stream_log_gzip(log->file->fd, buf, len, buffer->gzip,
+                                    s->connection->log);
+        } else {
+            n = ngx_write_fd(log->file->fd, buf, len);
+        }
+#else
+        n = ngx_write_fd(log->file->fd, buf, len);
+#endif
+
+    } else {
+        name = NULL;
+        n = ngx_stream_log_script_write(s, log->script, &name, buf, len);
+    }
+
+    if (n == (ssize_t) len) {
+        return;
+    }
+
+    now = ngx_time();
+
+    if (n == -1) {
+        err = ngx_errno;
+
+        if (err == NGX_ENOSPC) {
+            log->disk_full_time = now;
+        }
+
+        if (now - log->error_log_time > 59) {
+            ngx_log_error(NGX_LOG_ALERT, s->connection->log, err,
+                          ngx_write_fd_n " to \"%s\" failed", name);
+
+            log->error_log_time = now;
+        }
+
+        return;
+    }
+
+    if (now - log->error_log_time > 59) {
+        ngx_log_error(NGX_LOG_ALERT, s->connection->log, 0,
+                      ngx_write_fd_n " to \"%s\" was incomplete: %z of %uz",
+                      name, n, len);
+
+        log->error_log_time = now;
+    }
+}
+
+
+static ssize_t
+ngx_stream_log_script_write(ngx_stream_session_t *s,
+    ngx_stream_log_script_t *script, u_char **name, u_char *buf, size_t len)
+{
+    ssize_t                     n;
+    ngx_str_t                   log;
+    ngx_open_file_info_t        of;
+    ngx_stream_log_srv_conf_t  *lscf;
+
+    if (ngx_stream_script_run(s, &log, script->lengths->elts, 1,
+                              script->values->elts)
+        == NULL)
+    {
+        /* simulate successful logging */
+        return len;
+    }
+
+    log.data[log.len - 1] = '\0';
+    *name = log.data;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                   "stream log \"%s\"", log.data);
+
+    lscf = ngx_stream_get_module_srv_conf(s, ngx_stream_log_module);
+
+    ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+    of.log = 1;
+    of.valid = lscf->open_file_cache_valid;
+    of.min_uses = lscf->open_file_cache_min_uses;
+    of.directio = NGX_OPEN_FILE_DIRECTIO_OFF;
+
+    if (ngx_open_cached_file(lscf->open_file_cache, &log, &of,
+                             s->connection->pool)
+        != NGX_OK)
+    {
+        if (of.err == 0) {
+            /* simulate successful logging */
+            return len;
+        }
+
+        ngx_log_error(NGX_LOG_CRIT, s->connection->log, ngx_errno,
+                      "%s \"%s\" failed", of.failed, log.data);
+        /* simulate successful logging */
+        return len;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                   "stream log #%d", of.fd);
+
+    n = ngx_write_fd(of.fd, buf, len);
+
+    return n;
+}
+
+
+#if (NGX_ZLIB)
+
+static ssize_t
+ngx_stream_log_gzip(ngx_fd_t fd, u_char *buf, size_t len, ngx_int_t level,
+    ngx_log_t *log)
+{
+    int          rc, wbits, memlevel;
+    u_char      *out;
+    size_t       size;
+    ssize_t      n;
+    z_stream     zstream;
+    ngx_err_t    err;
+    ngx_pool_t  *pool;
+
+    wbits = MAX_WBITS;
+    memlevel = MAX_MEM_LEVEL - 1;
+
+    while ((ssize_t) len < ((1 << (wbits - 1)) - 262)) {
+        wbits--;
+        memlevel--;
+    }
+
+    /*
+     * This is a formula from deflateBound() for conservative upper bound of
+     * compressed data plus 18 bytes of gzip wrapper.
+     */
+
+    size = len + ((len + 7) >> 3) + ((len + 63) >> 6) + 5 + 18;
+
+    ngx_memzero(&zstream, sizeof(z_stream));
+
+    pool = ngx_create_pool(256, log);
+    if (pool == NULL) {
+        /* simulate successful logging */
+        return len;
+    }
+
+    pool->log = log;
+
+    zstream.zalloc = ngx_stream_log_gzip_alloc;
+    zstream.zfree = ngx_stream_log_gzip_free;
+    zstream.opaque = pool;
+
+    out = ngx_pnalloc(pool, size);
+    if (out == NULL) {
+        goto done;
+    }
+
+    zstream.next_in = buf;
+    zstream.avail_in = len;
+    zstream.next_out = out;
+    zstream.avail_out = size;
+
+    rc = deflateInit2(&zstream, (int) level, Z_DEFLATED, wbits + 16, memlevel,
+                      Z_DEFAULT_STRATEGY);
+
+    if (rc != Z_OK) {
+        ngx_log_error(NGX_LOG_ALERT, log, 0, "deflateInit2() failed: %d", rc);
+        goto done;
+    }
+
+    ngx_log_debug4(NGX_LOG_DEBUG_STREAM, log, 0,
+                   "deflate in: ni:%p no:%p ai:%ud ao:%ud",
+                   zstream.next_in, zstream.next_out,
+                   zstream.avail_in, zstream.avail_out);
+
+    rc = deflate(&zstream, Z_FINISH);
+
+    if (rc != Z_STREAM_END) {
+        ngx_log_error(NGX_LOG_ALERT, log, 0,
+                      "deflate(Z_FINISH) failed: %d", rc);
+        goto done;
+    }
+
+    ngx_log_debug5(NGX_LOG_DEBUG_STREAM, log, 0,
+                   "deflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d",
+                   zstream.next_in, zstream.next_out,
+                   zstream.avail_in, zstream.avail_out,
+                   rc);
+
+    size -= zstream.avail_out;
+
+    rc = deflateEnd(&zstream);
+
+    if (rc != Z_OK) {
+        ngx_log_error(NGX_LOG_ALERT, log, 0, "deflateEnd() failed: %d", rc);
+        goto done;
+    }
+
+    n = ngx_write_fd(fd, out, size);
+
+    if (n != (ssize_t) size) {
+        err = (n == -1) ? ngx_errno : 0;
+
+        ngx_destroy_pool(pool);
+
+        ngx_set_errno(err);
+        return -1;
+    }
+
+done:
+
+    ngx_destroy_pool(pool);
+
+    /* simulate successful logging */
+    return len;
+}
+
+
+static void *
+ngx_stream_log_gzip_alloc(void *opaque, u_int items, u_int size)
+{
+    ngx_pool_t *pool = opaque;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_STREAM, pool->log, 0,
+                   "gzip alloc: n:%ud s:%ud", items, size);
+
+    return ngx_palloc(pool, items * size);
+}
+
+
+static void
+ngx_stream_log_gzip_free(void *opaque, void *address)
+{
+#if 0
+    ngx_pool_t *pool = opaque;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pool->log, 0,
+                   "gzip free: %p", address);
+#endif
+}
+
+#endif
+
+
+static void
+ngx_stream_log_flush(ngx_open_file_t *file, ngx_log_t *log)
+{
+    size_t                 len;
+    ssize_t                n;
+    ngx_stream_log_buf_t  *buffer;
+
+    buffer = file->data;
+
+    len = buffer->pos - buffer->start;
+
+    if (len == 0) {
+        return;
+    }
+
+#if (NGX_ZLIB)
+    if (buffer->gzip) {
+        n = ngx_stream_log_gzip(file->fd, buffer->start, len, buffer->gzip,
+                                log);
+    } else {
+        n = ngx_write_fd(file->fd, buffer->start, len);
+    }
+#else
+    n = ngx_write_fd(file->fd, buffer->start, len);
+#endif
+
+    if (n == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      ngx_write_fd_n " to \"%s\" failed",
+                      file->name.data);
+
+    } else if ((size_t) n != len) {
+        ngx_log_error(NGX_LOG_ALERT, log, 0,
+                      ngx_write_fd_n " to \"%s\" was incomplete: %z of %uz",
+                      file->name.data, n, len);
+    }
+
+    buffer->pos = buffer->start;
+
+    if (buffer->event && buffer->event->timer_set) {
+        ngx_del_timer(buffer->event);
+    }
+}
+
+
+static void
+ngx_stream_log_flush_handler(ngx_event_t *ev)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "stream log buffer flush handler");
+
+    ngx_stream_log_flush(ev->data, ev->log);
+}
+
+
+static u_char *
+ngx_stream_log_copy_short(ngx_stream_session_t *s, u_char *buf,
+    ngx_stream_log_op_t *op)
+{
+    size_t     len;
+    uintptr_t  data;
+
+    len = op->len;
+    data = op->data;
+
+    while (len--) {
+        *buf++ = (u_char) (data & 0xff);
+        data >>= 8;
+    }
+
+    return buf;
+}
+
+
+static u_char *
+ngx_stream_log_copy_long(ngx_stream_session_t *s, u_char *buf,
+    ngx_stream_log_op_t *op)
+{
+    return ngx_cpymem(buf, (u_char *) op->data, op->len);
+}
+
+
+static ngx_int_t
+ngx_stream_log_variable_compile(ngx_conf_t *cf, ngx_stream_log_op_t *op,
+    ngx_str_t *value, ngx_uint_t escape)
+{
+    ngx_int_t  index;
+
+    index = ngx_stream_get_variable_index(cf, value);
+    if (index == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    op->len = 0;
+
+    switch (escape) {
+    case NGX_STREAM_LOG_ESCAPE_JSON:
+        op->getlen = ngx_stream_log_json_variable_getlen;
+        op->run = ngx_stream_log_json_variable;
+        break;
+
+    case NGX_STREAM_LOG_ESCAPE_NONE:
+        op->getlen = ngx_stream_log_unescaped_variable_getlen;
+        op->run = ngx_stream_log_unescaped_variable;
+        break;
+
+    default: /* NGX_STREAM_LOG_ESCAPE_DEFAULT */
+        op->getlen = ngx_stream_log_variable_getlen;
+        op->run = ngx_stream_log_variable;
+    }
+
+    op->data = index;
+
+    return NGX_OK;
+}
+
+
+static size_t
+ngx_stream_log_variable_getlen(ngx_stream_session_t *s, uintptr_t data)
+{
+    uintptr_t                     len;
+    ngx_stream_variable_value_t  *value;
+
+    value = ngx_stream_get_indexed_variable(s, data);
+
+    if (value == NULL || value->not_found) {
+        return 1;
+    }
+
+    len = ngx_stream_log_escape(NULL, value->data, value->len);
+
+    value->escape = len ? 1 : 0;
+
+    return value->len + len * 3;
+}
+
+
+static u_char *
+ngx_stream_log_variable(ngx_stream_session_t *s, u_char *buf,
+    ngx_stream_log_op_t *op)
+{
+    ngx_stream_variable_value_t  *value;
+
+    value = ngx_stream_get_indexed_variable(s, op->data);
+
+    if (value == NULL || value->not_found) {
+        *buf = '-';
+        return buf + 1;
+    }
+
+    if (value->escape == 0) {
+        return ngx_cpymem(buf, value->data, value->len);
+
+    } else {
+        return (u_char *) ngx_stream_log_escape(buf, value->data, value->len);
+    }
+}
+
+
+static uintptr_t
+ngx_stream_log_escape(u_char *dst, u_char *src, size_t size)
+{
+    ngx_uint_t      n;
+    static u_char   hex[] = "0123456789ABCDEF";
+
+    static uint32_t   escape[] = {
+        0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
+
+                    /* ?>=< ;:98 7654 3210  /.-, +*)( '&%$ #"!  */
+        0x00000004, /* 0000 0000 0000 0000  0000 0000 0000 0100 */
+
+                    /* _^]\ [ZYX WVUT SRQP  ONML KJIH GFED CBA@ */
+        0x10000000, /* 0001 0000 0000 0000  0000 0000 0000 0000 */
+
+                    /*  ~}| {zyx wvut srqp  onml kjih gfed cba` */
+        0x80000000, /* 1000 0000 0000 0000  0000 0000 0000 0000 */
+
+        0xffffffff, /* 1111 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 */
+        0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
+    };
+
+
+    if (dst == NULL) {
+
+        /* find the number of the characters to be escaped */
+
+        n = 0;
+
+        while (size) {
+            if (escape[*src >> 5] & (1U << (*src & 0x1f))) {
+                n++;
+            }
+            src++;
+            size--;
+        }
+
+        return (uintptr_t) n;
+    }
+
+    while (size) {
+        if (escape[*src >> 5] & (1U << (*src & 0x1f))) {
+            *dst++ = '\\';
+            *dst++ = 'x';
+            *dst++ = hex[*src >> 4];
+            *dst++ = hex[*src & 0xf];
+            src++;
+
+        } else {
+            *dst++ = *src++;
+        }
+        size--;
+    }
+
+    return (uintptr_t) dst;
+}
+
+
+static size_t
+ngx_stream_log_json_variable_getlen(ngx_stream_session_t *s, uintptr_t data)
+{
+    uintptr_t                     len;
+    ngx_stream_variable_value_t  *value;
+
+    value = ngx_stream_get_indexed_variable(s, data);
+
+    if (value == NULL || value->not_found) {
+        return 0;
+    }
+
+    len = ngx_escape_json(NULL, value->data, value->len);
+
+    value->escape = len ? 1 : 0;
+
+    return value->len + len;
+}
+
+
+static u_char *
+ngx_stream_log_json_variable(ngx_stream_session_t *s, u_char *buf,
+    ngx_stream_log_op_t *op)
+{
+    ngx_stream_variable_value_t  *value;
+
+    value = ngx_stream_get_indexed_variable(s, op->data);
+
+    if (value == NULL || value->not_found) {
+        return buf;
+    }
+
+    if (value->escape == 0) {
+        return ngx_cpymem(buf, value->data, value->len);
+
+    } else {
+        return (u_char *) ngx_escape_json(buf, value->data, value->len);
+    }
+}
+
+
+static size_t
+ngx_stream_log_unescaped_variable_getlen(ngx_stream_session_t *s,
+    uintptr_t data)
+{
+    ngx_stream_variable_value_t  *value;
+
+    value = ngx_stream_get_indexed_variable(s, data);
+
+    if (value == NULL || value->not_found) {
+        return 0;
+    }
+
+    value->escape = 0;
+
+    return value->len;
+}
+
+
+static u_char *
+ngx_stream_log_unescaped_variable(ngx_stream_session_t *s, u_char *buf,
+                                  ngx_stream_log_op_t *op)
+{
+    ngx_stream_variable_value_t  *value;
+
+    value = ngx_stream_get_indexed_variable(s, op->data);
+
+    if (value == NULL || value->not_found) {
+        return buf;
+    }
+
+    return ngx_cpymem(buf, value->data, value->len);
+}
+
+
+static void *
+ngx_stream_log_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_stream_log_main_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_log_main_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    if (ngx_array_init(&conf->formats, cf->pool, 4,
+                       sizeof(ngx_stream_log_fmt_t))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    return conf;
+}
+
+
+static void *
+ngx_stream_log_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_stream_log_srv_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_log_srv_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    conf->open_file_cache = NGX_CONF_UNSET_PTR;
+
+    return conf;
+}
+
+
+static char *
+ngx_stream_log_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_stream_log_srv_conf_t *prev = parent;
+    ngx_stream_log_srv_conf_t *conf = child;
+
+    if (conf->open_file_cache == NGX_CONF_UNSET_PTR) {
+
+        conf->open_file_cache = prev->open_file_cache;
+        conf->open_file_cache_valid = prev->open_file_cache_valid;
+        conf->open_file_cache_min_uses = prev->open_file_cache_min_uses;
+
+        if (conf->open_file_cache == NGX_CONF_UNSET_PTR) {
+            conf->open_file_cache = NULL;
+        }
+    }
+
+    if (conf->logs || conf->off) {
+        return NGX_CONF_OK;
+    }
+
+    conf->logs = prev->logs;
+    conf->off = prev->off;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_stream_log_set_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_log_srv_conf_t *lscf = conf;
+
+    ssize_t                              size;
+    ngx_int_t                            gzip;
+    ngx_uint_t                           i, n;
+    ngx_msec_t                           flush;
+    ngx_str_t                           *value, name, s;
+    ngx_stream_log_t                    *log;
+    ngx_syslog_peer_t                   *peer;
+    ngx_stream_log_buf_t                *buffer;
+    ngx_stream_log_fmt_t                *fmt;
+    ngx_stream_script_compile_t          sc;
+    ngx_stream_log_main_conf_t          *lmcf;
+    ngx_stream_compile_complex_value_t   ccv;
+
+    value = cf->args->elts;
+
+    if (ngx_strcmp(value[1].data, "off") == 0) {
+        lscf->off = 1;
+        if (cf->args->nelts == 2) {
+            return NGX_CONF_OK;
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[2]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (lscf->logs == NULL) {
+        lscf->logs = ngx_array_create(cf->pool, 2, sizeof(ngx_stream_log_t));
+        if (lscf->logs == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    lmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_log_module);
+
+    log = ngx_array_push(lscf->logs);
+    if (log == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memzero(log, sizeof(ngx_stream_log_t));
+
+
+    if (ngx_strncmp(value[1].data, "syslog:", 7) == 0) {
+
+        peer = ngx_pcalloc(cf->pool, sizeof(ngx_syslog_peer_t));
+        if (peer == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_syslog_process_conf(cf, peer) != NGX_CONF_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+        log->syslog_peer = peer;
+
+        goto process_formats;
+    }
+
+    n = ngx_stream_script_variables_count(&value[1]);
+
+    if (n == 0) {
+        log->file = ngx_conf_open_file(cf->cycle, &value[1]);
+        if (log->file == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+    } else {
+        if (ngx_conf_full_name(cf->cycle, &value[1], 0) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+        log->script = ngx_pcalloc(cf->pool, sizeof(ngx_stream_log_script_t));
+        if (log->script == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        ngx_memzero(&sc, sizeof(ngx_stream_script_compile_t));
+
+        sc.cf = cf;
+        sc.source = &value[1];
+        sc.lengths = &log->script->lengths;
+        sc.values = &log->script->values;
+        sc.variables = n;
+        sc.complete_lengths = 1;
+        sc.complete_values = 1;
+
+        if (ngx_stream_script_compile(&sc) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+process_formats:
+
+    if (cf->args->nelts >= 3) {
+        name = value[2];
+
+    } else {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "log format is not specified");
+        return NGX_CONF_ERROR;
+    }
+
+    fmt = lmcf->formats.elts;
+    for (i = 0; i < lmcf->formats.nelts; i++) {
+        if (fmt[i].name.len == name.len
+            && ngx_strcasecmp(fmt[i].name.data, name.data) == 0)
+        {
+            log->format = &fmt[i];
+            break;
+        }
+    }
+
+    if (log->format == NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "unknown log format \"%V\"", &name);
+        return NGX_CONF_ERROR;
+    }
+
+    size = 0;
+    flush = 0;
+    gzip = 0;
+
+    for (i = 3; i < cf->args->nelts; i++) {
+
+        if (ngx_strncmp(value[i].data, "buffer=", 7) == 0) {
+            s.len = value[i].len - 7;
+            s.data = value[i].data + 7;
+
+            size = ngx_parse_size(&s);
+
+            if (size == NGX_ERROR || size == 0) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid buffer size \"%V\"", &s);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "flush=", 6) == 0) {
+            s.len = value[i].len - 6;
+            s.data = value[i].data + 6;
+
+            flush = ngx_parse_time(&s, 0);
+
+            if (flush == (ngx_msec_t) NGX_ERROR || flush == 0) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid flush time \"%V\"", &s);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "gzip", 4) == 0
+            && (value[i].len == 4 || value[i].data[4] == '='))
+        {
+#if (NGX_ZLIB)
+            if (size == 0) {
+                size = 64 * 1024;
+            }
+
+            if (value[i].len == 4) {
+                gzip = Z_BEST_SPEED;
+                continue;
+            }
+
+            s.len = value[i].len - 5;
+            s.data = value[i].data + 5;
+
+            gzip = ngx_atoi(s.data, s.len);
+
+            if (gzip < 1 || gzip > 9) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid compression level \"%V\"", &s);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "nginx was built without zlib support");
+            return NGX_CONF_ERROR;
+#endif
+        }
+
+        if (ngx_strncmp(value[i].data, "if=", 3) == 0) {
+            s.len = value[i].len - 3;
+            s.data = value[i].data + 3;
+
+            ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t));
+
+            ccv.cf = cf;
+            ccv.value = &s;
+            ccv.complex_value = ngx_palloc(cf->pool,
+                                           sizeof(ngx_stream_complex_value_t));
+            if (ccv.complex_value == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) {
+                return NGX_CONF_ERROR;
+            }
+
+            log->filter = ccv.complex_value;
+
+            continue;
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[i]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (flush && size == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "no buffer is defined for access_log \"%V\"",
+                           &value[1]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (size) {
+
+        if (log->script) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "buffered logs cannot have variables in name");
+            return NGX_CONF_ERROR;
+        }
+
+        if (log->syslog_peer) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "logs to syslog cannot be buffered");
+            return NGX_CONF_ERROR;
+        }
+
+        if (log->file->data) {
+            buffer = log->file->data;
+
+            if (buffer->last - buffer->start != size
+                || buffer->flush != flush
+                || buffer->gzip != gzip)
+            {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "access_log \"%V\" already defined "
+                                   "with conflicting parameters",
+                                   &value[1]);
+                return NGX_CONF_ERROR;
+            }
+
+            return NGX_CONF_OK;
+        }
+
+        buffer = ngx_pcalloc(cf->pool, sizeof(ngx_stream_log_buf_t));
+        if (buffer == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        buffer->start = ngx_pnalloc(cf->pool, size);
+        if (buffer->start == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        buffer->pos = buffer->start;
+        buffer->last = buffer->start + size;
+
+        if (flush) {
+            buffer->event = ngx_pcalloc(cf->pool, sizeof(ngx_event_t));
+            if (buffer->event == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            buffer->event->data = log->file;
+            buffer->event->handler = ngx_stream_log_flush_handler;
+            buffer->event->log = &cf->cycle->new_log;
+            buffer->event->cancelable = 1;
+
+            buffer->flush = flush;
+        }
+
+        buffer->gzip = gzip;
+
+        log->file->flush = ngx_stream_log_flush;
+        log->file->data = buffer;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_stream_log_set_format(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_log_main_conf_t *lmcf = conf;
+
+    ngx_str_t             *value;
+    ngx_uint_t             i;
+    ngx_stream_log_fmt_t  *fmt;
+
+    value = cf->args->elts;
+
+    fmt = lmcf->formats.elts;
+    for (i = 0; i < lmcf->formats.nelts; i++) {
+        if (fmt[i].name.len == value[1].len
+            && ngx_strcmp(fmt[i].name.data, value[1].data) == 0)
+        {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "duplicate \"log_format\" name \"%V\"",
+                               &value[1]);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    fmt = ngx_array_push(&lmcf->formats);
+    if (fmt == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    fmt->name = value[1];
+
+    fmt->flushes = ngx_array_create(cf->pool, 4, sizeof(ngx_int_t));
+    if (fmt->flushes == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    fmt->ops = ngx_array_create(cf->pool, 16, sizeof(ngx_stream_log_op_t));
+    if (fmt->ops == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    return ngx_stream_log_compile_format(cf, fmt->flushes, fmt->ops,
+                                         cf->args, 2);
+}
+
+
+static char *
+ngx_stream_log_compile_format(ngx_conf_t *cf, ngx_array_t *flushes,
+    ngx_array_t *ops, ngx_array_t *args, ngx_uint_t s)
+{
+    u_char                *data, *p, ch;
+    size_t                 i, len;
+    ngx_str_t             *value, var;
+    ngx_int_t             *flush;
+    ngx_uint_t             bracket, escape;
+    ngx_stream_log_op_t   *op;
+
+    escape = NGX_STREAM_LOG_ESCAPE_DEFAULT;
+    value = args->elts;
+
+    if (s < args->nelts && ngx_strncmp(value[s].data, "escape=", 7) == 0) {
+        data = value[s].data + 7;
+
+        if (ngx_strcmp(data, "json") == 0) {
+            escape = NGX_STREAM_LOG_ESCAPE_JSON;
+
+        } else if (ngx_strcmp(data, "none") == 0) {
+            escape = NGX_STREAM_LOG_ESCAPE_NONE;
+
+        } else if (ngx_strcmp(data, "default") != 0) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "unknown log format escaping \"%s\"", data);
+            return NGX_CONF_ERROR;
+        }
+
+        s++;
+    }
+
+    for ( /* void */ ; s < args->nelts; s++) {
+
+        i = 0;
+
+        while (i < value[s].len) {
+
+            op = ngx_array_push(ops);
+            if (op == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            data = &value[s].data[i];
+
+            if (value[s].data[i] == '$') {
+
+                if (++i == value[s].len) {
+                    goto invalid;
+                }
+
+                if (value[s].data[i] == '{') {
+                    bracket = 1;
+
+                    if (++i == value[s].len) {
+                        goto invalid;
+                    }
+
+                    var.data = &value[s].data[i];
+
+                } else {
+                    bracket = 0;
+                    var.data = &value[s].data[i];
+                }
+
+                for (var.len = 0; i < value[s].len; i++, var.len++) {
+                    ch = value[s].data[i];
+
+                    if (ch == '}' && bracket) {
+                        i++;
+                        bracket = 0;
+                        break;
+                    }
+
+                    if ((ch >= 'A' && ch <= 'Z')
+                        || (ch >= 'a' && ch <= 'z')
+                        || (ch >= '0' && ch <= '9')
+                        || ch == '_')
+                    {
+                        continue;
+                    }
+
+                    break;
+                }
+
+                if (bracket) {
+                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                       "the closing bracket in \"%V\" "
+                                       "variable is missing", &var);
+                    return NGX_CONF_ERROR;
+                }
+
+                if (var.len == 0) {
+                    goto invalid;
+                }
+
+                if (ngx_stream_log_variable_compile(cf, op, &var, escape)
+                    != NGX_OK)
+                {
+                    return NGX_CONF_ERROR;
+                }
+
+                if (flushes) {
+
+                    flush = ngx_array_push(flushes);
+                    if (flush == NULL) {
+                        return NGX_CONF_ERROR;
+                    }
+
+                    *flush = op->data; /* variable index */
+                }
+
+                continue;
+            }
+
+            i++;
+
+            while (i < value[s].len && value[s].data[i] != '$') {
+                i++;
+            }
+
+            len = &value[s].data[i] - data;
+
+            if (len) {
+
+                op->len = len;
+                op->getlen = NULL;
+
+                if (len <= sizeof(uintptr_t)) {
+                    op->run = ngx_stream_log_copy_short;
+                    op->data = 0;
+
+                    while (len--) {
+                        op->data <<= 8;
+                        op->data |= data[len];
+                    }
+
+                } else {
+                    op->run = ngx_stream_log_copy_long;
+
+                    p = ngx_pnalloc(cf->pool, len);
+                    if (p == NULL) {
+                        return NGX_CONF_ERROR;
+                    }
+
+                    ngx_memcpy(p, data, len);
+                    op->data = (uintptr_t) p;
+                }
+            }
+        }
+    }
+
+    return NGX_CONF_OK;
+
+invalid:
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%s\"", data);
+
+    return NGX_CONF_ERROR;
+}
+
+
+static char *
+ngx_stream_log_open_file_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_log_srv_conf_t *lscf = conf;
+
+    time_t       inactive, valid;
+    ngx_str_t   *value, s;
+    ngx_int_t    max, min_uses;
+    ngx_uint_t   i;
+
+    if (lscf->open_file_cache != NGX_CONF_UNSET_PTR) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    max = 0;
+    inactive = 10;
+    valid = 60;
+    min_uses = 1;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+
+        if (ngx_strncmp(value[i].data, "max=", 4) == 0) {
+
+            max = ngx_atoi(value[i].data + 4, value[i].len - 4);
+            if (max == NGX_ERROR) {
+                goto failed;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "inactive=", 9) == 0) {
+
+            s.len = value[i].len - 9;
+            s.data = value[i].data + 9;
+
+            inactive = ngx_parse_time(&s, 1);
+            if (inactive == (time_t) NGX_ERROR) {
+                goto failed;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "min_uses=", 9) == 0) {
+
+            min_uses = ngx_atoi(value[i].data + 9, value[i].len - 9);
+            if (min_uses == NGX_ERROR) {
+                goto failed;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "valid=", 6) == 0) {
+
+            s.len = value[i].len - 6;
+            s.data = value[i].data + 6;
+
+            valid = ngx_parse_time(&s, 1);
+            if (valid == (time_t) NGX_ERROR) {
+                goto failed;
+            }
+
+            continue;
+        }
+
+        if (ngx_strcmp(value[i].data, "off") == 0) {
+
+            lscf->open_file_cache = NULL;
+
+            continue;
+        }
+
+    failed:
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid \"open_log_file_cache\" parameter \"%V\"",
+                           &value[i]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (lscf->open_file_cache == NULL) {
+        return NGX_CONF_OK;
+    }
+
+    if (max == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                        "\"open_log_file_cache\" must have \"max\" parameter");
+        return NGX_CONF_ERROR;
+    }
+
+    lscf->open_file_cache = ngx_open_file_cache_init(cf->pool, max, inactive);
+
+    if (lscf->open_file_cache) {
+
+        lscf->open_file_cache_valid = valid;
+        lscf->open_file_cache_min_uses = min_uses;
+
+        return NGX_CONF_OK;
+    }
+
+    return NGX_CONF_ERROR;
+}
+
+
+static ngx_int_t
+ngx_stream_log_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_LOG_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_stream_log_handler;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/stream/ngx_stream_map_module.c b/nginx/src/stream/ngx_stream_map_module.c
new file mode 100644 (file)
index 0000000..ef06b2d
--- /dev/null
@@ -0,0 +1,588 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+typedef struct {
+    ngx_uint_t                    hash_max_size;
+    ngx_uint_t                    hash_bucket_size;
+} ngx_stream_map_conf_t;
+
+
+typedef struct {
+    ngx_hash_keys_arrays_t        keys;
+
+    ngx_array_t                  *values_hash;
+#if (NGX_PCRE)
+    ngx_array_t                   regexes;
+#endif
+
+    ngx_stream_variable_value_t  *default_value;
+    ngx_conf_t                   *cf;
+    unsigned                      hostnames:1;
+    unsigned                      no_cacheable:1;
+} ngx_stream_map_conf_ctx_t;
+
+
+typedef struct {
+    ngx_stream_map_t              map;
+    ngx_stream_complex_value_t    value;
+    ngx_stream_variable_value_t  *default_value;
+    ngx_uint_t                    hostnames;      /* unsigned  hostnames:1 */
+} ngx_stream_map_ctx_t;
+
+
+static int ngx_libc_cdecl ngx_stream_map_cmp_dns_wildcards(const void *one,
+    const void *two);
+static void *ngx_stream_map_create_conf(ngx_conf_t *cf);
+static char *ngx_stream_map_block(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_stream_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf);
+
+
+static ngx_command_t  ngx_stream_map_commands[] = {
+
+    { ngx_string("map"),
+      NGX_STREAM_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE2,
+      ngx_stream_map_block,
+      NGX_STREAM_MAIN_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("map_hash_max_size"),
+      NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_STREAM_MAIN_CONF_OFFSET,
+      offsetof(ngx_stream_map_conf_t, hash_max_size),
+      NULL },
+
+    { ngx_string("map_hash_bucket_size"),
+      NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_STREAM_MAIN_CONF_OFFSET,
+      offsetof(ngx_stream_map_conf_t, hash_bucket_size),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_stream_module_t  ngx_stream_map_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    ngx_stream_map_create_conf,            /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL                                   /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_stream_map_module = {
+    NGX_MODULE_V1,
+    &ngx_stream_map_module_ctx,            /* module context */
+    ngx_stream_map_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_map_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v,
+    uintptr_t data)
+{
+    ngx_stream_map_ctx_t  *map = (ngx_stream_map_ctx_t *) data;
+
+    ngx_str_t                     val, str;
+    ngx_stream_complex_value_t   *cv;
+    ngx_stream_variable_value_t  *value;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                   "stream map started");
+
+    if (ngx_stream_complex_value(s, &map->value, &val) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (map->hostnames && val.len > 0 && val.data[val.len - 1] == '.') {
+        val.len--;
+    }
+
+    value = ngx_stream_map_find(s, &map->map, &val);
+
+    if (value == NULL) {
+        value = map->default_value;
+    }
+
+    if (!value->valid) {
+        cv = (ngx_stream_complex_value_t *) value->data;
+
+        if (ngx_stream_complex_value(s, cv, &str) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->len = str.len;
+        v->data = str.data;
+
+    } else {
+        *v = *value;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                   "stream map: \"%V\" \"%v\"", &val, v);
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_stream_map_create_conf(ngx_conf_t *cf)
+{
+    ngx_stream_map_conf_t  *mcf;
+
+    mcf = ngx_palloc(cf->pool, sizeof(ngx_stream_map_conf_t));
+    if (mcf == NULL) {
+        return NULL;
+    }
+
+    mcf->hash_max_size = NGX_CONF_UNSET_UINT;
+    mcf->hash_bucket_size = NGX_CONF_UNSET_UINT;
+
+    return mcf;
+}
+
+
+static char *
+ngx_stream_map_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_map_conf_t  *mcf = conf;
+
+    char                                *rv;
+    ngx_str_t                           *value, name;
+    ngx_conf_t                           save;
+    ngx_pool_t                          *pool;
+    ngx_hash_init_t                      hash;
+    ngx_stream_map_ctx_t                *map;
+    ngx_stream_variable_t               *var;
+    ngx_stream_map_conf_ctx_t            ctx;
+    ngx_stream_compile_complex_value_t   ccv;
+
+    if (mcf->hash_max_size == NGX_CONF_UNSET_UINT) {
+        mcf->hash_max_size = 2048;
+    }
+
+    if (mcf->hash_bucket_size == NGX_CONF_UNSET_UINT) {
+        mcf->hash_bucket_size = ngx_cacheline_size;
+
+    } else {
+        mcf->hash_bucket_size = ngx_align(mcf->hash_bucket_size,
+                                          ngx_cacheline_size);
+    }
+
+    map = ngx_pcalloc(cf->pool, sizeof(ngx_stream_map_ctx_t));
+    if (map == 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 = &map->value;
+
+    if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    name = value[2];
+
+    if (name.data[0] != '$') {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid variable name \"%V\"", &name);
+        return NGX_CONF_ERROR;
+    }
+
+    name.len--;
+    name.data++;
+
+    var = ngx_stream_add_variable(cf, &name, NGX_STREAM_VAR_CHANGEABLE);
+    if (var == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    var->get_handler = ngx_stream_map_variable;
+    var->data = (uintptr_t) map;
+
+    pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
+    if (pool == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ctx.keys.pool = cf->pool;
+    ctx.keys.temp_pool = pool;
+
+    if (ngx_hash_keys_array_init(&ctx.keys, NGX_HASH_LARGE) != NGX_OK) {
+        ngx_destroy_pool(pool);
+        return NGX_CONF_ERROR;
+    }
+
+    ctx.values_hash = ngx_pcalloc(pool, sizeof(ngx_array_t) * ctx.keys.hsize);
+    if (ctx.values_hash == NULL) {
+        ngx_destroy_pool(pool);
+        return NGX_CONF_ERROR;
+    }
+
+#if (NGX_PCRE)
+    if (ngx_array_init(&ctx.regexes, cf->pool, 2,
+                       sizeof(ngx_stream_map_regex_t))
+        != NGX_OK)
+    {
+        ngx_destroy_pool(pool);
+        return NGX_CONF_ERROR;
+    }
+#endif
+
+    ctx.default_value = NULL;
+    ctx.cf = &save;
+    ctx.hostnames = 0;
+    ctx.no_cacheable = 0;
+
+    save = *cf;
+    cf->pool = pool;
+    cf->ctx = &ctx;
+    cf->handler = ngx_stream_map;
+    cf->handler_conf = conf;
+
+    rv = ngx_conf_parse(cf, NULL);
+
+    *cf = save;
+
+    if (rv != NGX_CONF_OK) {
+        ngx_destroy_pool(pool);
+        return rv;
+    }
+
+    if (ctx.no_cacheable) {
+        var->flags |= NGX_STREAM_VAR_NOCACHEABLE;
+    }
+
+    map->default_value = ctx.default_value ? ctx.default_value:
+                                             &ngx_stream_variable_null_value;
+
+    map->hostnames = ctx.hostnames;
+
+    hash.key = ngx_hash_key_lc;
+    hash.max_size = mcf->hash_max_size;
+    hash.bucket_size = mcf->hash_bucket_size;
+    hash.name = "map_hash";
+    hash.pool = cf->pool;
+
+    if (ctx.keys.keys.nelts) {
+        hash.hash = &map->map.hash.hash;
+        hash.temp_pool = NULL;
+
+        if (ngx_hash_init(&hash, ctx.keys.keys.elts, ctx.keys.keys.nelts)
+            != NGX_OK)
+        {
+            ngx_destroy_pool(pool);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (ctx.keys.dns_wc_head.nelts) {
+
+        ngx_qsort(ctx.keys.dns_wc_head.elts,
+                  (size_t) ctx.keys.dns_wc_head.nelts,
+                  sizeof(ngx_hash_key_t), ngx_stream_map_cmp_dns_wildcards);
+
+        hash.hash = NULL;
+        hash.temp_pool = pool;
+
+        if (ngx_hash_wildcard_init(&hash, ctx.keys.dns_wc_head.elts,
+                                   ctx.keys.dns_wc_head.nelts)
+            != NGX_OK)
+        {
+            ngx_destroy_pool(pool);
+            return NGX_CONF_ERROR;
+        }
+
+        map->map.hash.wc_head = (ngx_hash_wildcard_t *) hash.hash;
+    }
+
+    if (ctx.keys.dns_wc_tail.nelts) {
+
+        ngx_qsort(ctx.keys.dns_wc_tail.elts,
+                  (size_t) ctx.keys.dns_wc_tail.nelts,
+                  sizeof(ngx_hash_key_t), ngx_stream_map_cmp_dns_wildcards);
+
+        hash.hash = NULL;
+        hash.temp_pool = pool;
+
+        if (ngx_hash_wildcard_init(&hash, ctx.keys.dns_wc_tail.elts,
+                                   ctx.keys.dns_wc_tail.nelts)
+            != NGX_OK)
+        {
+            ngx_destroy_pool(pool);
+            return NGX_CONF_ERROR;
+        }
+
+        map->map.hash.wc_tail = (ngx_hash_wildcard_t *) hash.hash;
+    }
+
+#if (NGX_PCRE)
+
+    if (ctx.regexes.nelts) {
+        map->map.regex = ctx.regexes.elts;
+        map->map.nregex = ctx.regexes.nelts;
+    }
+
+#endif
+
+    ngx_destroy_pool(pool);
+
+    return rv;
+}
+
+
+static int ngx_libc_cdecl
+ngx_stream_map_cmp_dns_wildcards(const void *one, const void *two)
+{
+    ngx_hash_key_t  *first, *second;
+
+    first = (ngx_hash_key_t *) one;
+    second = (ngx_hash_key_t *) two;
+
+    return ngx_dns_strcmp(first->key.data, second->key.data);
+}
+
+
+static char *
+ngx_stream_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
+{
+    u_char                              *data;
+    size_t                               len;
+    ngx_int_t                            rv;
+    ngx_str_t                           *value, v;
+    ngx_uint_t                           i, key;
+    ngx_stream_map_conf_ctx_t           *ctx;
+    ngx_stream_complex_value_t           cv, *cvp;
+    ngx_stream_variable_value_t         *var, **vp;
+    ngx_stream_compile_complex_value_t   ccv;
+
+    ctx = cf->ctx;
+
+    value = cf->args->elts;
+
+    if (cf->args->nelts == 1
+        && ngx_strcmp(value[0].data, "hostnames") == 0)
+    {
+        ctx->hostnames = 1;
+        return NGX_CONF_OK;
+    }
+
+    if (cf->args->nelts == 1
+        && ngx_strcmp(value[0].data, "volatile") == 0)
+    {
+        ctx->no_cacheable = 1;
+        return NGX_CONF_OK;
+    }
+
+    if (cf->args->nelts != 2) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid number of the map parameters");
+        return NGX_CONF_ERROR;
+    }
+
+    if (ngx_strcmp(value[0].data, "include") == 0) {
+        return ngx_conf_include(cf, dummy, conf);
+    }
+
+    key = 0;
+
+    for (i = 0; i < value[1].len; i++) {
+        key = ngx_hash(key, value[1].data[i]);
+    }
+
+    key %= ctx->keys.hsize;
+
+    vp = ctx->values_hash[key].elts;
+
+    if (vp) {
+        for (i = 0; i < ctx->values_hash[key].nelts; i++) {
+
+            if (vp[i]->valid) {
+                data = vp[i]->data;
+                len = vp[i]->len;
+
+            } else {
+                cvp = (ngx_stream_complex_value_t *) vp[i]->data;
+                data = cvp->value.data;
+                len = cvp->value.len;
+            }
+
+            if (value[1].len != len) {
+                continue;
+            }
+
+            if (ngx_strncmp(value[1].data, data, len) == 0) {
+                var = vp[i];
+                goto found;
+            }
+        }
+
+    } else {
+        if (ngx_array_init(&ctx->values_hash[key], cf->pool, 4,
+                           sizeof(ngx_stream_variable_value_t *))
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    var = ngx_palloc(ctx->keys.pool, sizeof(ngx_stream_variable_value_t));
+    if (var == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    v.len = value[1].len;
+    v.data = ngx_pstrdup(ctx->keys.pool, &value[1]);
+    if (v.data == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t));
+
+    ccv.cf = ctx->cf;
+    ccv.value = &v;
+    ccv.complex_value = &cv;
+
+    if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (cv.lengths != NULL) {
+        cvp = ngx_palloc(ctx->keys.pool, sizeof(ngx_stream_complex_value_t));
+        if (cvp == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        *cvp = cv;
+
+        var->len = 0;
+        var->data = (u_char *) cvp;
+        var->valid = 0;
+
+    } else {
+        var->len = v.len;
+        var->data = v.data;
+        var->valid = 1;
+    }
+
+    var->no_cacheable = 0;
+    var->not_found = 0;
+
+    vp = ngx_array_push(&ctx->values_hash[key]);
+    if (vp == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    *vp = var;
+
+found:
+
+    if (ngx_strcmp(value[0].data, "default") == 0) {
+
+        if (ctx->default_value) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "duplicate default map parameter");
+            return NGX_CONF_ERROR;
+        }
+
+        ctx->default_value = var;
+
+        return NGX_CONF_OK;
+    }
+
+#if (NGX_PCRE)
+
+    if (value[0].len && value[0].data[0] == '~') {
+        ngx_regex_compile_t      rc;
+        ngx_stream_map_regex_t  *regex;
+        u_char                   errstr[NGX_MAX_CONF_ERRSTR];
+
+        regex = ngx_array_push(&ctx->regexes);
+        if (regex == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        value[0].len--;
+        value[0].data++;
+
+        ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
+
+        if (value[0].data[0] == '*') {
+            value[0].len--;
+            value[0].data++;
+            rc.options = NGX_REGEX_CASELESS;
+        }
+
+        rc.pattern = value[0];
+        rc.err.len = NGX_MAX_CONF_ERRSTR;
+        rc.err.data = errstr;
+
+        regex->regex = ngx_stream_regex_compile(ctx->cf, &rc);
+        if (regex->regex == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        regex->value = var;
+
+        return NGX_CONF_OK;
+    }
+
+#endif
+
+    if (value[0].len && value[0].data[0] == '\\') {
+        value[0].len--;
+        value[0].data++;
+    }
+
+    rv = ngx_hash_add_key(&ctx->keys, &value[0], var,
+                          (ctx->hostnames) ? NGX_HASH_WILDCARD_KEY : 0);
+
+    if (rv == NGX_OK) {
+        return NGX_CONF_OK;
+    }
+
+    if (rv == NGX_DECLINED) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid hostname or wildcard \"%V\"", &value[0]);
+    }
+
+    if (rv == NGX_BUSY) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "conflicting parameter \"%V\"", &value[0]);
+    }
+
+    return NGX_CONF_ERROR;
+}
diff --git a/nginx/src/stream/ngx_stream_proxy_module.c b/nginx/src/stream/ngx_stream_proxy_module.c
new file mode 100644 (file)
index 0000000..f911135
--- /dev/null
@@ -0,0 +1,2187 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+typedef struct {
+    ngx_addr_t                      *addr;
+    ngx_stream_complex_value_t      *value;
+#if (NGX_HAVE_TRANSPARENT_PROXY)
+    ngx_uint_t                       transparent; /* unsigned  transparent:1; */
+#endif
+} ngx_stream_upstream_local_t;
+
+
+typedef struct {
+    ngx_msec_t                       connect_timeout;
+    ngx_msec_t                       timeout;
+    ngx_msec_t                       next_upstream_timeout;
+    size_t                           buffer_size;
+    size_t                           upload_rate;
+    size_t                           download_rate;
+    ngx_uint_t                       responses;
+    ngx_uint_t                       next_upstream_tries;
+    ngx_flag_t                       next_upstream;
+    ngx_flag_t                       proxy_protocol;
+    ngx_stream_upstream_local_t     *local;
+
+#if (NGX_STREAM_SSL)
+    ngx_flag_t                       ssl_enable;
+    ngx_flag_t                       ssl_session_reuse;
+    ngx_uint_t                       ssl_protocols;
+    ngx_str_t                        ssl_ciphers;
+    ngx_stream_complex_value_t      *ssl_name;
+    ngx_flag_t                       ssl_server_name;
+
+    ngx_flag_t                       ssl_verify;
+    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_ssl_t                       *ssl;
+#endif
+
+    ngx_stream_upstream_srv_conf_t  *upstream;
+    ngx_stream_complex_value_t      *upstream_value;
+} ngx_stream_proxy_srv_conf_t;
+
+
+static void ngx_stream_proxy_handler(ngx_stream_session_t *s);
+static ngx_int_t ngx_stream_proxy_eval(ngx_stream_session_t *s,
+    ngx_stream_proxy_srv_conf_t *pscf);
+static ngx_int_t ngx_stream_proxy_set_local(ngx_stream_session_t *s,
+    ngx_stream_upstream_t *u, ngx_stream_upstream_local_t *local);
+static void ngx_stream_proxy_connect(ngx_stream_session_t *s);
+static void ngx_stream_proxy_init_upstream(ngx_stream_session_t *s);
+static void ngx_stream_proxy_resolve_handler(ngx_resolver_ctx_t *ctx);
+static void ngx_stream_proxy_upstream_handler(ngx_event_t *ev);
+static void ngx_stream_proxy_downstream_handler(ngx_event_t *ev);
+static void ngx_stream_proxy_process_connection(ngx_event_t *ev,
+    ngx_uint_t from_upstream);
+static void ngx_stream_proxy_connect_handler(ngx_event_t *ev);
+static ngx_int_t ngx_stream_proxy_test_connect(ngx_connection_t *c);
+static void ngx_stream_proxy_process(ngx_stream_session_t *s,
+    ngx_uint_t from_upstream, ngx_uint_t do_write);
+static void ngx_stream_proxy_next_upstream(ngx_stream_session_t *s);
+static void ngx_stream_proxy_finalize(ngx_stream_session_t *s, ngx_uint_t rc);
+static u_char *ngx_stream_proxy_log_error(ngx_log_t *log, u_char *buf,
+    size_t len);
+
+static void *ngx_stream_proxy_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_stream_proxy_merge_srv_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static char *ngx_stream_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_stream_proxy_bind(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+#if (NGX_STREAM_SSL)
+
+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 void ngx_stream_proxy_ssl_init_connection(ngx_stream_session_t *s);
+static void ngx_stream_proxy_ssl_handshake(ngx_connection_t *pc);
+static ngx_int_t ngx_stream_proxy_ssl_name(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);
+
+
+static ngx_conf_bitmask_t  ngx_stream_proxy_ssl_protocols[] = {
+    { ngx_string("SSLv2"), NGX_SSL_SSLv2 },
+    { ngx_string("SSLv3"), NGX_SSL_SSLv3 },
+    { ngx_string("TLSv1"), NGX_SSL_TLSv1 },
+    { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 },
+    { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 },
+    { ngx_string("TLSv1.3"), NGX_SSL_TLSv1_3 },
+    { ngx_null_string, 0 }
+};
+
+#endif
+
+
+static ngx_conf_deprecated_t  ngx_conf_deprecated_proxy_downstream_buffer = {
+    ngx_conf_deprecated, "proxy_downstream_buffer", "proxy_buffer_size"
+};
+
+static ngx_conf_deprecated_t  ngx_conf_deprecated_proxy_upstream_buffer = {
+    ngx_conf_deprecated, "proxy_upstream_buffer", "proxy_buffer_size"
+};
+
+
+static ngx_command_t  ngx_stream_proxy_commands[] = {
+
+    { ngx_string("proxy_pass"),
+      NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_stream_proxy_pass,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("proxy_bind"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE12,
+      ngx_stream_proxy_bind,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("proxy_connect_timeout"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_proxy_srv_conf_t, connect_timeout),
+      NULL },
+
+    { ngx_string("proxy_timeout"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_proxy_srv_conf_t, timeout),
+      NULL },
+
+    { ngx_string("proxy_buffer_size"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_proxy_srv_conf_t, buffer_size),
+      NULL },
+
+    { ngx_string("proxy_downstream_buffer"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_proxy_srv_conf_t, buffer_size),
+      &ngx_conf_deprecated_proxy_downstream_buffer },
+
+    { ngx_string("proxy_upstream_buffer"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_proxy_srv_conf_t, buffer_size),
+      &ngx_conf_deprecated_proxy_upstream_buffer },
+
+    { ngx_string("proxy_upload_rate"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_proxy_srv_conf_t, upload_rate),
+      NULL },
+
+    { ngx_string("proxy_download_rate"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_proxy_srv_conf_t, download_rate),
+      NULL },
+
+    { ngx_string("proxy_responses"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_proxy_srv_conf_t, responses),
+      NULL },
+
+    { ngx_string("proxy_next_upstream"),
+      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, next_upstream),
+      NULL },
+
+    { ngx_string("proxy_next_upstream_tries"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_proxy_srv_conf_t, next_upstream_tries),
+      NULL },
+
+    { ngx_string("proxy_next_upstream_timeout"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_proxy_srv_conf_t, next_upstream_timeout),
+      NULL },
+
+    { ngx_string("proxy_protocol"),
+      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, proxy_protocol),
+      NULL },
+
+#if (NGX_STREAM_SSL)
+
+    { ngx_string("proxy_ssl"),
+      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, ssl_enable),
+      NULL },
+
+    { ngx_string("proxy_ssl_session_reuse"),
+      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, ssl_session_reuse),
+      NULL },
+
+    { ngx_string("proxy_ssl_protocols"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_proxy_srv_conf_t, ssl_protocols),
+      &ngx_stream_proxy_ssl_protocols },
+
+    { ngx_string("proxy_ssl_ciphers"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_proxy_srv_conf_t, ssl_ciphers),
+      NULL },
+
+    { ngx_string("proxy_ssl_name"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_stream_set_complex_value_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_proxy_srv_conf_t, ssl_name),
+      NULL },
+
+    { ngx_string("proxy_ssl_server_name"),
+      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, ssl_server_name),
+      NULL },
+
+    { ngx_string("proxy_ssl_verify"),
+      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, ssl_verify),
+      NULL },
+
+    { ngx_string("proxy_ssl_verify_depth"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_proxy_srv_conf_t, ssl_verify_depth),
+      NULL },
+
+    { ngx_string("proxy_ssl_trusted_certificate"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_proxy_srv_conf_t, ssl_trusted_certificate),
+      NULL },
+
+    { ngx_string("proxy_ssl_crl"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_proxy_srv_conf_t, ssl_crl),
+      NULL },
+
+    { ngx_string("proxy_ssl_certificate"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_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_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_proxy_srv_conf_t, ssl_certificate_key),
+      NULL },
+
+    { ngx_string("proxy_ssl_password_file"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_stream_proxy_ssl_password_file,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+#endif
+
+      ngx_null_command
+};
+
+
+static ngx_stream_module_t  ngx_stream_proxy_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_stream_proxy_create_srv_conf,      /* create server configuration */
+    ngx_stream_proxy_merge_srv_conf        /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_stream_proxy_module = {
+    NGX_MODULE_V1,
+    &ngx_stream_proxy_module_ctx,          /* module context */
+    ngx_stream_proxy_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 void
+ngx_stream_proxy_handler(ngx_stream_session_t *s)
+{
+    u_char                           *p;
+    ngx_str_t                        *host;
+    ngx_uint_t                        i;
+    ngx_connection_t                 *c;
+    ngx_resolver_ctx_t               *ctx, temp;
+    ngx_stream_upstream_t            *u;
+    ngx_stream_core_srv_conf_t       *cscf;
+    ngx_stream_proxy_srv_conf_t      *pscf;
+    ngx_stream_upstream_srv_conf_t   *uscf, **uscfp;
+    ngx_stream_upstream_main_conf_t  *umcf;
+
+    c = s->connection;
+
+    pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module);
+
+    ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0,
+                   "proxy connection handler");
+
+    u = ngx_pcalloc(c->pool, sizeof(ngx_stream_upstream_t));
+    if (u == NULL) {
+        ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    s->upstream = u;
+
+    s->log_handler = ngx_stream_proxy_log_error;
+
+    u->peer.log = c->log;
+    u->peer.log_error = NGX_ERROR_ERR;
+
+    if (ngx_stream_proxy_set_local(s, u, pscf->local) != NGX_OK) {
+        ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    u->peer.type = c->type;
+    u->start_sec = ngx_time();
+
+    c->write->handler = ngx_stream_proxy_downstream_handler;
+    c->read->handler = ngx_stream_proxy_downstream_handler;
+
+    s->upstream_states = ngx_array_create(c->pool, 1,
+                                          sizeof(ngx_stream_upstream_state_t));
+    if (s->upstream_states == NULL) {
+        ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    if (c->type == SOCK_STREAM) {
+        p = ngx_pnalloc(c->pool, pscf->buffer_size);
+        if (p == NULL) {
+            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        u->downstream_buf.start = p;
+        u->downstream_buf.end = p + pscf->buffer_size;
+        u->downstream_buf.pos = p;
+        u->downstream_buf.last = p;
+
+        if (c->read->ready) {
+            ngx_post_event(c->read, &ngx_posted_events);
+        }
+    }
+
+    if (pscf->upstream_value) {
+        if (ngx_stream_proxy_eval(s, pscf) != NGX_OK) {
+            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+            return;
+        }
+    }
+
+    if (u->resolved == NULL) {
+
+        uscf = pscf->upstream;
+
+    } else {
+
+#if (NGX_STREAM_SSL)
+        u->ssl_name = u->resolved->host;
+#endif
+
+        host = &u->resolved->host;
+
+        umcf = ngx_stream_get_module_main_conf(s, ngx_stream_upstream_module);
+
+        uscfp = umcf->upstreams.elts;
+
+        for (i = 0; i < umcf->upstreams.nelts; i++) {
+
+            uscf = uscfp[i];
+
+            if (uscf->host.len == host->len
+                && ((uscf->port == 0 && u->resolved->no_port)
+                     || uscf->port == u->resolved->port)
+                && ngx_strncasecmp(uscf->host.data, host->data, host->len) == 0)
+            {
+                goto found;
+            }
+        }
+
+        if (u->resolved->sockaddr) {
+
+            if (u->resolved->port == 0
+                && u->resolved->sockaddr->sa_family != AF_UNIX)
+            {
+                ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                              "no port in upstream \"%V\"", host);
+                ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+                return;
+            }
+
+            if (ngx_stream_upstream_create_round_robin_peer(s, u->resolved)
+                != NGX_OK)
+            {
+                ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+                return;
+            }
+
+            ngx_stream_proxy_connect(s);
+
+            return;
+        }
+
+        if (u->resolved->port == 0) {
+            ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                          "no port in upstream \"%V\"", host);
+            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        temp.name = *host;
+
+        cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module);
+
+        ctx = ngx_resolve_start(cscf->resolver, &temp);
+        if (ctx == NULL) {
+            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        if (ctx == NGX_NO_RESOLVER) {
+            ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                          "no resolver defined to resolve %V", host);
+            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        ctx->name = *host;
+        ctx->handler = ngx_stream_proxy_resolve_handler;
+        ctx->data = s;
+        ctx->timeout = cscf->resolver_timeout;
+
+        u->resolved->ctx = ctx;
+
+        if (ngx_resolve_name(ctx) != NGX_OK) {
+            u->resolved->ctx = NULL;
+            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        return;
+    }
+
+found:
+
+    if (uscf == NULL) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, 0, "no upstream configuration");
+        ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    u->upstream = uscf;
+
+#if (NGX_STREAM_SSL)
+    u->ssl_name = uscf->host;
+#endif
+
+    if (uscf->peer.init(s, uscf) != NGX_OK) {
+        ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    u->peer.start_time = ngx_current_msec;
+
+    if (pscf->next_upstream_tries
+        && u->peer.tries > pscf->next_upstream_tries)
+    {
+        u->peer.tries = pscf->next_upstream_tries;
+    }
+
+    ngx_stream_proxy_connect(s);
+}
+
+
+static ngx_int_t
+ngx_stream_proxy_eval(ngx_stream_session_t *s,
+    ngx_stream_proxy_srv_conf_t *pscf)
+{
+    ngx_str_t               host;
+    ngx_url_t               url;
+    ngx_stream_upstream_t  *u;
+
+    if (ngx_stream_complex_value(s, pscf->upstream_value, &host) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    ngx_memzero(&url, sizeof(ngx_url_t));
+
+    url.url = host;
+    url.no_resolve = 1;
+
+    if (ngx_parse_url(s->connection->pool, &url) != NGX_OK) {
+        if (url.err) {
+            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                          "%s in upstream \"%V\"", url.err, &url.url);
+        }
+
+        return NGX_ERROR;
+    }
+
+    u = s->upstream;
+
+    u->resolved = ngx_pcalloc(s->connection->pool,
+                              sizeof(ngx_stream_upstream_resolved_t));
+    if (u->resolved == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (url.addrs) {
+        u->resolved->sockaddr = url.addrs[0].sockaddr;
+        u->resolved->socklen = url.addrs[0].socklen;
+        u->resolved->name = url.addrs[0].name;
+        u->resolved->naddrs = 1;
+    }
+
+    u->resolved->host = url.host;
+    u->resolved->port = url.port;
+    u->resolved->no_port = url.no_port;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_proxy_set_local(ngx_stream_session_t *s, ngx_stream_upstream_t *u,
+    ngx_stream_upstream_local_t *local)
+{
+    ngx_int_t    rc;
+    ngx_str_t    val;
+    ngx_addr_t  *addr;
+
+    if (local == NULL) {
+        u->peer.local = NULL;
+        return NGX_OK;
+    }
+
+#if (NGX_HAVE_TRANSPARENT_PROXY)
+    u->peer.transparent = local->transparent;
+#endif
+
+    if (local->value == NULL) {
+        u->peer.local = local->addr;
+        return NGX_OK;
+    }
+
+    if (ngx_stream_complex_value(s, local->value, &val) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (val.len == 0) {
+        return NGX_OK;
+    }
+
+    addr = ngx_palloc(s->connection->pool, sizeof(ngx_addr_t));
+    if (addr == NULL) {
+        return NGX_ERROR;
+    }
+
+    rc = ngx_parse_addr_port(s->connection->pool, addr, val.data, val.len);
+    if (rc == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if (rc != NGX_OK) {
+        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                      "invalid local address \"%V\"", &val);
+        return NGX_OK;
+    }
+
+    addr->name = val;
+    u->peer.local = addr;
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_stream_proxy_connect(ngx_stream_session_t *s)
+{
+    ngx_int_t                     rc;
+    ngx_connection_t             *c, *pc;
+    ngx_stream_upstream_t        *u;
+    ngx_stream_proxy_srv_conf_t  *pscf;
+
+    c = s->connection;
+
+    c->log->action = "connecting to upstream";
+
+    pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module);
+
+    u = s->upstream;
+
+    u->connected = 0;
+    u->proxy_protocol = pscf->proxy_protocol;
+
+    if (u->state) {
+        u->state->response_time = ngx_current_msec - u->state->response_time;
+    }
+
+    u->state = ngx_array_push(s->upstream_states);
+    if (u->state == NULL) {
+        ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    ngx_memzero(u->state, sizeof(ngx_stream_upstream_state_t));
+
+    u->state->connect_time = (ngx_msec_t) -1;
+    u->state->first_byte_time = (ngx_msec_t) -1;
+    u->state->response_time = ngx_current_msec;
+
+    rc = ngx_event_connect_peer(&u->peer);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "proxy connect: %i", rc);
+
+    if (rc == NGX_ERROR) {
+        ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    u->state->peer = u->peer.name;
+
+    if (rc == NGX_BUSY) {
+        ngx_log_error(NGX_LOG_ERR, c->log, 0, "no live upstreams");
+        ngx_stream_proxy_finalize(s, NGX_STREAM_BAD_GATEWAY);
+        return;
+    }
+
+    if (rc == NGX_DECLINED) {
+        ngx_stream_proxy_next_upstream(s);
+        return;
+    }
+
+    /* rc == NGX_OK || rc == NGX_AGAIN || rc == NGX_DONE */
+
+    pc = u->peer.connection;
+
+    pc->data = s;
+    pc->log = c->log;
+    pc->pool = c->pool;
+    pc->read->log = c->log;
+    pc->write->log = c->log;
+
+    if (rc != NGX_AGAIN) {
+        ngx_stream_proxy_init_upstream(s);
+        return;
+    }
+
+    pc->read->handler = ngx_stream_proxy_connect_handler;
+    pc->write->handler = ngx_stream_proxy_connect_handler;
+
+    ngx_add_timer(pc->write, pscf->connect_timeout);
+}
+
+
+static void
+ngx_stream_proxy_init_upstream(ngx_stream_session_t *s)
+{
+    u_char                       *p;
+    ngx_chain_t                  *cl;
+    ngx_connection_t             *c, *pc;
+    ngx_log_handler_pt            handler;
+    ngx_stream_upstream_t        *u;
+    ngx_stream_core_srv_conf_t   *cscf;
+    ngx_stream_proxy_srv_conf_t  *pscf;
+
+    u = s->upstream;
+    pc = u->peer.connection;
+
+    cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module);
+
+    if (pc->type == SOCK_STREAM
+        && cscf->tcp_nodelay
+        && ngx_tcp_nodelay(pc) != NGX_OK)
+    {
+        ngx_stream_proxy_next_upstream(s);
+        return;
+    }
+
+    pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module);
+
+#if (NGX_STREAM_SSL)
+
+    if (pc->type == SOCK_STREAM && pscf->ssl) {
+
+        if (u->proxy_protocol) {
+            if (ngx_stream_proxy_send_proxy_protocol(s) != NGX_OK) {
+                return;
+            }
+
+            u->proxy_protocol = 0;
+        }
+
+        if (pc->ssl == NULL) {
+            ngx_stream_proxy_ssl_init_connection(s);
+            return;
+        }
+    }
+
+#endif
+
+    c = s->connection;
+
+    if (c->log->log_level >= NGX_LOG_INFO) {
+        ngx_str_t  str;
+        u_char     addr[NGX_SOCKADDR_STRLEN];
+
+        str.len = NGX_SOCKADDR_STRLEN;
+        str.data = addr;
+
+        if (ngx_connection_local_sockaddr(pc, &str, 1) == NGX_OK) {
+            handler = c->log->handler;
+            c->log->handler = NULL;
+
+            ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                          "%sproxy %V connected to %V",
+                          pc->type == SOCK_DGRAM ? "udp " : "",
+                          &str, u->peer.name);
+
+            c->log->handler = handler;
+        }
+    }
+
+    u->state->connect_time = ngx_current_msec - u->state->response_time;
+
+    if (u->peer.notify) {
+        u->peer.notify(&u->peer, u->peer.data,
+                       NGX_STREAM_UPSTREAM_NOTIFY_CONNECT);
+    }
+
+    if (u->upstream_buf.start == NULL) {
+        p = ngx_pnalloc(c->pool, pscf->buffer_size);
+        if (p == NULL) {
+            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        u->upstream_buf.start = p;
+        u->upstream_buf.end = p + pscf->buffer_size;
+        u->upstream_buf.pos = p;
+        u->upstream_buf.last = p;
+    }
+
+    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);
+
+        cl = ngx_chain_get_free_buf(c->pool, &u->free);
+        if (cl == NULL) {
+            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        *cl->buf = *c->buffer;
+
+        cl->buf->tag = (ngx_buf_tag_t) &ngx_stream_proxy_module;
+        cl->buf->flush = 1;
+        cl->buf->last_buf = (c->type == SOCK_DGRAM);
+
+        cl->next = u->upstream_out;
+        u->upstream_out = cl;
+    }
+
+    if (u->proxy_protocol) {
+        ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0,
+                       "stream proxy add PROXY protocol header");
+
+        cl = ngx_chain_get_free_buf(c->pool, &u->free);
+        if (cl == NULL) {
+            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        p = ngx_pnalloc(c->pool, NGX_PROXY_PROTOCOL_MAX_HEADER);
+        if (p == NULL) {
+            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        cl->buf->pos = p;
+
+        p = ngx_proxy_protocol_write(c, p, p + NGX_PROXY_PROTOCOL_MAX_HEADER);
+        if (p == NULL) {
+            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        cl->buf->last = p;
+        cl->buf->temporary = 1;
+        cl->buf->flush = 0;
+        cl->buf->last_buf = 0;
+        cl->buf->tag = (ngx_buf_tag_t) &ngx_stream_proxy_module;
+
+        cl->next = u->upstream_out;
+        u->upstream_out = cl;
+
+        u->proxy_protocol = 0;
+    }
+
+    if (c->type == SOCK_DGRAM && pscf->responses == 0) {
+        pc->read->ready = 0;
+        pc->read->eof = 1;
+    }
+
+    u->connected = 1;
+
+    pc->read->handler = ngx_stream_proxy_upstream_handler;
+    pc->write->handler = ngx_stream_proxy_upstream_handler;
+
+    if (pc->read->ready || pc->read->eof) {
+        ngx_post_event(pc->read, &ngx_posted_events);
+    }
+
+    ngx_stream_proxy_process(s, 0, 1);
+}
+
+
+#if (NGX_STREAM_SSL)
+
+static ngx_int_t
+ngx_stream_proxy_send_proxy_protocol(ngx_stream_session_t *s)
+{
+    u_char                       *p;
+    ssize_t                       n, size;
+    ngx_connection_t             *c, *pc;
+    ngx_stream_upstream_t        *u;
+    ngx_stream_proxy_srv_conf_t  *pscf;
+    u_char                        buf[NGX_PROXY_PROTOCOL_MAX_HEADER];
+
+    c = s->connection;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0,
+                   "stream proxy send PROXY protocol header");
+
+    p = ngx_proxy_protocol_write(c, buf, buf + NGX_PROXY_PROTOCOL_MAX_HEADER);
+    if (p == NULL) {
+        ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return NGX_ERROR;
+    }
+
+    u = s->upstream;
+
+    pc = u->peer.connection;
+
+    size = p - buf;
+
+    n = pc->send(pc, buf, size);
+
+    if (n == NGX_AGAIN) {
+        if (ngx_handle_write_event(pc->write, 0) != NGX_OK) {
+            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+            return NGX_ERROR;
+        }
+
+        pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module);
+
+        ngx_add_timer(pc->write, pscf->timeout);
+
+        pc->write->handler = ngx_stream_proxy_connect_handler;
+
+        return NGX_AGAIN;
+    }
+
+    if (n == NGX_ERROR) {
+        ngx_stream_proxy_finalize(s, NGX_STREAM_OK);
+        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, c->log, 0,
+                      "could not send PROXY protocol header at once");
+
+        ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_stream_proxy_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf)
+{
+    ngx_stream_proxy_srv_conf_t *pscf = conf;
+
+    ngx_str_t  *value;
+
+    if (pscf->ssl_passwords != NGX_CONF_UNSET_PTR) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    pscf->ssl_passwords = ngx_ssl_read_password_file(cf, &value[1]);
+
+    if (pscf->ssl_passwords == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static void
+ngx_stream_proxy_ssl_init_connection(ngx_stream_session_t *s)
+{
+    ngx_int_t                     rc;
+    ngx_connection_t             *pc;
+    ngx_stream_upstream_t        *u;
+    ngx_stream_proxy_srv_conf_t  *pscf;
+
+    u = s->upstream;
+
+    pc = u->peer.connection;
+
+    pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module);
+
+    if (ngx_ssl_create_connection(pscf->ssl, pc, NGX_SSL_BUFFER|NGX_SSL_CLIENT)
+        != NGX_OK)
+    {
+        ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    if (pscf->ssl_server_name || pscf->ssl_verify) {
+        if (ngx_stream_proxy_ssl_name(s) != NGX_OK) {
+            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+            return;
+        }
+    }
+
+    if (pscf->ssl_session_reuse) {
+        if (u->peer.set_session(&u->peer, u->peer.data) != NGX_OK) {
+            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+            return;
+        }
+    }
+
+    s->connection->log->action = "SSL handshaking to upstream";
+
+    rc = ngx_ssl_handshake(pc);
+
+    if (rc == NGX_AGAIN) {
+
+        if (!pc->write->timer_set) {
+            ngx_add_timer(pc->write, pscf->connect_timeout);
+        }
+
+        pc->ssl->handler = ngx_stream_proxy_ssl_handshake;
+        return;
+    }
+
+    ngx_stream_proxy_ssl_handshake(pc);
+}
+
+
+static void
+ngx_stream_proxy_ssl_handshake(ngx_connection_t *pc)
+{
+    long                          rc;
+    ngx_stream_session_t         *s;
+    ngx_stream_upstream_t        *u;
+    ngx_stream_proxy_srv_conf_t  *pscf;
+
+    s = pc->data;
+
+    pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module);
+
+    if (pc->ssl->handshaked) {
+
+        if (pscf->ssl_verify) {
+            rc = SSL_get_verify_result(pc->ssl->connection);
+
+            if (rc != X509_V_OK) {
+                ngx_log_error(NGX_LOG_ERR, pc->log, 0,
+                              "upstream SSL certificate verify error: (%l:%s)",
+                              rc, X509_verify_cert_error_string(rc));
+                goto failed;
+            }
+
+            u = s->upstream;
+
+            if (ngx_ssl_check_host(pc, &u->ssl_name) != NGX_OK) {
+                ngx_log_error(NGX_LOG_ERR, pc->log, 0,
+                              "upstream SSL certificate does not match \"%V\"",
+                              &u->ssl_name);
+                goto failed;
+            }
+        }
+
+        if (pscf->ssl_session_reuse) {
+            u = s->upstream;
+            u->peer.save_session(&u->peer, u->peer.data);
+        }
+
+        if (pc->write->timer_set) {
+            ngx_del_timer(pc->write);
+        }
+
+        ngx_stream_proxy_init_upstream(s);
+
+        return;
+    }
+
+failed:
+
+    ngx_stream_proxy_next_upstream(s);
+}
+
+
+static ngx_int_t
+ngx_stream_proxy_ssl_name(ngx_stream_session_t *s)
+{
+    u_char                       *p, *last;
+    ngx_str_t                     name;
+    ngx_stream_upstream_t        *u;
+    ngx_stream_proxy_srv_conf_t  *pscf;
+
+    pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module);
+
+    u = s->upstream;
+
+    if (pscf->ssl_name) {
+        if (ngx_stream_complex_value(s, pscf->ssl_name, &name) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+    } else {
+        name = u->ssl_name;
+    }
+
+    if (name.len == 0) {
+        goto done;
+    }
+
+    /*
+     * ssl name here may contain port, strip it for compatibility
+     * with the http module
+     */
+
+    p = name.data;
+    last = name.data + name.len;
+
+    if (*p == '[') {
+        p = ngx_strlchr(p, last, ']');
+
+        if (p == NULL) {
+            p = name.data;
+        }
+    }
+
+    p = ngx_strlchr(p, last, ':');
+
+    if (p != NULL) {
+        name.len = p - name.data;
+    }
+
+    if (!pscf->ssl_server_name) {
+        goto done;
+    }
+
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+
+    /* as per RFC 6066, literal IPv4 and IPv6 addresses are not permitted */
+
+    if (name.len == 0 || *name.data == '[') {
+        goto done;
+    }
+
+    if (ngx_inet_addr(name.data, name.len) != INADDR_NONE) {
+        goto done;
+    }
+
+    /*
+     * SSL_set_tlsext_host_name() needs a null-terminated string,
+     * hence we explicitly null-terminate name here
+     */
+
+    p = ngx_pnalloc(s->connection->pool, name.len + 1);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    (void) ngx_cpystrn(p, name.data, name.len + 1);
+
+    name.data = p;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                   "upstream SSL server name: \"%s\"", name.data);
+
+    if (SSL_set_tlsext_host_name(u->peer.connection->ssl->connection,
+                                 (char *) name.data)
+        == 0)
+    {
+        ngx_ssl_error(NGX_LOG_ERR, s->connection->log, 0,
+                      "SSL_set_tlsext_host_name(\"%s\") failed", name.data);
+        return NGX_ERROR;
+    }
+
+#endif
+
+done:
+
+    u->ssl_name = name;
+
+    return NGX_OK;
+}
+
+#endif
+
+
+static void
+ngx_stream_proxy_downstream_handler(ngx_event_t *ev)
+{
+    ngx_stream_proxy_process_connection(ev, ev->write);
+}
+
+
+static void
+ngx_stream_proxy_resolve_handler(ngx_resolver_ctx_t *ctx)
+{
+    ngx_stream_session_t            *s;
+    ngx_stream_upstream_t           *u;
+    ngx_stream_proxy_srv_conf_t     *pscf;
+    ngx_stream_upstream_resolved_t  *ur;
+
+    s = ctx->data;
+
+    u = s->upstream;
+    ur = u->resolved;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                   "stream upstream resolve");
+
+    if (ctx->state) {
+        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                      "%V could not be resolved (%i: %s)",
+                      &ctx->name, ctx->state,
+                      ngx_resolver_strerror(ctx->state));
+
+        ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    ur->naddrs = ctx->naddrs;
+    ur->addrs = ctx->addrs;
+
+#if (NGX_DEBUG)
+    {
+    u_char      text[NGX_SOCKADDR_STRLEN];
+    ngx_str_t   addr;
+    ngx_uint_t  i;
+
+    addr.data = text;
+
+    for (i = 0; i < ctx->naddrs; i++) {
+        addr.len = ngx_sock_ntop(ur->addrs[i].sockaddr, ur->addrs[i].socklen,
+                                 text, NGX_SOCKADDR_STRLEN, 0);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                       "name was resolved to %V", &addr);
+    }
+    }
+#endif
+
+    if (ngx_stream_upstream_create_round_robin_peer(s, ur) != NGX_OK) {
+        ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    ngx_resolve_name_done(ctx);
+    ur->ctx = NULL;
+
+    u->peer.start_time = ngx_current_msec;
+
+    pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module);
+
+    if (pscf->next_upstream_tries
+        && u->peer.tries > pscf->next_upstream_tries)
+    {
+        u->peer.tries = pscf->next_upstream_tries;
+    }
+
+    ngx_stream_proxy_connect(s);
+}
+
+
+static void
+ngx_stream_proxy_upstream_handler(ngx_event_t *ev)
+{
+    ngx_stream_proxy_process_connection(ev, !ev->write);
+}
+
+
+static void
+ngx_stream_proxy_process_connection(ngx_event_t *ev, ngx_uint_t from_upstream)
+{
+    ngx_connection_t             *c, *pc;
+    ngx_stream_session_t         *s;
+    ngx_stream_upstream_t        *u;
+    ngx_stream_proxy_srv_conf_t  *pscf;
+
+    c = ev->data;
+    s = c->data;
+    u = s->upstream;
+
+    if (c->close) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0, "shutdown timeout");
+        ngx_stream_proxy_finalize(s, NGX_STREAM_OK);
+        return;
+    }
+
+    c = s->connection;
+    pc = u->peer.connection;
+
+    pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module);
+
+    if (ev->timedout) {
+        ev->timedout = 0;
+
+        if (ev->delayed) {
+            ev->delayed = 0;
+
+            if (!ev->ready) {
+                if (ngx_handle_read_event(ev, 0) != NGX_OK) {
+                    ngx_stream_proxy_finalize(s,
+                                              NGX_STREAM_INTERNAL_SERVER_ERROR);
+                    return;
+                }
+
+                if (u->connected && !c->read->delayed && !pc->read->delayed) {
+                    ngx_add_timer(c->write, pscf->timeout);
+                }
+
+                return;
+            }
+
+        } else {
+            if (s->connection->type == SOCK_DGRAM) {
+                if (pscf->responses == NGX_MAX_INT32_VALUE) {
+
+                    /*
+                     * successfully terminate timed out UDP session
+                     * with unspecified number of responses
+                     */
+
+                    pc->read->ready = 0;
+                    pc->read->eof = 1;
+
+                    ngx_stream_proxy_process(s, 1, 0);
+                    return;
+                }
+
+                ngx_connection_error(pc, NGX_ETIMEDOUT, "upstream timed out");
+
+                if (u->received == 0) {
+                    ngx_stream_proxy_next_upstream(s);
+                    return;
+                }
+
+            } else {
+                ngx_connection_error(c, NGX_ETIMEDOUT, "connection timed out");
+            }
+
+            ngx_stream_proxy_finalize(s, NGX_STREAM_OK);
+            return;
+        }
+
+    } else if (ev->delayed) {
+
+        ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0,
+                       "stream connection delayed");
+
+        if (ngx_handle_read_event(ev, 0) != NGX_OK) {
+            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        }
+
+        return;
+    }
+
+    if (from_upstream && !u->connected) {
+        return;
+    }
+
+    ngx_stream_proxy_process(s, from_upstream, ev->write);
+}
+
+
+static void
+ngx_stream_proxy_connect_handler(ngx_event_t *ev)
+{
+    ngx_connection_t      *c;
+    ngx_stream_session_t  *s;
+
+    c = ev->data;
+    s = c->data;
+
+    if (ev->timedout) {
+        ngx_log_error(NGX_LOG_ERR, c->log, NGX_ETIMEDOUT, "upstream timed out");
+        ngx_stream_proxy_next_upstream(s);
+        return;
+    }
+
+    ngx_del_timer(c->write);
+
+    ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0,
+                   "stream proxy connect upstream");
+
+    if (ngx_stream_proxy_test_connect(c) != NGX_OK) {
+        ngx_stream_proxy_next_upstream(s);
+        return;
+    }
+
+    ngx_stream_proxy_init_upstream(s);
+}
+
+
+static ngx_int_t
+ngx_stream_proxy_test_connect(ngx_connection_t *c)
+{
+    int        err;
+    socklen_t  len;
+
+#if (NGX_HAVE_KQUEUE)
+
+    if (ngx_event_flags & NGX_USE_KQUEUE_EVENT)  {
+        err = c->write->kq_errno ? c->write->kq_errno : c->read->kq_errno;
+
+        if (err) {
+            (void) ngx_connection_error(c, err,
+                                    "kevent() reported that connect() failed");
+            return NGX_ERROR;
+        }
+
+    } else
+#endif
+    {
+        err = 0;
+        len = sizeof(int);
+
+        /*
+         * BSDs and Linux return 0 and set a pending error in err
+         * Solaris returns -1 and sets errno
+         */
+
+        if (ngxvcl_kvfd_getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len)
+            == -1)
+        {
+            err = ngx_socket_errno;
+        }
+
+        if (err) {
+            (void) ngx_connection_error(c, err, "connect() failed");
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_stream_proxy_process(ngx_stream_session_t *s, ngx_uint_t from_upstream,
+    ngx_uint_t do_write)
+{
+    char                         *recv_action, *send_action;
+    off_t                        *received, limit;
+    size_t                        size, limit_rate;
+    ssize_t                       n;
+    ngx_buf_t                    *b;
+    ngx_int_t                     rc;
+    ngx_uint_t                    flags;
+    ngx_msec_t                    delay;
+    ngx_chain_t                  *cl, **ll, **out, **busy;
+    ngx_connection_t             *c, *pc, *src, *dst;
+    ngx_log_handler_pt            handler;
+    ngx_stream_upstream_t        *u;
+    ngx_stream_proxy_srv_conf_t  *pscf;
+
+    u = s->upstream;
+
+    c = s->connection;
+    pc = u->connected ? u->peer.connection : NULL;
+
+    if (c->type == SOCK_DGRAM && (ngx_terminate || ngx_exiting)) {
+
+        /* socket is already closed on worker shutdown */
+
+        handler = c->log->handler;
+        c->log->handler = NULL;
+
+        ngx_log_error(NGX_LOG_INFO, c->log, 0, "disconnected on shutdown");
+
+        c->log->handler = handler;
+
+        ngx_stream_proxy_finalize(s, NGX_STREAM_OK);
+        return;
+    }
+
+    pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module);
+
+    if (from_upstream) {
+        src = pc;
+        dst = c;
+        b = &u->upstream_buf;
+        limit_rate = pscf->download_rate;
+        received = &u->received;
+        out = &u->downstream_out;
+        busy = &u->downstream_busy;
+        recv_action = "proxying and reading from upstream";
+        send_action = "proxying and sending to client";
+
+    } else {
+        src = c;
+        dst = pc;
+        b = &u->downstream_buf;
+        limit_rate = pscf->upload_rate;
+        received = &s->received;
+        out = &u->upstream_out;
+        busy = &u->upstream_busy;
+        recv_action = "proxying and reading from client";
+        send_action = "proxying and sending to upstream";
+    }
+
+    for ( ;; ) {
+
+        if (do_write && dst) {
+
+            if (*out || *busy || dst->buffered) {
+                c->log->action = send_action;
+
+                rc = ngx_stream_top_filter(s, *out, from_upstream);
+
+                if (rc == NGX_ERROR) {
+                    if (c->type == SOCK_DGRAM && !from_upstream) {
+                        ngx_stream_proxy_next_upstream(s);
+                        return;
+                    }
+
+                    ngx_stream_proxy_finalize(s, NGX_STREAM_OK);
+                    return;
+                }
+
+                ngx_chain_update_chains(c->pool, &u->free, busy, out,
+                                      (ngx_buf_tag_t) &ngx_stream_proxy_module);
+
+                if (*busy == NULL) {
+                    b->pos = b->start;
+                    b->last = b->start;
+                }
+            }
+        }
+
+        size = b->end - b->last;
+
+        if (size && src->read->ready && !src->read->delayed
+            && !src->read->error)
+        {
+            if (limit_rate) {
+                limit = (off_t) limit_rate * (ngx_time() - u->start_sec + 1)
+                        - *received;
+
+                if (limit <= 0) {
+                    src->read->delayed = 1;
+                    delay = (ngx_msec_t) (- limit * 1000 / limit_rate + 1);
+                    ngx_add_timer(src->read, delay);
+                    break;
+                }
+
+                if ((off_t) size > limit) {
+                    size = (size_t) limit;
+                }
+            }
+
+            c->log->action = recv_action;
+
+            n = src->recv(src, b->last, size);
+
+            if (n == NGX_AGAIN) {
+                break;
+            }
+
+            if (n == NGX_ERROR) {
+                if (c->type == SOCK_DGRAM && u->received == 0) {
+                    ngx_stream_proxy_next_upstream(s);
+                    return;
+                }
+
+                src->read->eof = 1;
+                n = 0;
+            }
+
+            if (n >= 0) {
+                if (limit_rate) {
+                    delay = (ngx_msec_t) (n * 1000 / limit_rate);
+
+                    if (delay > 0) {
+                        src->read->delayed = 1;
+                        ngx_add_timer(src->read, delay);
+                    }
+                }
+
+                if (from_upstream) {
+                    if (u->state->first_byte_time == (ngx_msec_t) -1) {
+                        u->state->first_byte_time = ngx_current_msec
+                                                    - u->state->response_time;
+                    }
+                }
+
+                if (c->type == SOCK_DGRAM && ++u->responses == pscf->responses)
+                {
+                    src->read->ready = 0;
+                    src->read->eof = 1;
+                }
+
+                for (ll = out; *ll; ll = &(*ll)->next) { /* void */ }
+
+                cl = ngx_chain_get_free_buf(c->pool, &u->free);
+                if (cl == NULL) {
+                    ngx_stream_proxy_finalize(s,
+                                              NGX_STREAM_INTERNAL_SERVER_ERROR);
+                    return;
+                }
+
+                *ll = cl;
+
+                cl->buf->pos = b->last;
+                cl->buf->last = b->last + n;
+                cl->buf->tag = (ngx_buf_tag_t) &ngx_stream_proxy_module;
+
+                cl->buf->temporary = (n ? 1 : 0);
+                cl->buf->last_buf = src->read->eof;
+                cl->buf->flush = 1;
+
+                *received += n;
+                b->last += n;
+                do_write = 1;
+
+                continue;
+            }
+        }
+
+        break;
+    }
+
+    c->log->action = "proxying connection";
+
+    if (src->read->eof && dst && (dst->read->eof || !dst->buffered)) {
+        handler = c->log->handler;
+        c->log->handler = NULL;
+
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "%s%s disconnected"
+                      ", bytes from/to client:%O/%O"
+                      ", bytes from/to upstream:%O/%O",
+                      src->type == SOCK_DGRAM ? "udp " : "",
+                      from_upstream ? "upstream" : "client",
+                      s->received, c->sent, u->received, pc ? pc->sent : 0);
+
+        c->log->handler = handler;
+
+        ngx_stream_proxy_finalize(s, NGX_STREAM_OK);
+        return;
+    }
+
+    flags = src->read->eof ? NGX_CLOSE_EVENT : 0;
+
+    if (!src->shared && ngx_handle_read_event(src->read, flags) != NGX_OK) {
+        ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    if (dst) {
+        if (!dst->shared && ngx_handle_write_event(dst->write, 0) != NGX_OK) {
+            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        if (!c->read->delayed && !pc->read->delayed) {
+            ngx_add_timer(c->write, pscf->timeout);
+
+        } else if (c->write->timer_set) {
+            ngx_del_timer(c->write);
+        }
+    }
+}
+
+
+static void
+ngx_stream_proxy_next_upstream(ngx_stream_session_t *s)
+{
+    ngx_msec_t                    timeout;
+    ngx_connection_t             *pc;
+    ngx_stream_upstream_t        *u;
+    ngx_stream_proxy_srv_conf_t  *pscf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                   "stream proxy next upstream");
+
+    u = s->upstream;
+    pc = u->peer.connection;
+
+    if (pc && pc->buffered) {
+        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                      "buffered data on next upstream");
+        ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    if (s->connection->type == SOCK_DGRAM) {
+        u->upstream_out = NULL;
+    }
+
+    if (u->peer.sockaddr) {
+        u->peer.free(&u->peer, u->peer.data, NGX_PEER_FAILED);
+        u->peer.sockaddr = NULL;
+    }
+
+    pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module);
+
+    timeout = pscf->next_upstream_timeout;
+
+    if (u->peer.tries == 0
+        || !pscf->next_upstream
+        || (timeout && ngx_current_msec - u->peer.start_time >= timeout))
+    {
+        ngx_stream_proxy_finalize(s, NGX_STREAM_BAD_GATEWAY);
+        return;
+    }
+
+    if (pc) {
+        ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                       "close proxy upstream connection: %d", pc->fd);
+
+#if (NGX_STREAM_SSL)
+        if (pc->ssl) {
+            pc->ssl->no_wait_shutdown = 1;
+            pc->ssl->no_send_shutdown = 1;
+
+            (void) ngx_ssl_shutdown(pc);
+        }
+#endif
+
+        u->state->bytes_received = u->received;
+        u->state->bytes_sent = pc->sent;
+
+        ngx_close_connection(pc);
+        u->peer.connection = NULL;
+    }
+
+    ngx_stream_proxy_connect(s);
+}
+
+
+static void
+ngx_stream_proxy_finalize(ngx_stream_session_t *s, ngx_uint_t rc)
+{
+    ngx_connection_t       *pc;
+    ngx_stream_upstream_t  *u;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                   "finalize stream proxy: %i", rc);
+
+    u = s->upstream;
+
+    if (u == NULL) {
+        goto noupstream;
+    }
+
+    if (u->resolved && u->resolved->ctx) {
+        ngx_resolve_name_done(u->resolved->ctx);
+        u->resolved->ctx = NULL;
+    }
+
+    pc = u->peer.connection;
+
+    if (u->state) {
+        u->state->response_time = ngx_current_msec - u->state->response_time;
+
+        if (pc) {
+            u->state->bytes_received = u->received;
+            u->state->bytes_sent = pc->sent;
+        }
+    }
+
+    if (u->peer.free && u->peer.sockaddr) {
+        u->peer.free(&u->peer, u->peer.data, 0);
+        u->peer.sockaddr = NULL;
+    }
+
+    if (pc) {
+        ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                       "close stream proxy upstream connection: %d", pc->fd);
+
+#if (NGX_STREAM_SSL)
+        if (pc->ssl) {
+            pc->ssl->no_wait_shutdown = 1;
+            (void) ngx_ssl_shutdown(pc);
+        }
+#endif
+
+        ngx_close_connection(pc);
+        u->peer.connection = NULL;
+    }
+
+noupstream:
+
+    ngx_stream_finalize_session(s, rc);
+}
+
+
+static u_char *
+ngx_stream_proxy_log_error(ngx_log_t *log, u_char *buf, size_t len)
+{
+    u_char                 *p;
+    ngx_connection_t       *pc;
+    ngx_stream_session_t   *s;
+    ngx_stream_upstream_t  *u;
+
+    s = log->data;
+
+    u = s->upstream;
+
+    p = buf;
+
+    if (u->peer.name) {
+        p = ngx_snprintf(p, len, ", upstream: \"%V\"", u->peer.name);
+        len -= p - buf;
+    }
+
+    pc = u->peer.connection;
+
+    p = ngx_snprintf(p, len,
+                     ", bytes from/to client:%O/%O"
+                     ", bytes from/to upstream:%O/%O",
+                     s->received, s->connection->sent,
+                     u->received, pc ? pc->sent : 0);
+
+    return p;
+}
+
+
+static void *
+ngx_stream_proxy_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_stream_proxy_srv_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_proxy_srv_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     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->ssl = NULL;
+     *     conf->upstream = NULL;
+     *     conf->upstream_value = NULL;
+     */
+
+    conf->connect_timeout = NGX_CONF_UNSET_MSEC;
+    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_SIZE;
+    conf->download_rate = NGX_CONF_UNSET_SIZE;
+    conf->responses = NGX_CONF_UNSET_UINT;
+    conf->next_upstream_tries = NGX_CONF_UNSET_UINT;
+    conf->next_upstream = NGX_CONF_UNSET;
+    conf->proxy_protocol = NGX_CONF_UNSET;
+    conf->local = NGX_CONF_UNSET_PTR;
+
+#if (NGX_STREAM_SSL)
+    conf->ssl_enable = NGX_CONF_UNSET;
+    conf->ssl_session_reuse = NGX_CONF_UNSET;
+    conf->ssl_server_name = NGX_CONF_UNSET;
+    conf->ssl_verify = NGX_CONF_UNSET;
+    conf->ssl_verify_depth = NGX_CONF_UNSET_UINT;
+    conf->ssl_passwords = NGX_CONF_UNSET_PTR;
+#endif
+
+    return conf;
+}
+
+
+static char *
+ngx_stream_proxy_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_stream_proxy_srv_conf_t *prev = parent;
+    ngx_stream_proxy_srv_conf_t *conf = child;
+
+    ngx_conf_merge_msec_value(conf->connect_timeout,
+                              prev->connect_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->timeout,
+                              prev->timeout, 10 * 60000);
+
+    ngx_conf_merge_msec_value(conf->next_upstream_timeout,
+                              prev->next_upstream_timeout, 0);
+
+    ngx_conf_merge_size_value(conf->buffer_size,
+                              prev->buffer_size, 16384);
+
+    ngx_conf_merge_size_value(conf->upload_rate,
+                              prev->upload_rate, 0);
+
+    ngx_conf_merge_size_value(conf->download_rate,
+                              prev->download_rate, 0);
+
+    ngx_conf_merge_uint_value(conf->responses,
+                              prev->responses, NGX_MAX_INT32_VALUE);
+
+    ngx_conf_merge_uint_value(conf->next_upstream_tries,
+                              prev->next_upstream_tries, 0);
+
+    ngx_conf_merge_value(conf->next_upstream, prev->next_upstream, 1);
+
+    ngx_conf_merge_value(conf->proxy_protocol, prev->proxy_protocol, 0);
+
+    ngx_conf_merge_ptr_value(conf->local, prev->local, NULL);
+
+#if (NGX_STREAM_SSL)
+
+    ngx_conf_merge_value(conf->ssl_enable, prev->ssl_enable, 0);
+
+    ngx_conf_merge_value(conf->ssl_session_reuse,
+                              prev->ssl_session_reuse, 1);
+
+    ngx_conf_merge_bitmask_value(conf->ssl_protocols, prev->ssl_protocols,
+                              (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1
+                               |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2));
+
+    ngx_conf_merge_str_value(conf->ssl_ciphers, prev->ssl_ciphers, "DEFAULT");
+
+    if (conf->ssl_name == NULL) {
+        conf->ssl_name = prev->ssl_name;
+    }
+
+    ngx_conf_merge_value(conf->ssl_server_name, prev->ssl_server_name, 0);
+
+    ngx_conf_merge_value(conf->ssl_verify, prev->ssl_verify, 0);
+
+    ngx_conf_merge_uint_value(conf->ssl_verify_depth,
+                              prev->ssl_verify_depth, 1);
+
+    ngx_conf_merge_str_value(conf->ssl_trusted_certificate,
+                              prev->ssl_trusted_certificate, "");
+
+    ngx_conf_merge_str_value(conf->ssl_crl, prev->ssl_crl, "");
+
+    ngx_conf_merge_str_value(conf->ssl_certificate,
+                              prev->ssl_certificate, "");
+
+    ngx_conf_merge_str_value(conf->ssl_certificate_key,
+                              prev->ssl_certificate_key, "");
+
+    ngx_conf_merge_ptr_value(conf->ssl_passwords, prev->ssl_passwords, NULL);
+
+    if (conf->ssl_enable && ngx_stream_proxy_set_ssl(cf, conf) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+#endif
+
+    return NGX_CONF_OK;
+}
+
+
+#if (NGX_STREAM_SSL)
+
+static ngx_int_t
+ngx_stream_proxy_set_ssl(ngx_conf_t *cf, ngx_stream_proxy_srv_conf_t *pscf)
+{
+    ngx_pool_cleanup_t  *cln;
+
+    pscf->ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t));
+    if (pscf->ssl == NULL) {
+        return NGX_ERROR;
+    }
+
+    pscf->ssl->log = cf->log;
+
+    if (ngx_ssl_create(pscf->ssl, pscf->ssl_protocols, NULL) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    cln = ngx_pool_cleanup_add(cf->pool, 0);
+    if (cln == NULL) {
+        return NGX_ERROR;
+    }
+
+    cln->handler = ngx_ssl_cleanup_ctx;
+    cln->data = pscf->ssl;
+
+    if (pscf->ssl_certificate.len) {
+
+        if (pscf->ssl_certificate_key.len == 0) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "no \"proxy_ssl_certificate_key\" is defined "
+                          "for certificate \"%V\"", &pscf->ssl_certificate);
+            return NGX_ERROR;
+        }
+
+        if (ngx_ssl_certificate(cf, pscf->ssl, &pscf->ssl_certificate,
+                                &pscf->ssl_certificate_key, pscf->ssl_passwords)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    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,
+                      "no proxy_ssl_trusted_certificate for proxy_ssl_verify");
+            return NGX_ERROR;
+        }
+
+        if (ngx_ssl_trusted_certificate(cf, pscf->ssl,
+                                        &pscf->ssl_trusted_certificate,
+                                        pscf->ssl_verify_depth)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+
+        if (ngx_ssl_crl(cf, pscf->ssl, &pscf->ssl_crl) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+#endif
+
+
+static char *
+ngx_stream_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_proxy_srv_conf_t *pscf = conf;
+
+    ngx_url_t                            u;
+    ngx_str_t                           *value, *url;
+    ngx_stream_complex_value_t           cv;
+    ngx_stream_core_srv_conf_t          *cscf;
+    ngx_stream_compile_complex_value_t   ccv;
+
+    if (pscf->upstream || pscf->upstream_value) {
+        return "is duplicate";
+    }
+
+    cscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_core_module);
+
+    cscf->handler = ngx_stream_proxy_handler;
+
+    value = cf->args->elts;
+
+    url = &value[1];
+
+    ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = url;
+    ccv.complex_value = &cv;
+
+    if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (cv.lengths) {
+        pscf->upstream_value = ngx_palloc(cf->pool,
+                                          sizeof(ngx_stream_complex_value_t));
+        if (pscf->upstream_value == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        *pscf->upstream_value = cv;
+
+        return NGX_CONF_OK;
+    }
+
+    ngx_memzero(&u, sizeof(ngx_url_t));
+
+    u.url = *url;
+    u.no_resolve = 1;
+
+    pscf->upstream = ngx_stream_upstream_add(cf, &u, 0);
+    if (pscf->upstream == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_stream_proxy_bind(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_proxy_srv_conf_t *pscf = conf;
+
+    ngx_int_t                            rc;
+    ngx_str_t                           *value;
+    ngx_stream_complex_value_t           cv;
+    ngx_stream_upstream_local_t         *local;
+    ngx_stream_compile_complex_value_t   ccv;
+
+    if (pscf->local != NGX_CONF_UNSET_PTR) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    if (cf->args->nelts == 2 && ngx_strcmp(value[1].data, "off") == 0) {
+        pscf->local = NULL;
+        return NGX_CONF_OK;
+    }
+
+    ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[1];
+    ccv.complex_value = &cv;
+
+    if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    local = ngx_pcalloc(cf->pool, sizeof(ngx_stream_upstream_local_t));
+    if (local == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    pscf->local = local;
+
+    if (cv.lengths) {
+        local->value = ngx_palloc(cf->pool, sizeof(ngx_stream_complex_value_t));
+        if (local->value == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        *local->value = cv;
+
+    } else {
+        local->addr = ngx_palloc(cf->pool, sizeof(ngx_addr_t));
+        if (local->addr == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        rc = ngx_parse_addr_port(cf->pool, local->addr, value[1].data,
+                                 value[1].len);
+
+        switch (rc) {
+        case NGX_OK:
+            local->addr->name = value[1];
+            break;
+
+        case NGX_DECLINED:
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid address \"%V\"", &value[1]);
+            /* fall through */
+
+        default:
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (cf->args->nelts > 2) {
+        if (ngx_strcmp(value[2].data, "transparent") == 0) {
+#if (NGX_HAVE_TRANSPARENT_PROXY)
+            ngx_core_conf_t  *ccf;
+
+            ccf = (ngx_core_conf_t *) ngx_get_conf(cf->cycle->conf_ctx,
+                                                   ngx_core_module);
+
+            ccf->transparent = 1;
+            local->transparent = 1;
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "transparent proxying is not supported "
+                               "on this platform, ignored");
+#endif
+        } else {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid parameter \"%V\"", &value[2]);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/stream/ngx_stream_realip_module.c b/nginx/src/stream/ngx_stream_realip_module.c
new file mode 100644 (file)
index 0000000..57b1ac2
--- /dev/null
@@ -0,0 +1,401 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+typedef struct {
+    ngx_array_t       *from;     /* array of ngx_cidr_t */
+} ngx_stream_realip_srv_conf_t;
+
+
+typedef struct {
+    struct sockaddr   *sockaddr;
+    socklen_t          socklen;
+    ngx_str_t          addr_text;
+} ngx_stream_realip_ctx_t;
+
+
+static ngx_int_t ngx_stream_realip_handler(ngx_stream_session_t *s);
+static ngx_int_t ngx_stream_realip_set_addr(ngx_stream_session_t *s,
+    ngx_addr_t *addr);
+static char *ngx_stream_realip_from(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static void *ngx_stream_realip_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_stream_realip_merge_srv_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static ngx_int_t ngx_stream_realip_add_variables(ngx_conf_t *cf);
+static ngx_int_t ngx_stream_realip_init(ngx_conf_t *cf);
+
+
+static ngx_int_t ngx_stream_realip_remote_addr_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_realip_remote_port_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+
+
+static ngx_command_t  ngx_stream_realip_commands[] = {
+
+    { ngx_string("set_real_ip_from"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_stream_realip_from,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_stream_module_t  ngx_stream_realip_module_ctx = {
+    ngx_stream_realip_add_variables,       /* preconfiguration */
+    ngx_stream_realip_init,                /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_stream_realip_create_srv_conf,     /* create server configuration */
+    ngx_stream_realip_merge_srv_conf       /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_stream_realip_module = {
+    NGX_MODULE_V1,
+    &ngx_stream_realip_module_ctx,         /* module context */
+    ngx_stream_realip_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_stream_variable_t  ngx_stream_realip_vars[] = {
+
+    { ngx_string("realip_remote_addr"), NULL,
+      ngx_stream_realip_remote_addr_variable, 0, 0, 0 },
+
+    { ngx_string("realip_remote_port"), NULL,
+      ngx_stream_realip_remote_port_variable, 0, 0, 0 },
+
+      ngx_stream_null_variable
+};
+
+
+static ngx_int_t
+ngx_stream_realip_handler(ngx_stream_session_t *s)
+{
+    ngx_addr_t                     addr;
+    ngx_connection_t              *c;
+    ngx_stream_realip_srv_conf_t  *rscf;
+
+    rscf = ngx_stream_get_module_srv_conf(s, ngx_stream_realip_module);
+
+    if (rscf->from == NULL) {
+        return NGX_DECLINED;
+    }
+
+    c = s->connection;
+
+    if (c->proxy_protocol_addr.len == 0) {
+        return NGX_DECLINED;
+    }
+
+    if (ngx_cidr_match(c->sockaddr, rscf->from) != NGX_OK) {
+        return NGX_DECLINED;
+    }
+
+    if (ngx_parse_addr(c->pool, &addr, c->proxy_protocol_addr.data,
+                       c->proxy_protocol_addr.len)
+        != NGX_OK)
+    {
+        return NGX_DECLINED;
+    }
+
+    ngx_inet_set_port(addr.sockaddr, c->proxy_protocol_port);
+
+    return ngx_stream_realip_set_addr(s, &addr);
+}
+
+
+static ngx_int_t
+ngx_stream_realip_set_addr(ngx_stream_session_t *s, ngx_addr_t *addr)
+{
+    size_t                    len;
+    u_char                   *p;
+    u_char                    text[NGX_SOCKADDR_STRLEN];
+    ngx_connection_t         *c;
+    ngx_stream_realip_ctx_t  *ctx;
+
+    c = s->connection;
+
+    ctx = ngx_palloc(c->pool, sizeof(ngx_stream_realip_ctx_t));
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    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);
+
+    ngx_stream_set_ctx(s, ctx, ngx_stream_realip_module);
+
+    ctx->sockaddr = c->sockaddr;
+    ctx->socklen = c->socklen;
+    ctx->addr_text = c->addr_text;
+
+    c->sockaddr = addr->sockaddr;
+    c->socklen = addr->socklen;
+    c->addr_text.len = len;
+    c->addr_text.data = p;
+
+    return NGX_DECLINED;
+}
+
+
+static char *
+ngx_stream_realip_from(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_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_stream_realip_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_stream_realip_srv_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_realip_srv_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->from = NULL;
+     */
+
+    return conf;
+}
+
+
+static char *
+ngx_stream_realip_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_stream_realip_srv_conf_t *prev = parent;
+    ngx_stream_realip_srv_conf_t *conf = child;
+
+    if (conf->from == NULL) {
+        conf->from = prev->from;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_realip_add_variables(ngx_conf_t *cf)
+{
+    ngx_stream_variable_t  *var, *v;
+
+    for (v = ngx_stream_realip_vars; v->name.len; v++) {
+        var = ngx_stream_add_variable(cf, &v->name, v->flags);
+        if (var == NULL) {
+            return NGX_ERROR;
+        }
+
+        var->get_handler = v->get_handler;
+        var->data = v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_realip_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_POST_ACCEPT_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_stream_realip_handler;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_realip_remote_addr_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    ngx_str_t                *addr_text;
+    ngx_stream_realip_ctx_t  *ctx;
+
+    ctx = ngx_stream_get_module_ctx(s, ngx_stream_realip_module);
+
+    addr_text = ctx ? &ctx->addr_text : &s->connection->addr_text;
+
+    v->len = addr_text->len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = addr_text->data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_realip_remote_port_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    ngx_uint_t                port;
+    struct sockaddr          *sa;
+    ngx_stream_realip_ctx_t  *ctx;
+
+    ctx = ngx_stream_get_module_ctx(s, ngx_stream_realip_module);
+
+    sa = ctx ? ctx->sockaddr : s->connection->sockaddr;
+
+    v->len = 0;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    v->data = ngx_pnalloc(s->connection->pool, sizeof("65535") - 1);
+    if (v->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    port = ngx_inet_get_port(sa);
+
+    if (port > 0 && port < 65536) {
+        v->len = ngx_sprintf(v->data, "%ui", port) - v->data;
+    }
+
+    return NGX_OK;
+}
diff --git a/nginx/src/stream/ngx_stream_return_module.c b/nginx/src/stream/ngx_stream_return_module.c
new file mode 100644 (file)
index 0000000..9301b02
--- /dev/null
@@ -0,0 +1,218 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+typedef struct {
+    ngx_stream_complex_value_t   text;
+} ngx_stream_return_srv_conf_t;
+
+
+typedef struct {
+    ngx_chain_t                 *out;
+} ngx_stream_return_ctx_t;
+
+
+static void ngx_stream_return_handler(ngx_stream_session_t *s);
+static void ngx_stream_return_write_handler(ngx_event_t *ev);
+
+static void *ngx_stream_return_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_stream_return(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+
+static ngx_command_t  ngx_stream_return_commands[] = {
+
+    { ngx_string("return"),
+      NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_stream_return,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_stream_module_t  ngx_stream_return_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_stream_return_create_srv_conf,     /* create server configuration */
+    NULL                                   /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_stream_return_module = {
+    NGX_MODULE_V1,
+    &ngx_stream_return_module_ctx,         /* module context */
+    ngx_stream_return_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 void
+ngx_stream_return_handler(ngx_stream_session_t *s)
+{
+    ngx_str_t                      text;
+    ngx_buf_t                     *b;
+    ngx_connection_t              *c;
+    ngx_stream_return_ctx_t       *ctx;
+    ngx_stream_return_srv_conf_t  *rscf;
+
+    c = s->connection;
+
+    c->log->action = "returning text";
+
+    rscf = ngx_stream_get_module_srv_conf(s, ngx_stream_return_module);
+
+    if (ngx_stream_complex_value(s, &rscf->text, &text) != NGX_OK) {
+        ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0,
+                   "stream return text: \"%V\"", &text);
+
+    if (text.len == 0) {
+        ngx_stream_finalize_session(s, NGX_STREAM_OK);
+        return;
+    }
+
+    ctx = ngx_pcalloc(c->pool, sizeof(ngx_stream_return_ctx_t));
+    if (ctx == NULL) {
+        ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    ngx_stream_set_ctx(s, ctx, ngx_stream_return_module);
+
+    b = ngx_calloc_buf(c->pool);
+    if (b == NULL) {
+        ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    b->memory = 1;
+    b->pos = text.data;
+    b->last = text.data + text.len;
+    b->last_buf = 1;
+
+    ctx->out = ngx_alloc_chain_link(c->pool);
+    if (ctx->out == NULL) {
+        ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    ctx->out->buf = b;
+    ctx->out->next = NULL;
+
+    c->write->handler = ngx_stream_return_write_handler;
+
+    ngx_stream_return_write_handler(c->write);
+}
+
+
+static void
+ngx_stream_return_write_handler(ngx_event_t *ev)
+{
+    ngx_connection_t         *c;
+    ngx_stream_session_t     *s;
+    ngx_stream_return_ctx_t  *ctx;
+
+    c = ev->data;
+    s = c->data;
+
+    if (ev->timedout) {
+        ngx_connection_error(c, NGX_ETIMEDOUT, "connection timed out");
+        ngx_stream_finalize_session(s, NGX_STREAM_OK);
+        return;
+    }
+
+    ctx = ngx_stream_get_module_ctx(s, ngx_stream_return_module);
+
+    if (ngx_stream_top_filter(s, ctx->out, 1) == NGX_ERROR) {
+        ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    ctx->out = NULL;
+
+    if (!c->buffered) {
+        ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0,
+                       "stream return done sending");
+        ngx_stream_finalize_session(s, NGX_STREAM_OK);
+        return;
+    }
+
+    if (ngx_handle_write_event(ev, 0) != NGX_OK) {
+        ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    ngx_add_timer(ev, 5000);
+}
+
+
+static void *
+ngx_stream_return_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_stream_return_srv_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_return_srv_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    return conf;
+}
+
+
+static char *
+ngx_stream_return(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_return_srv_conf_t *rscf = conf;
+
+    ngx_str_t                           *value;
+    ngx_stream_core_srv_conf_t          *cscf;
+    ngx_stream_compile_complex_value_t   ccv;
+
+    if (rscf->text.value.data) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[1];
+    ccv.complex_value = &rscf->text;
+
+    if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    cscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_core_module);
+
+    cscf->handler = ngx_stream_return_handler;
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/stream/ngx_stream_script.c b/nginx/src/stream/ngx_stream_script.c
new file mode 100644 (file)
index 0000000..b00e708
--- /dev/null
@@ -0,0 +1,922 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+static ngx_int_t ngx_stream_script_init_arrays(
+    ngx_stream_script_compile_t *sc);
+static ngx_int_t ngx_stream_script_done(ngx_stream_script_compile_t *sc);
+static ngx_int_t ngx_stream_script_add_copy_code(
+    ngx_stream_script_compile_t *sc, ngx_str_t *value, ngx_uint_t last);
+static ngx_int_t ngx_stream_script_add_var_code(
+    ngx_stream_script_compile_t *sc, ngx_str_t *name);
+#if (NGX_PCRE)
+static ngx_int_t ngx_stream_script_add_capture_code(
+    ngx_stream_script_compile_t *sc, ngx_uint_t n);
+#endif
+static ngx_int_t ngx_stream_script_add_full_name_code(
+    ngx_stream_script_compile_t *sc);
+static size_t ngx_stream_script_full_name_len_code(
+    ngx_stream_script_engine_t *e);
+static void ngx_stream_script_full_name_code(ngx_stream_script_engine_t *e);
+
+
+#define ngx_stream_script_exit  (u_char *) &ngx_stream_script_exit_code
+
+static uintptr_t ngx_stream_script_exit_code = (uintptr_t) NULL;
+
+
+void
+ngx_stream_script_flush_complex_value(ngx_stream_session_t *s,
+    ngx_stream_complex_value_t *val)
+{
+    ngx_uint_t *index;
+
+    index = val->flushes;
+
+    if (index) {
+        while (*index != (ngx_uint_t) -1) {
+
+            if (s->variables[*index].no_cacheable) {
+                s->variables[*index].valid = 0;
+                s->variables[*index].not_found = 0;
+            }
+
+            index++;
+        }
+    }
+}
+
+
+ngx_int_t
+ngx_stream_complex_value(ngx_stream_session_t *s,
+    ngx_stream_complex_value_t *val, ngx_str_t *value)
+{
+    size_t                         len;
+    ngx_stream_script_code_pt      code;
+    ngx_stream_script_engine_t     e;
+    ngx_stream_script_len_code_pt  lcode;
+
+    if (val->lengths == NULL) {
+        *value = val->value;
+        return NGX_OK;
+    }
+
+    ngx_stream_script_flush_complex_value(s, val);
+
+    ngx_memzero(&e, sizeof(ngx_stream_script_engine_t));
+
+    e.ip = val->lengths;
+    e.session = s;
+    e.flushed = 1;
+
+    len = 0;
+
+    while (*(uintptr_t *) e.ip) {
+        lcode = *(ngx_stream_script_len_code_pt *) e.ip;
+        len += lcode(&e);
+    }
+
+    value->len = len;
+    value->data = ngx_pnalloc(s->connection->pool, len);
+    if (value->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    e.ip = val->values;
+    e.pos = value->data;
+    e.buf = *value;
+
+    while (*(uintptr_t *) e.ip) {
+        code = *(ngx_stream_script_code_pt *) e.ip;
+        code((ngx_stream_script_engine_t *) &e);
+    }
+
+    *value = e.buf;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_stream_compile_complex_value(ngx_stream_compile_complex_value_t *ccv)
+{
+    ngx_str_t                    *v;
+    ngx_uint_t                    i, n, nv, nc;
+    ngx_array_t                   flushes, lengths, values, *pf, *pl, *pv;
+    ngx_stream_script_compile_t   sc;
+
+    v = ccv->value;
+
+    nv = 0;
+    nc = 0;
+
+    for (i = 0; i < v->len; i++) {
+        if (v->data[i] == '$') {
+            if (v->data[i + 1] >= '1' && v->data[i + 1] <= '9') {
+                nc++;
+
+            } else {
+                nv++;
+            }
+        }
+    }
+
+    if ((v->len == 0 || v->data[0] != '$')
+        && (ccv->conf_prefix || ccv->root_prefix))
+    {
+        if (ngx_conf_full_name(ccv->cf->cycle, v, ccv->conf_prefix) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        ccv->conf_prefix = 0;
+        ccv->root_prefix = 0;
+    }
+
+    ccv->complex_value->value = *v;
+    ccv->complex_value->flushes = NULL;
+    ccv->complex_value->lengths = NULL;
+    ccv->complex_value->values = NULL;
+
+    if (nv == 0 && nc == 0) {
+        return NGX_OK;
+    }
+
+    n = nv + 1;
+
+    if (ngx_array_init(&flushes, ccv->cf->pool, n, sizeof(ngx_uint_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    n = nv * (2 * sizeof(ngx_stream_script_copy_code_t)
+                  + sizeof(ngx_stream_script_var_code_t))
+        + sizeof(uintptr_t);
+
+    if (ngx_array_init(&lengths, ccv->cf->pool, n, 1) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    n = (nv * (2 * sizeof(ngx_stream_script_copy_code_t)
+                   + sizeof(ngx_stream_script_var_code_t))
+                + sizeof(uintptr_t)
+                + v->len
+                + sizeof(uintptr_t) - 1)
+            & ~(sizeof(uintptr_t) - 1);
+
+    if (ngx_array_init(&values, ccv->cf->pool, n, 1) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    pf = &flushes;
+    pl = &lengths;
+    pv = &values;
+
+    ngx_memzero(&sc, sizeof(ngx_stream_script_compile_t));
+
+    sc.cf = ccv->cf;
+    sc.source = v;
+    sc.flushes = &pf;
+    sc.lengths = &pl;
+    sc.values = &pv;
+    sc.complete_lengths = 1;
+    sc.complete_values = 1;
+    sc.zero = ccv->zero;
+    sc.conf_prefix = ccv->conf_prefix;
+    sc.root_prefix = ccv->root_prefix;
+
+    if (ngx_stream_script_compile(&sc) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (flushes.nelts) {
+        ccv->complex_value->flushes = flushes.elts;
+        ccv->complex_value->flushes[flushes.nelts] = (ngx_uint_t) -1;
+    }
+
+    ccv->complex_value->lengths = lengths.elts;
+    ccv->complex_value->values = values.elts;
+
+    return NGX_OK;
+}
+
+
+char *
+ngx_stream_set_complex_value_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 != NULL) {
+        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;
+
+    if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+ngx_uint_t
+ngx_stream_script_variables_count(ngx_str_t *value)
+{
+    ngx_uint_t  i, n;
+
+    for (n = 0, i = 0; i < value->len; i++) {
+        if (value->data[i] == '$') {
+            n++;
+        }
+    }
+
+    return n;
+}
+
+
+ngx_int_t
+ngx_stream_script_compile(ngx_stream_script_compile_t *sc)
+{
+    u_char       ch;
+    ngx_str_t    name;
+    ngx_uint_t   i, bracket;
+
+    if (ngx_stream_script_init_arrays(sc) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    for (i = 0; i < sc->source->len; /* void */ ) {
+
+        name.len = 0;
+
+        if (sc->source->data[i] == '$') {
+
+            if (++i == sc->source->len) {
+                goto invalid_variable;
+            }
+
+            if (sc->source->data[i] >= '1' && sc->source->data[i] <= '9') {
+#if (NGX_PCRE)
+                ngx_uint_t  n;
+
+                n = sc->source->data[i] - '0';
+
+                if (ngx_stream_script_add_capture_code(sc, n) != NGX_OK) {
+                    return NGX_ERROR;
+                }
+
+                i++;
+
+                continue;
+#else
+                ngx_conf_log_error(NGX_LOG_EMERG, sc->cf, 0,
+                                   "using variable \"$%c\" requires "
+                                   "PCRE library", sc->source->data[i]);
+                return NGX_ERROR;
+#endif
+            }
+
+            if (sc->source->data[i] == '{') {
+                bracket = 1;
+
+                if (++i == sc->source->len) {
+                    goto invalid_variable;
+                }
+
+                name.data = &sc->source->data[i];
+
+            } else {
+                bracket = 0;
+                name.data = &sc->source->data[i];
+            }
+
+            for ( /* void */ ; i < sc->source->len; i++, name.len++) {
+                ch = sc->source->data[i];
+
+                if (ch == '}' && bracket) {
+                    i++;
+                    bracket = 0;
+                    break;
+                }
+
+                if ((ch >= 'A' && ch <= 'Z')
+                    || (ch >= 'a' && ch <= 'z')
+                    || (ch >= '0' && ch <= '9')
+                    || ch == '_')
+                {
+                    continue;
+                }
+
+                break;
+            }
+
+            if (bracket) {
+                ngx_conf_log_error(NGX_LOG_EMERG, sc->cf, 0,
+                                   "the closing bracket in \"%V\" "
+                                   "variable is missing", &name);
+                return NGX_ERROR;
+            }
+
+            if (name.len == 0) {
+                goto invalid_variable;
+            }
+
+            sc->variables++;
+
+            if (ngx_stream_script_add_var_code(sc, &name) != NGX_OK) {
+                return NGX_ERROR;
+            }
+
+            continue;
+        }
+
+        name.data = &sc->source->data[i];
+
+        while (i < sc->source->len) {
+
+            if (sc->source->data[i] == '$') {
+                break;
+            }
+
+            i++;
+            name.len++;
+        }
+
+        sc->size += name.len;
+
+        if (ngx_stream_script_add_copy_code(sc, &name, (i == sc->source->len))
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    return ngx_stream_script_done(sc);
+
+invalid_variable:
+
+    ngx_conf_log_error(NGX_LOG_EMERG, sc->cf, 0, "invalid variable name");
+
+    return NGX_ERROR;
+}
+
+
+u_char *
+ngx_stream_script_run(ngx_stream_session_t *s, ngx_str_t *value,
+    void *code_lengths, size_t len, void *code_values)
+{
+    ngx_uint_t                      i;
+    ngx_stream_script_code_pt       code;
+    ngx_stream_script_engine_t      e;
+    ngx_stream_core_main_conf_t    *cmcf;
+    ngx_stream_script_len_code_pt   lcode;
+
+    cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module);
+
+    for (i = 0; i < cmcf->variables.nelts; i++) {
+        if (s->variables[i].no_cacheable) {
+            s->variables[i].valid = 0;
+            s->variables[i].not_found = 0;
+        }
+    }
+
+    ngx_memzero(&e, sizeof(ngx_stream_script_engine_t));
+
+    e.ip = code_lengths;
+    e.session = s;
+    e.flushed = 1;
+
+    while (*(uintptr_t *) e.ip) {
+        lcode = *(ngx_stream_script_len_code_pt *) e.ip;
+        len += lcode(&e);
+    }
+
+
+    value->len = len;
+    value->data = ngx_pnalloc(s->connection->pool, len);
+    if (value->data == NULL) {
+        return NULL;
+    }
+
+    e.ip = code_values;
+    e.pos = value->data;
+
+    while (*(uintptr_t *) e.ip) {
+        code = *(ngx_stream_script_code_pt *) e.ip;
+        code((ngx_stream_script_engine_t *) &e);
+    }
+
+    return e.pos;
+}
+
+
+void
+ngx_stream_script_flush_no_cacheable_variables(ngx_stream_session_t *s,
+    ngx_array_t *indices)
+{
+    ngx_uint_t  n, *index;
+
+    if (indices) {
+        index = indices->elts;
+        for (n = 0; n < indices->nelts; n++) {
+            if (s->variables[index[n]].no_cacheable) {
+                s->variables[index[n]].valid = 0;
+                s->variables[index[n]].not_found = 0;
+            }
+        }
+    }
+}
+
+
+static ngx_int_t
+ngx_stream_script_init_arrays(ngx_stream_script_compile_t *sc)
+{
+    ngx_uint_t   n;
+
+    if (sc->flushes && *sc->flushes == NULL) {
+        n = sc->variables ? sc->variables : 1;
+        *sc->flushes = ngx_array_create(sc->cf->pool, n, sizeof(ngx_uint_t));
+        if (*sc->flushes == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    if (*sc->lengths == NULL) {
+        n = sc->variables * (2 * sizeof(ngx_stream_script_copy_code_t)
+                             + sizeof(ngx_stream_script_var_code_t))
+            + sizeof(uintptr_t);
+
+        *sc->lengths = ngx_array_create(sc->cf->pool, n, 1);
+        if (*sc->lengths == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    if (*sc->values == NULL) {
+        n = (sc->variables * (2 * sizeof(ngx_stream_script_copy_code_t)
+                              + sizeof(ngx_stream_script_var_code_t))
+                + sizeof(uintptr_t)
+                + sc->source->len
+                + sizeof(uintptr_t) - 1)
+            & ~(sizeof(uintptr_t) - 1);
+
+        *sc->values = ngx_array_create(sc->cf->pool, n, 1);
+        if (*sc->values == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    sc->variables = 0;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_script_done(ngx_stream_script_compile_t *sc)
+{
+    ngx_str_t    zero;
+    uintptr_t   *code;
+
+    if (sc->zero) {
+
+        zero.len = 1;
+        zero.data = (u_char *) "\0";
+
+        if (ngx_stream_script_add_copy_code(sc, &zero, 0) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    if (sc->conf_prefix || sc->root_prefix) {
+        if (ngx_stream_script_add_full_name_code(sc) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    if (sc->complete_lengths) {
+        code = ngx_stream_script_add_code(*sc->lengths, sizeof(uintptr_t),
+                                          NULL);
+        if (code == NULL) {
+            return NGX_ERROR;
+        }
+
+        *code = (uintptr_t) NULL;
+    }
+
+    if (sc->complete_values) {
+        code = ngx_stream_script_add_code(*sc->values, sizeof(uintptr_t),
+                                          &sc->main);
+        if (code == NULL) {
+            return NGX_ERROR;
+        }
+
+        *code = (uintptr_t) NULL;
+    }
+
+    return NGX_OK;
+}
+
+
+void *
+ngx_stream_script_add_code(ngx_array_t *codes, size_t size, void *code)
+{
+    u_char  *elts, **p;
+    void    *new;
+
+    elts = codes->elts;
+
+    new = ngx_array_push_n(codes, size);
+    if (new == NULL) {
+        return NULL;
+    }
+
+    if (code) {
+        if (elts != codes->elts) {
+            p = code;
+            *p += (u_char *) codes->elts - elts;
+        }
+    }
+
+    return new;
+}
+
+
+static ngx_int_t
+ngx_stream_script_add_copy_code(ngx_stream_script_compile_t *sc,
+    ngx_str_t *value, ngx_uint_t last)
+{
+    u_char                         *p;
+    size_t                          size, len, zero;
+    ngx_stream_script_copy_code_t  *code;
+
+    zero = (sc->zero && last);
+    len = value->len + zero;
+
+    code = ngx_stream_script_add_code(*sc->lengths,
+                                      sizeof(ngx_stream_script_copy_code_t),
+                                      NULL);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = (ngx_stream_script_code_pt) (void *)
+                                               ngx_stream_script_copy_len_code;
+    code->len = len;
+
+    size = (sizeof(ngx_stream_script_copy_code_t) + len + sizeof(uintptr_t) - 1)
+            & ~(sizeof(uintptr_t) - 1);
+
+    code = ngx_stream_script_add_code(*sc->values, size, &sc->main);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = ngx_stream_script_copy_code;
+    code->len = len;
+
+    p = ngx_cpymem((u_char *) code + sizeof(ngx_stream_script_copy_code_t),
+                   value->data, value->len);
+
+    if (zero) {
+        *p = '\0';
+        sc->zero = 0;
+    }
+
+    return NGX_OK;
+}
+
+
+size_t
+ngx_stream_script_copy_len_code(ngx_stream_script_engine_t *e)
+{
+    ngx_stream_script_copy_code_t  *code;
+
+    code = (ngx_stream_script_copy_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_stream_script_copy_code_t);
+
+    return code->len;
+}
+
+
+void
+ngx_stream_script_copy_code(ngx_stream_script_engine_t *e)
+{
+    u_char                         *p;
+    ngx_stream_script_copy_code_t  *code;
+
+    code = (ngx_stream_script_copy_code_t *) e->ip;
+
+    p = e->pos;
+
+    if (!e->skip) {
+        e->pos = ngx_copy(p, e->ip + sizeof(ngx_stream_script_copy_code_t),
+                          code->len);
+    }
+
+    e->ip += sizeof(ngx_stream_script_copy_code_t)
+          + ((code->len + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1));
+
+    ngx_log_debug2(NGX_LOG_DEBUG_STREAM, e->session->connection->log, 0,
+                   "stream script copy: \"%*s\"", e->pos - p, p);
+}
+
+
+static ngx_int_t
+ngx_stream_script_add_var_code(ngx_stream_script_compile_t *sc, ngx_str_t *name)
+{
+    ngx_int_t                      index, *p;
+    ngx_stream_script_var_code_t  *code;
+
+    index = ngx_stream_get_variable_index(sc->cf, name);
+
+    if (index == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if (sc->flushes) {
+        p = ngx_array_push(*sc->flushes);
+        if (p == NULL) {
+            return NGX_ERROR;
+        }
+
+        *p = index;
+    }
+
+    code = ngx_stream_script_add_code(*sc->lengths,
+                                      sizeof(ngx_stream_script_var_code_t),
+                                      NULL);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = (ngx_stream_script_code_pt) (void *)
+                                           ngx_stream_script_copy_var_len_code;
+    code->index = (uintptr_t) index;
+
+    code = ngx_stream_script_add_code(*sc->values,
+                                      sizeof(ngx_stream_script_var_code_t),
+                                      &sc->main);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = ngx_stream_script_copy_var_code;
+    code->index = (uintptr_t) index;
+
+    return NGX_OK;
+}
+
+
+size_t
+ngx_stream_script_copy_var_len_code(ngx_stream_script_engine_t *e)
+{
+    ngx_stream_variable_value_t   *value;
+    ngx_stream_script_var_code_t  *code;
+
+    code = (ngx_stream_script_var_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_stream_script_var_code_t);
+
+    if (e->flushed) {
+        value = ngx_stream_get_indexed_variable(e->session, code->index);
+
+    } else {
+        value = ngx_stream_get_flushed_variable(e->session, code->index);
+    }
+
+    if (value && !value->not_found) {
+        return value->len;
+    }
+
+    return 0;
+}
+
+
+void
+ngx_stream_script_copy_var_code(ngx_stream_script_engine_t *e)
+{
+    u_char                        *p;
+    ngx_stream_variable_value_t   *value;
+    ngx_stream_script_var_code_t  *code;
+
+    code = (ngx_stream_script_var_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_stream_script_var_code_t);
+
+    if (!e->skip) {
+
+        if (e->flushed) {
+            value = ngx_stream_get_indexed_variable(e->session, code->index);
+
+        } else {
+            value = ngx_stream_get_flushed_variable(e->session, code->index);
+        }
+
+        if (value && !value->not_found) {
+            p = e->pos;
+            e->pos = ngx_copy(p, value->data, value->len);
+
+            ngx_log_debug2(NGX_LOG_DEBUG_STREAM,
+                           e->session->connection->log, 0,
+                           "stream script var: \"%*s\"", e->pos - p, p);
+        }
+    }
+}
+
+
+#if (NGX_PCRE)
+
+static ngx_int_t
+ngx_stream_script_add_capture_code(ngx_stream_script_compile_t *sc,
+    ngx_uint_t n)
+{
+    ngx_stream_script_copy_capture_code_t  *code;
+
+    code = ngx_stream_script_add_code(*sc->lengths,
+                                  sizeof(ngx_stream_script_copy_capture_code_t),
+                                  NULL);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = (ngx_stream_script_code_pt) (void *)
+                                       ngx_stream_script_copy_capture_len_code;
+    code->n = 2 * n;
+
+
+    code = ngx_stream_script_add_code(*sc->values,
+                                  sizeof(ngx_stream_script_copy_capture_code_t),
+                                  &sc->main);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = ngx_stream_script_copy_capture_code;
+    code->n = 2 * n;
+
+    if (sc->ncaptures < n) {
+        sc->ncaptures = n;
+    }
+
+    return NGX_OK;
+}
+
+
+size_t
+ngx_stream_script_copy_capture_len_code(ngx_stream_script_engine_t *e)
+{
+    int                                    *cap;
+    ngx_uint_t                              n;
+    ngx_stream_session_t                   *s;
+    ngx_stream_script_copy_capture_code_t  *code;
+
+    s = e->session;
+
+    code = (ngx_stream_script_copy_capture_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_stream_script_copy_capture_code_t);
+
+    n = code->n;
+
+    if (n < s->ncaptures) {
+        cap = s->captures;
+        return cap[n + 1] - cap[n];
+    }
+
+    return 0;
+}
+
+
+void
+ngx_stream_script_copy_capture_code(ngx_stream_script_engine_t *e)
+{
+    int                                    *cap;
+    u_char                                 *p, *pos;
+    ngx_uint_t                              n;
+    ngx_stream_session_t                   *s;
+    ngx_stream_script_copy_capture_code_t  *code;
+
+    s = e->session;
+
+    code = (ngx_stream_script_copy_capture_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_stream_script_copy_capture_code_t);
+
+    n = code->n;
+
+    pos = e->pos;
+
+    if (n < s->ncaptures) {
+        cap = s->captures;
+        p = s->captures_data;
+        e->pos = ngx_copy(pos, &p[cap[n]], cap[n + 1] - cap[n]);
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_STREAM, e->session->connection->log, 0,
+                   "stream script capture: \"%*s\"", e->pos - pos, pos);
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_stream_script_add_full_name_code(ngx_stream_script_compile_t *sc)
+{
+    ngx_stream_script_full_name_code_t  *code;
+
+    code = ngx_stream_script_add_code(*sc->lengths,
+                                    sizeof(ngx_stream_script_full_name_code_t),
+                                    NULL);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = (ngx_stream_script_code_pt) (void *)
+                                          ngx_stream_script_full_name_len_code;
+    code->conf_prefix = sc->conf_prefix;
+
+    code = ngx_stream_script_add_code(*sc->values,
+                        sizeof(ngx_stream_script_full_name_code_t), &sc->main);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = ngx_stream_script_full_name_code;
+    code->conf_prefix = sc->conf_prefix;
+
+    return NGX_OK;
+}
+
+
+static size_t
+ngx_stream_script_full_name_len_code(ngx_stream_script_engine_t *e)
+{
+    ngx_stream_script_full_name_code_t  *code;
+
+    code = (ngx_stream_script_full_name_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_stream_script_full_name_code_t);
+
+    return code->conf_prefix ? ngx_cycle->conf_prefix.len:
+                               ngx_cycle->prefix.len;
+}
+
+
+static void
+ngx_stream_script_full_name_code(ngx_stream_script_engine_t *e)
+{
+    ngx_stream_script_full_name_code_t  *code;
+
+    ngx_str_t  value, *prefix;
+
+    code = (ngx_stream_script_full_name_code_t *) e->ip;
+
+    value.data = e->buf.data;
+    value.len = e->pos - e->buf.data;
+
+    prefix = code->conf_prefix ? (ngx_str_t *) &ngx_cycle->conf_prefix:
+                                 (ngx_str_t *) &ngx_cycle->prefix;
+
+    if (ngx_get_full_name(e->session->connection->pool, prefix, &value)
+        != NGX_OK)
+    {
+        e->ip = ngx_stream_script_exit;
+        return;
+    }
+
+    e->buf = value;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, e->session->connection->log, 0,
+                   "stream script fullname: \"%V\"", &value);
+
+    e->ip += sizeof(ngx_stream_script_full_name_code_t);
+}
diff --git a/nginx/src/stream/ngx_stream_script.h b/nginx/src/stream/ngx_stream_script.h
new file mode 100644 (file)
index 0000000..25a450d
--- /dev/null
@@ -0,0 +1,127 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_STREAM_SCRIPT_H_INCLUDED_
+#define _NGX_STREAM_SCRIPT_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+typedef struct {
+    u_char                       *ip;
+    u_char                       *pos;
+    ngx_stream_variable_value_t  *sp;
+
+    ngx_str_t                     buf;
+    ngx_str_t                     line;
+
+    unsigned                      flushed:1;
+    unsigned                      skip:1;
+
+    ngx_stream_session_t         *session;
+} ngx_stream_script_engine_t;
+
+
+typedef struct {
+    ngx_conf_t                   *cf;
+    ngx_str_t                    *source;
+
+    ngx_array_t                 **flushes;
+    ngx_array_t                 **lengths;
+    ngx_array_t                 **values;
+
+    ngx_uint_t                    variables;
+    ngx_uint_t                    ncaptures;
+    ngx_uint_t                    size;
+
+    void                         *main;
+
+    unsigned                      complete_lengths:1;
+    unsigned                      complete_values:1;
+    unsigned                      zero:1;
+    unsigned                      conf_prefix:1;
+    unsigned                      root_prefix:1;
+} ngx_stream_script_compile_t;
+
+
+typedef struct {
+    ngx_str_t                     value;
+    ngx_uint_t                   *flushes;
+    void                         *lengths;
+    void                         *values;
+} ngx_stream_complex_value_t;
+
+
+typedef struct {
+    ngx_conf_t                   *cf;
+    ngx_str_t                    *value;
+    ngx_stream_complex_value_t   *complex_value;
+
+    unsigned                      zero:1;
+    unsigned                      conf_prefix:1;
+    unsigned                      root_prefix:1;
+} ngx_stream_compile_complex_value_t;
+
+
+typedef void (*ngx_stream_script_code_pt) (ngx_stream_script_engine_t *e);
+typedef size_t (*ngx_stream_script_len_code_pt) (ngx_stream_script_engine_t *e);
+
+
+typedef struct {
+    ngx_stream_script_code_pt     code;
+    uintptr_t                     len;
+} ngx_stream_script_copy_code_t;
+
+
+typedef struct {
+    ngx_stream_script_code_pt     code;
+    uintptr_t                     index;
+} ngx_stream_script_var_code_t;
+
+
+typedef struct {
+    ngx_stream_script_code_pt     code;
+    uintptr_t                     n;
+} ngx_stream_script_copy_capture_code_t;
+
+
+typedef struct {
+    ngx_stream_script_code_pt     code;
+    uintptr_t                     conf_prefix;
+} ngx_stream_script_full_name_code_t;
+
+
+void ngx_stream_script_flush_complex_value(ngx_stream_session_t *s,
+    ngx_stream_complex_value_t *val);
+ngx_int_t ngx_stream_complex_value(ngx_stream_session_t *s,
+    ngx_stream_complex_value_t *val, ngx_str_t *value);
+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);
+
+
+ngx_uint_t ngx_stream_script_variables_count(ngx_str_t *value);
+ngx_int_t ngx_stream_script_compile(ngx_stream_script_compile_t *sc);
+u_char *ngx_stream_script_run(ngx_stream_session_t *s, ngx_str_t *value,
+    void *code_lengths, size_t reserved, void *code_values);
+void ngx_stream_script_flush_no_cacheable_variables(ngx_stream_session_t *s,
+    ngx_array_t *indices);
+
+void *ngx_stream_script_add_code(ngx_array_t *codes, size_t size, void *code);
+
+size_t ngx_stream_script_copy_len_code(ngx_stream_script_engine_t *e);
+void ngx_stream_script_copy_code(ngx_stream_script_engine_t *e);
+size_t ngx_stream_script_copy_var_len_code(ngx_stream_script_engine_t *e);
+void ngx_stream_script_copy_var_code(ngx_stream_script_engine_t *e);
+size_t ngx_stream_script_copy_capture_len_code(ngx_stream_script_engine_t *e);
+void ngx_stream_script_copy_capture_code(ngx_stream_script_engine_t *e);
+
+#endif /* _NGX_STREAM_SCRIPT_H_INCLUDED_ */
diff --git a/nginx/src/stream/ngx_stream_split_clients_module.c b/nginx/src/stream/ngx_stream_split_clients_module.c
new file mode 100644 (file)
index 0000000..af6c8a1
--- /dev/null
@@ -0,0 +1,244 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+typedef struct {
+    uint32_t                      percent;
+    ngx_stream_variable_value_t   value;
+} ngx_stream_split_clients_part_t;
+
+
+typedef struct {
+    ngx_stream_complex_value_t    value;
+    ngx_array_t                   parts;
+} ngx_stream_split_clients_ctx_t;
+
+
+static char *ngx_conf_split_clients_block(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_stream_split_clients(ngx_conf_t *cf, ngx_command_t *dummy,
+    void *conf);
+
+static ngx_command_t  ngx_stream_split_clients_commands[] = {
+
+    { ngx_string("split_clients"),
+      NGX_STREAM_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE2,
+      ngx_conf_split_clients_block,
+      NGX_STREAM_MAIN_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_stream_module_t  ngx_stream_split_clients_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL                                   /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_stream_split_clients_module = {
+    NGX_MODULE_V1,
+    &ngx_stream_split_clients_module_ctx,  /* module context */
+    ngx_stream_split_clients_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_split_clients_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    ngx_stream_split_clients_ctx_t *ctx =
+                                       (ngx_stream_split_clients_ctx_t *) data;
+
+    uint32_t                          hash;
+    ngx_str_t                         val;
+    ngx_uint_t                        i;
+    ngx_stream_split_clients_part_t  *part;
+
+    *v = ngx_stream_variable_null_value;
+
+    if (ngx_stream_complex_value(s, &ctx->value, &val) != NGX_OK) {
+        return NGX_OK;
+    }
+
+    hash = ngx_murmur_hash2(val.data, val.len);
+
+    part = ctx->parts.elts;
+
+    for (i = 0; i < ctx->parts.nelts; i++) {
+
+        ngx_log_debug2(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                       "stream split: %uD %uD", hash, part[i].percent);
+
+        if (hash < part[i].percent || part[i].percent == 0) {
+            *v = part[i].value;
+            return NGX_OK;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_conf_split_clients_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char                                *rv;
+    uint32_t                             sum, last;
+    ngx_str_t                           *value, name;
+    ngx_uint_t                           i;
+    ngx_conf_t                           save;
+    ngx_stream_variable_t               *var;
+    ngx_stream_split_clients_ctx_t      *ctx;
+    ngx_stream_split_clients_part_t     *part;
+    ngx_stream_compile_complex_value_t   ccv;
+
+    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_stream_split_clients_ctx_t));
+    if (ctx == 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 = &ctx->value;
+
+    if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    name = value[2];
+
+    if (name.data[0] != '$') {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid variable name \"%V\"", &name);
+        return NGX_CONF_ERROR;
+    }
+
+    name.len--;
+    name.data++;
+
+    var = ngx_stream_add_variable(cf, &name, NGX_STREAM_VAR_CHANGEABLE);
+    if (var == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    var->get_handler = ngx_stream_split_clients_variable;
+    var->data = (uintptr_t) ctx;
+
+    if (ngx_array_init(&ctx->parts, cf->pool, 2,
+                       sizeof(ngx_stream_split_clients_part_t))
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    save = *cf;
+    cf->ctx = ctx;
+    cf->handler = ngx_stream_split_clients;
+    cf->handler_conf = conf;
+
+    rv = ngx_conf_parse(cf, NULL);
+
+    *cf = save;
+
+    if (rv != NGX_CONF_OK) {
+        return rv;
+    }
+
+    sum = 0;
+    last = 0;
+    part = ctx->parts.elts;
+
+    for (i = 0; i < ctx->parts.nelts; i++) {
+        sum = part[i].percent ? sum + part[i].percent : 10000;
+        if (sum > 10000) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "percent total is greater than 100%%");
+            return NGX_CONF_ERROR;
+        }
+
+        if (part[i].percent) {
+            last += part[i].percent * (uint64_t) 0xffffffff / 10000;
+            part[i].percent = last;
+        }
+    }
+
+    return rv;
+}
+
+
+static char *
+ngx_stream_split_clients(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
+{
+    ngx_int_t                         n;
+    ngx_str_t                        *value;
+    ngx_stream_split_clients_ctx_t   *ctx;
+    ngx_stream_split_clients_part_t  *part;
+
+    ctx = cf->ctx;
+    value = cf->args->elts;
+
+    part = ngx_array_push(&ctx->parts);
+    if (part == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (value[0].len == 1 && value[0].data[0] == '*') {
+        part->percent = 0;
+
+    } else {
+        if (value[0].len == 0 || value[0].data[value[0].len - 1] != '%') {
+            goto invalid;
+        }
+
+        n = ngx_atofp(value[0].data, value[0].len - 1, 2);
+        if (n == NGX_ERROR || n == 0) {
+            goto invalid;
+        }
+
+        part->percent = (uint32_t) n;
+    }
+
+    part->value.len = value[1].len;
+    part->value.valid = 1;
+    part->value.no_cacheable = 0;
+    part->value.not_found = 0;
+    part->value.data = value[1].data;
+
+    return NGX_CONF_OK;
+
+invalid:
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "invalid percent value \"%V\"", &value[0]);
+    return NGX_CONF_ERROR;
+}
diff --git a/nginx/src/stream/ngx_stream_ssl_module.c b/nginx/src/stream/ngx_stream_ssl_module.c
new file mode 100644 (file)
index 0000000..3e5a1f2
--- /dev/null
@@ -0,0 +1,851 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c,
+    ngx_pool_t *pool, ngx_str_t *s);
+
+
+#define NGX_DEFAULT_CIPHERS     "HIGH:!aNULL:!MD5"
+#define NGX_DEFAULT_ECDH_CURVE  "auto"
+
+
+static ngx_int_t ngx_stream_ssl_handler(ngx_stream_session_t *s);
+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);
+static ngx_int_t ngx_stream_ssl_static_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_ssl_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+
+static ngx_int_t ngx_stream_ssl_add_variables(ngx_conf_t *cf);
+static void *ngx_stream_ssl_create_conf(ngx_conf_t *cf);
+static char *ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+
+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 ngx_int_t ngx_stream_ssl_init(ngx_conf_t *cf);
+
+
+static ngx_conf_bitmask_t  ngx_stream_ssl_protocols[] = {
+    { ngx_string("SSLv2"), NGX_SSL_SSLv2 },
+    { ngx_string("SSLv3"), NGX_SSL_SSLv3 },
+    { ngx_string("TLSv1"), NGX_SSL_TLSv1 },
+    { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 },
+    { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 },
+    { ngx_string("TLSv1.3"), NGX_SSL_TLSv1_3 },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_enum_t  ngx_stream_ssl_verify[] = {
+    { ngx_string("off"), 0 },
+    { ngx_string("on"), 1 },
+    { ngx_string("optional"), 2 },
+    { ngx_string("optional_no_ca"), 3 },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t  ngx_stream_ssl_commands[] = {
+
+    { ngx_string("ssl_handshake_timeout"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_ssl_conf_t, handshake_timeout),
+      NULL },
+
+    { ngx_string("ssl_certificate"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_ssl_conf_t, certificates),
+      NULL },
+
+    { ngx_string("ssl_certificate_key"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_ssl_conf_t, certificate_keys),
+      NULL },
+
+    { ngx_string("ssl_password_file"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_stream_ssl_password_file,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("ssl_dhparam"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_ssl_conf_t, dhparam),
+      NULL },
+
+    { ngx_string("ssl_ecdh_curve"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_ssl_conf_t, ecdh_curve),
+      NULL },
+
+    { ngx_string("ssl_protocols"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_ssl_conf_t, protocols),
+      &ngx_stream_ssl_protocols },
+
+    { ngx_string("ssl_ciphers"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_ssl_conf_t, ciphers),
+      NULL },
+
+    { ngx_string("ssl_verify_client"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_ssl_conf_t, verify),
+      &ngx_stream_ssl_verify },
+
+    { ngx_string("ssl_verify_depth"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_ssl_conf_t, verify_depth),
+      NULL },
+
+    { ngx_string("ssl_client_certificate"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_ssl_conf_t, client_certificate),
+      NULL },
+
+    { ngx_string("ssl_trusted_certificate"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_ssl_conf_t, trusted_certificate),
+      NULL },
+
+    { ngx_string("ssl_prefer_server_ciphers"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_ssl_conf_t, prefer_server_ciphers),
+      NULL },
+
+    { ngx_string("ssl_session_cache"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE12,
+      ngx_stream_ssl_session_cache,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("ssl_session_tickets"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_ssl_conf_t, session_tickets),
+      NULL },
+
+    { ngx_string("ssl_session_ticket_key"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_ssl_conf_t, session_ticket_keys),
+      NULL },
+
+    { ngx_string("ssl_session_timeout"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_sec_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_ssl_conf_t, session_timeout),
+      NULL },
+
+    { ngx_string("ssl_crl"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_ssl_conf_t, crl),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_stream_module_t  ngx_stream_ssl_module_ctx = {
+    ngx_stream_ssl_add_variables,          /* preconfiguration */
+    ngx_stream_ssl_init,                   /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_stream_ssl_create_conf,            /* create server configuration */
+    ngx_stream_ssl_merge_conf              /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_stream_ssl_module = {
+    NGX_MODULE_V1,
+    &ngx_stream_ssl_module_ctx,            /* module context */
+    ngx_stream_ssl_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_stream_variable_t  ngx_stream_ssl_vars[] = {
+
+    { ngx_string("ssl_protocol"), NULL, ngx_stream_ssl_static_variable,
+      (uintptr_t) ngx_ssl_get_protocol, NGX_STREAM_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_cipher"), NULL, ngx_stream_ssl_static_variable,
+      (uintptr_t) ngx_ssl_get_cipher_name, NGX_STREAM_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_ciphers"), NULL, ngx_stream_ssl_variable,
+      (uintptr_t) ngx_ssl_get_ciphers, 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 },
+
+    { ngx_string("ssl_session_id"), NULL, ngx_stream_ssl_variable,
+      (uintptr_t) ngx_ssl_get_session_id, NGX_STREAM_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_session_reused"), NULL, ngx_stream_ssl_variable,
+      (uintptr_t) ngx_ssl_get_session_reused, NGX_STREAM_VAR_CHANGEABLE, 0 },
+
+    { 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_client_cert"), NULL, ngx_stream_ssl_variable,
+      (uintptr_t) ngx_ssl_get_certificate, NGX_STREAM_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_raw_cert"), NULL, ngx_stream_ssl_variable,
+      (uintptr_t) ngx_ssl_get_raw_certificate,
+      NGX_STREAM_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_escaped_cert"), NULL, ngx_stream_ssl_variable,
+      (uintptr_t) ngx_ssl_get_escaped_certificate,
+      NGX_STREAM_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_s_dn"), NULL, ngx_stream_ssl_variable,
+      (uintptr_t) ngx_ssl_get_subject_dn, NGX_STREAM_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_i_dn"), NULL, ngx_stream_ssl_variable,
+      (uintptr_t) ngx_ssl_get_issuer_dn, NGX_STREAM_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_serial"), NULL, ngx_stream_ssl_variable,
+      (uintptr_t) ngx_ssl_get_serial_number, NGX_STREAM_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_fingerprint"), NULL, ngx_stream_ssl_variable,
+      (uintptr_t) ngx_ssl_get_fingerprint, NGX_STREAM_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_verify"), NULL, ngx_stream_ssl_variable,
+      (uintptr_t) ngx_ssl_get_client_verify, NGX_STREAM_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_v_start"), NULL, ngx_stream_ssl_variable,
+      (uintptr_t) ngx_ssl_get_client_v_start, NGX_STREAM_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_v_end"), NULL, ngx_stream_ssl_variable,
+      (uintptr_t) ngx_ssl_get_client_v_end, NGX_STREAM_VAR_CHANGEABLE, 0 },
+
+    { ngx_string("ssl_client_v_remain"), NULL, ngx_stream_ssl_variable,
+      (uintptr_t) ngx_ssl_get_client_v_remain, NGX_STREAM_VAR_CHANGEABLE, 0 },
+
+      ngx_stream_null_variable
+};
+
+
+static ngx_str_t ngx_stream_ssl_sess_id_ctx = ngx_string("STREAM");
+
+
+static ngx_int_t
+ngx_stream_ssl_handler(ngx_stream_session_t *s)
+{
+    long                    rc;
+    X509                   *cert;
+    ngx_int_t               rv;
+    ngx_connection_t       *c;
+    ngx_stream_ssl_conf_t  *sslcf;
+
+    if (!s->ssl) {
+        return NGX_OK;
+    }
+
+    c = s->connection;
+
+    sslcf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_module);
+
+    if (c->ssl == NULL) {
+        c->log->action = "SSL handshaking";
+
+        if (sslcf->ssl.ctx == NULL) {
+            ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                          "no \"ssl_certificate\" is defined "
+                          "in server listening on SSL port");
+            return NGX_ERROR;
+        }
+
+        rv = ngx_stream_ssl_init_connection(&sslcf->ssl, c);
+
+        if (rv != NGX_OK) {
+            return rv;
+        }
+    }
+
+    if (sslcf->verify) {
+        rc = SSL_get_verify_result(c->ssl->connection);
+
+        if (rc != X509_V_OK
+            && (sslcf->verify != 3 || !ngx_ssl_verify_error_optional(rc)))
+        {
+            ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                          "client SSL certificate verify error: (%l:%s)",
+                          rc, X509_verify_cert_error_string(rc));
+
+            ngx_ssl_remove_cached_session(c->ssl->session_ctx,
+                                       (SSL_get0_session(c->ssl->connection)));
+            return NGX_ERROR;
+        }
+
+        if (sslcf->verify == 1) {
+            cert = SSL_get_peer_certificate(c->ssl->connection);
+
+            if (cert == NULL) {
+                ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                              "client sent no required SSL certificate");
+
+                ngx_ssl_remove_cached_session(c->ssl->session_ctx,
+                                       (SSL_get0_session(c->ssl->connection)));
+                return NGX_ERROR;
+            }
+
+            X509_free(cert);
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c)
+{
+    ngx_int_t                    rc;
+    ngx_stream_session_t        *s;
+    ngx_stream_ssl_conf_t       *sslcf;
+    ngx_stream_core_srv_conf_t  *cscf;
+
+    s = c->data;
+
+    cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module);
+
+    if (cscf->tcp_nodelay && ngx_tcp_nodelay(c) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_ssl_create_connection(ssl, c, 0) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    rc = ngx_ssl_handshake(c);
+
+    if (rc == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if (rc == NGX_AGAIN) {
+        sslcf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_module);
+
+        ngx_add_timer(c->read, sslcf->handshake_timeout);
+
+        c->ssl->handler = ngx_stream_ssl_handshake_handler;
+
+        return NGX_AGAIN;
+    }
+
+    /* rc == NGX_OK */
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_stream_ssl_handshake_handler(ngx_connection_t *c)
+{
+    ngx_stream_session_t  *s;
+
+    s = c->data;
+
+    if (!c->ssl->handshaked) {
+        ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    if (c->read->timer_set) {
+        ngx_del_timer(c->read);
+    }
+
+    ngx_stream_core_run_phases(s);
+}
+
+
+static ngx_int_t
+ngx_stream_ssl_static_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    ngx_ssl_variable_handler_pt  handler = (ngx_ssl_variable_handler_pt) data;
+
+    size_t     len;
+    ngx_str_t  str;
+
+    if (s->connection->ssl) {
+
+        (void) handler(s->connection, NULL, &str);
+
+        v->data = str.data;
+
+        for (len = 0; v->data[len]; len++) { /* void */ }
+
+        v->len = len;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+
+        return NGX_OK;
+    }
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_ssl_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    ngx_ssl_variable_handler_pt  handler = (ngx_ssl_variable_handler_pt) data;
+
+    ngx_str_t  str;
+
+    if (s->connection->ssl) {
+
+        if (handler(s->connection, s->connection->pool, &str) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        v->len = str.len;
+        v->data = str.data;
+
+        if (v->len) {
+            v->valid = 1;
+            v->no_cacheable = 0;
+            v->not_found = 0;
+
+            return NGX_OK;
+        }
+    }
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_ssl_add_variables(ngx_conf_t *cf)
+{
+    ngx_stream_variable_t  *var, *v;
+
+    for (v = ngx_stream_ssl_vars; v->name.len; v++) {
+        var = ngx_stream_add_variable(cf, &v->name, v->flags);
+        if (var == NULL) {
+            return NGX_ERROR;
+        }
+
+        var->get_handler = v->get_handler;
+        var->data = v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_stream_ssl_create_conf(ngx_conf_t *cf)
+{
+    ngx_stream_ssl_conf_t  *scf;
+
+    scf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_ssl_conf_t));
+    if (scf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     scf->protocols = 0;
+     *     scf->dhparam = { 0, NULL };
+     *     scf->ecdh_curve = { 0, NULL };
+     *     scf->client_certificate = { 0, NULL };
+     *     scf->trusted_certificate = { 0, NULL };
+     *     scf->crl = { 0, NULL };
+     *     scf->ciphers = { 0, NULL };
+     *     scf->shm_zone = NULL;
+     */
+
+    scf->handshake_timeout = NGX_CONF_UNSET_MSEC;
+    scf->certificates = NGX_CONF_UNSET_PTR;
+    scf->certificate_keys = NGX_CONF_UNSET_PTR;
+    scf->passwords = NGX_CONF_UNSET_PTR;
+    scf->prefer_server_ciphers = NGX_CONF_UNSET;
+    scf->verify = NGX_CONF_UNSET_UINT;
+    scf->verify_depth = NGX_CONF_UNSET_UINT;
+    scf->builtin_session_cache = NGX_CONF_UNSET;
+    scf->session_timeout = NGX_CONF_UNSET;
+    scf->session_tickets = NGX_CONF_UNSET;
+    scf->session_ticket_keys = NGX_CONF_UNSET_PTR;
+
+    return scf;
+}
+
+
+static char *
+ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_stream_ssl_conf_t *prev = parent;
+    ngx_stream_ssl_conf_t *conf = child;
+
+    ngx_pool_cleanup_t  *cln;
+
+    ngx_conf_merge_msec_value(conf->handshake_timeout,
+                         prev->handshake_timeout, 60000);
+
+    ngx_conf_merge_value(conf->session_timeout,
+                         prev->session_timeout, 300);
+
+    ngx_conf_merge_value(conf->prefer_server_ciphers,
+                         prev->prefer_server_ciphers, 0);
+
+    ngx_conf_merge_bitmask_value(conf->protocols, prev->protocols,
+                         (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1
+                          |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2));
+
+    ngx_conf_merge_uint_value(conf->verify, prev->verify, 0);
+    ngx_conf_merge_uint_value(conf->verify_depth, prev->verify_depth, 1);
+
+    ngx_conf_merge_ptr_value(conf->certificates, prev->certificates, NULL);
+    ngx_conf_merge_ptr_value(conf->certificate_keys, prev->certificate_keys,
+                         NULL);
+
+    ngx_conf_merge_ptr_value(conf->passwords, prev->passwords, NULL);
+
+    ngx_conf_merge_str_value(conf->dhparam, prev->dhparam, "");
+
+    ngx_conf_merge_str_value(conf->client_certificate, prev->client_certificate,
+                         "");
+    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->ecdh_curve, prev->ecdh_curve,
+                         NGX_DEFAULT_ECDH_CURVE);
+
+    ngx_conf_merge_str_value(conf->ciphers, prev->ciphers, NGX_DEFAULT_CIPHERS);
+
+
+    conf->ssl.log = cf->log;
+
+    if (conf->certificates == NULL) {
+        return NGX_CONF_OK;
+    }
+
+    if (conf->certificate_keys == NULL
+        || 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\"",
+                      ((ngx_str_t *) conf->certificates->elts)
+                      + conf->certificates->nelts - 1);
+        return NGX_CONF_ERROR;
+    }
+
+    if (ngx_ssl_create(&conf->ssl, conf->protocols, NULL) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    cln = ngx_pool_cleanup_add(cf->pool, 0);
+    if (cln == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    cln->handler = ngx_ssl_cleanup_ctx;
+    cln->data = &conf->ssl;
+
+    if (ngx_ssl_certificates(cf, &conf->ssl, conf->certificates,
+                             conf->certificate_keys, conf->passwords)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    if (ngx_ssl_ciphers(cf, &conf->ssl, &conf->ciphers,
+                        conf->prefer_server_ciphers)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    if (conf->verify) {
+
+        if (conf->client_certificate.len == 0 && conf->verify != 3) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "no ssl_client_certificate for ssl_client_verify");
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_ssl_client_certificate(cf, &conf->ssl,
+                                       &conf->client_certificate,
+                                       conf->verify_depth)
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_ssl_trusted_certificate(cf, &conf->ssl,
+                                        &conf->trusted_certificate,
+                                        conf->verify_depth)
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_ssl_crl(cf, &conf->ssl, &conf->crl) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (ngx_ssl_dhparam(cf, &conf->ssl, &conf->dhparam) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (ngx_ssl_ecdh_curve(cf, &conf->ssl, &conf->ecdh_curve) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_conf_merge_value(conf->builtin_session_cache,
+                         prev->builtin_session_cache, NGX_SSL_NONE_SCACHE);
+
+    if (conf->shm_zone == NULL) {
+        conf->shm_zone = prev->shm_zone;
+    }
+
+    if (ngx_ssl_session_cache(&conf->ssl, &ngx_stream_ssl_sess_id_ctx,
+                              conf->builtin_session_cache,
+                              conf->shm_zone, conf->session_timeout)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_conf_merge_value(conf->session_tickets,
+                         prev->session_tickets, 1);
+
+#ifdef SSL_OP_NO_TICKET
+    if (!conf->session_tickets) {
+        SSL_CTX_set_options(conf->ssl.ctx, SSL_OP_NO_TICKET);
+    }
+#endif
+
+    ngx_conf_merge_ptr_value(conf->session_ticket_keys,
+                         prev->session_ticket_keys, NULL);
+
+    if (ngx_ssl_session_ticket_keys(cf, &conf->ssl, conf->session_ticket_keys)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_stream_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_ssl_conf_t  *scf = conf;
+
+    ngx_str_t  *value;
+
+    if (scf->passwords != NGX_CONF_UNSET_PTR) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    scf->passwords = ngx_ssl_read_password_file(cf, &value[1]);
+
+    if (scf->passwords == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_ssl_conf_t  *scf = conf;
+
+    size_t       len;
+    ngx_str_t   *value, name, size;
+    ngx_int_t    n;
+    ngx_uint_t   i, j;
+
+    value = cf->args->elts;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+
+        if (ngx_strcmp(value[i].data, "off") == 0) {
+            scf->builtin_session_cache = NGX_SSL_NO_SCACHE;
+            continue;
+        }
+
+        if (ngx_strcmp(value[i].data, "none") == 0) {
+            scf->builtin_session_cache = NGX_SSL_NONE_SCACHE;
+            continue;
+        }
+
+        if (ngx_strcmp(value[i].data, "builtin") == 0) {
+            scf->builtin_session_cache = NGX_SSL_DFLT_BUILTIN_SCACHE;
+            continue;
+        }
+
+        if (value[i].len > sizeof("builtin:") - 1
+            && ngx_strncmp(value[i].data, "builtin:", sizeof("builtin:") - 1)
+               == 0)
+        {
+            n = ngx_atoi(value[i].data + sizeof("builtin:") - 1,
+                         value[i].len - (sizeof("builtin:") - 1));
+
+            if (n == NGX_ERROR) {
+                goto invalid;
+            }
+
+            scf->builtin_session_cache = n;
+
+            continue;
+        }
+
+        if (value[i].len > sizeof("shared:") - 1
+            && ngx_strncmp(value[i].data, "shared:", sizeof("shared:") - 1)
+               == 0)
+        {
+            len = 0;
+
+            for (j = sizeof("shared:") - 1; j < value[i].len; j++) {
+                if (value[i].data[j] == ':') {
+                    break;
+                }
+
+                len++;
+            }
+
+            if (len == 0) {
+                goto invalid;
+            }
+
+            name.len = len;
+            name.data = value[i].data + sizeof("shared:") - 1;
+
+            size.len = value[i].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,
+                                   "session cache \"%V\" is too small",
+                                   &value[i]);
+
+                return NGX_CONF_ERROR;
+            }
+
+            scf->shm_zone = ngx_shared_memory_add(cf, &name, n,
+                                                   &ngx_stream_ssl_module);
+            if (scf->shm_zone == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            scf->shm_zone->init = ngx_ssl_session_cache_init;
+
+            continue;
+        }
+
+        goto invalid;
+    }
+
+    if (scf->shm_zone && scf->builtin_session_cache == NGX_CONF_UNSET) {
+        scf->builtin_session_cache = NGX_SSL_NO_BUILTIN_SCACHE;
+    }
+
+    return NGX_CONF_OK;
+
+invalid:
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "invalid session cache \"%V\"", &value[i]);
+
+    return NGX_CONF_ERROR;
+}
+
+
+static ngx_int_t
+ngx_stream_ssl_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_SSL_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_stream_ssl_handler;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/stream/ngx_stream_ssl_module.h b/nginx/src/stream/ngx_stream_ssl_module.h
new file mode 100644 (file)
index 0000000..65f5d45
--- /dev/null
@@ -0,0 +1,56 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_STREAM_SSL_H_INCLUDED_
+#define _NGX_STREAM_SSL_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+typedef struct {
+    ngx_msec_t       handshake_timeout;
+
+    ngx_flag_t       prefer_server_ciphers;
+
+    ngx_ssl_t        ssl;
+
+    ngx_uint_t       protocols;
+
+    ngx_uint_t       verify;
+    ngx_uint_t       verify_depth;
+
+    ssize_t          builtin_session_cache;
+
+    time_t           session_timeout;
+
+    ngx_array_t     *certificates;
+    ngx_array_t     *certificate_keys;
+
+    ngx_str_t        dhparam;
+    ngx_str_t        ecdh_curve;
+    ngx_str_t        client_certificate;
+    ngx_str_t        trusted_certificate;
+    ngx_str_t        crl;
+
+    ngx_str_t        ciphers;
+
+    ngx_array_t     *passwords;
+
+    ngx_shm_zone_t  *shm_zone;
+
+    ngx_flag_t       session_tickets;
+    ngx_array_t     *session_ticket_keys;
+} ngx_stream_ssl_conf_t;
+
+
+extern ngx_module_t  ngx_stream_ssl_module;
+
+
+#endif /* _NGX_STREAM_SSL_H_INCLUDED_ */
diff --git a/nginx/src/stream/ngx_stream_ssl_preread_module.c b/nginx/src/stream/ngx_stream_ssl_preread_module.c
new file mode 100644 (file)
index 0000000..62d6524
--- /dev/null
@@ -0,0 +1,565 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+typedef struct {
+    ngx_flag_t      enabled;
+} ngx_stream_ssl_preread_srv_conf_t;
+
+
+typedef struct {
+    size_t          left;
+    size_t          size;
+    size_t          ext;
+    u_char         *pos;
+    u_char         *dst;
+    u_char          buf[4];
+    ngx_str_t       host;
+    ngx_str_t       alpn;
+    ngx_log_t      *log;
+    ngx_pool_t     *pool;
+    ngx_uint_t      state;
+} ngx_stream_ssl_preread_ctx_t;
+
+
+static ngx_int_t ngx_stream_ssl_preread_handler(ngx_stream_session_t *s);
+static ngx_int_t ngx_stream_ssl_preread_parse_record(
+    ngx_stream_ssl_preread_ctx_t *ctx, u_char *pos, u_char *last);
+static ngx_int_t ngx_stream_ssl_preread_server_name_variable(
+    ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_ssl_preread_alpn_protocols_variable(
+    ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_ssl_preread_add_variables(ngx_conf_t *cf);
+static void *ngx_stream_ssl_preread_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_stream_ssl_preread_merge_srv_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static ngx_int_t ngx_stream_ssl_preread_init(ngx_conf_t *cf);
+
+
+static ngx_command_t  ngx_stream_ssl_preread_commands[] = {
+
+    { ngx_string("ssl_preread"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_ssl_preread_srv_conf_t, enabled),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_stream_module_t  ngx_stream_ssl_preread_module_ctx = {
+    ngx_stream_ssl_preread_add_variables,   /* preconfiguration */
+    ngx_stream_ssl_preread_init,            /* postconfiguration */
+
+    NULL,                                   /* create main configuration */
+    NULL,                                   /* init main configuration */
+
+    ngx_stream_ssl_preread_create_srv_conf, /* create server configuration */
+    ngx_stream_ssl_preread_merge_srv_conf   /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_stream_ssl_preread_module = {
+    NGX_MODULE_V1,
+    &ngx_stream_ssl_preread_module_ctx,     /* module context */
+    ngx_stream_ssl_preread_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_stream_variable_t  ngx_stream_ssl_preread_vars[] = {
+
+    { ngx_string("ssl_preread_server_name"), NULL,
+      ngx_stream_ssl_preread_server_name_variable, 0, 0, 0 },
+
+    { ngx_string("ssl_preread_alpn_protocols"), NULL,
+      ngx_stream_ssl_preread_alpn_protocols_variable, 0, 0, 0 },
+
+      ngx_stream_null_variable
+};
+
+
+static ngx_int_t
+ngx_stream_ssl_preread_handler(ngx_stream_session_t *s)
+{
+    u_char                             *last, *p;
+    size_t                              len;
+    ngx_int_t                           rc;
+    ngx_connection_t                   *c;
+    ngx_stream_ssl_preread_ctx_t       *ctx;
+    ngx_stream_ssl_preread_srv_conf_t  *sscf;
+
+    c = s->connection;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, "ssl preread handler");
+
+    sscf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_preread_module);
+
+    if (!sscf->enabled) {
+        return NGX_DECLINED;
+    }
+
+    if (c->type != SOCK_STREAM) {
+        return NGX_DECLINED;
+    }
+
+    if (c->buffer == NULL) {
+        return NGX_AGAIN;
+    }
+
+    ctx = ngx_stream_get_module_ctx(s, ngx_stream_ssl_preread_module);
+    if (ctx == NULL) {
+        ctx = ngx_pcalloc(c->pool, sizeof(ngx_stream_ssl_preread_ctx_t));
+        if (ctx == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_stream_set_ctx(s, ctx, ngx_stream_ssl_preread_module);
+
+        ctx->pool = c->pool;
+        ctx->log = c->log;
+        ctx->pos = c->buffer->pos;
+    }
+
+    p = ctx->pos;
+    last = c->buffer->last;
+
+    while (last - p >= 5) {
+
+        if (p[0] != 0x16) {
+            ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0,
+                           "ssl preread: not a handshake");
+            ngx_stream_set_ctx(s, NULL, ngx_stream_ssl_preread_module);
+            return NGX_DECLINED;
+        }
+
+        if (p[1] != 3) {
+            ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0,
+                           "ssl preread: unsupported SSL version");
+            ngx_stream_set_ctx(s, NULL, ngx_stream_ssl_preread_module);
+            return NGX_DECLINED;
+        }
+
+        len = (p[3] << 8) + p[4];
+
+        /* read the whole record before parsing */
+        if ((size_t) (last - p) < len + 5) {
+            break;
+        }
+
+        p += 5;
+
+        rc = ngx_stream_ssl_preread_parse_record(ctx, p, p + len);
+
+        if (rc == NGX_DECLINED) {
+            ngx_stream_set_ctx(s, NULL, ngx_stream_ssl_preread_module);
+            return NGX_DECLINED;
+        }
+
+        if (rc != NGX_AGAIN) {
+            return rc;
+        }
+
+        p += len;
+    }
+
+    ctx->pos = p;
+
+    return NGX_AGAIN;
+}
+
+
+static ngx_int_t
+ngx_stream_ssl_preread_parse_record(ngx_stream_ssl_preread_ctx_t *ctx,
+    u_char *pos, u_char *last)
+{
+    size_t   left, n, size, ext;
+    u_char  *dst, *p;
+
+    enum {
+        sw_start = 0,
+        sw_header,          /* handshake msg_type, length */
+        sw_head_tail,       /* version, random */
+        sw_sid_len,         /* session_id length */
+        sw_sid,             /* session_id */
+        sw_cs_len,          /* cipher_suites length */
+        sw_cs,              /* cipher_suites */
+        sw_cm_len,          /* compression_methods length */
+        sw_cm,              /* compression_methods */
+        sw_ext,             /* extension */
+        sw_ext_header,      /* extension_type, extension_data length */
+        sw_sni_len,         /* SNI length */
+        sw_sni_host_head,   /* SNI name_type, host_name length */
+        sw_sni_host,        /* SNI host_name */
+        sw_alpn_len,        /* ALPN length */
+        sw_alpn_proto_len,  /* ALPN protocol_name length */
+        sw_alpn_proto_data  /* ALPN protocol_name */
+    } state;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_STREAM, ctx->log, 0,
+                   "ssl preread: state %ui left %z", ctx->state, ctx->left);
+
+    state = ctx->state;
+    size = ctx->size;
+    left = ctx->left;
+    ext = ctx->ext;
+    dst = ctx->dst;
+    p = ctx->buf;
+
+    for ( ;; ) {
+        n = ngx_min((size_t) (last - pos), size);
+
+        if (dst) {
+            dst = ngx_cpymem(dst, pos, n);
+        }
+
+        pos += n;
+        size -= n;
+        left -= n;
+
+        if (size != 0) {
+            break;
+        }
+
+        switch (state) {
+
+        case sw_start:
+            state = sw_header;
+            dst = p;
+            size = 4;
+            left = size;
+            break;
+
+        case sw_header:
+            if (p[0] != 1) {
+                ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0,
+                               "ssl preread: not a client hello");
+                return NGX_DECLINED;
+            }
+
+            state = sw_head_tail;
+            dst = NULL;
+            size = 34;
+            left = (p[1] << 16) + (p[2] << 8) + p[3];
+            break;
+
+        case sw_head_tail:
+            state = sw_sid_len;
+            dst = p;
+            size = 1;
+            break;
+
+        case sw_sid_len:
+            state = sw_sid;
+            dst = NULL;
+            size = p[0];
+            break;
+
+        case sw_sid:
+            state = sw_cs_len;
+            dst = p;
+            size = 2;
+            break;
+
+        case sw_cs_len:
+            state = sw_cs;
+            dst = NULL;
+            size = (p[0] << 8) + p[1];
+            break;
+
+        case sw_cs:
+            state = sw_cm_len;
+            dst = p;
+            size = 1;
+            break;
+
+        case sw_cm_len:
+            state = sw_cm;
+            dst = NULL;
+            size = p[0];
+            break;
+
+        case sw_cm:
+            if (left == 0) {
+                /* no extensions */
+                return NGX_OK;
+            }
+
+            state = sw_ext;
+            dst = p;
+            size = 2;
+            break;
+
+        case sw_ext:
+            if (left == 0) {
+                return NGX_OK;
+            }
+
+            state = sw_ext_header;
+            dst = p;
+            size = 4;
+            break;
+
+        case sw_ext_header:
+            if (p[0] == 0 && p[1] == 0 && ctx->host.data == NULL) {
+                /* SNI extension */
+                state = sw_sni_len;
+                dst = p;
+                size = 2;
+                break;
+            }
+
+            if (p[0] == 0 && p[1] == 16 && ctx->alpn.data == NULL) {
+                /* ALPN extension */
+                state = sw_alpn_len;
+                dst = p;
+                size = 2;
+                break;
+            }
+
+            state = sw_ext;
+            dst = NULL;
+            size = (p[2] << 8) + p[3];
+            break;
+
+        case sw_sni_len:
+            ext = (p[0] << 8) + p[1];
+            state = sw_sni_host_head;
+            dst = p;
+            size = 3;
+            break;
+
+        case sw_sni_host_head:
+            if (p[0] != 0) {
+                ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0,
+                               "ssl preread: SNI hostname type is not DNS");
+                return NGX_DECLINED;
+            }
+
+            size = (p[1] << 8) + p[2];
+
+            if (ext < 3 + size) {
+                ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0,
+                               "ssl preread: SNI format error");
+                return NGX_DECLINED;
+            }
+            ext -= 3 + size;
+
+            ctx->host.data = ngx_pnalloc(ctx->pool, size);
+            if (ctx->host.data == NULL) {
+                return NGX_ERROR;
+            }
+
+            state = sw_sni_host;
+            dst = ctx->host.data;
+            break;
+
+        case sw_sni_host:
+            ctx->host.len = (p[1] << 8) + p[2];
+
+            ngx_log_debug1(NGX_LOG_DEBUG_STREAM, ctx->log, 0,
+                           "ssl preread: SNI hostname \"%V\"", &ctx->host);
+
+            state = sw_ext;
+            dst = NULL;
+            size = ext;
+            break;
+
+        case sw_alpn_len:
+            ext = (p[0] << 8) + p[1];
+
+            ctx->alpn.data = ngx_pnalloc(ctx->pool, ext);
+            if (ctx->alpn.data == NULL) {
+                return NGX_ERROR;
+            }
+
+            state = sw_alpn_proto_len;
+            dst = p;
+            size = 1;
+            break;
+
+        case sw_alpn_proto_len:
+            size = p[0];
+
+            if (size == 0) {
+                ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0,
+                               "ssl preread: ALPN empty protocol");
+                return NGX_DECLINED;
+            }
+
+            if (ext < 1 + size) {
+                ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0,
+                               "ssl preread: ALPN format error");
+                return NGX_DECLINED;
+            }
+            ext -= 1 + size;
+
+            state = sw_alpn_proto_data;
+            dst = ctx->alpn.data + ctx->alpn.len;
+            break;
+
+        case sw_alpn_proto_data:
+            ctx->alpn.len += p[0];
+
+            ngx_log_debug1(NGX_LOG_DEBUG_STREAM, ctx->log, 0,
+                           "ssl preread: ALPN protocols \"%V\"", &ctx->alpn);
+
+            if (ext) {
+                ctx->alpn.data[ctx->alpn.len++] = ',';
+
+                state = sw_alpn_proto_len;
+                dst = p;
+                size = 1;
+                break;
+            }
+
+            state = sw_ext;
+            dst = NULL;
+            size = 0;
+            break;
+        }
+
+        if (left < size) {
+            ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0,
+                           "ssl preread: failed to parse handshake");
+            return NGX_DECLINED;
+        }
+    }
+
+    ctx->state = state;
+    ctx->size = size;
+    ctx->left = left;
+    ctx->ext = ext;
+    ctx->dst = dst;
+
+    return NGX_AGAIN;
+}
+
+
+static ngx_int_t
+ngx_stream_ssl_preread_server_name_variable(ngx_stream_session_t *s,
+    ngx_variable_value_t *v, uintptr_t data)
+{
+    ngx_stream_ssl_preread_ctx_t  *ctx;
+
+    ctx = ngx_stream_get_module_ctx(s, ngx_stream_ssl_preread_module);
+
+    if (ctx == NULL) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->len = ctx->host.len;
+    v->data = ctx->host.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_ssl_preread_alpn_protocols_variable(ngx_stream_session_t *s,
+    ngx_variable_value_t *v, uintptr_t data)
+{
+    ngx_stream_ssl_preread_ctx_t  *ctx;
+
+    ctx = ngx_stream_get_module_ctx(s, ngx_stream_ssl_preread_module);
+
+    if (ctx == NULL) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->len = ctx->alpn.len;
+    v->data = ctx->alpn.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_ssl_preread_add_variables(ngx_conf_t *cf)
+{
+    ngx_stream_variable_t  *var, *v;
+
+    for (v = ngx_stream_ssl_preread_vars; v->name.len; v++) {
+        var = ngx_stream_add_variable(cf, &v->name, v->flags);
+        if (var == NULL) {
+            return NGX_ERROR;
+        }
+
+        var->get_handler = v->get_handler;
+        var->data = v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_stream_ssl_preread_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_stream_ssl_preread_srv_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_ssl_preread_srv_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    conf->enabled = NGX_CONF_UNSET;
+
+    return conf;
+}
+
+
+static char *
+ngx_stream_ssl_preread_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_stream_ssl_preread_srv_conf_t *prev = parent;
+    ngx_stream_ssl_preread_srv_conf_t *conf = child;
+
+    ngx_conf_merge_value(conf->enabled, prev->enabled, 0);
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_ssl_preread_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_PREREAD_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_stream_ssl_preread_handler;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/stream/ngx_stream_upstream.c b/nginx/src/stream/ngx_stream_upstream.c
new file mode 100644 (file)
index 0000000..7feac43
--- /dev/null
@@ -0,0 +1,717 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+static ngx_int_t ngx_stream_upstream_add_variables(ngx_conf_t *cf);
+static ngx_int_t ngx_stream_upstream_addr_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_upstream_response_time_variable(
+    ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_upstream_bytes_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+
+static char *ngx_stream_upstream(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *dummy);
+static char *ngx_stream_upstream_server(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static void *ngx_stream_upstream_create_main_conf(ngx_conf_t *cf);
+static char *ngx_stream_upstream_init_main_conf(ngx_conf_t *cf, void *conf);
+
+
+static ngx_command_t  ngx_stream_upstream_commands[] = {
+
+    { ngx_string("upstream"),
+      NGX_STREAM_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1,
+      ngx_stream_upstream,
+      0,
+      0,
+      NULL },
+
+    { ngx_string("server"),
+      NGX_STREAM_UPS_CONF|NGX_CONF_1MORE,
+      ngx_stream_upstream_server,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_stream_module_t  ngx_stream_upstream_module_ctx = {
+    ngx_stream_upstream_add_variables,     /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    ngx_stream_upstream_create_main_conf,  /* create main configuration */
+    ngx_stream_upstream_init_main_conf,    /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL                                   /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_stream_upstream_module = {
+    NGX_MODULE_V1,
+    &ngx_stream_upstream_module_ctx,       /* module context */
+    ngx_stream_upstream_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_stream_variable_t  ngx_stream_upstream_vars[] = {
+
+    { ngx_string("upstream_addr"), NULL,
+      ngx_stream_upstream_addr_variable, 0,
+      NGX_STREAM_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("upstream_bytes_sent"), NULL,
+      ngx_stream_upstream_bytes_variable, 0,
+      NGX_STREAM_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("upstream_connect_time"), NULL,
+      ngx_stream_upstream_response_time_variable, 2,
+      NGX_STREAM_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("upstream_first_byte_time"), NULL,
+      ngx_stream_upstream_response_time_variable, 1,
+      NGX_STREAM_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("upstream_session_time"), NULL,
+      ngx_stream_upstream_response_time_variable, 0,
+      NGX_STREAM_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("upstream_bytes_received"), NULL,
+      ngx_stream_upstream_bytes_variable, 1,
+      NGX_STREAM_VAR_NOCACHEABLE, 0 },
+
+      ngx_stream_null_variable
+};
+
+
+static ngx_int_t
+ngx_stream_upstream_add_variables(ngx_conf_t *cf)
+{
+    ngx_stream_variable_t  *var, *v;
+
+    for (v = ngx_stream_upstream_vars; v->name.len; v++) {
+        var = ngx_stream_add_variable(cf, &v->name, v->flags);
+        if (var == NULL) {
+            return NGX_ERROR;
+        }
+
+        var->get_handler = v->get_handler;
+        var->data = v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_upstream_addr_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    u_char                       *p;
+    size_t                        len;
+    ngx_uint_t                    i;
+    ngx_stream_upstream_state_t  *state;
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    if (s->upstream_states == NULL || s->upstream_states->nelts == 0) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    len = 0;
+    state = s->upstream_states->elts;
+
+    for (i = 0; i < s->upstream_states->nelts; i++) {
+        if (state[i].peer) {
+            len += state[i].peer->len;
+        }
+
+        len += 2;
+    }
+
+    p = ngx_pnalloc(s->connection->pool, len);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->data = p;
+
+    i = 0;
+
+    for ( ;; ) {
+        if (state[i].peer) {
+            p = ngx_cpymem(p, state[i].peer->data, state[i].peer->len);
+        }
+
+        if (++i == s->upstream_states->nelts) {
+            break;
+        }
+
+        *p++ = ',';
+        *p++ = ' ';
+    }
+
+    v->len = p - v->data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_upstream_bytes_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    u_char                       *p;
+    size_t                        len;
+    ngx_uint_t                    i;
+    ngx_stream_upstream_state_t  *state;
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    if (s->upstream_states == NULL || s->upstream_states->nelts == 0) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    len = s->upstream_states->nelts * (NGX_OFF_T_LEN + 2);
+
+    p = ngx_pnalloc(s->connection->pool, len);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->data = p;
+
+    i = 0;
+    state = s->upstream_states->elts;
+
+    for ( ;; ) {
+
+        if (data == 1) {
+            p = ngx_sprintf(p, "%O", state[i].bytes_received);
+
+        } else {
+            p = ngx_sprintf(p, "%O", state[i].bytes_sent);
+        }
+
+        if (++i == s->upstream_states->nelts) {
+            break;
+        }
+
+        *p++ = ',';
+        *p++ = ' ';
+    }
+
+    v->len = p - v->data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_upstream_response_time_variable(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    u_char                       *p;
+    size_t                        len;
+    ngx_uint_t                    i;
+    ngx_msec_int_t                ms;
+    ngx_stream_upstream_state_t  *state;
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    if (s->upstream_states == NULL || s->upstream_states->nelts == 0) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    len = s->upstream_states->nelts * (NGX_TIME_T_LEN + 4 + 2);
+
+    p = ngx_pnalloc(s->connection->pool, len);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->data = p;
+
+    i = 0;
+    state = s->upstream_states->elts;
+
+    for ( ;; ) {
+
+        if (data == 1) {
+            if (state[i].first_byte_time == (ngx_msec_t) -1) {
+                *p++ = '-';
+                goto next;
+            }
+
+            ms = state[i].first_byte_time;
+
+        } else if (data == 2 && state[i].connect_time != (ngx_msec_t) -1) {
+            ms = state[i].connect_time;
+
+        } else {
+            ms = state[i].response_time;
+        }
+
+        ms = ngx_max(ms, 0);
+        p = ngx_sprintf(p, "%T.%03M", (time_t) ms / 1000, ms % 1000);
+
+    next:
+
+        if (++i == s->upstream_states->nelts) {
+            break;
+        }
+
+        *p++ = ',';
+        *p++ = ' ';
+    }
+
+    v->len = p - v->data;
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_stream_upstream(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
+{
+    char                            *rv;
+    void                            *mconf;
+    ngx_str_t                       *value;
+    ngx_url_t                        u;
+    ngx_uint_t                       m;
+    ngx_conf_t                       pcf;
+    ngx_stream_module_t             *module;
+    ngx_stream_conf_ctx_t           *ctx, *stream_ctx;
+    ngx_stream_upstream_srv_conf_t  *uscf;
+
+    ngx_memzero(&u, sizeof(ngx_url_t));
+
+    value = cf->args->elts;
+    u.host = value[1];
+    u.no_resolve = 1;
+    u.no_port = 1;
+
+    uscf = ngx_stream_upstream_add(cf, &u, NGX_STREAM_UPSTREAM_CREATE
+                                           |NGX_STREAM_UPSTREAM_WEIGHT
+                                           |NGX_STREAM_UPSTREAM_MAX_CONNS
+                                           |NGX_STREAM_UPSTREAM_MAX_FAILS
+                                           |NGX_STREAM_UPSTREAM_FAIL_TIMEOUT
+                                           |NGX_STREAM_UPSTREAM_DOWN
+                                           |NGX_STREAM_UPSTREAM_BACKUP);
+    if (uscf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+
+    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_stream_conf_ctx_t));
+    if (ctx == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    stream_ctx = cf->ctx;
+    ctx->main_conf = stream_ctx->main_conf;
+
+    /* the upstream{}'s srv_conf */
+
+    ctx->srv_conf = ngx_pcalloc(cf->pool,
+                                sizeof(void *) * ngx_stream_max_module);
+    if (ctx->srv_conf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ctx->srv_conf[ngx_stream_upstream_module.ctx_index] = uscf;
+
+    uscf->srv_conf = ctx->srv_conf;
+
+    for (m = 0; cf->cycle->modules[m]; m++) {
+        if (cf->cycle->modules[m]->type != NGX_STREAM_MODULE) {
+            continue;
+        }
+
+        module = cf->cycle->modules[m]->ctx;
+
+        if (module->create_srv_conf) {
+            mconf = module->create_srv_conf(cf);
+            if (mconf == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            ctx->srv_conf[cf->cycle->modules[m]->ctx_index] = mconf;
+        }
+    }
+
+    uscf->servers = ngx_array_create(cf->pool, 4,
+                                     sizeof(ngx_stream_upstream_server_t));
+    if (uscf->servers == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+
+    /* parse inside upstream{} */
+
+    pcf = *cf;
+    cf->ctx = ctx;
+    cf->cmd_type = NGX_STREAM_UPS_CONF;
+
+    rv = ngx_conf_parse(cf, NULL);
+
+    *cf = pcf;
+
+    if (rv != NGX_CONF_OK) {
+        return rv;
+    }
+
+    if (uscf->servers->nelts == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "no servers are inside upstream");
+        return NGX_CONF_ERROR;
+    }
+
+    return rv;
+}
+
+
+static char *
+ngx_stream_upstream_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_upstream_srv_conf_t  *uscf = conf;
+
+    time_t                         fail_timeout;
+    ngx_str_t                     *value, s;
+    ngx_url_t                      u;
+    ngx_int_t                      weight, max_conns, max_fails;
+    ngx_uint_t                     i;
+    ngx_stream_upstream_server_t  *us;
+
+    us = ngx_array_push(uscf->servers);
+    if (us == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memzero(us, sizeof(ngx_stream_upstream_server_t));
+
+    value = cf->args->elts;
+
+    weight = 1;
+    max_conns = 0;
+    max_fails = 1;
+    fail_timeout = 10;
+
+    for (i = 2; i < cf->args->nelts; i++) {
+
+        if (ngx_strncmp(value[i].data, "weight=", 7) == 0) {
+
+            if (!(uscf->flags & NGX_STREAM_UPSTREAM_WEIGHT)) {
+                goto not_supported;
+            }
+
+            weight = ngx_atoi(&value[i].data[7], value[i].len - 7);
+
+            if (weight == NGX_ERROR || weight == 0) {
+                goto invalid;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "max_conns=", 10) == 0) {
+
+            if (!(uscf->flags & NGX_STREAM_UPSTREAM_MAX_CONNS)) {
+                goto not_supported;
+            }
+
+            max_conns = ngx_atoi(&value[i].data[10], value[i].len - 10);
+
+            if (max_conns == NGX_ERROR) {
+                goto invalid;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "max_fails=", 10) == 0) {
+
+            if (!(uscf->flags & NGX_STREAM_UPSTREAM_MAX_FAILS)) {
+                goto not_supported;
+            }
+
+            max_fails = ngx_atoi(&value[i].data[10], value[i].len - 10);
+
+            if (max_fails == NGX_ERROR) {
+                goto invalid;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "fail_timeout=", 13) == 0) {
+
+            if (!(uscf->flags & NGX_STREAM_UPSTREAM_FAIL_TIMEOUT)) {
+                goto not_supported;
+            }
+
+            s.len = value[i].len - 13;
+            s.data = &value[i].data[13];
+
+            fail_timeout = ngx_parse_time(&s, 1);
+
+            if (fail_timeout == (time_t) NGX_ERROR) {
+                goto invalid;
+            }
+
+            continue;
+        }
+
+        if (ngx_strcmp(value[i].data, "backup") == 0) {
+
+            if (!(uscf->flags & NGX_STREAM_UPSTREAM_BACKUP)) {
+                goto not_supported;
+            }
+
+            us->backup = 1;
+
+            continue;
+        }
+
+        if (ngx_strcmp(value[i].data, "down") == 0) {
+
+            if (!(uscf->flags & NGX_STREAM_UPSTREAM_DOWN)) {
+                goto not_supported;
+            }
+
+            us->down = 1;
+
+            continue;
+        }
+
+        goto invalid;
+    }
+
+    ngx_memzero(&u, sizeof(ngx_url_t));
+
+    u.url = value[1];
+
+    if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
+        if (u.err) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "%s in upstream \"%V\"", u.err, &u.url);
+        }
+
+        return NGX_CONF_ERROR;
+    }
+
+    if (u.no_port) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "no port in upstream \"%V\"", &u.url);
+        return NGX_CONF_ERROR;
+    }
+
+    us->name = u.url;
+    us->addrs = u.addrs;
+    us->naddrs = u.naddrs;
+    us->weight = weight;
+    us->max_conns = max_conns;
+    us->max_fails = max_fails;
+    us->fail_timeout = fail_timeout;
+
+    return NGX_CONF_OK;
+
+invalid:
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "invalid parameter \"%V\"", &value[i]);
+
+    return NGX_CONF_ERROR;
+
+not_supported:
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "balancing method does not support parameter \"%V\"",
+                       &value[i]);
+
+    return NGX_CONF_ERROR;
+}
+
+
+ngx_stream_upstream_srv_conf_t *
+ngx_stream_upstream_add(ngx_conf_t *cf, ngx_url_t *u, ngx_uint_t flags)
+{
+    ngx_uint_t                        i;
+    ngx_stream_upstream_server_t     *us;
+    ngx_stream_upstream_srv_conf_t   *uscf, **uscfp;
+    ngx_stream_upstream_main_conf_t  *umcf;
+
+    if (!(flags & NGX_STREAM_UPSTREAM_CREATE)) {
+
+        if (ngx_parse_url(cf->pool, u) != NGX_OK) {
+            if (u->err) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "%s in upstream \"%V\"", u->err, &u->url);
+            }
+
+            return NULL;
+        }
+    }
+
+    umcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_upstream_module);
+
+    uscfp = umcf->upstreams.elts;
+
+    for (i = 0; i < umcf->upstreams.nelts; i++) {
+
+        if (uscfp[i]->host.len != u->host.len
+            || ngx_strncasecmp(uscfp[i]->host.data, u->host.data, u->host.len)
+               != 0)
+        {
+            continue;
+        }
+
+        if ((flags & NGX_STREAM_UPSTREAM_CREATE)
+             && (uscfp[i]->flags & NGX_STREAM_UPSTREAM_CREATE))
+        {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "duplicate upstream \"%V\"", &u->host);
+            return NULL;
+        }
+
+        if ((uscfp[i]->flags & NGX_STREAM_UPSTREAM_CREATE) && !u->no_port) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "upstream \"%V\" may not have port %d",
+                               &u->host, u->port);
+            return NULL;
+        }
+
+        if ((flags & NGX_STREAM_UPSTREAM_CREATE) && !uscfp[i]->no_port) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "upstream \"%V\" may not have port %d in %s:%ui",
+                          &u->host, uscfp[i]->port,
+                          uscfp[i]->file_name, uscfp[i]->line);
+            return NULL;
+        }
+
+        if (uscfp[i]->port != u->port) {
+            continue;
+        }
+
+        if (flags & NGX_STREAM_UPSTREAM_CREATE) {
+            uscfp[i]->flags = flags;
+        }
+
+        return uscfp[i];
+    }
+
+    uscf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_upstream_srv_conf_t));
+    if (uscf == NULL) {
+        return NULL;
+    }
+
+    uscf->flags = flags;
+    uscf->host = u->host;
+    uscf->file_name = cf->conf_file->file.name.data;
+    uscf->line = cf->conf_file->line;
+    uscf->port = u->port;
+    uscf->no_port = u->no_port;
+
+    if (u->naddrs == 1 && (u->port || u->family == AF_UNIX)) {
+        uscf->servers = ngx_array_create(cf->pool, 1,
+                                         sizeof(ngx_stream_upstream_server_t));
+        if (uscf->servers == NULL) {
+            return NULL;
+        }
+
+        us = ngx_array_push(uscf->servers);
+        if (us == NULL) {
+            return NULL;
+        }
+
+        ngx_memzero(us, sizeof(ngx_stream_upstream_server_t));
+
+        us->addrs = u->addrs;
+        us->naddrs = 1;
+    }
+
+    uscfp = ngx_array_push(&umcf->upstreams);
+    if (uscfp == NULL) {
+        return NULL;
+    }
+
+    *uscfp = uscf;
+
+    return uscf;
+}
+
+
+static void *
+ngx_stream_upstream_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_stream_upstream_main_conf_t  *umcf;
+
+    umcf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_upstream_main_conf_t));
+    if (umcf == NULL) {
+        return NULL;
+    }
+
+    if (ngx_array_init(&umcf->upstreams, cf->pool, 4,
+                       sizeof(ngx_stream_upstream_srv_conf_t *))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    return umcf;
+}
+
+
+static char *
+ngx_stream_upstream_init_main_conf(ngx_conf_t *cf, void *conf)
+{
+    ngx_stream_upstream_main_conf_t *umcf = conf;
+
+    ngx_uint_t                        i;
+    ngx_stream_upstream_init_pt       init;
+    ngx_stream_upstream_srv_conf_t  **uscfp;
+
+    uscfp = umcf->upstreams.elts;
+
+    for (i = 0; i < umcf->upstreams.nelts; i++) {
+
+        init = uscfp[i]->peer.init_upstream
+                                         ? uscfp[i]->peer.init_upstream
+                                         : ngx_stream_upstream_init_round_robin;
+
+        if (init(cf, uscfp[i]) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/stream/ngx_stream_upstream.h b/nginx/src/stream/ngx_stream_upstream.h
new file mode 100644 (file)
index 0000000..73947f4
--- /dev/null
@@ -0,0 +1,154 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_STREAM_UPSTREAM_H_INCLUDED_
+#define _NGX_STREAM_UPSTREAM_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+#include <ngx_event_connect.h>
+
+
+#define NGX_STREAM_UPSTREAM_CREATE        0x0001
+#define NGX_STREAM_UPSTREAM_WEIGHT        0x0002
+#define NGX_STREAM_UPSTREAM_MAX_FAILS     0x0004
+#define NGX_STREAM_UPSTREAM_FAIL_TIMEOUT  0x0008
+#define NGX_STREAM_UPSTREAM_DOWN          0x0010
+#define NGX_STREAM_UPSTREAM_BACKUP        0x0020
+#define NGX_STREAM_UPSTREAM_MAX_CONNS     0x0100
+
+
+#define NGX_STREAM_UPSTREAM_NOTIFY_CONNECT     0x1
+
+
+typedef struct {
+    ngx_array_t                        upstreams;
+                                           /* ngx_stream_upstream_srv_conf_t */
+} ngx_stream_upstream_main_conf_t;
+
+
+typedef struct ngx_stream_upstream_srv_conf_s  ngx_stream_upstream_srv_conf_t;
+
+
+typedef ngx_int_t (*ngx_stream_upstream_init_pt)(ngx_conf_t *cf,
+    ngx_stream_upstream_srv_conf_t *us);
+typedef ngx_int_t (*ngx_stream_upstream_init_peer_pt)(ngx_stream_session_t *s,
+    ngx_stream_upstream_srv_conf_t *us);
+
+
+typedef struct {
+    ngx_stream_upstream_init_pt        init_upstream;
+    ngx_stream_upstream_init_peer_pt   init;
+    void                              *data;
+} ngx_stream_upstream_peer_t;
+
+
+typedef struct {
+    ngx_str_t                          name;
+    ngx_addr_t                        *addrs;
+    ngx_uint_t                         naddrs;
+    ngx_uint_t                         weight;
+    ngx_uint_t                         max_conns;
+    ngx_uint_t                         max_fails;
+    time_t                             fail_timeout;
+    ngx_msec_t                         slow_start;
+    ngx_uint_t                         down;
+
+    unsigned                           backup:1;
+
+    NGX_COMPAT_BEGIN(4)
+    NGX_COMPAT_END
+} ngx_stream_upstream_server_t;
+
+
+struct ngx_stream_upstream_srv_conf_s {
+    ngx_stream_upstream_peer_t         peer;
+    void                             **srv_conf;
+
+    ngx_array_t                       *servers;
+                                              /* ngx_stream_upstream_server_t */
+
+    ngx_uint_t                         flags;
+    ngx_str_t                          host;
+    u_char                            *file_name;
+    ngx_uint_t                         line;
+    in_port_t                          port;
+    ngx_uint_t                         no_port;  /* unsigned no_port:1 */
+
+#if (NGX_STREAM_UPSTREAM_ZONE)
+    ngx_shm_zone_t                    *shm_zone;
+#endif
+};
+
+
+typedef struct {
+    ngx_msec_t                         response_time;
+    ngx_msec_t                         connect_time;
+    ngx_msec_t                         first_byte_time;
+    off_t                              bytes_sent;
+    off_t                              bytes_received;
+
+    ngx_str_t                         *peer;
+} ngx_stream_upstream_state_t;
+
+
+typedef struct {
+    ngx_str_t                          host;
+    in_port_t                          port;
+    ngx_uint_t                         no_port; /* unsigned no_port:1 */
+
+    ngx_uint_t                         naddrs;
+    ngx_resolver_addr_t               *addrs;
+
+    struct sockaddr                   *sockaddr;
+    socklen_t                          socklen;
+    ngx_str_t                          name;
+
+    ngx_resolver_ctx_t                *ctx;
+} ngx_stream_upstream_resolved_t;
+
+
+typedef struct {
+    ngx_peer_connection_t              peer;
+
+    ngx_buf_t                          downstream_buf;
+    ngx_buf_t                          upstream_buf;
+
+    ngx_chain_t                       *free;
+    ngx_chain_t                       *upstream_out;
+    ngx_chain_t                       *upstream_busy;
+    ngx_chain_t                       *downstream_out;
+    ngx_chain_t                       *downstream_busy;
+
+    off_t                              received;
+    time_t                             start_sec;
+    ngx_uint_t                         responses;
+
+    ngx_str_t                          ssl_name;
+
+    ngx_stream_upstream_srv_conf_t    *upstream;
+    ngx_stream_upstream_resolved_t    *resolved;
+    ngx_stream_upstream_state_t       *state;
+    unsigned                           connected:1;
+    unsigned                           proxy_protocol:1;
+} ngx_stream_upstream_t;
+
+
+ngx_stream_upstream_srv_conf_t *ngx_stream_upstream_add(ngx_conf_t *cf,
+    ngx_url_t *u, ngx_uint_t flags);
+
+
+#define ngx_stream_conf_upstream_srv_conf(uscf, module)                       \
+    uscf->srv_conf[module.ctx_index]
+
+
+extern ngx_module_t  ngx_stream_upstream_module;
+
+
+#endif /* _NGX_STREAM_UPSTREAM_H_INCLUDED_ */
diff --git a/nginx/src/stream/ngx_stream_upstream_hash_module.c b/nginx/src/stream/ngx_stream_upstream_hash_module.c
new file mode 100644 (file)
index 0000000..79ad742
--- /dev/null
@@ -0,0 +1,679 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+typedef struct {
+    uint32_t                              hash;
+    ngx_str_t                            *server;
+} ngx_stream_upstream_chash_point_t;
+
+
+typedef struct {
+    ngx_uint_t                            number;
+    ngx_stream_upstream_chash_point_t     point[1];
+} ngx_stream_upstream_chash_points_t;
+
+
+typedef struct {
+    ngx_stream_complex_value_t            key;
+    ngx_stream_upstream_chash_points_t   *points;
+} ngx_stream_upstream_hash_srv_conf_t;
+
+
+typedef struct {
+    /* the round robin data must be first */
+    ngx_stream_upstream_rr_peer_data_t    rrp;
+    ngx_stream_upstream_hash_srv_conf_t  *conf;
+    ngx_str_t                             key;
+    ngx_uint_t                            tries;
+    ngx_uint_t                            rehash;
+    uint32_t                              hash;
+    ngx_event_get_peer_pt                 get_rr_peer;
+} ngx_stream_upstream_hash_peer_data_t;
+
+
+static ngx_int_t ngx_stream_upstream_init_hash(ngx_conf_t *cf,
+    ngx_stream_upstream_srv_conf_t *us);
+static ngx_int_t ngx_stream_upstream_init_hash_peer(ngx_stream_session_t *s,
+    ngx_stream_upstream_srv_conf_t *us);
+static ngx_int_t ngx_stream_upstream_get_hash_peer(ngx_peer_connection_t *pc,
+    void *data);
+
+static ngx_int_t ngx_stream_upstream_init_chash(ngx_conf_t *cf,
+    ngx_stream_upstream_srv_conf_t *us);
+static int ngx_libc_cdecl
+    ngx_stream_upstream_chash_cmp_points(const void *one, const void *two);
+static ngx_uint_t ngx_stream_upstream_find_chash_point(
+    ngx_stream_upstream_chash_points_t *points, uint32_t hash);
+static ngx_int_t ngx_stream_upstream_init_chash_peer(ngx_stream_session_t *s,
+    ngx_stream_upstream_srv_conf_t *us);
+static ngx_int_t ngx_stream_upstream_get_chash_peer(ngx_peer_connection_t *pc,
+    void *data);
+
+static void *ngx_stream_upstream_hash_create_conf(ngx_conf_t *cf);
+static char *ngx_stream_upstream_hash(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+
+static ngx_command_t  ngx_stream_upstream_hash_commands[] = {
+
+    { ngx_string("hash"),
+      NGX_STREAM_UPS_CONF|NGX_CONF_TAKE12,
+      ngx_stream_upstream_hash,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_stream_module_t  ngx_stream_upstream_hash_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_stream_upstream_hash_create_conf,  /* create server configuration */
+    NULL                                   /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_stream_upstream_hash_module = {
+    NGX_MODULE_V1,
+    &ngx_stream_upstream_hash_module_ctx,  /* module context */
+    ngx_stream_upstream_hash_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_upstream_init_hash(ngx_conf_t *cf,
+    ngx_stream_upstream_srv_conf_t *us)
+{
+    if (ngx_stream_upstream_init_round_robin(cf, us) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    us->peer.init = ngx_stream_upstream_init_hash_peer;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_upstream_init_hash_peer(ngx_stream_session_t *s,
+    ngx_stream_upstream_srv_conf_t *us)
+{
+    ngx_stream_upstream_hash_srv_conf_t   *hcf;
+    ngx_stream_upstream_hash_peer_data_t  *hp;
+
+    hp = ngx_palloc(s->connection->pool,
+                    sizeof(ngx_stream_upstream_hash_peer_data_t));
+    if (hp == NULL) {
+        return NGX_ERROR;
+    }
+
+    s->upstream->peer.data = &hp->rrp;
+
+    if (ngx_stream_upstream_init_round_robin_peer(s, us) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    s->upstream->peer.get = ngx_stream_upstream_get_hash_peer;
+
+    hcf = ngx_stream_conf_upstream_srv_conf(us,
+                                            ngx_stream_upstream_hash_module);
+
+    if (ngx_stream_complex_value(s, &hcf->key, &hp->key) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                   "upstream hash key:\"%V\"", &hp->key);
+
+    hp->conf = hcf;
+    hp->tries = 0;
+    hp->rehash = 0;
+    hp->hash = 0;
+    hp->get_rr_peer = ngx_stream_upstream_get_round_robin_peer;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_upstream_get_hash_peer(ngx_peer_connection_t *pc, void *data)
+{
+    ngx_stream_upstream_hash_peer_data_t *hp = data;
+
+    time_t                          now;
+    u_char                          buf[NGX_INT_T_LEN];
+    size_t                          size;
+    uint32_t                        hash;
+    ngx_int_t                       w;
+    uintptr_t                       m;
+    ngx_uint_t                      n, p;
+    ngx_stream_upstream_rr_peer_t  *peer;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pc->log, 0,
+                   "get hash peer, try: %ui", pc->tries);
+
+    ngx_stream_upstream_rr_peers_wlock(hp->rrp.peers);
+
+    if (hp->tries > 20 || hp->rrp.peers->single) {
+        ngx_stream_upstream_rr_peers_unlock(hp->rrp.peers);
+        return hp->get_rr_peer(pc, &hp->rrp);
+    }
+
+    now = ngx_time();
+
+    pc->connection = NULL;
+
+    for ( ;; ) {
+
+        /*
+         * Hash expression is compatible with Cache::Memcached:
+         * ((crc32([REHASH] KEY) >> 16) & 0x7fff) + PREV_HASH
+         * with REHASH omitted at the first iteration.
+         */
+
+        ngx_crc32_init(hash);
+
+        if (hp->rehash > 0) {
+            size = ngx_sprintf(buf, "%ui", hp->rehash) - buf;
+            ngx_crc32_update(&hash, buf, size);
+        }
+
+        ngx_crc32_update(&hash, hp->key.data, hp->key.len);
+        ngx_crc32_final(hash);
+
+        hash = (hash >> 16) & 0x7fff;
+
+        hp->hash += hash;
+        hp->rehash++;
+
+        w = hp->hash % hp->rrp.peers->total_weight;
+        peer = hp->rrp.peers->peer;
+        p = 0;
+
+        while (w >= peer->weight) {
+            w -= peer->weight;
+            peer = peer->next;
+            p++;
+        }
+
+        n = p / (8 * sizeof(uintptr_t));
+        m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));
+
+        if (hp->rrp.tried[n] & m) {
+            goto next;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_STREAM, pc->log, 0,
+                       "get hash peer, value:%uD, peer:%ui", hp->hash, p);
+
+        if (peer->down) {
+            goto next;
+        }
+
+        if (peer->max_fails
+            && peer->fails >= peer->max_fails
+            && now - peer->checked <= peer->fail_timeout)
+        {
+            goto next;
+        }
+
+        if (peer->max_conns && peer->conns >= peer->max_conns) {
+            goto next;
+        }
+
+        break;
+
+    next:
+
+        if (++hp->tries > 20) {
+            ngx_stream_upstream_rr_peers_unlock(hp->rrp.peers);
+            return hp->get_rr_peer(pc, &hp->rrp);
+        }
+    }
+
+    hp->rrp.current = peer;
+
+    pc->sockaddr = peer->sockaddr;
+    pc->socklen = peer->socklen;
+    pc->name = &peer->name;
+
+    peer->conns++;
+
+    if (now - peer->checked > peer->fail_timeout) {
+        peer->checked = now;
+    }
+
+    ngx_stream_upstream_rr_peers_unlock(hp->rrp.peers);
+
+    hp->rrp.tried[n] |= m;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_upstream_init_chash(ngx_conf_t *cf,
+    ngx_stream_upstream_srv_conf_t *us)
+{
+    u_char                               *host, *port, c;
+    size_t                                host_len, port_len, size;
+    uint32_t                              hash, base_hash;
+    ngx_str_t                            *server;
+    ngx_uint_t                            npoints, i, j;
+    ngx_stream_upstream_rr_peer_t        *peer;
+    ngx_stream_upstream_rr_peers_t       *peers;
+    ngx_stream_upstream_chash_points_t   *points;
+    ngx_stream_upstream_hash_srv_conf_t  *hcf;
+    union {
+        uint32_t                          value;
+        u_char                            byte[4];
+    } prev_hash;
+
+    if (ngx_stream_upstream_init_round_robin(cf, us) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    us->peer.init = ngx_stream_upstream_init_chash_peer;
+
+    peers = us->peer.data;
+    npoints = peers->total_weight * 160;
+
+    size = sizeof(ngx_stream_upstream_chash_points_t)
+           + sizeof(ngx_stream_upstream_chash_point_t) * (npoints - 1);
+
+    points = ngx_palloc(cf->pool, size);
+    if (points == NULL) {
+        return NGX_ERROR;
+    }
+
+    points->number = 0;
+
+    for (peer = peers->peer; peer; peer = peer->next) {
+        server = &peer->server;
+
+        /*
+         * Hash expression is compatible with Cache::Memcached::Fast:
+         * crc32(HOST \0 PORT PREV_HASH).
+         */
+
+        if (server->len >= 5
+            && ngx_strncasecmp(server->data, (u_char *) "unix:", 5) == 0)
+        {
+            host = server->data + 5;
+            host_len = server->len - 5;
+            port = NULL;
+            port_len = 0;
+            goto done;
+        }
+
+        for (j = 0; j < server->len; j++) {
+            c = server->data[server->len - j - 1];
+
+            if (c == ':') {
+                host = server->data;
+                host_len = server->len - j - 1;
+                port = server->data + server->len - j;
+                port_len = j;
+                goto done;
+            }
+
+            if (c < '0' || c > '9') {
+                break;
+            }
+        }
+
+        host = server->data;
+        host_len = server->len;
+        port = NULL;
+        port_len = 0;
+
+    done:
+
+        ngx_crc32_init(base_hash);
+        ngx_crc32_update(&base_hash, host, host_len);
+        ngx_crc32_update(&base_hash, (u_char *) "", 1);
+        ngx_crc32_update(&base_hash, port, port_len);
+
+        prev_hash.value = 0;
+        npoints = peer->weight * 160;
+
+        for (j = 0; j < npoints; j++) {
+            hash = base_hash;
+
+            ngx_crc32_update(&hash, prev_hash.byte, 4);
+            ngx_crc32_final(hash);
+
+            points->point[points->number].hash = hash;
+            points->point[points->number].server = server;
+            points->number++;
+
+#if (NGX_HAVE_LITTLE_ENDIAN)
+            prev_hash.value = hash;
+#else
+            prev_hash.byte[0] = (u_char) (hash & 0xff);
+            prev_hash.byte[1] = (u_char) ((hash >> 8) & 0xff);
+            prev_hash.byte[2] = (u_char) ((hash >> 16) & 0xff);
+            prev_hash.byte[3] = (u_char) ((hash >> 24) & 0xff);
+#endif
+        }
+    }
+
+    ngx_qsort(points->point,
+              points->number,
+              sizeof(ngx_stream_upstream_chash_point_t),
+              ngx_stream_upstream_chash_cmp_points);
+
+    for (i = 0, j = 1; j < points->number; j++) {
+        if (points->point[i].hash != points->point[j].hash) {
+            points->point[++i] = points->point[j];
+        }
+    }
+
+    points->number = i + 1;
+
+    hcf = ngx_stream_conf_upstream_srv_conf(us,
+                                            ngx_stream_upstream_hash_module);
+    hcf->points = points;
+
+    return NGX_OK;
+}
+
+
+static int ngx_libc_cdecl
+ngx_stream_upstream_chash_cmp_points(const void *one, const void *two)
+{
+    ngx_stream_upstream_chash_point_t *first =
+                                     (ngx_stream_upstream_chash_point_t *) one;
+    ngx_stream_upstream_chash_point_t *second =
+                                     (ngx_stream_upstream_chash_point_t *) two;
+
+    if (first->hash < second->hash) {
+        return -1;
+
+    } else if (first->hash > second->hash) {
+        return 1;
+
+    } else {
+        return 0;
+    }
+}
+
+
+static ngx_uint_t
+ngx_stream_upstream_find_chash_point(ngx_stream_upstream_chash_points_t *points,
+    uint32_t hash)
+{
+    ngx_uint_t                          i, j, k;
+    ngx_stream_upstream_chash_point_t  *point;
+
+    /* find first point >= hash */
+
+    point = &points->point[0];
+
+    i = 0;
+    j = points->number;
+
+    while (i < j) {
+        k = (i + j) / 2;
+
+        if (hash > point[k].hash) {
+            i = k + 1;
+
+        } else if (hash < point[k].hash) {
+            j = k;
+
+        } else {
+            return k;
+        }
+    }
+
+    return i;
+}
+
+
+static ngx_int_t
+ngx_stream_upstream_init_chash_peer(ngx_stream_session_t *s,
+    ngx_stream_upstream_srv_conf_t *us)
+{
+    uint32_t                               hash;
+    ngx_stream_upstream_hash_srv_conf_t   *hcf;
+    ngx_stream_upstream_hash_peer_data_t  *hp;
+
+    if (ngx_stream_upstream_init_hash_peer(s, us) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    s->upstream->peer.get = ngx_stream_upstream_get_chash_peer;
+
+    hp = s->upstream->peer.data;
+    hcf = ngx_stream_conf_upstream_srv_conf(us,
+                                            ngx_stream_upstream_hash_module);
+
+    hash = ngx_crc32_long(hp->key.data, hp->key.len);
+
+    ngx_stream_upstream_rr_peers_rlock(hp->rrp.peers);
+
+    hp->hash = ngx_stream_upstream_find_chash_point(hcf->points, hash);
+
+    ngx_stream_upstream_rr_peers_unlock(hp->rrp.peers);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_upstream_get_chash_peer(ngx_peer_connection_t *pc, void *data)
+{
+    ngx_stream_upstream_hash_peer_data_t *hp = data;
+
+    time_t                                now;
+    intptr_t                              m;
+    ngx_str_t                            *server;
+    ngx_int_t                             total;
+    ngx_uint_t                            i, n, best_i;
+    ngx_stream_upstream_rr_peer_t        *peer, *best;
+    ngx_stream_upstream_chash_point_t    *point;
+    ngx_stream_upstream_chash_points_t   *points;
+    ngx_stream_upstream_hash_srv_conf_t  *hcf;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pc->log, 0,
+                   "get consistent hash peer, try: %ui", pc->tries);
+
+    ngx_stream_upstream_rr_peers_wlock(hp->rrp.peers);
+
+    if (hp->tries > 20 || hp->rrp.peers->single) {
+        ngx_stream_upstream_rr_peers_unlock(hp->rrp.peers);
+        return hp->get_rr_peer(pc, &hp->rrp);
+    }
+
+    pc->connection = NULL;
+
+    now = ngx_time();
+    hcf = hp->conf;
+
+    points = hcf->points;
+    point = &points->point[0];
+
+    for ( ;; ) {
+        server = point[hp->hash % points->number].server;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_STREAM, pc->log, 0,
+                       "consistent hash peer:%uD, server:\"%V\"",
+                       hp->hash, server);
+
+        best = NULL;
+        best_i = 0;
+        total = 0;
+
+        for (peer = hp->rrp.peers->peer, i = 0;
+             peer;
+             peer = peer->next, i++)
+        {
+            n = i / (8 * sizeof(uintptr_t));
+            m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));
+
+            if (hp->rrp.tried[n] & m) {
+                continue;
+            }
+
+            if (peer->down) {
+                continue;
+            }
+
+            if (peer->max_fails
+                && peer->fails >= peer->max_fails
+                && now - peer->checked <= peer->fail_timeout)
+            {
+                continue;
+            }
+
+            if (peer->max_conns && peer->conns >= peer->max_conns) {
+                continue;
+            }
+
+            if (peer->server.len != server->len
+                || ngx_strncmp(peer->server.data, server->data, server->len)
+                   != 0)
+            {
+                continue;
+            }
+
+            peer->current_weight += peer->effective_weight;
+            total += peer->effective_weight;
+
+            if (peer->effective_weight < peer->weight) {
+                peer->effective_weight++;
+            }
+
+            if (best == NULL || peer->current_weight > best->current_weight) {
+                best = peer;
+                best_i = i;
+            }
+        }
+
+        if (best) {
+            best->current_weight -= total;
+            break;
+        }
+
+        hp->hash++;
+        hp->tries++;
+
+        if (hp->tries > 20) {
+            ngx_stream_upstream_rr_peers_unlock(hp->rrp.peers);
+            return hp->get_rr_peer(pc, &hp->rrp);
+        }
+    }
+
+    hp->rrp.current = best;
+
+    pc->sockaddr = best->sockaddr;
+    pc->socklen = best->socklen;
+    pc->name = &best->name;
+
+    best->conns++;
+
+    if (now - best->checked > best->fail_timeout) {
+        best->checked = now;
+    }
+
+    ngx_stream_upstream_rr_peers_unlock(hp->rrp.peers);
+
+    n = best_i / (8 * sizeof(uintptr_t));
+    m = (uintptr_t) 1 << best_i % (8 * sizeof(uintptr_t));
+
+    hp->rrp.tried[n] |= m;
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_stream_upstream_hash_create_conf(ngx_conf_t *cf)
+{
+    ngx_stream_upstream_hash_srv_conf_t  *conf;
+
+    conf = ngx_palloc(cf->pool, sizeof(ngx_stream_upstream_hash_srv_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    conf->points = NULL;
+
+    return conf;
+}
+
+
+static char *
+ngx_stream_upstream_hash(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_upstream_hash_srv_conf_t  *hcf = conf;
+
+    ngx_str_t                           *value;
+    ngx_stream_upstream_srv_conf_t      *uscf;
+    ngx_stream_compile_complex_value_t   ccv;
+
+    value = cf->args->elts;
+
+    ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[1];
+    ccv.complex_value = &hcf->key;
+
+    if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    uscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_upstream_module);
+
+    if (uscf->peer.init_upstream) {
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                           "load balancing method redefined");
+    }
+
+    uscf->flags = NGX_STREAM_UPSTREAM_CREATE
+                  |NGX_STREAM_UPSTREAM_WEIGHT
+                  |NGX_STREAM_UPSTREAM_MAX_CONNS
+                  |NGX_STREAM_UPSTREAM_MAX_FAILS
+                  |NGX_STREAM_UPSTREAM_FAIL_TIMEOUT
+                  |NGX_STREAM_UPSTREAM_DOWN;
+
+    if (cf->args->nelts == 2) {
+        uscf->peer.init_upstream = ngx_stream_upstream_init_hash;
+
+    } else if (ngx_strcmp(value[2].data, "consistent") == 0) {
+        uscf->peer.init_upstream = ngx_stream_upstream_init_chash;
+
+    } else {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[2]);
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/stream/ngx_stream_upstream_least_conn_module.c b/nginx/src/stream/ngx_stream_upstream_least_conn_module.c
new file mode 100644 (file)
index 0000000..739b20a
--- /dev/null
@@ -0,0 +1,310 @@
+
+/*
+ * Copyright (C) Maxim Dounin
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+static ngx_int_t ngx_stream_upstream_init_least_conn_peer(
+    ngx_stream_session_t *s, ngx_stream_upstream_srv_conf_t *us);
+static ngx_int_t ngx_stream_upstream_get_least_conn_peer(
+    ngx_peer_connection_t *pc, void *data);
+static char *ngx_stream_upstream_least_conn(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+
+static ngx_command_t  ngx_stream_upstream_least_conn_commands[] = {
+
+    { ngx_string("least_conn"),
+      NGX_STREAM_UPS_CONF|NGX_CONF_NOARGS,
+      ngx_stream_upstream_least_conn,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_stream_module_t  ngx_stream_upstream_least_conn_module_ctx = {
+    NULL,                                    /* preconfiguration */
+    NULL,                                    /* postconfiguration */
+
+    NULL,                                    /* create main configuration */
+    NULL,                                    /* init main configuration */
+
+    NULL,                                    /* create server configuration */
+    NULL                                     /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_stream_upstream_least_conn_module = {
+    NGX_MODULE_V1,
+    &ngx_stream_upstream_least_conn_module_ctx, /* module context */
+    ngx_stream_upstream_least_conn_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_upstream_init_least_conn(ngx_conf_t *cf,
+    ngx_stream_upstream_srv_conf_t *us)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_STREAM, cf->log, 0,
+                   "init least conn");
+
+    if (ngx_stream_upstream_init_round_robin(cf, us) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    us->peer.init = ngx_stream_upstream_init_least_conn_peer;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_upstream_init_least_conn_peer(ngx_stream_session_t *s,
+    ngx_stream_upstream_srv_conf_t *us)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                   "init least conn peer");
+
+    if (ngx_stream_upstream_init_round_robin_peer(s, us) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    s->upstream->peer.get = ngx_stream_upstream_get_least_conn_peer;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_upstream_get_least_conn_peer(ngx_peer_connection_t *pc, void *data)
+{
+    ngx_stream_upstream_rr_peer_data_t *rrp = data;
+
+    time_t                           now;
+    uintptr_t                        m;
+    ngx_int_t                        rc, total;
+    ngx_uint_t                       i, n, p, many;
+    ngx_stream_upstream_rr_peer_t   *peer, *best;
+    ngx_stream_upstream_rr_peers_t  *peers;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pc->log, 0,
+                   "get least conn peer, try: %ui", pc->tries);
+
+    if (rrp->peers->single) {
+        return ngx_stream_upstream_get_round_robin_peer(pc, rrp);
+    }
+
+    pc->connection = NULL;
+
+    now = ngx_time();
+
+    peers = rrp->peers;
+
+    ngx_stream_upstream_rr_peers_wlock(peers);
+
+    best = NULL;
+    total = 0;
+
+#if (NGX_SUPPRESS_WARN)
+    many = 0;
+    p = 0;
+#endif
+
+    for (peer = peers->peer, i = 0;
+         peer;
+         peer = peer->next, i++)
+    {
+        n = i / (8 * sizeof(uintptr_t));
+        m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));
+
+        if (rrp->tried[n] & m) {
+            continue;
+        }
+
+        if (peer->down) {
+            continue;
+        }
+
+        if (peer->max_fails
+            && peer->fails >= peer->max_fails
+            && now - peer->checked <= peer->fail_timeout)
+        {
+            continue;
+        }
+
+        if (peer->max_conns && peer->conns >= peer->max_conns) {
+            continue;
+        }
+
+        /*
+         * select peer with least number of connections; if there are
+         * multiple peers with the same number of connections, select
+         * based on round-robin
+         */
+
+        if (best == NULL
+            || peer->conns * best->weight < best->conns * peer->weight)
+        {
+            best = peer;
+            many = 0;
+            p = i;
+
+        } else if (peer->conns * best->weight == best->conns * peer->weight) {
+            many = 1;
+        }
+    }
+
+    if (best == NULL) {
+        ngx_log_debug0(NGX_LOG_DEBUG_STREAM, pc->log, 0,
+                       "get least conn peer, no peer found");
+
+        goto failed;
+    }
+
+    if (many) {
+        ngx_log_debug0(NGX_LOG_DEBUG_STREAM, pc->log, 0,
+                       "get least conn peer, many");
+
+        for (peer = best, i = p;
+             peer;
+             peer = peer->next, i++)
+        {
+            n = i / (8 * sizeof(uintptr_t));
+            m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));
+
+            if (rrp->tried[n] & m) {
+                continue;
+            }
+
+            if (peer->down) {
+                continue;
+            }
+
+            if (peer->conns * best->weight != best->conns * peer->weight) {
+                continue;
+            }
+
+            if (peer->max_fails
+                && peer->fails >= peer->max_fails
+                && now - peer->checked <= peer->fail_timeout)
+            {
+                continue;
+            }
+
+            if (peer->max_conns && peer->conns >= peer->max_conns) {
+                continue;
+            }
+
+            peer->current_weight += peer->effective_weight;
+            total += peer->effective_weight;
+
+            if (peer->effective_weight < peer->weight) {
+                peer->effective_weight++;
+            }
+
+            if (peer->current_weight > best->current_weight) {
+                best = peer;
+                p = i;
+            }
+        }
+    }
+
+    best->current_weight -= total;
+
+    if (now - best->checked > best->fail_timeout) {
+        best->checked = now;
+    }
+
+    pc->sockaddr = best->sockaddr;
+    pc->socklen = best->socklen;
+    pc->name = &best->name;
+
+    best->conns++;
+
+    rrp->current = best;
+
+    n = p / (8 * sizeof(uintptr_t));
+    m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));
+
+    rrp->tried[n] |= m;
+
+    ngx_stream_upstream_rr_peers_unlock(peers);
+
+    return NGX_OK;
+
+failed:
+
+    if (peers->next) {
+        ngx_log_debug0(NGX_LOG_DEBUG_STREAM, pc->log, 0,
+                       "get least conn peer, backup servers");
+
+        rrp->peers = peers->next;
+
+        n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1))
+                / (8 * sizeof(uintptr_t));
+
+        for (i = 0; i < n; i++) {
+            rrp->tried[i] = 0;
+        }
+
+        ngx_stream_upstream_rr_peers_unlock(peers);
+
+        rc = ngx_stream_upstream_get_least_conn_peer(pc, rrp);
+
+        if (rc != NGX_BUSY) {
+            return rc;
+        }
+
+        ngx_stream_upstream_rr_peers_wlock(peers);
+    }
+
+    ngx_stream_upstream_rr_peers_unlock(peers);
+
+    pc->name = peers->name;
+
+    return NGX_BUSY;
+}
+
+
+static char *
+ngx_stream_upstream_least_conn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_upstream_srv_conf_t  *uscf;
+
+    uscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_upstream_module);
+
+    if (uscf->peer.init_upstream) {
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                           "load balancing method redefined");
+    }
+
+    uscf->peer.init_upstream = ngx_stream_upstream_init_least_conn;
+
+    uscf->flags = NGX_STREAM_UPSTREAM_CREATE
+                  |NGX_STREAM_UPSTREAM_WEIGHT
+                  |NGX_STREAM_UPSTREAM_MAX_CONNS
+                  |NGX_STREAM_UPSTREAM_MAX_FAILS
+                  |NGX_STREAM_UPSTREAM_FAIL_TIMEOUT
+                  |NGX_STREAM_UPSTREAM_DOWN
+                  |NGX_STREAM_UPSTREAM_BACKUP;
+
+    return NGX_CONF_OK;
+}
diff --git a/nginx/src/stream/ngx_stream_upstream_round_robin.c b/nginx/src/stream/ngx_stream_upstream_round_robin.c
new file mode 100644 (file)
index 0000000..526de3a
--- /dev/null
@@ -0,0 +1,874 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+#define ngx_stream_upstream_tries(p) ((p)->number                             \
+                                      + ((p)->next ? (p)->next->number : 0))
+
+
+static ngx_stream_upstream_rr_peer_t *ngx_stream_upstream_get_peer(
+    ngx_stream_upstream_rr_peer_data_t *rrp);
+static void ngx_stream_upstream_notify_round_robin_peer(
+    ngx_peer_connection_t *pc, void *data, ngx_uint_t state);
+
+#if (NGX_STREAM_SSL)
+
+static ngx_int_t ngx_stream_upstream_set_round_robin_peer_session(
+    ngx_peer_connection_t *pc, void *data);
+static void ngx_stream_upstream_save_round_robin_peer_session(
+    ngx_peer_connection_t *pc, void *data);
+static ngx_int_t ngx_stream_upstream_empty_set_session(
+    ngx_peer_connection_t *pc, void *data);
+static void ngx_stream_upstream_empty_save_session(ngx_peer_connection_t *pc,
+    void *data);
+
+#endif
+
+
+ngx_int_t
+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_stream_upstream_server_t    *server;
+    ngx_stream_upstream_rr_peer_t   *peer, **peerp;
+    ngx_stream_upstream_rr_peers_t  *peers, *backup;
+
+    us->peer.init = ngx_stream_upstream_init_round_robin_peer;
+
+    if (us->servers) {
+        server = us->servers->elts;
+
+        n = 0;
+        w = 0;
+
+        for (i = 0; i < us->servers->nelts; i++) {
+            if (server[i].backup) {
+                continue;
+            }
+
+            n += server[i].naddrs;
+            w += server[i].naddrs * server[i].weight;
+        }
+
+        if (n == 0) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "no servers in upstream \"%V\" in %s:%ui",
+                          &us->host, us->file_name, us->line);
+            return NGX_ERROR;
+        }
+
+        peers = ngx_pcalloc(cf->pool, sizeof(ngx_stream_upstream_rr_peers_t));
+        if (peers == NULL) {
+            return NGX_ERROR;
+        }
+
+        peer = ngx_pcalloc(cf->pool, sizeof(ngx_stream_upstream_rr_peer_t) * n);
+        if (peer == NULL) {
+            return NGX_ERROR;
+        }
+
+        peers->single = (n == 1);
+        peers->number = n;
+        peers->weighted = (w != n);
+        peers->total_weight = w;
+        peers->name = &us->host;
+
+        n = 0;
+        peerp = &peers->peer;
+
+        for (i = 0; i < us->servers->nelts; i++) {
+            if (server[i].backup) {
+                continue;
+            }
+
+            for (j = 0; j < server[i].naddrs; j++) {
+                peer[n].sockaddr = server[i].addrs[j].sockaddr;
+                peer[n].socklen = server[i].addrs[j].socklen;
+                peer[n].name = server[i].addrs[j].name;
+                peer[n].weight = server[i].weight;
+                peer[n].effective_weight = server[i].weight;
+                peer[n].current_weight = 0;
+                peer[n].max_conns = server[i].max_conns;
+                peer[n].max_fails = server[i].max_fails;
+                peer[n].fail_timeout = server[i].fail_timeout;
+                peer[n].down = server[i].down;
+                peer[n].server = server[i].name;
+
+                *peerp = &peer[n];
+                peerp = &peer[n].next;
+                n++;
+            }
+        }
+
+        us->peer.data = peers;
+
+        /* backup servers */
+
+        n = 0;
+        w = 0;
+
+        for (i = 0; i < us->servers->nelts; i++) {
+            if (!server[i].backup) {
+                continue;
+            }
+
+            n += server[i].naddrs;
+            w += server[i].naddrs * server[i].weight;
+        }
+
+        if (n == 0) {
+            return NGX_OK;
+        }
+
+        backup = ngx_pcalloc(cf->pool, sizeof(ngx_stream_upstream_rr_peers_t));
+        if (backup == NULL) {
+            return NGX_ERROR;
+        }
+
+        peer = ngx_pcalloc(cf->pool, sizeof(ngx_stream_upstream_rr_peer_t) * n);
+        if (peer == NULL) {
+            return NGX_ERROR;
+        }
+
+        peers->single = 0;
+        backup->single = 0;
+        backup->number = n;
+        backup->weighted = (w != n);
+        backup->total_weight = w;
+        backup->name = &us->host;
+
+        n = 0;
+        peerp = &backup->peer;
+
+        for (i = 0; i < us->servers->nelts; i++) {
+            if (!server[i].backup) {
+                continue;
+            }
+
+            for (j = 0; j < server[i].naddrs; j++) {
+                peer[n].sockaddr = server[i].addrs[j].sockaddr;
+                peer[n].socklen = server[i].addrs[j].socklen;
+                peer[n].name = server[i].addrs[j].name;
+                peer[n].weight = server[i].weight;
+                peer[n].effective_weight = server[i].weight;
+                peer[n].current_weight = 0;
+                peer[n].max_conns = server[i].max_conns;
+                peer[n].max_fails = server[i].max_fails;
+                peer[n].fail_timeout = server[i].fail_timeout;
+                peer[n].down = server[i].down;
+                peer[n].server = server[i].name;
+
+                *peerp = &peer[n];
+                peerp = &peer[n].next;
+                n++;
+            }
+        }
+
+        peers->next = backup;
+
+        return NGX_OK;
+    }
+
+
+    /* an upstream implicitly defined by proxy_pass, etc. */
+
+    if (us->port == 0) {
+        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                      "no port in upstream \"%V\" in %s:%ui",
+                      &us->host, us->file_name, us->line);
+        return NGX_ERROR;
+    }
+
+    ngx_memzero(&u, sizeof(ngx_url_t));
+
+    u.host = us->host;
+    u.port = us->port;
+
+    if (ngx_inet_resolve_host(cf->pool, &u) != NGX_OK) {
+        if (u.err) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "%s in upstream \"%V\" in %s:%ui",
+                          u.err, &us->host, us->file_name, us->line);
+        }
+
+        return NGX_ERROR;
+    }
+
+    n = u.naddrs;
+
+    peers = ngx_pcalloc(cf->pool, sizeof(ngx_stream_upstream_rr_peers_t));
+    if (peers == NULL) {
+        return NGX_ERROR;
+    }
+
+    peer = ngx_pcalloc(cf->pool, sizeof(ngx_stream_upstream_rr_peer_t) * n);
+    if (peer == NULL) {
+        return NGX_ERROR;
+    }
+
+    peers->single = (n == 1);
+    peers->number = n;
+    peers->weighted = 0;
+    peers->total_weight = n;
+    peers->name = &us->host;
+
+    peerp = &peers->peer;
+
+    for (i = 0; i < u.naddrs; i++) {
+        peer[i].sockaddr = u.addrs[i].sockaddr;
+        peer[i].socklen = u.addrs[i].socklen;
+        peer[i].name = u.addrs[i].name;
+        peer[i].weight = 1;
+        peer[i].effective_weight = 1;
+        peer[i].current_weight = 0;
+        peer[i].max_conns = 0;
+        peer[i].max_fails = 1;
+        peer[i].fail_timeout = 10;
+        *peerp = &peer[i];
+        peerp = &peer[i].next;
+    }
+
+    us->peer.data = peers;
+
+    /* implicitly defined upstream has no backup servers */
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_stream_upstream_init_round_robin_peer(ngx_stream_session_t *s,
+    ngx_stream_upstream_srv_conf_t *us)
+{
+    ngx_uint_t                           n;
+    ngx_stream_upstream_rr_peer_data_t  *rrp;
+
+    rrp = s->upstream->peer.data;
+
+    if (rrp == NULL) {
+        rrp = ngx_palloc(s->connection->pool,
+                         sizeof(ngx_stream_upstream_rr_peer_data_t));
+        if (rrp == NULL) {
+            return NGX_ERROR;
+        }
+
+        s->upstream->peer.data = rrp;
+    }
+
+    rrp->peers = us->peer.data;
+    rrp->current = NULL;
+    rrp->config = 0;
+
+    n = rrp->peers->number;
+
+    if (rrp->peers->next && rrp->peers->next->number > n) {
+        n = rrp->peers->next->number;
+    }
+
+    if (n <= 8 * sizeof(uintptr_t)) {
+        rrp->tried = &rrp->data;
+        rrp->data = 0;
+
+    } else {
+        n = (n + (8 * sizeof(uintptr_t) - 1)) / (8 * sizeof(uintptr_t));
+
+        rrp->tried = ngx_pcalloc(s->connection->pool, n * sizeof(uintptr_t));
+        if (rrp->tried == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    s->upstream->peer.get = ngx_stream_upstream_get_round_robin_peer;
+    s->upstream->peer.free = ngx_stream_upstream_free_round_robin_peer;
+    s->upstream->peer.notify = ngx_stream_upstream_notify_round_robin_peer;
+    s->upstream->peer.tries = ngx_stream_upstream_tries(rrp->peers);
+#if (NGX_STREAM_SSL)
+    s->upstream->peer.set_session =
+                             ngx_stream_upstream_set_round_robin_peer_session;
+    s->upstream->peer.save_session =
+                             ngx_stream_upstream_save_round_robin_peer_session;
+#endif
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_stream_upstream_create_round_robin_peer(ngx_stream_session_t *s,
+    ngx_stream_upstream_resolved_t *ur)
+{
+    u_char                              *p;
+    size_t                               len;
+    socklen_t                            socklen;
+    ngx_uint_t                           i, n;
+    struct sockaddr                     *sockaddr;
+    ngx_stream_upstream_rr_peer_t       *peer, **peerp;
+    ngx_stream_upstream_rr_peers_t      *peers;
+    ngx_stream_upstream_rr_peer_data_t  *rrp;
+
+    rrp = s->upstream->peer.data;
+
+    if (rrp == NULL) {
+        rrp = ngx_palloc(s->connection->pool,
+                         sizeof(ngx_stream_upstream_rr_peer_data_t));
+        if (rrp == NULL) {
+            return NGX_ERROR;
+        }
+
+        s->upstream->peer.data = rrp;
+    }
+
+    peers = ngx_pcalloc(s->connection->pool,
+                        sizeof(ngx_stream_upstream_rr_peers_t));
+    if (peers == NULL) {
+        return NGX_ERROR;
+    }
+
+    peer = ngx_pcalloc(s->connection->pool,
+                       sizeof(ngx_stream_upstream_rr_peer_t) * ur->naddrs);
+    if (peer == NULL) {
+        return NGX_ERROR;
+    }
+
+    peers->single = (ur->naddrs == 1);
+    peers->number = ur->naddrs;
+    peers->name = &ur->host;
+
+    if (ur->sockaddr) {
+        peer[0].sockaddr = ur->sockaddr;
+        peer[0].socklen = ur->socklen;
+        peer[0].name = ur->name;
+        peer[0].weight = 1;
+        peer[0].effective_weight = 1;
+        peer[0].current_weight = 0;
+        peer[0].max_conns = 0;
+        peer[0].max_fails = 1;
+        peer[0].fail_timeout = 10;
+        peers->peer = peer;
+
+    } else {
+        peerp = &peers->peer;
+
+        for (i = 0; i < ur->naddrs; i++) {
+
+            socklen = ur->addrs[i].socklen;
+
+            sockaddr = ngx_palloc(s->connection->pool, socklen);
+            if (sockaddr == NULL) {
+                return NGX_ERROR;
+            }
+
+            ngx_memcpy(sockaddr, ur->addrs[i].sockaddr, socklen);
+            ngx_inet_set_port(sockaddr, ur->port);
+
+            p = ngx_pnalloc(s->connection->pool, NGX_SOCKADDR_STRLEN);
+            if (p == NULL) {
+                return NGX_ERROR;
+            }
+
+            len = ngx_sock_ntop(sockaddr, socklen, p, NGX_SOCKADDR_STRLEN, 1);
+
+            peer[i].sockaddr = sockaddr;
+            peer[i].socklen = socklen;
+            peer[i].name.len = len;
+            peer[i].name.data = p;
+            peer[i].weight = 1;
+            peer[i].effective_weight = 1;
+            peer[i].current_weight = 0;
+            peer[i].max_conns = 0;
+            peer[i].max_fails = 1;
+            peer[i].fail_timeout = 10;
+            *peerp = &peer[i];
+            peerp = &peer[i].next;
+        }
+    }
+
+    rrp->peers = peers;
+    rrp->current = NULL;
+    rrp->config = 0;
+
+    if (rrp->peers->number <= 8 * sizeof(uintptr_t)) {
+        rrp->tried = &rrp->data;
+        rrp->data = 0;
+
+    } else {
+        n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1))
+                / (8 * sizeof(uintptr_t));
+
+        rrp->tried = ngx_pcalloc(s->connection->pool, n * sizeof(uintptr_t));
+        if (rrp->tried == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    s->upstream->peer.get = ngx_stream_upstream_get_round_robin_peer;
+    s->upstream->peer.free = ngx_stream_upstream_free_round_robin_peer;
+    s->upstream->peer.tries = ngx_stream_upstream_tries(rrp->peers);
+#if (NGX_STREAM_SSL)
+    s->upstream->peer.set_session = ngx_stream_upstream_empty_set_session;
+    s->upstream->peer.save_session = ngx_stream_upstream_empty_save_session;
+#endif
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_stream_upstream_get_round_robin_peer(ngx_peer_connection_t *pc, void *data)
+{
+    ngx_stream_upstream_rr_peer_data_t *rrp = data;
+
+    ngx_int_t                        rc;
+    ngx_uint_t                       i, n;
+    ngx_stream_upstream_rr_peer_t   *peer;
+    ngx_stream_upstream_rr_peers_t  *peers;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pc->log, 0,
+                   "get rr peer, try: %ui", pc->tries);
+
+    pc->connection = NULL;
+
+    peers = rrp->peers;
+    ngx_stream_upstream_rr_peers_wlock(peers);
+
+    if (peers->single) {
+        peer = peers->peer;
+
+        if (peer->down) {
+            goto failed;
+        }
+
+        if (peer->max_conns && peer->conns >= peer->max_conns) {
+            goto failed;
+        }
+
+        rrp->current = peer;
+
+    } else {
+
+        /* there are several peers */
+
+        peer = ngx_stream_upstream_get_peer(rrp);
+
+        if (peer == NULL) {
+            goto failed;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_STREAM, pc->log, 0,
+                       "get rr peer, current: %p %i",
+                       peer, peer->current_weight);
+    }
+
+    pc->sockaddr = peer->sockaddr;
+    pc->socklen = peer->socklen;
+    pc->name = &peer->name;
+
+    peer->conns++;
+
+    ngx_stream_upstream_rr_peers_unlock(peers);
+
+    return NGX_OK;
+
+failed:
+
+    if (peers->next) {
+
+        ngx_log_debug0(NGX_LOG_DEBUG_STREAM, pc->log, 0, "backup servers");
+
+        rrp->peers = peers->next;
+
+        n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1))
+                / (8 * sizeof(uintptr_t));
+
+        for (i = 0; i < n; i++) {
+            rrp->tried[i] = 0;
+        }
+
+        ngx_stream_upstream_rr_peers_unlock(peers);
+
+        rc = ngx_stream_upstream_get_round_robin_peer(pc, rrp);
+
+        if (rc != NGX_BUSY) {
+            return rc;
+        }
+
+        ngx_stream_upstream_rr_peers_wlock(peers);
+    }
+
+    ngx_stream_upstream_rr_peers_unlock(peers);
+
+    pc->name = peers->name;
+
+    return NGX_BUSY;
+}
+
+
+static ngx_stream_upstream_rr_peer_t *
+ngx_stream_upstream_get_peer(ngx_stream_upstream_rr_peer_data_t *rrp)
+{
+    time_t                          now;
+    uintptr_t                       m;
+    ngx_int_t                       total;
+    ngx_uint_t                      i, n, p;
+    ngx_stream_upstream_rr_peer_t  *peer, *best;
+
+    now = ngx_time();
+
+    best = NULL;
+    total = 0;
+
+#if (NGX_SUPPRESS_WARN)
+    p = 0;
+#endif
+
+    for (peer = rrp->peers->peer, i = 0;
+         peer;
+         peer = peer->next, i++)
+    {
+        n = i / (8 * sizeof(uintptr_t));
+        m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));
+
+        if (rrp->tried[n] & m) {
+            continue;
+        }
+
+        if (peer->down) {
+            continue;
+        }
+
+        if (peer->max_fails
+            && peer->fails >= peer->max_fails
+            && now - peer->checked <= peer->fail_timeout)
+        {
+            continue;
+        }
+
+        if (peer->max_conns && peer->conns >= peer->max_conns) {
+            continue;
+        }
+
+        peer->current_weight += peer->effective_weight;
+        total += peer->effective_weight;
+
+        if (peer->effective_weight < peer->weight) {
+            peer->effective_weight++;
+        }
+
+        if (best == NULL || peer->current_weight > best->current_weight) {
+            best = peer;
+            p = i;
+        }
+    }
+
+    if (best == NULL) {
+        return NULL;
+    }
+
+    rrp->current = best;
+
+    n = p / (8 * sizeof(uintptr_t));
+    m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));
+
+    rrp->tried[n] |= m;
+
+    best->current_weight -= total;
+
+    if (now - best->checked > best->fail_timeout) {
+        best->checked = now;
+    }
+
+    return best;
+}
+
+
+void
+ngx_stream_upstream_free_round_robin_peer(ngx_peer_connection_t *pc, void *data,
+    ngx_uint_t state)
+{
+    ngx_stream_upstream_rr_peer_data_t  *rrp = data;
+
+    time_t                          now;
+    ngx_stream_upstream_rr_peer_t  *peer;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_STREAM, pc->log, 0,
+                   "free rr peer %ui %ui", pc->tries, state);
+
+    peer = rrp->current;
+
+    ngx_stream_upstream_rr_peers_rlock(rrp->peers);
+    ngx_stream_upstream_rr_peer_lock(rrp->peers, peer);
+
+    if (rrp->peers->single) {
+        peer->conns--;
+
+        ngx_stream_upstream_rr_peer_unlock(rrp->peers, peer);
+        ngx_stream_upstream_rr_peers_unlock(rrp->peers);
+
+        pc->tries = 0;
+        return;
+    }
+
+    if (state & NGX_PEER_FAILED) {
+        now = ngx_time();
+
+        peer->fails++;
+        peer->accessed = now;
+        peer->checked = now;
+
+        if (peer->max_fails) {
+            peer->effective_weight -= peer->weight / peer->max_fails;
+
+            if (peer->fails >= peer->max_fails) {
+                ngx_log_error(NGX_LOG_WARN, pc->log, 0,
+                              "upstream server temporarily disabled");
+            }
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_STREAM, pc->log, 0,
+                       "free rr peer failed: %p %i",
+                       peer, peer->effective_weight);
+
+        if (peer->effective_weight < 0) {
+            peer->effective_weight = 0;
+        }
+
+    } else {
+
+        /* mark peer live if check passed */
+
+        if (peer->accessed < peer->checked) {
+            peer->fails = 0;
+        }
+    }
+
+    peer->conns--;
+
+    ngx_stream_upstream_rr_peer_unlock(rrp->peers, peer);
+    ngx_stream_upstream_rr_peers_unlock(rrp->peers);
+
+    if (pc->tries) {
+        pc->tries--;
+    }
+}
+
+
+static void
+ngx_stream_upstream_notify_round_robin_peer(ngx_peer_connection_t *pc,
+    void *data, ngx_uint_t type)
+{
+    ngx_stream_upstream_rr_peer_data_t  *rrp = data;
+
+    ngx_stream_upstream_rr_peer_t  *peer;
+
+    peer = rrp->current;
+
+    if (type == NGX_STREAM_UPSTREAM_NOTIFY_CONNECT
+        && pc->connection->type == SOCK_STREAM)
+    {
+        ngx_stream_upstream_rr_peers_rlock(rrp->peers);
+        ngx_stream_upstream_rr_peer_lock(rrp->peers, peer);
+
+        if (peer->accessed < peer->checked) {
+            peer->fails = 0;
+        }
+
+        ngx_stream_upstream_rr_peer_unlock(rrp->peers, peer);
+        ngx_stream_upstream_rr_peers_unlock(rrp->peers);
+    }
+}
+
+
+#if (NGX_STREAM_SSL)
+
+static ngx_int_t
+ngx_stream_upstream_set_round_robin_peer_session(ngx_peer_connection_t *pc,
+    void *data)
+{
+    ngx_stream_upstream_rr_peer_data_t  *rrp = data;
+
+    ngx_int_t                        rc;
+    ngx_ssl_session_t               *ssl_session;
+    ngx_stream_upstream_rr_peer_t   *peer;
+#if (NGX_STREAM_UPSTREAM_ZONE)
+    int                              len;
+#if OPENSSL_VERSION_NUMBER >= 0x0090707fL
+    const
+#endif
+    u_char                          *p;
+    ngx_stream_upstream_rr_peers_t  *peers;
+    u_char                           buf[NGX_SSL_MAX_SESSION_SIZE];
+#endif
+
+    peer = rrp->current;
+
+#if (NGX_STREAM_UPSTREAM_ZONE)
+    peers = rrp->peers;
+
+    if (peers->shpool) {
+        ngx_stream_upstream_rr_peers_rlock(peers);
+        ngx_stream_upstream_rr_peer_lock(peers, peer);
+
+        if (peer->ssl_session == NULL) {
+            ngx_stream_upstream_rr_peer_unlock(peers, peer);
+            ngx_stream_upstream_rr_peers_unlock(peers);
+            return NGX_OK;
+        }
+
+        len = peer->ssl_session_len;
+
+        ngx_memcpy(buf, peer->ssl_session, len);
+
+        ngx_stream_upstream_rr_peer_unlock(peers, peer);
+        ngx_stream_upstream_rr_peers_unlock(peers);
+
+        p = buf;
+        ssl_session = d2i_SSL_SESSION(NULL, &p, len);
+
+        rc = ngx_ssl_set_session(pc->connection, ssl_session);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pc->log, 0,
+                       "set session: %p", ssl_session);
+
+        ngx_ssl_free_session(ssl_session);
+
+        return rc;
+    }
+#endif
+
+    ssl_session = peer->ssl_session;
+
+    rc = ngx_ssl_set_session(pc->connection, ssl_session);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pc->log, 0,
+                   "set session: %p", ssl_session);
+
+    return rc;
+}
+
+
+static void
+ngx_stream_upstream_save_round_robin_peer_session(ngx_peer_connection_t *pc,
+    void *data)
+{
+    ngx_stream_upstream_rr_peer_data_t  *rrp = data;
+
+    ngx_ssl_session_t               *old_ssl_session, *ssl_session;
+    ngx_stream_upstream_rr_peer_t   *peer;
+#if (NGX_STREAM_UPSTREAM_ZONE)
+    int                              len;
+    u_char                          *p;
+    ngx_stream_upstream_rr_peers_t  *peers;
+    u_char                           buf[NGX_SSL_MAX_SESSION_SIZE];
+#endif
+
+#if (NGX_STREAM_UPSTREAM_ZONE)
+    peers = rrp->peers;
+
+    if (peers->shpool) {
+
+        ssl_session = SSL_get0_session(pc->connection->ssl->connection);
+
+        if (ssl_session == NULL) {
+            return;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pc->log, 0,
+                       "save session: %p", ssl_session);
+
+        len = i2d_SSL_SESSION(ssl_session, NULL);
+
+        /* do not cache too big session */
+
+        if (len > NGX_SSL_MAX_SESSION_SIZE) {
+            return;
+        }
+
+        p = buf;
+        (void) i2d_SSL_SESSION(ssl_session, &p);
+
+        peer = rrp->current;
+
+        ngx_stream_upstream_rr_peers_rlock(peers);
+        ngx_stream_upstream_rr_peer_lock(peers, peer);
+
+        if (len > peer->ssl_session_len) {
+            ngx_shmtx_lock(&peers->shpool->mutex);
+
+            if (peer->ssl_session) {
+                ngx_slab_free_locked(peers->shpool, peer->ssl_session);
+            }
+
+            peer->ssl_session = ngx_slab_alloc_locked(peers->shpool, len);
+
+            ngx_shmtx_unlock(&peers->shpool->mutex);
+
+            if (peer->ssl_session == NULL) {
+                peer->ssl_session_len = 0;
+
+                ngx_stream_upstream_rr_peer_unlock(peers, peer);
+                ngx_stream_upstream_rr_peers_unlock(peers);
+                return;
+            }
+
+            peer->ssl_session_len = len;
+        }
+
+        ngx_memcpy(peer->ssl_session, buf, len);
+
+        ngx_stream_upstream_rr_peer_unlock(peers, peer);
+        ngx_stream_upstream_rr_peers_unlock(peers);
+
+        return;
+    }
+#endif
+
+    ssl_session = ngx_ssl_get_session(pc->connection);
+
+    if (ssl_session == NULL) {
+        return;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pc->log, 0,
+                   "save session: %p", ssl_session);
+
+    peer = rrp->current;
+
+    old_ssl_session = peer->ssl_session;
+    peer->ssl_session = ssl_session;
+
+    if (old_ssl_session) {
+
+        ngx_log_debug1(NGX_LOG_DEBUG_STREAM, pc->log, 0,
+                       "old session: %p", old_ssl_session);
+
+        /* TODO: may block */
+
+        ngx_ssl_free_session(old_ssl_session);
+    }
+}
+
+
+static ngx_int_t
+ngx_stream_upstream_empty_set_session(ngx_peer_connection_t *pc, void *data)
+{
+    return NGX_OK;
+}
+
+
+static void
+ngx_stream_upstream_empty_save_session(ngx_peer_connection_t *pc, void *data)
+{
+    return;
+}
+
+#endif
diff --git a/nginx/src/stream/ngx_stream_upstream_round_robin.h b/nginx/src/stream/ngx_stream_upstream_round_robin.h
new file mode 100644 (file)
index 0000000..35d9fce
--- /dev/null
@@ -0,0 +1,146 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_STREAM_UPSTREAM_ROUND_ROBIN_H_INCLUDED_
+#define _NGX_STREAM_UPSTREAM_ROUND_ROBIN_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+typedef struct ngx_stream_upstream_rr_peer_s   ngx_stream_upstream_rr_peer_t;
+
+struct ngx_stream_upstream_rr_peer_s {
+    struct sockaddr                 *sockaddr;
+    socklen_t                        socklen;
+    ngx_str_t                        name;
+    ngx_str_t                        server;
+
+    ngx_int_t                        current_weight;
+    ngx_int_t                        effective_weight;
+    ngx_int_t                        weight;
+
+    ngx_uint_t                       conns;
+    ngx_uint_t                       max_conns;
+
+    ngx_uint_t                       fails;
+    time_t                           accessed;
+    time_t                           checked;
+
+    ngx_uint_t                       max_fails;
+    time_t                           fail_timeout;
+    ngx_msec_t                       slow_start;
+    ngx_msec_t                       start_time;
+
+    ngx_uint_t                       down;
+
+    void                            *ssl_session;
+    int                              ssl_session_len;
+
+#if (NGX_STREAM_UPSTREAM_ZONE)
+    ngx_atomic_t                     lock;
+#endif
+
+    ngx_stream_upstream_rr_peer_t   *next;
+
+    NGX_COMPAT_BEGIN(25)
+    NGX_COMPAT_END
+};
+
+
+typedef struct ngx_stream_upstream_rr_peers_s  ngx_stream_upstream_rr_peers_t;
+
+struct ngx_stream_upstream_rr_peers_s {
+    ngx_uint_t                       number;
+
+#if (NGX_STREAM_UPSTREAM_ZONE)
+    ngx_slab_pool_t                 *shpool;
+    ngx_atomic_t                     rwlock;
+    ngx_stream_upstream_rr_peers_t  *zone_next;
+#endif
+
+    ngx_uint_t                       total_weight;
+
+    unsigned                         single:1;
+    unsigned                         weighted:1;
+
+    ngx_str_t                       *name;
+
+    ngx_stream_upstream_rr_peers_t  *next;
+
+    ngx_stream_upstream_rr_peer_t   *peer;
+};
+
+
+#if (NGX_STREAM_UPSTREAM_ZONE)
+
+#define ngx_stream_upstream_rr_peers_rlock(peers)                             \
+                                                                              \
+    if (peers->shpool) {                                                      \
+        ngx_rwlock_rlock(&peers->rwlock);                                     \
+    }
+
+#define ngx_stream_upstream_rr_peers_wlock(peers)                             \
+                                                                              \
+    if (peers->shpool) {                                                      \
+        ngx_rwlock_wlock(&peers->rwlock);                                     \
+    }
+
+#define ngx_stream_upstream_rr_peers_unlock(peers)                            \
+                                                                              \
+    if (peers->shpool) {                                                      \
+        ngx_rwlock_unlock(&peers->rwlock);                                    \
+    }
+
+
+#define ngx_stream_upstream_rr_peer_lock(peers, peer)                         \
+                                                                              \
+    if (peers->shpool) {                                                      \
+        ngx_rwlock_wlock(&peer->lock);                                        \
+    }
+
+#define ngx_stream_upstream_rr_peer_unlock(peers, peer)                       \
+                                                                              \
+    if (peers->shpool) {                                                      \
+        ngx_rwlock_unlock(&peer->lock);                                       \
+    }
+
+#else
+
+#define ngx_stream_upstream_rr_peers_rlock(peers)
+#define ngx_stream_upstream_rr_peers_wlock(peers)
+#define ngx_stream_upstream_rr_peers_unlock(peers)
+#define ngx_stream_upstream_rr_peer_lock(peers, peer)
+#define ngx_stream_upstream_rr_peer_unlock(peers, peer)
+
+#endif
+
+
+typedef struct {
+    ngx_uint_t                       config;
+    ngx_stream_upstream_rr_peers_t  *peers;
+    ngx_stream_upstream_rr_peer_t   *current;
+    uintptr_t                       *tried;
+    uintptr_t                        data;
+} ngx_stream_upstream_rr_peer_data_t;
+
+
+ngx_int_t ngx_stream_upstream_init_round_robin(ngx_conf_t *cf,
+    ngx_stream_upstream_srv_conf_t *us);
+ngx_int_t ngx_stream_upstream_init_round_robin_peer(ngx_stream_session_t *s,
+    ngx_stream_upstream_srv_conf_t *us);
+ngx_int_t ngx_stream_upstream_create_round_robin_peer(ngx_stream_session_t *s,
+    ngx_stream_upstream_resolved_t *ur);
+ngx_int_t ngx_stream_upstream_get_round_robin_peer(ngx_peer_connection_t *pc,
+    void *data);
+void ngx_stream_upstream_free_round_robin_peer(ngx_peer_connection_t *pc,
+    void *data, ngx_uint_t state);
+
+
+#endif /* _NGX_STREAM_UPSTREAM_ROUND_ROBIN_H_INCLUDED_ */
diff --git a/nginx/src/stream/ngx_stream_upstream_zone_module.c b/nginx/src/stream/ngx_stream_upstream_zone_module.c
new file mode 100644 (file)
index 0000000..80d42fa
--- /dev/null
@@ -0,0 +1,322 @@
+
+/*
+ * Copyright (C) Ruslan Ermilov
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+static char *ngx_stream_upstream_zone(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static ngx_int_t ngx_stream_upstream_init_zone(ngx_shm_zone_t *shm_zone,
+    void *data);
+static ngx_stream_upstream_rr_peers_t *ngx_stream_upstream_zone_copy_peers(
+    ngx_slab_pool_t *shpool, ngx_stream_upstream_srv_conf_t *uscf);
+static ngx_stream_upstream_rr_peer_t *ngx_stream_upstream_zone_copy_peer(
+    ngx_stream_upstream_rr_peers_t *peers, ngx_stream_upstream_rr_peer_t *src);
+
+
+static ngx_command_t  ngx_stream_upstream_zone_commands[] = {
+
+    { ngx_string("zone"),
+      NGX_STREAM_UPS_CONF|NGX_CONF_TAKE12,
+      ngx_stream_upstream_zone,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_stream_module_t  ngx_stream_upstream_zone_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL                                   /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_stream_upstream_zone_module = {
+    NGX_MODULE_V1,
+    &ngx_stream_upstream_zone_module_ctx,  /* module context */
+    ngx_stream_upstream_zone_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 char *
+ngx_stream_upstream_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ssize_t                           size;
+    ngx_str_t                        *value;
+    ngx_stream_upstream_srv_conf_t   *uscf;
+    ngx_stream_upstream_main_conf_t  *umcf;
+
+    uscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_upstream_module);
+    umcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_upstream_module);
+
+    value = cf->args->elts;
+
+    if (!value[1].len) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid zone name \"%V\"", &value[1]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (cf->args->nelts == 3) {
+        size = ngx_parse_size(&value[2]);
+
+        if (size == NGX_ERROR) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid zone size \"%V\"", &value[2]);
+            return NGX_CONF_ERROR;
+        }
+
+        if (size < (ssize_t) (8 * ngx_pagesize)) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "zone \"%V\" is too small", &value[1]);
+            return NGX_CONF_ERROR;
+        }
+
+    } else {
+        size = 0;
+    }
+
+    uscf->shm_zone = ngx_shared_memory_add(cf, &value[1], size,
+                                           &ngx_stream_upstream_module);
+    if (uscf->shm_zone == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    uscf->shm_zone->init = ngx_stream_upstream_init_zone;
+    uscf->shm_zone->data = umcf;
+
+    uscf->shm_zone->noreuse = 1;
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_upstream_init_zone(ngx_shm_zone_t *shm_zone, void *data)
+{
+    size_t                            len;
+    ngx_uint_t                        i;
+    ngx_slab_pool_t                  *shpool;
+    ngx_stream_upstream_rr_peers_t   *peers, **peersp;
+    ngx_stream_upstream_srv_conf_t   *uscf, **uscfp;
+    ngx_stream_upstream_main_conf_t  *umcf;
+
+    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+    umcf = shm_zone->data;
+    uscfp = umcf->upstreams.elts;
+
+    if (shm_zone->shm.exists) {
+        peers = shpool->data;
+
+        for (i = 0; i < umcf->upstreams.nelts; i++) {
+            uscf = uscfp[i];
+
+            if (uscf->shm_zone != shm_zone) {
+                continue;
+            }
+
+            uscf->peer.data = peers;
+            peers = peers->zone_next;
+        }
+
+        return NGX_OK;
+    }
+
+    len = sizeof(" in upstream zone \"\"") + shm_zone->shm.name.len;
+
+    shpool->log_ctx = ngx_slab_alloc(shpool, len);
+    if (shpool->log_ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_sprintf(shpool->log_ctx, " in upstream zone \"%V\"%Z",
+                &shm_zone->shm.name);
+
+
+    /* copy peers to shared memory */
+
+    peersp = (ngx_stream_upstream_rr_peers_t **) (void *) &shpool->data;
+
+    for (i = 0; i < umcf->upstreams.nelts; i++) {
+        uscf = uscfp[i];
+
+        if (uscf->shm_zone != shm_zone) {
+            continue;
+        }
+
+        peers = ngx_stream_upstream_zone_copy_peers(shpool, uscf);
+        if (peers == NULL) {
+            return NGX_ERROR;
+        }
+
+        *peersp = peers;
+        peersp = &peers->zone_next;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_stream_upstream_rr_peers_t *
+ngx_stream_upstream_zone_copy_peers(ngx_slab_pool_t *shpool,
+    ngx_stream_upstream_srv_conf_t *uscf)
+{
+    ngx_str_t                       *name;
+    ngx_stream_upstream_rr_peer_t   *peer, **peerp;
+    ngx_stream_upstream_rr_peers_t  *peers, *backup;
+
+    peers = ngx_slab_alloc(shpool, sizeof(ngx_stream_upstream_rr_peers_t));
+    if (peers == NULL) {
+        return NULL;
+    }
+
+    ngx_memcpy(peers, uscf->peer.data, sizeof(ngx_stream_upstream_rr_peers_t));
+
+    name = ngx_slab_alloc(shpool, sizeof(ngx_str_t));
+    if (name == NULL) {
+        return NULL;
+    }
+
+    name->data = ngx_slab_alloc(shpool, peers->name->len);
+    if (name->data == NULL) {
+        return NULL;
+    }
+
+    ngx_memcpy(name->data, peers->name->data, peers->name->len);
+    name->len = peers->name->len;
+
+    peers->name = name;
+
+    peers->shpool = shpool;
+
+    for (peerp = &peers->peer; *peerp; peerp = &peer->next) {
+        /* pool is unlocked */
+        peer = ngx_stream_upstream_zone_copy_peer(peers, *peerp);
+        if (peer == NULL) {
+            return NULL;
+        }
+
+        *peerp = peer;
+    }
+
+    if (peers->next == NULL) {
+        goto done;
+    }
+
+    backup = ngx_slab_alloc(shpool, sizeof(ngx_stream_upstream_rr_peers_t));
+    if (backup == NULL) {
+        return NULL;
+    }
+
+    ngx_memcpy(backup, peers->next, sizeof(ngx_stream_upstream_rr_peers_t));
+
+    backup->name = name;
+
+    backup->shpool = shpool;
+
+    for (peerp = &backup->peer; *peerp; peerp = &peer->next) {
+        /* pool is unlocked */
+        peer = ngx_stream_upstream_zone_copy_peer(backup, *peerp);
+        if (peer == NULL) {
+            return NULL;
+        }
+
+        *peerp = peer;
+    }
+
+    peers->next = backup;
+
+done:
+
+    uscf->peer.data = peers;
+
+    return peers;
+}
+
+
+static ngx_stream_upstream_rr_peer_t *
+ngx_stream_upstream_zone_copy_peer(ngx_stream_upstream_rr_peers_t *peers,
+    ngx_stream_upstream_rr_peer_t *src)
+{
+    ngx_slab_pool_t                *pool;
+    ngx_stream_upstream_rr_peer_t  *dst;
+
+    pool = peers->shpool;
+
+    dst = ngx_slab_calloc_locked(pool, sizeof(ngx_stream_upstream_rr_peer_t));
+    if (dst == NULL) {
+        return NULL;
+    }
+
+    if (src) {
+        ngx_memcpy(dst, src, sizeof(ngx_stream_upstream_rr_peer_t));
+        dst->sockaddr = NULL;
+        dst->name.data = NULL;
+        dst->server.data = NULL;
+    }
+
+    dst->sockaddr = ngx_slab_calloc_locked(pool, sizeof(ngx_sockaddr_t));
+    if (dst->sockaddr == NULL) {
+        goto failed;
+    }
+
+    dst->name.data = ngx_slab_calloc_locked(pool, NGX_SOCKADDR_STRLEN);
+    if (dst->name.data == NULL) {
+        goto failed;
+    }
+
+    if (src) {
+        ngx_memcpy(dst->sockaddr, src->sockaddr, src->socklen);
+        ngx_memcpy(dst->name.data, src->name.data, src->name.len);
+
+        dst->server.data = ngx_slab_alloc_locked(pool, src->server.len);
+        if (dst->server.data == NULL) {
+            goto failed;
+        }
+
+        ngx_memcpy(dst->server.data, src->server.data, src->server.len);
+    }
+
+    return dst;
+
+failed:
+
+    if (dst->server.data) {
+        ngx_slab_free_locked(pool, dst->server.data);
+    }
+
+    if (dst->name.data) {
+        ngx_slab_free_locked(pool, dst->name.data);
+    }
+
+    if (dst->sockaddr) {
+        ngx_slab_free_locked(pool, dst->sockaddr);
+    }
+
+    ngx_slab_free_locked(pool, dst);
+
+    return NULL;
+}
diff --git a/nginx/src/stream/ngx_stream_variables.c b/nginx/src/stream/ngx_stream_variables.c
new file mode 100644 (file)
index 0000000..d1526a9
--- /dev/null
@@ -0,0 +1,1250 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+#include <nginx.h>
+
+static ngx_stream_variable_t *ngx_stream_add_prefix_variable(ngx_conf_t *cf,
+    ngx_str_t *name, ngx_uint_t flags);
+
+static ngx_int_t ngx_stream_variable_binary_remote_addr(
+    ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_variable_remote_addr(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_variable_remote_port(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_variable_proxy_protocol_addr(
+    ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_variable_proxy_protocol_port(
+    ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_variable_server_addr(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_variable_server_port(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_variable_bytes(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_variable_session_time(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_variable_status(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_variable_connection(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+
+static ngx_int_t ngx_stream_variable_nginx_version(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_variable_hostname(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_variable_pid(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_variable_msec(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_variable_time_iso8601(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_variable_time_local(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_variable_protocol(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+
+
+static ngx_stream_variable_t  ngx_stream_core_variables[] = {
+
+    { ngx_string("binary_remote_addr"), NULL,
+      ngx_stream_variable_binary_remote_addr, 0, 0, 0 },
+
+    { ngx_string("remote_addr"), NULL,
+      ngx_stream_variable_remote_addr, 0, 0, 0 },
+
+    { ngx_string("remote_port"), NULL,
+      ngx_stream_variable_remote_port, 0, 0, 0 },
+
+    { ngx_string("proxy_protocol_addr"), NULL,
+      ngx_stream_variable_proxy_protocol_addr, 0, 0, 0 },
+
+    { ngx_string("proxy_protocol_port"), NULL,
+      ngx_stream_variable_proxy_protocol_port, 0, 0, 0 },
+
+    { ngx_string("server_addr"), NULL,
+      ngx_stream_variable_server_addr, 0, 0, 0 },
+
+    { ngx_string("server_port"), NULL,
+      ngx_stream_variable_server_port, 0, 0, 0 },
+
+    { ngx_string("bytes_sent"), NULL, ngx_stream_variable_bytes,
+      0, 0, 0 },
+
+    { ngx_string("bytes_received"), NULL, ngx_stream_variable_bytes,
+      1, 0, 0 },
+
+    { ngx_string("session_time"), NULL, ngx_stream_variable_session_time,
+      0, NGX_STREAM_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("status"), NULL, ngx_stream_variable_status,
+      0, NGX_STREAM_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("connection"), NULL,
+      ngx_stream_variable_connection, 0, 0, 0 },
+
+    { ngx_string("nginx_version"), NULL, ngx_stream_variable_nginx_version,
+      0, 0, 0 },
+
+    { ngx_string("hostname"), NULL, ngx_stream_variable_hostname,
+      0, 0, 0 },
+
+    { ngx_string("pid"), NULL, ngx_stream_variable_pid,
+      0, 0, 0 },
+
+    { ngx_string("msec"), NULL, ngx_stream_variable_msec,
+      0, NGX_STREAM_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("time_iso8601"), NULL, ngx_stream_variable_time_iso8601,
+      0, NGX_STREAM_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("time_local"), NULL, ngx_stream_variable_time_local,
+      0, NGX_STREAM_VAR_NOCACHEABLE, 0 },
+
+    { ngx_string("protocol"), NULL,
+      ngx_stream_variable_protocol, 0, 0, 0 },
+
+      ngx_stream_null_variable
+};
+
+
+ngx_stream_variable_value_t  ngx_stream_variable_null_value =
+    ngx_stream_variable("");
+ngx_stream_variable_value_t  ngx_stream_variable_true_value =
+    ngx_stream_variable("1");
+
+
+static ngx_uint_t  ngx_stream_variable_depth = 100;
+
+
+ngx_stream_variable_t *
+ngx_stream_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags)
+{
+    ngx_int_t                     rc;
+    ngx_uint_t                    i;
+    ngx_hash_key_t               *key;
+    ngx_stream_variable_t        *v;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    if (name->len == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid variable name \"$\"");
+        return NULL;
+    }
+
+    if (flags & NGX_STREAM_VAR_PREFIX) {
+        return ngx_stream_add_prefix_variable(cf, name, flags);
+    }
+
+    cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module);
+
+    key = cmcf->variables_keys->keys.elts;
+    for (i = 0; i < cmcf->variables_keys->keys.nelts; i++) {
+        if (name->len != key[i].key.len
+            || ngx_strncasecmp(name->data, key[i].key.data, name->len) != 0)
+        {
+            continue;
+        }
+
+        v = key[i].value;
+
+        if (!(v->flags & NGX_STREAM_VAR_CHANGEABLE)) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "the duplicate \"%V\" variable", name);
+            return NULL;
+        }
+
+        if (!(flags & NGX_STREAM_VAR_WEAK)) {
+            v->flags &= ~NGX_STREAM_VAR_WEAK;
+        }
+
+        return v;
+    }
+
+    v = ngx_palloc(cf->pool, sizeof(ngx_stream_variable_t));
+    if (v == NULL) {
+        return NULL;
+    }
+
+    v->name.len = name->len;
+    v->name.data = ngx_pnalloc(cf->pool, name->len);
+    if (v->name.data == NULL) {
+        return NULL;
+    }
+
+    ngx_strlow(v->name.data, name->data, name->len);
+
+    v->set_handler = NULL;
+    v->get_handler = NULL;
+    v->data = 0;
+    v->flags = flags;
+    v->index = 0;
+
+    rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v, 0);
+
+    if (rc == NGX_ERROR) {
+        return NULL;
+    }
+
+    if (rc == NGX_BUSY) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "conflicting variable name \"%V\"", name);
+        return NULL;
+    }
+
+    return v;
+}
+
+
+static ngx_stream_variable_t *
+ngx_stream_add_prefix_variable(ngx_conf_t *cf, ngx_str_t *name,
+    ngx_uint_t flags)
+{
+    ngx_uint_t                    i;
+    ngx_stream_variable_t        *v;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module);
+
+    v = cmcf->prefix_variables.elts;
+    for (i = 0; i < cmcf->prefix_variables.nelts; i++) {
+        if (name->len != v[i].name.len
+            || ngx_strncasecmp(name->data, v[i].name.data, name->len) != 0)
+        {
+            continue;
+        }
+
+        v = &v[i];
+
+        if (!(v->flags & NGX_STREAM_VAR_CHANGEABLE)) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "the duplicate \"%V\" variable", name);
+            return NULL;
+        }
+
+        if (!(flags & NGX_STREAM_VAR_WEAK)) {
+            v->flags &= ~NGX_STREAM_VAR_WEAK;
+        }
+
+        return v;
+    }
+
+    v = ngx_array_push(&cmcf->prefix_variables);
+    if (v == NULL) {
+        return NULL;
+    }
+
+    v->name.len = name->len;
+    v->name.data = ngx_pnalloc(cf->pool, name->len);
+    if (v->name.data == NULL) {
+        return NULL;
+    }
+
+    ngx_strlow(v->name.data, name->data, name->len);
+
+    v->set_handler = NULL;
+    v->get_handler = NULL;
+    v->data = 0;
+    v->flags = flags;
+    v->index = 0;
+
+    return v;
+}
+
+
+ngx_int_t
+ngx_stream_get_variable_index(ngx_conf_t *cf, ngx_str_t *name)
+{
+    ngx_uint_t                    i;
+    ngx_stream_variable_t        *v;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    if (name->len == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid variable name \"$\"");
+        return NGX_ERROR;
+    }
+
+    cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module);
+
+    v = cmcf->variables.elts;
+
+    if (v == NULL) {
+        if (ngx_array_init(&cmcf->variables, cf->pool, 4,
+                           sizeof(ngx_stream_variable_t))
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+
+    } else {
+        for (i = 0; i < cmcf->variables.nelts; i++) {
+            if (name->len != v[i].name.len
+                || ngx_strncasecmp(name->data, v[i].name.data, name->len) != 0)
+            {
+                continue;
+            }
+
+            return i;
+        }
+    }
+
+    v = ngx_array_push(&cmcf->variables);
+    if (v == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->name.len = name->len;
+    v->name.data = ngx_pnalloc(cf->pool, name->len);
+    if (v->name.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_strlow(v->name.data, name->data, name->len);
+
+    v->set_handler = NULL;
+    v->get_handler = NULL;
+    v->data = 0;
+    v->flags = 0;
+    v->index = cmcf->variables.nelts - 1;
+
+    return v->index;
+}
+
+
+ngx_stream_variable_value_t *
+ngx_stream_get_indexed_variable(ngx_stream_session_t *s, ngx_uint_t index)
+{
+    ngx_stream_variable_t        *v;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module);
+
+    if (cmcf->variables.nelts <= index) {
+        ngx_log_error(NGX_LOG_ALERT, s->connection->log, 0,
+                      "unknown variable index: %ui", index);
+        return NULL;
+    }
+
+    if (s->variables[index].not_found || s->variables[index].valid) {
+        return &s->variables[index];
+    }
+
+    v = cmcf->variables.elts;
+
+    if (ngx_stream_variable_depth == 0) {
+        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                      "cycle while evaluating variable \"%V\"",
+                      &v[index].name);
+        return NULL;
+    }
+
+    ngx_stream_variable_depth--;
+
+    if (v[index].get_handler(s, &s->variables[index], v[index].data)
+        == NGX_OK)
+    {
+        ngx_stream_variable_depth++;
+
+        if (v[index].flags & NGX_STREAM_VAR_NOCACHEABLE) {
+            s->variables[index].no_cacheable = 1;
+        }
+
+        return &s->variables[index];
+    }
+
+    ngx_stream_variable_depth++;
+
+    s->variables[index].valid = 0;
+    s->variables[index].not_found = 1;
+
+    return NULL;
+}
+
+
+ngx_stream_variable_value_t *
+ngx_stream_get_flushed_variable(ngx_stream_session_t *s, ngx_uint_t index)
+{
+    ngx_stream_variable_value_t  *v;
+
+    v = &s->variables[index];
+
+    if (v->valid || v->not_found) {
+        if (!v->no_cacheable) {
+            return v;
+        }
+
+        v->valid = 0;
+        v->not_found = 0;
+    }
+
+    return ngx_stream_get_indexed_variable(s, index);
+}
+
+
+ngx_stream_variable_value_t *
+ngx_stream_get_variable(ngx_stream_session_t *s, ngx_str_t *name,
+    ngx_uint_t key)
+{
+    size_t                        len;
+    ngx_uint_t                    i, n;
+    ngx_stream_variable_t        *v;
+    ngx_stream_variable_value_t  *vv;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module);
+
+    v = ngx_hash_find(&cmcf->variables_hash, key, name->data, name->len);
+
+    if (v) {
+        if (v->flags & NGX_STREAM_VAR_INDEXED) {
+            return ngx_stream_get_flushed_variable(s, v->index);
+        }
+
+        if (ngx_stream_variable_depth == 0) {
+            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                          "cycle while evaluating variable \"%V\"", name);
+            return NULL;
+        }
+
+        ngx_stream_variable_depth--;
+
+        vv = ngx_palloc(s->connection->pool,
+                        sizeof(ngx_stream_variable_value_t));
+
+        if (vv && v->get_handler(s, vv, v->data) == NGX_OK) {
+            ngx_stream_variable_depth++;
+            return vv;
+        }
+
+        ngx_stream_variable_depth++;
+        return NULL;
+    }
+
+    vv = ngx_palloc(s->connection->pool, sizeof(ngx_stream_variable_value_t));
+    if (vv == NULL) {
+        return NULL;
+    }
+
+    len = 0;
+
+    v = cmcf->prefix_variables.elts;
+    n = cmcf->prefix_variables.nelts;
+
+    for (i = 0; i < cmcf->prefix_variables.nelts; i++) {
+        if (name->len >= v[i].name.len && name->len > len
+            && ngx_strncmp(name->data, v[i].name.data, v[i].name.len) == 0)
+        {
+            len = v[i].name.len;
+            n = i;
+        }
+    }
+
+    if (n != cmcf->prefix_variables.nelts) {
+        if (v[n].get_handler(s, vv, (uintptr_t) name) == NGX_OK) {
+            return vv;
+        }
+
+        return NULL;
+    }
+
+    vv->not_found = 1;
+
+    return vv;
+}
+
+
+static ngx_int_t
+ngx_stream_variable_binary_remote_addr(ngx_stream_session_t *s,
+     ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    struct sockaddr_in   *sin;
+#if (NGX_HAVE_INET6)
+    struct sockaddr_in6  *sin6;
+#endif
+
+    switch (s->connection->sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+    case AF_INET6:
+        sin6 = (struct sockaddr_in6 *) s->connection->sockaddr;
+
+        v->len = sizeof(struct in6_addr);
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = sin6->sin6_addr.s6_addr;
+
+        break;
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+    case AF_UNIX:
+
+        v->len = s->connection->addr_text.len;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = s->connection->addr_text.data;
+
+        break;
+#endif
+
+    default: /* AF_INET */
+        sin = (struct sockaddr_in *) s->connection->sockaddr;
+
+        v->len = sizeof(in_addr_t);
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = (u_char *) &sin->sin_addr;
+
+        break;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_variable_remote_addr(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    v->len = s->connection->addr_text.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = s->connection->addr_text.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_variable_remote_port(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    ngx_uint_t  port;
+
+    v->len = 0;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    v->data = ngx_pnalloc(s->connection->pool, sizeof("65535") - 1);
+    if (v->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    port = ngx_inet_get_port(s->connection->sockaddr);
+
+    if (port > 0 && port < 65536) {
+        v->len = ngx_sprintf(v->data, "%ui", port) - v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_variable_proxy_protocol_addr(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    v->len = s->connection->proxy_protocol_addr.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = s->connection->proxy_protocol_addr.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_variable_proxy_protocol_port(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    ngx_uint_t  port;
+
+    v->len = 0;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    v->data = ngx_pnalloc(s->connection->pool, sizeof("65535") - 1);
+    if (v->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    port = s->connection->proxy_protocol_port;
+
+    if (port > 0 && port < 65536) {
+        v->len = ngx_sprintf(v->data, "%ui", port) - v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_variable_server_addr(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    ngx_str_t  str;
+    u_char     addr[NGX_SOCKADDR_STRLEN];
+
+    str.len = NGX_SOCKADDR_STRLEN;
+    str.data = addr;
+
+    if (ngx_connection_local_sockaddr(s->connection, &str, 0) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    str.data = ngx_pnalloc(s->connection->pool, str.len);
+    if (str.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(str.data, addr, str.len);
+
+    v->len = str.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = str.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_variable_server_port(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    ngx_uint_t  port;
+
+    v->len = 0;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    if (ngx_connection_local_sockaddr(s->connection, NULL, 0) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    v->data = ngx_pnalloc(s->connection->pool, sizeof("65535") - 1);
+    if (v->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    port = ngx_inet_get_port(s->connection->local_sockaddr);
+
+    if (port > 0 && port < 65536) {
+        v->len = ngx_sprintf(v->data, "%ui", port) - v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_variable_bytes(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    u_char  *p;
+
+    p = ngx_pnalloc(s->connection->pool, NGX_OFF_T_LEN);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (data == 1) {
+        v->len = ngx_sprintf(p, "%O", s->received) - p;
+
+    } else {
+        v->len = ngx_sprintf(p, "%O", s->connection->sent) - p;
+    }
+
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = p;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_variable_session_time(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    u_char          *p;
+    ngx_time_t      *tp;
+    ngx_msec_int_t   ms;
+
+    p = ngx_pnalloc(s->connection->pool, NGX_TIME_T_LEN + 4);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    tp = ngx_timeofday();
+
+    ms = (ngx_msec_int_t)
+             ((tp->sec - s->start_sec) * 1000 + (tp->msec - s->start_msec));
+    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_stream_variable_status(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    v->data = ngx_pnalloc(s->connection->pool, NGX_INT_T_LEN);
+    if (v->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->len = ngx_sprintf(v->data, "%03ui", s->status) - v->data;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_variable_connection(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    u_char  *p;
+
+    p = ngx_pnalloc(s->connection->pool, NGX_ATOMIC_T_LEN);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->len = ngx_sprintf(p, "%uA", s->connection->number) - p;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = p;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_variable_nginx_version(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    v->len = sizeof(NGINX_VERSION) - 1;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = (u_char *) NGINX_VERSION;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_variable_hostname(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    v->len = ngx_cycle->hostname.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = ngx_cycle->hostname.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_variable_pid(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    u_char  *p;
+
+    p = ngx_pnalloc(s->connection->pool, NGX_INT64_LEN);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->len = ngx_sprintf(p, "%P", ngx_pid) - p;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = p;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_variable_msec(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    u_char      *p;
+    ngx_time_t  *tp;
+
+    p = ngx_pnalloc(s->connection->pool, NGX_TIME_T_LEN + 4);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    tp = ngx_timeofday();
+
+    v->len = ngx_sprintf(p, "%T.%03M", tp->sec, tp->msec) - p;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = p;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_variable_time_iso8601(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    u_char  *p;
+
+    p = ngx_pnalloc(s->connection->pool, ngx_cached_http_log_iso8601.len);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(p, ngx_cached_http_log_iso8601.data,
+               ngx_cached_http_log_iso8601.len);
+
+    v->len = ngx_cached_http_log_iso8601.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = p;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_variable_time_local(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    u_char  *p;
+
+    p = ngx_pnalloc(s->connection->pool, ngx_cached_http_log_time.len);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(p, ngx_cached_http_log_time.data, ngx_cached_http_log_time.len);
+
+    v->len = ngx_cached_http_log_time.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = p;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_variable_protocol(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    v->len = 3;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = (u_char *) (s->connection->type == SOCK_DGRAM ? "UDP" : "TCP");
+
+    return NGX_OK;
+}
+
+
+void *
+ngx_stream_map_find(ngx_stream_session_t *s, ngx_stream_map_t *map,
+    ngx_str_t *match)
+{
+    void        *value;
+    u_char      *low;
+    size_t       len;
+    ngx_uint_t   key;
+
+    len = match->len;
+
+    if (len) {
+        low = ngx_pnalloc(s->connection->pool, len);
+        if (low == NULL) {
+            return NULL;
+        }
+
+    } else {
+        low = NULL;
+    }
+
+    key = ngx_hash_strlow(low, match->data, len);
+
+    value = ngx_hash_find_combined(&map->hash, key, low, len);
+    if (value) {
+        return value;
+    }
+
+#if (NGX_PCRE)
+
+    if (len && map->nregex) {
+        ngx_int_t                n;
+        ngx_uint_t               i;
+        ngx_stream_map_regex_t  *reg;
+
+        reg = map->regex;
+
+        for (i = 0; i < map->nregex; i++) {
+
+            n = ngx_stream_regex_exec(s, reg[i].regex, match);
+
+            if (n == NGX_OK) {
+                return reg[i].value;
+            }
+
+            if (n == NGX_DECLINED) {
+                continue;
+            }
+
+            /* NGX_ERROR */
+
+            return NULL;
+        }
+    }
+
+#endif
+
+    return NULL;
+}
+
+
+#if (NGX_PCRE)
+
+static ngx_int_t
+ngx_stream_variable_not_found(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    v->not_found = 1;
+    return NGX_OK;
+}
+
+
+ngx_stream_regex_t *
+ngx_stream_regex_compile(ngx_conf_t *cf, ngx_regex_compile_t *rc)
+{
+    u_char                       *p;
+    size_t                        size;
+    ngx_str_t                     name;
+    ngx_uint_t                    i, n;
+    ngx_stream_variable_t        *v;
+    ngx_stream_regex_t           *re;
+    ngx_stream_regex_variable_t  *rv;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    rc->pool = cf->pool;
+
+    if (ngx_regex_compile(rc) != NGX_OK) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc->err);
+        return NULL;
+    }
+
+    re = ngx_pcalloc(cf->pool, sizeof(ngx_stream_regex_t));
+    if (re == NULL) {
+        return NULL;
+    }
+
+    re->regex = rc->regex;
+    re->ncaptures = rc->captures;
+    re->name = rc->pattern;
+
+    cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module);
+    cmcf->ncaptures = ngx_max(cmcf->ncaptures, re->ncaptures);
+
+    n = (ngx_uint_t) rc->named_captures;
+
+    if (n == 0) {
+        return re;
+    }
+
+    rv = ngx_palloc(rc->pool, n * sizeof(ngx_stream_regex_variable_t));
+    if (rv == NULL) {
+        return NULL;
+    }
+
+    re->variables = rv;
+    re->nvariables = n;
+
+    size = rc->name_size;
+    p = rc->names;
+
+    for (i = 0; i < n; i++) {
+        rv[i].capture = 2 * ((p[0] << 8) + p[1]);
+
+        name.data = &p[2];
+        name.len = ngx_strlen(name.data);
+
+        v = ngx_stream_add_variable(cf, &name, NGX_STREAM_VAR_CHANGEABLE);
+        if (v == NULL) {
+            return NULL;
+        }
+
+        rv[i].index = ngx_stream_get_variable_index(cf, &name);
+        if (rv[i].index == NGX_ERROR) {
+            return NULL;
+        }
+
+        v->get_handler = ngx_stream_variable_not_found;
+
+        p += size;
+    }
+
+    return re;
+}
+
+
+ngx_int_t
+ngx_stream_regex_exec(ngx_stream_session_t *s, ngx_stream_regex_t *re,
+    ngx_str_t *str)
+{
+    ngx_int_t                     rc, index;
+    ngx_uint_t                    i, n, len;
+    ngx_stream_variable_value_t  *vv;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module);
+
+    if (re->ncaptures) {
+        len = cmcf->ncaptures;
+
+        if (s->captures == NULL) {
+            s->captures = ngx_palloc(s->connection->pool, len * sizeof(int));
+            if (s->captures == NULL) {
+                return NGX_ERROR;
+            }
+        }
+
+    } else {
+        len = 0;
+    }
+
+    rc = ngx_regex_exec(re->regex, str, s->captures, len);
+
+    if (rc == NGX_REGEX_NO_MATCHED) {
+        return NGX_DECLINED;
+    }
+
+    if (rc < 0) {
+        ngx_log_error(NGX_LOG_ALERT, s->connection->log, 0,
+                      ngx_regex_exec_n " failed: %i on \"%V\" using \"%V\"",
+                      rc, str, &re->name);
+        return NGX_ERROR;
+    }
+
+    for (i = 0; i < re->nvariables; i++) {
+
+        n = re->variables[i].capture;
+        index = re->variables[i].index;
+        vv = &s->variables[index];
+
+        vv->len = s->captures[n + 1] - s->captures[n];
+        vv->valid = 1;
+        vv->no_cacheable = 0;
+        vv->not_found = 0;
+        vv->data = &str->data[s->captures[n]];
+
+#if (NGX_DEBUG)
+        {
+        ngx_stream_variable_t  *v;
+
+        v = cmcf->variables.elts;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                       "stream regex set $%V to \"%v\"", &v[index].name, vv);
+        }
+#endif
+    }
+
+    s->ncaptures = rc * 2;
+    s->captures_data = str->data;
+
+    return NGX_OK;
+}
+
+#endif
+
+
+ngx_int_t
+ngx_stream_variables_add_core_vars(ngx_conf_t *cf)
+{
+    ngx_stream_variable_t        *cv, *v;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module);
+
+    cmcf->variables_keys = ngx_pcalloc(cf->temp_pool,
+                                       sizeof(ngx_hash_keys_arrays_t));
+    if (cmcf->variables_keys == NULL) {
+        return NGX_ERROR;
+    }
+
+    cmcf->variables_keys->pool = cf->pool;
+    cmcf->variables_keys->temp_pool = cf->pool;
+
+    if (ngx_hash_keys_array_init(cmcf->variables_keys, NGX_HASH_SMALL)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_array_init(&cmcf->prefix_variables, cf->pool, 8,
+                       sizeof(ngx_stream_variable_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    for (cv = ngx_stream_core_variables; cv->name.len; cv++) {
+        v = ngx_stream_add_variable(cf, &cv->name, cv->flags);
+        if (v == NULL) {
+            return NGX_ERROR;
+        }
+
+        *v = *cv;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_stream_variables_init_vars(ngx_conf_t *cf)
+{
+    size_t                        len;
+    ngx_uint_t                    i, n;
+    ngx_hash_key_t               *key;
+    ngx_hash_init_t               hash;
+    ngx_stream_variable_t        *v, *av, *pv;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    /* set the handlers for the indexed stream variables */
+
+    cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module);
+
+    v = cmcf->variables.elts;
+    pv = cmcf->prefix_variables.elts;
+    key = cmcf->variables_keys->keys.elts;
+
+    for (i = 0; i < cmcf->variables.nelts; i++) {
+
+        for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) {
+
+            av = key[n].value;
+
+            if (v[i].name.len == key[n].key.len
+                && ngx_strncmp(v[i].name.data, key[n].key.data, v[i].name.len)
+                   == 0)
+            {
+                v[i].get_handler = av->get_handler;
+                v[i].data = av->data;
+
+                av->flags |= NGX_STREAM_VAR_INDEXED;
+                v[i].flags = av->flags;
+
+                av->index = i;
+
+                if (av->get_handler == NULL
+                    || (av->flags & NGX_STREAM_VAR_WEAK))
+                {
+                    break;
+                }
+
+                goto next;
+            }
+        }
+
+        len = 0;
+        av = NULL;
+
+        for (n = 0; n < cmcf->prefix_variables.nelts; n++) {
+            if (v[i].name.len >= pv[n].name.len && v[i].name.len > len
+                && ngx_strncmp(v[i].name.data, pv[n].name.data, pv[n].name.len)
+                   == 0)
+            {
+                av = &pv[n];
+                len = pv[n].name.len;
+            }
+        }
+
+        if (av) {
+            v[i].get_handler = av->get_handler;
+            v[i].data = (uintptr_t) &v[i].name;
+            v[i].flags = av->flags;
+
+            goto next;
+         }
+
+        if (v[i].get_handler == NULL) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "unknown \"%V\" variable", &v[i].name);
+            return NGX_ERROR;
+        }
+
+    next:
+        continue;
+    }
+
+
+    for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) {
+        av = key[n].value;
+
+        if (av->flags & NGX_STREAM_VAR_NOHASH) {
+            key[n].key.data = NULL;
+        }
+    }
+
+
+    hash.hash = &cmcf->variables_hash;
+    hash.key = ngx_hash_key;
+    hash.max_size = cmcf->variables_hash_max_size;
+    hash.bucket_size = cmcf->variables_hash_bucket_size;
+    hash.name = "variables_hash";
+    hash.pool = cf->pool;
+    hash.temp_pool = NULL;
+
+    if (ngx_hash_init(&hash, cmcf->variables_keys->keys.elts,
+                      cmcf->variables_keys->keys.nelts)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    cmcf->variables_keys = NULL;
+
+    return NGX_OK;
+}
diff --git a/nginx/src/stream/ngx_stream_variables.h b/nginx/src/stream/ngx_stream_variables.h
new file mode 100644 (file)
index 0000000..4ead2a6
--- /dev/null
@@ -0,0 +1,113 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_STREAM_VARIABLES_H_INCLUDED_
+#define _NGX_STREAM_VARIABLES_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+typedef ngx_variable_value_t  ngx_stream_variable_value_t;
+
+#define ngx_stream_variable(v)     { sizeof(v) - 1, 1, 0, 0, 0, (u_char *) v }
+
+typedef struct ngx_stream_variable_s  ngx_stream_variable_t;
+
+typedef void (*ngx_stream_set_variable_pt) (ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+typedef ngx_int_t (*ngx_stream_get_variable_pt) (ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+
+
+#define NGX_STREAM_VAR_CHANGEABLE   1
+#define NGX_STREAM_VAR_NOCACHEABLE  2
+#define NGX_STREAM_VAR_INDEXED      4
+#define NGX_STREAM_VAR_NOHASH       8
+#define NGX_STREAM_VAR_WEAK         16
+#define NGX_STREAM_VAR_PREFIX       32
+
+
+struct ngx_stream_variable_s {
+    ngx_str_t                     name;   /* must be first to build the hash */
+    ngx_stream_set_variable_pt    set_handler;
+    ngx_stream_get_variable_pt    get_handler;
+    uintptr_t                     data;
+    ngx_uint_t                    flags;
+    ngx_uint_t                    index;
+};
+
+#define ngx_stream_null_variable  { ngx_null_string, NULL, NULL, 0, 0, 0 }
+
+
+ngx_stream_variable_t *ngx_stream_add_variable(ngx_conf_t *cf, ngx_str_t *name,
+    ngx_uint_t flags);
+ngx_int_t ngx_stream_get_variable_index(ngx_conf_t *cf, ngx_str_t *name);
+ngx_stream_variable_value_t *ngx_stream_get_indexed_variable(
+    ngx_stream_session_t *s, ngx_uint_t index);
+ngx_stream_variable_value_t *ngx_stream_get_flushed_variable(
+    ngx_stream_session_t *s, ngx_uint_t index);
+
+ngx_stream_variable_value_t *ngx_stream_get_variable(ngx_stream_session_t *s,
+    ngx_str_t *name, ngx_uint_t key);
+
+
+#if (NGX_PCRE)
+
+typedef struct {
+    ngx_uint_t                    capture;
+    ngx_int_t                     index;
+} ngx_stream_regex_variable_t;
+
+
+typedef struct {
+    ngx_regex_t                  *regex;
+    ngx_uint_t                    ncaptures;
+    ngx_stream_regex_variable_t  *variables;
+    ngx_uint_t                    nvariables;
+    ngx_str_t                     name;
+} ngx_stream_regex_t;
+
+
+typedef struct {
+    ngx_stream_regex_t           *regex;
+    void                         *value;
+} ngx_stream_map_regex_t;
+
+
+ngx_stream_regex_t *ngx_stream_regex_compile(ngx_conf_t *cf,
+    ngx_regex_compile_t *rc);
+ngx_int_t ngx_stream_regex_exec(ngx_stream_session_t *s, ngx_stream_regex_t *re,
+    ngx_str_t *str);
+
+#endif
+
+
+typedef struct {
+    ngx_hash_combined_t           hash;
+#if (NGX_PCRE)
+    ngx_stream_map_regex_t       *regex;
+    ngx_uint_t                    nregex;
+#endif
+} ngx_stream_map_t;
+
+
+void *ngx_stream_map_find(ngx_stream_session_t *s, ngx_stream_map_t *map,
+    ngx_str_t *match);
+
+
+ngx_int_t ngx_stream_variables_add_core_vars(ngx_conf_t *cf);
+ngx_int_t ngx_stream_variables_init_vars(ngx_conf_t *cf);
+
+
+extern ngx_stream_variable_value_t  ngx_stream_variable_null_value;
+extern ngx_stream_variable_value_t  ngx_stream_variable_true_value;
+
+
+#endif /* _NGX_STREAM_VARIABLES_H_INCLUDED_ */
diff --git a/nginx/src/stream/ngx_stream_write_filter_module.c b/nginx/src/stream/ngx_stream_write_filter_module.c
new file mode 100644 (file)
index 0000000..8fdcd37
--- /dev/null
@@ -0,0 +1,273 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+typedef struct {
+    ngx_chain_t  *from_upstream;
+    ngx_chain_t  *from_downstream;
+} ngx_stream_write_filter_ctx_t;
+
+
+static ngx_int_t ngx_stream_write_filter(ngx_stream_session_t *s,
+    ngx_chain_t *in, ngx_uint_t from_upstream);
+static ngx_int_t ngx_stream_write_filter_init(ngx_conf_t *cf);
+
+
+static ngx_stream_module_t  ngx_stream_write_filter_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_stream_write_filter_init,          /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL                                   /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_stream_write_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_stream_write_filter_module_ctx,   /* module context */
+    NULL,                                  /* 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_write_filter(ngx_stream_session_t *s, ngx_chain_t *in,
+    ngx_uint_t from_upstream)
+{
+    off_t                           size;
+    ngx_uint_t                      last, flush, sync;
+    ngx_chain_t                    *cl, *ln, **ll, **out, *chain;
+    ngx_connection_t               *c;
+    ngx_stream_write_filter_ctx_t  *ctx;
+
+    ctx = ngx_stream_get_module_ctx(s, ngx_stream_write_filter_module);
+
+    if (ctx == NULL) {
+        ctx = ngx_pcalloc(s->connection->pool,
+                          sizeof(ngx_stream_write_filter_ctx_t));
+        if (ctx == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_stream_set_ctx(s, ctx, ngx_stream_write_filter_module);
+    }
+
+    if (from_upstream) {
+        c = s->connection;
+        out = &ctx->from_upstream;
+
+    } else {
+        c = s->upstream->peer.connection;
+        out = &ctx->from_downstream;
+    }
+
+    if (c->error) {
+        return NGX_ERROR;
+    }
+
+    size = 0;
+    flush = 0;
+    sync = 0;
+    last = 0;
+    ll = out;
+
+    /* find the size, the flush point and the last link of the saved chain */
+
+    for (cl = *out; cl; cl = cl->next) {
+        ll = &cl->next;
+
+        ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "write old buf t:%d f:%d %p, pos %p, size: %z "
+                       "file: %O, size: %O",
+                       cl->buf->temporary, cl->buf->in_file,
+                       cl->buf->start, cl->buf->pos,
+                       cl->buf->last - cl->buf->pos,
+                       cl->buf->file_pos,
+                       cl->buf->file_last - cl->buf->file_pos);
+
+#if 1
+        if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) {
+            ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                          "zero size buf in writer "
+                          "t:%d r:%d f:%d %p %p-%p %p %O-%O",
+                          cl->buf->temporary,
+                          cl->buf->recycled,
+                          cl->buf->in_file,
+                          cl->buf->start,
+                          cl->buf->pos,
+                          cl->buf->last,
+                          cl->buf->file,
+                          cl->buf->file_pos,
+                          cl->buf->file_last);
+
+            ngx_debug_point();
+            return NGX_ERROR;
+        }
+#endif
+
+        size += ngx_buf_size(cl->buf);
+
+        if (cl->buf->flush || cl->buf->recycled) {
+            flush = 1;
+        }
+
+        if (cl->buf->sync) {
+            sync = 1;
+        }
+
+        if (cl->buf->last_buf) {
+            last = 1;
+        }
+    }
+
+    /* add the new chain to the existent one */
+
+    for (ln = in; ln; ln = ln->next) {
+        cl = ngx_alloc_chain_link(c->pool);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+        cl->buf = ln->buf;
+        *ll = cl;
+        ll = &cl->next;
+
+        ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "write new buf t:%d f:%d %p, pos %p, size: %z "
+                       "file: %O, size: %O",
+                       cl->buf->temporary, cl->buf->in_file,
+                       cl->buf->start, cl->buf->pos,
+                       cl->buf->last - cl->buf->pos,
+                       cl->buf->file_pos,
+                       cl->buf->file_last - cl->buf->file_pos);
+
+#if 1
+        if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) {
+            ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                          "zero size buf in writer "
+                          "t:%d r:%d f:%d %p %p-%p %p %O-%O",
+                          cl->buf->temporary,
+                          cl->buf->recycled,
+                          cl->buf->in_file,
+                          cl->buf->start,
+                          cl->buf->pos,
+                          cl->buf->last,
+                          cl->buf->file,
+                          cl->buf->file_pos,
+                          cl->buf->file_last);
+
+            ngx_debug_point();
+            return NGX_ERROR;
+        }
+#endif
+
+        size += ngx_buf_size(cl->buf);
+
+        if (cl->buf->flush || cl->buf->recycled) {
+            flush = 1;
+        }
+
+        if (cl->buf->sync) {
+            sync = 1;
+        }
+
+        if (cl->buf->last_buf) {
+            last = 1;
+        }
+    }
+
+    *ll = NULL;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_STREAM, c->log, 0,
+                   "stream write filter: l:%ui f:%ui s:%O", last, flush, size);
+
+    if (size == 0
+        && !(c->buffered & NGX_LOWLEVEL_BUFFERED)
+        && !(last && c->need_last_buf))
+    {
+        if (last || flush || sync) {
+            for (cl = *out; cl; /* void */) {
+                ln = cl;
+                cl = cl->next;
+                ngx_free_chain(c->pool, ln);
+            }
+
+            *out = NULL;
+            c->buffered &= ~NGX_STREAM_WRITE_BUFFERED;
+
+            return NGX_OK;
+        }
+
+        ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                      "the stream output chain is empty");
+
+        ngx_debug_point();
+
+        return NGX_ERROR;
+    }
+
+    chain = c->send_chain(c, *out, 0);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0,
+                   "stream write filter %p", chain);
+
+    if (chain == NGX_CHAIN_ERROR) {
+        c->error = 1;
+        return NGX_ERROR;
+    }
+
+    for (cl = *out; cl && cl != chain; /* void */) {
+        ln = cl;
+        cl = cl->next;
+        ngx_free_chain(c->pool, ln);
+    }
+
+    *out = chain;
+
+    if (chain) {
+        if (c->shared) {
+            ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                          "shared connection is busy");
+            return NGX_ERROR;
+        }
+
+        c->buffered |= NGX_STREAM_WRITE_BUFFERED;
+        return NGX_AGAIN;
+    }
+
+    c->buffered &= ~NGX_STREAM_WRITE_BUFFERED;
+
+    if (c->buffered & NGX_LOWLEVEL_BUFFERED) {
+        return NGX_AGAIN;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_write_filter_init(ngx_conf_t *cf)
+{
+    ngx_stream_top_filter = ngx_stream_write_filter;
+
+    return NGX_OK;
+}
diff --git a/nginx_patches/0001-ngxvcl.patch b/nginx_patches/0001-ngxvcl.patch
new file mode 100644 (file)
index 0000000..78df515
--- /dev/null
@@ -0,0 +1,1164 @@
+From 6c00e577a820637bc4e7b1ddc9f44b312e947a21 Mon Sep 17 00:00:00 2001
+From: Zeyu Zhang <[email protected]>
+Date: Tue, 22 Oct 2019 15:50:59 +0800
+Subject: [PATCH] nginx: modified nginx to support vcl
+
+---
+ nginx/auto/make                               | 32 ++++++++-
+ nginx/auto/options                            |  8 +++
+ nginx/configure                               |  5 ++
+ nginx/src/core/nginx.c                        |  6 ++
+ nginx/src/core/ngx_connection.c               | 66 +++++++++----------
+ nginx/src/core/ngx_core.h                     |  1 +
+ nginx/src/core/ngx_resolver.c                 |  4 +-
+ nginx/src/core/ngx_syslog.c                   |  2 +-
+ nginx/src/event/modules/ngx_epoll_module.c    | 22 +++----
+ nginx/src/event/modules/ngx_select_module.c   |  4 +-
+ .../event/modules/ngx_win32_select_module.c   |  4 +-
+ nginx/src/event/ngx_event.c                   |  2 +-
+ nginx/src/event/ngx_event_accept.c            |  8 +--
+ nginx/src/event/ngx_event_connect.c           | 20 +++---
+ .../ngx_http_upstream_keepalive_module.c      |  2 +-
+ nginx/src/http/ngx_http_request.c             |  8 +--
+ nginx/src/http/ngx_http_upstream.c            |  6 +-
+ nginx/src/http/ngx_http_variables.c           |  2 +-
+ nginx/src/os/unix/ngx_linux_sendfile_chain.c  |  6 +-
+ nginx/src/os/unix/ngx_process_cycle.c         |  3 +
+ nginx/src/os/unix/ngx_readv_chain.c           |  2 +-
+ nginx/src/os/unix/ngx_recv.c                  |  2 +-
+ nginx/src/os/unix/ngx_send.c                  |  2 +-
+ nginx/src/os/unix/ngx_socket.c                | 12 ++--
+ nginx/src/os/unix/ngx_socket.h                | 10 +--
+ nginx/src/os/unix/ngx_udp_recv.c              |  2 +-
+ nginx/src/os/unix/ngx_udp_send.c              |  2 +-
+ nginx/src/os/unix/ngx_udp_sendmsg_chain.c     |  2 +-
+ nginx/src/os/unix/ngx_writev_chain.c          |  2 +-
+ nginx/src/stream/ngx_stream_handler.c         |  2 +-
+ nginx/src/stream/ngx_stream_proxy_module.c    |  2 +-
+ 31 files changed, 151 insertions(+), 100 deletions(-)
+
+diff --git a/nginx/auto/make b/nginx/auto/make
+index 7ddd100..0e21251 100644
+--- a/nginx/auto/make
++++ b/nginx/auto/make
+@@ -61,6 +61,27 @@ ngx_incs=`echo $CORE_INCS $NGX_OBJS \
+     | sed -e "s/  *\([^ ][^ ]*\)/$ngx_regex_cont$ngx_include_opt\1/g" \
+           -e "s/\//$ngx_regex_dirsep/g"`
++
++if [ $NGX_USE_VCL = YES ]; then
++
++cat << END                                                    >> $NGX_MAKEFILE
++
++VPPCOM_LIB_PATH = $VPP_LIB_PATH
++
++
++NGXVCL_INCS = -I $VPP_SRC_PATH
++
++
++NGXVCL_LIBS = -L \$(VPPCOM_LIB_PATH) -l ngxvcl -l vppcom -l vlibmemoryclient -l svm -l vppinfra
++
++END
++
++ngx_incs=$ngx_incs" \\
++      \$(NGXVCL_INCS)"
++
++fi
++
++
+ cat << END                                                    >> $NGX_MAKEFILE
+ CORE_DEPS = $ngx_deps
+@@ -222,14 +243,21 @@ ngx_main_link=${MAIN_LINK:+`echo $MAIN_LINK \
+     | sed -e "s/\//$ngx_regex_dirsep/g" -e "s/^/$ngx_long_regex_cont/"`}
++ngxvcl_libs=
++if [ $NGX_USE_VCL = YES ]; then
++ngxvcl_libs="\\
++      \$(NGXVCL_LIBS)"
++fi
++
++
+ cat << END                                                    >> $NGX_MAKEFILE
+ build:        binary modules manpage
+ binary:       $NGX_OBJS${ngx_dirsep}nginx$ngx_binext
+-$NGX_OBJS${ngx_dirsep}nginx$ngx_binext:       $ngx_deps$ngx_spacer
+-      \$(LINK) $ngx_long_start$ngx_binout$NGX_OBJS${ngx_dirsep}nginx$ngx_long_cont$ngx_objs$ngx_libs$ngx_link$ngx_main_link
++$NGX_OBJS${ngx_dirsep}nginx$ngx_binext:       $ngx_deps $ngx_spacer
++      \$(LINK) $ngx_long_start$ngx_binout$NGX_OBJS${ngx_dirsep}nginx$ngx_long_cont$ngx_objs$ngx_libs$ngx_link$ngx_main_link $ngxvcl_libs
+       $ngx_rcc
+ $ngx_long_end
+diff --git a/nginx/auto/options b/nginx/auto/options
+index 59f0449..a229efa 100644
+--- a/nginx/auto/options
++++ b/nginx/auto/options
+@@ -146,6 +146,10 @@ PCRE_JIT=NO
+ USE_OPENSSL=NO
+ OPENSSL=NONE
++NGX_USE_VCL=NO
++VPP_LIB_PATH=
++VPP_SRC_PATH=
++
+ USE_ZLIB=NO
+ ZLIB=NONE
+ ZLIB_OPT=
+@@ -352,6 +356,10 @@ use the \"--with-mail_ssl_module\" option instead"
+         --with-openssl=*)                OPENSSL="$value"           ;;
+         --with-openssl-opt=*)            OPENSSL_OPT="$value"       ;;
++        --with-vcl)                      NGX_USE_VCL=YES            ;;
++        --vpp-lib-path=*)                VPP_LIB_PATH="$value"      ;;
++        --vpp-src-path=*)                VPP_SRC_PATH="$value"      ;;
++
+         --with-md5=*)
+             NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG
+ $0: warning: the \"--with-md5\" option is deprecated"
+diff --git a/nginx/configure b/nginx/configure
+index 7e6e33a..fe8d36f 100755
+--- a/nginx/configure
++++ b/nginx/configure
+@@ -24,6 +24,11 @@ if [ $NGX_DEBUG = YES ]; then
+ fi
++if [ $NGX_USE_VCL = YES ]; then
++    have=NGX_USE_VCL . auto/have
++fi
++
++
+ if test -z "$NGX_PLATFORM"; then
+     echo "checking for OS"
+diff --git a/nginx/src/core/nginx.c b/nginx/src/core/nginx.c
+index 9fcb0eb..db1d61b 100644
+--- a/nginx/src/core/nginx.c
++++ b/nginx/src/core/nginx.c
+@@ -201,6 +201,8 @@ main(int argc, char *const *argv)
+     ngx_conf_dump_t  *cd;
+     ngx_core_conf_t  *ccf;
++    ngxvcl_app_create("Nginx with VCL");
++
+     ngx_debug_init();
+     if (ngx_strerror_init() != NGX_OK) {
+@@ -376,12 +378,16 @@ main(int argc, char *const *argv)
+     ngx_use_stderr = 0;
+     if (ngx_process == NGX_PROCESS_SINGLE) {
++        ngxvcl_wait_vep_only();
+         ngx_single_process_cycle(cycle);
+     } else {
++        ngxvcl_wait_kep_and_vep();
+         ngx_master_process_cycle(cycle);
+     }
++    ngxvcl_app_destroy();
++
+     return 0;
+ }
+diff --git a/nginx/src/core/ngx_connection.c b/nginx/src/core/ngx_connection.c
+index 9a74758..a61a7a3 100644
+--- a/nginx/src/core/ngx_connection.c
++++ b/nginx/src/core/ngx_connection.c
+@@ -157,7 +157,7 @@ ngx_set_inherited_sockets(ngx_cycle_t *cycle)
+         }
+         ls[i].socklen = sizeof(ngx_sockaddr_t);
+-        if (getsockname(ls[i].fd, ls[i].sockaddr, &ls[i].socklen) == -1) {
++        if (ngxvcl_kvfd_getsockname(ls[i].fd, ls[i].sockaddr, &ls[i].socklen) == -1) {
+             ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_socket_errno,
+                           "getsockname() of the inherited "
+                           "socket #%d failed", ls[i].fd);
+@@ -215,7 +215,7 @@ ngx_set_inherited_sockets(ngx_cycle_t *cycle)
+         olen = sizeof(int);
+-        if (getsockopt(ls[i].fd, SOL_SOCKET, SO_TYPE, (void *) &ls[i].type,
++        if (ngxvcl_kvfd_getsockopt(ls[i].fd, SOL_SOCKET, SO_TYPE, (void *) &ls[i].type,
+                        &olen)
+             == -1)
+         {
+@@ -227,7 +227,7 @@ ngx_set_inherited_sockets(ngx_cycle_t *cycle)
+         olen = sizeof(int);
+-        if (getsockopt(ls[i].fd, SOL_SOCKET, SO_RCVBUF, (void *) &ls[i].rcvbuf,
++        if (ngxvcl_kvfd_getsockopt(ls[i].fd, SOL_SOCKET, SO_RCVBUF, (void *) &ls[i].rcvbuf,
+                        &olen)
+             == -1)
+         {
+@@ -240,7 +240,7 @@ ngx_set_inherited_sockets(ngx_cycle_t *cycle)
+         olen = sizeof(int);
+-        if (getsockopt(ls[i].fd, SOL_SOCKET, SO_SNDBUF, (void *) &ls[i].sndbuf,
++        if (ngxvcl_kvfd_getsockopt(ls[i].fd, SOL_SOCKET, SO_SNDBUF, (void *) &ls[i].sndbuf,
+                        &olen)
+             == -1)
+         {
+@@ -258,7 +258,7 @@ ngx_set_inherited_sockets(ngx_cycle_t *cycle)
+         olen = sizeof(int);
+-        if (getsockopt(ls[i].fd, SOL_SOCKET, SO_SETFIB,
++        if (ngxvcl_kvfd_getsockopt(ls[i].fd, SOL_SOCKET, SO_SETFIB,
+                        (void *) &ls[i].setfib, &olen)
+             == -1)
+         {
+@@ -277,7 +277,7 @@ ngx_set_inherited_sockets(ngx_cycle_t *cycle)
+         reuseport = 0;
+         olen = sizeof(int);
+-        if (getsockopt(ls[i].fd, SOL_SOCKET, SO_REUSEPORT,
++        if (ngxvcl_kvfd_getsockopt(ls[i].fd, SOL_SOCKET, SO_REUSEPORT,
+                        (void *) &reuseport, &olen)
+             == -1)
+         {
+@@ -299,7 +299,7 @@ ngx_set_inherited_sockets(ngx_cycle_t *cycle)
+         olen = sizeof(int);
+-        if (getsockopt(ls[i].fd, IPPROTO_TCP, TCP_FASTOPEN,
++        if (ngxvcl_kvfd_getsockopt(ls[i].fd, IPPROTO_TCP, TCP_FASTOPEN,
+                        (void *) &ls[i].fastopen, &olen)
+             == -1)
+         {
+@@ -321,7 +321,7 @@ ngx_set_inherited_sockets(ngx_cycle_t *cycle)
+         ngx_memzero(&af, sizeof(struct accept_filter_arg));
+         olen = sizeof(struct accept_filter_arg);
+-        if (getsockopt(ls[i].fd, SOL_SOCKET, SO_ACCEPTFILTER, &af, &olen)
++        if (ngxvcl_kvfd_getsockopt(ls[i].fd, SOL_SOCKET, SO_ACCEPTFILTER, &af, &olen)
+             == -1)
+         {
+             err = ngx_socket_errno;
+@@ -354,7 +354,7 @@ ngx_set_inherited_sockets(ngx_cycle_t *cycle)
+         timeout = 0;
+         olen = sizeof(int);
+-        if (getsockopt(ls[i].fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &timeout, &olen)
++        if (ngxvcl_kvfd_getsockopt(ls[i].fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &timeout, &olen)
+             == -1)
+         {
+             err = ngx_socket_errno;
+@@ -424,7 +424,7 @@ ngx_open_listening_sockets(ngx_cycle_t *cycle)
+                 int  reuseport = 1;
+-                if (setsockopt(ls[i].fd, SOL_SOCKET, SO_REUSEPORT,
++                if (ngxvcl_kvfd_setsockopt(ls[i].fd, SOL_SOCKET, SO_REUSEPORT,
+                                (const void *) &reuseport, sizeof(int))
+                     == -1)
+                 {
+@@ -458,7 +458,7 @@ ngx_open_listening_sockets(ngx_cycle_t *cycle)
+                 return NGX_ERROR;
+             }
+-            if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
++            if (ngxvcl_kvfd_setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
+                            (const void *) &reuseaddr, sizeof(int))
+                 == -1)
+             {
+@@ -482,7 +482,7 @@ ngx_open_listening_sockets(ngx_cycle_t *cycle)
+                 reuseport = 1;
+-                if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT,
++                if (ngxvcl_kvfd_setsockopt(s, SOL_SOCKET, SO_REUSEPORT,
+                                (const void *) &reuseport, sizeof(int))
+                     == -1)
+                 {
+@@ -508,7 +508,7 @@ ngx_open_listening_sockets(ngx_cycle_t *cycle)
+                 ipv6only = ls[i].ipv6only;
+-                if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
++                if (ngxvcl_kvfd_setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
+                                (const void *) &ipv6only, sizeof(int))
+                     == -1)
+                 {
+@@ -539,7 +539,7 @@ ngx_open_listening_sockets(ngx_cycle_t *cycle)
+             ngx_log_debug2(NGX_LOG_DEBUG_CORE, log, 0,
+                            "bind() %V #%d ", &ls[i].addr_text, s);
+-            if (bind(s, ls[i].sockaddr, ls[i].socklen) == -1) {
++            if (ngxvcl_bind(s, ls[i].sockaddr, ls[i].socklen) == -1) {
+                 err = ngx_socket_errno;
+                 if (err != NGX_EADDRINUSE || !ngx_test_config) {
+@@ -592,7 +592,7 @@ ngx_open_listening_sockets(ngx_cycle_t *cycle)
+                 continue;
+             }
+-            if (listen(s, ls[i].backlog) == -1) {
++            if (ngxvcl_listen(s, ls[i].backlog) == -1) {
+                 err = ngx_socket_errno;
+                 /*
+@@ -667,7 +667,7 @@ ngx_configure_listening_sockets(ngx_cycle_t *cycle)
+         ls[i].log = *ls[i].logp;
+         if (ls[i].rcvbuf != -1) {
+-            if (setsockopt(ls[i].fd, SOL_SOCKET, SO_RCVBUF,
++            if (ngxvcl_kvfd_setsockopt(ls[i].fd, SOL_SOCKET, SO_RCVBUF,
+                            (const void *) &ls[i].rcvbuf, sizeof(int))
+                 == -1)
+             {
+@@ -678,7 +678,7 @@ ngx_configure_listening_sockets(ngx_cycle_t *cycle)
+         }
+         if (ls[i].sndbuf != -1) {
+-            if (setsockopt(ls[i].fd, SOL_SOCKET, SO_SNDBUF,
++            if (ngxvcl_kvfd_setsockopt(ls[i].fd, SOL_SOCKET, SO_SNDBUF,
+                            (const void *) &ls[i].sndbuf, sizeof(int))
+                 == -1)
+             {
+@@ -691,7 +691,7 @@ ngx_configure_listening_sockets(ngx_cycle_t *cycle)
+         if (ls[i].keepalive) {
+             value = (ls[i].keepalive == 1) ? 1 : 0;
+-            if (setsockopt(ls[i].fd, SOL_SOCKET, SO_KEEPALIVE,
++            if (ngxvcl_kvfd_setsockopt(ls[i].fd, SOL_SOCKET, SO_KEEPALIVE,
+                            (const void *) &value, sizeof(int))
+                 == -1)
+             {
+@@ -710,7 +710,7 @@ ngx_configure_listening_sockets(ngx_cycle_t *cycle)
+             value *= NGX_KEEPALIVE_FACTOR;
+ #endif
+-            if (setsockopt(ls[i].fd, IPPROTO_TCP, TCP_KEEPIDLE,
++            if (ngxvcl_kvfd_setsockopt(ls[i].fd, IPPROTO_TCP, TCP_KEEPIDLE,
+                            (const void *) &value, sizeof(int))
+                 == -1)
+             {
+@@ -727,7 +727,7 @@ ngx_configure_listening_sockets(ngx_cycle_t *cycle)
+             value *= NGX_KEEPALIVE_FACTOR;
+ #endif
+-            if (setsockopt(ls[i].fd, IPPROTO_TCP, TCP_KEEPINTVL,
++            if (ngxvcl_kvfd_setsockopt(ls[i].fd, IPPROTO_TCP, TCP_KEEPINTVL,
+                            (const void *) &value, sizeof(int))
+                 == -1)
+             {
+@@ -738,7 +738,7 @@ ngx_configure_listening_sockets(ngx_cycle_t *cycle)
+         }
+         if (ls[i].keepcnt) {
+-            if (setsockopt(ls[i].fd, IPPROTO_TCP, TCP_KEEPCNT,
++            if (ngxvcl_kvfd_setsockopt(ls[i].fd, IPPROTO_TCP, TCP_KEEPCNT,
+                            (const void *) &ls[i].keepcnt, sizeof(int))
+                 == -1)
+             {
+@@ -752,7 +752,7 @@ ngx_configure_listening_sockets(ngx_cycle_t *cycle)
+ #if (NGX_HAVE_SETFIB)
+         if (ls[i].setfib != -1) {
+-            if (setsockopt(ls[i].fd, SOL_SOCKET, SO_SETFIB,
++            if (ngxvcl_kvfd_setsockopt(ls[i].fd, SOL_SOCKET, SO_SETFIB,
+                            (const void *) &ls[i].setfib, sizeof(int))
+                 == -1)
+             {
+@@ -765,7 +765,7 @@ ngx_configure_listening_sockets(ngx_cycle_t *cycle)
+ #if (NGX_HAVE_TCP_FASTOPEN)
+         if (ls[i].fastopen != -1) {
+-            if (setsockopt(ls[i].fd, IPPROTO_TCP, TCP_FASTOPEN,
++            if (ngxvcl_kvfd_setsockopt(ls[i].fd, IPPROTO_TCP, TCP_FASTOPEN,
+                            (const void *) &ls[i].fastopen, sizeof(int))
+                 == -1)
+             {
+@@ -780,7 +780,7 @@ ngx_configure_listening_sockets(ngx_cycle_t *cycle)
+         if (1) {
+             int tcp_nodelay = 1;
+-            if (setsockopt(ls[i].fd, IPPROTO_TCP, TCP_NODELAY,
++            if (ngxvcl_kvfd_setsockopt(ls[i].fd, IPPROTO_TCP, TCP_NODELAY,
+                        (const void *) &tcp_nodelay, sizeof(int))
+                 == -1)
+             {
+@@ -795,7 +795,7 @@ ngx_configure_listening_sockets(ngx_cycle_t *cycle)
+             /* change backlog via listen() */
+-            if (listen(ls[i].fd, ls[i].backlog) == -1) {
++            if (ngxvcl_listen(ls[i].fd, ls[i].backlog) == -1) {
+                 ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                               "listen() to %V, backlog %d failed, ignored",
+                               &ls[i].addr_text, ls[i].backlog);
+@@ -812,7 +812,7 @@ ngx_configure_listening_sockets(ngx_cycle_t *cycle)
+ #ifdef SO_ACCEPTFILTER
+         if (ls[i].delete_deferred) {
+-            if (setsockopt(ls[i].fd, SOL_SOCKET, SO_ACCEPTFILTER, NULL, 0)
++            if (ngxvcl_kvfd_setsockopt(ls[i].fd, SOL_SOCKET, SO_ACCEPTFILTER, NULL, 0)
+                 == -1)
+             {
+                 ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+@@ -838,7 +838,7 @@ ngx_configure_listening_sockets(ngx_cycle_t *cycle)
+             (void) ngx_cpystrn((u_char *) af.af_name,
+                                (u_char *) ls[i].accept_filter, 16);
+-            if (setsockopt(ls[i].fd, SOL_SOCKET, SO_ACCEPTFILTER,
++            if (ngxvcl_kvfd_setsockopt(ls[i].fd, SOL_SOCKET, SO_ACCEPTFILTER,
+                            &af, sizeof(struct accept_filter_arg))
+                 == -1)
+             {
+@@ -871,7 +871,7 @@ ngx_configure_listening_sockets(ngx_cycle_t *cycle)
+                 value = 0;
+             }
+-            if (setsockopt(ls[i].fd, IPPROTO_TCP, TCP_DEFER_ACCEPT,
++            if (ngxvcl_kvfd_setsockopt(ls[i].fd, IPPROTO_TCP, TCP_DEFER_ACCEPT,
+                            &value, sizeof(int))
+                 == -1)
+             {
+@@ -900,7 +900,7 @@ ngx_configure_listening_sockets(ngx_cycle_t *cycle)
+         {
+             value = 1;
+-            if (setsockopt(ls[i].fd, IPPROTO_IP, IP_RECVDSTADDR,
++            if (ngxvcl_kvfd_setsockopt(ls[i].fd, IPPROTO_IP, IP_RECVDSTADDR,
+                            (const void *) &value, sizeof(int))
+                 == -1)
+             {
+@@ -919,7 +919,7 @@ ngx_configure_listening_sockets(ngx_cycle_t *cycle)
+         {
+             value = 1;
+-            if (setsockopt(ls[i].fd, IPPROTO_IP, IP_PKTINFO,
++            if (ngxvcl_kvfd_setsockopt(ls[i].fd, IPPROTO_IP, IP_PKTINFO,
+                            (const void *) &value, sizeof(int))
+                 == -1)
+             {
+@@ -940,7 +940,7 @@ ngx_configure_listening_sockets(ngx_cycle_t *cycle)
+         {
+             value = 1;
+-            if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_RECVPKTINFO,
++            if (ngxvcl_kvfd_setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+                            (const void *) &value, sizeof(int))
+                 == -1)
+             {
+@@ -1323,7 +1323,7 @@ ngx_connection_local_sockaddr(ngx_connection_t *c, ngx_str_t *s,
+         len = sizeof(ngx_sockaddr_t);
+-        if (getsockname(c->fd, &sa.sockaddr, &len) == -1) {
++        if (ngxvcl_kvfd_getsockname(c->fd, &sa.sockaddr, &len) == -1) {
+             ngx_connection_error(c, ngx_socket_errno, "getsockname() failed");
+             return NGX_ERROR;
+         }
+@@ -1362,7 +1362,7 @@ ngx_tcp_nodelay(ngx_connection_t *c)
+     tcp_nodelay = 1;
+-    if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY,
++    if (ngxvcl_kvfd_setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY,
+                    (const void *) &tcp_nodelay, sizeof(int))
+         == -1)
+     {
+diff --git a/nginx/src/core/ngx_core.h b/nginx/src/core/ngx_core.h
+index 2069373..03cb15c 100644
+--- a/nginx/src/core/ngx_core.h
++++ b/nginx/src/core/ngx_core.h
+@@ -11,6 +11,7 @@
+ #include <ngx_config.h>
++#include <vcl/ngxvcl.h>
+ typedef struct ngx_module_s          ngx_module_t;
+ typedef struct ngx_conf_s            ngx_conf_t;
+diff --git a/nginx/src/core/ngx_resolver.c b/nginx/src/core/ngx_resolver.c
+index cd55520..88add4d 100644
+--- a/nginx/src/core/ngx_resolver.c
++++ b/nginx/src/core/ngx_resolver.c
+@@ -4421,7 +4421,7 @@ ngx_udp_connect(ngx_resolver_connection_t *rec)
+     ngx_log_debug3(NGX_LOG_DEBUG_EVENT, &rec->log, 0,
+                    "connect to %V, fd:%d #%uA", &rec->server, s, c->number);
+-    rc = connect(s, rec->sockaddr, rec->socklen);
++    rc = ngxvcl_connect(s, rec->sockaddr, rec->socklen);
+     /* TODO: iocp */
+@@ -4513,7 +4513,7 @@ ngx_tcp_connect(ngx_resolver_connection_t *rec)
+     ngx_log_debug3(NGX_LOG_DEBUG_EVENT, &rec->log, 0,
+                    "connect to %V, fd:%d #%uA", &rec->server, s, c->number);
+-    rc = connect(s, rec->sockaddr, rec->socklen);
++    rc = ngxvcl_connect(s, rec->sockaddr, rec->socklen);
+     if (rc == -1) {
+         err = ngx_socket_errno;
+diff --git a/nginx/src/core/ngx_syslog.c b/nginx/src/core/ngx_syslog.c
+index 0a67928..285cc0c 100644
+--- a/nginx/src/core/ngx_syslog.c
++++ b/nginx/src/core/ngx_syslog.c
+@@ -331,7 +331,7 @@ ngx_syslog_init_peer(ngx_syslog_peer_t *peer)
+         goto failed;
+     }
+-    if (connect(fd, peer->server.sockaddr, peer->server.socklen) == -1) {
++    if (ngxvcl_connect(fd, peer->server.sockaddr, peer->server.socklen) == -1) {
+         ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_socket_errno,
+                       "connect() failed");
+         goto failed;
+diff --git a/nginx/src/event/modules/ngx_epoll_module.c b/nginx/src/event/modules/ngx_epoll_module.c
+index 76aee08..2e320ec 100644
+--- a/nginx/src/event/modules/ngx_epoll_module.c
++++ b/nginx/src/event/modules/ngx_epoll_module.c
+@@ -292,7 +292,7 @@ ngx_epoll_aio_init(ngx_cycle_t *cycle, ngx_epoll_conf_t *epcf)
+     ee.events = EPOLLIN|EPOLLET;
+     ee.data.ptr = &ngx_eventfd_conn;
+-    if (epoll_ctl(ep, EPOLL_CTL_ADD, ngx_eventfd, &ee) != -1) {
++    if (ngxvcl_kvfd_epoll_ctl(ep, EPOLL_CTL_ADD, ngx_eventfd, &ee) != -1) {
+         return;
+     }
+@@ -327,7 +327,7 @@ ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
+     epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module);
+     if (ep == -1) {
+-        ep = epoll_create(cycle->connection_n / 2);
++        ep = ngxvcl_epoll_create(cycle->connection_n / 2);
+         if (ep == -1) {
+             ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+@@ -412,7 +412,7 @@ ngx_epoll_notify_init(ngx_log_t *log)
+     ee.events = EPOLLIN|EPOLLET;
+     ee.data.ptr = &notify_conn;
+-    if (epoll_ctl(ep, EPOLL_CTL_ADD, notify_fd, &ee) == -1) {
++    if (ngxvcl_kvfd_epoll_ctl(ep, EPOLL_CTL_ADD, notify_fd, &ee) == -1) {
+         ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                       "epoll_ctl(EPOLL_CTL_ADD, eventfd) failed");
+@@ -475,7 +475,7 @@ ngx_epoll_test_rdhup(ngx_cycle_t *cycle)
+     ee.events = EPOLLET|EPOLLIN|EPOLLRDHUP;
+-    if (epoll_ctl(ep, EPOLL_CTL_ADD, s[0], &ee) == -1) {
++    if (ngxvcl_kvfd_epoll_ctl(ep, EPOLL_CTL_ADD, s[0], &ee) == -1) {
+         ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                       "epoll_ctl() failed");
+         goto failed;
+@@ -490,7 +490,7 @@ ngx_epoll_test_rdhup(ngx_cycle_t *cycle)
+     s[1] = -1;
+-    events = epoll_wait(ep, &ee, 1, 5000);
++    events = ngxvcl_kvfd_epoll_wait(ep, &ee, 1, 5000);
+     if (events == -1) {
+         ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+@@ -529,7 +529,7 @@ failed:
+ static void
+ ngx_epoll_done(ngx_cycle_t *cycle)
+ {
+-    if (close(ep) == -1) {
++    if (ngxvcl_close(ep) == -1) {
+         ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                       "epoll close() failed");
+     }
+@@ -624,7 +624,7 @@ ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+                    "epoll add event: fd:%d op:%d ev:%08XD",
+                    c->fd, op, ee.events);
+-    if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
++    if (ngxvcl_kvfd_epoll_ctl(ep, op, c->fd, &ee) == -1) {
+         ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+                       "epoll_ctl(%d, %d) failed", op, c->fd);
+         return NGX_ERROR;
+@@ -685,7 +685,7 @@ ngx_epoll_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+                    "epoll del event: fd:%d op:%d ev:%08XD",
+                    c->fd, op, ee.events);
+-    if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
++    if (ngxvcl_kvfd_epoll_ctl(ep, op, c->fd, &ee) == -1) {
+         ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+                       "epoll_ctl(%d, %d) failed", op, c->fd);
+         return NGX_ERROR;
+@@ -708,7 +708,7 @@ ngx_epoll_add_connection(ngx_connection_t *c)
+     ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                    "epoll add connection: fd:%d ev:%08XD", c->fd, ee.events);
+-    if (epoll_ctl(ep, EPOLL_CTL_ADD, c->fd, &ee) == -1) {
++    if (ngxvcl_kvfd_epoll_ctl(ep, EPOLL_CTL_ADD, c->fd, &ee) == -1) {
+         ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+                       "epoll_ctl(EPOLL_CTL_ADD, %d) failed", c->fd);
+         return NGX_ERROR;
+@@ -746,7 +746,7 @@ ngx_epoll_del_connection(ngx_connection_t *c, ngx_uint_t flags)
+     ee.events = 0;
+     ee.data.ptr = NULL;
+-    if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
++    if (ngxvcl_kvfd_epoll_ctl(ep, op, c->fd, &ee) == -1) {
+         ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+                       "epoll_ctl(%d, %d) failed", op, c->fd);
+         return NGX_ERROR;
+@@ -797,7 +797,7 @@ ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
+     ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                    "epoll timer: %M", timer);
+-    events = epoll_wait(ep, event_list, (int) nevents, timer);
++    events = ngxvcl_kvfd_epoll_wait(ep, event_list, (int) nevents, timer);
+     err = (events == -1) ? ngx_errno : 0;
+diff --git a/nginx/src/event/modules/ngx_select_module.c b/nginx/src/event/modules/ngx_select_module.c
+index 0644621..c76c915 100644
+--- a/nginx/src/event/modules/ngx_select_module.c
++++ b/nginx/src/event/modules/ngx_select_module.c
+@@ -367,7 +367,7 @@ ngx_select_repair_fd_sets(ngx_cycle_t *cycle)
+         len = sizeof(int);
+-        if (getsockopt(s, SOL_SOCKET, SO_TYPE, &n, &len) == -1) {
++        if (ngxvcl_kvfd_getsockopt(s, SOL_SOCKET, SO_TYPE, &n, &len) == -1) {
+             err = ngx_socket_errno;
+             ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+@@ -385,7 +385,7 @@ ngx_select_repair_fd_sets(ngx_cycle_t *cycle)
+         len = sizeof(int);
+-        if (getsockopt(s, SOL_SOCKET, SO_TYPE, &n, &len) == -1) {
++        if (ngxvcl_kvfd_getsockopt(s, SOL_SOCKET, SO_TYPE, &n, &len) == -1) {
+             err = ngx_socket_errno;
+             ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+diff --git a/nginx/src/event/modules/ngx_win32_select_module.c b/nginx/src/event/modules/ngx_win32_select_module.c
+index a98a83f..cb7febf 100644
+--- a/nginx/src/event/modules/ngx_win32_select_module.c
++++ b/nginx/src/event/modules/ngx_win32_select_module.c
+@@ -356,7 +356,7 @@ ngx_select_repair_fd_sets(ngx_cycle_t *cycle)
+         s = master_read_fd_set.fd_array[i];
+         len = sizeof(int);
+-        if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char *) &n, &len) == -1) {
++        if (ngxvcl_kvfd_getsockopt(s, SOL_SOCKET, SO_TYPE, (char *) &n, &len) == -1) {
+             err = ngx_socket_errno;
+             ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+@@ -371,7 +371,7 @@ ngx_select_repair_fd_sets(ngx_cycle_t *cycle)
+         s = master_write_fd_set.fd_array[i];
+         len = sizeof(int);
+-        if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char *) &n, &len) == -1) {
++        if (ngxvcl_kvfd_getsockopt(s, SOL_SOCKET, SO_TYPE, (char *) &n, &len) == -1) {
+             err = ngx_socket_errno;
+             ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+diff --git a/nginx/src/event/ngx_event.c b/nginx/src/event/ngx_event.c
+index 57af813..c9d3486 100644
+--- a/nginx/src/event/ngx_event.c
++++ b/nginx/src/event/ngx_event.c
+@@ -885,7 +885,7 @@ ngx_send_lowat(ngx_connection_t *c, size_t lowat)
+     sndlowat = (int) lowat;
+-    if (setsockopt(c->fd, SOL_SOCKET, SO_SNDLOWAT,
++    if (ngxvcl_kvfd_setsockopt(c->fd, SOL_SOCKET, SO_SNDLOWAT,
+                    (const void *) &sndlowat, sizeof(int))
+         == -1)
+     {
+diff --git a/nginx/src/event/ngx_event_accept.c b/nginx/src/event/ngx_event_accept.c
+index 7e9f742..d5cf094 100644
+--- a/nginx/src/event/ngx_event_accept.c
++++ b/nginx/src/event/ngx_event_accept.c
+@@ -62,12 +62,12 @@ ngx_event_accept(ngx_event_t *ev)
+ #if (NGX_HAVE_ACCEPT4)
+         if (use_accept4) {
+-            s = accept4(lc->fd, &sa.sockaddr, &socklen, SOCK_NONBLOCK);
++            s = ngxvcl_accept4(lc->fd, &sa.sockaddr, &socklen, SOCK_NONBLOCK);
+         } else {
+-            s = accept(lc->fd, &sa.sockaddr, &socklen);
++            s = ngxvcl_accept(lc->fd, &sa.sockaddr, &socklen);
+         }
+ #else
+-        s = accept(lc->fd, &sa.sockaddr, &socklen);
++        s = ngxvcl_accept(lc->fd, &sa.sockaddr, &socklen);
+ #endif
+         if (s == (ngx_socket_t) -1) {
+@@ -404,7 +404,7 @@ ngx_event_recvmsg(ngx_event_t *ev)
+ #endif
+-        n = recvmsg(lc->fd, &msg, 0);
++        n = ngxvcl_recvmsg(lc->fd, &msg, 0);
+         if (n == -1) {
+             err = ngx_socket_errno;
+diff --git a/nginx/src/event/ngx_event_connect.c b/nginx/src/event/ngx_event_connect.c
+index e7f28c9..f7e539f 100644
+--- a/nginx/src/event/ngx_event_connect.c
++++ b/nginx/src/event/ngx_event_connect.c
+@@ -64,7 +64,7 @@ ngx_event_connect_peer(ngx_peer_connection_t *pc)
+     c->type = type;
+     if (pc->rcvbuf) {
+-        if (setsockopt(s, SOL_SOCKET, SO_RCVBUF,
++        if (ngxvcl_kvfd_setsockopt(s, SOL_SOCKET, SO_RCVBUF,
+                        (const void *) &pc->rcvbuf, sizeof(int)) == -1)
+         {
+             ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+@@ -100,7 +100,7 @@ ngx_event_connect_peer(ngx_peer_connection_t *pc)
+             static int  bind_address_no_port = 1;
+             if (bind_address_no_port) {
+-                if (setsockopt(s, IPPROTO_IP, IP_BIND_ADDRESS_NO_PORT,
++                if (ngxvcl_kvfd_setsockopt(s, IPPROTO_IP, IP_BIND_ADDRESS_NO_PORT,
+                                (const void *) &bind_address_no_port,
+                                sizeof(int)) == -1)
+                 {
+@@ -125,7 +125,7 @@ ngx_event_connect_peer(ngx_peer_connection_t *pc)
+         if (pc->type == SOCK_DGRAM && port != 0) {
+             int  reuse_addr = 1;
+-            if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
++            if (ngxvcl_kvfd_setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
+                            (const void *) &reuse_addr, sizeof(int))
+                  == -1)
+             {
+@@ -137,7 +137,7 @@ ngx_event_connect_peer(ngx_peer_connection_t *pc)
+ #endif
+-        if (bind(s, pc->local->sockaddr, pc->local->socklen) == -1) {
++        if (ngxvcl_bind(s, pc->local->sockaddr, pc->local->socklen) == -1) {
+             ngx_log_error(NGX_LOG_CRIT, pc->log, ngx_socket_errno,
+                           "bind(%V) failed", &pc->local->name);
+@@ -190,7 +190,7 @@ ngx_event_connect_peer(ngx_peer_connection_t *pc)
+     ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pc->log, 0,
+                    "connect to %V, fd:%d #%uA", pc->name, s, c->number);
+-    rc = connect(s, pc->sockaddr, pc->socklen);
++    rc = ngxvcl_connect(s, pc->sockaddr, pc->socklen);
+     if (rc == -1) {
+         err = ngx_socket_errno;
+@@ -326,7 +326,7 @@ ngx_event_connect_set_transparent(ngx_peer_connection_t *pc, ngx_socket_t s)
+ #if defined(SO_BINDANY)
+-    if (setsockopt(s, SOL_SOCKET, SO_BINDANY,
++    if (ngxvcl_kvfd_setsockopt(s, SOL_SOCKET, SO_BINDANY,
+                    (const void *) &value, sizeof(int)) == -1)
+     {
+         ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+@@ -342,7 +342,7 @@ ngx_event_connect_set_transparent(ngx_peer_connection_t *pc, ngx_socket_t s)
+ #if defined(IP_TRANSPARENT)
+-        if (setsockopt(s, IPPROTO_IP, IP_TRANSPARENT,
++        if (ngxvcl_kvfd_setsockopt(s, IPPROTO_IP, IP_TRANSPARENT,
+                        (const void *) &value, sizeof(int)) == -1)
+         {
+             ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+@@ -352,7 +352,7 @@ ngx_event_connect_set_transparent(ngx_peer_connection_t *pc, ngx_socket_t s)
+ #elif defined(IP_BINDANY)
+-        if (setsockopt(s, IPPROTO_IP, IP_BINDANY,
++        if (ngxvcl_kvfd_setsockopt(s, IPPROTO_IP, IP_BINDANY,
+                        (const void *) &value, sizeof(int)) == -1)
+         {
+             ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+@@ -370,7 +370,7 @@ ngx_event_connect_set_transparent(ngx_peer_connection_t *pc, ngx_socket_t s)
+ #if defined(IPV6_TRANSPARENT)
+-        if (setsockopt(s, IPPROTO_IPV6, IPV6_TRANSPARENT,
++        if (ngxvcl_kvfd_setsockopt(s, IPPROTO_IPV6, IPV6_TRANSPARENT,
+                        (const void *) &value, sizeof(int)) == -1)
+         {
+             ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+@@ -380,7 +380,7 @@ ngx_event_connect_set_transparent(ngx_peer_connection_t *pc, ngx_socket_t s)
+ #elif defined(IPV6_BINDANY)
+-        if (setsockopt(s, IPPROTO_IPV6, IPV6_BINDANY,
++        if (ngxvcl_kvfd_setsockopt(s, IPPROTO_IPV6, IPV6_BINDANY,
+                        (const void *) &value, sizeof(int)) == -1)
+         {
+             ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+diff --git a/nginx/src/http/modules/ngx_http_upstream_keepalive_module.c b/nginx/src/http/modules/ngx_http_upstream_keepalive_module.c
+index 90a226d..2283023 100644
+--- a/nginx/src/http/modules/ngx_http_upstream_keepalive_module.c
++++ b/nginx/src/http/modules/ngx_http_upstream_keepalive_module.c
+@@ -397,7 +397,7 @@ ngx_http_upstream_keepalive_close_handler(ngx_event_t *ev)
+         goto close;
+     }
+-    n = recv(c->fd, buf, 1, MSG_PEEK);
++    n = ngxvcl_recv(c->fd, buf, 1, MSG_PEEK);
+     if (n == -1 && ngx_socket_errno == NGX_EAGAIN) {
+         ev->ready = 0;
+diff --git a/nginx/src/http/ngx_http_request.c b/nginx/src/http/ngx_http_request.c
+index c88c271..968ff84 100644
+--- a/nginx/src/http/ngx_http_request.c
++++ b/nginx/src/http/ngx_http_request.c
+@@ -664,7 +664,7 @@ ngx_http_ssl_handshake(ngx_event_t *rev)
+     size = hc->proxy_protocol ? sizeof(buf) : 1;
+-    n = recv(c->fd, (char *) buf, size, MSG_PEEK);
++    n = ngxvcl_recv(c->fd, (char *) buf, size, MSG_PEEK);
+     err = ngx_socket_errno;
+@@ -2831,7 +2831,7 @@ ngx_http_test_reading(ngx_http_request_t *r)
+          * Solaris returns -1 and sets errno
+          */
+-        if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len)
++        if (ngxvcl_kvfd_getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len)
+             == -1)
+         {
+             err = ngx_socket_errno;
+@@ -2842,7 +2842,7 @@ ngx_http_test_reading(ngx_http_request_t *r)
+ #endif
+-    n = recv(c->fd, buf, 1, MSG_PEEK);
++    n = ngxvcl_recv(c->fd, buf, 1, MSG_PEEK);
+     if (n == 0) {
+         rev->eof = 1;
+@@ -3521,7 +3521,7 @@ ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc)
+             linger.l_onoff = 1;
+             linger.l_linger = 0;
+-            if (setsockopt(r->connection->fd, SOL_SOCKET, SO_LINGER,
++            if (ngxvcl_kvfd_setsockopt(r->connection->fd, SOL_SOCKET, SO_LINGER,
+                            (const void *) &linger, sizeof(struct linger)) == -1)
+             {
+                 ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno,
+diff --git a/nginx/src/http/ngx_http_upstream.c b/nginx/src/http/ngx_http_upstream.c
+index 0760dc2..25beefc 100644
+--- a/nginx/src/http/ngx_http_upstream.c
++++ b/nginx/src/http/ngx_http_upstream.c
+@@ -1400,7 +1400,7 @@ ngx_http_upstream_check_broken_connection(ngx_http_request_t *r,
+          * Solaris returns -1 and sets errno
+          */
+-        if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len)
++        if (ngxvcl_kvfd_getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len)
+             == -1)
+         {
+             err = ngx_socket_errno;
+@@ -1433,7 +1433,7 @@ ngx_http_upstream_check_broken_connection(ngx_http_request_t *r,
+ #endif
+-    n = recv(c->fd, buf, 1, MSG_PEEK);
++    n = ngxvcl_recv(c->fd, buf, 1, MSG_PEEK);
+     err = ngx_socket_errno;
+@@ -2658,7 +2658,7 @@ ngx_http_upstream_test_connect(ngx_connection_t *c)
+          * Solaris returns -1 and sets errno
+          */
+-        if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len)
++        if (ngxvcl_kvfd_getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len)
+             == -1)
+         {
+             err = ngx_socket_errno;
+diff --git a/nginx/src/http/ngx_http_variables.c b/nginx/src/http/ngx_http_variables.c
+index 2deb968..9bd5b6b 100644
+--- a/nginx/src/http/ngx_http_variables.c
++++ b/nginx/src/http/ngx_http_variables.c
+@@ -1117,7 +1117,7 @@ ngx_http_variable_tcpinfo(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+     uint32_t         value;
+     len = sizeof(struct tcp_info);
+-    if (getsockopt(r->connection->fd, IPPROTO_TCP, TCP_INFO, &ti, &len) == -1) {
++    if (ngxvcl_kvfd_getsockopt(r->connection->fd, IPPROTO_TCP, TCP_INFO, &ti, &len) == -1) {
+         v->not_found = 1;
+         return NGX_OK;
+     }
+diff --git a/nginx/src/os/unix/ngx_linux_sendfile_chain.c b/nginx/src/os/unix/ngx_linux_sendfile_chain.c
+index 5695839..158f433 100644
+--- a/nginx/src/os/unix/ngx_linux_sendfile_chain.c
++++ b/nginx/src/os/unix/ngx_linux_sendfile_chain.c
+@@ -102,7 +102,7 @@ ngx_linux_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
+                 tcp_nodelay = 0;
+-                if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY,
++                if (ngxvcl_kvfd_setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY,
+                                (const void *) &tcp_nodelay, sizeof(int)) == -1)
+                 {
+                     err = ngx_socket_errno;
+@@ -256,7 +256,7 @@ eintr:
+     ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                    "sendfile: @%O %uz", file->file_pos, size);
+-    n = sendfile(c->fd, file->file->fd, &offset, size);
++    n = ngxvcl_sendfile(c->fd, file->file->fd, &offset, size);
+     if (n == -1) {
+         err = ngx_errno;
+@@ -416,7 +416,7 @@ ngx_linux_sendfile_thread_handler(void *data, ngx_log_t *log)
+ again:
+-    n = sendfile(ctx->socket, file->file->fd, &offset, ctx->size);
++    n = ngxvcl_sendfile(ctx->socket, file->file->fd, &offset, ctx->size);
+     if (n == -1) {
+         ctx->err = ngx_errno;
+diff --git a/nginx/src/os/unix/ngx_process_cycle.c b/nginx/src/os/unix/ngx_process_cycle.c
+index 5817a2c..3236904 100644
+--- a/nginx/src/os/unix/ngx_process_cycle.c
++++ b/nginx/src/os/unix/ngx_process_cycle.c
+@@ -354,6 +354,7 @@ ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type)
+     ch.command = NGX_CMD_OPEN_CHANNEL;
++    ngxvcl_wait_kep_and_vep();
+     for (i = 0; i < n; i++) {
+         ngx_spawn_process(cycle, ngx_worker_process_cycle,
+@@ -365,6 +366,8 @@ ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type)
+         ngx_pass_open_channel(cycle, &ch);
+     }
++    ngx_sleep(3);
++    ngxvcl_wait_vep_only();
+ }
+diff --git a/nginx/src/os/unix/ngx_readv_chain.c b/nginx/src/os/unix/ngx_readv_chain.c
+index 454cfdc..098b524 100644
+--- a/nginx/src/os/unix/ngx_readv_chain.c
++++ b/nginx/src/os/unix/ngx_readv_chain.c
+@@ -118,7 +118,7 @@ ngx_readv_chain(ngx_connection_t *c, ngx_chain_t *chain, off_t limit)
+                    "readv: %ui, last:%uz", vec.nelts, iov->iov_len);
+     do {
+-        n = readv(c->fd, (struct iovec *) vec.elts, vec.nelts);
++        n = ngxvcl_readv(c->fd, (struct iovec *) vec.elts, vec.nelts);
+         if (n == 0) {
+             rev->ready = 0;
+diff --git a/nginx/src/os/unix/ngx_recv.c b/nginx/src/os/unix/ngx_recv.c
+index c85fd45..9aaa809 100644
+--- a/nginx/src/os/unix/ngx_recv.c
++++ b/nginx/src/os/unix/ngx_recv.c
+@@ -66,7 +66,7 @@ ngx_unix_recv(ngx_connection_t *c, u_char *buf, size_t size)
+ #endif
+     do {
+-        n = recv(c->fd, buf, size, 0);
++        n = ngxvcl_recv(c->fd, buf, size, 0);
+         ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                        "recv: fd:%d %z of %uz", c->fd, n, size);
+diff --git a/nginx/src/os/unix/ngx_send.c b/nginx/src/os/unix/ngx_send.c
+index 61ea202..5b91df6 100644
+--- a/nginx/src/os/unix/ngx_send.c
++++ b/nginx/src/os/unix/ngx_send.c
+@@ -31,7 +31,7 @@ ngx_unix_send(ngx_connection_t *c, u_char *buf, size_t size)
+ #endif
+     for ( ;; ) {
+-        n = send(c->fd, buf, size, 0);
++        n = ngxvcl_send(c->fd, buf, size, 0);
+         ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                        "send: fd:%d %z of %uz", c->fd, n, size);
+diff --git a/nginx/src/os/unix/ngx_socket.c b/nginx/src/os/unix/ngx_socket.c
+index 3978f65..b3d5fe6 100644
+--- a/nginx/src/os/unix/ngx_socket.c
++++ b/nginx/src/os/unix/ngx_socket.c
+@@ -30,7 +30,7 @@ ngx_nonblocking(ngx_socket_t s)
+     nb = 1;
+-    return ioctl(s, FIONBIO, &nb);
++    return ngxvcl_kvfd_ioctl(s, FIONBIO, &nb);
+ }
+@@ -41,7 +41,7 @@ ngx_blocking(ngx_socket_t s)
+     nb = 0;
+-    return ioctl(s, FIONBIO, &nb);
++    return ngxvcl_kvfd_ioctl(s, FIONBIO, &nb);
+ }
+ #endif
+@@ -56,7 +56,7 @@ ngx_tcp_nopush(ngx_socket_t s)
+     tcp_nopush = 1;
+-    return setsockopt(s, IPPROTO_TCP, TCP_NOPUSH,
++    return ngxvcl_kvfd_setsockopt(s, IPPROTO_TCP, TCP_NOPUSH,
+                       (const void *) &tcp_nopush, sizeof(int));
+ }
+@@ -68,7 +68,7 @@ ngx_tcp_push(ngx_socket_t s)
+     tcp_nopush = 0;
+-    return setsockopt(s, IPPROTO_TCP, TCP_NOPUSH,
++    return ngxvcl_kvfd_setsockopt(s, IPPROTO_TCP, TCP_NOPUSH,
+                       (const void *) &tcp_nopush, sizeof(int));
+ }
+@@ -82,7 +82,7 @@ ngx_tcp_nopush(ngx_socket_t s)
+     cork = 1;
+-    return setsockopt(s, IPPROTO_TCP, TCP_CORK,
++    return ngxvcl_kvfd_setsockopt(s, IPPROTO_TCP, TCP_CORK,
+                       (const void *) &cork, sizeof(int));
+ }
+@@ -94,7 +94,7 @@ ngx_tcp_push(ngx_socket_t s)
+     cork = 0;
+-    return setsockopt(s, IPPROTO_TCP, TCP_CORK,
++    return ngxvcl_kvfd_setsockopt(s, IPPROTO_TCP, TCP_CORK,
+                       (const void *) &cork, sizeof(int));
+ }
+diff --git a/nginx/src/os/unix/ngx_socket.h b/nginx/src/os/unix/ngx_socket.h
+index fcc5153..bcdde38 100644
+--- a/nginx/src/os/unix/ngx_socket.h
++++ b/nginx/src/os/unix/ngx_socket.h
+@@ -16,7 +16,7 @@
+ typedef int  ngx_socket_t;
+-#define ngx_socket          socket
++#define ngx_socket          ngxvcl_socket
+ #define ngx_socket_n        "socket()"
+@@ -30,10 +30,10 @@ int ngx_blocking(ngx_socket_t s);
+ #else
+-#define ngx_nonblocking(s)  fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK)
++#define ngx_nonblocking(s)  ngxvcl_kvfd_fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK)
+ #define ngx_nonblocking_n   "fcntl(O_NONBLOCK)"
+-#define ngx_blocking(s)     fcntl(s, F_SETFL, fcntl(s, F_GETFL) & ~O_NONBLOCK)
++#define ngx_blocking(s)     ngxvcl_kvfd_fcntl(s, F_SETFL, fcntl(s, F_GETFL) & ~O_NONBLOCK)
+ #define ngx_blocking_n      "fcntl(!O_NONBLOCK)"
+ #endif
+@@ -54,10 +54,10 @@ int ngx_tcp_push(ngx_socket_t s);
+ #endif
+-#define ngx_shutdown_socket    shutdown
++#define ngx_shutdown_socket    ngxvcl_shutdown
+ #define ngx_shutdown_socket_n  "shutdown()"
+-#define ngx_close_socket    close
++#define ngx_close_socket    ngxvcl_close
+ #define ngx_close_socket_n  "close() socket"
+diff --git a/nginx/src/os/unix/ngx_udp_recv.c b/nginx/src/os/unix/ngx_udp_recv.c
+index 6d544c2..ced9261 100644
+--- a/nginx/src/os/unix/ngx_udp_recv.c
++++ b/nginx/src/os/unix/ngx_udp_recv.c
+@@ -20,7 +20,7 @@ ngx_udp_unix_recv(ngx_connection_t *c, u_char *buf, size_t size)
+     rev = c->read;
+     do {
+-        n = recv(c->fd, buf, size, 0);
++        n = ngxvcl_recv(c->fd, buf, size, 0);
+         ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                        "recv: fd:%d %z of %uz", c->fd, n, size);
+diff --git a/nginx/src/os/unix/ngx_udp_send.c b/nginx/src/os/unix/ngx_udp_send.c
+index aabbc8e..ad1af9d 100644
+--- a/nginx/src/os/unix/ngx_udp_send.c
++++ b/nginx/src/os/unix/ngx_udp_send.c
+@@ -20,7 +20,7 @@ ngx_udp_unix_send(ngx_connection_t *c, u_char *buf, size_t size)
+     wev = c->write;
+     for ( ;; ) {
+-        n = sendto(c->fd, buf, size, 0, c->sockaddr, c->socklen);
++        n = ngxvcl_sendto(c->fd, buf, size, 0, c->sockaddr, c->socklen);
+         ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                        "sendto: fd:%d %z of %uz to \"%V\"",
+diff --git a/nginx/src/os/unix/ngx_udp_sendmsg_chain.c b/nginx/src/os/unix/ngx_udp_sendmsg_chain.c
+index 5399c79..7a04f0d 100644
+--- a/nginx/src/os/unix/ngx_udp_sendmsg_chain.c
++++ b/nginx/src/os/unix/ngx_udp_sendmsg_chain.c
+@@ -305,7 +305,7 @@ ngx_sendmsg(ngx_connection_t *c, ngx_iovec_t *vec)
+ eintr:
+-    n = sendmsg(c->fd, &msg, 0);
++    n = ngxvcl_sendmsg(c->fd, &msg, 0);
+     ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                    "sendmsg: %z of %uz", n, vec->size);
+diff --git a/nginx/src/os/unix/ngx_writev_chain.c b/nginx/src/os/unix/ngx_writev_chain.c
+index e38a3aa..9edc838 100644
+--- a/nginx/src/os/unix/ngx_writev_chain.c
++++ b/nginx/src/os/unix/ngx_writev_chain.c
+@@ -186,7 +186,7 @@ ngx_writev(ngx_connection_t *c, ngx_iovec_t *vec)
+ eintr:
+-    n = writev(c->fd, vec->iovs, vec->count);
++    n = ngxvcl_writev(c->fd, vec->iovs, vec->count);
+     ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                    "writev: %z of %uz", n, vec->size);
+diff --git a/nginx/src/stream/ngx_stream_handler.c b/nginx/src/stream/ngx_stream_handler.c
+index 669b6a1..105b9f5 100644
+--- a/nginx/src/stream/ngx_stream_handler.c
++++ b/nginx/src/stream/ngx_stream_handler.c
+@@ -225,7 +225,7 @@ ngx_stream_proxy_protocol_handler(ngx_event_t *rev)
+         return;
+     }
+-    n = recv(c->fd, (char *) buf, sizeof(buf), MSG_PEEK);
++    n = ngxvcl_recv(c->fd, (char *) buf, sizeof(buf), MSG_PEEK);
+     err = ngx_socket_errno;
+diff --git a/nginx/src/stream/ngx_stream_proxy_module.c b/nginx/src/stream/ngx_stream_proxy_module.c
+index 30572cd..f911135 100644
+--- a/nginx/src/stream/ngx_stream_proxy_module.c
++++ b/nginx/src/stream/ngx_stream_proxy_module.c
+@@ -1427,7 +1427,7 @@ ngx_stream_proxy_test_connect(ngx_connection_t *c)
+          * Solaris returns -1 and sets errno
+          */
+-        if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len)
++        if (ngxvcl_kvfd_getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len)
+             == -1)
+         {
+             err = ngx_socket_errno;
+-- 
+2.17.1
+
diff --git a/vpp_patches/common/0001-session-pinning.patch b/vpp_patches/common/0001-session-pinning.patch
new file mode 100644 (file)
index 0000000..22faa0b
--- /dev/null
@@ -0,0 +1,168 @@
+From 310c46798bdfe655ea35eabec57215568b3632ec Mon Sep 17 00:00:00 2001
+From: SunGuoao <[email protected]>
+Date: Thu, 17 Oct 2019 00:16:31 +0800
+Subject: [PATCH] session: pinning
+
+Change-Id: Ic3600bc293bdae7ff62611da8d1241afccdac02a
+---
+ src/vnet/session/application.c | 83 ++++++++++++++++++++++++++++++++--
+ src/vnet/session/application.h | 13 ++++++
+ 2 files changed, 92 insertions(+), 4 deletions(-)
+
+diff --git a/src/vnet/session/application.c b/src/vnet/session/application.c
+index 396470ae6..869fec45c 100644
+--- a/src/vnet/session/application.c
++++ b/src/vnet/session/application.c
+@@ -32,13 +32,19 @@ static app_listener_t *
+ app_listener_alloc (application_t * app)
+ {
+   app_listener_t *app_listener;
++  vpp_app_worker_map_t *current_thread_map;
+   pool_get (app->listeners, app_listener);
++  pool_get (app->app_worker_thread_map, current_thread_map);
+   clib_memset (app_listener, 0, sizeof (*app_listener));
++  clib_memset (current_thread_map, 0, sizeof (*current_thread_map));
++  current_thread_map->map_index = current_thread_map - app->app_worker_thread_map;
+   app_listener->al_index = app_listener - app->listeners;
+   app_listener->app_index = app->app_index;
+   app_listener->session_index = SESSION_INVALID_INDEX;
+   app_listener->local_index = SESSION_INVALID_INDEX;
+   app_listener->ls_handle = SESSION_INVALID_HANDLE;
++  app_listener->app_worker_thread_map = app->app_worker_thread_map;
++  app_listener->all_selected = 0;
+   return app_listener;
+ }
+@@ -253,18 +259,87 @@ app_listener_cleanup (app_listener_t * al)
+   app_listener_free (app, al);
+ }
++static void
++app_listener_select_worker_add_worker (application_t * app, u32 wrk_index)
++{
++  u32 current_thread = vlib_get_thread_index ();
++  u32 *added_wrk_index;
++  vpp_app_worker_map_t *current_thread_map;
++
++  while (pool_is_free_index (app->app_worker_thread_map, current_thread))
++    {
++      vpp_app_worker_map_t *the_thread_map;
++
++      pool_get (app->app_worker_thread_map, the_thread_map);
++      clib_memset (the_thread_map, 0, sizeof (*the_thread_map));
++      the_thread_map->map_index = the_thread_map - app->app_worker_thread_map;
++    }
++  current_thread_map =
++    pool_elt_at_index (app->app_worker_thread_map, current_thread);
++  pool_get (current_thread_map->app_worker_map, added_wrk_index);
++  *added_wrk_index = wrk_index;
++  current_thread_map->total_workers += 1;
++}
++
++static u32
++app_listener_select_worker_interval (application_t * app)
++{
++  u32 current_thread = vlib_get_thread_index ();
++  u32 wrk_index;
++  vpp_app_worker_map_t *current_map;
++
++  if (PREDICT_FALSE(pool_is_free_index (app->app_worker_thread_map, current_thread)))
++    return 0;
++
++  current_map =
++    pool_elt_at_index (app->app_worker_thread_map, current_thread);
++
++  /** If the number of app worker(s) is smaller than vpp worker/thread(s) */
++  if (PREDICT_FALSE (current_map->total_workers == 0))
++    return 0;
++
++  current_map->last_accept_wrk += 1;
++  if (current_map->last_accept_wrk == current_map->total_workers)
++    current_map->last_accept_wrk = 0;
++
++  wrk_index = current_map->app_worker_map[current_map->last_accept_wrk];
++  return wrk_index;
++}
++
+ static app_worker_t *
+ app_listener_select_worker (application_t * app, app_listener_t * al)
+ {
+   u32 wrk_index;
+   app = application_get (al->app_index);
+-  wrk_index = clib_bitmap_next_set (al->workers, al->accept_rotor + 1);
+-  if (wrk_index == ~0)
+-    wrk_index = clib_bitmap_first_set (al->workers);
++  if (PREDICT_FALSE (al->all_selected == 0))
++    {
++      wrk_index = clib_bitmap_next_set (al->workers, al->accept_rotor + 1);
++      if (wrk_index == ~0)
++       {
++         al->all_selected = 1;
++         wrk_index = app_listener_select_worker_interval (app);
++         if (wrk_index == 0)
++           wrk_index = clib_bitmap_first_set (al->workers);
++       }
++      else
++       {
++         app_listener_select_worker_add_worker (app, wrk_index);
++         al->accept_rotor = wrk_index;
++       }
++    }
++  else
++    {
++      wrk_index = app_listener_select_worker_interval (app);
++      if (PREDICT_FALSE (wrk_index == 0)){
++         wrk_index = clib_bitmap_next_set (al->workers, al->accept_rotor + 1);
++         if (wrk_index == ~0)
++          wrk_index = clib_bitmap_first_set (al->workers);
++        al->accept_rotor = wrk_index;
++        }
++   }
+   ASSERT (wrk_index != ~0);
+-  al->accept_rotor = wrk_index;
+   return application_get_worker (app, wrk_index);
+ }
+diff --git a/src/vnet/session/application.h b/src/vnet/session/application.h
+index a853c3cb7..5da3ce84a 100644
+--- a/src/vnet/session/application.h
++++ b/src/vnet/session/application.h
+@@ -70,6 +70,14 @@ typedef struct app_worker_map_
+   u32 wrk_index;
+ } app_worker_map_t;
++typedef struct vpp_app_worker_map_
++{
++  u32 map_index;
++  u32 *app_worker_map;         /** app worker(s) pinned to the thread */
++  u32 last_accept_wrk;         /** last selected app worker by the thread */
++  u32 total_workers;         /** total app worker(s) pinned to the thread */
++}vpp_app_worker_map_t;
++
+ typedef struct app_listener_
+ {
+   clib_bitmap_t *workers;     /**< workers accepting connections */
+@@ -81,6 +89,8 @@ typedef struct app_listener_
+   session_handle_t ls_handle; /**< session handle of the local or global
+                                    listening session that also identifies
+                                    the app listener */
++  u8 all_selected;
++  vpp_app_worker_map_t *app_worker_thread_map;   /** app worker map for the vpp thread */
+ } app_listener_t;
+ typedef struct application_
+@@ -111,6 +121,9 @@ typedef struct application_
+   /** Pool of listeners for the app */
+   app_listener_t *listeners;
++  /** app worker(s) selected by this VPP thread */
++  vpp_app_worker_map_t *app_worker_thread_map;
++
+   /** Preferred tls engine */
+   u8 tls_engine;
+-- 
+2.17.1
+
diff --git a/vpp_patches/ldp/0001-LDP-remove-lock.patch b/vpp_patches/ldp/0001-LDP-remove-lock.patch
new file mode 100644 (file)
index 0000000..091bc78
--- /dev/null
@@ -0,0 +1,1700 @@
+From dea792cdf1af0b343180846c33be14681323f5a3 Mon Sep 17 00:00:00 2001
+From: Yu Ping <[email protected]>
+Date: Wed, 23 Oct 2019 17:56:55 +0800
+Subject: [PATCH] LDP remove lock
+
+Change-Id: I3b5135194ed93c5e4a259fb2d3f8cae7ef8de4c5
+Signed-off-by: Yu Ping <[email protected]>
+---
+ src/vcl/CMakeLists.txt |   5 +-
+ src/vcl/ldp.c          | 740 +++++++++++++++++++++++++++--------------
+ src/vcl/ldp.h          |   2 +-
+ src/vcl/vcl_private.h  |   7 +-
+ 4 files changed, 495 insertions(+), 259 deletions(-)
+
+diff --git a/src/vcl/CMakeLists.txt b/src/vcl/CMakeLists.txt
+index ab0a6ad6a..0d30eb6af 100644
+--- a/src/vcl/CMakeLists.txt
++++ b/src/vcl/CMakeLists.txt
+@@ -20,7 +20,6 @@ add_vpp_library(vppcom
+   vcl_bapi.c
+   vcl_cfg.c
+   vcl_private.c
+-  vcl_locked.c
+   LINK_LIBRARIES
+   vppinfra svm vlibmemoryclient rt pthread
+@@ -41,7 +40,7 @@ add_vpp_library(vcl_ldpreload
+ add_vpp_headers(vcl
+   ldp.h
+   ldp_glibc_socket.h
++  vcl_private.h
+   vppcom.h
+-  vcl_locked.h
+   ldp_socket_wrapper.h
+-)
+\ No newline at end of file
++)
+diff --git a/src/vcl/ldp.c b/src/vcl/ldp.c
+index 7aa383052..4e7695454 100644
+--- a/src/vcl/ldp.c
++++ b/src/vcl/ldp.c
+@@ -24,9 +24,10 @@
+ #include <vcl/ldp_socket_wrapper.h>
+ #include <vcl/ldp.h>
++#include <vcl/vppcom.h>
++#include <vcl/vcl_private.h>
+ #include <sys/time.h>
+-#include <vcl/vcl_locked.h>
+ #include <vppinfra/time.h>
+ #include <vppinfra/bitmap.h>
+ #include <vppinfra/lock.h>
+@@ -96,8 +97,8 @@ typedef struct
+   ldp_worker_ctx_t *workers;
+   int init;
+   char app_name[LDP_APP_NAME_MAX];
+-  u32 vlsh_bit_val;
+-  u32 vlsh_bit_mask;
++  u32 vcl_bit_val;
++  u32 vcl_bit_mask;
+   u32 debug;
+   u8 transparent_tls;
+@@ -112,8 +113,8 @@ typedef struct
+     clib_warning ("ldp<%d>: " _fmt, getpid(), ##_args)
+ static ldp_main_t ldp_main = {
+-  .vlsh_bit_val = (1 << LDP_SID_BIT_MIN),
+-  .vlsh_bit_mask = (1 << LDP_SID_BIT_MIN) - 1,
++  .vcl_bit_val = (1 << LDP_SID_BIT_MIN),
++  .vcl_bit_mask = (1 << LDP_SID_BIT_MIN) - 1,
+   .debug = LDP_DEBUG_INIT,
+   .transparent_tls = 0,
+ };
+@@ -146,18 +147,18 @@ ldp_get_app_name ()
+ }
+ static inline int
+-ldp_vlsh_to_fd (vls_handle_t vlsh)
++ldp_vclsh_to_fd (vcl_session_handle_t vclsh)
+ {
+-  return (vlsh + ldp->vlsh_bit_val);
++  return (vclsh + ldp->vcl_bit_val);
+ }
+-static inline vls_handle_t
+-ldp_fd_to_vlsh (int fd)
++static inline vcl_session_handle_t
++ldp_fd_to_vclsh (int fd)
+ {
+-  if (fd < ldp->vlsh_bit_val)
+-    return VLS_INVALID_HANDLE;
++  if (fd < (ldp->vcl_bit_val))
++    return INVALID_SESSION_ID;
+-  return (fd - ldp->vlsh_bit_val);
++  return (fd - ldp->vcl_bit_val);
+ }
+ static void
+@@ -168,6 +169,194 @@ ldp_alloc_workers (void)
+   pool_alloc (ldp->workers, LDP_MAX_NWORKERS);
+ }
++static void
++ldp_share_listen_session (vcl_worker_t * parent_wrk,
++                        vcl_worker_t * child_wrk,
++                        vcl_session_t * listen_session)
++{
++/*Find the listen session of parent worker*/
++  if (listen_session->session_index == parent_wrk->listen_session_index)
++    {
++      listen_session->session_state = STATE_LISTEN_NO_MQ;
++      vppcom_session_listen (vcl_session_handle_from_index
++                           (parent_wrk->listen_session_index),
++                           parent_wrk->listen_queue_size);
++    }
++}
++
++void
++ldp_vcl_worker_copy_on_fork (vcl_worker_t * parent_wrk)
++{
++  vcl_worker_t *wrk = vcl_worker_get_current ();
++  vcl_session_t *listen_session;
++  wrk->vpp_event_queues = vec_dup (parent_wrk->vpp_event_queues);
++  wrk->sessions = pool_dup (parent_wrk->sessions);
++  wrk->session_index_by_vpp_handles =
++    hash_dup (parent_wrk->session_index_by_vpp_handles);
++/*Update listen session for child*/
++  pool_foreach (listen_session, wrk->sessions, (
++                                               {
++                                               ldp_share_listen_session
++                                               (parent_wrk, wrk,
++                                                listen_session);}));
++}
++
++static void
++ldp_cleanup_vcl_worker (vcl_worker_t * wrk)
++{
++  vcl_worker_cleanup (wrk, 1 /* notify vpp */ );
++}
++
++static void
++ldp_cleanup_forked_child (vcl_worker_t * wrk, vcl_worker_t * child_wrk)
++{
++  vcl_worker_t *sub_child;
++  int tries = 0;
++
++  if (child_wrk->forked_child != ~0)
++    {
++      sub_child = vcl_worker_get_if_valid (child_wrk->forked_child);
++      if (sub_child)
++      {
++        /* Wait a bit, maybe the process is going away */
++        while (kill (sub_child->current_pid, 0) >= 0 && tries++ < 50)
++          usleep (1e3);
++        if (kill (sub_child->current_pid, 0) < 0)
++          ldp_cleanup_forked_child (child_wrk, sub_child);
++      }
++    }
++  ldp_cleanup_vcl_worker (child_wrk);
++  VDBG (0, "Cleaned up forked child wrk %u", child_wrk->wrk_index);
++  wrk->forked_child = ~0;
++}
++
++static struct sigaction old_sa;
++
++static void
++ldp_intercept_sigchld_handler (int signum, siginfo_t * si, void *uc)
++{
++  vcl_worker_t *wrk, *child_wrk;
++
++  if (vcl_get_worker_index () == ~0)
++    return;
++
++  /*restore sigchld */
++  if (sigaction (SIGCHLD, &old_sa, 0))
++    {
++      VERR ("couldn't restore sigchld");
++      exit (-1);
++    }
++
++  wrk = vcl_worker_get_current ();
++  if (wrk->forked_child == ~0)
++    return;
++
++  child_wrk = vcl_worker_get_if_valid (wrk->forked_child);
++  if (!child_wrk)
++    goto done;
++
++  if (si && si->si_pid != child_wrk->current_pid)
++    {
++      VDBG (0, "unexpected child pid %u", si->si_pid);
++      goto done;
++    }
++  ldp_cleanup_forked_child (wrk, child_wrk);
++
++done:
++  if (old_sa.sa_flags & SA_SIGINFO)
++    {
++      void (*fn) (int, siginfo_t *, void *) = old_sa.sa_sigaction;
++      fn (signum, si, uc);
++    }
++  else
++    {
++      void (*fn) (int) = old_sa.sa_handler;
++      if (fn)
++      fn (signum);
++    }
++}
++
++/*Intercept signal SIGCHLD*/
++static void
++ldp_intercept_sigchld ()
++{
++  struct sigaction sa;
++  clib_memset (&sa, 0, sizeof (sa));
++  /*set SA_SIGINFO to validate sa.sa_sigaction rather than sa.sa_handler */
++  sa.sa_sigaction = ldp_intercept_sigchld_handler;
++  sa.sa_flags = SA_SIGINFO;
++  /*When current process receive the SIGCHLD signal, it would call
++   **ldp_intercept_sigchld_handler.
++   */
++  if (sigaction (SIGCHLD, &sa, &old_sa))
++    {
++      VERR ("couldn't intercept sigchld");
++      exit (-1);
++    }
++}
++
++static void
++ldp_app_pre_fork (void)
++{
++  ldp_intercept_sigchld ();
++  vcl_flush_mq_events ();
++}
++
++static void
++ldp_app_fork_parent_handler (void)
++{
++  vcl_session_t *listen_session;
++  vcl_worker_t *wrk = vcl_worker_get_current ();
++  listen_session = vcl_session_get (wrk, wrk->listen_session_index);
++  listen_session->session_state = STATE_LISTEN_NO_MQ;
++  vcl_send_session_unlisten (wrk, listen_session);
++  vcm->forking = 1;
++  while (vcm->forking)
++    ;
++
++}
++
++static void
++ldp_app_fork_child_handler (void)
++{
++  vcl_worker_t *parent_wrk;
++  int rv, parent_wrk_index;
++  u8 *child_name;
++
++  parent_wrk_index = vcl_get_worker_index ();
++  VDBG (0,
++      "initializing forked child (pid) %u with parent wrk (vcl worker index) %u",
++      getpid (), parent_wrk_index);
++
++/*Allocate vcl worker for child*/
++  vcl_set_worker_index (~0);
++  if (!vcl_worker_alloc_and_init ())
++    VERR ("couldn't allocate new worker for child process %u", getpid ());
++
++/*Attach to binary api*/
++  child_name = format (0, "%v-child-%u%c", vcm->app_name, getpid (), 0);
++  vcl_cleanup_bapi ();
++  vppcom_api_hookup ();
++  vcm->app_state = STATE_APP_START;
++  rv = vppcom_connect_to_vpp ((char *) child_name);
++  vec_free (child_name);
++  if (rv)
++    {
++      VERR ("couldn't connect to VPP!");
++      return;
++    }
++
++/*
++**Register new allocated vcl worker with VPP
++*/
++  vcl_worker_register_with_vpp ();
++  parent_wrk = vcl_worker_get (parent_wrk_index);
++  ldp_vcl_worker_copy_on_fork (parent_wrk);
++  parent_wrk->forked_child = vcl_get_worker_index ();
++  VDBG (0, "forked child main worker initialized");
++  vcm->forking = 0;
++}
++
+ static inline int
+ ldp_init (void)
+ {
+@@ -179,7 +368,9 @@ ldp_init (void)
+   ldp->init = 1;
+   ldp->vcl_needs_real_epoll = 1;
+-  rv = vls_app_create (ldp_get_app_name ());
++  rv = vppcom_app_create (ldp_get_app_name ());
++  pthread_atfork (ldp_app_pre_fork, ldp_app_fork_parent_handler,
++                ldp_app_fork_child_handler);
+   if (rv != VPPCOM_OK)
+     {
+       ldp->vcl_needs_real_epoll = 0;
+@@ -226,43 +417,43 @@ ldp_init (void)
+       {
+         LDBG (0, "WARNING: Invalid LDP sid bit specified in the env var "
+               LDP_ENV_SID_BIT " (%s)! sid bit value %d (0x%x)", env_var_str,
+-              ldp->vlsh_bit_val, ldp->vlsh_bit_val);
++              ldp->vcl_bit_val, ldp->vcl_bit_val);
+       }
+       else if (sb < LDP_SID_BIT_MIN)
+       {
+-        ldp->vlsh_bit_val = (1 << LDP_SID_BIT_MIN);
+-        ldp->vlsh_bit_mask = ldp->vlsh_bit_val - 1;
++        ldp->vcl_bit_val = (1 << LDP_SID_BIT_MIN);
++        ldp->vcl_bit_mask = ldp->vcl_bit_val - 1;
+         LDBG (0, "WARNING: LDP sid bit (%u) specified in the env var "
+               LDP_ENV_SID_BIT " (%s) is too small. Using LDP_SID_BIT_MIN"
+               " (%d)! sid bit value %d (0x%x)", sb, env_var_str,
+-              LDP_SID_BIT_MIN, ldp->vlsh_bit_val, ldp->vlsh_bit_val);
++              LDP_SID_BIT_MIN, ldp->vcl_bit_val, ldp->vcl_bit_val);
+       }
+       else if (sb > LDP_SID_BIT_MAX)
+       {
+-        ldp->vlsh_bit_val = (1 << LDP_SID_BIT_MAX);
+-        ldp->vlsh_bit_mask = ldp->vlsh_bit_val - 1;
++        ldp->vcl_bit_val = (1 << LDP_SID_BIT_MAX);
++        ldp->vcl_bit_mask = ldp->vcl_bit_val - 1;
+         LDBG (0, "WARNING: LDP sid bit (%u) specified in the env var "
+               LDP_ENV_SID_BIT " (%s) is too big. Using LDP_SID_BIT_MAX"
+               " (%d)! sid bit value %d (0x%x)", sb, env_var_str,
+-              LDP_SID_BIT_MAX, ldp->vlsh_bit_val, ldp->vlsh_bit_val);
++              LDP_SID_BIT_MAX, ldp->vcl_bit_val, ldp->vcl_bit_val);
+       }
+       else
+       {
+-        ldp->vlsh_bit_val = (1 << sb);
+-        ldp->vlsh_bit_mask = ldp->vlsh_bit_val - 1;
++        ldp->vcl_bit_val = (1 << sb);
++        ldp->vcl_bit_mask = ldp->vcl_bit_val - 1;
+         LDBG (0, "configured LDP sid bit (%u) from "
+               LDP_ENV_SID_BIT "!  sid bit value %d (0x%x)", sb,
+-              ldp->vlsh_bit_val, ldp->vlsh_bit_val);
++              ldp->vcl_bit_val, ldp->vcl_bit_val);
+       }
+       /* Make sure there are enough bits in the fd set for vcl sessions */
+-      if (ldp->vlsh_bit_val > FD_SETSIZE / 2)
++      if (ldp->vcl_bit_val > FD_SETSIZE / 2)
+       {
+-        LDBG (0, "ERROR: LDP vlsh bit value %d > FD_SETSIZE/2 %d!",
+-              ldp->vlsh_bit_val, FD_SETSIZE / 2);
++        LDBG (0, "ERROR: LDP vclsh bit value %d > FD_SETSIZE/2 %d!",
++              ldp->vcl_bit_val, FD_SETSIZE / 2);
+         ldp->init = 0;
+         return -1;
+       }
+@@ -287,16 +478,16 @@ ldp_init (void)
+ int
+ close (int fd)
+ {
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   int rv, epfd;
+   if ((errno = -ldp_init ()))
+     return -1;
+-  vlsh = ldp_fd_to_vlsh (fd);
+-  if (vlsh != VLS_INVALID_HANDLE)
++  vclsh = ldp_fd_to_vclsh (fd);
++  if (vclsh != INVALID_SESSION_ID)
+     {
+-      epfd = vls_attr (vlsh, VPPCOM_ATTR_GET_LIBC_EPFD, 0, 0);
++      epfd = vppcom_session_attr (vclsh, VPPCOM_ATTR_GET_LIBC_EPFD, 0, 0);
+       if (epfd > 0)
+       {
+         LDBG (0, "fd %d: calling libc_close: epfd %u", fd, epfd);
+@@ -307,7 +498,8 @@ close (int fd)
+             u32 size = sizeof (epfd);
+             epfd = 0;
+-            (void) vls_attr (vlsh, VPPCOM_ATTR_SET_LIBC_EPFD, &epfd, &size);
++            (void) vppcom_session_attr (vclsh, VPPCOM_ATTR_SET_LIBC_EPFD,
++                                        &epfd, &size);
+           }
+       }
+       else if (PREDICT_FALSE (epfd < 0))
+@@ -317,9 +509,9 @@ close (int fd)
+         goto done;
+       }
+-      LDBG (0, "fd %d: calling vls_close: vlsh %u", fd, vlsh);
++      LDBG (0, "fd %d: calling vppcom_session_close: vclsh %u", fd, vclsh);
+-      rv = vls_close (vlsh);
++      rv = vppcom_session_close (vclsh);
+       if (rv != VPPCOM_OK)
+       {
+         errno = -rv;
+@@ -339,16 +531,16 @@ done:
+ ssize_t
+ read (int fd, void *buf, size_t nbytes)
+ {
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   ssize_t size;
+   if ((errno = -ldp_init ()))
+     return -1;
+-  vlsh = ldp_fd_to_vlsh (fd);
+-  if (vlsh != VLS_INVALID_HANDLE)
++  vclsh = ldp_fd_to_vclsh (fd);
++  if (vclsh != INVALID_SESSION_ID)
+     {
+-      size = vls_read (vlsh, buf, nbytes);
++      size = vppcom_session_read (vclsh, buf, nbytes);
+       if (size < 0)
+       {
+         errno = -size;
+@@ -367,20 +559,21 @@ ssize_t
+ readv (int fd, const struct iovec * iov, int iovcnt)
+ {
+   int rv = 0, i, total = 0;
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   ssize_t size = 0;
+   if ((errno = -ldp_init ()))
+     return -1;
+-  vlsh = ldp_fd_to_vlsh (fd);
+-  if (vlsh != VLS_INVALID_HANDLE)
++  vclsh = ldp_fd_to_vclsh (fd);
++  if (vclsh != INVALID_SESSION_ID)
+     {
+       do
+       {
+         for (i = 0; i < iovcnt; ++i)
+           {
+-            rv = vls_read (vlsh, iov[i].iov_base, iov[i].iov_len);
++            rv =
++              vppcom_session_read (vclsh, iov[i].iov_base, iov[i].iov_len);
+             if (rv < 0)
+               break;
+             else
+@@ -412,16 +605,16 @@ readv (int fd, const struct iovec * iov, int iovcnt)
+ ssize_t
+ write (int fd, const void *buf, size_t nbytes)
+ {
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   ssize_t size = 0;
+   if ((errno = -ldp_init ()))
+     return -1;
+-  vlsh = ldp_fd_to_vlsh (fd);
+-  if (vlsh != VLS_INVALID_HANDLE)
++  vclsh = ldp_fd_to_vclsh (fd);
++  if (vclsh != INVALID_SESSION_ID)
+     {
+-      size = vls_write_msg (vlsh, (void *) buf, nbytes);
++      size = vppcom_session_write_msg (vclsh, (void *) buf, nbytes);
+       if (size < 0)
+       {
+         errno = -size;
+@@ -440,20 +633,22 @@ ssize_t
+ writev (int fd, const struct iovec * iov, int iovcnt)
+ {
+   ssize_t size = 0, total = 0;
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   int i, rv = 0;
+   if ((errno = -ldp_init ()))
+     return -1;
+-  vlsh = ldp_fd_to_vlsh (fd);
+-  if (vlsh != VLS_INVALID_HANDLE)
++  vclsh = ldp_fd_to_vclsh (fd);
++  if (vclsh != INVALID_SESSION_ID)
+     {
+       do
+       {
+         for (i = 0; i < iovcnt; ++i)
+           {
+-            rv = vls_write_msg (vlsh, iov[i].iov_base, iov[i].iov_len);
++            rv =
++              vppcom_session_write_msg (vclsh, iov[i].iov_base,
++                                        iov[i].iov_len);
+             if (rv < 0)
+               break;
+             else
+@@ -485,7 +680,7 @@ writev (int fd, const struct iovec * iov, int iovcnt)
+ int
+ fcntl (int fd, int cmd, ...)
+ {
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   int rv = 0;
+   va_list ap;
+@@ -494,9 +689,9 @@ fcntl (int fd, int cmd, ...)
+   va_start (ap, cmd);
+-  vlsh = ldp_fd_to_vlsh (fd);
+-  LDBG (0, "fd %u vlsh %d, cmd %u", fd, vlsh, cmd);
+-  if (vlsh != VLS_INVALID_HANDLE)
++  vclsh = ldp_fd_to_vclsh (fd);
++  LDBG (0, "fd %u vclsh %d, cmd %u", fd, vclsh, cmd);
++  if (vclsh != INVALID_SESSION_ID)
+     {
+       int flags = va_arg (ap, int);
+       u32 size;
+@@ -506,11 +701,13 @@ fcntl (int fd, int cmd, ...)
+       switch (cmd)
+       {
+       case F_SETFL:
+-        rv = vls_attr (vlsh, VPPCOM_ATTR_SET_FLAGS, &flags, &size);
++        rv =
++          vppcom_session_attr (vclsh, VPPCOM_ATTR_SET_FLAGS, &flags, &size);
+         break;
+       case F_GETFL:
+-        rv = vls_attr (vlsh, VPPCOM_ATTR_GET_FLAGS, &flags, &size);
++        rv =
++          vppcom_session_attr (vclsh, VPPCOM_ATTR_GET_FLAGS, &flags, &size);
+         if (rv == VPPCOM_OK)
+           rv = flags;
+         break;
+@@ -542,7 +739,7 @@ fcntl (int fd, int cmd, ...)
+ int
+ ioctl (int fd, unsigned long int cmd, ...)
+ {
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   va_list ap;
+   int rv;
+@@ -551,13 +748,13 @@ ioctl (int fd, unsigned long int cmd, ...)
+   va_start (ap, cmd);
+-  vlsh = ldp_fd_to_vlsh (fd);
+-  if (vlsh != VLS_INVALID_HANDLE)
++  vclsh = ldp_fd_to_vclsh (fd);
++  if (vclsh != INVALID_SESSION_ID)
+     {
+       switch (cmd)
+       {
+       case FIONREAD:
+-        rv = vls_attr (vlsh, VPPCOM_ATTR_GET_NREAD, 0, 0);
++        rv = vppcom_session_attr (vclsh, VPPCOM_ATTR_GET_NREAD, 0, 0);
+         break;
+       case FIONBIO:
+@@ -569,7 +766,9 @@ ioctl (int fd, unsigned long int cmd, ...)
+            *      non-blocking, the flags should be read here and merged
+            *      with O_NONBLOCK.
+            */
+-          rv = vls_attr (vlsh, VPPCOM_ATTR_SET_FLAGS, &flags, &size);
++          rv =
++            vppcom_session_attr (vclsh, VPPCOM_ATTR_SET_FLAGS, &flags,
++                                 &size);
+         }
+         break;
+@@ -599,7 +798,7 @@ ldp_select_init_maps (fd_set * __restrict original,
+                     u32 n_bytes, uword * si_bits, uword * libc_bits)
+ {
+   uword si_bits_set, libc_bits_set;
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   int fd;
+   clib_bitmap_validate (*vclb, minbits);
+@@ -612,11 +811,11 @@ ldp_select_init_maps (fd_set * __restrict original,
+   clib_bitmap_foreach (fd, *resultb, ({
+     if (fd > nfds)
+       break;
+-    vlsh = ldp_fd_to_vlsh (fd);
+-    if (vlsh == VLS_INVALID_HANDLE)
++    vclsh = ldp_fd_to_vclsh (fd);
++    if (vclsh == INVALID_SESSION_ID)
+       clib_bitmap_set_no_check (*libcb, fd, 1);
+     else
+-      clib_bitmap_set_no_check (*vclb, vlsh_to_session_index (vlsh), 1);
++      clib_bitmap_set_no_check (*vclb, vppcom_session_index (vclsh), 1);
+   }));
+   /* *INDENT-ON* */
+@@ -630,7 +829,7 @@ ldp_select_init_maps (fd_set * __restrict original,
+ always_inline int
+ ldp_select_vcl_map_to_libc (clib_bitmap_t * vclb, fd_set * __restrict libcb)
+ {
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   uword si;
+   int fd;
+@@ -639,8 +838,8 @@ ldp_select_vcl_map_to_libc (clib_bitmap_t * vclb, fd_set * __restrict libcb)
+   /* *INDENT-OFF* */
+   clib_bitmap_foreach (si, vclb, ({
+-    vlsh = vls_session_index_to_vlsh (si);
+-    fd = ldp_vlsh_to_fd (vlsh);
++    vclsh = vcl_session_handle_from_index (si);
++    fd = ldp_vclsh_to_fd (vclsh);
+     if (PREDICT_FALSE (fd < 0))
+       {
+         errno = EBADFD;
+@@ -713,7 +912,7 @@ ldp_pselect (int nfds, fd_set * __restrict readfds,
+   else
+     time_out = -1;
+-  if (nfds <= ldp->vlsh_bit_val)
++  if (nfds <= ldp->vcl_bit_val)
+     {
+       rv = libc_pselect (nfds, readfds, writefds, exceptfds,
+                        timeout, sigmask);
+@@ -763,9 +962,10 @@ ldp_pselect (int nfds, fd_set * __restrict readfds,
+                             vec_len (ldpw->ex_bitmap) *
+                             sizeof (clib_bitmap_t));
+-        rv = vls_select (si_bits, readfds ? ldpw->rd_bitmap : NULL,
+-                         writefds ? ldpw->wr_bitmap : NULL,
+-                         exceptfds ? ldpw->ex_bitmap : NULL, vcl_timeout);
++        rv = vppcom_select (si_bits, readfds ? ldpw->rd_bitmap : NULL,
++                            writefds ? ldpw->wr_bitmap : NULL,
++                            exceptfds ? ldpw->ex_bitmap : NULL,
++                            vcl_timeout);
+         if (rv < 0)
+           {
+             errno = -rv;
+@@ -877,7 +1077,7 @@ pselect (int nfds, fd_set * __restrict readfds,
+ /* If transparent TLS mode is turned on, then ldp will load key and cert.
+  */
+ static int
+-load_tls_cert (vls_handle_t vlsh)
++load_tls_cert (vcl_session_handle_t vclsh)
+ {
+   char *env_var_str = getenv (LDP_ENV_TLS_CERT);
+   char inbuf[4096];
+@@ -895,7 +1095,7 @@ load_tls_cert (vls_handle_t vlsh)
+       }
+       cert_size = fread (inbuf, sizeof (char), sizeof (inbuf), fp);
+       tls_cert = inbuf;
+-      vppcom_session_tls_add_cert (vlsh_to_session_index (vlsh), tls_cert,
++      vppcom_session_tls_add_cert (vppcom_session_index (vclsh), tls_cert,
+                                  cert_size);
+       fclose (fp);
+     }
+@@ -909,7 +1109,7 @@ load_tls_cert (vls_handle_t vlsh)
+ }
+ static int
+-load_tls_key (vls_handle_t vlsh)
++load_tls_key (vcl_session_handle_t vclsh)
+ {
+   char *env_var_str = getenv (LDP_ENV_TLS_KEY);
+   char inbuf[4096];
+@@ -927,7 +1127,7 @@ load_tls_key (vls_handle_t vlsh)
+       }
+       key_size = fread (inbuf, sizeof (char), sizeof (inbuf), fp);
+       tls_key = inbuf;
+-      vppcom_session_tls_add_key (vlsh_to_session_index (vlsh), tls_key,
++      vppcom_session_tls_add_key (vppcom_session_index (vclsh), tls_key,
+                                 key_size);
+       fclose (fp);
+     }
+@@ -944,7 +1144,7 @@ socket (int domain, int type, int protocol)
+ {
+   int rv, sock_type = type & ~(SOCK_CLOEXEC | SOCK_NONBLOCK);
+   u8 is_nonblocking = type & SOCK_NONBLOCK ? 1 : 0;
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   if ((errno = -ldp_init ()))
+     return -1;
+@@ -961,25 +1161,26 @@ socket (int domain, int type, int protocol)
+       proto = ((sock_type == SOCK_DGRAM) ?
+                VPPCOM_PROTO_UDP : VPPCOM_PROTO_TCP);
+-      LDBG (0, "calling vls_create: proto %u (%s), is_nonblocking %u",
++      LDBG (0,
++          "calling vppcom_session_create: proto %u (%s), is_nonblocking %u",
+           proto, vppcom_proto_str (proto), is_nonblocking);
+-      vlsh = vls_create (proto, is_nonblocking);
+-      if (vlsh < 0)
++      vclsh = vppcom_session_create (proto, is_nonblocking);
++      if (vclsh < 0)
+       {
+-        errno = -vlsh;
++        errno = -vclsh;
+         rv = -1;
+       }
+       else
+       {
+         if (ldp->transparent_tls)
+           {
+-            if (load_tls_cert (vlsh) < 0 || load_tls_key (vlsh) < 0)
++            if (load_tls_cert (vclsh) < 0 || load_tls_key (vclsh) < 0)
+               {
+                 return -1;
+               }
+           }
+-        rv = ldp_vlsh_to_fd (vlsh);
++        rv = ldp_vclsh_to_fd (vclsh);
+       }
+     }
+   else
+@@ -1025,14 +1226,14 @@ socketpair (int domain, int type, int protocol, int fds[2])
+ int
+ bind (int fd, __CONST_SOCKADDR_ARG addr, socklen_t len)
+ {
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   int rv;
+   if ((errno = -ldp_init ()))
+     return -1;
+-  vlsh = ldp_fd_to_vlsh (fd);
+-  if (vlsh != VLS_INVALID_HANDLE)
++  vclsh = ldp_fd_to_vclsh (fd);
++  if (vclsh != INVALID_SESSION_ID)
+     {
+       vppcom_endpt_t ep;
+@@ -1041,8 +1242,8 @@ bind (int fd, __CONST_SOCKADDR_ARG addr, socklen_t len)
+       case AF_INET:
+         if (len != sizeof (struct sockaddr_in))
+           {
+-            LDBG (0, "ERROR: fd %d: vlsh %u: Invalid AF_INET addr len %u!",
+-                  fd, vlsh, len);
++            LDBG (0, "ERROR: fd %d: vclsh %u: Invalid AF_INET addr len %u!",
++                  fd, vclsh, len);
+             errno = EINVAL;
+             rv = -1;
+             goto done;
+@@ -1055,8 +1256,9 @@ bind (int fd, __CONST_SOCKADDR_ARG addr, socklen_t len)
+       case AF_INET6:
+         if (len != sizeof (struct sockaddr_in6))
+           {
+-            LDBG (0, "ERROR: fd %d: vlsh %u: Invalid AF_INET6 addr len %u!",
+-                  fd, vlsh, len);
++            LDBG (0,
++                  "ERROR: fd %d: vclsh %u: Invalid AF_INET6 addr len %u!",
++                  fd, vclsh, len);
+             errno = EINVAL;
+             rv = -1;
+             goto done;
+@@ -1067,16 +1269,17 @@ bind (int fd, __CONST_SOCKADDR_ARG addr, socklen_t len)
+         break;
+       default:
+-        LDBG (0, "ERROR: fd %d: vlsh %u: Unsupported address family %u!",
+-              fd, vlsh, addr->sa_family);
++        LDBG (0, "ERROR: fd %d: vclsh %u: Unsupported address family %u!",
++              fd, vclsh, addr->sa_family);
+         errno = EAFNOSUPPORT;
+         rv = -1;
+         goto done;
+       }
+-      LDBG (0, "fd %d: calling vls_bind: vlsh %u, addr %p, len %u", fd, vlsh,
+-          addr, len);
++      LDBG (0,
++          "fd %d: calling vppcom_session_bind: vclsh %u, addr %p, len %u",
++          fd, vclsh, addr, len);
+-      rv = vls_bind (vlsh, &ep);
++      rv = vppcom_session_bind (vclsh, &ep);
+       if (rv != VPPCOM_OK)
+       {
+         errno = -rv;
+@@ -1144,14 +1347,14 @@ ldp_copy_ep_to_sockaddr (__SOCKADDR_ARG addr, socklen_t * __restrict len,
+ int
+ getsockname (int fd, __SOCKADDR_ARG addr, socklen_t * __restrict len)
+ {
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   int rv;
+   if ((errno = -ldp_init ()))
+     return -1;
+-  vlsh = ldp_fd_to_vlsh (fd);
+-  if (vlsh != VLS_INVALID_HANDLE)
++  vclsh = ldp_fd_to_vclsh (fd);
++  if (vclsh != INVALID_SESSION_ID)
+     {
+       vppcom_endpt_t ep;
+       u8 addr_buf[sizeof (struct in6_addr)];
+@@ -1159,7 +1362,7 @@ getsockname (int fd, __SOCKADDR_ARG addr, socklen_t * __restrict len)
+       ep.ip = addr_buf;
+-      rv = vls_attr (vlsh, VPPCOM_ATTR_GET_LCL_ADDR, &ep, &size);
++      rv = vppcom_session_attr (vclsh, VPPCOM_ATTR_GET_LCL_ADDR, &ep, &size);
+       if (rv != VPPCOM_OK)
+       {
+         errno = -rv;
+@@ -1186,7 +1389,7 @@ getsockname (int fd, __SOCKADDR_ARG addr, socklen_t * __restrict len)
+ int
+ connect (int fd, __CONST_SOCKADDR_ARG addr, socklen_t len)
+ {
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   int rv;
+   if ((errno = -ldp_init ()))
+@@ -1200,8 +1403,8 @@ connect (int fd, __CONST_SOCKADDR_ARG addr, socklen_t len)
+       goto done;
+     }
+-  vlsh = ldp_fd_to_vlsh (fd);
+-  if (vlsh != VLS_INVALID_HANDLE)
++  vclsh = ldp_fd_to_vclsh (fd);
++  if (vclsh != INVALID_SESSION_ID)
+     {
+       vppcom_endpt_t ep;
+@@ -1210,8 +1413,8 @@ connect (int fd, __CONST_SOCKADDR_ARG addr, socklen_t len)
+       case AF_INET:
+         if (len != sizeof (struct sockaddr_in))
+           {
+-            LDBG (0, "fd %d: ERROR vlsh %u: Invalid AF_INET addr len %u!",
+-                  fd, vlsh, len);
++            LDBG (0, "fd %d: ERROR vclsh %u: Invalid AF_INET addr len %u!",
++                  fd, vclsh, len);
+             errno = EINVAL;
+             rv = -1;
+             goto done;
+@@ -1224,8 +1427,8 @@ connect (int fd, __CONST_SOCKADDR_ARG addr, socklen_t len)
+       case AF_INET6:
+         if (len != sizeof (struct sockaddr_in6))
+           {
+-            LDBG (0, "fd %d: ERROR vlsh %u: Invalid AF_INET6 addr len %u!",
+-                  fd, vlsh, len);
++            LDBG (0, "fd %d: ERROR vclsh %u: Invalid AF_INET6 addr len %u!",
++                  fd, vclsh, len);
+             errno = EINVAL;
+             rv = -1;
+             goto done;
+@@ -1236,16 +1439,17 @@ connect (int fd, __CONST_SOCKADDR_ARG addr, socklen_t len)
+         break;
+       default:
+-        LDBG (0, "fd %d: ERROR vlsh %u: Unsupported address family %u!",
+-              fd, vlsh, addr->sa_family);
++        LDBG (0, "fd %d: ERROR vclsh %u: Unsupported address family %u!",
++              fd, vclsh, addr->sa_family);
+         errno = EAFNOSUPPORT;
+         rv = -1;
+         goto done;
+       }
+-      LDBG (0, "fd %d: calling vls_connect(): vlsh %u addr %p len %u", fd,
+-          vlsh, addr, len);
++      LDBG (0,
++          "fd %d: calling vppcom_session_connect(): vclsh %u addr %p len %u",
++          fd, vclsh, addr, len);
+-      rv = vls_connect (vlsh, &ep);
++      rv = vppcom_session_connect (vclsh, &ep);
+       if (rv != VPPCOM_OK)
+       {
+         errno = -rv;
+@@ -1268,21 +1472,21 @@ done:
+ int
+ getpeername (int fd, __SOCKADDR_ARG addr, socklen_t * __restrict len)
+ {
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   int rv;
+   if ((errno = -ldp_init ()))
+     return -1;
+-  vlsh = ldp_fd_to_vlsh (fd);
+-  if (vlsh != VLS_INVALID_HANDLE)
++  vclsh = ldp_fd_to_vclsh (fd);
++  if (vclsh != INVALID_SESSION_ID)
+     {
+       vppcom_endpt_t ep;
+       u8 addr_buf[sizeof (struct in6_addr)];
+       u32 size = sizeof (ep);
+       ep.ip = addr_buf;
+-      rv = vls_attr (vlsh, VPPCOM_ATTR_GET_PEER_ADDR, &ep, &size);
++      rv = vppcom_session_attr (vclsh, VPPCOM_ATTR_GET_PEER_ADDR, &ep, &size);
+       if (rv != VPPCOM_OK)
+       {
+         errno = -rv;
+@@ -1309,15 +1513,15 @@ getpeername (int fd, __SOCKADDR_ARG addr, socklen_t * __restrict len)
+ ssize_t
+ send (int fd, const void *buf, size_t n, int flags)
+ {
+-  vls_handle_t vlsh = ldp_fd_to_vlsh (fd);
++  vcl_session_handle_t vclsh = ldp_fd_to_vclsh (fd);
+   ssize_t size;
+   if ((errno = -ldp_init ()))
+     return -1;
+-  if (vlsh != VLS_INVALID_HANDLE)
++  if (vclsh != INVALID_SESSION_ID)
+     {
+-      size = vls_sendto (vlsh, (void *) buf, n, flags, NULL);
++      size = vppcom_session_sendto (vclsh, (void *) buf, n, flags, NULL);
+       if (size < VPPCOM_OK)
+       {
+         errno = -size;
+@@ -1336,14 +1540,14 @@ ssize_t
+ sendfile (int out_fd, int in_fd, off_t * offset, size_t len)
+ {
+   ldp_worker_ctx_t *ldpw = ldp_worker_get_current ();
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   ssize_t size = 0;
+   if ((errno = -ldp_init ()))
+     return -1;
+-  vlsh = ldp_fd_to_vlsh (out_fd);
+-  if (vlsh != VLS_INVALID_HANDLE)
++  vclsh = ldp_fd_to_vclsh (out_fd);
++  if (vclsh != INVALID_SESSION_ID)
+     {
+       int rv;
+       ssize_t results = 0;
+@@ -1353,11 +1557,14 @@ sendfile (int out_fd, int in_fd, off_t * offset, size_t len)
+       u8 eagain = 0;
+       u32 flags, flags_len = sizeof (flags);
+-      rv = vls_attr (vlsh, VPPCOM_ATTR_GET_FLAGS, &flags, &flags_len);
++      rv =
++      vppcom_session_attr (vclsh, VPPCOM_ATTR_GET_FLAGS, &flags,
++                           &flags_len);
+       if (PREDICT_FALSE (rv != VPPCOM_OK))
+       {
+-        LDBG (0, "ERROR: out fd %d: vls_attr: vlsh %u, returned %d (%s)!",
+-              out_fd, vlsh, rv, vppcom_retval_str (rv));
++        LDBG (0,
++              "ERROR: out fd %d: vppcom_session_attr: vclsh %u, returned %d (%s)!",
++              out_fd, vclsh, rv, vppcom_retval_str (rv));
+         vec_reset_length (ldpw->io_buffer);
+         errno = -rv;
+@@ -1379,11 +1586,12 @@ sendfile (int out_fd, int in_fd, off_t * offset, size_t len)
+       do
+       {
+-        size = vls_attr (vlsh, VPPCOM_ATTR_GET_NWRITE, 0, 0);
++        size = vppcom_session_attr (vclsh, VPPCOM_ATTR_GET_NWRITE, 0, 0);
+         if (size < 0)
+           {
+-            LDBG (0, "ERROR: fd %d: vls_attr: vlsh %u returned %d (%s)!",
+-                  out_fd, vlsh, size, vppcom_retval_str (size));
++            LDBG (0,
++                  "ERROR: fd %d: voocom_session_attr: vclsh %u returned %d (%s)!",
++                  out_fd, vclsh, size, vppcom_retval_str (size));
+             vec_reset_length (ldpw->io_buffer);
+             errno = -size;
+             size = -1;
+@@ -1416,7 +1624,7 @@ sendfile (int out_fd, int in_fd, off_t * offset, size_t len)
+             goto update_offset;
+           }
+-        size = vls_write (vlsh, ldpw->io_buffer, nbytes);
++        size = vppcom_session_write (vclsh, ldpw->io_buffer, nbytes);
+         if (size < 0)
+           {
+             if (size == VPPCOM_EAGAIN)
+@@ -1486,16 +1694,16 @@ sendfile64 (int out_fd, int in_fd, off_t * offset, size_t len)
+ ssize_t
+ recv (int fd, void *buf, size_t n, int flags)
+ {
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   ssize_t size;
+   if ((errno = -ldp_init ()))
+     return -1;
+-  vlsh = ldp_fd_to_vlsh (fd);
+-  if (vlsh != VLS_INVALID_HANDLE)
++  vclsh = ldp_fd_to_vclsh (fd);
++  if (vclsh != INVALID_SESSION_ID)
+     {
+-      size = vls_recvfrom (vlsh, buf, n, flags, NULL);
++      size = vppcom_session_recvfrom (vclsh, buf, n, flags, NULL);
+       if (size < 0)
+       errno = -size;
+     }
+@@ -1511,14 +1719,14 @@ ssize_t
+ sendto (int fd, const void *buf, size_t n, int flags,
+       __CONST_SOCKADDR_ARG addr, socklen_t addr_len)
+ {
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   ssize_t size;
+   if ((errno = -ldp_init ()))
+     return -1;
+-  vlsh = ldp_fd_to_vlsh (fd);
+-  if (vlsh != INVALID_SESSION_ID)
++  vclsh = ldp_fd_to_vclsh (fd);
++  if (vclsh != INVALID_SESSION_ID)
+     {
+       vppcom_endpt_t *ep = 0;
+       vppcom_endpt_t _ep;
+@@ -1551,7 +1759,7 @@ sendto (int fd, const void *buf, size_t n, int flags,
+           }
+       }
+-      size = vls_sendto (vlsh, (void *) buf, n, flags, ep);
++      size = vppcom_session_sendto (vclsh, (void *) buf, n, flags, ep);
+       if (size < 0)
+       {
+         errno = -size;
+@@ -1571,14 +1779,14 @@ ssize_t
+ recvfrom (int fd, void *__restrict buf, size_t n, int flags,
+         __SOCKADDR_ARG addr, socklen_t * __restrict addr_len)
+ {
+-  vls_handle_t sid;
++  vcl_session_handle_t sid;
+   ssize_t size, rv;
+   if ((errno = -ldp_init ()))
+     return -1;
+-  sid = ldp_fd_to_vlsh (fd);
+-  if (sid != VLS_INVALID_HANDLE)
++  sid = ldp_fd_to_vclsh (fd);
++  if (sid != INVALID_SESSION_ID)
+     {
+       vppcom_endpt_t ep;
+       u8 src_addr[sizeof (struct sockaddr_in6)];
+@@ -1586,7 +1794,7 @@ recvfrom (int fd, void *__restrict buf, size_t n, int flags,
+       if (addr)
+       {
+         ep.ip = src_addr;
+-        size = vls_recvfrom (sid, buf, n, flags, &ep);
++        size = vppcom_session_recvfrom (sid, buf, n, flags, &ep);
+         if (size > 0)
+           {
+@@ -1596,7 +1804,7 @@ recvfrom (int fd, void *__restrict buf, size_t n, int flags,
+           }
+       }
+       else
+-      size = vls_recvfrom (sid, buf, n, flags, NULL);
++      size = vppcom_session_recvfrom (sid, buf, n, flags, NULL);
+       if (size < 0)
+       {
+@@ -1615,14 +1823,14 @@ recvfrom (int fd, void *__restrict buf, size_t n, int flags,
+ ssize_t
+ sendmsg (int fd, const struct msghdr * message, int flags)
+ {
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   ssize_t size;
+   if ((errno = -ldp_init ()))
+     return -1;
+-  vlsh = ldp_fd_to_vlsh (fd);
+-  if (vlsh != VLS_INVALID_HANDLE)
++  vclsh = ldp_fd_to_vclsh (fd);
++  if (vclsh != INVALID_SESSION_ID)
+     {
+       LDBG (0, "LDP-TBD");
+       errno = ENOSYS;
+@@ -1642,7 +1850,7 @@ sendmmsg (int fd, struct mmsghdr *vmessages, unsigned int vlen, int flags)
+ {
+   ssize_t size;
+   const char *func_str;
+-  u32 sh = ldp_fd_to_vlsh (fd);
++  u32 sh = ldp_fd_to_vclsh (fd);
+   if ((errno = -ldp_init ()))
+     return -1;
+@@ -1687,14 +1895,14 @@ sendmmsg (int fd, struct mmsghdr *vmessages, unsigned int vlen, int flags)
+ ssize_t
+ recvmsg (int fd, struct msghdr * message, int flags)
+ {
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   ssize_t size;
+   if ((errno = -ldp_init ()))
+     return -1;
+-  vlsh = ldp_fd_to_vlsh (fd);
+-  if (vlsh != VLS_INVALID_HANDLE)
++  vclsh = ldp_fd_to_vclsh (fd);
++  if (vclsh != INVALID_SESSION_ID)
+     {
+       LDBG (0, "LDP-TBD");
+       errno = ENOSYS;
+@@ -1715,7 +1923,7 @@ recvmmsg (int fd, struct mmsghdr *vmessages,
+ {
+   ssize_t size;
+   const char *func_str;
+-  u32 sh = ldp_fd_to_vlsh (fd);
++  u32 sh = ldp_fd_to_vclsh (fd);
+   if ((errno = -ldp_init ()))
+     return -1;
+@@ -1762,14 +1970,14 @@ int
+ getsockopt (int fd, int level, int optname,
+           void *__restrict optval, socklen_t * __restrict optlen)
+ {
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   int rv;
+   if ((errno = -ldp_init ()))
+     return -1;
+-  vlsh = ldp_fd_to_vlsh (fd);
+-  if (vlsh != VLS_INVALID_HANDLE)
++  vclsh = ldp_fd_to_vclsh (fd);
++  if (vclsh != INVALID_SESSION_ID)
+     {
+       rv = -EOPNOTSUPP;
+@@ -1779,26 +1987,26 @@ getsockopt (int fd, int level, int optname,
+         switch (optname)
+           {
+           case TCP_NODELAY:
+-            rv = vls_attr (vlsh, VPPCOM_ATTR_GET_TCP_NODELAY,
+-                           optval, optlen);
++            rv = vppcom_session_attr (vclsh, VPPCOM_ATTR_GET_TCP_NODELAY,
++                                      optval, optlen);
+             break;
+           case TCP_MAXSEG:
+-            rv = vls_attr (vlsh, VPPCOM_ATTR_GET_TCP_USER_MSS,
+-                           optval, optlen);
++            rv = vppcom_session_attr (vclsh, VPPCOM_ATTR_GET_TCP_USER_MSS,
++                                      optval, optlen);
+             break;
+           case TCP_KEEPIDLE:
+-            rv = vls_attr (vlsh, VPPCOM_ATTR_GET_TCP_KEEPIDLE,
+-                           optval, optlen);
++            rv = vppcom_session_attr (vclsh, VPPCOM_ATTR_GET_TCP_KEEPIDLE,
++                                      optval, optlen);
+             break;
+           case TCP_KEEPINTVL:
+-            rv = vls_attr (vlsh, VPPCOM_ATTR_GET_TCP_KEEPINTVL,
+-                           optval, optlen);
++            rv = vppcom_session_attr (vclsh, VPPCOM_ATTR_GET_TCP_KEEPINTVL,
++                                      optval, optlen);
+             break;
+           case TCP_INFO:
+             if (optval && optlen && (*optlen == sizeof (struct tcp_info)))
+               {
+-                LDBG (1, "fd %d: vlsh %u SOL_TCP, TCP_INFO, optval %p, "
+-                      "optlen %d: #LDP-NOP#", fd, vlsh, optval, *optlen);
++                LDBG (1, "fd %d: vclsh %u SOL_TCP, TCP_INFO, optval %p, "
++                      "optlen %d: #LDP-NOP#", fd, vclsh, optval, *optlen);
+                 memset (optval, 0, *optlen);
+                 rv = VPPCOM_OK;
+               }
+@@ -1812,7 +2020,7 @@ getsockopt (int fd, int level, int optname,
+             break;
+           default:
+             LDBG (0, "ERROR: fd %d: getsockopt SOL_TCP: sid %u, "
+-                  "optname %d unsupported!", fd, vlsh, optname);
++                  "optname %d unsupported!", fd, vclsh, optname);
+             break;
+           }
+         break;
+@@ -1820,11 +2028,13 @@ getsockopt (int fd, int level, int optname,
+         switch (optname)
+           {
+           case IPV6_V6ONLY:
+-            rv = vls_attr (vlsh, VPPCOM_ATTR_GET_V6ONLY, optval, optlen);
++            rv =
++              vppcom_session_attr (vclsh, VPPCOM_ATTR_GET_V6ONLY, optval,
++                                   optlen);
+             break;
+           default:
+-            LDBG (0, "ERROR: fd %d: getsockopt SOL_IPV6: vlsh %u "
+-                  "optname %d unsupported!", fd, vlsh, optname);
++            LDBG (0, "ERROR: fd %d: getsockopt SOL_IPV6: vclsh %u "
++                  "optname %d unsupported!", fd, vclsh, optname);
+             break;
+           }
+         break;
+@@ -1832,35 +2042,47 @@ getsockopt (int fd, int level, int optname,
+         switch (optname)
+           {
+           case SO_ACCEPTCONN:
+-            rv = vls_attr (vlsh, VPPCOM_ATTR_GET_LISTEN, optval, optlen);
++            rv =
++              vppcom_session_attr (vclsh, VPPCOM_ATTR_GET_LISTEN, optval,
++                                   optlen);
+             break;
+           case SO_KEEPALIVE:
+-            rv = vls_attr (vlsh, VPPCOM_ATTR_GET_KEEPALIVE, optval, optlen);
++            rv =
++              vppcom_session_attr (vclsh, VPPCOM_ATTR_GET_KEEPALIVE, optval,
++                                   optlen);
+             break;
+           case SO_PROTOCOL:
+-            rv = vls_attr (vlsh, VPPCOM_ATTR_GET_PROTOCOL, optval, optlen);
++            rv =
++              vppcom_session_attr (vclsh, VPPCOM_ATTR_GET_PROTOCOL, optval,
++                                   optlen);
+             *(int *) optval = *(int *) optval ? SOCK_DGRAM : SOCK_STREAM;
+             break;
+           case SO_SNDBUF:
+-            rv = vls_attr (vlsh, VPPCOM_ATTR_GET_TX_FIFO_LEN,
+-                           optval, optlen);
++            rv = vppcom_session_attr (vclsh, VPPCOM_ATTR_GET_TX_FIFO_LEN,
++                                      optval, optlen);
+             break;
+           case SO_RCVBUF:
+-            rv = vls_attr (vlsh, VPPCOM_ATTR_GET_RX_FIFO_LEN,
+-                           optval, optlen);
++            rv = vppcom_session_attr (vclsh, VPPCOM_ATTR_GET_RX_FIFO_LEN,
++                                      optval, optlen);
+             break;
+           case SO_REUSEADDR:
+-            rv = vls_attr (vlsh, VPPCOM_ATTR_GET_REUSEADDR, optval, optlen);
++            rv =
++              vppcom_session_attr (vclsh, VPPCOM_ATTR_GET_REUSEADDR, optval,
++                                   optlen);
+             break;
+           case SO_BROADCAST:
+-            rv = vls_attr (vlsh, VPPCOM_ATTR_GET_BROADCAST, optval, optlen);
++            rv =
++              vppcom_session_attr (vclsh, VPPCOM_ATTR_GET_BROADCAST, optval,
++                                   optlen);
+             break;
+           case SO_ERROR:
+-            rv = vls_attr (vlsh, VPPCOM_ATTR_GET_ERROR, optval, optlen);
++            rv =
++              vppcom_session_attr (vclsh, VPPCOM_ATTR_GET_ERROR, optval,
++                                   optlen);
+             break;
+           default:
+-            LDBG (0, "ERROR: fd %d: getsockopt SOL_SOCKET: vlsh %u "
+-                  "optname %d unsupported!", fd, vlsh, optname);
++            LDBG (0, "ERROR: fd %d: getsockopt SOL_SOCKET: vclsh %u "
++                  "optname %d unsupported!", fd, vclsh, optname);
+             break;
+           }
+         break;
+@@ -1886,14 +2108,14 @@ int
+ setsockopt (int fd, int level, int optname,
+           const void *optval, socklen_t optlen)
+ {
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   int rv;
+   if ((errno = -ldp_init ()))
+     return -1;
+-  vlsh = ldp_fd_to_vlsh (fd);
+-  if (vlsh != VLS_INVALID_HANDLE)
++  vclsh = ldp_fd_to_vclsh (fd);
++  if (vclsh != INVALID_SESSION_ID)
+     {
+       rv = -EOPNOTSUPP;
+@@ -1903,20 +2125,20 @@ setsockopt (int fd, int level, int optname,
+         switch (optname)
+           {
+           case TCP_NODELAY:
+-            rv = vls_attr (vlsh, VPPCOM_ATTR_SET_TCP_NODELAY,
+-                           (void *) optval, &optlen);
++            rv = vppcom_session_attr (vclsh, VPPCOM_ATTR_SET_TCP_NODELAY,
++                                      (void *) optval, &optlen);
+             break;
+           case TCP_MAXSEG:
+-            rv = vls_attr (vlsh, VPPCOM_ATTR_SET_TCP_USER_MSS,
+-                           (void *) optval, &optlen);
++            rv = vppcom_session_attr (vclsh, VPPCOM_ATTR_SET_TCP_USER_MSS,
++                                      (void *) optval, &optlen);
+             break;
+           case TCP_KEEPIDLE:
+-            rv = vls_attr (vlsh, VPPCOM_ATTR_SET_TCP_KEEPIDLE,
+-                           (void *) optval, &optlen);
++            rv = vppcom_session_attr (vclsh, VPPCOM_ATTR_SET_TCP_KEEPIDLE,
++                                      (void *) optval, &optlen);
+             break;
+           case TCP_KEEPINTVL:
+-            rv = vls_attr (vlsh, VPPCOM_ATTR_SET_TCP_KEEPINTVL,
+-                           (void *) optval, &optlen);
++            rv = vppcom_session_attr (vclsh, VPPCOM_ATTR_SET_TCP_KEEPINTVL,
++                                      (void *) optval, &optlen);
+             break;
+           case TCP_CONGESTION:
+           case TCP_CORK:
+@@ -1924,8 +2146,8 @@ setsockopt (int fd, int level, int optname,
+             rv = 0;
+             break;
+           default:
+-            LDBG (0, "ERROR: fd %d: setsockopt() SOL_TCP: vlsh %u"
+-                  "optname %d unsupported!", fd, vlsh, optname);
++            LDBG (0, "ERROR: fd %d: setsockopt() SOL_TCP: vclsh %u"
++                  "optname %d unsupported!", fd, vclsh, optname);
+             break;
+           }
+         break;
+@@ -1933,12 +2155,12 @@ setsockopt (int fd, int level, int optname,
+         switch (optname)
+           {
+           case IPV6_V6ONLY:
+-            rv = vls_attr (vlsh, VPPCOM_ATTR_SET_V6ONLY,
+-                           (void *) optval, &optlen);
++            rv = vppcom_session_attr (vclsh, VPPCOM_ATTR_SET_V6ONLY,
++                                      (void *) optval, &optlen);
+             break;
+           default:
+-            LDBG (0, "ERROR: fd %d: setsockopt SOL_IPV6: vlsh %u"
+-                  "optname %d unsupported!", fd, vlsh, optname);
++            LDBG (0, "ERROR: fd %d: setsockopt SOL_IPV6: vclsh %u"
++                  "optname %d unsupported!", fd, vclsh, optname);
+             break;
+           }
+         break;
+@@ -1946,20 +2168,20 @@ setsockopt (int fd, int level, int optname,
+         switch (optname)
+           {
+           case SO_KEEPALIVE:
+-            rv = vls_attr (vlsh, VPPCOM_ATTR_SET_KEEPALIVE,
+-                           (void *) optval, &optlen);
++            rv = vppcom_session_attr (vclsh, VPPCOM_ATTR_SET_KEEPALIVE,
++                                      (void *) optval, &optlen);
+             break;
+           case SO_REUSEADDR:
+-            rv = vls_attr (vlsh, VPPCOM_ATTR_SET_REUSEADDR,
+-                           (void *) optval, &optlen);
++            rv = vppcom_session_attr (vclsh, VPPCOM_ATTR_SET_REUSEADDR,
++                                      (void *) optval, &optlen);
+             break;
+           case SO_BROADCAST:
+-            rv = vls_attr (vlsh, VPPCOM_ATTR_SET_BROADCAST,
+-                           (void *) optval, &optlen);
++            rv = vppcom_session_attr (vclsh, VPPCOM_ATTR_SET_BROADCAST,
++                                      (void *) optval, &optlen);
+             break;
+           default:
+-            LDBG (0, "ERROR: fd %d: setsockopt SOL_SOCKET: vlsh %u "
+-                  "optname %d unsupported!", fd, vlsh, optname);
++            LDBG (0, "ERROR: fd %d: setsockopt SOL_SOCKET: vclsh %u "
++                  "optname %d unsupported!", fd, vclsh, optname);
+             break;
+           }
+         break;
+@@ -1984,18 +2206,20 @@ setsockopt (int fd, int level, int optname,
+ int
+ listen (int fd, int n)
+ {
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
++  vcl_worker_t *wrk = vcl_worker_get_current ();
+   int rv;
+   if ((errno = -ldp_init ()))
+     return -1;
+-  vlsh = ldp_fd_to_vlsh (fd);
+-  if (vlsh != VLS_INVALID_HANDLE)
++  vclsh = ldp_fd_to_vclsh (fd);
++  if (vclsh != INVALID_SESSION_ID)
+     {
+-      LDBG (0, "fd %d: calling vls_listen: vlsh %u, n %d", fd, vlsh, n);
++      LDBG (0, "fd %d: calling vppcom_session_listen: vclsh %u, n %d", fd,
++          vclsh, n);
+-      rv = vls_listen (vlsh, n);
++      rv = vppcom_session_listen (vclsh, n);
+       if (rv != VPPCOM_OK)
+       {
+         errno = -rv;
+@@ -2009,6 +2233,10 @@ listen (int fd, int n)
+     }
+   LDBG (1, "fd %d: returning %d", fd, rv);
++/*Update listen info in vcl worker*/
++  wrk->listen_fd = fd;
++  wrk->listen_queue_size = n;
++  wrk->listen_session_index = vppcom_session_index (vclsh);
+   return rv;
+ }
+@@ -2016,14 +2244,14 @@ static inline int
+ ldp_accept4 (int listen_fd, __SOCKADDR_ARG addr,
+            socklen_t * __restrict addr_len, int flags)
+ {
+-  vls_handle_t listen_vlsh, accept_vlsh;
++  vcl_session_handle_t listen_vclsh, accept_vclsh;
+   int rv;
+   if ((errno = -ldp_init ()))
+     return -1;
+-  listen_vlsh = ldp_fd_to_vlsh (listen_fd);
+-  if (listen_vlsh != VLS_INVALID_HANDLE)
++  listen_vclsh = ldp_fd_to_vclsh (listen_fd);
++  if (listen_vclsh != INVALID_SESSION_ID)
+     {
+       vppcom_endpt_t ep;
+       u8 src_addr[sizeof (struct sockaddr_in6)];
+@@ -2031,12 +2259,12 @@ ldp_accept4 (int listen_fd, __SOCKADDR_ARG addr,
+       ep.ip = src_addr;
+       LDBG (0, "listen fd %d: calling vppcom_session_accept: listen sid %u,"
+-          " ep %p, flags 0x%x", listen_fd, listen_vlsh, ep, flags);
++          " ep %p, flags 0x%x", listen_fd, listen_vclsh, ep, flags);
+-      accept_vlsh = vls_accept (listen_vlsh, &ep, flags);
+-      if (accept_vlsh < 0)
++      accept_vclsh = vppcom_session_accept (listen_vclsh, &ep, flags);
++      if (accept_vclsh < 0)
+       {
+-        errno = -accept_vlsh;
++        errno = -accept_vclsh;
+         rv = -1;
+       }
+       else
+@@ -2044,13 +2272,13 @@ ldp_accept4 (int listen_fd, __SOCKADDR_ARG addr,
+         rv = ldp_copy_ep_to_sockaddr (addr, addr_len, &ep);
+         if (rv != VPPCOM_OK)
+           {
+-            (void) vls_close (accept_vlsh);
++            (void) vppcom_session_close (accept_vclsh);
+             errno = -rv;
+             rv = -1;
+           }
+         else
+           {
+-            rv = ldp_vlsh_to_fd (accept_vlsh);
++            rv = ldp_vclsh_to_fd (accept_vclsh);
+           }
+       }
+     }
+@@ -2083,25 +2311,26 @@ accept (int fd, __SOCKADDR_ARG addr, socklen_t * __restrict addr_len)
+ int
+ shutdown (int fd, int how)
+ {
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   int rv = 0, flags;
+   u32 flags_len = sizeof (flags);
+   if ((errno = -ldp_init ()))
+     return -1;
+-  vlsh = ldp_fd_to_vlsh (fd);
+-  if (vlsh != VLS_INVALID_HANDLE)
++  vclsh = ldp_fd_to_vclsh (fd);
++  if (vclsh != INVALID_SESSION_ID)
+     {
+-      LDBG (0, "called shutdown: fd %u vlsh %u how %d", fd, vlsh, how);
++      LDBG (0, "called shutdown: fd %u vclsh %u how %d", fd, vclsh, how);
+-      if (vls_attr (vlsh, VPPCOM_ATTR_SET_SHUT, &how, &flags_len))
++      if (vppcom_session_attr (vclsh, VPPCOM_ATTR_SET_SHUT, &how, &flags_len))
+       {
+         close (fd);
+         return -1;
+       }
+-      if (vls_attr (vlsh, VPPCOM_ATTR_GET_SHUT, &flags, &flags_len))
++      if (vppcom_session_attr
++        (vclsh, VPPCOM_ATTR_GET_SHUT, &flags, &flags_len))
+       {
+         close (fd);
+         return -1;
+@@ -2123,7 +2352,7 @@ int
+ epoll_create1 (int flags)
+ {
+   ldp_worker_ctx_t *ldpw = ldp_worker_get_current ();
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   int rv;
+   if ((errno = -ldp_init ()))
+@@ -2144,17 +2373,17 @@ epoll_create1 (int flags)
+       return rv;
+     }
+-  vlsh = vls_epoll_create ();
+-  if (PREDICT_FALSE (vlsh == VLS_INVALID_HANDLE))
++  vclsh = vppcom_epoll_create ();
++  if (PREDICT_FALSE (vclsh == INVALID_SESSION_ID))
+     {
+-      errno = -vlsh;
++      errno = -vclsh;
+       rv = -1;
+     }
+   else
+     {
+-      rv = ldp_vlsh_to_fd (vlsh);
++      rv = ldp_vclsh_to_fd (vclsh);
+     }
+-  LDBG (0, "epoll_create epfd %u vlsh %u", rv, vlsh);
++  LDBG (0, "epoll_create epfd %u vclsh %u", rv, vclsh);
+   return rv;
+ }
+@@ -2167,14 +2396,14 @@ epoll_create (int size)
+ int
+ epoll_ctl (int epfd, int op, int fd, struct epoll_event *event)
+ {
+-  vls_handle_t vep_vlsh, vlsh;
++  vcl_session_handle_t vep_vclsh, vclsh;
+   int rv;
+   if ((errno = -ldp_init ()))
+     return -1;
+-  vep_vlsh = ldp_fd_to_vlsh (epfd);
+-  if (PREDICT_FALSE (vep_vlsh == VLS_INVALID_HANDLE))
++  vep_vclsh = ldp_fd_to_vclsh (epfd);
++  if (PREDICT_FALSE (vep_vclsh == INVALID_SESSION_ID))
+     {
+       /* The LDP epoll_create1 always creates VCL epfd's.
+        * The app should never have a kernel base epoll fd unless it
+@@ -2188,17 +2417,18 @@ epoll_ctl (int epfd, int op, int fd, struct epoll_event *event)
+       goto done;
+     }
+-  vlsh = ldp_fd_to_vlsh (fd);
++  vclsh = ldp_fd_to_vclsh (fd);
+-  LDBG (0, "epfd %d ep_vlsh %d, fd %u vlsh %d, op %u", epfd, vep_vlsh, fd,
+-      vlsh, op);
++  LDBG (0, "epfd %d ep_vclsh %d, fd %u vclsh %d, op %u", epfd, vep_vclsh, fd,
++      vclsh, op);
+-  if (vlsh != VLS_INVALID_HANDLE)
++  if (vclsh != INVALID_SESSION_ID)
+     {
+-      LDBG (1, "epfd %d: calling vls_epoll_ctl: ep_vlsh %d op %d, vlsh %u,"
+-          " event %p", epfd, vep_vlsh, vlsh, event);
++      LDBG (1,
++          "epfd %d: calling vppcom_epoll_ctl: ep_vclsh %d op %d, vclsh %u,"
++          " event %p", epfd, vep_vclsh, vclsh, event);
+-      rv = vls_epoll_ctl (vep_vlsh, op, vlsh, event);
++      rv = vppcom_epoll_ctl (vep_vclsh, op, vclsh, event);
+       if (rv != VPPCOM_OK)
+       {
+         errno = -rv;
+@@ -2210,11 +2440,12 @@ epoll_ctl (int epfd, int op, int fd, struct epoll_event *event)
+       int libc_epfd;
+       u32 size = sizeof (epfd);
+-      libc_epfd = vls_attr (vep_vlsh, VPPCOM_ATTR_GET_LIBC_EPFD, 0, 0);
++      libc_epfd =
++      vppcom_session_attr (vep_vclsh, VPPCOM_ATTR_GET_LIBC_EPFD, 0, 0);
+       if (!libc_epfd)
+       {
+-        LDBG (1, "epfd %d, vep_vlsh %d calling libc_epoll_create1: "
+-              "EPOLL_CLOEXEC", epfd, vep_vlsh);
++        LDBG (1, "epfd %d, vep_vclsh %d calling libc_epoll_create1: "
++              "EPOLL_CLOEXEC", epfd, vep_vclsh);
+         libc_epfd = libc_epoll_create1 (EPOLL_CLOEXEC);
+         if (libc_epfd < 0)
+@@ -2223,8 +2454,9 @@ epoll_ctl (int epfd, int op, int fd, struct epoll_event *event)
+             goto done;
+           }
+-        rv = vls_attr (vep_vlsh, VPPCOM_ATTR_SET_LIBC_EPFD, &libc_epfd,
+-                       &size);
++        rv =
++          vppcom_session_attr (vep_vclsh, VPPCOM_ATTR_SET_LIBC_EPFD,
++                               &libc_epfd, &size);
+         if (rv < 0)
+           {
+             errno = -rv;
+@@ -2256,7 +2488,7 @@ ldp_epoll_pwait (int epfd, struct epoll_event *events, int maxevents,
+   ldp_worker_ctx_t *ldpw = ldp_worker_get_current ();
+   double time_to_wait = (double) 0, max_time;
+   int libc_epfd, rv = 0;
+-  vls_handle_t ep_vlsh;
++  vcl_session_handle_t ep_vclsh;
+   if ((errno = -ldp_init ()))
+     return -1;
+@@ -2270,10 +2502,10 @@ ldp_epoll_pwait (int epfd, struct epoll_event *events, int maxevents,
+   if (epfd == ldpw->vcl_mq_epfd)
+     return libc_epoll_pwait (epfd, events, maxevents, timeout, sigmask);
+-  ep_vlsh = ldp_fd_to_vlsh (epfd);
+-  if (PREDICT_FALSE (ep_vlsh == VLS_INVALID_HANDLE))
++  ep_vclsh = ldp_fd_to_vclsh (epfd);
++  if (PREDICT_FALSE (ep_vclsh == INVALID_SESSION_ID))
+     {
+-      LDBG (0, "epfd %d: bad ep_vlsh %d!", epfd, ep_vlsh);
++      LDBG (0, "epfd %d: bad ep_vclsh %d!", epfd, ep_vclsh);
+       errno = EBADFD;
+       return -1;
+     }
+@@ -2283,7 +2515,7 @@ ldp_epoll_pwait (int epfd, struct epoll_event *events, int maxevents,
+   time_to_wait = ((timeout >= 0) ? (double) timeout / 1000 : 0);
+   max_time = clib_time_now (&ldpw->clib_time) + time_to_wait;
+-  libc_epfd = vls_attr (ep_vlsh, VPPCOM_ATTR_GET_LIBC_EPFD, 0, 0);
++  libc_epfd = vppcom_session_attr (ep_vclsh, VPPCOM_ATTR_GET_LIBC_EPFD, 0, 0);
+   if (PREDICT_FALSE (libc_epfd < 0))
+     {
+       errno = -libc_epfd;
+@@ -2292,13 +2524,13 @@ ldp_epoll_pwait (int epfd, struct epoll_event *events, int maxevents,
+     }
+   LDBG (2, "epfd %d: vep_idx %d, libc_epfd %d, events %p, maxevents %d, "
+-      "timeout %d, sigmask %p: time_to_wait %.02f", epfd, ep_vlsh,
++      "timeout %d, sigmask %p: time_to_wait %.02f", epfd, ep_vclsh,
+       libc_epfd, events, maxevents, timeout, sigmask, time_to_wait);
+   do
+     {
+       if (!ldpw->epoll_wait_vcl)
+       {
+-        rv = vls_epoll_wait (ep_vlsh, events, maxevents, 0);
++        rv = vppcom_epoll_wait (ep_vclsh, events, maxevents, 0);
+         if (rv > 0)
+           {
+             ldpw->epoll_wait_vcl = 1;
+@@ -2345,7 +2577,7 @@ poll (struct pollfd *fds, nfds_t nfds, int timeout)
+ {
+   ldp_worker_ctx_t *ldpw = ldp_worker_get_current ();
+   int rv, i, n_revents = 0;
+-  vls_handle_t vlsh;
++  vcl_session_handle_t vclsh;
+   vcl_poll_t *vp;
+   double max_time;
+@@ -2362,13 +2594,13 @@ poll (struct pollfd *fds, nfds_t nfds, int timeout)
+       if (fds[i].fd < 0)
+       continue;
+-      vlsh = ldp_fd_to_vlsh (fds[i].fd);
+-      if (vlsh != VLS_INVALID_HANDLE)
++      vclsh = ldp_fd_to_vclsh (fds[i].fd);
++      if (vclsh != INVALID_SESSION_ID)
+       {
+         fds[i].fd = -fds[i].fd;
+         vec_add2 (ldpw->vcl_poll, vp, 1);
+         vp->fds_ndx = i;
+-        vp->sh = vlsh_to_sh (vlsh);
++        vp->sh = vclsh;
+         vp->events = fds[i].events;
+ #ifdef __USE_XOPEN2K
+         if (fds[i].events & POLLRDNORM)
+diff --git a/src/vcl/ldp.h b/src/vcl/ldp.h
+index 8d78ead08..0a03f442d 100644
+--- a/src/vcl/ldp.h
++++ b/src/vcl/ldp.h
+@@ -34,7 +34,7 @@
+ #define LDP_ENV_TLS_KEY   "LDP_TLS_KEY_FILE"
+ #define LDP_ENV_TLS_TRANS "LDP_TRANSPARENT_TLS"
+-#define LDP_SID_BIT_MIN   5
++#define LDP_SID_BIT_MIN   16
+ #define LDP_SID_BIT_MAX   30
+ #define LDP_APP_NAME_MAX  256
+diff --git a/src/vcl/vcl_private.h b/src/vcl/vcl_private.h
+index 8fdf7551c..16c23de82 100644
+--- a/src/vcl/vcl_private.h
++++ b/src/vcl/vcl_private.h
+@@ -238,7 +238,12 @@ typedef struct vcl_worker_
+   /* Session pool */
+   vcl_session_t *sessions;
+-  /** Worker/thread index in current process */
++  u32 listen_session_index;
++
++  u32 listen_fd;
++
++  u32 listen_queue_size;
++/** Worker/thread index in current process */
+   u32 wrk_index;
+   /** Worker index in vpp*/
+-- 
+2.17.1
+
diff --git a/vpp_patches/vcl/0001-ngxvcl-api.patch b/vpp_patches/vcl/0001-ngxvcl-api.patch
new file mode 100644 (file)
index 0000000..f294b36
--- /dev/null
@@ -0,0 +1,1713 @@
+From 1c07c530f6308362f32c686a571a577380a1b7d8 Mon Sep 17 00:00:00 2001
+From: Zeyu Zhang <[email protected]>
+Date: Wed, 16 Oct 2019 16:51:22 +0800
+Subject: [PATCH] ngxvcl
+
+---
+ src/vcl/CMakeLists.txt |   10 +-
+ src/vcl/ngxvcl.c       | 1590 ++++++++++++++++++++++++++++++++++++++++
+ src/vcl/ngxvcl.h       |   70 ++
+ 3 files changed, 1669 insertions(+), 1 deletion(-)
+ create mode 100644 src/vcl/ngxvcl.c
+ create mode 100644 src/vcl/ngxvcl.h
+
+diff --git a/src/vcl/CMakeLists.txt b/src/vcl/CMakeLists.txt
+index ab0a6ad6a..1b6a9f351 100644
+--- a/src/vcl/CMakeLists.txt
++++ b/src/vcl/CMakeLists.txt
+@@ -38,10 +38,18 @@ add_vpp_library(vcl_ldpreload
+   vppinfra svm vlibmemoryclient rt pthread vppcom dl
+ )
++add_vpp_library(ngxvcl
++  SOURCES
++  ngxvcl.c
++
++  LINK_LIBRARIES
++  vppinfra svm vlibmemoryclient rt pthread vppcom dl
++)
++
+ add_vpp_headers(vcl
+   ldp.h
+   ldp_glibc_socket.h
+   vppcom.h
+   vcl_locked.h
+   ldp_socket_wrapper.h
+-)
+\ No newline at end of file
++)
+diff --git a/src/vcl/ngxvcl.c b/src/vcl/ngxvcl.c
+new file mode 100644
+index 000000000..157c05246
+--- /dev/null
++++ b/src/vcl/ngxvcl.c
+@@ -0,0 +1,1590 @@
++#include <vcl/ngxvcl.h>
++#include <vcl/vcl_private.h>
++
++#define MAX_NGX_WORKERS 100
++#define VFD_OFFSET 0X003F3F3F
++#define NGXVCL_TLS_ON "NGXVCL_TLS_ON"
++#define NGXVCL_TLS_CERT "NGXVCL_TLS_CERT"
++#define NGXVCL_TLS_KEY "NGXVCL_TLS_KEY"
++
++typedef unsigned char u8;
++typedef unsigned short u16;
++typedef unsigned int u32;
++
++typedef struct ngxvcl_main_t_
++{
++  u32 listen_session_index;
++  u32 master_worker_index;
++  /** Not include master worker index. */
++  u32 *workers_subscribed_by_ls;
++  clib_bitmap_t *listeners;
++  uword *worker_index_by_pid;
++  int wait_vep_only;
++  int intercepted_sigchld;
++  u8 transparent_tls;
++} ngxvcl_main_t;
++
++static ngxvcl_main_t *nvm = NULL;
++
++static u8 *sendfile_io_buffer = NULL;
++
++static int epoll_fd_for_evtfd = 0;
++
++static u8 use_mq_eventfd = 0;
++
++static int wait_kep_next = 0;
++
++static inline _Bool is_offset_vfd(int fd)
++{
++    return fd >= VFD_OFFSET;
++}
++static inline int vfd_to_offset_vfd(int vfd)
++{
++    return vfd + VFD_OFFSET;
++}
++static inline int offset_vfd_to_vfd(int offset_fd)
++{
++    int vfd = offset_fd - VFD_OFFSET;
++
++    return (vcl_get_worker_index () << 24) | (vfd & 0X00FFFFFF);
++}
++
++static int copy_ep_to_sockaddr(struct sockaddr *addr, socklen_t *len,
++                                      vppcom_endpt_t *ep)
++{
++    int rv = 0;
++    int sa_len, copy_len;
++
++    if (addr && len && ep)
++    {
++        addr->sa_family = (ep->is_ip4 == VPPCOM_IS_IP4) ? AF_INET : AF_INET6;
++
++        switch (addr->sa_family)
++        {
++        case AF_INET:
++            ((struct sockaddr_in *)addr)->sin_port = ep->port;
++            if (*len > sizeof(struct sockaddr_in))
++                *len = sizeof(struct sockaddr_in);
++            sa_len = sizeof(struct sockaddr_in) - sizeof(struct in_addr);
++            copy_len = *len - sa_len;
++            if (copy_len > 0)
++                memcpy(&((struct sockaddr_in *)addr)->sin_addr, ep->ip,
++                       copy_len);
++            break;
++
++        case AF_INET6:
++            ((struct sockaddr_in6 *)addr)->sin6_port = ep->port;
++            if (*len > sizeof(struct sockaddr_in6))
++                *len = sizeof(struct sockaddr_in6);
++            sa_len = sizeof(struct sockaddr_in6) - sizeof(struct in6_addr);
++            copy_len = *len - sa_len;
++            if (copy_len > 0)
++                memcpy(
++                    ((struct sockaddr_in6 *)addr)->sin6_addr.__in6_u.__u6_addr8,
++                    ep->ip, copy_len);
++            break;
++
++        default:
++            /* Not possible */
++            rv = -EAFNOSUPPORT;
++            break;
++        }
++    }
++
++    return rv;
++}
++
++static void listener_wrk_stop_listen(u32 wrk_index) {
++    vcl_worker_t *wrk;
++    vcl_session_t *s;
++
++    wrk = vcl_worker_get(wrk_index);
++    s = vcl_session_get (wrk, nvm->listen_session_index);
++    if (s->session_state != STATE_LISTEN)
++        return;
++    vcl_send_session_unlisten(wrk, s);
++    s->session_state = STATE_LISTEN_NO_MQ;
++    clib_bitmap_set(nvm->listeners, wrk_index, 0);
++}
++
++static void
++share_listen_session (vcl_worker_t * wrk)
++{
++    vcl_session_t *s;
++
++    s = vcl_session_get (wrk, nvm->listen_session_index);
++    s->session_state = STATE_LISTEN_NO_MQ;
++    vppcom_session_listen((vcl_get_worker_index() << 24 | nvm->listen_session_index), ~0);
++    clib_bitmap_set(nvm->listeners, vcl_get_worker_index(), 1);
++    if (clib_bitmap_get(nvm->listeners, 0) == 1)
++        listener_wrk_stop_listen (0);
++    vec_add1 (nvm->workers_subscribed_by_ls, wrk->wrk_index);
++}
++
++static void
++worker_copy_on_fork (vcl_worker_t * parent_wrk)
++{
++  vcl_worker_t *wrk = vcl_worker_get_current ();
++
++  wrk->vpp_event_queues = vec_dup (parent_wrk->vpp_event_queues);
++  wrk->sessions = pool_dup (parent_wrk->sessions);
++  wrk->session_index_by_vpp_handles =
++    hash_dup (parent_wrk->session_index_by_vpp_handles);
++
++  share_listen_session (wrk);
++}
++
++static void
++ngxvcl_cleanup_child_worker (u32 child_wrk_index)
++{
++  vcl_worker_t *child_wrk = vcl_worker_get(child_wrk_index);
++  vcl_session_t *s;
++
++  /** Unshare listen session. */
++  s = vcl_session_get (child_wrk, nvm->listen_session_index);
++  clib_bitmap_set (nvm->listeners, child_wrk_index, 0);
++  vec_del1 (nvm->workers_subscribed_by_ls, child_wrk_index);
++  vcl_session_cleanup (child_wrk, s, vcl_session_handle (s), 1);
++
++  hash_unset (nvm->worker_index_by_pid, child_wrk->current_pid);
++  vcl_worker_cleanup (child_wrk, 1 /* notify vpp */);
++}
++
++static struct sigaction old_sa;
++
++static void
++ngxvcl_intercept_sigchld_handler (int signum, siginfo_t * si, void *uc)
++{
++  vcl_worker_t *wrk;
++  u32 child_wrk_index;
++
++  if (vcl_get_worker_index () == ~0)
++    return;
++
++  if (sigaction (SIGCHLD, &old_sa, 0))
++    {
++      VERR ("couldn't restore sigchld");
++      exit (-1);
++    }
++
++  wrk = vcl_worker_get_current ();
++  child_wrk_index = *(hash_get (nvm->worker_index_by_pid, si->si_pid));
++
++  if (si->si_pid != vcl_worker_get(child_wrk_index)->current_pid)
++    {
++      VDBG (0, "unexpected child pid %u", si->si_pid);
++      goto done;
++    }
++
++  ngxvcl_cleanup_child_worker (child_wrk_index);
++  wrk->forked_child = ~0;
++
++done:
++  if (old_sa.sa_flags & SA_SIGINFO)
++    {
++      void (*fn) (int, siginfo_t *, void *) = old_sa.sa_sigaction;
++      fn (signum, si, uc);
++    }
++  else
++    {
++      void (*fn) (int) = old_sa.sa_handler;
++      if (fn)
++      fn (signum);
++    }
++}
++
++static void
++ngxvcl_incercept_sigchld ()
++{
++  if (!nvm->intercepted_sigchld)
++  {
++    struct sigaction sa;
++    clib_memset (&sa, 0, sizeof (sa));
++    sa.sa_sigaction = ngxvcl_intercept_sigchld_handler;
++    sa.sa_flags = SA_SIGINFO;
++    if (sigaction (SIGCHLD, &sa, &old_sa))
++      {
++        VERR ("couldn't intercept sigchld");
++        exit (-1);
++      }
++    nvm->intercepted_sigchld = 1;
++  }
++}
++
++static void
++app_pre_fork (void)
++{
++  ngxvcl_incercept_sigchld ();
++  vcl_flush_mq_events ();
++}
++
++static void
++app_fork_parent_handler (void)
++{
++  vcm->forking = 1;
++  while (vcm->forking)
++    ;
++}
++
++static void
++app_fork_child_handler (void)
++{
++  vcl_worker_t *parent_wrk;
++  int rv, parent_wrk_index;
++  u8 *child_name;
++
++  parent_wrk_index = vcl_get_worker_index ();
++  VDBG (0, "initializing forked child %u with parent wrk %u", getpid (),
++  parent_wrk_index);
++
++  /*
++   * Allocate worker
++   */
++  vcl_set_worker_index (~0);
++  if (!vcl_worker_alloc_and_init ())
++    VERR ("couldn't allocate new worker");
++
++  /*
++   * Attach to binary api
++   */
++  child_name = format (0, "%v-child-%u%c", vcm->app_name, getpid (), 0);
++  vcl_cleanup_bapi ();
++  vppcom_api_hookup ();
++  vcm->app_state = STATE_APP_START;
++  rv = vppcom_connect_to_vpp ((char *) child_name);
++  vec_free (child_name);
++  if (rv)
++    {
++      VERR ("couldn't connect to VPP!");
++      return;
++    }
++
++  /*
++   * Register worker with vpp and share listen session
++   */
++  vcl_worker_register_with_vpp ();
++  parent_wrk = vcl_worker_get (parent_wrk_index);
++  worker_copy_on_fork (parent_wrk);
++  hash_set(nvm->worker_index_by_pid, getpid(), vcl_get_worker_index());
++  parent_wrk->forked_child = vcl_get_worker_index ();
++
++  sendfile_io_buffer = NULL;
++
++  VDBG (0, "forked child main worker initialized");
++  vcm->forking = 0;
++}
++
++static void
++sendfile_io_buffer_free (void)
++{
++  vec_free (sendfile_io_buffer);
++}
++
++void ngxvcl_wait_vep_only()
++{
++    if (use_mq_eventfd)
++        return;
++    nvm->wait_vep_only = 1;
++}
++
++void ngxvcl_wait_kep_and_vep()
++{
++    if (use_mq_eventfd)
++        return;
++    nvm->wait_vep_only = 0;
++}
++
++void ngxvcl_app_create(char *app_name)
++{
++    int rv = vppcom_app_create(app_name);
++
++    if (rv)
++    {
++        errno = -rv;
++        perror("ERROR when calling ngxvcl_app_create()!");
++        fprintf(stderr, "\nERROR: ngxvcl_app_create() failed (errno = %d)!\n", -rv);
++        exit(1);
++    }
++
++    pthread_atfork(app_pre_fork, app_fork_parent_handler,
++                   app_fork_child_handler);
++    atexit(sendfile_io_buffer_free);
++
++    nvm = clib_mem_alloc (sizeof (ngxvcl_main_t));
++    if (!nvm)
++    {
++      clib_warning ("NgxVCL<%d>: ERROR: clib_mem_alloc() failed!", getpid ());
++      ASSERT (nvm);
++      return;
++    }
++    clib_memset(nvm, 0, sizeof(ngxvcl_main_t));
++    clib_bitmap_validate(nvm->listeners, MAX_NGX_WORKERS + 1);
++    clib_bitmap_set(nvm->listeners, vcl_get_worker_index(), 1);
++    hash_set(nvm->worker_index_by_pid, getpid(), vcl_get_worker_index());
++
++    if (getenv (NGXVCL_TLS_ON))
++        nvm->transparent_tls = 1;
++
++    use_mq_eventfd = vcm->cfg.use_mq_eventfd;
++}
++
++void ngxvcl_app_destroy(void)
++{
++    vec_free(nvm->workers_subscribed_by_ls);
++    clib_bitmap_free(nvm->listeners);
++    hash_free(nvm->worker_index_by_pid);
++    clib_mem_free(nvm);
++    vppcom_app_destroy();
++}
++
++static int
++ngxvcl_load_tls_cert (uint32_t sh)
++{
++  char *env_var_str = getenv (NGXVCL_TLS_CERT);
++  char inbuf[4096];
++  char *tls_cert;
++  int cert_size;
++  FILE *fp;
++
++  if (env_var_str)
++    {
++      fp = fopen (env_var_str, "r");
++      if (fp == NULL)
++      {
++        VDBG (0, "ERROR: failed to open cert file %s \n", env_var_str);
++        return -1;
++      }
++      cert_size = fread (inbuf, sizeof (char), sizeof (inbuf), fp);
++      tls_cert = inbuf;
++      vppcom_session_tls_add_cert (sh, tls_cert,
++                                 cert_size);
++      fclose (fp);
++    }
++  else
++    {
++      VDBG (0, "ERROR: failed to read LDP environment %s\n",
++          NGXVCL_TLS_CERT);
++      return -1;
++    }
++  return 0;
++}
++
++static int
++ngxvcl_load_tls_key (uint32_t sh)
++{
++  char *env_var_str = getenv (NGXVCL_TLS_KEY);
++  char inbuf[4096];
++  char *tls_key;
++  int key_size;
++  FILE *fp;
++
++  if (env_var_str)
++    {
++      fp = fopen (env_var_str, "r");
++      if (fp == NULL)
++      {
++        VDBG (0, "ERROR: failed to open key file %s \n", env_var_str);
++        return -1;
++      }
++      key_size = fread (inbuf, sizeof (char), sizeof (inbuf), fp);
++      tls_key = inbuf;
++      vppcom_session_tls_add_key (sh, tls_key,
++                                key_size);
++      fclose (fp);
++    }
++  else
++    {
++      VDBG (0, "ERROR: failed to read NGXVCL environment %s\n", NGXVCL_TLS_KEY);
++      return -1;
++    }
++  return 0;
++}
++
++int ngxvcl_socket(int domain, int type, int protocol)
++{
++    int rv, sock_type = type & ~(SOCK_CLOEXEC | SOCK_NONBLOCK);
++    u8 is_nonblocking = type & SOCK_NONBLOCK ? 1 : 0;
++
++    if (((domain == AF_INET) || (domain == AF_INET6)) &&
++        ((sock_type == SOCK_STREAM) || (sock_type == SOCK_DGRAM)))
++    {
++        u8 proto;
++
++        if (nvm->transparent_tls)
++            proto = VPPCOM_PROTO_TLS;
++        else
++            proto = ((sock_type == SOCK_DGRAM) ? VPPCOM_PROTO_UDP : VPPCOM_PROTO_TCP);
++
++        rv = vppcom_session_create(proto, is_nonblocking);
++
++        if (rv < 0)
++        {
++            errno = -rv;
++            rv = -1;
++        }
++        else {
++            if (nvm->transparent_tls)
++                if (ngxvcl_load_tls_cert (rv) < 0 || ngxvcl_load_tls_key (rv) < 0)
++                    return -1;
++
++            rv = vfd_to_offset_vfd(rv);
++        }
++    }
++    else
++    {
++        rv = -1;
++    }
++
++    return rv;
++}
++
++int ngxvcl_close(int offset_vfd)
++{
++    int rv, epfd, vfd = offset_vfd_to_vfd(offset_vfd);
++
++    epfd = vppcom_session_attr(vfd, VPPCOM_ATTR_GET_LIBC_EPFD, 0, 0);
++    if (epfd > 0) {
++        rv = close(epfd);
++        if (rv < 0) {
++            u32 size = sizeof(epfd);
++            epfd = 0;
++            vppcom_session_attr(vfd, VPPCOM_ATTR_SET_LIBC_EPFD, &epfd, &size);
++        }
++    }
++    else if (epfd < 0) {
++        errno = -epfd;
++        rv = -1;
++        return rv;
++    }
++
++    rv = vppcom_session_close(offset_vfd_to_vfd(offset_vfd));
++
++    if (rv != VPPCOM_OK)
++    {
++        errno = -rv;
++        rv = -1;
++    }
++
++    return rv;
++}
++
++int ngxvcl_kvfd_close(int fd)
++{
++    int rv;
++
++    if (is_offset_vfd(fd))
++        rv = ngxvcl_close(fd);
++    else
++        rv = close(fd);
++
++    return rv;
++}
++
++int ngxvcl_bind(int offset_vfd, const struct sockaddr *addr, socklen_t addrlen)
++{
++    int rv;
++    vppcom_endpt_t ep;
++
++    switch (addr->sa_family)
++    {
++    case AF_INET:
++        if (addrlen != sizeof(struct sockaddr_in))
++        {
++            errno = EINVAL;
++            rv = -1;
++            goto done;
++        }
++        ep.is_ip4 = VPPCOM_IS_IP4;
++        ep.ip = (u8 *)&((const struct sockaddr_in *)addr)->sin_addr;
++        ep.port = (u16)((const struct sockaddr_in *)addr)->sin_port;
++        break;
++    case AF_INET6:
++        if (addrlen != sizeof(struct sockaddr_in6))
++        {
++            errno = EINVAL;
++            rv = -1;
++            goto done;
++        }
++        ep.is_ip4 = VPPCOM_IS_IP6;
++        ep.ip = (u8 *)&((const struct sockaddr_in6 *)addr)->sin6_addr;
++        ep.port = (u16)((const struct sockaddr_in6 *)addr)->sin6_port;
++        break;
++    default:
++        errno = EAFNOSUPPORT;
++        rv = -1;
++        goto done;
++    }
++
++    rv = vppcom_session_bind(offset_vfd_to_vfd(offset_vfd), &ep);
++
++    if (rv != VPPCOM_OK)
++    {
++        errno = -rv;
++        rv = -1;
++        goto done;
++    }
++
++    nvm->master_worker_index = offset_vfd_to_vfd(offset_vfd) >> 24;
++    nvm->listen_session_index = offset_vfd_to_vfd(offset_vfd) & 0X00FFFFFF;
++
++done:
++    return rv;
++}
++
++int ngxvcl_listen(int offset_vfd, int backlog)
++{
++    int rv;
++
++    ASSERT((u32)(offset_vfd_to_vfd(offset_vfd) & 0X00FFFFFF) == nvm->listen_session_index);
++
++    rv = vppcom_session_listen(offset_vfd_to_vfd(offset_vfd), backlog);
++
++    if (rv != VPPCOM_OK)
++    {
++        errno = -rv;
++        rv = -1;
++    }
++
++    return rv;
++}
++
++int ngxvcl_accept4(int offset_vfd, struct sockaddr *addr, socklen_t *addrlen,
++                   int flags)
++{
++    int accepted_fd, rv;
++    vppcom_endpt_t ep;
++    u8 src_addr[sizeof(struct sockaddr_in6)];
++    memset(&ep, 0, sizeof(ep));
++    ep.ip = src_addr;
++
++    accepted_fd = vppcom_session_accept(offset_vfd_to_vfd(offset_vfd), &ep, flags);
++
++    if (accepted_fd < 0)
++    {
++        errno = -accepted_fd;
++        rv = -1;
++    }
++    else
++    {
++        rv = copy_ep_to_sockaddr(addr, addrlen, &ep);
++
++        if (rv != VPPCOM_OK)
++        {
++            (void)vppcom_session_close(accepted_fd);
++            errno = -rv;
++            rv = -1;
++        }
++        else
++        {
++            rv = vfd_to_offset_vfd(accepted_fd);
++        }
++    }
++
++    return rv;
++}
++
++int ngxvcl_accept(int offset_vfd, struct sockaddr *addr, socklen_t *addrlen)
++{
++    return ngxvcl_accept4(offset_vfd, addr, addrlen, 0);
++}
++
++int ngxvcl_connect(int offset_vfd, const struct sockaddr *addr, socklen_t addrlen)
++{
++    int rv;
++
++    if (!addr)
++    {
++        errno = EINVAL;
++        rv = -1;
++        goto done;
++    }
++
++    vppcom_endpt_t ep;
++
++    switch (addr->sa_family)
++    {
++    case AF_INET:
++        if (addrlen != sizeof(struct sockaddr_in))
++        {
++            errno = EINVAL;
++            rv = -1;
++            goto done;
++        }
++        ep.is_ip4 = VPPCOM_IS_IP4;
++        ep.ip = (u8 *)&((const struct sockaddr_in *)addr)->sin_addr;
++        ep.port = (u16)((const struct sockaddr_in *)addr)->sin_port;
++        break;
++    case AF_INET6:
++        if (addrlen != sizeof(struct sockaddr_in6))
++        {
++            errno = EINVAL;
++            rv = -1;
++            goto done;
++        }
++        ep.is_ip4 = VPPCOM_IS_IP6;
++        ep.ip = (u8 *)&((const struct sockaddr_in6 *)addr)->sin6_addr;
++        ep.port = (u16)((const struct sockaddr_in6 *)addr)->sin6_port;
++        break;
++    default:
++        errno = EAFNOSUPPORT;
++        rv = -1;
++        goto done;
++    }
++
++    rv = vppcom_session_connect(offset_vfd_to_vfd(offset_vfd), &ep);
++
++    if (rv != VPPCOM_OK)
++    {
++        errno = -rv;
++        rv = -1;
++    }
++
++done:
++    return rv;
++}
++
++int ngxvcl_read(int offset_vfd, void *buf, size_t count)
++{
++    ssize_t size;
++
++    size = vppcom_session_read(offset_vfd_to_vfd(offset_vfd), buf, count);
++
++    if (size < 0)
++    {
++        errno = -size;
++        size = -1;
++    }
++
++    return size;
++}
++
++int ngxvcl_write(int offset_vfd, const void *buf, size_t count)
++{
++    ssize_t size = 0;
++
++    size = vppcom_session_write_msg(offset_vfd_to_vfd(offset_vfd), (void *)buf, count);
++
++    if (size < 0)
++    {
++        errno = -size;
++        size = -1;
++    }
++
++    return size;
++}
++
++int ngxvcl_epoll_create(int size)
++{
++    int rv, vepfd;
++
++    rv = vppcom_epoll_create();
++
++    if (rv < 0)
++    {
++        errno = -rv;
++        return -1;
++    }
++    else
++        vepfd = rv;
++
++    if (use_mq_eventfd) {
++        int libc_epfd;
++        u32 size = sizeof (u32);
++        struct epoll_event e = { 0 };
++
++        libc_epfd = epoll_create1 (EPOLL_CLOEXEC);
++        if (libc_epfd < 0)
++            return libc_epfd;
++        rv = vppcom_session_attr (vepfd, VPPCOM_ATTR_SET_LIBC_EPFD, &libc_epfd, &size);
++        if (rv < 0)
++        {
++            errno = -rv;
++            return -1;
++        }
++        e.events = EPOLLIN;
++        if (epoll_ctl (libc_epfd, EPOLL_CTL_ADD, vcl_worker_get_current ()->app_event_queue->q->consumer_evtfd, &e) < 0)
++            return -1;
++        epoll_fd_for_evtfd = libc_epfd;
++    }
++
++    return vfd_to_offset_vfd (vepfd);
++}
++
++int ngxvcl_kvfd_epoll_ctl(int offset_vepfd, int op, int fd, struct epoll_event *event)
++{
++    int rv, vepfd = offset_vfd_to_vfd(offset_vepfd);
++
++    if (is_offset_vfd(fd)) {
++        rv = vppcom_epoll_ctl(vepfd, op, offset_vfd_to_vfd(fd),
++                              event);
++
++        if (rv != VPPCOM_OK)
++        {
++            errno = -rv;
++            rv = -1;
++        }
++    }
++    else {
++        int libc_epfd;
++        u32 size = sizeof (vepfd);
++
++        libc_epfd = vppcom_session_attr (vepfd, VPPCOM_ATTR_GET_LIBC_EPFD, 0, 0);
++        if (!libc_epfd) {
++            libc_epfd = epoll_create1 (EPOLL_CLOEXEC);
++            if (libc_epfd < 0)
++            {
++                rv = libc_epfd;
++                return rv;
++            }
++            rv = vppcom_session_attr (vepfd, VPPCOM_ATTR_SET_LIBC_EPFD, &libc_epfd, &size);
++            if (rv < 0)
++            {
++                errno = -rv;
++                rv = -1;
++                return rv;
++            }
++        }
++        else if (libc_epfd < 0) {
++            errno = -vepfd;
++              rv = -1;
++            return rv;
++        }
++
++        rv = epoll_ctl (libc_epfd, op, fd, event);
++    }
++
++    return rv;
++}
++
++int ngxvcl_kvfd_epoll_wait(int offset_vepfd, struct epoll_event *events, int maxevents,
++                           int timeout)
++{
++    int rv = 0, vepfd = offset_vfd_to_vfd (offset_vepfd);
++
++    if (use_mq_eventfd) {
++        int i, n_evts = 0, veprv;
++        struct epoll_event temp_evts[2];
++
++again:
++        rv = epoll_wait (epoll_fd_for_evtfd, temp_evts, 2, timeout);
++
++        if (PREDICT_TRUE (rv > 0))
++            for (i = 0; i < rv; i++) {
++                if (PREDICT_FALSE (n_evts == maxevents))
++                    return n_evts;
++                if (PREDICT_TRUE (temp_evts[i].data.u32 == 0)) {
++                    veprv = vppcom_epoll_wait(vepfd, events + n_evts, maxevents - n_evts, 0);
++                    if (PREDICT_FALSE (veprv < 0)) {
++                        errno = -veprv;
++                        return -1;
++                    }
++                    n_evts += veprv;
++                }
++                else {
++                    events[n_evts] = temp_evts[i];
++                    n_evts += 1;
++                }
++            }
++
++        if (PREDICT_FALSE (!n_evts && rv > 0))
++            goto again;
++
++        return n_evts;
++    }
++
++    if (nvm->wait_vep_only)
++    {
++        rv = vppcom_epoll_wait(vepfd, events, maxevents, timeout);
++
++        if (rv < 0)
++        {
++            errno = -rv;
++            rv = -1;
++        }
++
++        return rv;
++    }
++    else
++    {
++        double time_to_wait = (double) 0, max_time;
++        int libc_epfd;
++        clib_time_t clib_time = {};
++
++        if (clib_time.init_cpu_time == 0)
++            clib_time_init (&clib_time);
++        time_to_wait = ((timeout >= 0) ? (double) timeout / 1000 : 0);
++        max_time = clib_time_now (&clib_time) + time_to_wait;
++
++        libc_epfd = vppcom_session_attr (vepfd, VPPCOM_ATTR_GET_LIBC_EPFD, 0, 0);
++        if (libc_epfd < 0)
++        {
++            errno = -libc_epfd;
++            rv = -1;
++            return rv;
++        }
++
++        do {
++            if (nvm->wait_vep_only) {
++                int time_remained = 0;
++                if (timeout > 0) {
++                    time_remained = (int)(1000 * (max_time - clib_time_now (&clib_time)));
++                }
++                else
++                    time_remained = timeout;
++                rv = vppcom_epoll_wait(vepfd, events, maxevents, time_remained);
++                if (rv < 0)
++                {
++                    errno = -rv;
++                    rv = -1;
++                }
++                return rv;
++            }
++
++            if (!wait_kep_next)
++            {
++                rv = vppcom_epoll_wait(vepfd, events, maxevents, 0);
++
++                if (rv < 0)
++                {
++                    errno = -rv;
++                    rv = -1;
++                    return rv;
++                }
++                else if (rv > 0)
++                {
++                    wait_kep_next = 1;
++                    return rv;
++                }
++            }
++            else
++                wait_kep_next = 0;
++
++            if (libc_epfd > 0) {
++                rv = epoll_wait(libc_epfd, events, maxevents, 0);
++                if (rv != 0)
++                    return rv;
++            }
++        } while ((timeout == -1) || (clib_time_now (&clib_time) < max_time));
++
++        return rv;
++    }
++}
++
++ssize_t ngxvcl_readv(int offset_vfd, const struct iovec *iov, int iovcnt)
++{
++    int rv = 0, i, total = 0;
++    ssize_t size = 0;
++
++    do
++    {
++        for (i = 0; i < iovcnt; ++i)
++        {
++            rv = vppcom_session_read(offset_vfd_to_vfd(offset_vfd), iov[i].iov_base,
++                                     iov[i].iov_len);
++            if (rv < 0)
++                break;
++            else
++            {
++                total += rv;
++                if ((size_t)rv < iov[i].iov_len)
++                    break;
++            }
++        }
++    } while ((rv >= 0) && (total == 0));
++
++    if (rv < 0)
++    {
++        errno = -rv;
++        size = -1;
++    }
++    else
++        size = total;
++
++    return size;
++}
++
++ssize_t ngxvcl_writev(int offset_vfd, const struct iovec *iov, int iovcnt)
++{
++    ssize_t size = 0, total = 0;
++    int i, rv = 0;
++
++    do
++    {
++        for (i = 0; i < iovcnt; ++i)
++        {
++            rv = vppcom_session_write_msg(offset_vfd_to_vfd(offset_vfd),
++                                          iov[i].iov_base, iov[i].iov_len);
++            if (rv < 0)
++                break;
++            else
++            {
++                total += rv;
++                if ((size_t)rv < iov[i].iov_len)
++                    break;
++            }
++        }
++    } while ((rv >= 0) && (total == 0));
++
++    if (rv < 0)
++    {
++        errno = -rv;
++        size = -1;
++    }
++    else
++        size = total;
++
++    return size;
++}
++
++int ngxvcl_kvfd_fcntl(int fd, int cmd, ...)
++{
++    int rv = 0;
++    va_list ap;
++
++    va_start(ap, cmd);
++
++    if (is_offset_vfd(fd))
++    {
++        int flags = va_arg(ap, int), vfd = offset_vfd_to_vfd(fd);
++        u32 size;
++
++        size = sizeof(flags);
++        rv = -EOPNOTSUPP;
++
++        switch (cmd)
++        {
++        case F_SETFL:
++            rv = vppcom_session_attr(vfd, VPPCOM_ATTR_SET_FLAGS, &flags, &size);
++            break;
++        case F_GETFL:
++            rv = vppcom_session_attr(vfd, VPPCOM_ATTR_GET_FLAGS, &flags, &size);
++            if (rv == VPPCOM_OK)
++                rv = flags;
++            break;
++        case F_SETFD:
++            /* TODO handle this */
++            rv = 0;
++            break;
++        default:
++            rv = -EOPNOTSUPP;
++            break;
++        }
++
++        if (rv < 0)
++        {
++            errno = -rv;
++            rv = -1;
++        }
++    }
++    else
++    {
++        long int args[4];
++
++        for (int i = 0; i < 4; i++)
++            args[i] = va_arg(ap, long int);
++
++        rv = fcntl(fd, cmd, args[0], args[1], args[2], args[3]);
++    }
++
++    va_end(ap);
++
++    return rv;
++}
++
++int ngxvcl_kvfd_ioctl(int fd, unsigned long int cmd, ...)
++{
++    va_list ap;
++    int rv;
++
++    va_start(ap, cmd);
++
++    if (is_offset_vfd(fd))
++    {
++        int vfd = offset_vfd_to_vfd(fd);
++
++        switch (cmd)
++        {
++        case FIONREAD:
++            rv = vppcom_session_attr(vfd, VPPCOM_ATTR_GET_NREAD, 0, 0);
++            break;
++
++        case FIONBIO:
++        {
++            u32 flags = va_arg(ap, int) ? O_NONBLOCK : 0;
++            u32 size = sizeof(flags);
++
++            /* TBD: When VPPCOM_ATTR_[GS]ET_FLAGS supports flags other than
++             *      non-blocking, the flags should be read here and merged
++             *      with O_NONBLOCK.
++             */
++            rv = vppcom_session_attr(vfd, VPPCOM_ATTR_SET_FLAGS, &flags, &size);
++        }
++        break;
++
++        default:
++            rv = -EOPNOTSUPP;
++            break;
++        }
++
++        if (rv < 0)
++        {
++            errno = -rv;
++            rv = -1;
++        }
++    }
++    else
++    {
++        long int args[4];
++
++        for (int i = 0; i < 4; i++)
++            args[i] = va_arg(ap, long int);
++
++        rv = ioctl(fd, cmd, args[0], args[1], args[2], args[3]);
++    }
++
++    va_end(ap);
++
++    return rv;
++}
++
++int ngxvcl_socketpair(int domain, int type, int protocol, int fds[2])
++{
++    int rv, sock_type = type & ~(SOCK_CLOEXEC | SOCK_NONBLOCK);
++
++    if (((domain == AF_INET) || (domain == AF_INET6)) &&
++        ((sock_type == SOCK_STREAM) || (sock_type == SOCK_DGRAM)))
++    {
++        errno = ENOSYS;
++        rv = -1;
++    }
++    else
++    {
++        rv = socketpair(domain, type, protocol, fds);
++    }
++
++    return rv;
++}
++
++int ngxvcl_kvfd_getsockname(int fd, struct sockaddr *addr, socklen_t *len)
++{
++    int rv;
++
++    if (is_offset_vfd(fd))
++    {
++        vppcom_endpt_t ep;
++        u8 addr_buf[sizeof(struct in6_addr)];
++        u32 size = sizeof(ep);
++
++        ep.ip = addr_buf;
++
++        rv = vppcom_session_attr(offset_vfd_to_vfd(fd),
++                                 VPPCOM_ATTR_GET_LCL_ADDR, &ep, &size);
++
++        if (rv != VPPCOM_OK)
++        {
++            errno = -rv;
++            rv = -1;
++        }
++        else
++        {
++            rv = copy_ep_to_sockaddr(addr, len, &ep);
++
++            if (rv != VPPCOM_OK)
++            {
++                errno = -rv;
++                rv = -1;
++            }
++        }
++    }
++    else
++    {
++        rv = getsockname(fd, addr, len);
++    }
++
++    return rv;
++}
++
++int ngxvcl_kvfd_getsockopt(int fd, int level, int optname, void *optval,
++                           socklen_t *optlen)
++{
++    int rv;
++
++    if (is_offset_vfd(fd))
++    {
++        int vfd = offset_vfd_to_vfd(fd);
++
++        rv = -EOPNOTSUPP;
++
++        switch (level)
++        {
++        case SOL_TCP:
++            switch (optname)
++            {
++            case TCP_NODELAY:
++                rv = vppcom_session_attr(vfd, VPPCOM_ATTR_GET_TCP_NODELAY,
++                                         optval, optlen);
++                break;
++            case TCP_MAXSEG:
++                rv = vppcom_session_attr(vfd, VPPCOM_ATTR_GET_TCP_USER_MSS,
++                                         optval, optlen);
++                break;
++            case TCP_KEEPIDLE:
++                rv = vppcom_session_attr(vfd, VPPCOM_ATTR_GET_TCP_KEEPIDLE,
++                                         optval, optlen);
++                break;
++            case TCP_KEEPINTVL:
++                rv = vppcom_session_attr(vfd, VPPCOM_ATTR_GET_TCP_KEEPINTVL,
++                                         optval, optlen);
++                break;
++            case TCP_INFO:
++                if (optval && optlen && (*optlen == sizeof(struct tcp_info)))
++                {
++                    memset(optval, 0, *optlen);
++                    rv = VPPCOM_OK;
++                }
++                else
++                    rv = -EFAULT;
++                break;
++            case TCP_CONGESTION:
++                strcpy(optval, "cubic");
++                *optlen = strlen("cubic");
++                rv = 0;
++                break;
++            default:
++                break;
++            }
++            break;
++        case SOL_IPV6:
++            switch (optname)
++            {
++            case IPV6_V6ONLY:
++                rv =
++                    vppcom_session_attr(vfd, VPPCOM_ATTR_GET_V6ONLY,
++                                        optval, optlen);
++                break;
++            default:
++                break;
++            }
++            break;
++        case SOL_SOCKET:
++            switch (optname)
++            {
++            case SO_ACCEPTCONN:
++                rv = vppcom_session_attr(vfd, VPPCOM_ATTR_GET_LISTEN,
++                                         optval, optlen);
++                break;
++            case SO_KEEPALIVE:
++                rv = vppcom_session_attr(vfd, VPPCOM_ATTR_GET_KEEPALIVE,
++                                         optval, optlen);
++                break;
++            case SO_PROTOCOL:
++                rv = vppcom_session_attr(vfd, VPPCOM_ATTR_GET_PROTOCOL,
++                                         optval, optlen);
++                *(int *)optval = *(int *)optval ? SOCK_DGRAM : SOCK_STREAM;
++                break;
++            case SO_SNDBUF:
++                rv = vppcom_session_attr(vfd, VPPCOM_ATTR_GET_TX_FIFO_LEN,
++                                         optval, optlen);
++                break;
++            case SO_RCVBUF:
++                rv = vppcom_session_attr(vfd, VPPCOM_ATTR_GET_RX_FIFO_LEN,
++                                         optval, optlen);
++                break;
++            case SO_REUSEADDR:
++                rv = vppcom_session_attr(vfd, VPPCOM_ATTR_GET_REUSEADDR,
++                                         optval, optlen);
++                break;
++            case SO_BROADCAST:
++                rv = vppcom_session_attr(vfd, VPPCOM_ATTR_GET_BROADCAST,
++                                         optval, optlen);
++                break;
++            case SO_ERROR:
++                rv = vppcom_session_attr(vfd, VPPCOM_ATTR_GET_ERROR,
++                                         optval, optlen);
++                break;
++            default:
++                break;
++            }
++            break;
++        default:
++            break;
++        }
++
++        if (rv != VPPCOM_OK)
++        {
++            errno = -rv;
++            rv = -1;
++        }
++    }
++    else
++    {
++        rv = getsockopt(fd, level, optname, optval, optlen);
++    }
++
++    return rv;
++}
++
++int ngxvcl_kvfd_setsockopt(int fd, int level, int optname, const void *optval,
++                           socklen_t optlen)
++{
++    int rv;
++
++    if (is_offset_vfd(fd))
++    {
++        int vfd = offset_vfd_to_vfd(fd);
++
++        rv = -EOPNOTSUPP;
++
++        switch (level)
++        {
++        case SOL_TCP:
++            switch (optname)
++            {
++            case TCP_NODELAY:
++                rv = vppcom_session_attr(vfd, VPPCOM_ATTR_SET_TCP_NODELAY,
++                                         (void *)optval, &optlen);
++                break;
++            case TCP_MAXSEG:
++                rv = vppcom_session_attr(vfd, VPPCOM_ATTR_SET_TCP_USER_MSS,
++                                         (void *)optval, &optlen);
++                break;
++            case TCP_KEEPIDLE:
++                rv = vppcom_session_attr(vfd, VPPCOM_ATTR_SET_TCP_KEEPIDLE,
++                                         (void *)optval, &optlen);
++                break;
++            case TCP_KEEPINTVL:
++                rv = vppcom_session_attr(vfd, VPPCOM_ATTR_SET_TCP_KEEPINTVL,
++                                         (void *)optval, &optlen);
++                break;
++            case TCP_CONGESTION:
++            case TCP_CORK:
++                /* Ignore */
++                rv = 0;
++                break;
++            default:
++                break;
++            }
++            break;
++        case SOL_IPV6:
++            switch (optname)
++            {
++            case IPV6_V6ONLY:
++                rv = vppcom_session_attr(vfd, VPPCOM_ATTR_SET_V6ONLY,
++                                         (void *)optval, &optlen);
++                break;
++            default:
++                break;
++            }
++            break;
++        case SOL_SOCKET:
++            switch (optname)
++            {
++            case SO_KEEPALIVE:
++                rv = vppcom_session_attr(vfd, VPPCOM_ATTR_SET_KEEPALIVE,
++                                         (void *)optval, &optlen);
++                break;
++            case SO_REUSEADDR:
++                rv = vppcom_session_attr(vfd, VPPCOM_ATTR_SET_REUSEADDR,
++                                         (void *)optval, &optlen);
++                break;
++            case SO_BROADCAST:
++                rv = vppcom_session_attr(vfd, VPPCOM_ATTR_SET_BROADCAST,
++                                         (void *)optval, &optlen);
++                break;
++            default:
++                break;
++            }
++            break;
++        default:
++            break;
++        }
++
++        if (rv != VPPCOM_OK)
++        {
++            errno = -rv;
++            rv = -1;
++        }
++    }
++    else
++    {
++        rv = setsockopt(fd, level, optname, optval, optlen);
++    }
++
++    return rv;
++}
++
++ssize_t ngxvcl_send(int offset_vfd, const void *buf, size_t n, int flags)
++{
++    ssize_t size;
++
++    size = vppcom_session_sendto(offset_vfd_to_vfd(offset_vfd), (void *)buf,
++                                 n, flags, NULL);
++
++    if (size < VPPCOM_OK)
++    {
++        errno = -size;
++        size = -1;
++    }
++
++    return size;
++}
++
++ssize_t ngxvcl_sendfile(int out_offset_vfd, int in_kfd, off_t *offset, size_t len)
++{
++    ssize_t size = 0;
++    int rv, out_vfd = offset_vfd_to_vfd(out_offset_vfd);
++    ssize_t results = 0;
++    size_t n_bytes_left = len;
++    size_t bytes_to_read;
++    int nbytes;
++    u8 eagain = 0;
++    u32 flags, flags_len = sizeof(flags);
++
++    rv = vppcom_session_attr(out_vfd, VPPCOM_ATTR_GET_FLAGS, &flags,
++                             &flags_len);
++
++    if (rv != VPPCOM_OK)
++    {
++        vec_reset_length (sendfile_io_buffer);
++        errno = -rv;
++        size = -1;
++        goto done;
++    }
++
++    if (offset)
++    {
++        off_t off = lseek(in_kfd, *offset, SEEK_SET);
++
++        if (off == -1)
++        {
++            size = -1;
++            goto done;
++        }
++    }
++
++    do
++    {
++        size = vppcom_session_attr(out_vfd, VPPCOM_ATTR_GET_NWRITE, 0, 0);
++
++        if (size < 0)
++        {
++            vec_reset_length (sendfile_io_buffer);
++            errno = -size;
++            size = -1;
++            goto done;
++        }
++
++        bytes_to_read = size;
++
++        if (bytes_to_read == 0)
++        {
++            if (flags & O_NONBLOCK)
++            {
++                if (!results)
++                    eagain = 1;
++                goto update_offset;
++            }
++            else
++                continue;
++        }
++
++        bytes_to_read = clib_min (n_bytes_left, bytes_to_read);
++        vec_validate (sendfile_io_buffer, bytes_to_read);
++        nbytes = read (in_kfd, sendfile_io_buffer, bytes_to_read);
++
++        if (nbytes < 0)
++        {
++            if (results == 0)
++            {
++                vec_reset_length (sendfile_io_buffer);
++                size = -1;
++                goto done;
++            }
++            goto update_offset;
++        }
++
++        size = vppcom_session_write(out_vfd, sendfile_io_buffer, nbytes);
++
++        if (size < 0)
++        {
++            if (size == VPPCOM_EAGAIN)
++            {
++                if (flags & O_NONBLOCK)
++                {
++                    if (!results)
++                        eagain = 1;
++                    goto update_offset;
++                }
++                else
++                    continue;
++            }
++            if (results == 0)
++            {
++                vec_reset_length (sendfile_io_buffer);
++                errno = -size;
++                size = -1;
++                goto done;
++            }
++            goto update_offset;
++        }
++
++        results += nbytes;
++        n_bytes_left = n_bytes_left - nbytes;
++    } while (n_bytes_left > 0);
++
++update_offset:
++    vec_reset_length (sendfile_io_buffer);
++    if (offset)
++    {
++        off_t off = lseek(in_kfd, *offset, SEEK_SET);
++
++        if (off == -1)
++        {
++            size = -1;
++            goto done;
++        }
++
++        *offset += results + 1;
++    }
++    if (eagain)
++    {
++        errno = EAGAIN;
++        size = -1;
++    }
++    else
++        size = results;
++
++done:
++    return size;
++}
++
++ssize_t ngxvcl_recv(int offset_vfd, void *buf, size_t n, int flags)
++{
++    ssize_t size;
++
++    size = vppcom_session_recvfrom(offset_vfd_to_vfd(offset_vfd), buf, n, flags, NULL);
++
++    if (size < 0)
++        errno = -size;
++
++    return size;
++}
++
++ssize_t ngxvcl_sendto(int offset_vfd, const void *buf, size_t n, int flags,
++                      const struct sockaddr *addr, socklen_t addr_len)
++{
++    ssize_t size;
++
++    vppcom_endpt_t *ep = 0;
++    vppcom_endpt_t _ep;
++
++    if (addr)
++    {
++        ep = &_ep;
++        switch (addr->sa_family)
++        {
++        case AF_INET:
++            ep->is_ip4 = VPPCOM_IS_IP4;
++            ep->ip = (uint8_t *)&((const struct sockaddr_in *)addr)->sin_addr;
++            ep->port = (uint16_t)((const struct sockaddr_in *)addr)->sin_port;
++            break;
++        case AF_INET6:
++            ep->is_ip4 = VPPCOM_IS_IP6;
++            ep->ip = (uint8_t *)&((const struct sockaddr_in6 *)addr)->sin6_addr;
++            ep->port = (uint16_t)((const struct sockaddr_in6 *)addr)->sin6_port;
++            break;
++        default:
++            errno = EAFNOSUPPORT;
++            size = -1;
++            goto done;
++        }
++    }
++
++    size = vppcom_session_sendto(offset_vfd_to_vfd(offset_vfd), (void *)buf, n, flags, ep);
++
++    if (size < 0)
++    {
++        errno = -size;
++        size = -1;
++    }
++
++done:
++    return size;
++}
++
++ssize_t ngxvcl_recvfrom(int offset_vfd, void *buf, size_t n, int flags,
++                        struct sockaddr *addr, socklen_t *addr_len)
++{
++    int vfd = offset_vfd_to_vfd(offset_vfd);
++    ssize_t size, rv;
++
++    vppcom_endpt_t ep;
++    u8 src_addr[sizeof(struct sockaddr_in6)];
++
++    if (addr)
++    {
++        ep.ip = src_addr;
++        size = vppcom_session_recvfrom(vfd, buf, n, flags, &ep);
++
++        if (size > 0)
++        {
++            rv = copy_ep_to_sockaddr(addr, addr_len, &ep);
++
++            if (rv < 0)
++                size = rv;
++        }
++    }
++    else
++        size = vppcom_session_recvfrom(vfd, buf, n, flags, NULL);
++
++    if (size < 0)
++    {
++        errno = -size;
++        size = -1;
++    }
++
++    return size;
++}
++
++ssize_t ngxvcl_sendmsg(int offset_vfd, const struct msghdr *message, int flags)
++{
++    ssize_t size;
++
++    errno = ENOSYS;
++    size = -1;
++
++    return size;
++}
++
++ssize_t ngxvcl_recvmsg(int offset_vfd, struct msghdr *message, int flags)
++{
++    ssize_t size;
++
++    errno = ENOSYS;
++    size = -1;
++
++    return size;
++}
++
++int ngxvcl_shutdown(int offset_vfd, int how)
++{
++    int rv = 0, flags, vfd = offset_vfd_to_vfd(offset_vfd);
++    u32 flags_len = sizeof(flags);
++
++    if (vppcom_session_attr(vfd, VPPCOM_ATTR_SET_SHUT, &how, &flags_len))
++    {
++        vppcom_session_close(vfd);
++        return -1;
++    }
++
++    if (vppcom_session_attr(vfd, VPPCOM_ATTR_GET_SHUT, &flags, &flags_len))
++    {
++        vppcom_session_close(vfd);
++        return -1;
++    }
++
++    if (flags == SHUT_RDWR)
++        rv = vppcom_session_close(vfd);
++
++    return rv;
++}
+diff --git a/src/vcl/ngxvcl.h b/src/vcl/ngxvcl.h
+new file mode 100644
+index 000000000..60b5116a8
+--- /dev/null
++++ b/src/vcl/ngxvcl.h
+@@ -0,0 +1,70 @@
++#ifndef _NGXVCL_H_
++#define _NGXVCL_H_
++
++#define _GNU_SOURCE
++#include <arpa/inet.h>
++#include <fcntl.h>
++#include <netinet/tcp.h>
++#include <stdarg.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <sys/errno.h>
++#include <sys/ioctl.h>
++#include <sys/sendfile.h>
++#include <sys/socket.h>
++#include <sys/uio.h>
++#include <unistd.h>
++#include <vcl/vppcom.h>
++
++void ngxvcl_wait_vep_only();
++void ngxvcl_wait_kep_and_vep();
++
++void ngxvcl_app_create(char *app_name);
++void ngxvcl_app_destroy(void);
++
++int ngxvcl_socket(int domain, int type, int protocol);
++int ngxvcl_close(int offset_vfd);
++int ngxvcl_kvfd_close(int fd);
++int ngxvcl_bind(int offset_vfd, const struct sockaddr *addr, socklen_t addrlen);
++int ngxvcl_listen(int offset_vfd, int backlog);
++
++int ngxvcl_accept4(int offset_vfd, struct sockaddr *addr, socklen_t *addrlen,
++                   int flags);
++int ngxvcl_accept(int offset_vfd, struct sockaddr *addr, socklen_t *addrlen);
++
++int ngxvcl_connect(int offset_vfd, const struct sockaddr *addr, socklen_t addrlen);
++int ngxvcl_read(int offset_vfd, void *buf, size_t count);
++int ngxvcl_write(int offset_vfd, const void *buf, size_t count);
++
++int ngxvcl_epoll_create(int size);
++int ngxvcl_kvfd_epoll_ctl(int offset_vepfd, int op, int fd, struct epoll_event *event);
++int ngxvcl_kvfd_epoll_wait(int offset_vepfd, struct epoll_event *events, int maxevents,
++                           int timeout);
++
++ssize_t ngxvcl_readv(int offset_vfd, const struct iovec *iov, int iovcnt);
++ssize_t ngxvcl_writev(int offset_vfd, const struct iovec *iov, int iovcnt);
++
++int ngxvcl_kvfd_fcntl(int fd, int cmd, ...);
++int ngxvcl_kvfd_ioctl(int fd, unsigned long int cmd, ...);
++
++int ngxvcl_socketpair(int domain, int type, int protocol, int fds[2]);
++int ngxvcl_kvfd_getsockname(int fd, struct sockaddr *addr, socklen_t *len);
++int ngxvcl_kvfd_getsockopt(int fd, int level, int optname, void *optval,
++                           socklen_t *optlen);
++int ngxvcl_kvfd_setsockopt(int fd, int level, int optname, const void *optval,
++                           socklen_t optlen);
++
++ssize_t ngxvcl_send(int fd, const void *buf, size_t n, int flags);
++ssize_t ngxvcl_sendfile(int out_offset_vfd, int in_kfd, off_t *offset, size_t len);
++ssize_t ngxvcl_recv(int offset_vfd, void *buf, size_t n, int flags);
++ssize_t ngxvcl_sendto(int offset_vfd, const void *buf, size_t n, int flags,
++                      const struct sockaddr *addr, socklen_t addr_len);
++ssize_t ngxvcl_recvfrom(int offset_vfd, void *buf, size_t n, int flags,
++                        struct sockaddr *addr, socklen_t *addr_len);
++ssize_t ngxvcl_sendmsg(int offset_vfd, const struct msghdr *message, int flags);
++ssize_t ngxvcl_recvmsg(int offset_vfd, struct msghdr *message, int flags);
++
++int ngxvcl_shutdown(int offset_vfd, int how);
++
++#endif
+-- 
+2.17.1
+