diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b208c6f --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2002-2016 Igor Sysoev + * Copyright (C) 2011-2016 Nginx, Inc. + * Copyright (C) 2014-2017 Intel, 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/README.md b/README.md new file mode 100644 index 0000000..c5b756b --- /dev/null +++ b/README.md @@ -0,0 +1,181 @@ +# Intel® QuickAssist Technology (QAT) Async Mode Nginx + +## Table of Contents + +- [Introduction](#introduction) +- [Licensing](#licensing) +- [Features](#features) +- [Hardware Requirements](#hardware-requirements) +- [Software Requirements](#software-requirements) +- [Additional Information](#additional-information) +- [Limitations](#limitations) +- [Installation Instructions](#installation-instructions) + - [Install Async Mode Nginx](#install-async-mode-nginx) + - [Build OpenSSL\* and QAT engine](#build-openssl-and-qat-engine) +- [Intended Audience](#intended-audience) +- [Legal](#legal) + +## Introduction + +Nginx\* [engine x] is an HTTP and reverse proxy server, a mail proxy server, +and a generic TCP/UDP proxy server, originally written by Igor Sysoev. +This project provides an extended Nginx working with asynchronous mode OpenSSL\*. +With Intel® QuickAssist Technology(QAT) acceleration, the asynchronous mode Nginx +can provide significant performance improvement. + +## Licensing + +The Licensing of the files within this project is: + +Intel® Quickassist Technology (QAT) Async Mode Nginx - BSD License. Please +see the `LICENSE` file contained in the top level folder. Further details can +be found in the file headers of the relevant files. + +## Features + +* Asynchronous Mode in SSL/TLS processing + +## Hardware Requirements + +Async Mode Nginx supports Crypto offload to the following acceleration devices: + +* Intel® C62X Series Chipset +* [Intel® Communications Chipset 8925 to 8955 Series][1] + +[1]:https://www.intel.com/content/www/us/en/ethernet-products/gigabit-server-adapters/quickassist-adapter-8950-brief.html + +## Software Requirements + +This release was validated on the following: + +* OpenSSL-1.1.0f +* QAT engine v0.5.30 + +## Additional Information + +* Async Mode Nginx SSL engine framework provides new directives: + +**Directives** +```bash +Syntax: ssl_asynch on | off; +Default: ssl_asynch off; +Context: http, server + + Enables SSL/TLS asynchronous mode +``` +**Example** + +file: conf/nginx.conf + +```bash + http { + ssl_asynch on; + server { + ... + } + } + } +``` + +```bash + http { + server { + ssl_asynch on; + } + } + } +``` + +* [White Paper: Intel® Quickassist Technology and OpenSSL-1.1.0:Performance][2] + +[2]: https://01.org/sites/default/files/downloads/intelr-quickassist-technology/intelquickassisttechnologyopensslperformance.pdf + +## Limitations + +* Nginx supports `reload` operation, when QAT hardware is involved for crypto + offload, user should enure that there are enough number of qat instances. + For example, the available qat instance number should be 2x equal or more than + Nginx worker process number. + +For example, in Nginx configuration file (`nginx.conf`) worker process number +is configured as + +```bash + worker_processes 16; +``` + +Then the instance configuration in QAT driver configuration file should be + +```bash + [SHIM] + NumberCyInstances = 1 + NumberDcInstances = 0 + NumProcesses = 32 + LimitDevAccess = 1 +``` + +## Installation Instructions + +### Install Async Mode Nginx + +**Set the following environmental variables:** + +`NGINX_INSTALL_DIR` is the directory where nginx will be installed to + +`OPENSSL_LIB` is the directory where the openssl has been installed to + +**Configure nginx for compilation:** + +```bash + ./configure \ + --prefix=$NGINX_INSTALL_DIR \ + --with-http_ssl_module \ + --with-cc-opt="-DNGX_SECURE_MEM -I$OPENSSL_LIB/include -Wno-error=deprecated-declarations" \ + --with-ld-opt="-Wl,-rpath=$OPENSSL_LIB/lib -L$OPENSSL_LIB/lib" +``` + +**Compile and Install:** + +```bash + make + make install +``` + +### Build OpenSSL\* and QAT engine + +These instructions can be found on [QAT engine][4] + +[4]: https://github.com/intel/QAT_Engine#installation-instructions + + +## Intended Audience + +The target audience may be software developers, test and validation engineers, +system integrators, end users and consumers for Async Mode Nginx integrated +Intel® Quick Assist Technology. + +## Legal + +Intel® disclaims all express and implied warranties, including without +limitation, the implied warranties of merchantability, fitness for a +particular purpose, and non-infringement, as well as any warranty arising +from course of performance, course of dealing, or usage in trade. + +This document contains information on products, services and/or processes in +development. All information provided here is subject to change without +notice. Contact your Intel® representative to obtain the latest forecast +, schedule, specifications and roadmaps. + +The products and services described may contain defects or errors known as +errata which may cause deviations from published specifications. Current +characterized errata are available on request. + +Copies of documents which have an order number and are referenced in this +document may be obtained by calling 1-800-548-4725 or by visiting +www.intel.com/design/literature.htm. + +Intel, the Intel logo are trademarks of Intel Corporation in the U.S. +and/or other countries. + +\*Other names and brands may be claimed as the property of others + diff --git a/auto/cc/acc b/auto/cc/acc new file mode 100644 index 0000000..6baee67 --- /dev/null +++ b/auto/cc/acc @@ -0,0 +1,15 @@ + +# 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" +MD5_OPT="$MD5_OPT -Ae" diff --git a/auto/cc/bcc b/auto/cc/bcc new file mode 100644 index 0000000..ec82e60 --- /dev/null +++ b/auto/cc/bcc @@ -0,0 +1,72 @@ + +# 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_binext=".exe" + +ngx_long_start='@&&| + ' +ngx_long_end='|' + +ngx_regex_dirsep='\\' +ngx_dirsep="\\" diff --git a/auto/cc/ccc b/auto/cc/ccc new file mode 100644 index 0000000..c964045 --- /dev/null +++ b/auto/cc/ccc @@ -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/auto/cc/clang b/auto/cc/clang new file mode 100644 index 0000000..25707b4 --- /dev/null +++ b/auto/cc/clang @@ -0,0 +1,104 @@ + +# Copyright (C) Nginx, Inc. + + +# clang + + +NGX_CLANG_VER=`$CC -v 2>&1 | grep '\(clang\|LLVM\) 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 [ ".$MD5_OPT" = "." ]; then + MD5_OPT="-O2 -pipe $CPU_OPT" +else + MD5_OPT="$MD5_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/auto/cc/conf b/auto/cc/conf new file mode 100644 index 0000000..f2c25ed --- /dev/null +++ b/auto/cc/conf @@ -0,0 +1,250 @@ + +# 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 + + + 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 + + + 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 +#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 +#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="__builtin_bswap64(0)" + . 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/auto/cc/gcc b/auto/cc/gcc new file mode 100644 index 0000000..3d738d8 --- /dev/null +++ b/auto/cc/gcc @@ -0,0 +1,190 @@ + +# 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 -D_FORTIFY_SOURCE=2" +#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 [ ".$MD5_OPT" = "." ]; then + MD5_OPT="-O2 -fomit-frame-pointer $PIPE $CPU_OPT" +else + MD5_OPT="$MD5_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 + [3-5].*) + # 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 + ;; + + *) + # we have a lot of the unused function arguments + CFLAGS="$CFLAGS -Wno-unused" + ;; +esac + + +# stop on warning +CFLAGS="$CFLAGS -Werror" + +# debug +CFLAGS="$CFLAGS -g" + +# DragonFly's gcc3 generates DWARF +#CFLAGS="$CFLAGS -g -gstabs" + +# required by Intel SDL +CFLAGS="$CFLAGS -fstack-protector -fPIE -fPIC -Wformat -Wformat-security" +CORE_LINK="-z noexecstack -z relro -z now -pie" + +if [ ".$CPP" = "." ]; then + CPP="$CC -E" +fi diff --git a/auto/cc/icc b/auto/cc/icc new file mode 100644 index 0000000..1c0df1a --- /dev/null +++ b/auto/cc/icc @@ -0,0 +1,121 @@ + +# 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 [ ".$MD5_OPT" = "." ]; then + MD5_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/auto/cc/msvc b/auto/cc/msvc new file mode 100644 index 0000000..4eef101 --- /dev/null +++ b/auto/cc/msvc @@ -0,0 +1,157 @@ + +# 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_binext=".exe" + +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/auto/cc/name b/auto/cc/name new file mode 100644 index 0000000..35d319e --- /dev/null +++ b/auto/cc/name @@ -0,0 +1,66 @@ + +# 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\|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/auto/cc/owc b/auto/cc/owc new file mode 100644 index 0000000..a063aa3 --- /dev/null +++ b/auto/cc/owc @@ -0,0 +1,104 @@ + +# 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_binext=".exe" + +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/auto/cc/sunc b/auto/cc/sunc new file mode 100644 index 0000000..8360c49 --- /dev/null +++ b/auto/cc/sunc @@ -0,0 +1,161 @@ + +# 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 Sun Studio 12.2 + +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() { printf("%d", __SUNPRO_C); } + +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 [ ".$MD5_OPT" = "." ]; then + MD5_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/auto/configure b/auto/configure new file mode 100644 index 0000000..ceff15e --- /dev/null +++ b/auto/configure @@ -0,0 +1,116 @@ +#!/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 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_*) + 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/auto/define b/auto/define new file mode 100644 index 0000000..b5a7622 --- /dev/null +++ b/auto/define @@ -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/auto/endianness b/auto/endianness new file mode 100644 index 0000000..70b0a10 --- /dev/null +++ b/auto/endianness @@ -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() { + 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/auto/feature b/auto/feature new file mode 100644 index 0000000..1145f28 --- /dev/null +++ b/auto/feature @@ -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 +$NGX_INCLUDE_UNISTD_H +$ngx_feature_incs + +int main() { + $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/auto/have b/auto/have new file mode 100644 index 0000000..f8e3751 --- /dev/null +++ b/auto/have @@ -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/auto/have_headers b/auto/have_headers new file mode 100644 index 0000000..a3a7543 --- /dev/null +++ b/auto/have_headers @@ -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/auto/headers b/auto/headers new file mode 100644 index 0000000..5a2e6b9 --- /dev/null +++ b/auto/headers @@ -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/auto/include b/auto/include new file mode 100644 index 0000000..e34dabd --- /dev/null +++ b/auto/include @@ -0,0 +1,61 @@ + +# 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() { + 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>'" + + #STUB + eval "NGX_$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/auto/init b/auto/init new file mode 100644 index 0000000..910f529 --- /dev/null +++ b/auto/init @@ -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/auto/install b/auto/install new file mode 100644 index 0000000..d884487 --- /dev/null +++ b/auto/install @@ -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/auto/lib/conf b/auto/lib/conf new file mode 100644 index 0000000..a6242e7 --- /dev/null +++ b/auto/lib/conf @@ -0,0 +1,83 @@ + +# 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_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_MD5 = YES ]; then + + if [ $USE_OPENSSL = YES ]; then + have=NGX_HAVE_OPENSSL_MD5_H . auto/have + have=NGX_OPENSSL_MD5 . auto/have + have=NGX_HAVE_MD5 . auto/have + MD5=YES + MD5_LIB=OpenSSL + + else + . auto/lib/md5/conf + fi + +fi + +if [ $USE_SHA1 = YES ]; then + + if [ $USE_OPENSSL = YES ]; then + have=NGX_HAVE_OPENSSL_SHA1_H . auto/have + have=NGX_HAVE_SHA1 . auto/have + SHA1=YES + SHA1_LIB=OpenSSL + + else + . auto/lib/sha1/conf + fi + +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/auto/lib/geoip/conf b/auto/lib/geoip/conf new file mode 100644 index 0000000..ebd2e15 --- /dev/null +++ b/auto/lib/geoip/conf @@ -0,0 +1,99 @@ + +# Copyright (C) Igor Sysoev +# Copyright (C) Nginx, Inc. + + + ngx_feature="GeoIP library" + ngx_feature_name= + ngx_feature_run=no + ngx_feature_incs="#include " + 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 + + if [ $NGX_IPV6 = YES ]; then + ngx_feature="GeoIP IPv6 support" + ngx_feature_name="NGX_HAVE_GEOIP_V6" + ngx_feature_run=no + ngx_feature_incs="#include + #include " + #ngx_feature_path= + #ngx_feature_libs= + ngx_feature_test="printf(\"%d\", GEOIP_CITY_EDITION_REV0_V6);" + . auto/feature + fi + +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/auto/lib/google-perftools/conf b/auto/lib/google-perftools/conf new file mode 100644 index 0000000..5d5ddae --- /dev/null +++ b/auto/lib/google-perftools/conf @@ -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/auto/lib/libatomic/conf b/auto/lib/libatomic/conf new file mode 100644 index 0000000..d1e484a --- /dev/null +++ b/auto/lib/libatomic/conf @@ -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 " + 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/auto/lib/libatomic/make b/auto/lib/libatomic/make new file mode 100644 index 0000000..c90318e --- /dev/null +++ b/auto/lib/libatomic/make @@ -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/auto/lib/libgd/conf b/auto/lib/libgd/conf new file mode 100644 index 0000000..6e4e91c --- /dev/null +++ b/auto/lib/libgd/conf @@ -0,0 +1,88 @@ + +# Copyright (C) Igor Sysoev +# Copyright (C) Nginx, Inc. + + + ngx_feature="GD library" + ngx_feature_name= + ngx_feature_run=no + ngx_feature_incs="#include " + 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 + +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/auto/lib/libxslt/conf b/auto/lib/libxslt/conf new file mode 100644 index 0000000..3a0f37b --- /dev/null +++ b/auto/lib/libxslt/conf @@ -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 + #include + #include + #include + #include + #include " + 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 " + 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/auto/lib/make b/auto/lib/make new file mode 100644 index 0000000..6298b94 --- /dev/null +++ b/auto/lib/make @@ -0,0 +1,32 @@ + +# Copyright (C) Igor Sysoev +# Copyright (C) Nginx, Inc. + + +if [ $PCRE != NONE -a $PCRE != NO -a $PCRE != YES ]; then + . auto/lib/pcre/make +fi + +if [ $MD5 != NONE -a $MD5 != NO -a $MD5 != YES ]; then + . auto/lib/md5/make +fi + +if [ $SHA1 != NONE -a $SHA1 != NO -a $SHA1 != YES ]; then + . auto/lib/sha1/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/auto/lib/md5/conf b/auto/lib/md5/conf new file mode 100644 index 0000000..49c0ddf --- /dev/null +++ b/auto/lib/md5/conf @@ -0,0 +1,103 @@ + +# Copyright (C) Igor Sysoev +# Copyright (C) Nginx, Inc. + + +if [ $MD5 != NONE ]; then + + if grep MD5_Init $MD5/md5.h 2>&1 >/dev/null; then + # OpenSSL md5 + OPENSSL_MD5=YES + have=NGX_HAVE_OPENSSL_MD5 . auto/have + have=NGX_OPENSSL_MD5 . auto/have + else + # rsaref md5 + OPENSSL_MD5=NO + fi + + have=NGX_HAVE_MD5 . auto/have + CORE_INCS="$CORE_INCS $MD5" + + case "$NGX_CC_NAME" in + + msvc | owc | bcc) + LINK_DEPS="$LINK_DEPS $MD5/md5.lib" + CORE_LIBS="$CORE_LIBS $MD5/md5.lib" + ;; + + icc) + LINK_DEPS="$LINK_DEPS $MD5/libmd5.a" + + # to allow -ipo optimization we link with the *.o but not library + CORE_LIBS="$CORE_LIBS $MD5/md5_dgst.o" + + if [ $MD5_ASM = YES ]; then + CORE_LIBS="$CORE_LIBS $MD5/asm/mx86-elf.o" + fi + ;; + + *) + LINK_DEPS="$LINK_DEPS $MD5/libmd5.a" + CORE_LIBS="$CORE_LIBS $MD5/libmd5.a" + #CORE_LIBS="$CORE_LIBS -L $MD5 -lmd5" + ;; + + esac + +else + + if [ "$NGX_PLATFORM" != win32 ]; then + + MD5=NO + + # FreeBSD, Solaris 10 + + ngx_feature="md5 in system md library" + ngx_feature_name=NGX_HAVE_MD5 + ngx_feature_run=no + ngx_feature_incs="#include " + ngx_feature_path= + ngx_feature_libs="-lmd" + ngx_feature_test="MD5_CTX md5; MD5Init(&md5)" + . auto/feature + + ngx_md5_lib="system md" + + if [ $ngx_found = no ]; then + + # Solaris 8/9 + + ngx_feature="md5 in system md5 library" + ngx_feature_libs="-lmd5" + . auto/feature + + ngx_md5_lib="system md5" + fi + + if [ $ngx_found = no ]; then + + # OpenSSL crypto library + + ngx_feature="md5 in system OpenSSL crypto library" + ngx_feature_name="NGX_OPENSSL_MD5" + ngx_feature_incs="#include " + ngx_feature_libs="-lcrypto" + ngx_feature_test="MD5_CTX md5; MD5_Init(&md5)" + . auto/feature + + ngx_md5_lib="system crypto" + + if [ $ngx_found = yes ]; then + have=NGX_HAVE_OPENSSL_MD5_H . auto/have + have=NGX_HAVE_MD5 . auto/have + fi + fi + + if [ $ngx_found = yes ]; then + CORE_LIBS="$CORE_LIBS $ngx_feature_libs" + MD5=YES + MD5_LIB=$ngx_md5_lib + fi + fi + +fi diff --git a/auto/lib/md5/make b/auto/lib/md5/make new file mode 100644 index 0000000..7000b20 --- /dev/null +++ b/auto/lib/md5/make @@ -0,0 +1,96 @@ + +# 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 MD5_ASM=$MD5_ASM" + ngx_md5="MD5=\"$MD5\"" + ;; + + owc) + ngx_makefile=makefile.owc + ngx_opt="CPU_OPT=\"$CPU_OPT\"" + ngx_md5=`echo MD5=\"$MD5\" | sed -e "s/\//$ngx_regex_dirsep/g"` + ;; + + bcc) + ngx_makefile=makefile.bcc + ngx_opt="-DCPU_OPT=\"$CPU_OPT\" -DMD5_ASM=$MD5_ASM" + ngx_md5=`echo \-DMD5=\"$MD5\" | sed -e "s/\//$ngx_regex_dirsep/g"` + ;; + +esac + + +done=NO + + +case "$NGX_PLATFORM" in + + win32) + cat << END >> $NGX_MAKEFILE + +`echo "$MD5/md5.lib: $NGX_MAKEFILE" | sed -e "s/\//$ngx_regex_dirsep/g"` + \$(MAKE) -f auto/lib/md5/$ngx_makefile $ngx_opt $ngx_md5 + +END + + done=YES + ;; + + SunOS:*:i86pc) + if [ $MD5_ASM = YES ]; then + + cat << END >> $NGX_MAKEFILE + +$MD5/libmd5.a: $NGX_MAKEFILE + cd $MD5 \\ + && \$(MAKE) CFLAGS="$MD5_OPT -DSOL -DMD5_ASM -DL_ENDIAN" \\ + CC="\$(CC)" CPP="\$(CPP)" \\ + MD5_ASM_OBJ=asm/mx86-sol.o clean libmd5.a + +END + + done=YES + fi + ;; + + # FreeBSD: i386 + # Linux: i686 + + *:i386 | *:i686) + if [ $MD5_ASM = YES ]; then + + cat << END >> $NGX_MAKEFILE + +$MD5/libmd5.a: $NGX_MAKEFILE + cd $MD5 \\ + && \$(MAKE) CFLAGS="$MD5_OPT -DELF -DMD5_ASM -DL_ENDIAN" \\ + CC="\$(CC)" CPP="\$(CPP)" \\ + MD5_ASM_OBJ=asm/mx86-elf.o clean libmd5.a + +END + + done=YES + fi + ;; + +esac + + +if [ $done = NO ]; then + + cat << END >> $NGX_MAKEFILE + +$MD5/libmd5.a: $NGX_MAKEFILE + cd $MD5 \\ + && \$(MAKE) CFLAGS="$MD5_OPT" \\ + CC="\$(CC)" MD5_ASM_OBJ= clean libmd5.a + +END + +fi diff --git a/auto/lib/md5/makefile.bcc b/auto/lib/md5/makefile.bcc new file mode 100644 index 0000000..eb6fb62 --- /dev/null +++ b/auto/lib/md5/makefile.bcc @@ -0,0 +1,22 @@ + +# Copyright (C) Igor Sysoev +# Copyright (C) Nginx, Inc. + + +CFLAGS = -q -O2 -tWM $(CPU_OPT) -DL_ENDIAN + +!if "$(MD5_ASM)" == "YES" + +md5.lib: + cd $(MD5) + bcc32 -c $(CFLAGS) -DMD5_ASM md5_dgst.c + tlib md5.lib +md5_dgst.obj +"asm\m-win32.obj" + +!else + +md5.lib: + cd $(MD5) + bcc32 -c $(CFLAGS) md5_dgst.c + tlib md5.lib +md5_dgst.obj + +!endif diff --git a/auto/lib/md5/makefile.msvc b/auto/lib/md5/makefile.msvc new file mode 100644 index 0000000..90d62fa --- /dev/null +++ b/auto/lib/md5/makefile.msvc @@ -0,0 +1,22 @@ + +# Copyright (C) Igor Sysoev +# Copyright (C) Nginx, Inc. + + +CFLAGS = -nologo -O2 -Ob1 -Oi -Gs $(LIBC) $(CPU_OPT) -D L_ENDIAN + +!IF "$(MD5_ASM)" == "YES" + +md5.lib: + cd $(MD5) + cl -c $(CFLAGS) -D MD5_ASM md5_dgst.c + link -lib -out:md5.lib md5_dgst.obj asm/m-win32.obj + +!ELSE + +md5.lib: + cd $(MD5) + cl -c $(CFLAGS) md5_dgst.c + link -lib -out:md5.lib md5_dgst.obj + +!ENDIF diff --git a/auto/lib/md5/makefile.owc b/auto/lib/md5/makefile.owc new file mode 100644 index 0000000..78c1e61 --- /dev/null +++ b/auto/lib/md5/makefile.owc @@ -0,0 +1,11 @@ + +# Copyright (C) Igor Sysoev +# Copyright (C) Nginx, Inc. + + +CFLAGS = -zq -bt=nt -bm -ot -op -oi -oe -s $(CPU_OPT) + +md5.lib: + cd $(MD5) + wcl386 -c $(CFLAGS) -dL_ENDIAN md5_dgst.c + wlib -n md5.lib md5_dgst.obj diff --git a/auto/lib/openssl/conf b/auto/lib/openssl/conf new file mode 100644 index 0000000..39d9602 --- /dev/null +++ b/auto/lib/openssl/conf @@ -0,0 +1,127 @@ + +# 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" + CORE_LIBS="$CORE_LIBS $OPENSSL/openssl/lib/ssleay32.lib" + CORE_LIBS="$CORE_LIBS $OPENSSL/openssl/lib/libeay32.lib" + + # 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" + + 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 " + ngx_feature_path= + ngx_feature_libs="-lssl -lcrypto $NGX_LIBDL" + 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 $NGX_LIBDL" + else + ngx_feature_libs="-L/usr/local/lib -lssl -lcrypto $NGX_LIBDL" + fi + + . 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 $NGX_LIBDL" + else + ngx_feature_libs="-L/usr/pkg/lib -lssl -lcrypto $NGX_LIBDL" + fi + + . 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 $NGX_LIBDL" + else + ngx_feature_libs="-L/opt/local/lib -lssl -lcrypto $NGX_LIBDL" + fi + + . 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= option. + +END + exit 1 + fi + +fi diff --git a/auto/lib/openssl/make b/auto/lib/openssl/make new file mode 100644 index 0000000..a6090c6 --- /dev/null +++ b/auto/lib/openssl/make @@ -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 $OPENSSL_OPT \\ + && \$(MAKE) \\ + && \$(MAKE) install_sw LIBDIR=lib + +END + + ;; + +esac diff --git a/auto/lib/openssl/makefile.bcc b/auto/lib/openssl/makefile.bcc new file mode 100644 index 0000000..6a94ff7 --- /dev/null +++ b/auto/lib/openssl/makefile.bcc @@ -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/auto/lib/openssl/makefile.msvc b/auto/lib/openssl/makefile.msvc new file mode 100644 index 0000000..fc9e578 --- /dev/null +++ b/auto/lib/openssl/makefile.msvc @@ -0,0 +1,14 @@ + +# Copyright (C) Igor Sysoev +# Copyright (C) Nginx, Inc. + + +all: + cd $(OPENSSL) + + perl Configure VC-WIN32 no-shared --prefix=openssl $(OPENSSL_OPT) + + ms\do_ms + + $(MAKE) -f ms\nt.mak + $(MAKE) -f ms\nt.mak install diff --git a/auto/lib/pcre/conf b/auto/lib/pcre/conf new file mode 100644 index 0000000..5e3960f --- /dev/null +++ b/auto/lib/pcre/conf @@ -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 " + 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= option. + +END + exit 1 + fi + +fi diff --git a/auto/lib/pcre/make b/auto/lib/pcre/make new file mode 100644 index 0000000..97c9f3b --- /dev/null +++ b/auto/lib/pcre/make @@ -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/auto/lib/pcre/makefile.bcc b/auto/lib/pcre/makefile.bcc new file mode 100644 index 0000000..7a0f2be --- /dev/null +++ b/auto/lib/pcre/makefile.bcc @@ -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/auto/lib/pcre/makefile.msvc b/auto/lib/pcre/makefile.msvc new file mode 100644 index 0000000..07fd9a2 --- /dev/null +++ b/auto/lib/pcre/makefile.msvc @@ -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/auto/lib/pcre/makefile.owc b/auto/lib/pcre/makefile.owc new file mode 100644 index 0000000..122fd5b --- /dev/null +++ b/auto/lib/pcre/makefile.owc @@ -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/auto/lib/perl/conf b/auto/lib/perl/conf new file mode 100644 index 0000000..f5f5d3e --- /dev/null +++ b/auto/lib/perl/conf @@ -0,0 +1,81 @@ + +# 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.006001; print "OK"'`" != "OK" ]; then + echo + echo "$0: error: perl 5.6.1 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_PERL_CFLAGS="$CFLAGS `$NGX_PERL -MExtUtils::Embed -e ccopts`" + NGX_PM_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.6.1 or higher is required" + echo + + exit 1; +fi diff --git a/auto/lib/perl/make b/auto/lib/perl/make new file mode 100644 index 0000000..8af8902 --- /dev/null +++ b/auto/lib/perl/make @@ -0,0 +1,44 @@ + +# Copyright (C) Igor Sysoev +# Copyright (C) Nginx, Inc. + + +v=`grep 'define NGINX_VERSION' src/core/nginx.h | sed -e 's/^.*"\(.*\)".*/\1/'` + + +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 + sed "s/%%VERSION%%/$v/" 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_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/auto/lib/sha1/conf b/auto/lib/sha1/conf new file mode 100644 index 0000000..78f9efd --- /dev/null +++ b/auto/lib/sha1/conf @@ -0,0 +1,79 @@ + +# Copyright (C) Igor Sysoev +# Copyright (C) Nginx, Inc. + + +if [ $SHA1 != NONE ]; then + + have=NGX_HAVE_SHA1 . auto/have + CORE_INCS="$CORE_INCS $SHA1" + + case "$NGX_CC_NAME" in + + msvc | owc | bcc) + LINK_DEPS="$LINK_DEPS $SHA1/sha1.lib" + CORE_LIBS="$CORE_LIBS $SHA1/sha1.lib" + ;; + + icc) + LINK_DEPS="$LINK_DEPS $SHA1/libsha.a" + + # to allow -ipo optimization we link with the *.o but not library + CORE_LIBS="$CORE_LIBS $SHA1/sha1_dgst.o" + + if [ $SHA1_ASM = YES ]; then + CORE_LIBS="$CORE_LIBS $SHA1/asm/sx86-elf.o" + fi + ;; + + *) + LINK_DEPS="$LINK_DEPS $SHA1/libsha.a" + CORE_LIBS="$CORE_LIBS $SHA1/libsha.a" + #CORE_LIBS="$CORE_LIBS -L $SHA1 -lsha" + ;; + + esac + +else + + if [ "$NGX_PLATFORM" != win32 ]; then + + SHA1=NO + + # FreeBSD + + ngx_feature="sha1 in system md library" + ngx_feature_name=NGX_HAVE_SHA1 + ngx_feature_run=no + ngx_feature_incs="#include " + ngx_feature_path= + ngx_feature_libs="-lmd" + ngx_feature_test="SHA_CTX sha1; SHA1_Init(&sha1)" + . auto/feature + + ngx_sha1_lib="system md" + + if [ $ngx_found = no ]; then + + # OpenSSL crypto library + + ngx_feature="sha1 in system OpenSSL crypto library" + ngx_feature_incs="#include " + ngx_feature_libs="-lcrypto" + . auto/feature + + ngx_sha1_lib="system crypto" + + if [ $ngx_found = yes ]; then + have=NGX_HAVE_OPENSSL_SHA1_H . auto/have + fi + fi + + if [ $ngx_found = yes ]; then + CORE_LIBS="$CORE_LIBS $ngx_feature_libs" + SHA1=YES + SHA1_LIB=$ngx_sha1_lib + fi + fi + +fi diff --git a/auto/lib/sha1/make b/auto/lib/sha1/make new file mode 100644 index 0000000..fc3b340 --- /dev/null +++ b/auto/lib/sha1/make @@ -0,0 +1,96 @@ + +# 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 SHA1_ASM=$SHA1_ASM" + ngx_sha1="SHA1=\"$SHA1\"" + ;; + + owc) + ngx_makefile=makefile.owc + ngx_opt="CPU_OPT=\"$CPU_OPT\"" + ngx_sha1=`echo SHA1=\"$SHA1\" | sed -e "s/\//$ngx_regex_dirsep/g"` + ;; + + bcc) + ngx_makefile=makefile.bcc + ngx_opt="-DCPU_OPT=\"$CPU_OPT\" -DSHA1_ASM=$SHA1_ASM" + ngx_sha1=`echo \-DSHA1=\"$SHA1\" | sed -e "s/\//$ngx_regex_dirsep/g"` + ;; + +esac + + +done=NO + + +case "$NGX_PLATFORM" in + + win32) + cat << END >> $NGX_MAKEFILE + +`echo "$SHA1/sha1.lib: $NGX_MAKEFILE" | sed -e "s/\//$ngx_regex_dirsep/g"` + \$(MAKE) -f auto/lib/sha1/$ngx_makefile $ngx_opt $ngx_sha1 + +END + + done=YES + ;; + + SunOS:*:i86pc) + if [ $SHA1_ASM = YES ]; then + + cat << END >> $NGX_MAKEFILE + +$SHA1/libsha.a: $NGX_MAKEFILE + cd $SHA1 \\ + && \$(MAKE) CFLAGS="$SHA1_OPT -DSOL -DSHA1_ASM -DL_ENDIAN" \\ + CC="\$(CC)" CPP="\$(CPP)" \\ + SHA_ASM_OBJ=asm/sx86-sol.o clean libsha.a + +END + + done=YES + fi + ;; + + # FreeBSD: i386 + # Linux: i686 + + *:i386 | *:i686) + if [ $SHA1_ASM = YES ]; then + + cat << END >> $NGX_MAKEFILE + +$SHA1/libsha.a: $NGX_MAKEFILE + cd $SHA1 \\ + && \$(MAKE) CFLAGS="$SHA1_OPT -DELF -DSHA1_ASM -DL_ENDIAN" \\ + CC="\$(CC)" CPP="\$(CPP)" \\ + SHA_ASM_OBJ=asm/sx86-elf.o clean libsha.a + +END + + done=YES + fi + ;; + +esac + + +if [ $done = NO ]; then + + cat << END >> $NGX_MAKEFILE + +$SHA1/libsha.a: $NGX_MAKEFILE + cd $SHA1 \\ + && \$(MAKE) CFLAGS="$SHA1_OPT" \\ + CC="\$(CC)" SHA_ASM_OBJ= clean libsha.a + +END + +fi diff --git a/auto/lib/sha1/makefile.bcc b/auto/lib/sha1/makefile.bcc new file mode 100644 index 0000000..b0685fa --- /dev/null +++ b/auto/lib/sha1/makefile.bcc @@ -0,0 +1,22 @@ + +# Copyright (C) Igor Sysoev +# Copyright (C) Nginx, Inc. + + +CFLAGS = -q -O2 -tWM $(CPU_OPT) -DL_ENDIAN + +!if "$(SHA1_ASM)" == "YES" + +sha1.lib: + cd $(SHA1) + bcc32 -c $(CFLAGS) -DSHA1_ASM sha1dgst.c + tlib sha1.lib +sha1dgst.obj +"asm\s-win32.obj" + +!else + +sha1.lib: + cd $(SHA1) + bcc32 -c $(CFLAGS) sha1dgst.c + tlib sha1.lib +sha1dgst.obj + +!endif diff --git a/auto/lib/sha1/makefile.msvc b/auto/lib/sha1/makefile.msvc new file mode 100644 index 0000000..3cbd21b --- /dev/null +++ b/auto/lib/sha1/makefile.msvc @@ -0,0 +1,22 @@ + +# Copyright (C) Igor Sysoev +# Copyright (C) Nginx, Inc. + + +CFLAGS = -nologo -O2 -Ob1 -Oi -Gs $(LIBC) $(CPU_OPT) -D L_ENDIAN + +!IF "$(SHA1_ASM)" == "YES" + +sha1.lib: + cd $(SHA1) + cl -c $(CFLAGS) -D SHA1_ASM sha1dgst.c + link -lib -out:sha1.lib sha1dgst.obj asm/s-win32.obj + +!ELSE + +sha1.lib: + cd $(SHA1) + cl -c $(CFLAGS) sha1dgst.c + link -lib -out:sha1.lib sha1dgst.obj + +!ENDIF diff --git a/auto/lib/sha1/makefile.owc b/auto/lib/sha1/makefile.owc new file mode 100644 index 0000000..fc095cc --- /dev/null +++ b/auto/lib/sha1/makefile.owc @@ -0,0 +1,11 @@ + +# Copyright (C) Igor Sysoev +# Copyright (C) Nginx, Inc. + + +CFLAGS = -zq -bt=nt -bm -ot -op -oi -oe -s $(CPU_OPT) + +sha1.lib: + cd $(SHA1) + wcl386 -c $(CFLAGS) -dL_ENDIAN sha1dgst.c + wlib -n sha1.lib sha1dgst.obj diff --git a/auto/lib/test b/auto/lib/test new file mode 100644 index 0000000..ba943a2 --- /dev/null +++ b/auto/lib/test @@ -0,0 +1,40 @@ + +# Copyright (C) Igor Sysoev +# Copyright (C) Nginx, Inc. + + +echo $ngx_n "checking for $ngx_lib ...$ngx_c" + +cat << END >> $NGX_AUTOCONF_ERR + +---------------------------------------- +checking for $ngx_lib + +END + +ngx_found=no + +cat << END > $NGX_AUTOTEST.c + +$ngx_lib_incs + +int main() { + $ngx_lib_test; + return 0; +} + + +eval "$CC $cc_test_flags $ngx_lib_cflags \ + -o $NGX_AUTOTEST $NGX_AUTOTEST.c $ngx_libs \ + >> $NGX_ERR 2>&1" + +if [ -x $NGX_AUTOTEST ]; then + echo " found" + + ngx_found=yes + +else + echo " not found" +fi + +rm -rf $NGX_AUTOTEST* diff --git a/auto/lib/zlib/conf b/auto/lib/zlib/conf new file mode 100644 index 0000000..239592e --- /dev/null +++ b/auto/lib/zlib/conf @@ -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 " + 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= option. + +END + exit 1 + fi + +fi diff --git a/auto/lib/zlib/make b/auto/lib/zlib/make new file mode 100644 index 0000000..0082ad5 --- /dev/null +++ b/auto/lib/zlib/make @@ -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/auto/lib/zlib/makefile.bcc b/auto/lib/zlib/makefile.bcc new file mode 100644 index 0000000..97a30ea --- /dev/null +++ b/auto/lib/zlib/makefile.bcc @@ -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/auto/lib/zlib/makefile.msvc b/auto/lib/zlib/makefile.msvc new file mode 100644 index 0000000..6fbd691 --- /dev/null +++ b/auto/lib/zlib/makefile.msvc @@ -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/auto/lib/zlib/makefile.owc b/auto/lib/zlib/makefile.owc new file mode 100644 index 0000000..9e123be --- /dev/null +++ b/auto/lib/zlib/makefile.owc @@ -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/auto/make b/auto/make new file mode 100644 index 0000000..5589bee --- /dev/null +++ b/auto/make @@ -0,0 +1,682 @@ + +# 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 +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"` + +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"; 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/"`} + + +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_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_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 + +ngx_obj_deps="\$(CORE_DEPS)" +if [ $HTTP != NO ]; then + ngx_obj_deps="$ngx_obj_deps \$(HTTP_DEPS)" +fi +if [ $MAIL != NO ]; then + ngx_obj_deps="$ngx_obj_deps \$(MAIL_DEPS)" +fi +if [ $STREAM != NO ]; then + ngx_obj_deps="$ngx_obj_deps \$(STREAM_DEPS)" +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 +#include + +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: $ngx_obj_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: $ngx_obj_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/auto/module b/auto/module new file mode 100644 index 0000000..16a816f --- /dev/null +++ b/auto/module @@ -0,0 +1,122 @@ + +# 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 + + 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 | MD5 | SHA1 | ZLIB) + eval USE_${lib}=YES + ;; + + *) + 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 | MD5 | SHA1 | ZLIB | LIBXSLT | LIBGD | PERL | GEOIP) + eval USE_${lib}=YES + ;; + + *) + 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 | MD5 | SHA1 | ZLIB | LIBXSLT | LIBGD | PERL | GEOIP) + eval USE_${lib}=YES + ;; + + *) + CORE_LIBS="$CORE_LIBS $lib" + ;; + + esac + done +fi diff --git a/auto/modules b/auto/modules new file mode 100644 index 0000000..22ff6d9 --- /dev/null +++ b/auto/modules @@ -0,0 +1,1271 @@ + +# 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_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 + + +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 != YES ]; then + have=NGX_CRYPT . auto/nohave + CRYPT_LIB= +fi + + +if [ $HTTP_CACHE = YES ]; then + USE_MD5=YES + 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 + + 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_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_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 + USE_MD5=YES + USE_SHA1=YES + 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 + #USE_MD5=YES + + 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_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 + USE_MD5=YES + + 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 + + +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_proxy_module \ + ngx_stream_upstream_module" + ngx_module_incs="src/stream" + ngx_module_deps="src/stream/ngx_stream.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_handler.c \ + src/stream/ngx_stream_core_module.c \ + src/stream/ngx_stream_proxy_module.c \ + src/stream/ngx_stream_upstream.c \ + src/stream/ngx_stream_upstream_round_robin.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 + + . 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 + + . 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 + + . 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 + + . 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 + + . 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 + + . 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=$MAIL_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=$STREAM_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" + + +cat << END > $NGX_MODULES_C + +#include +#include + +$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/auto/nohave b/auto/nohave new file mode 100644 index 0000000..dfb1718 --- /dev/null +++ b/auto/nohave @@ -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/auto/options b/auto/options new file mode 100644 index 0000000..ac8beb1 --- /dev/null +++ b/auto/options @@ -0,0 +1,590 @@ + +# 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 +NGX_IPV6=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_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_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_LIMIT_CONN=YES +STREAM_ACCESS=YES +STREAM_UPSTREAM_HASH=YES +STREAM_UPSTREAM_LEAST_CONN=YES +STREAM_UPSTREAM_ZONE=YES + +DYNAMIC_MODULES= + +NGX_ADDONS= +NGX_ADDON_DEPS= +DYNAMIC_ADDONS= + +USE_PCRE=NO +PCRE=NONE +PCRE_OPT= +PCRE_CONF_OPT= +PCRE_JIT=NO + +USE_OPENSSL=NO +OPENSSL=NONE + +USE_MD5=NO +MD5=NONE +MD5_OPT= +MD5_ASM=NO + +USE_SHA1=NO +SHA1=NONE +SHA1_OPT= +SHA1_ASM=NO + +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_IPV6=YES ;; + + --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_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_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 ;; + --without-stream_limit_conn_module) + STREAM_LIMIT_CONN=NO ;; + --without-stream_access_module) STREAM_ACCESS=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-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-md5=*) MD5="$value" ;; + --with-md5-opt=*) MD5_OPT="$value" ;; + --with-md5-asm) MD5_ASM=YES ;; + + --with-sha1=*) SHA1="$value" ;; + --with-sha1-opt=*) SHA1_OPT="$value" ;; + --with-sha1-asm) SHA1_ASM=YES ;; + + --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-ipv6 enable IPv6 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_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_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 + --without-stream_limit_conn_module disable ngx_stream_limit_conn_module + --without-stream_access_module disable ngx_stream_access_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-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-md5=DIR set path to md5 library sources + --with-md5-opt=OPTIONS set additional build options for md5 + --with-md5-asm use md5 assembler sources + + --with-sha1=DIR set path to sha1 library sources + --with-sha1-opt=OPTIONS set additional build options for sha1 + --with-sha1-asm use sha1 assembler sources + + --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 [ $HTTP = NO ]; then + HTTP_CHARSET=NO + HTTP_GZIP=NO + HTTP_SSI=NO + HTTP_USERID=NO + HTTP_ACCESS=NO + HTTP_STATUS=NO + HTTP_REWRITE=NO + HTTP_PROXY=NO + HTTP_FASTCGI=NO +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/auto/os/conf b/auto/os/conf new file mode 100644 index 0000000..6ad0e74 --- /dev/null +++ b/auto/os/conf @@ -0,0 +1,116 @@ + +# 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=' +' + ;; + + 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 + ;; + + *) + 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/auto/os/darwin b/auto/os/darwin new file mode 100644 index 0000000..9b31b1f --- /dev/null +++ b/auto/os/darwin @@ -0,0 +1,118 @@ + +# 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" + +# 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 + #include " +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 + #include " +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() + +CC_AUX_FLAGS="$CC_AUX_FLAGS" +ngx_feature="sendfile()" +ngx_feature_name="NGX_HAVE_SENDFILE" +ngx_feature_run=yes +ngx_feature_incs="#include + #include + #include + #include " +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 " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="int32_t lock, n; + n = OSAtomicCompareAndSwap32Barrier(0, 1, &lock)" +. auto/feature diff --git a/auto/os/freebsd b/auto/os/freebsd new file mode 100644 index 0000000..937ca20 --- /dev/null +++ b/auto/os/freebsd @@ -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/auto/os/linux b/auto/os/linux new file mode 100644 index 0000000..c932267 --- /dev/null +++ b/auto/os/linux @@ -0,0 +1,173 @@ + +# 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 " +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; + 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 " + 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 +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 + #include + #include " +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 + #include " +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 + #include " +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 " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) == -1) return 1" +. auto/feature + + +# sched_setaffinity() + +ngx_feature="sched_setaffinity()" +ngx_feature_name="NGX_HAVE_SCHED_SETAFFINITY" +ngx_feature_run=no +ngx_feature_incs="#include " +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 + + +# crypt_r() + +ngx_feature="crypt_r()" +ngx_feature_name="NGX_HAVE_GNU_CRYPT_R" +ngx_feature_run=no +ngx_feature_incs="#include " +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/auto/os/solaris b/auto/os/solaris new file mode 100644 index 0000000..d39df0b --- /dev/null +++ b/auto/os/solaris @@ -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 " +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 " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="int n = 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/auto/os/win32 b/auto/os/win32 new file mode 100644 index 0000000..14ae3b8 --- /dev/null +++ b/auto/os/win32 @@ -0,0 +1,44 @@ + +# 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= + +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 + +if [ $NGX_IPV6 = YES ]; then + have=NGX_HAVE_INET6 . auto/have +fi + +have=NGX_HAVE_IOCP . auto/have diff --git a/auto/sources b/auto/sources new file mode 100644 index 0000000..27849e6 --- /dev/null +++ b/auto/sources @@ -0,0 +1,253 @@ + +# 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_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_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/auto/stubs b/auto/stubs new file mode 100644 index 0000000..d8bc1f0 --- /dev/null +++ b/auto/stubs @@ -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/auto/summary b/auto/summary new file mode 100644 index 0000000..dc8fe4f --- /dev/null +++ b/auto/summary @@ -0,0 +1,96 @@ + +# 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 $MD5 in + YES) echo " + md5: using $MD5_LIB library" ;; + NONE) echo " + md5 library is not used" ;; + NO) echo " + using builtin md5 code" ;; + *) echo " + using md5 library: $MD5" ;; +esac + +case $SHA1 in + YES) echo " + sha1: using $SHA1_LIB library" ;; + NONE) echo " + sha1 library is not used" ;; + NO) echo " + sha1 library is not found" ;; + *) echo " + using sha1 library: $SHA1" ;; +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/auto/threads b/auto/threads new file mode 100644 index 0000000..381f07a --- /dev/null +++ b/auto/threads @@ -0,0 +1,20 @@ + +# 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" +fi diff --git a/auto/types/sizeof b/auto/types/sizeof new file mode 100644 index 0000000..b5b71bb --- /dev/null +++ b/auto/types/sizeof @@ -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 +#include +$NGX_INCLUDE_UNISTD_H +#include +#include +#include +$NGX_INCLUDE_INTTYPES_H +$NGX_INCLUDE_AUTO_CONFIG_H + +int main() { + 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/auto/types/typedef b/auto/types/typedef new file mode 100644 index 0000000..b55237e --- /dev/null +++ b/auto/types/typedef @@ -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 +#include +#include +#include +#include +#include +$NGX_INCLUDE_INTTYPES_H + +int main() { + $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/auto/types/uintptr_t b/auto/types/uintptr_t new file mode 100644 index 0000000..2b7212e --- /dev/null +++ b/auto/types/uintptr_t @@ -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 +$NGX_INTTYPES_H + +int main() { + 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/auto/types/value b/auto/types/value new file mode 100644 index 0000000..ac88a39 --- /dev/null +++ b/auto/types/value @@ -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/auto/unix b/auto/unix new file mode 100644 index 0000000..8c0e813 --- /dev/null +++ b/auto/unix @@ -0,0 +1,911 @@ + +# 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 " +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 " +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 " + ngx_feature_path= + ngx_feature_libs= + ngx_feature_test="int kq; kq = 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 " + ngx_feature_path= + ngx_feature_libs= + ngx_feature_test="struct kevent kev; + kev.fflags = NOTE_LOWAT;" + . auto/feature + + + ngx_feature="kqueue's EVFILT_TIMER" + ngx_feature_name="NGX_HAVE_TIMER_EVENT" + ngx_feature_run=yes + ngx_feature_incs="#include + #include " + 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 " +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 " +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 " +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 " +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 + #include " +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 + #include " +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 " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="dlopen(NULL, RTLD_NOW | RTLD_GLOBAL); dlsym(NULL, NULL)" +. auto/feature + + +if [ $ngx_found != yes ]; 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 " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="sched_yield()" +. auto/feature + + +if [ $ngx_found != yes ]; 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="SO_SETFIB" +ngx_feature_name="NGX_HAVE_SETFIB" +ngx_feature_run=no +ngx_feature_incs="#include " +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 " +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 " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="setsockopt(0, SOL_SOCKET, SO_ACCEPTFILTER, 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 + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="setsockopt(0, IPPROTO_IP, IP_RECVDSTADDR, 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 + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="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 + #include " +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 + #include + #include " +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 + #include + #include " +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 + #include + #include " +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 + #include + #include " +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 " +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 " + ngx_feature_path= + ngx_feature_libs= + ngx_feature_test="int n; struct aiocb iocb; + iocb.aio_sigevent.sigev_notify = SIGEV_KEVENT; + n = 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 + #include " + 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) 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 + #include " + ngx_feature_test="int n = SYS_eventfd; + struct iocb iocb; + iocb.aio_lio_opcode = IOCB_CMD_PREAD; + iocb.aio_flags = IOCB_FLAG_RESFD; + iocb.aio_resfd = -1;" + . 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 " + ngx_feature_path= + ngx_feature_libs= + ngx_feature_test="(void) eventfd(0, 0)" + . auto/feature + + if [ $ngx_found = yes ]; then + have=NGX_HAVE_SYS_EVENTFD_H . auto/have + fi + + if [ $ngx_found = no ]; then + + ngx_feature="eventfd() (SYS_eventfd)" + ngx_feature_incs="#include " + ngx_feature_test="int n = 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 + + +if [ $NGX_IPV6 = YES ]; then + ngx_feature="AF_INET6" + ngx_feature_name="NGX_HAVE_INET6" + ngx_feature_run=no + ngx_feature_incs="#include + #include + #include " + ngx_feature_path= + ngx_feature_libs= + ngx_feature_test="struct sockaddr_in6 sin6; + sin6.sin6_family = AF_INET6;" + . auto/feature +fi + + +ngx_feature="setproctitle()" +ngx_feature_name="NGX_HAVE_SETPROCTITLE" +ngx_feature_run=no +ngx_feature_incs="#include " +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 ' +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 + #include ' +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 + #include ' + 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 + #include + #include ' + ngx_feature_path= + ngx_feature_libs= + ngx_feature_test='int n; + char *p; + for (n = 1; n < 1000; n++) { + errno = 0; + p = strerror(n); + if (errno == EINVAL + || p == NULL + || strncmp(p, "Unknown error", 13) == 0) + { + break; + } + } + printf("%d", n);' + . auto/feature +fi + + +ngx_feature="localtime_r()" +ngx_feature_name="NGX_HAVE_LOCALTIME_R" +ngx_feature_run=no +ngx_feature_incs="#include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="struct tm t; time_t c=0; localtime_r(&c, &t)" +. auto/feature + + +ngx_feature="posix_memalign()" +ngx_feature_name="NGX_HAVE_POSIX_MEMALIGN" +ngx_feature_run=no +ngx_feature_incs="#include " +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 + #include " +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 " +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 + #include + #include " +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 + #include " +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 " +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" + 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 + #include " +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 + #include + $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 + #include " +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 + #include " +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 + #include " +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="openat(), fstatat()" +ngx_feature_name="NGX_HAVE_OPENAT" +ngx_feature_run=no +ngx_feature_incs="#include + #include + #include " +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 + #include + #include " +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/conf/fastcgi.conf b/conf/fastcgi.conf new file mode 100644 index 0000000..091738c --- /dev/null +++ b/conf/fastcgi.conf @@ -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/conf/fastcgi_params b/conf/fastcgi_params new file mode 100644 index 0000000..28decb9 --- /dev/null +++ b/conf/fastcgi_params @@ -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/conf/koi-utf b/conf/koi-utf new file mode 100644 index 0000000..e7974ff --- /dev/null +++ b/conf/koi-utf @@ -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 ; #   + + 9E C2B7 ; # · + + 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 ; # ° + + 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/conf/koi-win b/conf/koi-win new file mode 100644 index 0000000..72afabe --- /dev/null +++ b/conf/koi-win @@ -0,0 +1,103 @@ + +charset_map koi8-r windows-1251 { + + 80 88 ; # euro + + 95 95 ; # bullet + + 9A A0 ; #   + + 9E B7 ; # · + + 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 ; # ° + + 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/conf/mime.types b/conf/mime.types new file mode 100644 index 0000000..89be9a4 --- /dev/null +++ b/conf/mime.types @@ -0,0 +1,89 @@ + +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/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + image/svg+xml svg svgz; + image/webp webp; + + 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.ms-excel xls; + application/vnd.ms-fontobject eot; + application/vnd.ms-powerpoint ppt; + application/vnd.wap.wmlc wmlc; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + 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; + + application/vnd.openxmlformats-officedocument.wordprocessingml.document docx; + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx; + application/vnd.openxmlformats-officedocument.presentationml.presentation pptx; + + 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/conf/nginx.conf b/conf/nginx.conf new file mode 100644 index 0000000..29bc085 --- /dev/null +++ b/conf/nginx.conf @@ -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/conf/scgi_params b/conf/scgi_params new file mode 100644 index 0000000..6d4ce4f --- /dev/null +++ b/conf/scgi_params @@ -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/conf/uwsgi_params b/conf/uwsgi_params new file mode 100644 index 0000000..09c732c --- /dev/null +++ b/conf/uwsgi_params @@ -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/conf/win-utf b/conf/win-utf new file mode 100644 index 0000000..ed8bc00 --- /dev/null +++ b/conf/win-utf @@ -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 ; #   + 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 ; # ° + 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 ; # · + 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/config.example b/config.example new file mode 100755 index 0000000..291fbc4 --- /dev/null +++ b/config.example @@ -0,0 +1,52 @@ +# ============================================================================ +# +# BSD LICENSE +# +# Copyright (c) 2014-2017, Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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. +# +# +# ============================================================================ + +#!/bin/bash -x +if [ ! -d "$NGINX_INSTALL_DIR" ]; then + echo "NGINX install directory not been set or does not exist. Run:- export NGINX_INSTALL_DIR=\n" + exit 0 +fi + +if [ ! -d "$OPENSSL_LIB" ]; then + echo "OPENSSL_LIB has not been set or does not exist. Run:- export OPENSSL_LIB=\n" + exit 0 +fi + +./configure \ +--prefix=$NGINX_INSTALL_DIR \ +--without-http_rewrite_module \ +--with-http_ssl_module \ +--with-http_stub_status_module \ +--with-cc-opt="-DNGX_SECURE_MEM -I$OPENSSL_LIB/include -Wno-error=deprecated-declarations" \ +--with-ld-opt="-Wl,-rpath=$OPENSSL_LIB/lib -L$OPENSSL_LIB/lib" diff --git a/configure b/configure new file mode 100755 index 0000000..ceff15e --- /dev/null +++ b/configure @@ -0,0 +1,116 @@ +#!/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 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_*) + 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/contrib/README b/contrib/README new file mode 100644 index 0000000..fec4b20 --- /dev/null +++ b/contrib/README @@ -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/contrib/geo2nginx.pl b/contrib/geo2nginx.pl new file mode 100644 index 0000000..29243ec --- /dev/null +++ b/contrib/geo2nginx.pl @@ -0,0 +1,58 @@ +#!/usr/bin/perl -w + +# (c) Andrei Nigmatulin, 2005 +# +# this script provided "as is", without any warranties. use it at your own risk. +# +# special thanx to Andrew Sitnikov for perl port +# +# this script converts CSV geoip database (free download at http://www.maxmind.com/app/geoip_country) +# to format, suitable for use with nginx_http_geo module (http://sysoev.ru/nginx) +# +# for example, line with ip range +# +# "62.16.68.0","62.16.127.255","1041253376","1041268735","RU","Russian Federation" +# +# will be converted to four subnetworks: +# +# 62.16.68.0/22 RU; +# 62.16.72.0/21 RU; +# 62.16.80.0/20 RU; +# 62.16.96.0/19 RU; + + +use warnings; +use strict; + +while( ){ + if (/"[^"]+","[^"]+","([^"]+)","([^"]+)","([^"]+)"/){ + print_subnets($1, $2, $3); + } +} + +sub print_subnets { + my ($a1, $a2, $c) = @_; + my $l; + while ($a1 <= $a2) { + for ($l = 0; ($a1 & (1 << $l)) == 0 && ($a1 + ((1 << ($l + 1)) - 1)) <= $a2; $l++){}; + print long2ip($a1) . "/" . (32 - $l) . " " . $c . ";\n"; + $a1 += (1 << $l); + } +} + +sub long2ip { + my $ip = shift; + + my $str = 0; + + $str = ($ip & 255); + + $ip >>= 8; + $str = ($ip & 255).".$str"; + + $ip >>= 8; + $str = ($ip & 255).".$str"; + + $ip >>= 8; + $str = ($ip & 255).".$str"; +} diff --git a/contrib/unicode2nginx/koi-utf b/contrib/unicode2nginx/koi-utf new file mode 100644 index 0000000..48853af --- /dev/null +++ b/contrib/unicode2nginx/koi-utf @@ -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/contrib/unicode2nginx/unicode-to-nginx.pl b/contrib/unicode2nginx/unicode-to-nginx.pl new file mode 100644 index 0000000..daaf354 --- /dev/null +++ b/contrib/unicode2nginx/unicode-to-nginx.pl @@ -0,0 +1,45 @@ +#!/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, mdounin@rambler-co.ru + +############################################################################### + +require 5.006; + +while (<>) { + # Skip comments and empty lines + + next if /^#/; + next if /^\s*$/; + chomp; + + # Convert mappings + + if (/^\s*0x(..)\s*0x(....)\s*(#.*)/) { + # Mapping "#" + 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("C*", pack("U", hex($un_code)))); + + print " $cs_code $un_utf8 ; $un_name\n"; + + } else { + warn "Unrecognized line: '$_'"; + } +} + +############################################################################### diff --git a/contrib/unicode2nginx/win-utf b/contrib/unicode2nginx/win-utf new file mode 100644 index 0000000..af9f9aa --- /dev/null +++ b/contrib/unicode2nginx/win-utf @@ -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/contrib/vim/ftdetect/nginx.vim b/contrib/vim/ftdetect/nginx.vim new file mode 100644 index 0000000..3ae470d --- /dev/null +++ b/contrib/vim/ftdetect/nginx.vim @@ -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/contrib/vim/indent/nginx.vim b/contrib/vim/indent/nginx.vim new file mode 100644 index 0000000..8601366 --- /dev/null +++ b/contrib/vim/indent/nginx.vim @@ -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/contrib/vim/syntax/nginx.vim b/contrib/vim/syntax/nginx.vim new file mode 100644 index 0000000..f1fd48a --- /dev/null +++ b/contrib/vim/syntax/nginx.vim @@ -0,0 +1,836 @@ +" Vim syntax file +" Language: nginx.conf + +if exists("b:current_syntax") + finish +end + +setlocal iskeyword+=. +setlocal iskeyword+=/ +setlocal iskeyword+=: + +syn match ngxVariable '\$\(\w\+\|{\w\+}\)' +syn match ngxVariableBlock '\$\(\w\+\|{\w\+}\)' contained +syn match ngxVariableString '\$\(\w\+\|{\w\+}\)' contained +syn region ngxBlock start=+^+ end=+{+ skip=+\${+ contains=ngxComment,ngxDirectiveBlock,ngxVariableBlock,ngxString oneline +syn region ngxString start=+\z(["']\)+ end=+\z1+ skip=+\\\\\|\\\z1+ contains=ngxVariableString +syn match ngxComment ' *#.*$' + +syn keyword ngxBoolean on +syn keyword ngxBoolean off + +syn keyword ngxDirectiveBlock http contained +syn keyword ngxDirectiveBlock mail contained +syn keyword ngxDirectiveBlock events contained +syn keyword ngxDirectiveBlock server contained +syn keyword ngxDirectiveBlock types contained +syn keyword ngxDirectiveBlock location contained +syn keyword ngxDirectiveBlock upstream contained +syn keyword ngxDirectiveBlock charset_map contained +syn keyword ngxDirectiveBlock limit_except contained +syn keyword ngxDirectiveBlock if contained +syn keyword ngxDirectiveBlock geo contained +syn keyword ngxDirectiveBlock map contained +syn keyword ngxDirectiveBlock split_clients contained + +syn keyword ngxDirectiveImportant include +syn keyword ngxDirectiveImportant root +syn keyword ngxDirectiveImportant server +syn keyword ngxDirectiveImportant server_name +syn keyword ngxDirectiveImportant listen +syn keyword ngxDirectiveImportant internal +syn keyword ngxDirectiveImportant proxy_pass +syn keyword ngxDirectiveImportant memcached_pass +syn keyword ngxDirectiveImportant fastcgi_pass +syn keyword ngxDirectiveImportant scgi_pass +syn keyword ngxDirectiveImportant uwsgi_pass +syn keyword ngxDirectiveImportant try_files + +syn keyword ngxDirectiveControl break +syn keyword ngxDirectiveControl return +syn keyword ngxDirectiveControl rewrite +syn keyword ngxDirectiveControl set + +syn keyword ngxDirectiveError error_page +syn keyword ngxDirectiveError post_action + +syn keyword ngxDirectiveDeprecated connections +syn keyword ngxDirectiveDeprecated imap +syn keyword ngxDirectiveDeprecated limit_zone +syn keyword ngxDirectiveDeprecated mysql_test +syn keyword ngxDirectiveDeprecated open_file_cache_retest +syn keyword ngxDirectiveDeprecated optimize_server_names +syn keyword ngxDirectiveDeprecated satisfy_any +syn keyword ngxDirectiveDeprecated so_keepalive + +syn keyword ngxDirective accept_mutex +syn keyword ngxDirective accept_mutex_delay +syn keyword ngxDirective acceptex_read +syn keyword ngxDirective access_log +syn keyword ngxDirective add_after_body +syn keyword ngxDirective add_before_body +syn keyword ngxDirective add_header +syn keyword ngxDirective addition_types +syn keyword ngxDirective aio +syn keyword ngxDirective alias +syn keyword ngxDirective allow +syn keyword ngxDirective ancient_browser +syn keyword ngxDirective ancient_browser_value +syn keyword ngxDirective auth_basic +syn keyword ngxDirective auth_basic_user_file +syn keyword ngxDirective auth_http +syn keyword ngxDirective auth_http_header +syn keyword ngxDirective auth_http_timeout +syn keyword ngxDirective auth_request +syn keyword ngxDirective auth_request_set +syn keyword ngxDirective autoindex +syn keyword ngxDirective autoindex_exact_size +syn keyword ngxDirective autoindex_localtime +syn keyword ngxDirective charset +syn keyword ngxDirective charset_types +syn keyword ngxDirective chunked_transfer_encoding +syn keyword ngxDirective client_body_buffer_size +syn keyword ngxDirective client_body_in_file_only +syn keyword ngxDirective client_body_in_single_buffer +syn keyword ngxDirective client_body_temp_path +syn keyword ngxDirective client_body_timeout +syn keyword ngxDirective client_header_buffer_size +syn keyword ngxDirective client_header_timeout +syn keyword ngxDirective client_max_body_size +syn keyword ngxDirective connection_pool_size +syn keyword ngxDirective create_full_put_path +syn keyword ngxDirective daemon +syn keyword ngxDirective dav_access +syn keyword ngxDirective dav_methods +syn keyword ngxDirective debug_connection +syn keyword ngxDirective debug_points +syn keyword ngxDirective default_type +syn keyword ngxDirective degradation +syn keyword ngxDirective degrade +syn keyword ngxDirective deny +syn keyword ngxDirective devpoll_changes +syn keyword ngxDirective devpoll_events +syn keyword ngxDirective directio +syn keyword ngxDirective directio_alignment +syn keyword ngxDirective disable_symlinks +syn keyword ngxDirective empty_gif +syn keyword ngxDirective env +syn keyword ngxDirective epoll_events +syn keyword ngxDirective error_log +syn keyword ngxDirective etag +syn keyword ngxDirective eventport_events +syn keyword ngxDirective expires +syn keyword ngxDirective fastcgi_bind +syn keyword ngxDirective fastcgi_buffer_size +syn keyword ngxDirective fastcgi_buffering +syn keyword ngxDirective fastcgi_buffers +syn keyword ngxDirective fastcgi_busy_buffers_size +syn keyword ngxDirective fastcgi_cache +syn keyword ngxDirective fastcgi_cache_bypass +syn keyword ngxDirective fastcgi_cache_key +syn keyword ngxDirective fastcgi_cache_lock +syn keyword ngxDirective fastcgi_cache_lock_timeout +syn keyword ngxDirective fastcgi_cache_methods +syn keyword ngxDirective fastcgi_cache_min_uses +syn keyword ngxDirective fastcgi_cache_path +syn keyword ngxDirective fastcgi_cache_revalidate +syn keyword ngxDirective fastcgi_cache_use_stale +syn keyword ngxDirective fastcgi_cache_valid +syn keyword ngxDirective fastcgi_catch_stderr +syn keyword ngxDirective fastcgi_connect_timeout +syn keyword ngxDirective fastcgi_force_ranges +syn keyword ngxDirective fastcgi_hide_header +syn keyword ngxDirective fastcgi_ignore_client_abort +syn keyword ngxDirective fastcgi_ignore_headers +syn keyword ngxDirective fastcgi_index +syn keyword ngxDirective fastcgi_intercept_errors +syn keyword ngxDirective fastcgi_keep_conn +syn keyword ngxDirective fastcgi_max_temp_file_size +syn keyword ngxDirective fastcgi_next_upstream +syn keyword ngxDirective fastcgi_next_upstream_timeout +syn keyword ngxDirective fastcgi_next_upstream_tries +syn keyword ngxDirective fastcgi_no_cache +syn keyword ngxDirective fastcgi_param +syn keyword ngxDirective fastcgi_pass_header +syn keyword ngxDirective fastcgi_pass_request_body +syn keyword ngxDirective fastcgi_pass_request_headers +syn keyword ngxDirective fastcgi_read_timeout +syn keyword ngxDirective fastcgi_send_lowat +syn keyword ngxDirective fastcgi_send_timeout +syn keyword ngxDirective fastcgi_split_path_info +syn keyword ngxDirective fastcgi_store +syn keyword ngxDirective fastcgi_store_access +syn keyword ngxDirective fastcgi_temp_file_write_size +syn keyword ngxDirective fastcgi_temp_path +syn keyword ngxDirective flv +syn keyword ngxDirective geoip_city +syn keyword ngxDirective geoip_country +syn keyword ngxDirective geoip_org +syn keyword ngxDirective geoip_proxy +syn keyword ngxDirective geoip_proxy_recursive +syn keyword ngxDirective google_perftools_profiles +syn keyword ngxDirective gunzip +syn keyword ngxDirective gunzip_buffers +syn keyword ngxDirective gzip +syn keyword ngxDirective gzip_buffers +syn keyword ngxDirective gzip_comp_level +syn keyword ngxDirective gzip_disable +syn keyword ngxDirective gzip_hash +syn keyword ngxDirective gzip_http_version +syn keyword ngxDirective gzip_min_length +syn keyword ngxDirective gzip_no_buffer +syn keyword ngxDirective gzip_proxied +syn keyword ngxDirective gzip_static +syn keyword ngxDirective gzip_types +syn keyword ngxDirective gzip_vary +syn keyword ngxDirective gzip_window +syn keyword ngxDirective hash +syn keyword ngxDirective if_modified_since +syn keyword ngxDirective ignore_invalid_headers +syn keyword ngxDirective image_filter +syn keyword ngxDirective image_filter_buffer +syn keyword ngxDirective image_filter_interlace +syn keyword ngxDirective image_filter_jpeg_quality +syn keyword ngxDirective image_filter_sharpen +syn keyword ngxDirective image_filter_transparency +syn keyword ngxDirective imap_auth +syn keyword ngxDirective imap_capabilities +syn keyword ngxDirective imap_client_buffer +syn keyword ngxDirective index +syn keyword ngxDirective iocp_threads +syn keyword ngxDirective ip_hash +syn keyword ngxDirective keepalive +syn keyword ngxDirective keepalive_disable +syn keyword ngxDirective keepalive_requests +syn keyword ngxDirective keepalive_timeout +syn keyword ngxDirective kqueue_changes +syn keyword ngxDirective kqueue_events +syn keyword ngxDirective large_client_header_buffers +syn keyword ngxDirective least_conn +syn keyword ngxDirective limit_conn +syn keyword ngxDirective limit_conn_log_level +syn keyword ngxDirective limit_conn_status +syn keyword ngxDirective limit_conn_zone +syn keyword ngxDirective limit_rate +syn keyword ngxDirective limit_rate_after +syn keyword ngxDirective limit_req +syn keyword ngxDirective limit_req_log_level +syn keyword ngxDirective limit_req_status +syn keyword ngxDirective limit_req_zone +syn keyword ngxDirective lingering_close +syn keyword ngxDirective lingering_time +syn keyword ngxDirective lingering_timeout +syn keyword ngxDirective lock_file +syn keyword ngxDirective log_format +syn keyword ngxDirective log_not_found +syn keyword ngxDirective log_subrequest +syn keyword ngxDirective map_hash_bucket_size +syn keyword ngxDirective map_hash_max_size +syn keyword ngxDirective master_process +syn keyword ngxDirective max_ranges +syn keyword ngxDirective memcached_bind +syn keyword ngxDirective memcached_buffer_size +syn keyword ngxDirective memcached_connect_timeout +syn keyword ngxDirective memcached_gzip_flag +syn keyword ngxDirective memcached_next_upstream +syn keyword ngxDirective memcached_next_upstream_timeout +syn keyword ngxDirective memcached_next_upstream_tries +syn keyword ngxDirective memcached_read_timeout +syn keyword ngxDirective memcached_send_timeout +syn keyword ngxDirective merge_slashes +syn keyword ngxDirective min_delete_depth +syn keyword ngxDirective modern_browser +syn keyword ngxDirective modern_browser_value +syn keyword ngxDirective mp4 +syn keyword ngxDirective mp4_buffer_size +syn keyword ngxDirective mp4_max_buffer_size +syn keyword ngxDirective msie_padding +syn keyword ngxDirective msie_refresh +syn keyword ngxDirective multi_accept +syn keyword ngxDirective open_file_cache +syn keyword ngxDirective open_file_cache_errors +syn keyword ngxDirective open_file_cache_events +syn keyword ngxDirective open_file_cache_min_uses +syn keyword ngxDirective open_file_cache_valid +syn keyword ngxDirective open_log_file_cache +syn keyword ngxDirective output_buffers +syn keyword ngxDirective override_charset +syn keyword ngxDirective pcre_jit +syn keyword ngxDirective perl +syn keyword ngxDirective perl_modules +syn keyword ngxDirective perl_require +syn keyword ngxDirective perl_set +syn keyword ngxDirective pid +syn keyword ngxDirective pop3_auth +syn keyword ngxDirective pop3_capabilities +syn keyword ngxDirective port_in_redirect +syn keyword ngxDirective post_acceptex +syn keyword ngxDirective postpone_gzipping +syn keyword ngxDirective postpone_output +syn keyword ngxDirective protocol +syn keyword ngxDirective proxy +syn keyword ngxDirective proxy_bind +syn keyword ngxDirective proxy_buffer +syn keyword ngxDirective proxy_buffer_size +syn keyword ngxDirective proxy_buffering +syn keyword ngxDirective proxy_buffers +syn keyword ngxDirective proxy_busy_buffers_size +syn keyword ngxDirective proxy_cache +syn keyword ngxDirective proxy_cache_bypass +syn keyword ngxDirective proxy_cache_key +syn keyword ngxDirective proxy_cache_lock +syn keyword ngxDirective proxy_cache_lock_timeout +syn keyword ngxDirective proxy_cache_methods +syn keyword ngxDirective proxy_cache_min_uses +syn keyword ngxDirective proxy_cache_path +syn keyword ngxDirective proxy_cache_revalidate +syn keyword ngxDirective proxy_cache_use_stale +syn keyword ngxDirective proxy_cache_valid +syn keyword ngxDirective proxy_connect_timeout +syn keyword ngxDirective proxy_cookie_domain +syn keyword ngxDirective proxy_cookie_path +syn keyword ngxDirective proxy_force_ranges +syn keyword ngxDirective proxy_headers_hash_bucket_size +syn keyword ngxDirective proxy_headers_hash_max_size +syn keyword ngxDirective proxy_hide_header +syn keyword ngxDirective proxy_http_version +syn keyword ngxDirective proxy_ignore_client_abort +syn keyword ngxDirective proxy_ignore_headers +syn keyword ngxDirective proxy_intercept_errors +syn keyword ngxDirective proxy_max_temp_file_size +syn keyword ngxDirective proxy_method +syn keyword ngxDirective proxy_next_upstream +syn keyword ngxDirective proxy_next_upstream_timeout +syn keyword ngxDirective proxy_next_upstream_tries +syn keyword ngxDirective proxy_no_cache +syn keyword ngxDirective proxy_pass_error_message +syn keyword ngxDirective proxy_pass_header +syn keyword ngxDirective proxy_pass_request_body +syn keyword ngxDirective proxy_pass_request_headers +syn keyword ngxDirective proxy_read_timeout +syn keyword ngxDirective proxy_redirect +syn keyword ngxDirective proxy_send_lowat +syn keyword ngxDirective proxy_send_timeout +syn keyword ngxDirective proxy_set_body +syn keyword ngxDirective proxy_set_header +syn keyword ngxDirective proxy_ssl_ciphers +syn keyword ngxDirective proxy_ssl_crl +syn keyword ngxDirective proxy_ssl_name +syn keyword ngxDirective proxy_ssl_protocols +syn keyword ngxDirective proxy_ssl_server_name +syn keyword ngxDirective proxy_ssl_session_reuse +syn keyword ngxDirective proxy_ssl_trusted_certificate +syn keyword ngxDirective proxy_ssl_verify +syn keyword ngxDirective proxy_ssl_verify_depth +syn keyword ngxDirective proxy_store +syn keyword ngxDirective proxy_store_access +syn keyword ngxDirective proxy_temp_file_write_size +syn keyword ngxDirective proxy_temp_path +syn keyword ngxDirective proxy_timeout +syn keyword ngxDirective random_index +syn keyword ngxDirective read_ahead +syn keyword ngxDirective real_ip_header +syn keyword ngxDirective real_ip_recursive +syn keyword ngxDirective recursive_error_pages +syn keyword ngxDirective referer_hash_bucket_size +syn keyword ngxDirective referer_hash_max_size +syn keyword ngxDirective request_pool_size +syn keyword ngxDirective reset_timedout_connection +syn keyword ngxDirective resolver +syn keyword ngxDirective resolver_timeout +syn keyword ngxDirective rewrite_log +syn keyword ngxDirective rtsig_overflow_events +syn keyword ngxDirective rtsig_overflow_test +syn keyword ngxDirective rtsig_overflow_threshold +syn keyword ngxDirective rtsig_signo +syn keyword ngxDirective satisfy +syn keyword ngxDirective scgi_bind +syn keyword ngxDirective scgi_buffer_size +syn keyword ngxDirective scgi_buffering +syn keyword ngxDirective scgi_buffers +syn keyword ngxDirective scgi_busy_buffers_size +syn keyword ngxDirective scgi_cache +syn keyword ngxDirective scgi_cache_bypass +syn keyword ngxDirective scgi_cache_key +syn keyword ngxDirective scgi_cache_lock +syn keyword ngxDirective scgi_cache_lock_timeout +syn keyword ngxDirective scgi_cache_methods +syn keyword ngxDirective scgi_cache_min_uses +syn keyword ngxDirective scgi_cache_path +syn keyword ngxDirective scgi_cache_revalidate +syn keyword ngxDirective scgi_cache_use_stale +syn keyword ngxDirective scgi_cache_valid +syn keyword ngxDirective scgi_connect_timeout +syn keyword ngxDirective scgi_force_ranges +syn keyword ngxDirective scgi_hide_header +syn keyword ngxDirective scgi_ignore_client_abort +syn keyword ngxDirective scgi_ignore_headers +syn keyword ngxDirective scgi_intercept_errors +syn keyword ngxDirective scgi_max_temp_file_size +syn keyword ngxDirective scgi_next_upstream +syn keyword ngxDirective scgi_next_upstream_timeout +syn keyword ngxDirective scgi_next_upstream_tries +syn keyword ngxDirective scgi_no_cache +syn keyword ngxDirective scgi_param +syn keyword ngxDirective scgi_pass_header +syn keyword ngxDirective scgi_pass_request_body +syn keyword ngxDirective scgi_pass_request_headers +syn keyword ngxDirective scgi_read_timeout +syn keyword ngxDirective scgi_send_timeout +syn keyword ngxDirective scgi_store +syn keyword ngxDirective scgi_store_access +syn keyword ngxDirective scgi_temp_file_write_size +syn keyword ngxDirective scgi_temp_path +syn keyword ngxDirective secure_link +syn keyword ngxDirective secure_link_md5 +syn keyword ngxDirective secure_link_secret +syn keyword ngxDirective send_lowat +syn keyword ngxDirective send_timeout +syn keyword ngxDirective sendfile +syn keyword ngxDirective sendfile_max_chunk +syn keyword ngxDirective server_name_in_redirect +syn keyword ngxDirective server_names_hash_bucket_size +syn keyword ngxDirective server_names_hash_max_size +syn keyword ngxDirective server_tokens +syn keyword ngxDirective set_real_ip_from +syn keyword ngxDirective smtp_auth +syn keyword ngxDirective smtp_capabilities +syn keyword ngxDirective smtp_client_buffer +syn keyword ngxDirective smtp_greeting_delay +syn keyword ngxDirective source_charset +syn keyword ngxDirective spdy_chunk_size +syn keyword ngxDirective spdy_headers_comp +syn keyword ngxDirective spdy_keepalive_timeout +syn keyword ngxDirective spdy_max_concurrent_streams +syn keyword ngxDirective spdy_pool_size +syn keyword ngxDirective spdy_recv_buffer_size +syn keyword ngxDirective spdy_recv_timeout +syn keyword ngxDirective spdy_streams_index_size +syn keyword ngxDirective ssi +syn keyword ngxDirective ssi_ignore_recycled_buffers +syn keyword ngxDirective ssi_last_modified +syn keyword ngxDirective ssi_min_file_chunk +syn keyword ngxDirective ssi_silent_errors +syn keyword ngxDirective ssi_types +syn keyword ngxDirective ssi_value_length +syn keyword ngxDirective ssl +syn keyword ngxDirective ssl_buffer_size +syn keyword ngxDirective ssl_certificate +syn keyword ngxDirective ssl_certificate_key +syn keyword ngxDirective ssl_ciphers +syn keyword ngxDirective ssl_client_certificate +syn keyword ngxDirective ssl_crl +syn keyword ngxDirective ssl_dhparam +syn keyword ngxDirective ssl_ecdh_curve +syn keyword ngxDirective ssl_engine +syn keyword ngxDirective ssl_password_file +syn keyword ngxDirective ssl_prefer_server_ciphers +syn keyword ngxDirective ssl_protocols +syn keyword ngxDirective ssl_session_cache +syn keyword ngxDirective ssl_session_ticket_key +syn keyword ngxDirective ssl_session_tickets +syn keyword ngxDirective ssl_session_timeout +syn keyword ngxDirective ssl_stapling +syn keyword ngxDirective ssl_stapling_file +syn keyword ngxDirective ssl_stapling_responder +syn keyword ngxDirective ssl_stapling_verify +syn keyword ngxDirective ssl_trusted_certificate +syn keyword ngxDirective ssl_verify_client +syn keyword ngxDirective ssl_verify_depth +syn keyword ngxDirective starttls +syn keyword ngxDirective stub_status +syn keyword ngxDirective sub_filter +syn keyword ngxDirective sub_filter_last_modified +syn keyword ngxDirective sub_filter_once +syn keyword ngxDirective sub_filter_types +syn keyword ngxDirective tcp_nodelay +syn keyword ngxDirective tcp_nopush +syn keyword ngxDirective thread_stack_size +syn keyword ngxDirective timeout +syn keyword ngxDirective timer_resolution +syn keyword ngxDirective types_hash_bucket_size +syn keyword ngxDirective types_hash_max_size +syn keyword ngxDirective underscores_in_headers +syn keyword ngxDirective uninitialized_variable_warn +syn keyword ngxDirective use +syn keyword ngxDirective user +syn keyword ngxDirective userid +syn keyword ngxDirective userid_domain +syn keyword ngxDirective userid_expires +syn keyword ngxDirective userid_mark +syn keyword ngxDirective userid_name +syn keyword ngxDirective userid_p3p +syn keyword ngxDirective userid_path +syn keyword ngxDirective userid_service +syn keyword ngxDirective uwsgi_bind +syn keyword ngxDirective uwsgi_buffer_size +syn keyword ngxDirective uwsgi_buffering +syn keyword ngxDirective uwsgi_buffers +syn keyword ngxDirective uwsgi_busy_buffers_size +syn keyword ngxDirective uwsgi_cache +syn keyword ngxDirective uwsgi_cache_bypass +syn keyword ngxDirective uwsgi_cache_key +syn keyword ngxDirective uwsgi_cache_lock +syn keyword ngxDirective uwsgi_cache_lock_timeout +syn keyword ngxDirective uwsgi_cache_methods +syn keyword ngxDirective uwsgi_cache_min_uses +syn keyword ngxDirective uwsgi_cache_path +syn keyword ngxDirective uwsgi_cache_revalidate +syn keyword ngxDirective uwsgi_cache_use_stale +syn keyword ngxDirective uwsgi_cache_valid +syn keyword ngxDirective uwsgi_connect_timeout +syn keyword ngxDirective uwsgi_force_ranges +syn keyword ngxDirective uwsgi_hide_header +syn keyword ngxDirective uwsgi_ignore_client_abort +syn keyword ngxDirective uwsgi_ignore_headers +syn keyword ngxDirective uwsgi_intercept_errors +syn keyword ngxDirective uwsgi_max_temp_file_size +syn keyword ngxDirective uwsgi_modifier1 +syn keyword ngxDirective uwsgi_modifier2 +syn keyword ngxDirective uwsgi_next_upstream +syn keyword ngxDirective uwsgi_next_upstream_timeout +syn keyword ngxDirective uwsgi_next_upstream_tries +syn keyword ngxDirective uwsgi_no_cache +syn keyword ngxDirective uwsgi_param +syn keyword ngxDirective uwsgi_pass_header +syn keyword ngxDirective uwsgi_pass_request_body +syn keyword ngxDirective uwsgi_pass_request_headers +syn keyword ngxDirective uwsgi_read_timeout +syn keyword ngxDirective uwsgi_send_timeout +syn keyword ngxDirective uwsgi_ssl_ciphers +syn keyword ngxDirective uwsgi_ssl_crl +syn keyword ngxDirective uwsgi_ssl_name +syn keyword ngxDirective uwsgi_ssl_protocols +syn keyword ngxDirective uwsgi_ssl_server_name +syn keyword ngxDirective uwsgi_ssl_session_reuse +syn keyword ngxDirective uwsgi_ssl_trusted_certificate +syn keyword ngxDirective uwsgi_ssl_verify +syn keyword ngxDirective uwsgi_ssl_verify_depth +syn keyword ngxDirective uwsgi_store +syn keyword ngxDirective uwsgi_store_access +syn keyword ngxDirective uwsgi_string +syn keyword ngxDirective uwsgi_temp_file_write_size +syn keyword ngxDirective uwsgi_temp_path +syn keyword ngxDirective valid_referers +syn keyword ngxDirective variables_hash_bucket_size +syn keyword ngxDirective variables_hash_max_size +syn keyword ngxDirective worker_aio_requests +syn keyword ngxDirective worker_connections +syn keyword ngxDirective worker_cpu_affinity +syn keyword ngxDirective worker_priority +syn keyword ngxDirective worker_processes +syn keyword ngxDirective worker_rlimit_core +syn keyword ngxDirective worker_rlimit_nofile +syn keyword ngxDirective worker_rlimit_sigpending +syn keyword ngxDirective worker_threads +syn keyword ngxDirective working_directory +syn keyword ngxDirective xclient +syn keyword ngxDirective xml_entities +syn keyword ngxDirective xslt_last_modified +syn keyword ngxDirective xslt_param +syn keyword ngxDirective xslt_string_param +syn keyword ngxDirective xslt_stylesheet +syn keyword ngxDirective xslt_types + +" 3rd party module list: +" http://wiki.nginx.org/Nginx3rdPartyModules + +" Accept Language Module +" Parses the Accept-Language header and gives the most suitable locale from a list of supported locales. +syn keyword ngxDirectiveThirdParty set_from_accept_language + +" Access Key Module +" Denies access unless the request URL contains an access key. +syn keyword ngxDirectiveThirdParty accesskey +syn keyword ngxDirectiveThirdParty accesskey_arg +syn keyword ngxDirectiveThirdParty accesskey_hashmethod +syn keyword ngxDirectiveThirdParty accesskey_signature + +" Auth PAM Module +" HTTP Basic Authentication using PAM. +syn keyword ngxDirectiveThirdParty auth_pam +syn keyword ngxDirectiveThirdParty auth_pam_service_name + +" Cache Purge Module +" Module adding ability to purge content from FastCGI and proxy caches. +syn keyword ngxDirectiveThirdParty fastcgi_cache_purge +syn keyword ngxDirectiveThirdParty proxy_cache_purge + +" Chunkin Module +" HTTP 1.1 chunked-encoding request body support for Nginx. +syn keyword ngxDirectiveThirdParty chunkin +syn keyword ngxDirectiveThirdParty chunkin_keepalive +syn keyword ngxDirectiveThirdParty chunkin_max_chunks_per_buf +syn keyword ngxDirectiveThirdParty chunkin_resume + +" Circle GIF Module +" Generates simple circle images with the colors and size specified in the URL. +syn keyword ngxDirectiveThirdParty circle_gif +syn keyword ngxDirectiveThirdParty circle_gif_max_radius +syn keyword ngxDirectiveThirdParty circle_gif_min_radius +syn keyword ngxDirectiveThirdParty circle_gif_step_radius + +" Drizzle Module +" Make nginx talk directly to mysql, drizzle, and sqlite3 by libdrizzle. +syn keyword ngxDirectiveThirdParty drizzle_connect_timeout +syn keyword ngxDirectiveThirdParty drizzle_dbname +syn keyword ngxDirectiveThirdParty drizzle_keepalive +syn keyword ngxDirectiveThirdParty drizzle_module_header +syn keyword ngxDirectiveThirdParty drizzle_pass +syn keyword ngxDirectiveThirdParty drizzle_query +syn keyword ngxDirectiveThirdParty drizzle_recv_cols_timeout +syn keyword ngxDirectiveThirdParty drizzle_recv_rows_timeout +syn keyword ngxDirectiveThirdParty drizzle_send_query_timeout +syn keyword ngxDirectiveThirdParty drizzle_server + +" Echo Module +" Brings 'echo', 'sleep', 'time', 'exec' and more shell-style goodies to Nginx config file. +syn keyword ngxDirectiveThirdParty echo +syn keyword ngxDirectiveThirdParty echo_after_body +syn keyword ngxDirectiveThirdParty echo_before_body +syn keyword ngxDirectiveThirdParty echo_blocking_sleep +syn keyword ngxDirectiveThirdParty echo_duplicate +syn keyword ngxDirectiveThirdParty echo_end +syn keyword ngxDirectiveThirdParty echo_exec +syn keyword ngxDirectiveThirdParty echo_flush +syn keyword ngxDirectiveThirdParty echo_foreach_split +syn keyword ngxDirectiveThirdParty echo_location +syn keyword ngxDirectiveThirdParty echo_location_async +syn keyword ngxDirectiveThirdParty echo_read_request_body +syn keyword ngxDirectiveThirdParty echo_request_body +syn keyword ngxDirectiveThirdParty echo_reset_timer +syn keyword ngxDirectiveThirdParty echo_sleep +syn keyword ngxDirectiveThirdParty echo_subrequest +syn keyword ngxDirectiveThirdParty echo_subrequest_async + +" Events Module +" Provides options for start/stop events. +syn keyword ngxDirectiveThirdParty on_start +syn keyword ngxDirectiveThirdParty on_stop + +" EY Balancer Module +" Adds a request queue to Nginx that allows the limiting of concurrent requests passed to the upstream. +syn keyword ngxDirectiveThirdParty max_connections +syn keyword ngxDirectiveThirdParty max_connections_max_queue_length +syn keyword ngxDirectiveThirdParty max_connections_queue_timeout + +" Fancy Indexes Module +" Like the built-in autoindex module, but fancier. +syn keyword ngxDirectiveThirdParty fancyindex +syn keyword ngxDirectiveThirdParty fancyindex_exact_size +syn keyword ngxDirectiveThirdParty fancyindex_footer +syn keyword ngxDirectiveThirdParty fancyindex_header +syn keyword ngxDirectiveThirdParty fancyindex_localtime +syn keyword ngxDirectiveThirdParty fancyindex_readme +syn keyword ngxDirectiveThirdParty fancyindex_readme_mode + +" GeoIP Module (DEPRECATED) +" Country code lookups via the MaxMind GeoIP API. +syn keyword ngxDirectiveThirdParty geoip_country_file + +" Headers More Module +" Set and clear input and output headers...more than "add"! +syn keyword ngxDirectiveThirdParty more_clear_headers +syn keyword ngxDirectiveThirdParty more_clear_input_headers +syn keyword ngxDirectiveThirdParty more_set_headers +syn keyword ngxDirectiveThirdParty more_set_input_headers + +" HTTP Push Module +" Turn Nginx into an adept long-polling HTTP Push (Comet) server. +syn keyword ngxDirectiveThirdParty push_buffer_size +syn keyword ngxDirectiveThirdParty push_listener +syn keyword ngxDirectiveThirdParty push_message_timeout +syn keyword ngxDirectiveThirdParty push_queue_messages +syn keyword ngxDirectiveThirdParty push_sender + +" HTTP Redis Module > +" Redis support.> +syn keyword ngxDirectiveThirdParty redis_bind +syn keyword ngxDirectiveThirdParty redis_buffer_size +syn keyword ngxDirectiveThirdParty redis_connect_timeout +syn keyword ngxDirectiveThirdParty redis_next_upstream +syn keyword ngxDirectiveThirdParty redis_pass +syn keyword ngxDirectiveThirdParty redis_read_timeout +syn keyword ngxDirectiveThirdParty redis_send_timeout + +" HTTP JavaScript Module +" Embedding SpiderMonkey. Nearly full port on Perl module. +syn keyword ngxDirectiveThirdParty js +syn keyword ngxDirectiveThirdParty js_filter +syn keyword ngxDirectiveThirdParty js_filter_types +syn keyword ngxDirectiveThirdParty js_load +syn keyword ngxDirectiveThirdParty js_maxmem +syn keyword ngxDirectiveThirdParty js_require +syn keyword ngxDirectiveThirdParty js_set +syn keyword ngxDirectiveThirdParty js_utf8 + +" Log Request Speed +" Log the time it took to process each request. +syn keyword ngxDirectiveThirdParty log_request_speed_filter +syn keyword ngxDirectiveThirdParty log_request_speed_filter_timeout + +" Memc Module +" An extended version of the standard memcached module that supports set, add, delete, and many more memcached commands. +syn keyword ngxDirectiveThirdParty memc_buffer_size +syn keyword ngxDirectiveThirdParty memc_cmds_allowed +syn keyword ngxDirectiveThirdParty memc_connect_timeout +syn keyword ngxDirectiveThirdParty memc_flags_to_last_modified +syn keyword ngxDirectiveThirdParty memc_next_upstream +syn keyword ngxDirectiveThirdParty memc_pass +syn keyword ngxDirectiveThirdParty memc_read_timeout +syn keyword ngxDirectiveThirdParty memc_send_timeout +syn keyword ngxDirectiveThirdParty memc_upstream_fail_timeout +syn keyword ngxDirectiveThirdParty memc_upstream_max_fails + +" Mogilefs Module +" Implements a MogileFS client, provides a replace to the Perlbal reverse proxy of the original MogileFS. +syn keyword ngxDirectiveThirdParty mogilefs_connect_timeout +syn keyword ngxDirectiveThirdParty mogilefs_domain +syn keyword ngxDirectiveThirdParty mogilefs_methods +syn keyword ngxDirectiveThirdParty mogilefs_noverify +syn keyword ngxDirectiveThirdParty mogilefs_pass +syn keyword ngxDirectiveThirdParty mogilefs_read_timeout +syn keyword ngxDirectiveThirdParty mogilefs_send_timeout +syn keyword ngxDirectiveThirdParty mogilefs_tracker + +" MP4 Streaming Lite Module +" Will seek to a certain time within H.264/MP4 files when provided with a 'start' parameter in the URL. +syn keyword ngxDirectiveThirdParty mp4 + +" Nginx Notice Module +" Serve static file to POST requests. +syn keyword ngxDirectiveThirdParty notice +syn keyword ngxDirectiveThirdParty notice_type + +" Phusion Passenger +" Easy and robust deployment of Ruby on Rails application on Apache and Nginx webservers. +syn keyword ngxDirectiveThirdParty passenger_base_uri +syn keyword ngxDirectiveThirdParty passenger_default_user +syn keyword ngxDirectiveThirdParty passenger_enabled +syn keyword ngxDirectiveThirdParty passenger_log_level +syn keyword ngxDirectiveThirdParty passenger_max_instances_per_app +syn keyword ngxDirectiveThirdParty passenger_max_pool_size +syn keyword ngxDirectiveThirdParty passenger_pool_idle_time +syn keyword ngxDirectiveThirdParty passenger_root +syn keyword ngxDirectiveThirdParty passenger_ruby +syn keyword ngxDirectiveThirdParty passenger_use_global_queue +syn keyword ngxDirectiveThirdParty passenger_user_switching +syn keyword ngxDirectiveThirdParty rack_env +syn keyword ngxDirectiveThirdParty rails_app_spawner_idle_time +syn keyword ngxDirectiveThirdParty rails_env +syn keyword ngxDirectiveThirdParty rails_framework_spawner_idle_time +syn keyword ngxDirectiveThirdParty rails_spawn_method + +" RDS JSON Module +" Help ngx_drizzle and other DBD modules emit JSON data. +syn keyword ngxDirectiveThirdParty rds_json +syn keyword ngxDirectiveThirdParty rds_json_content_type +syn keyword ngxDirectiveThirdParty rds_json_format +syn keyword ngxDirectiveThirdParty rds_json_ret + +" RRD Graph Module +" This module provides an HTTP interface to RRDtool's graphing facilities. +syn keyword ngxDirectiveThirdParty rrd_graph +syn keyword ngxDirectiveThirdParty rrd_graph_root + +" Secure Download +" Create expiring links. +syn keyword ngxDirectiveThirdParty secure_download +syn keyword ngxDirectiveThirdParty secure_download_fail_location +syn keyword ngxDirectiveThirdParty secure_download_path_mode +syn keyword ngxDirectiveThirdParty secure_download_secret + +" SlowFS Cache Module +" Module adding ability to cache static files. +syn keyword ngxDirectiveThirdParty slowfs_big_file_size +syn keyword ngxDirectiveThirdParty slowfs_cache +syn keyword ngxDirectiveThirdParty slowfs_cache_key +syn keyword ngxDirectiveThirdParty slowfs_cache_min_uses +syn keyword ngxDirectiveThirdParty slowfs_cache_path +syn keyword ngxDirectiveThirdParty slowfs_cache_purge +syn keyword ngxDirectiveThirdParty slowfs_cache_valid +syn keyword ngxDirectiveThirdParty slowfs_temp_path + +" Strip Module +" Whitespace remover. +syn keyword ngxDirectiveThirdParty strip + +" Substitutions Module +" A filter module which can do both regular expression and fixed string substitutions on response bodies. +syn keyword ngxDirectiveThirdParty subs_filter +syn keyword ngxDirectiveThirdParty subs_filter_types + +" Supervisord Module +" Module providing nginx with API to communicate with supervisord and manage (start/stop) backends on-demand. +syn keyword ngxDirectiveThirdParty supervisord +syn keyword ngxDirectiveThirdParty supervisord_inherit_backend_status +syn keyword ngxDirectiveThirdParty supervisord_name +syn keyword ngxDirectiveThirdParty supervisord_start +syn keyword ngxDirectiveThirdParty supervisord_stop + +" Upload Module +" Parses multipart/form-data allowing arbitrary handling of uploaded files. +syn keyword ngxDirectiveThirdParty upload_aggregate_form_field +syn keyword ngxDirectiveThirdParty upload_buffer_size +syn keyword ngxDirectiveThirdParty upload_cleanup +syn keyword ngxDirectiveThirdParty upload_limit_rate +syn keyword ngxDirectiveThirdParty upload_max_file_size +syn keyword ngxDirectiveThirdParty upload_max_output_body_len +syn keyword ngxDirectiveThirdParty upload_max_part_header_len +syn keyword ngxDirectiveThirdParty upload_pass +syn keyword ngxDirectiveThirdParty upload_pass_args +syn keyword ngxDirectiveThirdParty upload_pass_form_field +syn keyword ngxDirectiveThirdParty upload_set_form_field +syn keyword ngxDirectiveThirdParty upload_store +syn keyword ngxDirectiveThirdParty upload_store_access + +" Upload Progress Module +" Tracks and reports upload progress. +syn keyword ngxDirectiveThirdParty report_uploads +syn keyword ngxDirectiveThirdParty track_uploads +syn keyword ngxDirectiveThirdParty upload_progress +syn keyword ngxDirectiveThirdParty upload_progress_content_type +syn keyword ngxDirectiveThirdParty upload_progress_header +syn keyword ngxDirectiveThirdParty upload_progress_json_output +syn keyword ngxDirectiveThirdParty upload_progress_template + +" Upstream Fair Balancer +" Sends an incoming request to the least-busy backend server, rather than distributing requests round-robin. +syn keyword ngxDirectiveThirdParty fair +syn keyword ngxDirectiveThirdParty upstream_fair_shm_size + +" Upstream Consistent Hash +" Select backend based on Consistent hash ring. +syn keyword ngxDirectiveThirdParty consistent_hash + +" Upstream Hash Module +" Provides simple upstream load distribution by hashing a configurable variable. +syn keyword ngxDirectiveThirdParty hash +syn keyword ngxDirectiveThirdParty hash_again + +" XSS Module +" Native support for cross-site scripting (XSS) in an nginx. +syn keyword ngxDirectiveThirdParty xss_callback_arg +syn keyword ngxDirectiveThirdParty xss_get +syn keyword ngxDirectiveThirdParty xss_input_types +syn keyword ngxDirectiveThirdParty xss_output_type + +" highlight + +hi link ngxComment Comment +hi link ngxVariable Identifier +hi link ngxVariableBlock Identifier +hi link ngxVariableString PreProc +hi link ngxBlock Normal +hi link ngxString String + +hi link ngxBoolean Boolean +hi link ngxDirectiveBlock Statement +hi link ngxDirectiveImportant Type +hi link ngxDirectiveControl Keyword +hi link ngxDirectiveError Constant +hi link ngxDirectiveDeprecated Error +hi link ngxDirective Identifier +hi link ngxDirectiveThirdParty Special + +let b:current_syntax = "nginx" diff --git a/docs/html/50x.html b/docs/html/50x.html new file mode 100644 index 0000000..f60f5e7 --- /dev/null +++ b/docs/html/50x.html @@ -0,0 +1,21 @@ + + + +Error + + + +

An error occurred.

+

Sorry, the page you are looking for is currently unavailable.
+Please try again later.

+

If you are the system administrator of this resource then you should check +the error log for details.

+

Faithfully yours, nginx.

+ + diff --git a/docs/html/index.html b/docs/html/index.html new file mode 100644 index 0000000..2ca3b95 --- /dev/null +++ b/docs/html/index.html @@ -0,0 +1,25 @@ + + + +Welcome to nginx! + + + +

Welcome to nginx!

+

If you see this page, the nginx web server is successfully installed and +working. Further configuration is required.

+ +

For online documentation and support please refer to +nginx.org.
+Commercial support is available at +nginx.com.

+ +

Thank you for using nginx.

+ + diff --git a/docs/man/nginx.8 b/docs/man/nginx.8 new file mode 100644 index 0000000..1f4dc89 --- /dev/null +++ b/docs/man/nginx.8 @@ -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 igor@sysoev.ru . +.Pp +This manual page was originally written by +.An Sergey A. Osokin Aq osa@FreeBSD.org.ru +as a result of compiling many +.Nm +documents from all over the world. diff --git a/src/core/nginx.c b/src/core/nginx.c new file mode 100644 index 0000000..be772eb --- /dev/null +++ b/src/core/nginx.c @@ -0,0 +1,1556 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +static void ngx_show_version_info(void); +static ngx_int_t ngx_add_inherited_sockets(ngx_cycle_t *cycle); +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("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; +/* indicate that nginx start without ngx_ssl_init() + * which will involve OpenSSL configuration file to + * start OpenSSL engine */ +static ngx_uint_t ngx_no_ssl_init; +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; + ngx_int_t ret_int_num; + + + 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(); + + log = ngx_log_init(ngx_prefix); + if (log == NULL) { + return 1; + } + + /* STUB */ +#if (NGX_OPENSSL) + if(!ngx_no_ssl_init) + 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; + init_cycle.no_ssl_init = ngx_no_ssl_init; + 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; + } + + 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) { + ret_int_num = ngx_signal_process(cycle, ngx_signal); + ngx_destroy_pool(cycle->pool); + return ret_int_num; + } + + 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) != 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) { + ngx_single_process_cycle(cycle); + + } else { + ngx_master_process_cycle(cycle); + } + + 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; + + 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); + *last = n; + + } else { + env = ngx_palloc(cycle->pool, (n + 1) * sizeof(char *)); + } + + if (env == NULL) { + return NULL; + } + + 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; +} + + +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; + ngx_no_ssl_init = 1; + break; + + case 'T': + ngx_test_config = 1; + ngx_dump_config = 1; + ngx_no_ssl_init = 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': + ngx_no_ssl_init = 1; + 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)); + ngx_no_ssl_init = 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 - ngx_cycle->conf_file.data + 1; + cycle->conf_prefix.data = ngx_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->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_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/src/core/nginx.h b/src/core/nginx.h new file mode 100644 index 0000000..6bf1618 --- /dev/null +++ b/src/core/nginx.h @@ -0,0 +1,26 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGINX_H_INCLUDED_ +#define _NGINX_H_INCLUDED_ + + +#define nginx_version 1010003 +#define NGINX_VERSION "1.10.3" +#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/src/core/ngx_array.c b/src/core/ngx_array.c new file mode 100644 index 0000000..4ea226f --- /dev/null +++ b/src/core/ngx_array.c @@ -0,0 +1,141 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +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/src/core/ngx_array.h b/src/core/ngx_array.h new file mode 100644 index 0000000..a0f2a74 --- /dev/null +++ b/src/core/ngx_array.h @@ -0,0 +1,53 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_ARRAY_H_INCLUDED_ +#define _NGX_ARRAY_H_INCLUDED_ + + +#include +#include + + +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/src/core/ngx_buf.c b/src/core/ngx_buf.c new file mode 100644 index 0000000..418411b --- /dev/null +++ b/src/core/ngx_buf.c @@ -0,0 +1,311 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +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 (*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/src/core/ngx_buf.h b/src/core/ngx_buf.h new file mode 100644 index 0000000..b55ce85 --- /dev/null +++ b/src/core/ngx_buf.h @@ -0,0 +1,176 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_BUF_H_INCLUDED_ +#define _NGX_BUF_H_INCLUDED_ + + +#include +#include + + +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); + +#if (NGX_HAVE_FILE_AIO) +typedef void (*ngx_output_chain_aio_pt)(ngx_output_chain_ctx_t *ctx, + ngx_file_t *file); +#endif + +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; +#if (NGX_HAVE_ALIGNED_DIRECTIO) + unsigned unaligned:1; +#endif + unsigned need_in_memory:1; + unsigned need_in_temp:1; +#if (NGX_HAVE_FILE_AIO || NGX_THREADS) + unsigned aio:1; +#endif + +#if (NGX_HAVE_FILE_AIO) + ngx_output_chain_aio_pt aio_handler; +#if (NGX_HAVE_AIO_SENDFILE) + ssize_t (*aio_preload)(ngx_buf_t *file); +#endif +#endif + +#if (NGX_THREADS) + 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) (unsigned) (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/src/core/ngx_conf_file.c b/src/core/ngx_conf_file.c new file mode 100644 index 0000000..c60d5fb --- /dev/null +++ b/src/core/ngx_conf_file.c @@ -0,0 +1,1444 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + +#define NGX_CONF_BUFFER 4096 + +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; +} + + +char * +ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename) +{ + char *rv; + u_char *p; + off_t size; + ngx_fd_t fd; + ngx_int_t rc; + ngx_buf_t buf, *tbuf; + ngx_conf_file_t *prev, conf_file; + ngx_conf_dump_t *cd; + 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 + ) + { + p = ngx_pstrdup(cf->cycle->pool, filename); + if (p == NULL) { + goto failed; + } + + size = ngx_file_size(&cf->conf_file->file.info); + + tbuf = ngx_create_temp_buf(cf->cycle->pool, (size_t) size); + if (tbuf == NULL) { + goto failed; + } + + cd = ngx_array_push(&cf->cycle->config_dump); + if (cd == NULL) { + goto failed; + } + + cd->name.len = filename->len; + cd->name.data = p; + cd->buffer = tbuf; + + cf->conf_file->dump = tbuf; + + } 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; + + 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_WARN, 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_WARN, 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/src/core/ngx_conf_file.h b/src/core/ngx_conf_file.h new file mode 100644 index 0000000..8e9e6be --- /dev/null +++ b/src/core/ngx_conf_file.h @@ -0,0 +1,297 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_CONF_FILE_H_INCLUDED_ +#define _NGX_CONF_FILE_H_INCLUDED_ + + +#include +#include + + +/* + * 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_CONF_MULTI 0x00000000 /* compatibility */ + +#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; + char *handler_conf; + ngx_flag_t no_ssl_init; +}; + + +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/src/core/ngx_config.h b/src/core/ngx_config.h new file mode 100644 index 0000000..a0bfa63 --- /dev/null +++ b/src/core/ngx_config.h @@ -0,0 +1,132 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_CONFIG_H_INCLUDED_ +#define _NGX_CONFIG_H_INCLUDED_ + + +#include + + +#if defined __DragonFly__ && !defined __FreeBSD__ +#define __FreeBSD__ 4 +#define __FreeBSD_version 480101 +#endif + + +#if (NGX_FREEBSD) +#include + + +#elif (NGX_LINUX) +#include + + +#elif (NGX_SOLARIS) +#include + + +#elif (NGX_DARWIN) +#include + + +#elif (NGX_WIN32) +#include + + +#else /* POSIX */ +#include + +#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 + + +#endif /* _NGX_CONFIG_H_INCLUDED_ */ diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c new file mode 100644 index 0000000..7992d6c --- /dev/null +++ b/src/core/ngx_connection.c @@ -0,0 +1,1467 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +ngx_os_io_t ngx_io; + + +static void ngx_drain_connections(void); + + +ngx_listening_t * +ngx_create_listening(ngx_conf_t *cf, void *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, NGX_SOCKADDRLEN); + if (ls[i].sockaddr == NULL) { + return NGX_ERROR; + } + + ls[i].socklen = NGX_SOCKADDRLEN; + if (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; + } + + 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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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) { + int reuseport; + + reuseport = 1; + + if (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, ignored", + &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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 + */ +#if (NGX_SSL) + if (c->asynch && ngx_del_async_conn) { + if (c->num_async_fds) { + ngx_del_async_conn(c, NGX_DISABLE_EVENT); + c->num_async_fds--; + } + } +#endif + 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; +#if (NGX_SSL) + ngx_event_t *aev; +#endif + 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(); + 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; +#if (NGX_SSL) + aev = c->async; +#endif + + ngx_memzero(c, sizeof(ngx_connection_t)); + + c->read = rev; + c->write = wev; +#if (NGX_SSL) + c->async = aev; +#endif + + c->fd = s; + c->log = log; + + instance = rev->instance; + + ngx_memzero(rev, sizeof(ngx_event_t)); + ngx_memzero(wev, sizeof(ngx_event_t)); +#if (NGX_SSL) + ngx_memzero(aev, sizeof(ngx_event_t)); +#endif + + rev->instance = !instance; + wev->instance = !instance; +#if (NGX_SSL) + aev->instance = !instance; +#endif + + rev->index = NGX_INVALID_INDEX; + wev->index = NGX_INVALID_INDEX; +#if (NGX_SSL) + aev->index = NGX_INVALID_INDEX; +#endif + + rev->data = c; + wev->data = c; +#if (NGX_SSL) + aev->data = c; +#endif + + wev->write = 1; +#if (NGX_SSL) + aev->async = 1; +#endif + + 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 (NGX_SSL) + if (c->async->timer_set) { + ngx_del_timer(c->async); + } + + if (c->asynch && ngx_del_async_conn) { + if (c->num_async_fds) { + ngx_del_async_conn(c, NGX_DISABLE_EVENT); + c->num_async_fds--; + } + } +#endif + + if (!c->shared) { + if (ngx_del_conn) { + ngx_del_conn(c, NGX_CLOSE_EVENT); + + } else { +#if (NGX_SSL) + if (c->asynch && ngx_del_async_conn) { + if (c->num_async_fds) { + ngx_del_async_conn(c, NGX_DISABLE_EVENT); + c->num_async_fds--; + } + } +#endif + 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); + } + +#if (NGX_SSL) + if (c->async->posted) { + ngx_delete_posted_event(c->async); + } +#endif + + c->read->closed = 1; + c->write->closed = 1; +#if (NGX_SSL) + c->async->closed = 1; +#endif + + ngx_reusable_connection(c, 0); + + log_error = c->log_error; + + ngx_free_connection(c); + + fd = c->fd; + c->fd = (ngx_socket_t) -1; +#if (NGX_SSL) + c->async_fd = (ngx_socket_t) -1; +#endif + + 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; + } + + /* we use ngx_cycle->log because c->log was in c->pool */ + + ngx_log_error(level, ngx_cycle->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); + +#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); + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_waiting, 1); +#endif + } +} + + +static void +ngx_drain_connections(void) +{ + ngx_int_t i; + ngx_queue_t *q; + ngx_connection_t *c; + + for (i = 0; i < 32; i++) { + if (ngx_queue_empty(&ngx_cycle->reusable_connections_queue)) { + break; + } + + q = ngx_queue_last(&ngx_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; + u_char sa[NGX_SOCKADDRLEN]; + 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 = NGX_SOCKADDRLEN; + + if (getsockname(c->fd, (struct sockaddr *) &sa, &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_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/src/core/ngx_connection.h b/src/core/ngx_connection.h new file mode 100644 index 0000000..0b5e59b --- /dev/null +++ b/src/core/ngx_connection.h @@ -0,0 +1,240 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_CONNECTION_H_INCLUDED_ +#define _NGX_CONNECTION_H_INCLUDED_ + + +#include +#include + + +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 && defined IPV6_V6ONLY) + unsigned ipv6only:1; +#endif +#if (NGX_HAVE_REUSEPORT) + unsigned reuseport:1; + unsigned add_reuseport:1; +#endif + unsigned keepalive:2; + +#if (NGX_HAVE_DEFERRED_ACCEPT) + unsigned deferred_accept:1; + unsigned delete_deferred:1; + unsigned add_deferred:1; +#ifdef SO_ACCEPTFILTER + char *accept_filter; +#endif +#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; +#if (NGX_SSL) + ngx_event_t *async; +#endif + + ngx_socket_t fd; +#if (NGX_SSL) + ngx_socket_t async_fd; +#endif + 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; + +#if (NGX_SSL) + ngx_ssl_connection_t *ssl; + ngx_flag_t asynch; +#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 unexpected_eof:1; + 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_SSL) + unsigned num_async_fds:8; +#endif +#if (NGX_HAVE_IOCP) + unsigned accept_context_updated:1; +#endif + +#if (NGX_HAVE_AIO_SENDFILE) + unsigned busy_count:2; +#endif + +#if (NGX_THREADS) + 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, void *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_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/src/core/ngx_core.h b/src/core/ngx_core.h new file mode 100644 index 0000000..2819c1a --- /dev/null +++ b/src/core/ngx_core.h @@ -0,0 +1,112 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_CORE_H_INCLUDED_ +#define _NGX_CORE_H_INCLUDED_ + + +#include + + +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; + +#if (NGX_THREADS) +typedef struct ngx_thread_task_s ngx_thread_task_t; +#endif + +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if (NGX_PCRE) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#if (NGX_OPENSSL) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + + +#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/src/core/ngx_cpuinfo.c b/src/core/ngx_cpuinfo.c new file mode 100644 index 0000000..7205319 --- /dev/null +++ b/src/core/ngx_cpuinfo.c @@ -0,0 +1,139 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +#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/src/core/ngx_crc.h b/src/core/ngx_crc.h new file mode 100644 index 0000000..35981bc --- /dev/null +++ b/src/core/ngx_crc.h @@ -0,0 +1,39 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_CRC_H_INCLUDED_ +#define _NGX_CRC_H_INCLUDED_ + + +#include +#include + + +/* 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/src/core/ngx_crc32.c b/src/core/ngx_crc32.c new file mode 100644 index 0000000..a5b4017 --- /dev/null +++ b/src/core/ngx_crc32.c @@ -0,0 +1,129 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +/* + * 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/src/core/ngx_crc32.h b/src/core/ngx_crc32.h new file mode 100644 index 0000000..f6d6865 --- /dev/null +++ b/src/core/ngx_crc32.h @@ -0,0 +1,79 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_CRC32_H_INCLUDED_ +#define _NGX_CRC32_H_INCLUDED_ + + +#include +#include + + +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/src/core/ngx_crypt.c b/src/core/ngx_crypt.c new file mode 100644 index 0000000..9db74f4 --- /dev/null +++ b/src/core/ngx_crypt.c @@ -0,0 +1,283 @@ + +/* + * Copyright (C) Maxim Dounin + */ + + +#include +#include +#include +#include +#if (NGX_HAVE_SHA1) +#include +#endif + + +#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); + +#if (NGX_HAVE_SHA1) + +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); + +#endif + + +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); + +#if (NGX_HAVE_SHA1) + } 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); +#endif + } + + /* 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; +} + + +#if (NGX_HAVE_SHA1) + +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_HAVE_SHA1 */ + +#endif /* NGX_CRYPT */ diff --git a/src/core/ngx_crypt.h b/src/core/ngx_crypt.h new file mode 100644 index 0000000..3869114 --- /dev/null +++ b/src/core/ngx_crypt.h @@ -0,0 +1,20 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_CRYPT_H_INCLUDED_ +#define _NGX_CRYPT_H_INCLUDED_ + + +#include +#include + + +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/src/core/ngx_cycle.c b/src/core/ngx_cycle.c new file mode 100644 index 0000000..5b6c4d1 --- /dev/null +++ b/src/core/ngx_cycle.c @@ -0,0 +1,1346 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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); + + +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; + +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, **env; + 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->no_ssl_init = old_cycle->no_ssl_init; + + 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; + + cycle->paths.elts = ngx_pcalloc(pool, n * sizeof(ngx_path_t *)); + if (cycle->paths.elts == NULL) { + ngx_destroy_pool(pool); + return NULL; + } + + cycle->paths.nelts = 0; + cycle->paths.size = sizeof(ngx_path_t *); + cycle->paths.nalloc = n; + cycle->paths.pool = pool; + + + if (ngx_array_init(&cycle->config_dump, pool, 1, sizeof(ngx_conf_dump_t)) + != NGX_OK) + { + ngx_destroy_pool(pool); + return NULL; + } + + 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; + + cycle->listening.elts = ngx_pcalloc(pool, n * sizeof(ngx_listening_t)); + if (cycle->listening.elts == NULL) { + ngx_destroy_pool(pool); + return NULL; + } + + cycle->listening.nelts = 0; + cycle->listening.size = sizeof(ngx_listening_t); + cycle->listening.nalloc = n; + cycle->listening.pool = pool; + + + 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; + conf.no_ssl_init = cycle->no_ssl_init; + +#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; + } + + ngx_shm_free(&oshm_zone[n].shm); + + 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 + && ngx_strncmp(oshm_zone[i].shm.name.data, + shm_zone[n].shm.name.data, + oshm_zone[i].shm.name.len) + == 0) + { + goto live_shm_zone; + } + } + + 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)) { + + /* + * perl_destruct() frees environ, if it is not the same as it was at + * perl_construct() time, therefore we save the previous cycle + * environment before ngx_conf_parse() where it will be changed. + */ + + env = environ; + environ = senv; + + ngx_destroy_pool(old_cycle->pool); + cycle->old_cycle = NULL; + + environ = env; + + 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; + ngx_old_cycles.elts = ngx_pcalloc(ngx_temp_pool, + n * sizeof(ngx_cycle_t *)); + if (ngx_old_cycles.elts == NULL) { + exit(1); + } + ngx_old_cycles.nelts = 0; + ngx_old_cycles.size = sizeof(ngx_cycle_t *); + ngx_old_cycles.nalloc = n; + ngx_old_cycles.pool = ngx_temp_pool; + + 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) { + ngx_close_file(file.fd); + 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((const char *) 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; + } +} diff --git a/src/core/ngx_cycle.h b/src/core/ngx_cycle.h new file mode 100644 index 0000000..64208a5 --- /dev/null +++ b/src/core/ngx_cycle.h @@ -0,0 +1,141 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_CYCLE_H_INCLUDED_ +#define _NGX_CYCLE_H_INCLUDED_ + + +#include +#include + + +#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; + 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_array_t listening; + ngx_array_t paths; + ngx_array_t config_dump; + 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; +#if (NGX_SSL) + ngx_event_t *async_events; +#endif + + 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; + ngx_flag_t no_ssl_init; +}; + + +typedef struct { + ngx_flag_t daemon; + ngx_flag_t master; + + ngx_msec_t timer_resolution; + + 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_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); + + +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/src/core/ngx_file.c b/src/core/ngx_file.c new file mode 100644 index 0000000..fc2dfd3 --- /dev/null +++ b/src/core/ngx_file.c @@ -0,0 +1,1102 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +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) +{ + uint32_t n; + ngx_err_t err; + ngx_pool_cleanup_t *cln; + ngx_pool_cleanup_file_t *clnf; + + file->name.len = path->name.len + 1 + path->len + 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 + + ngx_memcpy(file->name.data, path->name.data, path->name.len); + + 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(file->name.data + path->name.len + 1 + path->len, + "%010uD%Z", n); + + 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 < 3; 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 < 3; 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) +{ + 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; + } + + (*path)->level[0] = init->level[0]; + (*path)->level[1] = init->level[1]; + (*path)->level[2] = init->level[2]; + + (*path)->len = init->level[0] + (init->level[0] ? 1 : 0) + + init->level[1] + (init->level[1] ? 1 : 0) + + init->level[2] + (init->level[2] ? 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; + + access = (ngx_uint_t *) (confp + cmd->offset); + + if (*access != NGX_CONF_UNSET_UINT) { + return "is duplicate"; + } + + value = cf->args->elts; + + *access = 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; + + } 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; + } + + 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 < 3; 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((const char *) 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/src/core/ngx_file.h b/src/core/ngx_file.h new file mode 100644 index 0000000..5f8228b --- /dev/null +++ b/src/core/ngx_file.h @@ -0,0 +1,162 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_FILE_H_INCLUDED_ +#define _NGX_FILE_H_INCLUDED_ + + +#include +#include + + +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_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_event_aio_t *aio; +#endif + + unsigned valid_info:1; + unsigned directio:1; +}; + + +#define NGX_MAX_PATH_LEVEL 3 + + +typedef time_t (*ngx_path_manager_pt) (void *data); +typedef void (*ngx_path_loader_pt) (void *data); + + +typedef struct { + ngx_str_t name; + size_t len; + size_t level[3]; + + ngx_path_manager_pt manager; + 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[3]; +} 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/src/core/ngx_hash.c b/src/core/ngx_hash.c new file mode 100644 index 0000000..151e643 --- /dev/null +++ b/src/core/ngx_hash.c @@ -0,0 +1,989 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +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/src/core/ngx_hash.h b/src/core/ngx_hash.h new file mode 100644 index 0000000..abc3cbe --- /dev/null +++ b/src/core/ngx_hash.h @@ -0,0 +1,122 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HASH_H_INCLUDED_ +#define _NGX_HASH_H_INCLUDED_ + + +#include +#include + + +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/src/core/ngx_inet.c b/src/core/ngx_inet.c new file mode 100644 index 0000000..404f763 --- /dev/null +++ b/src/core/ngx_inet.c @@ -0,0 +1,1284 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +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; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + size_t n; + 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 { + p = ngx_snprintf(text, len, "unix:%s%Z", 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_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_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; + } + + if (last - p) { + + 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 == ':') { + 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; +} diff --git a/src/core/ngx_inet.h b/src/core/ngx_inet.h new file mode 100644 index 0000000..0555750 --- /dev/null +++ b/src/core/ngx_inet.h @@ -0,0 +1,122 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_INET_H_INCLUDED_ +#define _NGX_INET_H_INCLUDED_ + + +#include +#include + + +/* + * TODO: autoconfigure NGX_SOCKADDRLEN and NGX_SOCKADDR_STRLEN as + * sizeof(struct sockaddr_storage) + * sizeof(struct sockaddr_un) + * sizeof(struct sockaddr_in6) + * sizeof(struct sockaddr_in) + */ + +#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(struct sockaddr_un) - offsetof(struct sockaddr_un, sun_path)) + +#if (NGX_HAVE_UNIX_DOMAIN) +#define NGX_SOCKADDR_STRLEN (sizeof("unix:") - 1 + NGX_UNIX_ADDRSTRLEN) +#else +#define NGX_SOCKADDR_STRLEN (NGX_INET6_ADDRSTRLEN + sizeof("[]:65535") - 1) +#endif + +#if (NGX_HAVE_UNIX_DOMAIN) +#define NGX_SOCKADDRLEN sizeof(struct sockaddr_un) +#else +#define NGX_SOCKADDRLEN 512 +#endif + + +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 one_addr:1; /* compatibility */ + + unsigned no_port:1; + unsigned wildcard:1; + + socklen_t socklen; + u_char sockaddr[NGX_SOCKADDRLEN]; + + 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_parse_addr(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); + + +#endif /* _NGX_INET_H_INCLUDED_ */ diff --git a/src/core/ngx_list.c b/src/core/ngx_list.c new file mode 100644 index 0000000..d0eb159 --- /dev/null +++ b/src/core/ngx_list.c @@ -0,0 +1,63 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +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/src/core/ngx_list.h b/src/core/ngx_list.h new file mode 100644 index 0000000..e0fe643 --- /dev/null +++ b/src/core/ngx_list.h @@ -0,0 +1,83 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_LIST_H_INCLUDED_ +#define _NGX_LIST_H_INCLUDED_ + + +#include +#include + + +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/src/core/ngx_log.c b/src/core/ngx_log.c new file mode 100644 index 0000000..8e9408d --- /dev/null +++ b/src/core/ngx_log.c @@ -0,0 +1,755 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +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/src/core/ngx_log.h b/src/core/ngx_log.h new file mode 100644 index 0000000..afb73bf --- /dev/null +++ b/src/core/ngx_log.h @@ -0,0 +1,268 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_LOG_H_INCLUDED_ +#define _NGX_LOG_H_INCLUDED_ + + +#include +#include + + +#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/src/core/ngx_md5.c b/src/core/ngx_md5.c new file mode 100644 index 0000000..440c75b --- /dev/null +++ b/src/core/ngx_md5.c @@ -0,0 +1,289 @@ + +/* + * An internal implementation, based on Alexander Peslyak's + * public domain implementation: + * http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 + * It is not expected to be optimal and is used only + * if no MD5 implementation was found in system. + */ + + +#include +#include +#include + + +#if !(NGX_HAVE_MD5) + +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; +} + +#endif diff --git a/src/core/ngx_md5.h b/src/core/ngx_md5.h new file mode 100644 index 0000000..18d09d6 --- /dev/null +++ b/src/core/ngx_md5.h @@ -0,0 +1,60 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_MD5_H_INCLUDED_ +#define _NGX_MD5_H_INCLUDED_ + + +#include +#include + + +#if (NGX_HAVE_MD5) + +#if (NGX_HAVE_OPENSSL_MD5_H) +#include +#else +#include +#endif + + +typedef MD5_CTX ngx_md5_t; + + +#if (NGX_OPENSSL_MD5) + +#define ngx_md5_init MD5_Init +#define ngx_md5_update MD5_Update +#define ngx_md5_final MD5_Final + +#else + +#define ngx_md5_init MD5Init +#define ngx_md5_update MD5Update +#define ngx_md5_final MD5Final + +#endif + + +#else /* !NGX_HAVE_MD5 */ + + +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 + +#endif /* _NGX_MD5_H_INCLUDED_ */ diff --git a/src/core/ngx_module.c b/src/core/ngx_module.c new file mode 100644 index 0000000..3e3c506 --- /dev/null +++ b/src/core/ngx_module.c @@ -0,0 +1,360 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Maxim Dounin + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +#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/src/core/ngx_module.h b/src/core/ngx_module.h new file mode 100644 index 0000000..e911cb4 --- /dev/null +++ b/src/core/ngx_module.h @@ -0,0 +1,307 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Maxim Dounin + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_MODULE_H_INCLUDED_ +#define _NGX_MODULE_H_INCLUDED_ + + +#include +#include +#include + + +#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) +#define NGX_MODULE_SIGNATURE_3 "1" +#else +#define NGX_MODULE_SIGNATURE_3 "0" +#endif + +#if (NGX_HAVE_AIO_SENDFILE) +#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 + +#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) +#define NGX_MODULE_SIGNATURE_9 "1" +#else +#define NGX_MODULE_SIGNATURE_9 "0" +#endif + +#if (NGX_HAVE_REUSEPORT) +#define NGX_MODULE_SIGNATURE_10 "1" +#else +#define NGX_MODULE_SIGNATURE_10 "0" +#endif + +#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) +#define NGX_MODULE_SIGNATURE_11 "1" +#else +#define NGX_MODULE_SIGNATURE_11 "0" +#endif + +#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT) +#define NGX_MODULE_SIGNATURE_12 "1" +#else +#define NGX_MODULE_SIGNATURE_12 "0" +#endif + +#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 + +#if (NGX_HAVE_MD5) +#define NGX_MODULE_SIGNATURE_17 "1" +#else +#define NGX_MODULE_SIGNATURE_17 "0" +#endif + +#if (NGX_HAVE_SHA1) +#define NGX_MODULE_SIGNATURE_18 "1" +#else +#define NGX_MODULE_SIGNATURE_18 "0" +#endif + +#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) +#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) +#define NGX_MODULE_SIGNATURE_24 "1" +#else +#define NGX_MODULE_SIGNATURE_24 "0" +#endif + +#if (NGX_HTTP_V2) +#define NGX_MODULE_SIGNATURE_25 "1" +#else +#define NGX_MODULE_SIGNATURE_25 "0" +#endif + +#if (NGX_HTTP_GZIP) +#define NGX_MODULE_SIGNATURE_26 "1" +#else +#define NGX_MODULE_SIGNATURE_26 "0" +#endif + +#if (NGX_HTTP_DEGRADATION) +#define NGX_MODULE_SIGNATURE_27 "1" +#else +#define NGX_MODULE_SIGNATURE_27 "0" +#endif + +#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 + +#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 + + +#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/src/core/ngx_murmurhash.c b/src/core/ngx_murmurhash.c new file mode 100644 index 0000000..c31e0e0 --- /dev/null +++ b/src/core/ngx_murmurhash.c @@ -0,0 +1,50 @@ + +/* + * Copyright (C) Austin Appleby + */ + + +#include +#include + + +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; + case 2: + h ^= data[1] << 8; + case 1: + h ^= data[0]; + h *= 0x5bd1e995; + } + + h ^= h >> 13; + h *= 0x5bd1e995; + h ^= h >> 15; + + return h; +} diff --git a/src/core/ngx_murmurhash.h b/src/core/ngx_murmurhash.h new file mode 100644 index 0000000..54e867d --- /dev/null +++ b/src/core/ngx_murmurhash.h @@ -0,0 +1,19 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_MURMURHASH_H_INCLUDED_ +#define _NGX_MURMURHASH_H_INCLUDED_ + + +#include +#include + + +uint32_t ngx_murmur_hash2(u_char *data, size_t len); + + +#endif /* _NGX_MURMURHASH_H_INCLUDED_ */ diff --git a/src/core/ngx_open_file_cache.c b/src/core/ngx_open_file_cache.c new file mode 100644 index 0000000..b23ee78 --- /dev/null +++ b/src/core/ngx_open_file_cache.c @@ -0,0 +1,1253 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +/* + * 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/src/core/ngx_open_file_cache.h b/src/core/ngx_open_file_cache.h new file mode 100644 index 0000000..d119c12 --- /dev/null +++ b/src/core/ngx_open_file_cache.h @@ -0,0 +1,129 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +#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/src/core/ngx_output_chain.c b/src/core/ngx_output_chain.c new file mode 100644 index 0000000..f784578 --- /dev/null +++ b/src/core/ngx_output_chain.c @@ -0,0 +1,767 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#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/src/core/ngx_palloc.c b/src/core/ngx_palloc.c new file mode 100644 index 0000000..d3044ac --- /dev/null +++ b/src/core/ngx_palloc.c @@ -0,0 +1,430 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +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/src/core/ngx_palloc.h b/src/core/ngx_palloc.h new file mode 100644 index 0000000..d652829 --- /dev/null +++ b/src/core/ngx_palloc.h @@ -0,0 +1,95 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_PALLOC_H_INCLUDED_ +#define _NGX_PALLOC_H_INCLUDED_ + + +#include +#include + + +/* + * 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; + + +void *ngx_alloc(size_t size, ngx_log_t *log); +void *ngx_calloc(size_t size, ngx_log_t *log); + +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/src/core/ngx_parse.c b/src/core/ngx_parse.c new file mode 100644 index 0000000..7b60c5f --- /dev/null +++ b/src/core/ngx_parse.c @@ -0,0 +1,273 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +ssize_t +ngx_parse_size(ngx_str_t *line) +{ + u_char unit; + size_t len; + ssize_t size, scale, max; + + len = line->len; + 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; + 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/src/core/ngx_parse.h b/src/core/ngx_parse.h new file mode 100644 index 0000000..ec093b5 --- /dev/null +++ b/src/core/ngx_parse.h @@ -0,0 +1,21 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_PARSE_H_INCLUDED_ +#define _NGX_PARSE_H_INCLUDED_ + + +#include +#include + + +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/src/core/ngx_parse_time.c b/src/core/ngx_parse_time.c new file mode 100644 index 0000000..13afde3 --- /dev/null +++ b/src/core/ngx_parse_time.c @@ -0,0 +1,276 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +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/src/core/ngx_parse_time.h b/src/core/ngx_parse_time.h new file mode 100644 index 0000000..aa542eb --- /dev/null +++ b/src/core/ngx_parse_time.h @@ -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 +#include + + +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/src/core/ngx_proxy_protocol.c b/src/core/ngx_proxy_protocol.c new file mode 100644 index 0000000..f347e7f --- /dev/null +++ b/src/core/ngx_proxy_protocol.c @@ -0,0 +1,140 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +u_char * +ngx_proxy_protocol_read(ngx_connection_t *c, u_char *buf, u_char *last) +{ + size_t len; + u_char ch, *p, *addr; + + p = buf; + len = last - buf; + + 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; + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0, + "PROXY protocol address: \"%V\"", &c->proxy_protocol_addr); + +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); + + port = ntohs(((struct sockaddr_in *) c->sockaddr)->sin_port); + lport = ntohs(((struct sockaddr_in *) c->local_sockaddr)->sin_port); + + break; + +#if (NGX_HAVE_INET6) + case AF_INET6: + buf = ngx_cpymem(buf, "PROXY TCP6 ", sizeof("PROXY TCP6 ") - 1); + + port = ntohs(((struct sockaddr_in6 *) c->sockaddr)->sin6_port); + lport = ntohs(((struct sockaddr_in6 *) c->local_sockaddr)->sin6_port); + + 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); + + return ngx_slprintf(buf, last, " %ui %ui" CRLF, port, lport); +} diff --git a/src/core/ngx_proxy_protocol.h b/src/core/ngx_proxy_protocol.h new file mode 100644 index 0000000..fb848f6 --- /dev/null +++ b/src/core/ngx_proxy_protocol.h @@ -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 +#include + + +#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/src/core/ngx_queue.c b/src/core/ngx_queue.c new file mode 100644 index 0000000..3cacaf3 --- /dev/null +++ b/src/core/ngx_queue.c @@ -0,0 +1,80 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +/* + * 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/src/core/ngx_queue.h b/src/core/ngx_queue.h new file mode 100644 index 0000000..038bf12 --- /dev/null +++ b/src/core/ngx_queue.h @@ -0,0 +1,112 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +#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/src/core/ngx_radix_tree.c b/src/core/ngx_radix_tree.c new file mode 100644 index 0000000..c1d8737 --- /dev/null +++ b/src/core/ngx_radix_tree.c @@ -0,0 +1,488 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +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/src/core/ngx_radix_tree.h b/src/core/ngx_radix_tree.h new file mode 100644 index 0000000..4fe06e0 --- /dev/null +++ b/src/core/ngx_radix_tree.h @@ -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 +#include + + +#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/src/core/ngx_rbtree.c b/src/core/ngx_rbtree.c new file mode 100644 index 0000000..6c66f40 --- /dev/null +++ b/src/core/ngx_rbtree.c @@ -0,0 +1,380 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +/* + * 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 = (ngx_rbtree_node_t **) &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 = (ngx_rbtree_node_t **) &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; +} diff --git a/src/core/ngx_rbtree.h b/src/core/ngx_rbtree.h new file mode 100644 index 0000000..1d33e3f --- /dev/null +++ b/src/core/ngx_rbtree.h @@ -0,0 +1,82 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_RBTREE_H_INCLUDED_ +#define _NGX_RBTREE_H_INCLUDED_ + + +#include +#include + + +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); + + +#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/src/core/ngx_regex.c b/src/core/ngx_regex.c new file mode 100644 index 0000000..9939dce --- /dev/null +++ b/src/core/ngx_regex.c @@ -0,0 +1,435 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +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/src/core/ngx_regex.h b/src/core/ngx_regex.h new file mode 100644 index 0000000..680486c --- /dev/null +++ b/src/core/ngx_regex.h @@ -0,0 +1,60 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_REGEX_H_INCLUDED_ +#define _NGX_REGEX_H_INCLUDED_ + + +#include +#include + +#include + + +#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/src/core/ngx_resolver.c b/src/core/ngx_resolver.c new file mode 100644 index 0000000..e00fe22 --- /dev/null +++ b/src/core/ngx_resolver.c @@ -0,0 +1,4662 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#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)) + + +ngx_int_t ngx_udp_connect(ngx_resolver_connection_t *rec); +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 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->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) { + 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) { + 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) { + return NGX_ERROR; + } + + 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); + } + + 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 (ctx->event == NULL && ctx->timeout) { + 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->ident = -1; + + ngx_add_timer(ctx->event, ctx->timeout); + } + + last->next = rn->waiting; + rn->waiting = ctx; + ctx->state = NGX_AGAIN; + + 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 < 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 (ctx->event == NULL && ctx->timeout) { + ctx->event = ngx_resolver_calloc(r, sizeof(ngx_event_t)); + if (ctx->event == NULL) { + goto failed; + } + + ctx->event->handler = ngx_resolver_timeout_handler; + ctx->event->data = ctx; + ctx->event->log = r->log; + ctx->ident = -1; + + ngx_add_timer(ctx->event, ctx->timeout); + } + + if (ngx_queue_empty(resend_queue)) { + 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; + + 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 (ctx->event == NULL && ctx->timeout) { + 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->ident = -1; + + ngx_add_timer(ctx->event, ctx->timeout); + } + + ctx->next = rn->waiting; + rn->waiting = ctx; + ctx->state = NGX_AGAIN; + 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 (ctx->event == NULL && ctx->timeout) { + ctx->event = ngx_resolver_calloc(r, sizeof(ngx_event_t)); + if (ctx->event == NULL) { + goto failed; + } + + ctx->event->handler = ngx_resolver_timeout_handler; + ctx->event->data = ctx; + ctx->event->log = r->log; + ctx->ident = -1; + + ngx_add_timer(ctx->event, ctx->timeout); + } + + if (ngx_queue_empty(resend_queue)) { + 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->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) +#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; + + for (i = 0; i < 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 = 0; + + 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; + u_char (*sockaddr)[NGX_SOCKADDRLEN]; + ngx_addr_t *addrs; + ngx_resolver_t *r; + struct sockaddr_in *sin; + ngx_resolver_ctx_t *ctx; + ngx_resolver_srv_name_t *srv; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + + r = cctx->resolver; + ctx = cctx->data; + srv = cctx->srvs; + + ctx->count--; + + srv->ctx = NULL; + + 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) { + ngx_resolve_name_done(cctx); + + ctx->state = NGX_ERROR; + ctx->valid = ngx_time() + (r->valid ? r->valid : 10); + + ctx->handler(ctx); + return; + } + + sockaddr = ngx_resolver_alloc(r, cctx->naddrs * NGX_SOCKADDRLEN); + if (sockaddr == NULL) { + ngx_resolver_free(r, addrs); + ngx_resolve_name_done(cctx); + + ctx->state = NGX_ERROR; + ctx->valid = ngx_time() + (r->valid ? r->valid : 10); + + ctx->handler(ctx); + return; + } + + for (i = 0; i < cctx->naddrs; i++) { + addrs[i].sockaddr = (struct sockaddr *) sockaddr[i]; + addrs[i].socklen = cctx->addrs[i].socklen; + + ngx_memcpy(sockaddr[i], cctx->addrs[i].sockaddr, + addrs[i].socklen); + + switch (addrs[i].sockaddr->sa_family) { +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) addrs[i].sockaddr; + sin6->sin6_port = htons(srv->port); + break; +#endif + default: /* AF_INET */ + sin = (struct sockaddr_in *) addrs[i].sockaddr; + sin->sin_port = htons(srv->port); + } + } + + srv->addrs = addrs; + srv->naddrs = cctx->naddrs; + } + + 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 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 < 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; + u_char (*sockaddr)[NGX_SOCKADDRLEN]; + in_addr_t *addr; + 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 * NGX_SOCKADDRLEN); + 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 = (struct sockaddr_in *) sockaddr[d]; + 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 == rn->naddrs) { + j = 0; + } + } while (++i < 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 = (struct sockaddr_in6 *) sockaddr[d]; + 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; + + naddrs = 0; + + for (i = 0; i < ctx->nsrvs; i++) { + naddrs += ctx->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; + } + + srvs = ctx->srvs; + nsrvs = ctx->nsrvs; + + 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; +} + + +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 = 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; +} + + +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 = 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/src/core/ngx_resolver.h b/src/core/ngx_resolver.h new file mode 100644 index 0000000..e36cfdc --- /dev/null +++ b/src/core/ngx_resolver.h @@ -0,0 +1,237 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +#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_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; + + ngx_uint_t quick; /* unsigned quick: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/src/core/ngx_rwlock.c b/src/core/ngx_rwlock.c new file mode 100644 index 0000000..905de78 --- /dev/null +++ b/src/core/ngx_rwlock.c @@ -0,0 +1,120 @@ + +/* + * Copyright (C) Ruslan Ermilov + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +#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) { + *lock = 0; + return; + } + + for ( ;; ) { + + if (ngx_atomic_cmp_set(lock, readers, readers - 1)) { + return; + } + + readers = *lock; + } +} + + +#else + +#if (NGX_HTTP_UPSTREAM_ZONE || NGX_STREAM_UPSTREAM_ZONE) + +#error ngx_atomic_cmp_set() is not defined! + +#endif + +#endif diff --git a/src/core/ngx_rwlock.h b/src/core/ngx_rwlock.h new file mode 100644 index 0000000..8b16eca --- /dev/null +++ b/src/core/ngx_rwlock.h @@ -0,0 +1,21 @@ + +/* + * Copyright (C) Ruslan Ermilov + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_RWLOCK_H_INCLUDED_ +#define _NGX_RWLOCK_H_INCLUDED_ + + +#include +#include + + +void ngx_rwlock_wlock(ngx_atomic_t *lock); +void ngx_rwlock_rlock(ngx_atomic_t *lock); +void ngx_rwlock_unlock(ngx_atomic_t *lock); + + +#endif /* _NGX_RWLOCK_H_INCLUDED_ */ diff --git a/src/core/ngx_sha1.h b/src/core/ngx_sha1.h new file mode 100644 index 0000000..81c909e --- /dev/null +++ b/src/core/ngx_sha1.h @@ -0,0 +1,31 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_SHA1_H_INCLUDED_ +#define _NGX_SHA1_H_INCLUDED_ + + +#include +#include + + +#if (NGX_HAVE_OPENSSL_SHA1_H) +#include +#else +#include +#endif + + +typedef SHA_CTX ngx_sha1_t; + + +#define ngx_sha1_init SHA1_Init +#define ngx_sha1_update SHA1_Update +#define ngx_sha1_final SHA1_Final + + +#endif /* _NGX_SHA1_H_INCLUDED_ */ diff --git a/src/core/ngx_shmtx.c b/src/core/ngx_shmtx.c new file mode 100644 index 0000000..a255903 --- /dev/null +++ b/src/core/ngx_shmtx.c @@ -0,0 +1,310 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +#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/src/core/ngx_shmtx.h b/src/core/ngx_shmtx.h new file mode 100644 index 0000000..91e11be --- /dev/null +++ b/src/core/ngx_shmtx.h @@ -0,0 +1,49 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_SHMTX_H_INCLUDED_ +#define _NGX_SHMTX_H_INCLUDED_ + + +#include +#include + + +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/src/core/ngx_slab.c b/src/core/ngx_slab.c new file mode 100644 index 0000000..56e7765 --- /dev/null +++ b/src/core/ngx_slab.c @@ -0,0 +1,790 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + +#include +#include + + +#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 + + +#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_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; + + /* STUB */ + if (ngx_slab_max_size == 0) { + 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 */ + } + } + /**/ + + pool->min_size = 1 << pool->min_shift; + + p = (u_char *) pool + sizeof(ngx_slab_pool_t); + size = pool->end - p; + + ngx_slab_junk(p, size); + + slots = (ngx_slab_page_t *) p; + n = ngx_pagesize_shift - pool->min_shift; + + for (i = 0; i < n; i++) { + slots[i].slab = 0; + slots[i].next = &slots[i]; + slots[i].prev = 0; + } + + p += n * sizeof(ngx_slab_page_t); + + pages = (ngx_uint_t) (size / (ngx_pagesize + sizeof(ngx_slab_page_t))); + + ngx_memzero(p, pages * sizeof(ngx_slab_page_t)); + + pool->pages = (ngx_slab_page_t *) p; + + pool->free.prev = 0; + pool->free.next = (ngx_slab_page_t *) p; + + pool->pages->slab = pages; + pool->pages->next = &pool->free; + pool->pages->prev = (uintptr_t) &pool->free; + + pool->start = (u_char *) + ngx_align_ptr((uintptr_t) p + pages * sizeof(ngx_slab_page_t), + ngx_pagesize); + + m = pages - (pool->end - pool->start) / ngx_pagesize; + if (m > 0) { + pages -= m; + pool->pages->slab = pages; + } + + pool->last = pool->pages + 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, n, m, mask, *bitmap; + ngx_uint_t i, 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 = (page - pool->pages) << ngx_pagesize_shift; + p += (uintptr_t) pool->start; + + } 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 { + size = pool->min_size; + shift = pool->min_shift; + slot = 0; + } + + ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0, + "slab alloc: %uz slot: %ui", size, slot); + + slots = (ngx_slab_page_t *) ((u_char *) pool + sizeof(ngx_slab_pool_t)); + page = slots[slot].next; + + if (page->next != page) { + + if (shift < ngx_slab_exact_shift) { + + do { + p = (page - pool->pages) << ngx_pagesize_shift; + bitmap = (uintptr_t *) (pool->start + p); + + map = (1 << (ngx_pagesize_shift - shift)) + / (sizeof(uintptr_t) * 8); + + 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 * sizeof(uintptr_t) * 8) << shift) + + (i << shift); + + if (bitmap[n] == NGX_SLAB_BUSY) { + for (n = n + 1; n < map; n++) { + if (bitmap[n] != NGX_SLAB_BUSY) { + p = (uintptr_t) bitmap + i; + + goto done; + } + } + + prev = (ngx_slab_page_t *) + (page->prev & ~NGX_SLAB_PAGE_MASK); + prev->next = page->next; + page->next->prev = page->prev; + + page->next = NULL; + page->prev = NGX_SLAB_SMALL; + } + + p = (uintptr_t) bitmap + i; + + goto done; + } + } + } + + page = page->next; + + } while (page); + + } else if (shift == ngx_slab_exact_shift) { + + do { + if (page->slab != NGX_SLAB_BUSY) { + + 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_t *) + (page->prev & ~NGX_SLAB_PAGE_MASK); + prev->next = page->next; + page->next->prev = page->prev; + + page->next = NULL; + page->prev = NGX_SLAB_EXACT; + } + + p = (page - pool->pages) << ngx_pagesize_shift; + p += i << shift; + p += (uintptr_t) pool->start; + + goto done; + } + } + + page = page->next; + + } while (page); + + } else { /* shift > ngx_slab_exact_shift */ + + n = ngx_pagesize_shift - (page->slab & NGX_SLAB_SHIFT_MASK); + n = 1 << n; + n = ((uintptr_t) 1 << n) - 1; + mask = n << NGX_SLAB_MAP_SHIFT; + + do { + if ((page->slab & NGX_SLAB_MAP_MASK) != mask) { + + 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_t *) + (page->prev & ~NGX_SLAB_PAGE_MASK); + prev->next = page->next; + page->next->prev = page->prev; + + page->next = NULL; + page->prev = NGX_SLAB_BIG; + } + + p = (page - pool->pages) << ngx_pagesize_shift; + p += i << shift; + p += (uintptr_t) pool->start; + + goto done; + } + } + + page = page->next; + + } while (page); + } + } + + page = ngx_slab_alloc_pages(pool, 1); + + if (page) { + if (shift < ngx_slab_exact_shift) { + p = (page - pool->pages) << ngx_pagesize_shift; + bitmap = (uintptr_t *) (pool->start + p); + + s = 1 << shift; + n = (1 << (ngx_pagesize_shift - shift)) / 8 / s; + + if (n == 0) { + n = 1; + } + + bitmap[0] = (2 << n) - 1; + + map = (1 << (ngx_pagesize_shift - shift)) / (sizeof(uintptr_t) * 8); + + for (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; + + p = ((page - pool->pages) << ngx_pagesize_shift) + s * n; + p += (uintptr_t) pool->start; + + 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; + + p = (page - pool->pages) << ngx_pagesize_shift; + p += (uintptr_t) pool->start; + + 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; + + p = (page - pool->pages) << ngx_pagesize_shift; + p += (uintptr_t) pool->start; + + goto done; + } + } + + p = 0; + +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 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 = page->prev & NGX_SLAB_PAGE_MASK; + + switch (type) { + + case NGX_SLAB_SMALL: + + shift = slab & NGX_SLAB_SHIFT_MASK; + size = 1 << shift; + + if ((uintptr_t) p & (size - 1)) { + goto wrong_chunk; + } + + n = ((uintptr_t) p & (ngx_pagesize - 1)) >> shift; + m = (uintptr_t) 1 << (n & (sizeof(uintptr_t) * 8 - 1)); + n /= (sizeof(uintptr_t) * 8); + bitmap = (uintptr_t *) + ((uintptr_t) p & ~((uintptr_t) ngx_pagesize - 1)); + + if (bitmap[n] & m) { + + if (page->next == NULL) { + slots = (ngx_slab_page_t *) + ((u_char *) pool + sizeof(ngx_slab_pool_t)); + slot = shift - pool->min_shift; + + 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 = (1 << (ngx_pagesize_shift - shift)) / 8 / (1 << shift); + + if (n == 0) { + n = 1; + } + + if (bitmap[0] & ~(((uintptr_t) 1 << n) - 1)) { + goto done; + } + + map = (1 << (ngx_pagesize_shift - shift)) / (sizeof(uintptr_t) * 8); + + for (n = 1; n < map; n++) { + if (bitmap[n]) { + goto done; + } + } + + ngx_slab_free_pages(pool, page, 1); + + 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) { + if (slab == NGX_SLAB_BUSY) { + slots = (ngx_slab_page_t *) + ((u_char *) pool + sizeof(ngx_slab_pool_t)); + slot = ngx_slab_exact_shift - pool->min_shift; + + 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); + + goto done; + } + + goto chunk_already_free; + + case NGX_SLAB_BIG: + + shift = slab & NGX_SLAB_SHIFT_MASK; + size = 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) { + + if (page->next == NULL) { + slots = (ngx_slab_page_t *) + ((u_char *) pool + sizeof(ngx_slab_pool_t)); + slot = shift - pool->min_shift; + + 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); + + 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_FREE) { + 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: + + 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; + + 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_uint_t type; + ngx_slab_page_t *prev, *join; + + page->slab = pages--; + + if (pages) { + ngx_memzero(&page[1], pages * sizeof(ngx_slab_page_t)); + } + + if (page->next) { + prev = (ngx_slab_page_t *) (page->prev & ~NGX_SLAB_PAGE_MASK); + prev->next = page->next; + page->next->prev = page->prev; + } + + join = page + page->slab; + + if (join < pool->last) { + type = join->prev & NGX_SLAB_PAGE_MASK; + + if (type == NGX_SLAB_PAGE) { + + if (join->next != NULL) { + pages += join->slab; + page->slab += join->slab; + + prev = (ngx_slab_page_t *) (join->prev & ~NGX_SLAB_PAGE_MASK); + 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; + type = join->prev & NGX_SLAB_PAGE_MASK; + + if (type == NGX_SLAB_PAGE) { + + if (join->slab == NGX_SLAB_PAGE_FREE) { + join = (ngx_slab_page_t *) (join->prev & ~NGX_SLAB_PAGE_MASK); + } + + if (join->next != NULL) { + pages += join->slab; + join->slab += page->slab; + + prev = (ngx_slab_page_t *) (join->prev & ~NGX_SLAB_PAGE_MASK); + 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/src/core/ngx_slab.h b/src/core/ngx_slab.h new file mode 100644 index 0000000..2922a80 --- /dev/null +++ b/src/core/ngx_slab.h @@ -0,0 +1,59 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_SLAB_H_INCLUDED_ +#define _NGX_SLAB_H_INCLUDED_ + + +#include +#include + + +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_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; + + 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_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/src/core/ngx_spinlock.c b/src/core/ngx_spinlock.c new file mode 100644 index 0000000..9c93afa --- /dev/null +++ b/src/core/ngx_spinlock.c @@ -0,0 +1,53 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +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/src/core/ngx_string.c b/src/core/ngx_string.c new file mode 100644 index 0000000..cf665a4 --- /dev/null +++ b/src/core/ngx_string.c @@ -0,0 +1,1976 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +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--; + } +} + + +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] & (1 << (*src & 0x1f))) { + n++; + } + src++; + size--; + } + + return (uintptr_t) n; + } + + while (size) { + if (escape[*src >> 5] & (1 << (*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("<") - 2; + break; + + case '>': + len += sizeof(">") - 2; + break; + + case '&': + len += sizeof("&") - 2; + break; + + case '"': + len += sizeof(""") - 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) { + len += sizeof("\\u001F") - 2; + } + + size--; + } + + return (uintptr_t) len; + } + + while (size) { + ch = *src++; + + if (ch > 0x1f) { + + if (ch == '\\' || ch == '"') { + *dst++ = '\\'; + } + + *dst++ = ch; + + } else { + *dst++ = '\\'; *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/src/core/ngx_string.h b/src/core/ngx_string.h new file mode 100644 index 0000000..b7dc39c --- /dev/null +++ b/src/core/ngx_string.h @@ -0,0 +1,245 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_STRING_H_INCLUDED_ +#define _NGX_STRING_H_INCLUDED_ + + +#include +#include + + +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) + +#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 + +#if (NGX_SECURE_MEM) + +#define _MIN_(a,b) (((a)<(b))?(a):(b)) +#define MEMCPY_S(dest, src, dest_sz, src_sz) \ + memcpy((void *)(dest), (void *) (src), (size_t)_MIN_(dest_sz, src_sz)) +#define ngx_memcpy(dst, src, n) (void) MEMCPY_S(dst, src, n, n) +#define ngx_cpymem(dst, src, n) (((u_char *) MEMCPY_S(dst, src, n, 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 /* NGX_SECURE_MEM */ + +#endif /* NGX_MEMCPY_LIMIT */ + +#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/src/core/ngx_syslog.c b/src/core/ngx_syslog.c new file mode 100644 index 0000000..0a67928 --- /dev/null +++ b/src/core/ngx_syslog.c @@ -0,0 +1,382 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#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 (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/src/core/ngx_syslog.h b/src/core/ngx_syslog.h new file mode 100644 index 0000000..cc4c842 --- /dev/null +++ b/src/core/ngx_syslog.h @@ -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/src/core/ngx_thread_pool.c b/src/core/ngx_thread_pool.c new file mode 100644 index 0000000..f3655aa --- /dev/null +++ b/src/core/ngx_thread_pool.c @@ -0,0 +1,634 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + * Copyright (C) Ruslan Ermilov + */ + + +#include +#include +#include + + +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; + } + +#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/src/core/ngx_thread_pool.h b/src/core/ngx_thread_pool.h new file mode 100644 index 0000000..5e5adf6 --- /dev/null +++ b/src/core/ngx_thread_pool.h @@ -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 +#include +#include + + +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/src/core/ngx_times.c b/src/core/ngx_times.c new file mode 100644 index 0000000..843314a --- /dev/null +++ b/src/core/ngx_times.c @@ -0,0 +1,428 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +/* + * 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_msec_t) sec * 1000 + 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); +} + + +#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 n, sec, min, hour, mday, mon, year, wday, days, leap; + + /* the calculation is valid for positive time_t only */ + + n = (ngx_uint_t) t; + + days = n / 86400; + + /* January 1, 1970 was Thursday */ + + wday = (4 + days) % 7; + + n %= 86400; + hour = n / 3600; + n %= 3600; + min = n / 60; + sec = n % 60; + + /* + * the algorithm based on Gauss' formula, + * see src/http/ngx_http_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/src/core/ngx_times.h b/src/core/ngx_times.h new file mode 100644 index 0000000..94aedcd --- /dev/null +++ b/src/core/ngx_times.h @@ -0,0 +1,52 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_TIMES_H_INCLUDED_ +#define _NGX_TIMES_H_INCLUDED_ + + +#include +#include + + +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/src/event/modules/ngx_devpoll_module.c b/src/event/modules/ngx_devpoll_module.c new file mode 100644 index 0000000..bf33a05 --- /dev/null +++ b/src/event/modules/ngx_devpoll_module.c @@ -0,0 +1,564 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#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 +}; + + +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 */ + NULL, /* add an async conn */ + NULL /* del an async conn */ + } + +}; + +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; +} + + +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)) + && (revents & (POLLIN|POLLOUT)) == 0) + { + /* + * if the error events were returned without POLLIN or POLLOUT, + * then add these flags 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/src/event/modules/ngx_epoll_module.c b/src/event/modules/ngx_epoll_module.c new file mode 100644 index 0000000..fd353af --- /dev/null +++ b/src/event/modules/ngx_epoll_module.c @@ -0,0 +1,1069 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#if (NGX_TEST_BUILD_EPOLL) + +/* epoll declarations */ + +#define EPOLLIN 0x001 +#define EPOLLPRI 0x002 +#define EPOLLOUT 0x004 +#define EPOLLRDNORM 0x040 +#define EPOLLRDBAND 0x080 +#define EPOLLWRNORM 0x100 +#define EPOLLWRBAND 0x200 +#define EPOLLMSG 0x400 +#define EPOLLERR 0x008 +#define EPOLLHUP 0x010 + +#define EPOLLRDHUP 0x2000 + +#define EPOLLET 0x80000000 +#define EPOLLONESHOT 0x40000000 + +#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 +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_SSL) +static ngx_int_t ngx_epoll_add_async_connection(ngx_connection_t *c); +static ngx_int_t ngx_epoll_del_async_connection(ngx_connection_t *c, + ngx_uint_t flags); +#endif + +#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 + +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 +}; + + +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 */ +#if (NGX_SSL) + ngx_epoll_add_async_connection, /* add an async conn */ + ngx_epoll_del_async_connection /* del an async conn */ +#else + NULL, /* add an async conn */ + NULL /* del an async conn */ +#endif + } +}; + +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 (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 = 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 (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 = ¬ify_event; + notify_conn.log = log; + + ee.events = EPOLLIN|EPOLLET; + ee.data.ptr = ¬ify_conn; + + if (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 + + +static void +ngx_epoll_done(ngx_cycle_t *cycle) +{ + if (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; + } + + 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 (epoll_ctl(ep, op, c->fd, &ee) == -1) { + ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno, + "socket add event 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 (epoll_ctl(ep, op, c->fd, &ee) == -1) { + ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno, + "socket del event 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 (epoll_ctl(ep, EPOLL_CTL_ADD, c->fd, &ee) == -1) { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, + "socket add_conn 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 (epoll_ctl(ep, op, c->fd, &ee) == -1) { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, + "socket del conn epoll_ctl(%d, %d) failed", op, c->fd); + return NGX_ERROR; + } + + c->read->active = 0; + c->write->active = 0; + + return NGX_OK; +} + +#if (NGX_SSL) +static ngx_int_t +ngx_epoll_add_async_connection(ngx_connection_t *c) +{ + struct epoll_event ee; + + ee.events = EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP; + ee.data.ptr = (void *) ((uintptr_t) c | (c->async->async << 1) | c->async->instance); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "epoll add async connection: fd:%d ev:%08XD", c->async_fd, ee.events); + if (epoll_ctl(ep, EPOLL_CTL_ADD, c->async_fd, &ee) == -1) { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, + "async add conn epoll_ctl(EPOLL_CTL_ADD, %d) failed", c->async_fd); + return NGX_ERROR; + } + + c->async->active = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_epoll_del_async_connection(ngx_connection_t *c, ngx_uint_t flags) +{ + int op; + struct epoll_event ee; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "epoll del async connection: fd:%d", c->async_fd); + + op = EPOLL_CTL_DEL; + ee.events = 0; + ee.data.ptr = NULL; + if (epoll_ctl(ep, op, c->async_fd, &ee) == -1) { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, + "async del conn epoll_ctl(%d, %d) failed", op, c->async_fd); + c->async_fd = -1; + return NGX_ERROR; + } + c->async_fd = -1; + c->async->active = 0; + + return NGX_OK; +} +#endif + +#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; +#if (NGX_SSL) + ngx_int_t async; + ngx_event_t *aev; +#endif + + /* NGX_TIMER_INFINITE == INFTIM */ + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "epoll timer: %M", timer); + + events = 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; +#if (NGX_SSL) + async = ((uintptr_t) c & 2) >> 1; + c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~3); +#else + c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1); +#endif + + 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 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 & (EPOLLERR|EPOLLHUP)) + && (revents & (EPOLLIN|EPOLLOUT)) == 0) + { + /* + * if the error events were returned without EPOLLIN or EPOLLOUT, + * then add these flags to handle the events at least in one + * active handler + */ + + revents |= EPOLLIN|EPOLLOUT; + } + +#if (NGX_SSL) + if ((revents & EPOLLIN) && rev->active && !async) { +#else + if ((revents & EPOLLIN) && rev->active) { +#endif + +#if (NGX_HAVE_EPOLLRDHUP) + if (revents & EPOLLRDHUP) { + rev->pending_eof = 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 (NGX_SSL) + if ((revents & EPOLLOUT) && wev->active && !async) { +#else + if ((revents & EPOLLOUT) && wev->active) { +#endif + + 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); + } + } + +#if (NGX_SSL) + aev = c->async; + + if ((revents & EPOLLIN) && aev->active && async) { + + if (c->async_fd == -1 || aev->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; + } + + aev->ready = 1; + + if (flags & NGX_POST_EVENTS) { + ngx_post_event(aev, &ngx_posted_events); + + } else { + aev->handler(aev); + } + } +#endif + } + + 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/src/event/modules/ngx_eventport_module.c b/src/event/modules/ngx_eventport_module.c new file mode 100644 index 0000000..1871308 --- /dev/null +++ b/src/event/modules/ngx_eventport_module.c @@ -0,0 +1,653 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#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; +#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 +}; + + +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 */ + NULL, /* add an async conn */ + NULL /* del an async conn */ + } + +}; + +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, ¬ify_event) != 0) { + ngx_log_error(NGX_LOG_ALERT, notify_event.log, ngx_errno, + "port_send() failed"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +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)) + && (revents & (POLLIN|POLLOUT)) == 0) + { + /* + * if the error events were returned without POLLIN or POLLOUT, + * then add these flags 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/src/event/modules/ngx_iocp_module.c b/src/event/modules/ngx_iocp_module.c new file mode 100644 index 0000000..8f163e3 --- /dev/null +++ b/src/event/modules/ngx_iocp_module.c @@ -0,0 +1,378 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +static ngx_int_t ngx_iocp_init(ngx_cycle_t *cycle, ngx_msec_t timer); +static ngx_thread_value_t __stdcall ngx_iocp_timer(void *data); +static void ngx_iocp_done(ngx_cycle_t *cycle); +static ngx_int_t ngx_iocp_add_event(ngx_event_t *ev, ngx_int_t event, + ngx_uint_t key); +static ngx_int_t ngx_iocp_del_connection(ngx_connection_t *c, ngx_uint_t flags); +static ngx_int_t ngx_iocp_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, + ngx_uint_t flags); +static void *ngx_iocp_create_conf(ngx_cycle_t *cycle); +static char *ngx_iocp_init_conf(ngx_cycle_t *cycle, void *conf); + + +static ngx_str_t iocp_name = ngx_string("iocp"); + +static ngx_command_t ngx_iocp_commands[] = { + + { ngx_string("iocp_threads"), + NGX_EVENT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + 0, + offsetof(ngx_iocp_conf_t, threads), + NULL }, + + { ngx_string("post_acceptex"), + NGX_EVENT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + 0, + offsetof(ngx_iocp_conf_t, post_acceptex), + NULL }, + + { ngx_string("acceptex_read"), + NGX_EVENT_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + 0, + offsetof(ngx_iocp_conf_t, acceptex_read), + NULL }, + + ngx_null_command +}; + + +ngx_event_module_t ngx_iocp_module_ctx = { + &iocp_name, + ngx_iocp_create_conf, /* create configuration */ + ngx_iocp_init_conf, /* init configuration */ + + { + ngx_iocp_add_event, /* add an event */ + NULL, /* delete an event */ + NULL, /* enable an event */ + NULL, /* disable an event */ + NULL, /* add an connection */ + ngx_iocp_del_connection, /* delete an connection */ + NULL, /* trigger a notify */ + ngx_iocp_process_events, /* process the events */ + ngx_iocp_init, /* init the events */ + ngx_iocp_done /* done the events */ + } + +}; + +ngx_module_t ngx_iocp_module = { + NGX_MODULE_V1, + &ngx_iocp_module_ctx, /* module context */ + ngx_iocp_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 +}; + + +ngx_os_io_t ngx_iocp_io = { + ngx_overlapped_wsarecv, + NULL, + ngx_udp_overlapped_wsarecv, + NULL, + ngx_overlapped_wsasend_chain, + 0 +}; + + +static HANDLE iocp; +static ngx_tid_t timer_thread; +static ngx_msec_t msec; + + +static ngx_int_t +ngx_iocp_init(ngx_cycle_t *cycle, ngx_msec_t timer) +{ + ngx_iocp_conf_t *cf; + + cf = ngx_event_get_conf(cycle->conf_ctx, ngx_iocp_module); + + if (iocp == NULL) { + iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, + cf->threads); + } + + if (iocp == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "CreateIoCompletionPort() failed"); + return NGX_ERROR; + } + + ngx_io = ngx_iocp_io; + + ngx_event_actions = ngx_iocp_module_ctx.actions; + + ngx_event_flags = NGX_USE_IOCP_EVENT; + + if (timer == 0) { + return NGX_OK; + } + + /* + * The waitable timer could not be used, because + * GetQueuedCompletionStatus() does not set a thread to alertable state + */ + + if (timer_thread == NULL) { + + msec = timer; + + if (ngx_create_thread(&timer_thread, ngx_iocp_timer, &msec, cycle->log) + != 0) + { + return NGX_ERROR; + } + } + + ngx_event_flags |= NGX_USE_TIMER_EVENT; + + return NGX_OK; +} + + +static ngx_thread_value_t __stdcall +ngx_iocp_timer(void *data) +{ + ngx_msec_t timer = *(ngx_msec_t *) data; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, + "THREAD %p %p", &msec, data); + + for ( ;; ) { + Sleep(timer); + + ngx_time_update(); +#if 1 + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, "timer"); +#endif + } + +#if defined(__WATCOMC__) || defined(__GNUC__) + return 0; +#endif +} + + +static void +ngx_iocp_done(ngx_cycle_t *cycle) +{ + if (CloseHandle(iocp) == -1) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "iocp CloseHandle() failed"); + } + + iocp = NULL; +} + + +static ngx_int_t +ngx_iocp_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t key) +{ + ngx_connection_t *c; + + c = (ngx_connection_t *) ev->data; + + c->read->active = 1; + c->write->active = 1; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0, + "iocp add: fd:%d k:%ui ov:%p", c->fd, key, &ev->ovlp); + + if (CreateIoCompletionPort((HANDLE) c->fd, iocp, key, 0) == NULL) { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, + "CreateIoCompletionPort() failed"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_iocp_del_connection(ngx_connection_t *c, ngx_uint_t flags) +{ +#if 0 + if (flags & NGX_CLOSE_EVENT) { + return NGX_OK; + } + + if (CancelIo((HANDLE) c->fd) == 0) { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, "CancelIo() failed"); + return NGX_ERROR; + } +#endif + + return NGX_OK; +} + + +static +ngx_int_t ngx_iocp_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, + ngx_uint_t flags) +{ + int rc; + u_int key; + u_long bytes; + ngx_err_t err; + ngx_msec_t delta; + ngx_event_t *ev; + ngx_event_ovlp_t *ovlp; + + if (timer == NGX_TIMER_INFINITE) { + timer = INFINITE; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "iocp timer: %M", timer); + + rc = GetQueuedCompletionStatus(iocp, &bytes, (PULONG_PTR) &key, + (LPOVERLAPPED *) &ovlp, (u_long) timer); + + if (rc == 0) { + err = ngx_errno; + } else { + err = 0; + } + + delta = ngx_current_msec; + + if (flags & NGX_UPDATE_TIME) { + ngx_time_update(); + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "iocp: %d b:%d k:%d ov:%p", rc, bytes, key, ovlp); + + if (timer != INFINITE) { + delta = ngx_current_msec - delta; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "iocp timer: %M, delta: %M", timer, delta); + } + + if (err) { + if (ovlp == NULL) { + if (err != WAIT_TIMEOUT) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, err, + "GetQueuedCompletionStatus() failed"); + + return NGX_ERROR; + } + + return NGX_OK; + } + + ovlp->error = err; + } + + if (ovlp == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "GetQueuedCompletionStatus() returned no operation"); + return NGX_ERROR; + } + + + ev = ovlp->event; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, err, "iocp event:%p", ev); + + + if (err == ERROR_NETNAME_DELETED /* the socket was closed */ + || err == ERROR_OPERATION_ABORTED /* the operation was canceled */) + { + + /* + * the WSA_OPERATION_ABORTED completion notification + * for a file descriptor that was closed + */ + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, err, + "iocp: aborted event %p", ev); + + return NGX_OK; + } + + if (err) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, err, + "GetQueuedCompletionStatus() returned operation error"); + } + + switch (key) { + + case NGX_IOCP_ACCEPT: + if (bytes) { + ev->ready = 1; + } + break; + + case NGX_IOCP_IO: + ev->complete = 1; + ev->ready = 1; + break; + + case NGX_IOCP_CONNECT: + ev->ready = 1; + } + + ev->available = bytes; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "iocp event handler: %p", ev->handler); + + ev->handler(ev); + + return NGX_OK; +} + + +static void * +ngx_iocp_create_conf(ngx_cycle_t *cycle) +{ + ngx_iocp_conf_t *cf; + + cf = ngx_palloc(cycle->pool, sizeof(ngx_iocp_conf_t)); + if (cf == NULL) { + return NGX_CONF_ERROR; + } + + cf->threads = NGX_CONF_UNSET; + cf->post_acceptex = NGX_CONF_UNSET; + cf->acceptex_read = NGX_CONF_UNSET; + + return cf; +} + + +static char * +ngx_iocp_init_conf(ngx_cycle_t *cycle, void *conf) +{ + ngx_iocp_conf_t *cf = conf; + + ngx_conf_init_value(cf->threads, 0); + ngx_conf_init_value(cf->post_acceptex, 10); + ngx_conf_init_value(cf->acceptex_read, 1); + + return NGX_CONF_OK; +} diff --git a/src/event/modules/ngx_iocp_module.h b/src/event/modules/ngx_iocp_module.h new file mode 100644 index 0000000..dc73983 --- /dev/null +++ b/src/event/modules/ngx_iocp_module.h @@ -0,0 +1,22 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_IOCP_MODULE_H_INCLUDED_ +#define _NGX_IOCP_MODULE_H_INCLUDED_ + + +typedef struct { + int threads; + int post_acceptex; + int acceptex_read; +} ngx_iocp_conf_t; + + +extern ngx_module_t ngx_iocp_module; + + +#endif /* _NGX_IOCP_MODULE_H_INCLUDED_ */ diff --git a/src/event/modules/ngx_kqueue_module.c b/src/event/modules/ngx_kqueue_module.c new file mode 100644 index 0000000..cec078b --- /dev/null +++ b/src/event/modules/ngx_kqueue_module.c @@ -0,0 +1,724 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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 +}; + + +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 */ + NULL, /* add an async conn */ + NULL /* del an async conn */ + } + +}; + +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, ¬ify_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) ¬ify_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, ¬ify_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/src/event/modules/ngx_poll_module.c b/src/event/modules/ngx_poll_module.c new file mode 100644 index 0000000..21e275f --- /dev/null +++ b/src/event/modules/ngx_poll_module.c @@ -0,0 +1,419 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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"); + +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 */ + NULL, /* add an async conn */ + NULL /* del an async conn */ + } + +}; + +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)) + && (revents & (POLLIN|POLLOUT)) == 0) + { + /* + * if the error events were returned without POLLIN or POLLOUT, + * then add these flags 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/src/event/modules/ngx_select_module.c b/src/event/modules/ngx_select_module.c new file mode 100644 index 0000000..a4cf3f0 --- /dev/null +++ b/src/event/modules/ngx_select_module.c @@ -0,0 +1,425 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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"); + +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 */ + NULL, /* add an async conn */ + NULL /* del an async conn */ + } + +}; + +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 (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 (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/src/event/modules/ngx_win32_select_module.c b/src/event/modules/ngx_win32_select_module.c new file mode 100644 index 0000000..ce77341 --- /dev/null +++ b/src/event/modules/ngx_win32_select_module.c @@ -0,0 +1,400 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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"); + +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 */ + NULL, /* add an async conn */ + NULL /* del an async conn */ + } + +}; + +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 (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 (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/src/event/ngx_event.c b/src/event/ngx_event.c new file mode 100644 index 0000000..b8e1643 --- /dev/null +++ b/src/event/ngx_event.c @@ -0,0 +1,1289 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#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) + +ngx_atomic_t ngx_stat_accepted0; +ngx_atomic_t *ngx_stat_accepted = &ngx_stat_accepted0; +ngx_atomic_t ngx_stat_handled0; +ngx_atomic_t *ngx_stat_handled = &ngx_stat_handled0; +ngx_atomic_t ngx_stat_requests0; +ngx_atomic_t *ngx_stat_requests = &ngx_stat_requests0; +ngx_atomic_t ngx_stat_active0; +ngx_atomic_t *ngx_stat_active = &ngx_stat_active0; +ngx_atomic_t ngx_stat_reading0; +ngx_atomic_t *ngx_stat_reading = &ngx_stat_reading0; +ngx_atomic_t ngx_stat_writing0; +ngx_atomic_t *ngx_stat_writing = &ngx_stat_writing0; +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 +}; + + +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, 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; + shm.name.len = sizeof("nginx_shared_zone") - 1; + shm.name.data = (u_char *) "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; +#if (NGX_SSL) + ngx_event_t *aev; +#endif + 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; + } + +#if (NGX_SSL) + cycle->async_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, + cycle->log); + if (cycle->async_events == NULL) { + return NGX_ERROR; + } + + aev = cycle->async_events; + for (i = 0; i < cycle->connection_n; i++) { + aev[i].closed = 1; + aev[i].instance = 1; + } +#endif + + 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; +#if (NGX_SSL) + c[i].async = &cycle->async_events[i]; + c[i].async_fd = (ngx_socket_t) -1; +#endif + + 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_use_accept_mutex +#if (NGX_HAVE_REUSEPORT) + && !ls[i].reuseport +#endif + ) + { + continue; + } + + 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 (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, 1); + ngx_conf_init_msec_value(ecf->accept_mutex_delay, 500); + + return NGX_CONF_OK; +} diff --git a/src/event/ngx_event.h b/src/event/ngx_event.h new file mode 100644 index 0000000..90e78cd --- /dev/null +++ b/src/event/ngx_event.h @@ -0,0 +1,542 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_H_INCLUDED_ +#define _NGX_EVENT_H_INCLUDED_ + + +#include +#include + + +#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; + +#if (NGX_SSL) + unsigned async:1; +#endif + + 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_WIN32) + /* setsockopt(SO_UPDATE_ACCEPT_CONTEXT) was successful */ + unsigned accept_context_updated:1; +#endif + +#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 + * + * 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_SSL) + ngx_event_handler_pt saved_handler; +#endif + + +#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; + +#if (NGX_HAVE_AIO_SENDFILE) + ssize_t (*preload_handler)(ngx_buf_t *file); +#endif + + ngx_fd_t fd; + +#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_int_t (*add_async_conn)(ngx_connection_t *c); + ngx_int_t (*del_async_conn)(ngx_connection_t *c, ngx_uint_t flags); +} ngx_event_actions_t; + + +extern ngx_event_actions_t ngx_event_actions; + + +/* + * 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 + + +#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 + + +#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_add_async_conn ngx_event_actions.add_async_conn +#define ngx_del_async_conn ngx_event_actions.del_async_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_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 +#include + +#if (NGX_WIN32) +#include +#endif + + +#endif /* _NGX_EVENT_H_INCLUDED_ */ diff --git a/src/event/ngx_event_accept.c b/src/event/ngx_event_accept.c new file mode 100644 index 0000000..b478b10 --- /dev/null +++ b/src/event/ngx_event_accept.c @@ -0,0 +1,846 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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_listening_t *ls; + ngx_connection_t *c, *lc; + ngx_event_conf_t *ecf; + u_char sa[NGX_SOCKADDRLEN]; +#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 = NGX_SOCKADDRLEN; + +#if (NGX_HAVE_ACCEPT4) + if (use_accept4) { + s = accept4(lc->fd, (struct sockaddr *) sa, &socklen, + SOCK_NONBLOCK); + } else { + s = accept(lc->fd, (struct sockaddr *) sa, &socklen); + } +#else + s = accept(lc->fd, (struct sockaddr *) sa, &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; + } + + 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; + + c->unexpected_eof = 1; + +#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) + rev->available = 1; +#endif + } + + rev->log = log; + wev->log = log; +#if (NGX_SSL) + c->async->log = log; +#endif + + /* + * 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_listening_t *ls; + ngx_event_conf_t *ecf; + ngx_connection_t *c, *lc; + u_char sa[NGX_SOCKADDRLEN]; + 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(sa); + 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 = 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 (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->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_SSL) + if (c->asynch && ngx_del_async_conn) { + if (c->num_async_fds) { + ngx_del_async_conn(c, NGX_DISABLE_EVENT); + c->num_async_fds--; + } + } +#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/src/event/ngx_event_acceptex.c b/src/event/ngx_event_acceptex.c new file mode 100644 index 0000000..1dbfd6c --- /dev/null +++ b/src/event/ngx_event_acceptex.c @@ -0,0 +1,229 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +static void ngx_close_posted_connection(ngx_connection_t *c); + + +void +ngx_event_acceptex(ngx_event_t *rev) +{ + ngx_listening_t *ls; + ngx_connection_t *c; + + c = rev->data; + ls = c->listening; + + c->log->handler = ngx_accept_log_error; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "AcceptEx: %d", c->fd); + + if (rev->ovlp.error) { + ngx_log_error(NGX_LOG_CRIT, c->log, rev->ovlp.error, + "AcceptEx() %V failed", &ls->addr_text); + return; + } + + /* SO_UPDATE_ACCEPT_CONTEXT is required for shutdown() to work */ + + if (setsockopt(c->fd, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, + (char *) &ls->fd, sizeof(ngx_socket_t)) + == -1) + { + ngx_log_error(NGX_LOG_CRIT, c->log, ngx_socket_errno, + "setsockopt(SO_UPDATE_ACCEPT_CONTEXT) failed for %V", + &c->addr_text); + } else { + c->accept_context_updated = 1; + } + + ngx_getacceptexsockaddrs(c->buffer->pos, + ls->post_accept_buffer_size, + ls->socklen + 16, + ls->socklen + 16, + &c->local_sockaddr, &c->local_socklen, + &c->sockaddr, &c->socklen); + + if (ls->post_accept_buffer_size) { + c->buffer->last += rev->available; + c->buffer->end = c->buffer->start + ls->post_accept_buffer_size; + + } else { + c->buffer = NULL; + } + + if (ls->addr_ntop) { + c->addr_text.data = ngx_pnalloc(c->pool, ls->addr_text_max_len); + if (c->addr_text.data == NULL) { + /* TODO: close socket */ + 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) { + /* TODO: close socket */ + return; + } + } + + ngx_event_post_acceptex(ls, 1); + + c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + + ls->handler(c); + + return; + +} + + +ngx_int_t +ngx_event_post_acceptex(ngx_listening_t *ls, ngx_uint_t n) +{ + u_long rcvd; + ngx_err_t err; + ngx_log_t *log; + ngx_uint_t i; + ngx_event_t *rev, *wev; + ngx_socket_t s; + ngx_connection_t *c; + + for (i = 0; i < n; i++) { + + /* TODO: look up reused sockets */ + + s = ngx_socket(ls->sockaddr->sa_family, ls->type, 0); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, &ls->log, 0, + ngx_socket_n " s:%d", s); + + if (s == (ngx_socket_t) -1) { + ngx_log_error(NGX_LOG_ALERT, &ls->log, ngx_socket_errno, + ngx_socket_n " failed"); + + return NGX_ERROR; + } + + c = ngx_get_connection(s, &ls->log); + + if (c == NULL) { + return NGX_ERROR; + } + + c->pool = ngx_create_pool(ls->pool_size, &ls->log); + if (c->pool == NULL) { + ngx_close_posted_connection(c); + return NGX_ERROR; + } + + log = ngx_palloc(c->pool, sizeof(ngx_log_t)); + if (log == NULL) { + ngx_close_posted_connection(c); + return NGX_ERROR; + } + + c->buffer = ngx_create_temp_buf(c->pool, ls->post_accept_buffer_size + + 2 * (ls->socklen + 16)); + if (c->buffer == NULL) { + ngx_close_posted_connection(c); + return NGX_ERROR; + } + + c->local_sockaddr = ngx_palloc(c->pool, ls->socklen); + if (c->local_sockaddr == NULL) { + ngx_close_posted_connection(c); + return NGX_ERROR; + } + + c->sockaddr = ngx_palloc(c->pool, ls->socklen); + if (c->sockaddr == NULL) { + ngx_close_posted_connection(c); + return NGX_ERROR; + } + + *log = ls->log; + c->log = log; + + c->recv = ngx_recv; + c->send = ngx_send; + c->recv_chain = ngx_recv_chain; + c->send_chain = ngx_send_chain; + + c->unexpected_eof = 1; + + c->listening = ls; + + rev = c->read; + wev = c->write; + + rev->ovlp.event = rev; + wev->ovlp.event = wev; + rev->handler = ngx_event_acceptex; + + rev->ready = 1; + wev->ready = 1; + + rev->log = c->log; + wev->log = c->log; + + if (ngx_add_event(rev, 0, NGX_IOCP_IO) == NGX_ERROR) { + ngx_close_posted_connection(c); + return NGX_ERROR; + } + + if (ngx_acceptex(ls->fd, s, c->buffer->pos, ls->post_accept_buffer_size, + ls->socklen + 16, ls->socklen + 16, + &rcvd, (LPOVERLAPPED) &rev->ovlp) + == 0) + { + err = ngx_socket_errno; + if (err != WSA_IO_PENDING) { + ngx_log_error(NGX_LOG_ALERT, &ls->log, err, + "AcceptEx() %V failed", &ls->addr_text); + + ngx_close_posted_connection(c); + return NGX_ERROR; + } + } + } + + return NGX_OK; +} + + +static void +ngx_close_posted_connection(ngx_connection_t *c) +{ + ngx_socket_t fd; + + ngx_free_connection(c); + + fd = c->fd; + c->fd = (ngx_socket_t) -1; + + if (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); + } +} + + +u_char * +ngx_acceptex_log_error(ngx_log_t *log, u_char *buf, size_t len) +{ + return ngx_snprintf(buf, len, " while posting AcceptEx() on %V", log->data); +} diff --git a/src/event/ngx_event_connect.c b/src/event/ngx_event_connect.c new file mode 100644 index 0000000..8aca862 --- /dev/null +++ b/src/event/ngx_event_connect.c @@ -0,0 +1,256 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +ngx_int_t +ngx_event_connect_peer(ngx_peer_connection_t *pc) +{ + int rc, type; + 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 (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 (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->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 = 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; +} + + +ngx_int_t +ngx_event_get_peer(ngx_peer_connection_t *pc, void *data) +{ + return NGX_OK; +} diff --git a/src/event/ngx_event_connect.h b/src/event/ngx_event_connect.h new file mode 100644 index 0000000..1bacf82 --- /dev/null +++ b/src/event/ngx_event_connect.h @@ -0,0 +1,74 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_CONNECT_H_INCLUDED_ +#define _NGX_EVENT_CONNECT_H_INCLUDED_ + + +#include +#include +#include + + +#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); +#if (NGX_SSL) + +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); +#endif + + +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; + void *data; + +#if (NGX_SSL) + 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; + + /* ngx_connection_log_error_e */ + unsigned log_error:2; +}; + + +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/src/event/ngx_event_connectex.c b/src/event/ngx_event_connectex.c new file mode 100644 index 0000000..59ada74 --- /dev/null +++ b/src/event/ngx_event_connectex.c @@ -0,0 +1,206 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#define NGX_MAX_PENDING_CONN 10 + + +static CRITICAL_SECTION connect_lock; +static int nconnects; +static ngx_connection_t pending_connects[NGX_MAX_PENDING_CONN]; + +static HANDLE pending_connect_event; + +__declspec(thread) int nevents = 0; +__declspec(thread) WSAEVENT events[WSA_MAXIMUM_WAIT_EVENTS + 1]; +__declspec(thread) ngx_connection_t *conn[WSA_MAXIMUM_WAIT_EVENTS + 1]; + + + +int ngx_iocp_wait_connect(ngx_connection_t *c) +{ + for ( ;; ) { + EnterCriticalSection(&connect_lock); + + if (nconnects < NGX_MAX_PENDING_CONN) { + pending_connects[--nconnects] = c; + LeaveCriticalSection(&connect_lock); + + if (SetEvent(pending_connect_event) == 0) { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, + "SetEvent() failed"); + return NGX_ERROR; + + break; + } + + LeaveCriticalSection(&connect_lock); + ngx_log_error(NGX_LOG_NOTICE, c->log, 0, + "max number of pending connect()s is %d", + NGX_MAX_PENDING_CONN); + msleep(100); + } + + if (!started) { + if (ngx_iocp_new_thread(1) == NGX_ERROR) { + return NGX_ERROR; + } + started = 1; + } + + return NGX_OK; +} + + +int ngx_iocp_new_thread(int main) +{ + u_int id; + + if (main) { + pending_connect_event = CreateEvent(NULL, 0, 1, NULL); + if (pending_connect_event == INVALID_HANDLE_VALUE) { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, + "CreateThread() failed"); + return NGX_ERROR; + } + } + + if (CreateThread(NULL, 0, ngx_iocp_wait_events, main, 0, &id) + == INVALID_HANDLE_VALUE) + { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, + "CreateThread() failed"); + return NGX_ERROR; + } + + SetEvent(event) { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, + "SetEvent() failed"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +int ngx_iocp_new_connect() +{ + EnterCriticalSection(&connect_lock); + c = pending_connects[--nconnects]; + LeaveCriticalSection(&connect_lock); + + conn[nevents] = c; + + events[nevents] = WSACreateEvent(); + if (events[nevents] == INVALID_HANDLE_VALUE) { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno, + "WSACreateEvent() failed"); + return NGX_ERROR; + } + + if (WSAEventSelect(c->fd, events[nevents], FD_CONNECT) == -1) + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno, + "WSAEventSelect() failed"); + return NGX_ERROR; + } + + nevents++; + + return NGX_OK; +} + + +void ngx_iocp_wait_events(int main) +{ + WSANETWORKEVENTS ne; + + nevents = 1; + events[0] = pending_connect_event; + conn[0] = NULL; + + for ( ;; ) { + offset = (nevents == WSA_MAXIMUM_WAIT_EVENTS + 1) ? 1: 0; + timeout = (nevents == 1 && !first) ? 60000: INFINITE; + + n = WSAWaitForMultipleEvents(nevents - offset, events[offset], + 0, timeout, 0); + if (n == WAIT_FAILED) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno, + "WSAWaitForMultipleEvents() failed"); + continue; + } + + if (n == WAIT_TIMEOUT) { + if (nevents == 2 && !main) { + ExitThread(0); + } + + ngx_log_error(NGX_LOG_ALERT, log, 0, + "WSAWaitForMultipleEvents() " + "returned unexpected WAIT_TIMEOUT"); + continue; + } + + n -= WSA_WAIT_EVENT_0; + + if (events[n] == NULL) { + + /* the pending_connect_event */ + + if (nevents == WSA_MAXIMUM_WAIT_EVENTS) { + ngx_iocp_new_thread(0); + } else { + ngx_iocp_new_connect(); + } + + continue; + } + + if (WSAEnumNetworkEvents(c[n].fd, events[n], &ne) == -1) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno, + "WSAEnumNetworkEvents() failed"); + continue; + } + + if (ne.lNetworkEvents & FD_CONNECT) { + conn[n].write->ovlp.error = ne.iErrorCode[FD_CONNECT_BIT]; + + if (PostQueuedCompletionStatus(iocp, 0, NGX_IOCP_CONNECT, + &conn[n].write->ovlp) == 0) + { + ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno, + "PostQueuedCompletionStatus() failed"); + continue; + } + + if (n < nevents) { + conn[n] = conn[nevents]; + events[n] = events[nevents]; + } + + nevents--; + continue; + } + + if (ne.lNetworkEvents & FD_ACCEPT) { + + /* CHECK ERROR ??? */ + + ngx_event_post_acceptex(conn[n].listening, 1); + continue; + } + + ngx_log_error(NGX_LOG_ALERT, c[n].log, 0, + "WSAWaitForMultipleEvents() " + "returned unexpected network event %ul", + ne.lNetworkEvents); + } +} diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c new file mode 100644 index 0000000..2e54006 --- /dev/null +++ b/src/event/ngx_event_openssl.c @@ -0,0 +1,3945 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#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 + +#if OPENSSL_VERSION_NUMBER < 0x10002002L +static ngx_int_t ngx_ssl_check_name(ngx_str_t *name, ASN1_STRING *str); +#endif + +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 void ngx_ssl_handshake_async_handler(ngx_event_t * aev); +static void ngx_ssl_read_async_handler(ngx_event_t * aev); +static void ngx_ssl_write_async_handler(ngx_event_t * aev); +static void ngx_ssl_shutdown_async_handler(ngx_event_t *aev); + +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_stapling_index; + + +static void +ngx_ssl_empty_handler(ngx_event_t *aev) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, aev->log, 0, "ssl empty handler"); + + return; +} + + +ngx_int_t +ngx_ssl_init(ngx_log_t *log) +{ +#if OPENSSL_VERSION_NUMBER >= 0x10100003L + + OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL); + +#else + +#ifndef OPENSSL_IS_BORINGSSL + OPENSSL_config(NULL); +#endif + + 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_stapling_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, + NULL); + if (ngx_ssl_stapling_index == -1) { + ngx_ssl_error(NGX_LOG_ALERT, log, 0, + "SSL_CTX_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; + } + + 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); + +#ifdef SSL_CTRL_CLEAR_OPTIONS + /* 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_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 + + if(ssl->asynch) { + SSL_CTX_set_mode(ssl->ctx, SSL_MODE_ASYNC); + } + + 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_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 (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; + } + + X509_free(x509); + + /* 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; + } + + 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; + } + } + + 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_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) { + 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; + + /* + * -----BEGIN DH PARAMETERS----- + * MIGHAoGBALu8LcrYRnSQfEP89YDpz9vZWKP1aLQtSwju1OsPs1BMbAMCducQgAxc + * y7qokiYUxb7spWWl/fHSh6K8BJvmd4Bg6RqSp1fjBI9osHb302zI8pul34HcLKcl + * 7OZicMyaUDXYzs7vnqAnSmOrHlj6/UmI0PZdFGdX2gcd8EXP4WubAgEC + * -----END DH PARAMETERS----- + */ + + static unsigned char dh1024_p[] = { + 0xBB, 0xBC, 0x2D, 0xCA, 0xD8, 0x46, 0x74, 0x90, 0x7C, 0x43, 0xFC, 0xF5, + 0x80, 0xE9, 0xCF, 0xDB, 0xD9, 0x58, 0xA3, 0xF5, 0x68, 0xB4, 0x2D, 0x4B, + 0x08, 0xEE, 0xD4, 0xEB, 0x0F, 0xB3, 0x50, 0x4C, 0x6C, 0x03, 0x02, 0x76, + 0xE7, 0x10, 0x80, 0x0C, 0x5C, 0xCB, 0xBA, 0xA8, 0x92, 0x26, 0x14, 0xC5, + 0xBE, 0xEC, 0xA5, 0x65, 0xA5, 0xFD, 0xF1, 0xD2, 0x87, 0xA2, 0xBC, 0x04, + 0x9B, 0xE6, 0x77, 0x80, 0x60, 0xE9, 0x1A, 0x92, 0xA7, 0x57, 0xE3, 0x04, + 0x8F, 0x68, 0xB0, 0x76, 0xF7, 0xD3, 0x6C, 0xC8, 0xF2, 0x9B, 0xA5, 0xDF, + 0x81, 0xDC, 0x2C, 0xA7, 0x25, 0xEC, 0xE6, 0x62, 0x70, 0xCC, 0x9A, 0x50, + 0x35, 0xD8, 0xCE, 0xCE, 0xEF, 0x9E, 0xA0, 0x27, 0x4A, 0x63, 0xAB, 0x1E, + 0x58, 0xFA, 0xFD, 0x49, 0x88, 0xD0, 0xF6, 0x5D, 0x14, 0x67, 0x57, 0xDA, + 0x07, 0x1D, 0xF0, 0x45, 0xCF, 0xE1, 0x6B, 0x9B + }; + + static unsigned char dh1024_g[] = { 0x02 }; + + + if (file->len == 0) { + + dh = DH_new(); + if (dh == NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "DH_new() failed"); + return NGX_ERROR; + } + +#if OPENSSL_VERSION_NUMBER < 0x10100005L + + dh->p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL); + dh->g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), NULL); + + if (dh->p == NULL || dh->g == NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "BN_bin2bn() failed"); + DH_free(dh); + return NGX_ERROR; + } + +#else + { + BIGNUM *p, *g; + + p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL); + g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), NULL); + + if (p == NULL || g == NULL || !DH_set0_pqg(dh, p, NULL, g)) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "BN_bin2bn() failed"); + DH_free(dh); + BN_free(p); + BN_free(g); + return NGX_ERROR; + } + } +#endif + + SSL_CTX_set_tmp_dh(ssl->ctx, dh); + + DH_free(dh); + + 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 + int nid; + EC_KEY *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. + */ + + nid = OBJ_sn2nid((const char *) name->data); + if (nid == 0) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "Unknown curve name \"%s\"", name->data); + return NGX_ERROR; + } + + ecdh = EC_KEY_new_by_curve_name(nid); + if (ecdh == NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "Unable to create curve \"%s\"", name->data); + 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 + + 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; + c->asynch = ssl->asynch; + + 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_async_process_fds(ngx_connection_t *c) +{ + OSSL_ASYNC_FD *add_fds = NULL; + OSSL_ASYNC_FD *del_fds = NULL; + size_t num_add_fds = 0; + size_t num_del_fds = 0; + unsigned loop = 0; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_ssl_async_process_fds called"); + + if (!ngx_del_async_conn || !ngx_add_async_conn) { + ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, + "Async notifications not supported"); + return 0; + } + + SSL_get_changed_async_fds(c->ssl->connection, NULL, &num_add_fds, + NULL, &num_del_fds); + + if (num_add_fds) { + add_fds = ngx_alloc(num_add_fds * sizeof(OSSL_ASYNC_FD), c->log); + if (add_fds == NULL) { + ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, + "Memory Allocation Error"); + return 0; + } + } + + if (num_del_fds) { + del_fds = ngx_alloc(num_del_fds * sizeof(OSSL_ASYNC_FD), c->log); + if (del_fds == NULL) { + ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, + "Memory Allocation Error"); + if (add_fds) + ngx_free(add_fds); + return 0; + } + } + + SSL_get_changed_async_fds(c->ssl->connection, add_fds, &num_add_fds, + del_fds, &num_del_fds); + + if (num_del_fds) { + for (loop = 0; loop < num_del_fds; loop++) { + c->async_fd = del_fds[loop]; + if (c->num_async_fds) { + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "%s: deleting fd = %d", __func__, c->async_fd); + ngx_del_async_conn(c, NGX_DISABLE_EVENT); + c->num_async_fds--; + } + } + } + if (num_add_fds) { + for (loop = 0; loop < num_add_fds; loop++) { + if (c->num_async_fds == 0) { + c->num_async_fds++; + c->async_fd = add_fds[loop]; + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "%s: adding fd = %d", __func__, c->async_fd); + ngx_add_async_conn(c); + } + } + } + + if (add_fds) + ngx_free(add_fds); + if (del_fds) + ngx_free(del_fds); + + return 1; +} + +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(c->asynch && ngx_ssl_async_process_fds(c) == 0) { + return NGX_ERROR; + } + + 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) { + 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) { + if (c->asynch && ngx_ssl_async_process_fds(c) == 0) { + return NGX_ERROR; + } + + 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) { + if (c->asynch && ngx_ssl_async_process_fds(c) == 0) { + return NGX_ERROR; + } + 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; + } + + if (c->asynch && sslerr == SSL_ERROR_WANT_ASYNC) + { + c->async->handler = ngx_ssl_handshake_async_handler; + c->read->saved_handler = c->read->handler; + c->read->handler = ngx_ssl_empty_handler; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL ASYNC WANT recieved: \"%s\"", __func__); + + if (ngx_ssl_async_process_fds(c) == 0) { + 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_async_handler(ngx_event_t *aev) +{ + ngx_connection_t *c; + + c = aev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL handshake async handler"); + + aev->ready = 0; + aev->handler = ngx_ssl_empty_handler; + c->read->handler = c->read->saved_handler; + + if (ngx_ssl_handshake(c) == NGX_AGAIN) { + return; + } + + c->ssl->handler(c); +} + + +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; + } + + /* + * empty the handler of async event to avoid + * going back to previous ssl handshake state + */ + c->async->handler = ngx_ssl_empty_handler; + 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->asynch && ngx_ssl_async_process_fds(c) == 0) { + return NGX_ERROR; + } + + 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) { + if (c->asynch && ngx_ssl_async_process_fds(c) == 0) { + return NGX_ERROR; + } + 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"); + + if (c->asynch && ngx_ssl_async_process_fds(c) == 0) { + return NGX_ERROR; + } + + 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; + } + + if (c->asynch && sslerr == SSL_ERROR_WANT_ASYNC) { + c->async->handler = ngx_ssl_read_async_handler; + c->read->saved_handler = c->read->handler; + c->read->handler = ngx_ssl_empty_handler; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL ASYNC WANT recieved: \"%s\"", __func__); + + if (ngx_ssl_async_process_fds(c) == 0) { + return NGX_ERROR; + } + + 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_read_async_handler(ngx_event_t *aev) +{ + ngx_connection_t *c; + + c = aev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL read async handler"); + + aev->ready = 0; + aev->handler = ngx_ssl_empty_handler; + c->read->handler = c->read->saved_handler; + + c->read->handler(c->read); +} + + +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->asynch && ngx_ssl_async_process_fds(c) == 0) { + return NGX_ERROR; + } + + 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) { + if (c->asynch && ngx_ssl_async_process_fds(c) == 0) { + return NGX_ERROR; + } + 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"); + + if (c->asynch && ngx_ssl_async_process_fds(c) == 0) { + return NGX_ERROR; + } + 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; + } + + if(c->asynch && sslerr == SSL_ERROR_WANT_ASYNC) { + c->async->handler = ngx_ssl_write_async_handler; + c->read->saved_handler = c->read->handler; + c->read->handler = ngx_ssl_empty_handler; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL ASYNC WANT recieved: \"%s\"", __func__); + + if (ngx_ssl_async_process_fds(c) == 0) { + return NGX_ERROR; + } + + 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_write_async_handler(ngx_event_t *aev) +{ + ngx_connection_t *c; + + c = aev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL write async handler"); + + aev->ready = 0; + aev->handler = ngx_ssl_empty_handler; + c->read->handler = c->read->saved_handler; + + c->write->handler(c->write); +} + + +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(!c->ssl) { + return NGX_OK; + } + + 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. + */ + + if(c->asynch) { + /* Check if there is inflight request */ + if (SSL_want_async(c->ssl->connection) && !c->timedout) { + c->async->handler = ngx_ssl_shutdown_async_handler; + ngx_ssl_async_process_fds(c); + ngx_add_timer(c->async, 300); + return NGX_AGAIN; + } + + /* Ignore errors from ngx_ssl_async_process_fds as + we want to carry on and close the SSL connection + anyway. */ + ngx_ssl_async_process_fds(c); + if (ngx_del_async_conn) { + if (c->num_async_fds) { + ngx_del_async_conn(c, NGX_DISABLE_EVENT); + c->num_async_fds--; + } + } + ngx_del_conn(c, NGX_DISABLE_EVENT); + } + + 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()) { + if (c->asynch && ngx_ssl_async_process_fds(c) == 0) { + return NGX_ERROR; + } + sslerr = SSL_get_error(c->ssl->connection, n); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_get_error: %d", sslerr); + } + else if (c->asynch && n == -1) { + sslerr = SSL_get_error(c->ssl->connection, n); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_get_error async: %d", sslerr); + } + + if (n == 1 || sslerr == 0 || sslerr == SSL_ERROR_ZERO_RETURN) { + if(c->asynch) { + /* Ignore errors from ngx_ssl_async_process_fds as + we want to carry on and close the SSL connection + anyway. */ + ngx_ssl_async_process_fds(c); + if (ngx_del_async_conn) { + if (c->num_async_fds) { + ngx_del_async_conn(c, NGX_DISABLE_EVENT); + c->num_async_fds--; + } + } + ngx_del_conn(c, NGX_DISABLE_EVENT); + } + SSL_free(c->ssl->connection); + c->ssl = NULL; + + return NGX_OK; + } + + if (sslerr == SSL_ERROR_WANT_READ || sslerr == SSL_ERROR_WANT_WRITE) { + if (c->asynch && ngx_ssl_async_process_fds(c) == 0) { + return NGX_ERROR; + } + c->read->handler = ngx_ssl_shutdown_handler; + c->write->handler = ngx_ssl_shutdown_handler; + + //Work around: Readd write event on shutdown; + c->write->ready = 0; + c->write->active = 0; + + 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); + } + + if (sslerr == SSL_ERROR_WANT_WRITE) { + ngx_add_timer(c->write, 10000); + } + + return NGX_AGAIN; + } + + if(c->asynch) { + if (sslerr == SSL_ERROR_WANT_ASYNC) { + c->async->handler = ngx_ssl_shutdown_async_handler; + c->read->saved_handler = ngx_ssl_shutdown_handler; + c->read->handler = ngx_ssl_empty_handler; + c->write->handler = ngx_ssl_shutdown_handler; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL ASYNC WANT recieved: \"%s\"", __func__); + + /* Ignore errors from ngx_ssl_async_process_fds as + we want to carry on anyway */ + ngx_ssl_async_process_fds(c); + return NGX_AGAIN; + } + + /* Ignore errors from ngx_ssl_async_process_fds as + we want to carry on and close the SSL connection + anyway. */ + ngx_ssl_async_process_fds(c); + if (ngx_del_async_conn) { + if (c->num_async_fds) { + ngx_del_async_conn(c, NGX_DISABLE_EVENT); + c->num_async_fds--; + } + } + ngx_del_conn(c, NGX_DISABLE_EVENT); + } + 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_async_handler(ngx_event_t *aev) +{ + ngx_connection_t *c; + ngx_connection_handler_pt handler; + + c = aev->data; + handler = c->ssl->handler; + + if (aev->timedout) { + c->timedout = 1; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, aev->log, 0, + "SSL shutdown async handler"); + + aev->ready = 0; + aev->handler = ngx_ssl_empty_handler; + c->read->handler = c->read->saved_handler; + + if (ngx_ssl_shutdown(c) == NGX_AGAIN) { + return; + } + + handler(c); +} + + +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; + } + + /* + * empty the handler of async event to avoid + * going back to previous ssl shutdown state + */ + c->async->handler = ngx_ssl_empty_handler; + 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 */ + || 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_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 */ + || 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 + || 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; + } + + if (p >= last) { + 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 certificate, 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; + } + + cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_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[48]; + 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, 0, 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; + } + + if (ngx_file_size(&fi) != 48) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" must be 48 bytes", &file.name); + goto failed; + } + + n = ngx_read_file(&file, buf, 48, 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 (n != 48) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, 0, + ngx_read_file_n " \"%V\" returned only " + "%z bytes instead of 48", &file.name, n); + goto failed; + } + + key = ngx_array_push(keys); + if (key == NULL) { + goto failed; + } + + ngx_memcpy(key->name, buf, 16); + ngx_memcpy(key->aes_key, buf + 16, 16); + ngx_memcpy(key->hmac_key, buf + 32, 16); + + 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; +} + + +#ifdef OPENSSL_NO_SHA256 +#define ngx_ssl_session_ticket_md EVP_sha1 +#else +#define ngx_ssl_session_ticket_md EVP_sha256 +#endif + + +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) +{ + SSL_CTX *ssl_ctx; + ngx_uint_t i; + ngx_array_t *keys; + ngx_connection_t *c; + ngx_ssl_session_ticket_key_t *key; +#if (NGX_DEBUG) + u_char buf[32]; +#endif + + c = ngx_ssl_get_connection(ssl_conn); + ssl_ctx = c->ssl->session_ctx; + + 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"); + + RAND_bytes(iv, 16); + EVP_EncryptInit_ex(ectx, EVP_aes_128_cbc(), NULL, key[0].aes_key, iv); + HMAC_Init_ex(hctx, key[0].hmac_key, 16, + ngx_ssl_session_ticket_md(), NULL); + 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)" : ""); + + HMAC_Init_ex(hctx, key[i].hmac_key, 16, + ngx_ssl_session_ticket_md(), NULL); + EVP_DecryptInit_ex(ectx, EVP_aes_128_cbc(), NULL, key[i].aes_key, iv); + + 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_keys\" ignored, not supported"); + } + + return NGX_OK; +} + +#endif + + +void +ngx_ssl_cleanup_ctx(void *data) +{ + ngx_ssl_t *ssl = data; + + 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; + } + +#if OPENSSL_VERSION_NUMBER >= 0x10002002L + + /* 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; +} + + +#if OPENSSL_VERSION_NUMBER < 0x10002002L + +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_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 + + const char *servername; + + servername = SSL_get_servername(c->ssl->connection, + TLSEXT_NAMETYPE_host_name); + if (servername) { + s->data = (u_char *) servername; + s->len = ngx_strlen(servername); + 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_subject_dn(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(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; + + if (SSL_get_verify_result(c->ssl->connection) != X509_V_OK) { + ngx_str_set(s, "FAILED"); + return NGX_OK; + } + + cert = SSL_get_peer_certificate(c->ssl->connection); + + if (cert) { + ngx_str_set(s, "SUCCESS"); + + } else { + ngx_str_set(s, "NONE"); + } + + X509_free(cert); + + return NGX_OK; +} + + +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; + + if(cf->no_ssl_init) { + return NGX_CONF_OK; + } + + value = cf->args->elts; + + engine = ENGINE_by_id((const char *) value[1].data); + + if (engine == NULL) { + ngx_ssl_error(NGX_LOG_WARN, 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_WARN, 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/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h new file mode 100644 index 0000000..a36fd26 --- /dev/null +++ b/src/event/ngx_event_openssl.h @@ -0,0 +1,235 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_OPENSSL_H_INCLUDED_ +#define _NGX_EVENT_OPENSSL_H_INCLUDED_ + + +#include +#include + +#include +#include +#include +#include +#include +#include +#ifndef OPENSSL_NO_ENGINE +#include +#endif +#include +#ifndef OPENSSL_NO_OCSP +#include +#endif +#include +#include +#include +#include + +#define NGX_SSL_NAME "OpenSSL" + + +#if (defined LIBRESSL_VERSION_NUMBER && OPENSSL_VERSION_NUMBER == 0x20000000L) +#undef OPENSSL_VERSION_NUMBER +#define OPENSSL_VERSION_NUMBER 0x1000107fL +#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 + + +typedef struct { + SSL_CTX *ctx; + ngx_log_t *log; + size_t buffer_size; + ngx_flag_t asynch; +} ngx_ssl_t; + + +typedef struct { + 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; +} ngx_ssl_connection_t; + + +#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 { + u_char name[16]; + u_char aes_key[16]; + u_char hmac_key[16]; +} 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_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_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_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); + +#define ngx_ssl_waiting_for_async(c) SSL_waiting_for_async(c->ssl->connection) + +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_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_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_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_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); +ngx_int_t ngx_ssl_async_process_fds(ngx_connection_t *c) ; + +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_stapling_index; + + +#endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */ diff --git a/src/event/ngx_event_openssl_stapling.c b/src/event/ngx_event_openssl_stapling.c new file mode 100644 index 0000000..5322b1b --- /dev/null +++ b/src/event/ngx_event_openssl_stapling.c @@ -0,0 +1,1830 @@ + +/* + * Copyright (C) Maxim Dounin + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#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; + + 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; + + 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 *r); + void *data; + + ngx_buf_t *request; + ngx_buf_t *response; + ngx_peer_connection_t peer; + + ngx_int_t (*process)(ngx_ssl_ocsp_ctx_t *r); + + 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_file(ngx_conf_t *cf, ngx_ssl_t *ssl, + ngx_str_t *file); +static ngx_int_t ngx_ssl_stapling_issuer(ngx_conf_t *cf, ngx_ssl_t *ssl); +static ngx_int_t ngx_ssl_stapling_responder(ngx_conf_t *cf, ngx_ssl_t *ssl, + 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) +{ + 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 (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_stapling_index, staple) + == 0) + { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_set_ex_data() failed"); + return NGX_ERROR; + } + + staple->ssl_ctx = ssl->ctx; + staple->timeout = 60000; + staple->verify = verify; + + if (file->len) { + /* use OCSP response from the file */ + + if (ngx_ssl_stapling_file(cf, ssl, file) != NGX_OK) { + return NGX_ERROR; + } + + goto done; + } + + rc = ngx_ssl_stapling_issuer(cf, ssl); + + if (rc == NGX_DECLINED) { + return NGX_OK; + } + + if (rc != NGX_OK) { + return NGX_ERROR; + } + + rc = ngx_ssl_stapling_responder(cf, ssl, responder); + + if (rc == NGX_DECLINED) { + return NGX_OK; + } + + if (rc != NGX_OK) { + return NGX_ERROR; + } + +done: + + SSL_CTX_set_tlsext_status_cb(ssl->ctx, ngx_ssl_certificate_status_callback); + SSL_CTX_set_tlsext_status_arg(ssl->ctx, staple); + + return NGX_OK; +} + + +static ngx_int_t +ngx_ssl_stapling_file(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file) +{ + BIO *bio; + int len; + u_char *p, *buf; + OCSP_RESPONSE *response; + ngx_ssl_stapling_t *staple; + + staple = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_stapling_index); + + 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) +{ + int i, n, rc; + X509 *cert, *issuer; + X509_STORE *store; + X509_STORE_CTX *store_ctx; + STACK_OF(X509) *chain; + ngx_ssl_stapling_t *staple; + + staple = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_stapling_index); + cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index); + +#if OPENSSL_VERSION_NUMBER >= 0x10001000L + 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->cert = cert; + 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"); + 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->cert = cert; + staple->issuer = issuer; + + return NGX_OK; +} + + +static ngx_int_t +ngx_ssl_stapling_responder(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *responder) +{ + ngx_url_t u; + char *s; + ngx_ssl_stapling_t *staple; + STACK_OF(OPENSSL_STRING) *aia; + + staple = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_stapling_index); + + 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"); + 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"); + X509_email_free(aia); + return NGX_DECLINED; + } + + 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\"", &u.url); + 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\"", u.err, &u.url); + 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) +{ + ngx_ssl_stapling_t *staple; + + staple = SSL_CTX_get_ex_data(ssl->ctx, 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; + 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"); + + staple = data; + rc = SSL_TLSEXT_ERR_NOACK; + + 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->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; + } + +#if OPENSSL_VERSION_NUMBER >= 0x10001000L + 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) +{ + u_char *value; + size_t len; + time_t time; + BIO *bio; + + /* + * 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(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; + in_port_t port; + 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; + } + + port = htons(ctx->port); + + 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); + + switch (sockaddr->sa_family) { +#if (NGX_HAVE_INET6) + case AF_INET6: + ((struct sockaddr_in6 *) sockaddr)->sin6_port = port; + break; +#endif + default: /* AF_INET */ + ((struct sockaddr_in *) sockaddr)->sin_port = 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_ssl_ocsp_ctx_t *ctx; + ngx_connection_t *c; + + 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) { +#if 0 + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ctx->log, 0, + "ssl ocsp status line \"%*s\"", + ctx->response->pos - ctx->response->start, + ctx->response->start); +#endif + + 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; + } + + 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: + switch (ch) { + case LF: + 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; + } + + ctx = log->data; + + if (ctx) { + p = ngx_snprintf(p, len, ", responder: %V", &ctx->host); + } + + 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/src/event/ngx_event_pipe.c b/src/event/ngx_event_pipe.c new file mode 100644 index 0000000..f54e070 --- /dev/null +++ b/src/event/ngx_event_pipe.c @@ -0,0 +1,1110 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +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; + return NGX_ERROR; + } + + 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/src/event/ngx_event_pipe.h b/src/event/ngx_event_pipe.h new file mode 100644 index 0000000..ef2e7a0 --- /dev/null +++ b/src/event/ngx_event_pipe.h @@ -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 +#include +#include + + +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_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/src/event/ngx_event_posted.c b/src/event/ngx_event_posted.c new file mode 100644 index 0000000..d851f3d --- /dev/null +++ b/src/event/ngx_event_posted.c @@ -0,0 +1,35 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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/src/event/ngx_event_posted.h b/src/event/ngx_event_posted.h new file mode 100644 index 0000000..145d30f --- /dev/null +++ b/src/event/ngx_event_posted.h @@ -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 +#include +#include + + +#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/src/event/ngx_event_timer.c b/src/event/ngx_event_timer.c new file mode 100644 index 0000000..8f547b2 --- /dev/null +++ b/src/event/ngx_event_timer.c @@ -0,0 +1,138 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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_time */ + + 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); + } +} + + +void +ngx_event_cancel_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); + + ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer)); + + if (!ev->cancelable) { + return; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0, + "event timer cancel: %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->handler(ev); + } +} diff --git a/src/event/ngx_event_timer.h b/src/event/ngx_event_timer.h new file mode 100644 index 0000000..99f8a48 --- /dev/null +++ b/src/event/ngx_event_timer.h @@ -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 +#include +#include + + +#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); +void ngx_event_cancel_timers(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/src/http/modules/ngx_http_access_module.c b/src/http/modules/ngx_http_access_module.c new file mode 100644 index 0000000..c553e46 --- /dev/null +++ b/src/http/modules/ngx_http_access_module.c @@ -0,0 +1,469 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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 + + ngx_memzero(&cidr, sizeof(ngx_cidr_t)); + + value = cf->args->elts; + + all = (value[1].len == 3 && ngx_strcmp(value[1].data, "all") == 0); + + if (!all) { + +#if (NGX_HAVE_UNIX_DOMAIN) + + if (value[1].len == 5 && ngx_strcmp(value[1].data, "unix:") == 0) { + cidr.family = AF_UNIX; + rc = NGX_OK; + + } else { + rc = ngx_ptocidr(&value[1], &cidr); + } + +#else + rc = ngx_ptocidr(&value[1], &cidr); +#endif + + 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/src/http/modules/ngx_http_addition_filter_module.c b/src/http/modules/ngx_http_addition_filter_module.c new file mode 100644 index 0000000..2fad0e5 --- /dev/null +++ b/src/http/modules/ngx_http_addition_filter_module.c @@ -0,0 +1,252 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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); + + 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/src/http/modules/ngx_http_auth_basic_module.c b/src/http/modules/ngx_http_auth_basic_module.c new file mode 100644 index 0000000..1e7a0c2 --- /dev/null +++ b/src/http/modules/ngx_http_auth_basic_module.c @@ -0,0 +1,467 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#define NGX_HTTP_AUTH_BUF_SIZE 2048 + + +typedef struct { + ngx_str_t passwd; +} ngx_http_auth_basic_ctx_t; + + +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_http_auth_basic_ctx_t *ctx, 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_ctx_t *ctx; + 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; + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_auth_basic_module); + + if (ctx) { + return ngx_http_auth_basic_crypt_handler(r, ctx, &ctx->passwd, + &realm); + } + + 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, NULL, &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, NULL, &pwd, &realm); + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "user \"%V\" was not found in \"%V\"", + &r->headers_in.user, &user_file); + + 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_http_auth_basic_ctx_t *ctx, 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) { + 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); + } + + if (rc == NGX_ERROR) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + /* rc == NGX_AGAIN */ + + if (ctx == NULL) { + ctx = ngx_palloc(r->pool, sizeof(ngx_http_auth_basic_ctx_t)); + if (ctx == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_http_set_ctx(r, ctx, ngx_http_auth_basic_module); + + ctx->passwd.len = passwd->len; + passwd->len++; + + ctx->passwd.data = ngx_pstrdup(r->pool, passwd); + if (ctx->passwd.data == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + } + + /* TODO: add mutex event */ + + return rc; +} + + +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) { + 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/src/http/modules/ngx_http_auth_request_module.c b/src/http/modules/ngx_http_auth_request_module.c new file mode 100644 index 0000000..bab79e4 --- /dev/null +++ b/src/http/modules/ngx_http_auth_request_module.c @@ -0,0 +1,444 @@ + +/* + * Copyright (C) Maxim Dounin + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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/src/http/modules/ngx_http_autoindex_module.c b/src/http/modules/ngx_http_autoindex_module.c new file mode 100644 index 0000000..b3bf652 --- /dev/null +++ b/src/http/modules/ngx_http_autoindex_module.c @@ -0,0 +1,1050 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#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; + } + + /* 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[] = + "" CRLF + "Index of " + ; + + static u_char header[] = + "" CRLF + "" CRLF + "

Index of " + ; + + static u_char tail[] = + "" CRLF + "" 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("

") - 1 + + sizeof("
../" CRLF) - 1
+          + sizeof("

") - 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("") - 1 + + entry[i].name.len - entry[i].utf_len + + entry[i].escape_html + + NGX_HTTP_AUTOINDEX_NAME_LEN + sizeof(">") - 2 + + sizeof("") - 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, "", sizeof("") - 1); + + b->last = ngx_cpymem(b->last, "
../" CRLF,
+                         sizeof("
../" 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, "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, "..>", sizeof("..>") - 1);
+
+        } else {
+            if (entry[i].dir && NGX_HTTP_AUTOINDEX_NAME_LEN - len > 0) {
+                *b->last++ = '/';
+                len++;
+            }
+
+            b->last = ngx_cpymem(b->last, "", sizeof("") - 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, "

", sizeof("

") - 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[] = "" CRLF "" CRLF; + static u_char tail[] = "" 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("" 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/src/http/modules/ngx_http_browser_module.c b/src/http/modules/ngx_http_browser_module.c new file mode 100644 index 0000000..80da0d8 --- /dev/null +++ b/src/http/modules/ngx_http_browser_module.c @@ -0,0 +1,715 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +/* + * 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_str_t name; + ngx_http_get_variable_pt handler; + uintptr_t data; +} ngx_http_browser_variable_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_variable(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_variable, /* 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_browser_variable_t ngx_http_browsers[] = { + { ngx_string("msie"), ngx_http_msie_variable, 0 }, + { ngx_string("modern_browser"), ngx_http_browser_variable, + NGX_HTTP_MODERN_BROWSER }, + { ngx_string("ancient_browser"), ngx_http_browser_variable, + NGX_HTTP_ANCIENT_BROWSER }, + { ngx_null_string, NULL, 0 } +}; + + +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_variable(ngx_conf_t *cf) +{ + ngx_http_browser_variable_t *var; + ngx_http_variable_t *v; + + for (var = ngx_http_browsers; var->name.len; var++) { + + v = ngx_http_add_variable(cf, &var->name, NGX_HTTP_VAR_CHANGEABLE); + if (v == NULL) { + return NGX_ERROR; + } + + v->get_handler = var->handler; + v->data = var->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/src/http/modules/ngx_http_charset_filter_module.c b/src/http/modules/ngx_http_charset_filter_module.c new file mode 100644 index 0000000..4ea9818 --- /dev/null +++ b/src/http/modules/ngx_http_charset_filter_module.c @@ -0,0 +1,1685 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#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("􏿿") - 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); + + +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/src/http/modules/ngx_http_chunked_filter_module.c b/src/http/modules/ngx_http_chunked_filter_module.c new file mode 100644 index 0000000..ac2e3e8 --- /dev/null +++ b/src/http/modules/ngx_http_chunked_filter_module.c @@ -0,0 +1,243 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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_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) { + if (r->http_version < NGX_HTTP_VERSION_11) { + r->keepalive = 0; + + } else { + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (clcf->chunked_transfer_encoding) { + 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 { + 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_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->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_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_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/src/http/modules/ngx_http_dav_module.c b/src/http/modules/ngx_http_dav_module.c new file mode 100644 index 0000000..3600265 --- /dev/null +++ b/src/http/modules/ngx_http_dav_module.c @@ -0,0 +1,1156 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#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; + } + + 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 || r->request_body->temp_file == NULL) { + 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, ©.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, ©.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 = © + 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, ©.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_palloc(r->pool, sizeof(ngx_table_elt_t)); + 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) { + return NGX_ERROR; + } + + ngx_memcpy(location, r->uri.data, r->uri.len); + } + + /* + * we do not need to set the r->headers_out.location->hash and + * r->headers_out.location->key fields + */ + + 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/src/http/modules/ngx_http_degradation_module.c b/src/http/modules/ngx_http_degradation_module.c new file mode 100644 index 0000000..b9c65cd --- /dev/null +++ b/src/http/modules/ngx_http_degradation_module.c @@ -0,0 +1,243 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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/src/http/modules/ngx_http_empty_gif_module.c b/src/http/modules/ngx_http_empty_gif_module.c new file mode 100644 index 0000000..04114dc --- /dev/null +++ b/src/http/modules/ngx_http_empty_gif_module.c @@ -0,0 +1,140 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + +#include +#include +#include + + +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/src/http/modules/ngx_http_fastcgi_module.c b/src/http/modules/ngx_http_fastcgi_module.c new file mode 100644 index 0000000..2d288ce --- /dev/null +++ b/src/http/modules/ngx_http_fastcgi_module.c @@ -0,0 +1,3752 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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("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_TAKE1, + 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_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 }, + +#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_null_string, NULL, NULL, 0, 0, 0 } +}; + + +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 && url.addrs[0].sockaddr) { + u->resolved->sockaddr = url.addrs[0].sockaddr; + u->resolved->socklen = url.addrs[0].socklen; + u->resolved->naddrs = 1; + u->resolved->host = url.addrs[0].name; + + } else { + 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(¶ms->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) { + 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"); + 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) { + 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; + } + + /* provide continuous buffer for subrequests in memory */ + + if (r->subrequest_in_memory) { + + cl = u->out_bufs; + + if (cl) { + buf->pos = cl->buf->pos; + } + + buf->last = buf->pos; + + for (cl = u->out_bufs; cl; cl = cl->next) { + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http fastcgi in memory %p-%p %O", + cl->buf->pos, cl->buf->last, ngx_buf_size(cl->buf)); + + if (buf->last == cl->buf->pos) { + buf->last = cl->buf->last; + continue; + } + + buf->last = ngx_movemem(buf->last, cl->buf->pos, + cl->buf->last - cl->buf->pos); + + cl->buf->pos = buf->last - (cl->buf->last - cl->buf->pos); + cl->buf->last = buf->last; + } + } + + return NGX_OK; +} + + +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->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.uri = { 0, NULL }; + * conf->upstream.location = NULL; + * 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_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; +#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_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); + +#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 + + 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(¶ms_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(¶ms_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(¶ms_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) 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) 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 = ¶ms->flushes; + sc.lengths = ¶ms->lengths; + sc.values = ¶ms->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 = ¶ms->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/src/http/modules/ngx_http_flv_module.c b/src/http/modules/ngx_http_flv_module.c new file mode 100644 index 0000000..cc25320 --- /dev/null +++ b/src/http/modules/ngx_http_flv_module.c @@ -0,0 +1,266 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + +#include +#include +#include + + +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_pcalloc(r->pool, sizeof(ngx_buf_t)); + 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_pcalloc(r->pool, sizeof(ngx_buf_t)); + 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/src/http/modules/ngx_http_geo_module.c b/src/http/modules/ngx_http_geo_module.c new file mode 100644 index 0000000..9b3c6cb --- /dev/null +++ b/src/http/modules/ngx_http_geo_module.c @@ -0,0 +1,1644 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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) { + 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; + + 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 || a->nelts == 0) { + 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) { + return NGX_CONF_ERROR; + } + + 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; + + ngx_destroy_pool(ctx.temp_pool); + ngx_destroy_pool(pool); + + } else { + if (ctx.tree == NULL) { + ctx.tree = ngx_radix_tree_create(cf->pool, -1); + if (ctx.tree == NULL) { + return NGX_CONF_ERROR; + } + } + + 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) { + return NGX_CONF_ERROR; + } + } + + geo->u.trees.tree6 = ctx.tree6; +#endif + + var->get_handler = ngx_http_geo_cidr_variable; + var->data = (uintptr_t) geo; + + ngx_destroy_pool(ctx.temp_pool); + ngx_destroy_pool(pool); + + if (ngx_radix32tree_insert(ctx.tree, 0, 0, + (uintptr_t) &ngx_http_variable_null_value) + == NGX_ERROR) + { + return NGX_CONF_ERROR; + } + + /* 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) + { + return NGX_CONF_ERROR; + } +#endif + } + + return rv; +} + + +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->start = (u_short) s; + range->end = (u_short) e; + range->value = ctx->value; + + next: + + continue; + } + + 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 += 0x10000) { + + 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) { + warn = 1; + continue; + } + + 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 (s != (ngx_uint_t) range[i].start + && e != (ngx_uint_t) range[i].end) + { + continue; + } + + warn = 1; + } + } + + 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, 0, 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/src/http/modules/ngx_http_geoip_module.c b/src/http/modules/ngx_http_geoip_module.c new file mode 100644 index 0000000..8e151aa --- /dev/null +++ b/src/http/modules/ngx_http_geoip_module.c @@ -0,0 +1,925 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + +#include +#include + + +#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_null_string, NULL, NULL, 0, 0, 0 } +}; + + +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/src/http/modules/ngx_http_gunzip_filter_module.c b/src/http/modules/ngx_http_gunzip_filter_module.c new file mode 100644 index 0000000..c1341f5 --- /dev/null +++ b/src/http/modules/ngx_http_gunzip_filter_module.c @@ -0,0 +1,687 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Maxim Dounin + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + +#include + + +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/src/http/modules/ngx_http_gzip_filter_module.c b/src/http/modules/ngx_http_gzip_filter_module.c new file mode 100644 index 0000000..536fdf8 --- /dev/null +++ b/src/http/modules/ngx_http_gzip_filter_module.c @@ -0,0 +1,1235 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + +#include + + +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; + + 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_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 + */ + + ctx->allocated = 8192 + (1 << (wbits + 2)) + (1 << (memlevel + 9)); +} + + +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_pcalloc(r->pool, sizeof(ngx_buf_t)); + 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) +{ + 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; + } + + ctx->in_buf = ctx->in->buf; + + if (ctx->in_buf->tag == (ngx_buf_tag_t) &ngx_http_gzip_filter_module) { + ctx->copy_buf = ctx->in; + } + + 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, + "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_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) { + ctx->out_buf = ctx->free->buf; + ctx->free = ctx->free->next; + + } 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 (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; + } + + 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); + + 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; + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + 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->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/src/http/modules/ngx_http_gzip_static_module.c b/src/http/modules/ngx_http_gzip_static_module.c new file mode 100644 index 0000000..4d54090 --- /dev/null +++ b/src/http/modules/ngx_http_gzip_static_module.c @@ -0,0 +1,331 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#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 +}; + + +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_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_pcalloc(r->pool, sizeof(ngx_buf_t)); + 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/src/http/modules/ngx_http_headers_filter_module.c b/src/http/modules/ngx_http_headers_filter_module.c new file mode 100644 index 0000000..6738afe --- /dev/null +++ b/src/http/modules/ngx_http_headers_filter_module.c @@ -0,0 +1,741 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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_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_cache_control(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"), 0, ngx_http_add_cache_control }, + + { 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, + 0, + 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_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; + + conf = ngx_http_get_module_loc_conf(r, ngx_http_headers_filter_module); + + if ((conf->expires == NGX_HTTP_EXPIRES_OFF && conf->headers == NULL) + || r != r->main) + { + 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: + 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; + } + } + } + + return ngx_http_next_header_filter(r); +} + + +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; + } + + ccp = ngx_array_push(&r->headers_out.cache_control); + if (ccp == NULL) { + 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 = 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_cache_control(ngx_http_request_t *r, ngx_http_header_val_t *hv, + ngx_str_t *value) +{ + ngx_table_elt_t *cc, **ccp; + + if (value->len == 0) { + return NGX_OK; + } + + 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; + } + } + + ccp = ngx_array_push(&r->headers_out.cache_control); + if (ccp == NULL) { + 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"); + cc->value = *value; + + *ccp = cc; + + 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->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; + } + + 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; + + 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_http_header_val_t *hv; + ngx_http_set_header_t *set; + ngx_http_compile_complex_value_t ccv; + + value = cf->args->elts; + + if (hcf->headers == NULL) { + hcf->headers = ngx_array_create(cf->pool, 1, + sizeof(ngx_http_header_val_t)); + if (hcf->headers == NULL) { + return NGX_CONF_ERROR; + } + } + + hv = ngx_array_push(hcf->headers); + if (hv == NULL) { + return NGX_CONF_ERROR; + } + + hv->key = value[1]; + hv->handler = ngx_http_add_header; + hv->offset = 0; + hv->always = 0; + + 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/src/http/modules/ngx_http_image_filter_module.c b/src/http/modules/ngx_http_image_filter_module.c new file mode 100644 index 0000000..b608de1 --- /dev/null +++ b/src/http/modules/ngx_http_image_filter_module.c @@ -0,0 +1,1521 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + +#include + + +#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_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 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 *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_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_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") +}; + + +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; + } + + 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_pcalloc(r->pool, sizeof(ngx_buf_t)); + 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_pcalloc(r->pool, sizeof(ngx_buf_t)); + 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; + + 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_pcalloc(r->pool, sizeof(ngx_buf_t)); + 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; + + 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 jq; + 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); + + jq = ngx_http_image_filter_get_value(r, conf->jqcv, conf->jpeg_quality); + if (jq <= 0) { + return NULL; + } + + out = gdImageJpegPtr(img, size, jq); + 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; + + 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->shcv = NULL; + */ + + conf->filter = NGX_CONF_UNSET_UINT; + conf->jpeg_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->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_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/src/http/modules/ngx_http_index_module.c b/src/http/modules/ngx_http_index_module.c new file mode 100644 index 0000000..d3544db --- /dev/null +++ b/src/http/modules/ngx_http_index_module.c @@ -0,0 +1,540 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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) + { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, of.err, + "%s \"%s\" failed", of.failed, path.data); + + if (of.err == 0) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + +#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/src/http/modules/ngx_http_limit_conn_module.c b/src/http/modules/ngx_http_limit_conn_module.c new file mode 100644 index 0000000..913d599 --- /dev/null +++ b/src/http/modules/ngx_http_limit_conn_module.c @@ -0,0 +1,670 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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/src/http/modules/ngx_http_limit_req_module.c b/src/http/modules/ngx_http_limit_req_module.c new file mode 100644 index 0000000..9059ac3 --- /dev/null +++ b/src/http/modules/ngx_http_limit_req_module.c @@ -0,0 +1,968 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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; + 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->timedout) { + + if (ngx_handle_write_event(wev, 0) != NGX_OK) { + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + return; + } + + wev->timedout = 0; + + 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_time_t *tp; + 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; + + tp = ngx_timeofday(); + now = (ngx_msec_t) (tp->sec * 1000 + tp->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_time_t *tp; + 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); + + tp = ngx_timeofday(); + + now = (ngx_msec_t) (tp->sec * 1000 + tp->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_time_t *tp; + 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; + + tp = ngx_timeofday(); + + now = (ngx_msec_t) (tp->sec * 1000 + tp->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/src/http/modules/ngx_http_log_module.c b/src/http/modules/ngx_http_log_module.c new file mode 100644 index 0000000..df9424f --- /dev/null +++ b/src/http/modules/ngx_http_log_module.c @@ -0,0 +1,1794 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + +#if (NGX_ZLIB) +#include +#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; + + +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); +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 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) + { + 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_open_file_t *file; + ngx_http_log_buf_t *buffer; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, + "http log buffer flush handler"); + + if (ev->timedout) { + ngx_http_log_flush(ev->data, ev->log); + return; + } + + /* cancel the flush timer for graceful shutdown */ + + file = ev->data; + buffer = file->data; + + buffer->event = NULL; +} + + +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_int_t index; + + index = ngx_http_get_variable_index(cf, value); + if (index == NGX_ERROR) { + return NGX_ERROR; + } + + op->len = 0; + 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] & (1 << (*src & 0x1f))) { + n++; + } + src++; + size--; + } + + return (uintptr_t) n; + } + + while (size) { + if (escape[*src >> 5] & (1 << (*src & 0x1f))) { + *dst++ = '\\'; + *dst++ = 'x'; + *dst++ = hex[*src >> 4]; + *dst++ = hex[*src & 0xf]; + src++; + + } else { + *dst++ = *src++; + } + size--; + } + + return (uintptr_t) dst; +} + + +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; + ngx_http_log_op_t *op; + ngx_http_log_var_t *v; + + value = args->elts; + + 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) != 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/src/http/modules/ngx_http_map_module.c b/src/http/modules/ngx_http_map_module.c new file mode 100644 index 0000000..091ff09 --- /dev/null +++ b/src/http/modules/ngx_http_map_module.c @@ -0,0 +1,567 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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; + ngx_array_t var_values; +#if (NGX_PCRE) + ngx_array_t regexes; +#endif + + ngx_http_variable_value_t *default_value; + ngx_conf_t *cf; + ngx_uint_t hostnames; /* unsigned hostnames: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; + 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) { + value = ngx_http_get_flushed_variable(r, (uintptr_t) value->data); + + if (value == NULL || value->not_found) { + value = &ngx_http_variable_null_value; + } + } + + *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_array_init(&ctx.var_values, cf->pool, 2, + sizeof(ngx_http_variable_value_t)) + != NGX_OK) + { + 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; + + 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; + } + + 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) +{ + ngx_int_t rv, index; + ngx_str_t *value, name; + ngx_uint_t i, key; + ngx_http_map_conf_ctx_t *ctx; + ngx_http_variable_value_t *var, **vp; + + 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; + + } else 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); + } + + if (value[1].data[0] == '$') { + name = value[1]; + name.len--; + name.data++; + + index = ngx_http_get_variable_index(ctx->cf, &name); + if (index == NGX_ERROR) { + return NGX_CONF_ERROR; + } + + var = ctx->var_values.elts; + + for (i = 0; i < ctx->var_values.nelts; i++) { + if (index == (intptr_t) var[i].data) { + var = &var[i]; + goto found; + } + } + + var = ngx_array_push(&ctx->var_values); + if (var == NULL) { + return NGX_CONF_ERROR; + } + + var->valid = 0; + var->no_cacheable = 0; + var->not_found = 0; + var->len = 0; + var->data = (u_char *) (intptr_t) index; + + goto found; + } + + 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 (value[1].len != (size_t) vp[i]->len) { + continue; + } + + if (ngx_strncmp(value[1].data, vp[i]->data, value[1].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; + } + + var->len = value[1].len; + var->data = ngx_pstrdup(ctx->keys.pool, &value[1]); + if (var->data == NULL) { + return NGX_CONF_ERROR; + } + + 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/src/http/modules/ngx_http_memcached_module.c b/src/http/modules/ngx_http_memcached_module.c new file mode 100644 index 0000000..0292408 --- /dev/null +++ b/src/http/modules/ngx_http_memcached_module.c @@ -0,0 +1,723 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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_TAKE1, + 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,(unsigned)(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.uri = { 0, NULL }; + * conf->upstream.location = 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/src/http/modules/ngx_http_mp4_module.c b/src/http/modules/ngx_http_mp4_module.c new file mode 100644 index 0000000..16ef83c --- /dev/null +++ b/src/http/modules/ngx_http_mp4_module.c @@ -0,0 +1,3500 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + +#include +#include +#include + + +#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) \ + 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_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 strtod() is used instead of atofp(). NaNs and + * infinities become negative numbers after (int) conversion. + */ + + ngx_set_errno(0); + start = (int) (strtod((char *) value.data, NULL) * 1000); + + if (ngx_errno != 0) { + start = -1; + } + } + + if (ngx_http_arg(r, (u_char *) "end", 3, &value) == NGX_OK) { + + ngx_set_errno(0); + end = (int) (strtod((char *) value.data, NULL) * 1000); + + if (ngx_errno != 0) { + end = -1; + } + + 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_pcalloc(r->pool, sizeof(ngx_buf_t)); + 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_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); + + } 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 = 1; + 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) { + 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/src/http/modules/ngx_http_not_modified_filter_module.c b/src/http/modules/ngx_http_not_modified_filter_module.c new file mode 100644 index 0000000..032ba96 --- /dev/null +++ b/src/http/modules/ngx_http_not_modified_filter_module.c @@ -0,0 +1,266 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c new file mode 100644 index 0000000..c24ef17 --- /dev/null +++ b/src/http/modules/ngx_http_proxy_module.c @@ -0,0 +1,4393 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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_str_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("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_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_TAKE1, + 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_conf_set_str_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_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 }, + +#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_null_string, NULL, NULL, 0, 0, 0 } +}; + + +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 && url.addrs[0].sockaddr) { + u->resolved->sockaddr = url.addrs[0].sockaddr; + u->resolved->socklen = url.addrs[0].socklen; + u->resolved->naddrs = 1; + u->resolved->host = url.addrs[0].name; + + } else { + 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 && r == r->main) + { + *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->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; + 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.len) { + method = plcf->method; + + } 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 && r == r->main) + { + 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) { + while (*(uintptr_t *) le.ip) { + lcode = *(ngx_http_script_len_code_pt *) le.ip; + len += lcode(&le); + } + le.ip += sizeof(uintptr_t); + } + + + 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; + + /* skip the header line name length */ + (void) lcode(&le); + + if (*(ngx_http_script_len_code_pt *) le.ip) { + + for (len = 0; *(uintptr_t *) le.ip; len += lcode(&le)) { + lcode = *(ngx_http_script_len_code_pt *) le.ip; + } + + e.skip = (len == sizeof(CRLF) - 1) ? 1 : 0; + + } else { + e.skip = 0; + } + + le.ip += sizeof(uintptr_t); + + 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 = 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) { + 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; + } + + /* provide continuous buffer for subrequests in memory */ + + if (r->subrequest_in_memory) { + + cl = u->out_bufs; + + if (cl) { + buf->pos = cl->buf->pos; + } + + buf->last = buf->pos; + + for (cl = u->out_bufs; cl; cl = cl->next) { + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy in memory %p-%p %O", + cl->buf->pos, cl->buf->last, ngx_buf_size(cl->buf)); + + if (buf->last == cl->buf->pos) { + buf->last = cl->buf->last; + continue; + } + + buf->last = ngx_movemem(buf->last, cl->buf->pos, + cl->buf->last - cl->buf->pos); + + cl->buf->pos = buf->last - (cl->buf->last - cl->buf->pos); + cl->buf->last = buf->last; + } + } + + return NGX_OK; +} + + +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.uri = { 0, NULL }; + * conf->upstream.location = NULL; + * conf->upstream.store_lengths = NULL; + * conf->upstream.store_values = NULL; + * conf->upstream.ssl_name = NULL; + * + * conf->method = { 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_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; +#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_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); + +#endif + + ngx_conf_merge_str_value(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 + + 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; + } + + if (conf->headers_source == NULL) { + conf->headers_source = ngx_array_create(cf->pool, 4, + sizeof(ngx_keyval_t)); + if (conf->headers_source == NULL) { + 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; + } + + 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; + } + + if (ngx_http_script_variables_count(&src[i].value) == 0) { + copy = ngx_array_push_n(headers->lengths, + sizeof(ngx_http_script_copy_code_t)); + if (copy == NULL) { + return NGX_ERROR; + } + + copy->code = (ngx_http_script_code_pt) + ngx_http_script_copy_len_code; + copy->len = src[i].key.len + sizeof(": ") - 1 + + src[i].value.len + sizeof(CRLF) - 1; + + + size = (sizeof(ngx_http_script_copy_code_t) + + src[i].key.len + sizeof(": ") - 1 + + src[i].value.len + sizeof(CRLF) - 1 + + 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 + sizeof(": ") - 1 + + src[i].value.len + sizeof(CRLF) - 1; + + p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t); + + p = ngx_cpymem(p, src[i].key.data, src[i].key.len); + *p++ = ':'; *p++ = ' '; + p = ngx_cpymem(p, src[i].value.data, src[i].value.len); + *p++ = CR; *p = LF; + + } else { + copy = ngx_array_push_n(headers->lengths, + sizeof(ngx_http_script_copy_code_t)); + if (copy == NULL) { + return NGX_ERROR; + } + + copy->code = (ngx_http_script_code_pt) + ngx_http_script_copy_len_code; + copy->len = src[i].key.len + sizeof(": ") - 1; + + + size = (sizeof(ngx_http_script_copy_code_t) + + src[i].key.len + sizeof(": ") - 1 + 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 + sizeof(": ") - 1; + + p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t); + p = ngx_cpymem(p, src[i].key.data, src[i].key.len); + *p++ = ':'; *p = ' '; + + + 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; + } + + + copy = ngx_array_push_n(headers->lengths, + sizeof(ngx_http_script_copy_code_t)); + if (copy == NULL) { + return NGX_ERROR; + } + + copy->code = (ngx_http_script_code_pt) + ngx_http_script_copy_len_code; + copy->len = sizeof(CRLF) - 1; + + + size = (sizeof(ngx_http_script_copy_code_t) + + sizeof(CRLF) - 1 + 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 = sizeof(CRLF) - 1; + + p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t); + *p++ = CR; *p = LF; + } + + 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 (SSL_CTX_set_cipher_list(plcf->upstream.ssl->ctx, + (const char *) plcf->ssl_ciphers.data) + == 0) + { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "SSL_CTX_set_cipher_list(\"%V\") failed", + &plcf->ssl_ciphers); + 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/src/http/modules/ngx_http_random_index_module.c b/src/http/modules/ngx_http_random_index_module.c new file mode 100644 index 0000000..b47ee4f --- /dev/null +++ b/src/http/modules/ngx_http_random_index_module.c @@ -0,0 +1,317 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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/src/http/modules/ngx_http_range_filter_module.c b/src/http/modules/ngx_http_range_filter_module.c new file mode 100644 index 0000000..57065e1 --- /dev/null +++ b/src/http/modules/ngx_http_range_filter_module.c @@ -0,0 +1,919 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +/* + * 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; + + if (ngx_array_init(&ctx->ranges, r->pool, 1, sizeof(ngx_http_range_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + 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; + } + } + + 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 = content_length - end; + 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; + + size += end - start; + + if (ranges-- == 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) { + 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) +{ + size_t len; + ngx_uint_t i; + ngx_http_range_t *range; + ngx_atomic_uint_t boundary; + + len = 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) + { + len += sizeof("; charset=") - 1 + r->headers_out.charset.len; + } + + ctx->boundary_header.data = ngx_pnalloc(r->pool, len); + 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 + + (size_t) (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) { + 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 = 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/src/http/modules/ngx_http_realip_module.c b/src/http/modules/ngx_http_realip_module.c new file mode 100644 index 0000000..b7befe6 --- /dev/null +++ b/src/http/modules/ngx_http_realip_module.c @@ -0,0 +1,512 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#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_int_t ngx_http_realip_remote_addr_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_null_string, NULL, NULL, 0, 0, 0 } +}; + + +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; + + ctx = ngx_http_get_module_ctx(r, ngx_http_realip_module); + + if (ctx) { + return NGX_DECLINED; + } + + rlcf = ngx_http_get_module_loc_conf(r, ngx_http_realip_module); + + if (rlcf->from == NULL) { + 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) + { + 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; + ngx_http_set_ctx(r, ctx, ngx_http_realip_module); + + 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; + + 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_cidr_t *cidr; + + 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; + } + } + + cidr = ngx_array_push(rlcf->from); + if (cidr == NULL) { + return NGX_CONF_ERROR; + } + +#if (NGX_HAVE_UNIX_DOMAIN) + + if (ngx_strcmp(value[1].data, "unix:") == 0) { + cidr->family = AF_UNIX; + return NGX_CONF_OK; + } + +#endif + + 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]); + } + + 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; + + 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_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_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; + } + } + } + + 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; +} diff --git a/src/http/modules/ngx_http_referer_module.c b/src/http/modules/ngx_http_referer_module.c new file mode 100644 index 0000000..3f0f78e --- /dev/null +++ b/src/http/modules/ngx_http_referer_module.c @@ -0,0 +1,671 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#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 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 = { + NULL, /* 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_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 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, name; + ngx_uint_t i; + ngx_http_variable_t *var; + + ngx_str_set(&name, "invalid_referer"); + + var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE); + if (var == NULL) { + return NGX_CONF_ERROR; + } + + var->get_handler = ngx_http_referer_variable; + + 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/src/http/modules/ngx_http_rewrite_module.c b/src/http/modules/ngx_http_rewrite_module.c new file mode 100644 index 0000000..6b2444c --- /dev/null +++ b/src/http/modules/ngx_http_rewrite_module.c @@ -0,0 +1,1029 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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 = ®ex->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), + ®ex); + 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), ®ex); + 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); + 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 + && ngx_strncasecmp(value[1].data, (u_char *) "http_", 5) != 0 + && ngx_strncasecmp(value[1].data, (u_char *) "sent_http_", 10) != 0 + && ngx_strncasecmp(value[1].data, (u_char *) "upstream_http_", 14) != 0 + && ngx_strncasecmp(value[1].data, (u_char *) "cookie_", 7) != 0 + && ngx_strncasecmp(value[1].data, (u_char *) "upstream_cookie_", 16) + != 0 + && ngx_strncasecmp(value[1].data, (u_char *) "arg_", 4) != 0) + { + 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/src/http/modules/ngx_http_scgi_module.c b/src/http/modules/ngx_http_scgi_module.c new file mode 100644 index 0000000..f09617e --- /dev/null +++ b/src/http/modules/ngx_http_scgi_module.c @@ -0,0 +1,1969 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + * Copyright (C) Manlio Perillo (manlio.perillo@gmail.com) + */ + + +#include +#include +#include + + +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("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_TAKE1, + 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_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 }, + +#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 && url.addrs[0].sockaddr) { + u->resolved->sockaddr = url.addrs[0].sockaddr; + u->resolved->socklen = url.addrs[0].socklen; + u->resolved->naddrs = 1; + u->resolved->host = url.addrs[0].name; + + } else { + 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(¶ms->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) { + 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_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; +#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_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); + +#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 + + 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(¶ms_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(¶ms_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(¶ms_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) 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) 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 = ¶ms->flushes; + sc.lengths = ¶ms->lengths; + sc.values = ¶ms->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 = ¶ms->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/src/http/modules/ngx_http_secure_link_module.c b/src/http/modules/ngx_http_secure_link_module.c new file mode 100644 index 0000000..907ba6e --- /dev/null +++ b/src/http/modules/ngx_http_secure_link_module.c @@ -0,0 +1,368 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +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[16], 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.len = 16; + 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/src/http/modules/ngx_http_slice_filter_module.c b/src/http/modules/ngx_http_slice_filter_module.c new file mode 100644 index 0000000..5e149b4 --- /dev/null +++ b/src/http/modules/ngx_http_slice_filter_module.c @@ -0,0 +1,526 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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; + ngx_uint_t last; /* unsigned last:1; */ +} 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; + + 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; + } + + 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_request_t *sr; + 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->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, &sr, NULL, 0) != NGX_OK) { + return NGX_ERROR; + } + + ngx_http_set_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; + + 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/src/http/modules/ngx_http_split_clients_module.c b/src/http/modules/ngx_http_split_clients_module.c new file mode 100644 index 0000000..2f92c9e --- /dev/null +++ b/src/http/modules/ngx_http_split_clients_module.c @@ -0,0 +1,246 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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/src/http/modules/ngx_http_ssi_filter_module.c b/src/http/modules/ngx_http_ssi_filter_module.c new file mode 100644 index 0000000..fc6e65b --- /dev/null +++ b/src/http/modules/ngx_http_ssi_filter_module.c @@ -0,0 +1,2930 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + +#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[] = "" CRLF +"" CRLF +"" CRLF +"" CRLF +"" CRLF +"" CRLF +; + + +static u_char ngx_http_msie_refresh_head[] = +"" CRLF; + + +static char ngx_http_error_301_page[] = +"" CRLF +"301 Moved Permanently" CRLF +"" CRLF +"

301 Moved Permanently

" CRLF +; + + +static char ngx_http_error_302_page[] = +"" CRLF +"302 Found" CRLF +"" CRLF +"

302 Found

" CRLF +; + + +static char ngx_http_error_303_page[] = +"" CRLF +"303 See Other" CRLF +"" CRLF +"

303 See Other

" CRLF +; + + +static char ngx_http_error_307_page[] = +"" CRLF +"307 Temporary Redirect" CRLF +"" CRLF +"

307 Temporary Redirect

" CRLF +; + + +static char ngx_http_error_400_page[] = +"" CRLF +"400 Bad Request" CRLF +"" CRLF +"

400 Bad Request

" CRLF +; + + +static char ngx_http_error_401_page[] = +"" CRLF +"401 Authorization Required" CRLF +"" CRLF +"

401 Authorization Required

" CRLF +; + + +static char ngx_http_error_402_page[] = +"" CRLF +"402 Payment Required" CRLF +"" CRLF +"

402 Payment Required

" CRLF +; + + +static char ngx_http_error_403_page[] = +"" CRLF +"403 Forbidden" CRLF +"" CRLF +"

403 Forbidden

" CRLF +; + + +static char ngx_http_error_404_page[] = +"" CRLF +"404 Not Found" CRLF +"" CRLF +"

404 Not Found

" CRLF +; + + +static char ngx_http_error_405_page[] = +"" CRLF +"405 Not Allowed" CRLF +"" CRLF +"

405 Not Allowed

" CRLF +; + + +static char ngx_http_error_406_page[] = +"" CRLF +"406 Not Acceptable" CRLF +"" CRLF +"

406 Not Acceptable

" CRLF +; + + +static char ngx_http_error_408_page[] = +"" CRLF +"408 Request Time-out" CRLF +"" CRLF +"

408 Request Time-out

" CRLF +; + + +static char ngx_http_error_409_page[] = +"" CRLF +"409 Conflict" CRLF +"" CRLF +"

409 Conflict

" CRLF +; + + +static char ngx_http_error_410_page[] = +"" CRLF +"410 Gone" CRLF +"" CRLF +"

410 Gone

" CRLF +; + + +static char ngx_http_error_411_page[] = +"" CRLF +"411 Length Required" CRLF +"" CRLF +"

411 Length Required

" CRLF +; + + +static char ngx_http_error_412_page[] = +"" CRLF +"412 Precondition Failed" CRLF +"" CRLF +"

412 Precondition Failed

" CRLF +; + + +static char ngx_http_error_413_page[] = +"" CRLF +"413 Request Entity Too Large" CRLF +"" CRLF +"

413 Request Entity Too Large

" CRLF +; + + +static char ngx_http_error_414_page[] = +"" CRLF +"414 Request-URI Too Large" CRLF +"" CRLF +"

414 Request-URI Too Large

" CRLF +; + + +static char ngx_http_error_415_page[] = +"" CRLF +"415 Unsupported Media Type" CRLF +"" CRLF +"

415 Unsupported Media Type

" CRLF +; + + +static char ngx_http_error_416_page[] = +"" CRLF +"416 Requested Range Not Satisfiable" CRLF +"" CRLF +"

416 Requested Range Not Satisfiable

" CRLF +; + + +static char ngx_http_error_421_page[] = +"" CRLF +"421 Misdirected Request" CRLF +"" CRLF +"

421 Misdirected Request

" CRLF +; + + +static char ngx_http_error_494_page[] = +"" CRLF +"400 Request Header Or Cookie Too Large" +CRLF +"" CRLF +"

400 Bad Request

" CRLF +"
Request Header Or Cookie Too Large
" CRLF +; + + +static char ngx_http_error_495_page[] = +"" CRLF +"400 The SSL certificate error" +CRLF +"" CRLF +"

400 Bad Request

" CRLF +"
The SSL certificate error
" CRLF +; + + +static char ngx_http_error_496_page[] = +"" CRLF +"400 No required SSL certificate was sent" +CRLF +"" CRLF +"

400 Bad Request

" CRLF +"
No required SSL certificate was sent
" CRLF +; + + +static char ngx_http_error_497_page[] = +"" CRLF +"400 The plain HTTP request was sent to HTTPS port" +CRLF +"" CRLF +"

400 Bad Request

" CRLF +"
The plain HTTP request was sent to HTTPS port
" CRLF +; + + +static char ngx_http_error_500_page[] = +"" CRLF +"500 Internal Server Error" CRLF +"" CRLF +"

500 Internal Server Error

" CRLF +; + + +static char ngx_http_error_501_page[] = +"" CRLF +"501 Not Implemented" CRLF +"" CRLF +"

501 Not Implemented

" CRLF +; + + +static char ngx_http_error_502_page[] = +"" CRLF +"502 Bad Gateway" CRLF +"" CRLF +"

502 Bad Gateway

" CRLF +; + + +static char ngx_http_error_503_page[] = +"" CRLF +"503 Service Temporarily Unavailable" CRLF +"" CRLF +"

503 Service Temporarily Unavailable

" CRLF +; + + +static char ngx_http_error_504_page[] = +"" CRLF +"504 Gateway Time-out" CRLF +"" CRLF +"

504 Gateway Time-out

" CRLF +; + + +static char ngx_http_error_507_page[] = +"" CRLF +"507 Insufficient Storage" CRLF +"" CRLF +"

507 Insufficient Storage

" 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), + +#define NGX_HTTP_LAST_3XX 308 +#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), + +#define NGX_HTTP_LAST_4XX 422 +#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_null_string, /* 505 */ + 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; + break; + } + + } 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) + { + 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) { + len = sizeof(ngx_http_error_full_tail) - 1; + tail = ngx_http_error_full_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 = 1; + b->last_in_chain = 1; + + out.buf = b; + out.next = NULL; + + return ngx_http_output_filter(r, &out); +} diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c new file mode 100644 index 0000000..ef1c9d1 --- /dev/null +++ b/src/http/ngx_http_upstream.c @@ -0,0 +1,6157 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#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_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 void ngx_http_upstream_process_body_in_memory(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static void ngx_http_upstream_send_response(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static void ngx_http_upstream_upgrade(ngx_http_request_t *r, + 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 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_addr_t *ngx_http_upstream_get_local(ngx_http_request_t *r, + 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(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 + + +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("X-Powered-By"), + ngx_http_upstream_ignore_header_line, 0, + ngx_http_upstream_copy_header_line, 0, 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 }, + +#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_null_string, NULL, NULL, 0, 0, 0 } +}; + + +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 }, + { 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; + } + } + + 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; + } + + u->peer.local = ngx_http_upstream_get_local(r, u->conf->local); + + 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; + + 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; + } + + 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->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; + } + +#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_UPDATING: + + if (u->conf->cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING) { + 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; + u->buffer.start = NULL; + u->cache_status = NGX_HTTP_CACHE_EXPIRED; + + break; + + case NGX_DECLINED: + + if ((size_t) (unsigned) (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; + } + + 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; + } + + 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; + } + + /* rc == NGX_HTTP_UPSTREAM_INVALID_HEADER */ + + /* TODO: delete file */ + + return rc; +} + +#endif + + +static void +ngx_http_upstream_resolve_handler(ngx_resolver_ctx_t *ctx) +{ + ngx_connection_t *c; + ngx_http_request_t *r; + ngx_http_upstream_t *u; + ngx_http_upstream_resolved_t *ur; + + 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: + + 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->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_HTTP_SSL) + if (c->asynch && ngx_del_async_conn) { + if (c->num_async_fds) { + ngx_del_async_conn(c, NGX_DISABLE_EVENT); + c->num_async_fds--; + } + } +#endif + 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) && ev->pending_eof) { + socklen_t len; + + 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 (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 = 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_HTTP_SSL) + if (c->asynch && ngx_del_async_conn) { + if (c->num_async_fds) { + ngx_del_async_conn(c, NGX_DISABLE_EVENT); + c->num_async_fds--; + } + } +#endif + 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; + + 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) +{ + int tcp_nodelay; + 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 && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay"); + + tcp_nodelay = 1; + + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) == -1) + { + ngx_connection_error(c, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + c->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + } + + 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; + return; + } + + ngx_http_upstream_ssl_handshake(c); +} + + +static void +ngx_http_upstream_ssl_handshake(ngx_connection_t *c) +{ + long rc; + ngx_http_request_t *r; + ngx_http_upstream_t *u; + + r = c->data; + u = r->upstream; + + ngx_http_set_log_request(c->log, r); + + 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; + + c = r->connection; + + ngx_http_upstream_send_request(r, u, 1); + + ngx_http_run_posted_requests(c); + return; + } + +failed: + + c = r->connection; + + ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); + + ngx_http_run_posted_requests(c); +} + + +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, 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; + } + + /* 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) { + 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; + } + + return; + } + + /* rc == NGX_OK */ + + u->request_body_sent = 1; + + if (c->write->timer_set) { + ngx_del_timer(c->write); + } + + if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) { + if (ngx_tcp_push(c->fd) == NGX_ERROR) { + 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; + } + + 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; + } + + 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) +{ + int tcp_nodelay; + 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; + } + + return ngx_output_chain(&u->output, out); + } + + 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 && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay"); + + tcp_nodelay = 1; + + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) == -1) + { + ngx_connection_error(c, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + return NGX_ERROR; + } + + c->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + + 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_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->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_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->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; + } + + if (!r->subrequest_in_memory) { + ngx_http_upstream_send_response(r, u); + return; + } + + /* subrequest content in memory */ + + if (u->input_filter == NULL) { + u->input_filter_init = ngx_http_upstream_non_buffered_filter_init; + u->input_filter = ngx_http_upstream_non_buffered_filter; + u->input_filter_ctx = r; + } + + if (u->input_filter_init(u->input_filter_ctx) == NGX_ERROR) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + n = u->buffer.last - u->buffer.pos; + + if (n) { + u->buffer.last = u->buffer.pos; + + u->state->response_length += n; + + if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + } + + if (u->length == 0) { + ngx_http_upstream_finalize_request(r, u, 0); + return; + } + + u->read_event_handler = ngx_http_upstream_process_body_in_memory; + + ngx_http_upstream_process_body_in_memory(r, u); +} + + +static ngx_int_t +ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u) +{ + ngx_uint_t status; + 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; + } + + if (u->peer.tries > 1 && (u->conf->next_upstream & un->mask)) { + 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)) + { + ngx_int_t rc; + + rc = u->reinit_request(r); + + if (rc == NGX_OK) { + u->cache_status = NGX_HTTP_CACHE_STALE; + rc = ngx_http_upstream_cache_send(r, u); + } + + 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; + 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; + + 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 (valid == 0) { + 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) { + valid = now + valid; + } + } + + if (valid) { + r->cache->valid_sec = valid; + 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) { + time_t valid; + + valid = ngx_http_file_cache_valid(u->conf->cache_valid, status); + + if (valid) { + r->cache->valid_sec = ngx_time() + 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 (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 void +ngx_http_upstream_process_body_in_memory(ngx_http_request_t *r, + ngx_http_upstream_t *u) +{ + size_t size; + ssize_t n; + ngx_buf_t *b; + ngx_event_t *rev; + ngx_connection_t *c; + + c = u->peer.connection; + rev = c->read; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http upstream process body on memory"); + + if (rev->timedout) { + ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out"); + ngx_http_upstream_finalize_request(r, u, NGX_HTTP_GATEWAY_TIME_OUT); + return; + } + + b = &u->buffer; + + for ( ;; ) { + + size = b->end - b->last; + + if (size == 0) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "upstream buffer is too small to read response"); + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + n = c->recv(c, b->last, size); + + if (n == NGX_AGAIN) { + break; + } + + if (n == 0 || n == NGX_ERROR) { + ngx_http_upstream_finalize_request(r, u, n); + return; + } + + u->state->response_length += n; + + if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + if (!rev->ready) { + break; + } + } + + if (u->length == 0) { + ngx_http_upstream_finalize_request(r, u, 0); + return; + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + if (rev->active) { + ngx_add_timer(rev, u->conf->read_timeout); + + } else if (rev->timer_set) { + ngx_del_timer(rev); + } +} + + +static void +ngx_http_upstream_send_response(ngx_http_request_t *r, ngx_http_upstream_t *u) +{ + int tcp_nodelay; + 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) { + 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) { + 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 (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 && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay"); + + tcp_nodelay = 1; + + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) == -1) + { + ngx_connection_error(c, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + c->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + + 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->temp_path) { + p->temp_file->path = r->cache->file_cache->temp_path; + } +#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) +{ + int tcp_nodelay; + 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 */ + + 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) { + tcp_nodelay = 1; + + if (c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay"); + + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) == -1) + { + ngx_connection_error(c, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + c->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + + if (u->peer.connection->tcp_nodelay == NGX_TCP_NODELAY_UNSET) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, u->peer.connection->log, 0, + "tcp_nodelay"); + + if (setsockopt(u->peer.connection->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) == -1) + { + ngx_connection_error(u->peer.connection, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + u->peer.connection->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + } + + 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; + + 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) { + 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->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; + + 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) { + + if (wev->delayed) { + + wev->timedout = 0; + wev->delayed = 0; + + if (!wev->ready) { + ngx_add_timer(wev, p->send_timeout); + + 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, wev->write) == NGX_ABORT) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + } else { + 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) { + + if (rev->delayed) { + + rev->timedout = 0; + rev->delayed = 0; + + if (!rev->ready) { + ngx_add_timer(rev, p->read_timeout); + + 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; + } + + } else { + 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: + 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_403: + status = NGX_HTTP_FORBIDDEN; + break; + + case NGX_HTTP_UPSTREAM_FT_HTTP_404: + status = NGX_HTTP_NOT_FOUND; + 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)) + { + ngx_int_t rc; + + rc = u->reinit_request(r); + + if (rc == NGX_OK) { + u->cache_status = NGX_HTTP_CACHE_STALE; + rc = ngx_http_upstream_cache_send(r, u); + } + + 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->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; + } + } + } + + if (u->pipe) + ngx_http_file_cache_free(r->cache, u->pipe->temp_file); + } + +#endif + + if (r->subrequest_in_memory + && u->headers_in.status_n >= NGX_HTTP_SPECIAL_RESPONSE) + { + u->buffer.last = u->buffer.pos; + } + + 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 + || (u->pipe && u->pipe->downstream_error)) + { + ngx_http_finalize_request(r, rc); + return; + } + + flush = 0; + + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + rc = NGX_ERROR; + flush = 1; + } + + if (r->header_only) { + ngx_http_finalize_request(r, rc); + return; + } + + if (rc == 0) { + 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; + +#if (NGX_HTTP_CACHE) + + if (u->cacheable) { + u->headers_in.last_modified_time = ngx_parse_http_time(h->value.data, + h->value.len); + } + +#endif + + 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 == NULL) { + return NGX_OK; + } + + 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; + } +#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; + } + } + + ph = ngx_array_push(pa); + if (ph == NULL) { + return NGX_ERROR; + } + + ho = ngx_list_push(&r->headers_out.headers); + if (ho == NULL) { + return NGX_ERROR; + } + + *ho = *h; + *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; + +#if (NGX_HTTP_CACHE) + + if (r->upstream->cacheable) { + r->headers_out.last_modified_time = + r->upstream->headers_in.last_modified_time; + } + +#endif + + 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 the handling + * the local redirects without a host name by 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 ( ;; ) { + 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; +} + + +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); +} + + +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) + +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_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_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_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_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_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_WARN, 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_WARN, 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 (uscfp[i]->default_port && u->default_port + && uscfp[i]->default_port != u->default_port) + { + continue; + } + + if (flags & NGX_HTTP_UPSTREAM_CREATE) { + uscfp[i]->flags = flags; + } + + 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->default_port = u->default_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 (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; + + return NGX_CONF_OK; + } + + local->addr = ngx_palloc(cf->pool, sizeof(ngx_addr_t)); + if (local->addr == NULL) { + return NGX_CONF_ERROR; + } + + rc = ngx_parse_addr(cf->pool, local->addr, value[1].data, value[1].len); + + switch (rc) { + case NGX_OK: + local->addr->name = value[1]; + return NGX_CONF_OK; + + case NGX_DECLINED: + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid address \"%V\"", &value[1]); + /* fall through */ + + default: + return NGX_CONF_ERROR; + } +} + + +static ngx_addr_t * +ngx_http_upstream_get_local(ngx_http_request_t *r, + ngx_http_upstream_local_t *local) +{ + ngx_int_t rc; + ngx_str_t val; + ngx_addr_t *addr; + + if (local == NULL) { + return NULL; + } + + if (local->value == NULL) { + return local->addr; + } + + if (ngx_http_complex_value(r, local->value, &val) != NGX_OK) { + return NULL; + } + + if (val.len == 0) { + return NULL; + } + + addr = ngx_palloc(r->pool, sizeof(ngx_addr_t)); + if (addr == NULL) { + return NULL; + } + + rc = ngx_parse_addr(r->pool, addr, val.data, val.len); + + switch (rc) { + case NGX_OK: + addr->name = val; + return addr; + + case NGX_DECLINED: + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "invalid local address \"%V\"", &val); + /* fall through */ + + default: + return NULL; + } +} + + +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 +#if (NGX_HTTP_CACHE) + && ((conf->cache == 0) == (prev->cache == 0)) +#endif + ) + { + 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; + + return ngx_hash_init(hash, hide_headers.elts, hide_headers.nelts); +} + + +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/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h new file mode 100644 index 0000000..7595dcf --- /dev/null +++ b/src/http/ngx_http_upstream.h @@ -0,0 +1,419 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_UPSTREAM_H_INCLUDED_ +#define _NGX_HTTP_UPSTREAM_H_INCLUDED_ + + +#include +#include +#include +#include +#include +#include + + +#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_UPDATING 0x00000400 +#define NGX_HTTP_UPSTREAM_FT_BUSY_LOCK 0x00000800 +#define NGX_HTTP_UPSTREAM_FT_MAX_WAITING 0x00001000 +#define NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT 0x00002000 +#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) + +#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_msec_t bl_time; + ngx_uint_t bl_state; + + ngx_uint_t status; + ngx_msec_t response_time; + ngx_msec_t connect_time; + ngx_msec_t header_time; + off_t response_length; + + 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_fails; + time_t fail_timeout; + + unsigned down:1; + unsigned backup:1; +} 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 + + +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; + in_port_t default_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; +} 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 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; + + 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_array_t *cache_valid; + ngx_array_t *cache_bypass; + 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; + +#if (NGX_HTTP_SSL) + 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_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_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_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; +#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_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 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_cookie_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +ngx_int_t ngx_http_upstream_header_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); + +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/src/http/ngx_http_upstream_round_robin.c b/src/http/ngx_http_upstream_round_robin.c new file mode 100644 index 0000000..8342dc8 --- /dev/null +++ b/src/http/ngx_http_upstream_round_robin.c @@ -0,0 +1,843 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#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_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_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_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; + + 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->host; + peer[0].weight = 1; + peer[0].effective_weight = 1; + peer[0].current_weight = 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); + + switch (sockaddr->sa_family) { +#if (NGX_HAVE_INET6) + case AF_INET6: + ((struct sockaddr_in6 *) sockaddr)->sin6_port = htons(ur->port); + break; +#endif + default: /* AF_INET */ + ((struct sockaddr_in *) sockaddr)->sin_port = htons(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_fails = 1; + peer[i].fail_timeout = 10; + *peerp = &peer[i]; + peerp = &peer[i].next; + } + } + + rrp->peers = peers; + rrp->current = NULL; + + 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; + } + + 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); + } + + /* all peers failed, mark them as live for quick recovery */ + + for (peer = peers->peer; peer; peer = peer->next) { + peer->fails = 0; + } + + 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; + } + + 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/src/http/ngx_http_upstream_round_robin.h b/src/http/ngx_http_upstream_round_robin.h new file mode 100644 index 0000000..f2c573f --- /dev/null +++ b/src/http/ngx_http_upstream_round_robin.h @@ -0,0 +1,149 @@ + +/* + * 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 +#include +#include + + +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 fails; + time_t accessed; + time_t checked; + + ngx_uint_t max_fails; + time_t fail_timeout; + + ngx_uint_t down; /* unsigned down:1; */ + +#if (NGX_HTTP_SSL) + void *ssl_session; + int ssl_session_len; +#endif + + ngx_http_upstream_rr_peer_t *next; + +#if (NGX_HTTP_UPSTREAM_ZONE) + ngx_atomic_t lock; +#endif +}; + + +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_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/src/http/ngx_http_variables.c b/src/http/ngx_http_variables.c new file mode 100644 index 0000000..f8271ab --- /dev/null +++ b/src/http/ngx_http_variables.c @@ -0,0 +1,2633 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +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_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_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_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("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("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("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_null_string, NULL, NULL, 0, 0, 0 } +}; + + +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"); + + +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; + } + + 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; + } + + 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; +} + + +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 (v[index].get_handler(r, &r->variables[index], v[index].data) + == NGX_OK) + { + if (v[index].flags & NGX_HTTP_VAR_NOCACHEABLE) { + r->variables[index].no_cacheable = 1; + } + + return &r->variables[index]; + } + + 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) +{ + 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); + + } else { + + vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t)); + + if (vv && v->get_handler(r, vv, v->data) == NGX_OK) { + return vv; + } + + return NULL; + } + } + + vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t)); + if (vv == NULL) { + return NULL; + } + + if (name->len >= 5 && ngx_strncmp(name->data, "http_", 5) == 0) { + + if (ngx_http_variable_unknown_header_in(r, vv, (uintptr_t) name) + == NGX_OK) + { + return vv; + } + + return NULL; + } + + if (name->len >= 10 && ngx_strncmp(name->data, "sent_http_", 10) == 0) { + + if (ngx_http_variable_unknown_header_out(r, vv, (uintptr_t) name) + == NGX_OK) + { + return vv; + } + + return NULL; + } + + if (name->len >= 14 && ngx_strncmp(name->data, "upstream_http_", 14) == 0) { + + if (ngx_http_upstream_header_variable(r, vv, (uintptr_t) name) + == NGX_OK) + { + return vv; + } + + return NULL; + } + + if (name->len >= 7 && ngx_strncmp(name->data, "cookie_", 7) == 0) { + + if (ngx_http_variable_cookie(r, vv, (uintptr_t) name) == NGX_OK) { + return vv; + } + + return NULL; + } + + if (name->len >= 16 + && ngx_strncmp(name->data, "upstream_cookie_", 16) == 0) + { + + if (ngx_http_upstream_cookie_variable(r, vv, (uintptr_t) name) + == NGX_OK) + { + return vv; + } + + return NULL; + } + + if (name->len >= 4 && ngx_strncmp(name->data, "arg_", 4) == 0) { + + if (ngx_http_variable_argument(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); +} + + +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 (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 + + 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; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + + 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; + } + + switch (r->connection->sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) r->connection->sockaddr; + port = ntohs(sin6->sin6_port); + break; +#endif + +#if (NGX_HAVE_UNIX_DOMAIN) + case AF_UNIX: + port = 0; + break; +#endif + + default: /* AF_INET */ + sin = (struct sockaddr_in *) r->connection->sockaddr; + port = ntohs(sin->sin_port); + break; + } + + 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_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; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + + 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; + } + + switch (r->connection->local_sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) r->connection->local_sockaddr; + port = ntohs(sin6->sin6_port); + break; +#endif + +#if (NGX_HAVE_UNIX_DOMAIN) + case AF_UNIX: + port = 0; + break; +#endif + + default: /* AF_INET */ + sin = (struct sockaddr_in *) r->connection->local_sockaddr; + port = ntohs(sin->sin_port); + break; + } + + 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) +{ + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + if (r->args.len == 0) { + v->len = 0; + v->data = NULL; + return NGX_OK; + } + + v->len = 1; + 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->len = 0; + 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_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_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_int_t rc; + 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; + } + + for (cv = ngx_http_core_variables; cv->name.len; cv++) { + v = ngx_palloc(cf->pool, sizeof(ngx_http_variable_t)); + if (v == NULL) { + return NGX_ERROR; + } + + *v = *cv; + + rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v, + NGX_HASH_READONLY_KEY); + + if (rc == NGX_OK) { + continue; + } + + if (rc == NGX_BUSY) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "conflicting variable name \"%V\"", &v->name); + } + + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_variables_init_vars(ngx_conf_t *cf) +{ + ngx_uint_t i, n; + ngx_hash_key_t *key; + ngx_hash_init_t hash; + ngx_http_variable_t *v, *av; + 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; + 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) { + break; + } + + goto next; + } + } + + if (v[i].name.len >= 5 + && ngx_strncmp(v[i].name.data, "http_", 5) == 0) + { + v[i].get_handler = ngx_http_variable_unknown_header_in; + v[i].data = (uintptr_t) &v[i].name; + + continue; + } + + if (v[i].name.len >= 10 + && ngx_strncmp(v[i].name.data, "sent_http_", 10) == 0) + { + v[i].get_handler = ngx_http_variable_unknown_header_out; + v[i].data = (uintptr_t) &v[i].name; + + continue; + } + + if (v[i].name.len >= 14 + && ngx_strncmp(v[i].name.data, "upstream_http_", 14) == 0) + { + v[i].get_handler = ngx_http_upstream_header_variable; + v[i].data = (uintptr_t) &v[i].name; + v[i].flags = NGX_HTTP_VAR_NOCACHEABLE; + + continue; + } + + if (v[i].name.len >= 7 + && ngx_strncmp(v[i].name.data, "cookie_", 7) == 0) + { + v[i].get_handler = ngx_http_variable_cookie; + v[i].data = (uintptr_t) &v[i].name; + + continue; + } + + if (v[i].name.len >= 16 + && ngx_strncmp(v[i].name.data, "upstream_cookie_", 16) == 0) + { + v[i].get_handler = ngx_http_upstream_cookie_variable; + v[i].data = (uintptr_t) &v[i].name; + v[i].flags = NGX_HTTP_VAR_NOCACHEABLE; + + continue; + } + + if (v[i].name.len >= 4 + && ngx_strncmp(v[i].name.data, "arg_", 4) == 0) + { + v[i].get_handler = ngx_http_variable_argument; + v[i].data = (uintptr_t) &v[i].name; + v[i].flags = NGX_HTTP_VAR_NOCACHEABLE; + + continue; + } + + 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/src/http/ngx_http_variables.h b/src/http/ngx_http_variables.h new file mode 100644 index 0000000..829fab3 --- /dev/null +++ b/src/http/ngx_http_variables.h @@ -0,0 +1,112 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_VARIABLES_H_INCLUDED_ +#define _NGX_HTTP_VARIABLES_H_INCLUDED_ + + +#include +#include +#include + + +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 + + +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; +}; + + +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/src/http/ngx_http_write_filter_module.c b/src/http/ngx_http_write_filter_module.c new file mode 100644 index 0000000..0036231 --- /dev/null +++ b/src/http/ngx_http_write_filter_module.c @@ -0,0 +1,327 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c new file mode 100644 index 0000000..421ec6e --- /dev/null +++ b/src/http/v2/ngx_http_v2.c @@ -0,0 +1,4456 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include +#include + + +/* 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_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_STREAM_ID_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_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_DEFAULT_FRAME_SIZE (1 << 14) + +#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); +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, + ngx_uint_t ack); +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_http_v2_header_t *header); +static ngx_int_t ngx_http_v2_parse_method(ngx_http_request_t *r, + ngx_http_v2_header_t *header); +static ngx_int_t ngx_http_v2_parse_scheme(ngx_http_request_t *r, + ngx_http_v2_header_t *header); +static ngx_int_t ngx_http_v2_parse_authority(ngx_http_request_t *r, + ngx_http_v2_header_t *header); +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 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_state_headers, + ngx_http_v2_state_priority, + ngx_http_v2_state_rst_stream, + ngx_http_v2_state_settings, + ngx_http_v2_state_push_promise, + ngx_http_v2_state_ping, + ngx_http_v2_state_goaway, + ngx_http_v2_state_window_update, + ngx_http_v2_state_continuation +}; + +#define NGX_HTTP_V2_FRAME_STATES \ + (sizeof(ngx_http_v2_frame_states) / sizeof(ngx_http_v2_handler_pt)) + + +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; + + h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); + + 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, 0) == 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; + 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)) { + 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) { + 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 + && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) + { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay"); + + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) + == -1) + { +#if (NGX_SOLARIS) + /* Solaris returns EINVAL if a socket has been shut down */ + c->log_error = NGX_ERROR_IGNORE_EINVAL; +#endif + + ngx_connection_error(c, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + + c->log_error = NGX_ERROR_INFO; + goto error; + } + + c->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + + 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) { + 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->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, + "process 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_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 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) +{ + ngx_http_v2_node_t *node; + ngx_http_v2_stream_t *stream; + + 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++; + h2c->state.length--; + + if (h2c->state.padding > h2c->state.length) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent padded DATA frame " + "with incorrect length: %uz, padding: %uz", + h2c->state.length, h2c->state.padding); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + h2c->state.length -= h2c->state.padding; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 DATA frame"); + + if (h2c->state.length > 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", + h2c->state.length, h2c->recv_window); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_FLOW_CTRL_ERROR); + } + + h2c->recv_window -= h2c->state.length; + + 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 (h2c->state.length > 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, h2c->state.length, 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 -= h2c->state.length; + + 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_SIZE_ERROR); + } + + h2c->state.length -= h2c->state.padding; + } + + depend = 0; + excl = 0; + weight = 16; + + 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 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); + 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); + } + + 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 hpack %s string length: %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) { + return ngx_http_v2_state_header_complete(h2c, pos, end); + } + + if (rc == NGX_ABORT) { + goto error; + } + + if (rc == NGX_DECLINED) { + 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; + } + + 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); + } + + return ngx_http_v2_state_header_complete(h2c, pos, end); + } + + 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 http header: \"%V: %V\"", &h->key, &h->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 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_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_http_v2_send_settings(h2c, 1); + + 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) +{ + ngx_uint_t id, value; + + 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]); + + 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); + } + + if (ngx_http_v2_adjust_windows(h2c, value - h2c->init_window) + != NGX_OK) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + h2c->init_window = value; + 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; + + default: + break; + } + + pos += NGX_HTTP_V2_SETTINGS_PARAM_SIZE; + } + + 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_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 PING frame, flags: %ud", h2c->state.flags); + + 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 (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; +} + + +static ngx_int_t +ngx_http_v2_send_settings(ngx_http_v2_connection_t *h2c, ngx_uint_t ack) +{ + 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_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 send SETTINGS frame ack:%ui", ack); + + 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 = ack ? 0 : (sizeof(uint16_t) + sizeof(uint32_t)) * 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++ = ack ? NGX_HTTP_V2_ACK_FLAG : NGX_HTTP_V2_NO_FLAG; + + buf->last = ngx_http_v2_write_sid(buf->last, 0); + + if (!ack) { + 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:%uz", + 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_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 send GOAWAY frame, status:%uz", 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 { + 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; + } + +#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_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; + 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; + + 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; + } + + switch (ch) { + case '\0': + case LF: + case CR: + case ':': + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \"%V\"", + &header->name); + + return NGX_ERROR; + } + + if (ch >= 'A' && ch <= 'Z') { + 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]; + + switch (ch) { + case '\0': + case LF: + case 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); + } + + break; + + case 6: + if (ngx_memcmp(header->name.data, "method", sizeof("method") - 1) + == 0) + { + return ngx_http_v2_parse_method(r, header); + } + + if (ngx_memcmp(header->name.data, "scheme", sizeof("scheme") - 1) + == 0) + { + return ngx_http_v2_parse_scheme(r, header); + } + + break; + + case 9: + if (ngx_memcmp(header->name.data, "authority", sizeof("authority") - 1) + == 0) + { + return ngx_http_v2_parse_authority(r, header); + } + + 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_http_v2_header_t *header) +{ + if (r->unparsed_uri.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :path header"); + + return NGX_DECLINED; + } + + if (header->value.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :path header"); + + return NGX_DECLINED; + } + + r->uri_start = header->value.data; + r->uri_end = header->value.data + header->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\"", + &header->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_http_v2_header_t *header) +{ + 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 (header->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 = header->value.len; + r->method_name.data = header->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 != '_') { + 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_http_v2_header_t *header) +{ + if (r->schema_start) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :schema header"); + + return NGX_DECLINED; + } + + if (header->value.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :schema header"); + + return NGX_DECLINED; + } + + r->schema_start = header->value.data; + r->schema_end = header->value.data + header->value.len; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_parse_authority(ngx_http_request_t *r, ngx_http_v2_header_t *header) +{ + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t host = ngx_string("host"); + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->hash = ngx_hash_key(host.data, host.len); + + h->key.len = host.len; + h->key.data = host.data; + + h->value.len = header->value.len; + h->value.data = header->value.data; + + h->lowcase_key = host.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) { + return NGX_ERROR; + } + + if (hh->handler(r, h, hh->offset) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_host() + */ + 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->unparsed_uri.len == 0) + { + 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 http 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_key(cookie.data, cookie.len); + + 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); +} + + +ngx_int_t +ngx_http_v2_read_request_body(ngx_http_request_t *r, + ngx_http_client_body_handler_pt post_handler) +{ + 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; + + if (stream->skip_data) { + r->request_body_no_buffering = 0; + post_handler(r); + return NGX_OK; + } + + rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t)); + if (rb == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + /* + * set by ngx_pcalloc(): + * + * rb->bufs = NULL; + * rb->buf = NULL; + * rb->received = 0; + * rb->free = NULL; + * rb->busy = NULL; + */ + + rb->rest = 1; + rb->post_handler = post_handler; + + r->request_body = rb; + + 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 { + if (stream->preread) { + /* enforce writing preread buffer to file */ + r->request_body_in_file_only = 1; + } + + 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; + } + + 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; + + } 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_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_debug3(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 close stream %ui, queued %ui, processing %ui", + node->id, stream->queued, h2c->processing); + + 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; + } + + 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; + + h2c->processing--; + + if (h2c->processing || 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"); + + rev->handler = ngx_http_v2_read_handler; + + if (rev->ready) { + ngx_http_v2_read_handler(rev); + return; + } + + c = rev->data; + h2c = c->data; + + 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 + + c->destroyed = 0; + ngx_reusable_connection(c, 0); + + h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + + h2c->pool = ngx_create_pool(h2scf->pool_size, h2c->connection->log); + if (h2c->pool == NULL) { + ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + 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) { + 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; + + } else { + ev = fc->read; + } + + ev->eof = 1; + ev->handler(ev); + } + } + + h2c->blocked = 0; + + if (h2c->processing) { + 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/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h new file mode 100644 index 0000000..2f519e1 --- /dev/null +++ b/src/http/v2/ngx_http_v2.h @@ -0,0 +1,342 @@ +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#ifndef _NGX_HTTP_V2_H_INCLUDED_ +#define _NGX_HTTP_V2_H_INCLUDED_ + + +#include +#include +#include + + +#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_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_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 + + +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; + + 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; + + unsigned closed_nodes:8; + unsigned settings_ack:1; + unsigned blocked:1; + unsigned goaway: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; + + size_t header_limit; + + 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; +} + + +void ngx_http_v2_init(ngx_event_t *rev); +void ngx_http_v2_request_headers_init(void); + +ngx_int_t ngx_http_v2_read_request_body(ngx_http_request_t *r, + ngx_http_client_body_handler_pt post_handler); +ngx_int_t ngx_http_v2_read_unbuffered_request_body(ngx_http_request_t *r); + +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_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) \ + ((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 + +#endif /* _NGX_HTTP_V2_H_INCLUDED_ */ diff --git a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c new file mode 100644 index 0000000..fa53070 --- /dev/null +++ b/src/http/v2/ngx_http_v2_filter_module.c @@ -0,0 +1,1425 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include +#include +#include + + +/* + * 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_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_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_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_VARY_INDEX 59 + + +static u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, + u_char *tmp, ngx_uint_t lower); +static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, + ngx_uint_t value); +static ngx_http_v2_out_frame_t *ngx_http_v2_create_headers_frame( + ngx_http_request_t *r, u_char *pos, u_char *end); + +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_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; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_connection_t *fc; + ngx_http_cleanup_t *cln; + ngx_http_v2_out_frame_t *frame; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + 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)]; + + if (!r->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; + } + } + + 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) { + len += 1 + (clcf->server_tokens ? nginx_ver_len : 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] == '/') { + 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; + } + } + + switch (fc->local_sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) fc->local_sockaddr; + port = ntohs(sin6->sin6_port); + break; +#endif +#if (NGX_HAVE_UNIX_DOMAIN) + case AF_UNIX: + port = 0; + break; +#endif + default: /* AF_INET */ + sin = (struct sockaddr_in *) fc->local_sockaddr; + port = ntohs(sin->sin_port); + break; + } + + 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; + + 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) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 output header: \"server: %s\"", + clcf->server_tokens ? NGINX_VER : "nginx"); + + *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX); + + if (clcf->server_tokens) { + 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 { + 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); + } + + frame = ngx_http_v2_create_headers_frame(r, start, pos); + if (frame == NULL) { + return NGX_ERROR; + } + + ngx_http_v2_queue_blocked_frame(r->stream->connection, frame); + + cln = ngx_http_cleanup_add(r, 0); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_http_v2_filter_cleanup; + cln->data = r->stream; + + r->stream->queued = 1; + + fc->send_chain = ngx_http_v2_send_chain; + fc->need_last_buf = 1; + + return ngx_http_v2_filter_send(fc, r->stream); +} + + +static u_char * +ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp, + ngx_uint_t lower) +{ + size_t hlen; + + hlen = ngx_http_v2_huff_encode(src, len, tmp, lower); + + if (hlen > 0) { + *dst = NGX_HTTP_V2_ENCODE_HUFF; + dst = ngx_http_v2_write_int(dst, ngx_http_v2_prefix(7), hlen); + return ngx_cpymem(dst, tmp, hlen); + } + + *dst = NGX_HTTP_V2_ENCODE_RAW; + dst = ngx_http_v2_write_int(dst, ngx_http_v2_prefix(7), len); + + if (lower) { + ngx_strlow(dst, src, len); + return dst + len; + } + + return ngx_cpymem(dst, src, len); +} + + +static u_char * +ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value) +{ + if (value < prefix) { + *pos++ |= value; + return pos; + } + + *pos++ |= prefix; + value -= prefix; + + while (value >= 128) { + *pos++ = value % 128 + 128; + value /= 128; + } + + *pos++ = (u_char) value; + + return pos; +} + + +static ngx_http_v2_out_frame_t * +ngx_http_v2_create_headers_frame(ngx_http_request_t *r, u_char *pos, + u_char *end) +{ + 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 = r->header_only; + + ll = &frame->first; + + type = NGX_HTTP_V2_HEADERS_FRAME; + flags = r->header_only ? 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) { + type = NGX_HTTP_V2_CONTINUATION_FRAME; + flags = NGX_HTTP_V2_NO_FLAG; + continue; + } + + b->last_buf = r->header_only; + cl->next = NULL; + frame->last = cl; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http2:%ui create HEADERS frame %p: len:%uz", + stream->node->id, frame, frame->length); + + return frame; + } +} + + +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; + 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) { + + 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; + +#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; + } + + 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) { + 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) { + 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_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_inline ngx_int_t +ngx_http_v2_flow_control(ngx_http_v2_connection_t *h2c, + ngx_http_v2_stream_t *stream) +{ + 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_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); + + 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/src/http/v2/ngx_http_v2_huff_decode.c b/src/http/v2/ngx_http_v2_huff_decode.c new file mode 100644 index 0000000..49ca576 --- /dev/null +++ b/src/http/v2/ngx_http_v2_huff_decode.c @@ -0,0 +1,2714 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include + + +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/src/http/v2/ngx_http_v2_huff_encode.c b/src/http/v2/ngx_http_v2_huff_encode.c new file mode 100644 index 0000000..3f822cd --- /dev/null +++ b/src/http/v2/ngx_http_v2_huff_encode.c @@ -0,0 +1,254 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + * Copyright (C) 2015 Vlad Krasnov + */ + + +#include +#include +#include + + +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/src/http/v2/ngx_http_v2_module.c b/src/http/v2/ngx_http_v2_module.c new file mode 100644 index 0000000..b7d99e0 --- /dev/null +++ b/src/http/v2/ngx_http_v2_module.c @@ -0,0 +1,500 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include +#include + + +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_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_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("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_null_string, NULL, NULL, 0, 0, 0 } +}; + + +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->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_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; + } + + h2lcf->chunk_size = NGX_CONF_UNSET_SIZE; + + 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); + + 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/src/http/v2/ngx_http_v2_module.h b/src/http/v2/ngx_http_v2_module.h new file mode 100644 index 0000000..91f97c2 --- /dev/null +++ b/src/http/v2/ngx_http_v2_module.h @@ -0,0 +1,43 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#ifndef _NGX_HTTP_V2_MODULE_H_INCLUDED_ +#define _NGX_HTTP_V2_MODULE_H_INCLUDED_ + + +#include +#include +#include + + +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; + 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_http_v2_loc_conf_t; + + +extern ngx_module_t ngx_http_v2_module; + + +#endif /* _NGX_HTTP_V2_MODULE_H_INCLUDED_ */ diff --git a/src/http/v2/ngx_http_v2_table.c b/src/http/v2/ngx_http_v2_table.c new file mode 100644 index 0000000..a73748a --- /dev/null +++ b/src/http/v2/ngx_http_v2_table.c @@ -0,0 +1,349 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include + + +#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_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 ? "header" : "header name", 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 add header to hpack table: \"%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 hpack 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/src/mail/ngx_mail.c b/src/mail/ngx_mail.c new file mode 100644 index 0000000..6ad5a67 --- /dev/null +++ b/src/mail/ngx_mail.c @@ -0,0 +1,534 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +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; + struct sockaddr_in *sin; + ngx_mail_conf_port_t *port; + ngx_mail_conf_addr_t *addr; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + + sa = &listen->u.sockaddr; + + switch (sa->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = &listen->u.sockaddr_in6; + p = sin6->sin6_port; + break; +#endif + +#if (NGX_HAVE_UNIX_DOMAIN) + case AF_UNIX: + p = 0; + break; +#endif + + default: /* AF_INET */ + sin = &listen->u.sockaddr_in; + p = sin->sin_port; + break; + } + + 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.u.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->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 && defined IPV6_V6ONLY) + 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.u.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.u.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.u.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.u.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/src/mail/ngx_mail.h b/src/mail/ngx_mail.h new file mode 100644 index 0000000..bfbf768 --- /dev/null +++ b/src/mail/ngx_mail.h @@ -0,0 +1,416 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_MAIL_H_INCLUDED_ +#define _NGX_MAIL_H_INCLUDED_ + + +#include +#include +#include +#include + +#if (NGX_MAIL_SSL) +#include +#endif + + + +typedef struct { + void **main_conf; + void **srv_conf; +} ngx_mail_conf_ctx_t; + + +typedef struct { + 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 + u_char sockaddr_data[NGX_SOCKADDRLEN]; + } u; + + socklen_t socklen; + + /* server ctx */ + ngx_mail_conf_ctx_t *ctx; + + unsigned bind:1; + unsigned wildcard:1; +#if (NGX_MAIL_SSL) + unsigned ssl:1; +#endif +#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) + 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; +} ngx_mail_listen_t; + + +typedef struct { + ngx_mail_conf_ctx_t *ctx; + ngx_str_t addr_text; +#if (NGX_MAIL_SSL) + ngx_uint_t ssl; /* unsigned ssl:1; */ +#endif +} 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_int_t line; + + ngx_resolver_t *resolver; + ngx_log_t *error_log; + + /* server ctx */ + ngx_mail_conf_ctx_t *ctx; +} 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_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_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_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_NONE 5 + + +#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_NONE_ENABLED 0x0020 + + +#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_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/src/mail/ngx_mail_auth_http_module.c b/src/mail/ngx_mail_auth_http_module.c new file mode 100644 index 0000000..39f9b17 --- /dev/null +++ b/src/mail/ngx_mail_auth_http_module.c @@ -0,0 +1,1590 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include +#include + + +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("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; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + + 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; + } + + switch (peer->sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) peer->sockaddr; + sin6->sin6_port = htons((in_port_t) port); + break; +#endif + + default: /* AF_INET */ + sin = (struct sockaddr_in *) peer->sockaddr; + sin->sin_port = htons((in_port_t) port); + break; + } + + 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/src/mail/ngx_mail_core_module.c b/src/mail/ngx_mail_core_module.c new file mode 100644 index 0000000..8ea8ea9 --- /dev/null +++ b/src/mail/ngx_mail_core_module.c @@ -0,0 +1,679 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +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; + + 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; + + size_t len, off; + in_port_t port; + ngx_str_t *value; + ngx_url_t u; + ngx_uint_t i, m; + struct sockaddr *sa; + ngx_mail_listen_t *ls; + ngx_mail_module_t *module; + struct sockaddr_in *sin; + ngx_mail_core_main_conf_t *cmcf; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + + 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++) { + + sa = &ls[i].u.sockaddr; + + if (sa->sa_family != u.family) { + continue; + } + + switch (sa->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + off = offsetof(struct sockaddr_in6, sin6_addr); + len = 16; + sin6 = &ls[i].u.sockaddr_in6; + port = ntohs(sin6->sin6_port); + break; +#endif + +#if (NGX_HAVE_UNIX_DOMAIN) + case AF_UNIX: + off = offsetof(struct sockaddr_un, sun_path); + len = sizeof(((struct sockaddr_un *) sa)->sun_path); + port = 0; + break; +#endif + + default: /* AF_INET */ + off = offsetof(struct sockaddr_in, sin_addr); + len = 4; + sin = &ls[i].u.sockaddr_in; + port = ntohs(sin->sin_port); + break; + } + + if (ngx_memcmp(ls[i].u.sockaddr_data + off, u.sockaddr + off, len) + != 0) + { + continue; + } + + if (port != u.port) { + 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->u.sockaddr, u.sockaddr, u.socklen); + + ls->socklen = u.socklen; + ls->backlog = NGX_LISTEN_BACKLOG; + ls->wildcard = u.wildcard; + ls->ctx = cf->ctx; + +#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) + 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, "ipv6only=o", 10) == 0) { +#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) + u_char buf[NGX_SOCKADDR_STRLEN]; + + sa = &ls->u.sockaddr; + + if (sa->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(sa, 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/src/mail/ngx_mail_handler.c b/src/mail/ngx_mail_handler.c new file mode 100644 index 0000000..901bb8f --- /dev/null +++ b/src/mail/ngx_mail_handler.c @@ -0,0 +1,866 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +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_ERROR) { + 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(sslcf->ssl.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(sslcf->ssl.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; +} + + +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/src/mail/ngx_mail_imap_handler.c b/src/mail/ngx_mail_imap_handler.c new file mode 100644 index 0000000..57e2fb7 --- /dev/null +++ b/src/mail/ngx_mail_imap_handler.c @@ -0,0 +1,457 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include +#include + + +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; + } + + } 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 + + 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: + + iscf = ngx_mail_get_module_srv_conf(s, ngx_mail_imap_module); + + 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; + } + + 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/src/mail/ngx_mail_imap_module.c b/src/mail/ngx_mail_imap_module.c new file mode 100644 index 0000000..d281070 --- /dev/null +++ b/src/mail/ngx_mail_imap_module.c @@ -0,0 +1,255 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include +#include + + +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_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_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_CRAM_MD5_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_CRAM_MD5_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/src/mail/ngx_mail_imap_module.h b/src/mail/ngx_mail_imap_module.h new file mode 100644 index 0000000..131b445 --- /dev/null +++ b/src/mail/ngx_mail_imap_module.h @@ -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 +#include +#include + + +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/src/mail/ngx_mail_parse.c b/src/mail/ngx_mail_parse.c new file mode 100644 index 0000000..b158f5a --- /dev/null +++ b/src/mail/ngx_mail_parse.c @@ -0,0 +1,918 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include +#include +#include +#include + + +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 (s->args.nelts != 1) { + return NGX_MAIL_PARSE_INVALID_COMMAND; + } + + if (ngx_strncasecmp(arg[0].data, (u_char *) "CRAM-MD5", 8) == 0) { + return NGX_MAIL_AUTH_CRAM_MD5; + } + } + + return NGX_MAIL_PARSE_INVALID_COMMAND; +} diff --git a/src/mail/ngx_mail_pop3_handler.c b/src/mail/ngx_mail_pop3_handler.c new file mode 100644 index 0000000..51bc257 --- /dev/null +++ b/src/mail/ngx_mail_pop3_handler.c @@ -0,0 +1,500 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include +#include + + +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; + } + } + + 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; + } + + return rc; +} diff --git a/src/mail/ngx_mail_pop3_module.c b/src/mail/ngx_mail_pop3_module.c new file mode 100644 index 0000000..73f8531 --- /dev/null +++ b/src/mail/ngx_mail_pop3_module.c @@ -0,0 +1,266 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include +#include + + +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_null_string, 0 } +}; + + +static ngx_str_t ngx_mail_pop3_auth_plain_capability = + ngx_string("+OK methods supported:" CRLF + "LOGIN" CRLF + "PLAIN" CRLF + "." CRLF); + + +static ngx_str_t ngx_mail_pop3_auth_cram_md5_capability = + ngx_string("+OK methods supported:" CRLF + "LOGIN" CRLF + "PLAIN" CRLF + "CRAM-MD5" CRLF + "." CRLF); + + +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; + + 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_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; + } + + if (conf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED) { + size += sizeof("SASL LOGIN PLAIN CRAM-MD5" CRLF) - 1; + + } else { + size += sizeof("SASL LOGIN PLAIN" CRLF) - 1; + } + + 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; + } + + if (conf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED) { + p = ngx_cpymem(p, "SASL LOGIN PLAIN CRAM-MD5" CRLF, + sizeof("SASL LOGIN PLAIN CRAM-MD5" CRLF) - 1); + + } else { + p = ngx_cpymem(p, "SASL LOGIN PLAIN" CRLF, + sizeof("SASL LOGIN PLAIN" CRLF) - 1); + } + + *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; + + + if (conf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED) { + conf->auth_capability = ngx_mail_pop3_auth_cram_md5_capability; + + } else { + conf->auth_capability = ngx_mail_pop3_auth_plain_capability; + } + + + 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/src/mail/ngx_mail_pop3_module.h b/src/mail/ngx_mail_pop3_module.h new file mode 100644 index 0000000..86947a7 --- /dev/null +++ b/src/mail/ngx_mail_pop3_module.h @@ -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 +#include +#include + + +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/src/mail/ngx_mail_proxy_module.c b/src/mail/ngx_mail_proxy_module.c new file mode 100644 index 0000000..007284b --- /dev/null +++ b/src/mail/ngx_mail_proxy_module.c @@ -0,0 +1,1123 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include +#include + + +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->log->action = "proxying"; + + 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/src/mail/ngx_mail_smtp_handler.c b/src/mail/ngx_mail_smtp_handler.c new file mode 100644 index 0000000..81cc75f --- /dev/null +++ b/src/mail/ngx_mail_smtp_handler.c @@ -0,0 +1,857 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include +#include + + +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; + } + } + + 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; + } + + 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: + + sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module); + + 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; + } + + 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/src/mail/ngx_mail_smtp_module.c b/src/mail/ngx_mail_smtp_module.c new file mode 100644 index 0000000..d5bb51c --- /dev/null +++ b/src/mail/ngx_mail_smtp_module.c @@ -0,0 +1,309 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include +#include + + +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("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_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_CRAM_MD5_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_CRAM_MD5_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); + + p = ngx_cpymem(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/src/mail/ngx_mail_smtp_module.h b/src/mail/ngx_mail_smtp_module.h new file mode 100644 index 0000000..04ffab6 --- /dev/null +++ b/src/mail/ngx_mail_smtp_module.h @@ -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 +#include +#include +#include + + +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/src/mail/ngx_mail_ssl_module.c b/src/mail/ngx_mail_ssl_module.c new file mode 100644 index 0000000..ff5c141 --- /dev/null +++ b/src/mail/ngx_mail_ssl_module.c @@ -0,0 +1,657 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#define NGX_DEFAULT_CIPHERS "HIGH:!aNULL:!MD5" +#define NGX_DEFAULT_ECDH_CURVE "prime256v1" + + +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_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_slot, + NGX_MAIL_SRV_CONF_OFFSET, + offsetof(ngx_mail_ssl_conf_t, certificate), + NULL }, + + { ngx_string("ssl_certificate_key"), + 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, certificate_key), + 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->certificate = { 0, NULL }; + * scf->certificate_key = { 0, NULL }; + * 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->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_str_value(conf->certificate, prev->certificate, ""); + ngx_conf_merge_str_value(conf->certificate_key, prev->certificate_key, ""); + + 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->certificate.len == 0) { + 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_key.len == 0) { + 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; + } + + } else { + + if (conf->certificate.len == 0) { + return NGX_CONF_OK; + } + + if (conf->certificate_key.len == 0) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no \"ssl_certificate_key\" is defined " + "for certificate \"%V\"", + &conf->certificate); + 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_certificate(cf, &conf->ssl, &conf->certificate, + &conf->certificate_key, 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 (SSL_CTX_set_cipher_list(conf->ssl.ctx, + (const char *) conf->ciphers.data) + == 0) + { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "SSL_CTX_set_cipher_list(\"%V\") failed", + &conf->ciphers); + return NGX_CONF_ERROR; + } + + if (conf->prefer_server_ciphers) { + SSL_CTX_set_options(conf->ssl.ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); + } + +#if (OPENSSL_VERSION_NUMBER < 0x10100001L && !defined LIBRESSL_VERSION_NUMBER) + SSL_CTX_set_tmp_rsa_callback(conf->ssl.ctx, ngx_ssl_rsa512_key_callback); +#endif + + 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_WARN, 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_WARN, 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/src/mail/ngx_mail_ssl_module.h b/src/mail/ngx_mail_ssl_module.h new file mode 100644 index 0000000..296a6a2 --- /dev/null +++ b/src/mail/ngx_mail_ssl_module.h @@ -0,0 +1,63 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_MAIL_SSL_H_INCLUDED_ +#define _NGX_MAIL_SSL_H_INCLUDED_ + + +#include +#include +#include + + +#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_str_t certificate; + ngx_str_t certificate_key; + 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/src/misc/ngx_cpp_test_module.cpp b/src/misc/ngx_cpp_test_module.cpp new file mode 100644 index 0000000..5d2f08d --- /dev/null +++ b/src/misc/ngx_cpp_test_module.cpp @@ -0,0 +1,29 @@ + +// stub module to test header files' C++ compatibility + +extern "C" { + #include + #include + #include + #include + #include + + #include + + #include + #include + #include + #include +} + +// nginx header files should go before other, because they define 64-bit off_t +// #include + + +void ngx_cpp_test_handler(void *data); + +void +ngx_cpp_test_handler(void *data) +{ + return; +} diff --git a/src/misc/ngx_google_perftools_module.c b/src/misc/ngx_google_perftools_module.c new file mode 100644 index 0000000..f2f8221 --- /dev/null +++ b/src/misc/ngx_google_perftools_module.c @@ -0,0 +1,126 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + +/* + * declare Profiler interface here because + * 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/src/os/unix/ngx_alloc.c b/src/os/unix/ngx_alloc.c new file mode 100644 index 0000000..5c2f787 --- /dev/null +++ b/src/os/unix/ngx_alloc.c @@ -0,0 +1,90 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +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/src/os/unix/ngx_alloc.h b/src/os/unix/ngx_alloc.h new file mode 100644 index 0000000..655db25 --- /dev/null +++ b/src/os/unix/ngx_alloc.h @@ -0,0 +1,45 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_ALLOC_H_INCLUDED_ +#define _NGX_ALLOC_H_INCLUDED_ + + +#include +#include + + +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/src/os/unix/ngx_atomic.h b/src/os/unix/ngx_atomic.h new file mode 100644 index 0000000..74b8b7f --- /dev/null +++ b/src/os/unix/ngx_atomic.h @@ -0,0 +1,313 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_ATOMIC_H_INCLUDED_ +#define _NGX_ATOMIC_H_INCLUDED_ + + +#include +#include + + +#if (NGX_HAVE_LIBATOMIC) + +#define AO_REQUIRE_CAS +#include + +#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 + +/* "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/src/os/unix/ngx_channel.c b/src/os/unix/ngx_channel.c new file mode 100644 index 0000000..1efa066 --- /dev/null +++ b/src/os/unix/ngx_channel.c @@ -0,0 +1,253 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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/src/os/unix/ngx_channel.h b/src/os/unix/ngx_channel.h new file mode 100644 index 0000000..362cc64 --- /dev/null +++ b/src/os/unix/ngx_channel.h @@ -0,0 +1,34 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_CHANNEL_H_INCLUDED_ +#define _NGX_CHANNEL_H_INCLUDED_ + + +#include +#include +#include + + +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/src/os/unix/ngx_daemon.c b/src/os/unix/ngx_daemon.c new file mode 100644 index 0000000..1834922 --- /dev/null +++ b/src/os/unix/ngx_daemon.c @@ -0,0 +1,71 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +ngx_int_t +ngx_daemon(ngx_cycle_t *cycle) +{ + int fd; + + switch (fork()) { + case -1: + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, "fork() failed"); + return NGX_ERROR; + + case 0: + break; + + default: + ngx_destroy_pool(cycle->pool); + exit(0); + } + + ngx_pid = ngx_getpid(); + + if (setsid() == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->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, cycle->log, ngx_errno, + "open(\"/dev/null\") failed"); + return NGX_ERROR; + } + + if (dup2(fd, STDIN_FILENO) == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, "dup2(STDIN) failed"); + return NGX_ERROR; + } + + if (dup2(fd, STDOUT_FILENO) == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, "dup2(STDOUT) failed"); + return NGX_ERROR; + } + +#if 0 + if (dup2(fd, STDERR_FILENO) == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, "dup2(STDERR) failed"); + return NGX_ERROR; + } +#endif + + if (fd > STDERR_FILENO) { + if (close(fd) == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, "close() failed"); + return NGX_ERROR; + } + } + + return NGX_OK; +} diff --git a/src/os/unix/ngx_darwin.h b/src/os/unix/ngx_darwin.h new file mode 100644 index 0000000..4d01b26 --- /dev/null +++ b/src/os/unix/ngx_darwin.h @@ -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/src/os/unix/ngx_darwin_config.h b/src/os/unix/ngx_darwin_config.h new file mode 100644 index 0000000..cfe3ce2 --- /dev/null +++ b/src/os/unix/ngx_darwin_config.h @@ -0,0 +1,97 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_DARWIN_CONFIG_H_INCLUDED_ +#define _NGX_DARWIN_CONFIG_H_INCLUDED_ + + +#include +#include +#include +#include +#include +#include /* offsetof() */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* statfs() */ + +#include /* FIONBIO */ +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include /* TCP_NODELAY */ +#include +#include +#include + +#include +#include + +#include + + +#ifndef IOV_MAX +#define IOV_MAX 64 +#endif + + +#include + + +#if (NGX_HAVE_POSIX_SEM) +#include +#endif + + +#if (NGX_HAVE_POLL) +#include +#endif + + +#if (NGX_HAVE_KQUEUE) +#include +#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/src/os/unix/ngx_darwin_init.c b/src/os/unix/ngx_darwin_init.c new file mode 100644 index 0000000..a9d12a8 --- /dev/null +++ b/src/os/unix/ngx_darwin_init.c @@ -0,0 +1,197 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +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, +#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/src/os/unix/ngx_darwin_sendfile_chain.c b/src/os/unix/ngx_darwin_sendfile_chain.c new file mode 100644 index 0000000..2a76c15 --- /dev/null +++ b/src/os/unix/ngx_darwin_sendfile_chain.c @@ -0,0 +1,206 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +/* + * 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/src/os/unix/ngx_dlopen.c b/src/os/unix/ngx_dlopen.c new file mode 100644 index 0000000..a0efc69 --- /dev/null +++ b/src/os/unix/ngx_dlopen.c @@ -0,0 +1,28 @@ + +/* + * Copyright (C) Maxim Dounin + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +#if (NGX_HAVE_DLOPEN) + +char * +ngx_dlerror(void) +{ + char *err; + + err = (char *) dlerror(); + + if (err == NULL) { + return ""; + } + + return err; +} + +#endif diff --git a/src/os/unix/ngx_dlopen.h b/src/os/unix/ngx_dlopen.h new file mode 100644 index 0000000..7a3159f --- /dev/null +++ b/src/os/unix/ngx_dlopen.h @@ -0,0 +1,31 @@ + +/* + * Copyright (C) Maxim Dounin + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_DLOPEN_H_INCLUDED_ +#define _NGX_DLOPEN_H_INCLUDED_ + + +#include +#include + + +#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/src/os/unix/ngx_errno.c b/src/os/unix/ngx_errno.c new file mode 100644 index 0000000..e787b23 --- /dev/null +++ b/src/os/unix/ngx_errno.c @@ -0,0 +1,87 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +/* + * 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/src/os/unix/ngx_errno.h b/src/os/unix/ngx_errno.h new file mode 100644 index 0000000..7d6ca76 --- /dev/null +++ b/src/os/unix/ngx_errno.h @@ -0,0 +1,79 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_ERRNO_H_INCLUDED_ +#define _NGX_ERRNO_H_INCLUDED_ + + +#include +#include + + +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/src/os/unix/ngx_file_aio_read.c b/src/os/unix/ngx_file_aio_read.c new file mode 100644 index 0000000..aedc3c9 --- /dev/null +++ b/src/os/unix/ngx_file_aio_read.c @@ -0,0 +1,216 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +/* + * 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/src/os/unix/ngx_files.c b/src/os/unix/ngx_files.c new file mode 100644 index 0000000..7fbb7c9 --- /dev/null +++ b/src/os/unix/ngx_files.c @@ -0,0 +1,906 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +#if (NGX_THREADS) +#include +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/src/os/unix/ngx_files.h b/src/os/unix/ngx_files.h new file mode 100644 index 0000000..07872b1 --- /dev/null +++ b/src/os/unix/ngx_files.h @@ -0,0 +1,395 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_FILES_H_INCLUDED_ +#define _NGX_FILES_H_INCLUDED_ + + +#include +#include + + +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/src/os/unix/ngx_freebsd.h b/src/os/unix/ngx_freebsd.h new file mode 100644 index 0000000..4f93da5 --- /dev/null +++ b/src/os/unix/ngx_freebsd.h @@ -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/src/os/unix/ngx_freebsd_config.h b/src/os/unix/ngx_freebsd_config.h new file mode 100644 index 0000000..b7da48c --- /dev/null +++ b/src/os/unix/ngx_freebsd_config.h @@ -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 +#include +#include +#include +#include /* offsetof() */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* ALIGN() */ +#include /* statfs() */ + +#include /* FIONBIO */ +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include /* TCP_NODELAY, TCP_NOPUSH */ +#include +#include +#include + +#include /* setproctitle() before 4.1 */ +#include +#include + +#include + + +#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 + + +#if (NGX_HAVE_POSIX_SEM) +#include +#endif + + +#if (NGX_HAVE_POLL) +#include +#endif + + +#if (NGX_HAVE_KQUEUE) +#include +#endif + + +#if (NGX_HAVE_FILE_AIO) +#include +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/src/os/unix/ngx_freebsd_init.c b/src/os/unix/ngx_freebsd_init.c new file mode 100644 index 0000000..71672c7 --- /dev/null +++ b/src/os/unix/ngx_freebsd_init.c @@ -0,0 +1,261 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +/* 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, +#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/src/os/unix/ngx_freebsd_sendfile_chain.c b/src/os/unix/ngx_freebsd_sendfile_chain.c new file mode 100644 index 0000000..4822e72 --- /dev/null +++ b/src/os/unix/ngx_freebsd_sendfile_chain.c @@ -0,0 +1,333 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +/* + * 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) == NGX_ERROR) { + 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/src/os/unix/ngx_gcc_atomic_amd64.h b/src/os/unix/ngx_gcc_atomic_amd64.h new file mode 100644 index 0000000..159a297 --- /dev/null +++ b/src/os/unix/ngx_gcc_atomic_amd64.h @@ -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/src/os/unix/ngx_gcc_atomic_ppc.h b/src/os/unix/ngx_gcc_atomic_ppc.h new file mode 100644 index 0000000..45afc4b --- /dev/null +++ b/src/os/unix/ngx_gcc_atomic_ppc.h @@ -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/src/os/unix/ngx_gcc_atomic_sparc64.h b/src/os/unix/ngx_gcc_atomic_sparc64.h new file mode 100644 index 0000000..a84db35 --- /dev/null +++ b/src/os/unix/ngx_gcc_atomic_sparc64.h @@ -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/src/os/unix/ngx_gcc_atomic_x86.h b/src/os/unix/ngx_gcc_atomic_x86.h new file mode 100644 index 0000000..54e01ae --- /dev/null +++ b/src/os/unix/ngx_gcc_atomic_x86.h @@ -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/src/os/unix/ngx_linux.h b/src/os/unix/ngx_linux.h new file mode 100644 index 0000000..13d654e --- /dev/null +++ b/src/os/unix/ngx_linux.h @@ -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/src/os/unix/ngx_linux_aio_read.c b/src/os/unix/ngx_linux_aio_read.c new file mode 100644 index 0000000..9f0a6c1 --- /dev/null +++ b/src/os/unix/ngx_linux_aio_read.c @@ -0,0 +1,148 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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/src/os/unix/ngx_linux_config.h b/src/os/unix/ngx_linux_config.h new file mode 100644 index 0000000..2f6129d --- /dev/null +++ b/src/os/unix/ngx_linux_config.h @@ -0,0 +1,123 @@ + +/* + * 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 +#include +#include +#include +#include /* offsetof() */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* statfs() */ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include /* TCP_NODELAY, TCP_CORK */ +#include +#include +#include + +#include /* tzset() */ +#include /* memalign() */ +#include /* IOV_MAX */ +#include +#include +#include /* uname() */ + +#include + + +#include + + +#if (NGX_HAVE_POSIX_SEM) +#include +#endif + + +#if (NGX_HAVE_SYS_PRCTL_H) +#include +#endif + + +#if (NGX_HAVE_SENDFILE64) +#include +#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 +#endif + + +#if (NGX_HAVE_EPOLL) +#include +#endif + + +#if (NGX_HAVE_SYS_EVENTFD_H) +#include +#endif +#include +#if (NGX_HAVE_FILE_AIO) +#include +typedef struct iocb ngx_aiocb_t; +#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/src/os/unix/ngx_linux_init.c b/src/os/unix/ngx_linux_init.c new file mode 100644 index 0000000..a1372e9 --- /dev/null +++ b/src/os/unix/ngx_linux_init.c @@ -0,0 +1,59 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +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, +#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/src/os/unix/ngx_linux_sendfile_chain.c b/src/os/unix/ngx_linux_sendfile_chain.c new file mode 100644 index 0000000..3c0696a --- /dev/null +++ b/src/os/unix/ngx_linux_sendfile_chain.c @@ -0,0 +1,462 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +static ssize_t ngx_linux_sendfile(ngx_connection_t *c, ngx_buf_t *file, + size_t size); + +#if (NGX_THREADS) +#include + +#if !(NGX_HAVE_SENDFILE64) +#error sendfile64() is required! +#endif + +static ngx_int_t ngx_linux_sendfile_thread(ngx_connection_t *c, ngx_buf_t *file, + size_t size, size_t *sent); +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 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]; +#if (NGX_THREADS) + ngx_int_t rc; + ngx_uint_t thread_handled, thread_complete; +#endif + + 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; +#if (NGX_THREADS) + thread_handled = 0; + thread_complete = 0; +#endif + + /* 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 (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) == NGX_ERROR) { + 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 + +#if (NGX_THREADS) + if (file->file->thread_handler) { + rc = ngx_linux_sendfile_thread(c, file, file_size, &sent); + + switch (rc) { + case NGX_OK: + thread_handled = 1; + break; + + case NGX_DONE: + thread_complete = 1; + break; + + case NGX_AGAIN: + break; + + default: /* NGX_ERROR */ + return NGX_CHAIN_ERROR; + } + + } else +#endif + { + n = ngx_linux_sendfile(c, file, file_size); + + if (n == NGX_ERROR) { + return NGX_CHAIN_ERROR; + } + + 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 ((size_t) (send - prev_send) != sent) { +#if (NGX_THREADS) + if (thread_handled) { + return in; + } + + if (thread_complete) { + send = prev_send + sent; + continue; + } +#endif + wev->ready = 0; + return in; + } + + 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_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 = 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 ngx_int_t +ngx_linux_sendfile_thread(ngx_connection_t *c, ngx_buf_t *file, size_t size, + size_t *sent) +{ + 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) { + *sent = 0; + + if (wev->complete) { + return NGX_DONE; + } + + 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; + } + + *sent = ctx->sent; + + if (ctx->sent == ctx->size || wev->complete) { + return NGX_DONE; + } + + return NGX_AGAIN; + } + + 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 + */ + + *sent = 0; + + return NGX_OK; + } + + 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; + } + + *sent = 0; + + return NGX_OK; +} + + +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 = 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/src/os/unix/ngx_os.h b/src/os/unix/ngx_os.h new file mode 100644 index 0000000..66e971f --- /dev/null +++ b/src/os/unix/ngx_os.h @@ -0,0 +1,99 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_OS_H_INCLUDED_ +#define _NGX_OS_H_INCLUDED_ + + +#include +#include + + +#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 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_cycle_t *cycle); +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); + + +#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 + + +#elif (NGX_LINUX) +#include + + +#elif (NGX_SOLARIS) +#include + + +#elif (NGX_DARWIN) +#include +#endif + + +#endif /* _NGX_OS_H_INCLUDED_ */ diff --git a/src/os/unix/ngx_posix_config.h b/src/os/unix/ngx_posix_config.h new file mode 100644 index 0000000..5d1358e --- /dev/null +++ b/src/os/unix/ngx_posix_config.h @@ -0,0 +1,171 @@ + +/* + * 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 +#include +#if (NGX_HAVE_UNISTD_H) +#include +#endif +#if (NGX_HAVE_INTTYPES_H) +#include +#endif +#include +#include /* offsetof() */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if (NGX_HAVE_SYS_PARAM_H) +#include /* statfs() */ +#endif +#if (NGX_HAVE_SYS_MOUNT_H) +#include /* statfs() */ +#endif +#if (NGX_HAVE_SYS_STATVFS_H) +#include /* statvfs() */ +#endif + +#if (NGX_HAVE_SYS_FILIO_H) +#include /* FIONBIO */ +#endif +#include /* FIONBIO */ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include /* TCP_NODELAY */ +#include +#include +#include + +#if (NGX_HAVE_LIMITS_H) +#include /* IOV_MAX */ +#endif + +#ifdef __CYGWIN__ +#include /* memalign() */ +#endif + +#if (NGX_HAVE_CRYPT_H) +#include +#endif + + +#ifndef IOV_MAX +#define IOV_MAX 16 +#endif + + +#include + + +#if (NGX_HAVE_DLOPEN) +#include +#endif + + +#if (NGX_HAVE_POSIX_SEM) +#include +#endif + + +#if (NGX_HAVE_POLL) +#include +#endif + + +#if (NGX_HAVE_KQUEUE) +#include +#endif + + +#if (NGX_HAVE_DEVPOLL) && !(NGX_TEST_BUILD_DEVPOLL) +#include +#include +#endif + + +#if (NGX_HAVE_FILE_AIO) +#include +typedef struct aiocb ngx_aiocb_t; +#endif + + +#define NGX_LISTEN_BACKLOG 511 + +#define ngx_debug_init() + + +#if (__FreeBSD__) && (__FreeBSD_version < 400017) + +#include /* ALIGN() */ + +/* + * 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 + + +extern char **environ; + + +#endif /* _NGX_POSIX_CONFIG_H_INCLUDED_ */ diff --git a/src/os/unix/ngx_posix_init.c b/src/os/unix/ngx_posix_init.c new file mode 100644 index 0000000..76ed94e --- /dev/null +++ b/src/os/unix/ngx_posix_init.c @@ -0,0 +1,131 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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_writev_chain, + 0 +}; + + +ngx_int_t +ngx_os_init(ngx_log_t *log) +{ + ngx_uint_t n; + +#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; + } + + 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 + + srandom(ngx_time()); + + 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/src/os/unix/ngx_process.c b/src/os/unix/ngx_process.c new file mode 100644 index 0000000..24a63fb --- /dev/null +++ b/src/os/unix/ngx_process.c @@ -0,0 +1,630 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +typedef struct { + int signo; + char *signame; + char *name; + void (*handler)(int signo); +} ngx_signal_t; + + + +static void ngx_execute_proc(ngx_cycle_t *cycle, void *data); +static void ngx_signal_handler(int signo); +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", "", SIG_IGN }, + + { SIGPIPE, "SIGPIPE, SIG_IGN", "", SIG_IGN }, + + { 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_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)); + sa.sa_handler = sig->handler; + 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; +} + + +void +ngx_signal_handler(int signo) +{ + 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 (getppid() > 1 || ngx_new_binary > 0) { + + /* + * Ignore the signal in the new binary if its parent is + * not the init process, 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; + 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; + } + + 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/src/os/unix/ngx_process.h b/src/os/unix/ngx_process.h new file mode 100644 index 0000000..7b5e8c0 --- /dev/null +++ b/src/os/unix/ngx_process.h @@ -0,0 +1,88 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_PROCESS_H_INCLUDED_ +#define _NGX_PROCESS_H_INCLUDED_ + + +#include +#include + + +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 + +#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_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/src/os/unix/ngx_process_cycle.c b/src/os/unix/ngx_process_cycle.c new file mode 100644 index 0000000..e8ca91f --- /dev/null +++ b/src/os/unix/ngx_process_cycle.c @@ -0,0 +1,1214 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include +#include + +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; + +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; + + 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); + } +} + + +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_SSL) + /* Delay added to give Quickassist Driver time to cleanup + * if worker exit with non-zero code. */ + if(ngx_processes[i].status != 0) { + usleep(2000000); + } +#endif + 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) { + ngx_event_cancel_timers(); + + if (ngx_event_timer_rbtree.root == ngx_event_timer_rbtree.sentinel) + { + 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_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_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 (setuid(ccf->user) == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, + "setuid(%d) failed", ccf->user); + /* fatal */ + exit(2); + } + } + + 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"); + } + + srandom((ngx_pid << 16) ^ ngx_time()); + + /* + * 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; + } + + ngx_memzero(&ch, sizeof(ngx_channel_t)); + + 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) { +#if (NGX_SSL) + if (c->asynch && ngx_del_async_conn) { + if (c->num_async_fds) { + ngx_del_async_conn(c, NGX_DISABLE_EVENT); + c->num_async_fds--; + } + } +#endif + 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) +{ + time_t next, n; + ngx_uint_t i; + ngx_path_t **path; + + next = 60 * 60; + + 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 * 1000); +} + + +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/src/os/unix/ngx_process_cycle.h b/src/os/unix/ngx_process_cycle.h new file mode 100644 index 0000000..69495d5 --- /dev/null +++ b/src/os/unix/ngx_process_cycle.h @@ -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 +#include + + +#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/src/os/unix/ngx_readv_chain.c b/src/os/unix/ngx_readv_chain.c new file mode 100644 index 0000000..d23508e --- /dev/null +++ b/src/os/unix/ngx_readv_chain.c @@ -0,0 +1,182 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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 + + 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 = 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 (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/src/os/unix/ngx_recv.c b/src/os/unix/ngx_recv.c new file mode 100644 index 0000000..5013ae3 --- /dev/null +++ b/src/os/unix/ngx_recv.c @@ -0,0 +1,134 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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 + + do { + n = 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 ((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/src/os/unix/ngx_send.c b/src/os/unix/ngx_send.c new file mode 100644 index 0000000..61ea202 --- /dev/null +++ b/src/os/unix/ngx_send.c @@ -0,0 +1,73 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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 = 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/src/os/unix/ngx_setaffinity.c b/src/os/unix/ngx_setaffinity.c new file mode 100644 index 0000000..34ec390 --- /dev/null +++ b/src/os/unix/ngx_setaffinity.c @@ -0,0 +1,53 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +#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/src/os/unix/ngx_setaffinity.h b/src/os/unix/ngx_setaffinity.h new file mode 100644 index 0000000..a4139ed --- /dev/null +++ b/src/os/unix/ngx_setaffinity.h @@ -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 + +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/src/os/unix/ngx_setproctitle.c b/src/os/unix/ngx_setproctitle.c new file mode 100644 index 0000000..91afa51 --- /dev/null +++ b/src/os/unix/ngx_setproctitle.c @@ -0,0 +1,135 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +#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/src/os/unix/ngx_setproctitle.h b/src/os/unix/ngx_setproctitle.h new file mode 100644 index 0000000..c363662 --- /dev/null +++ b/src/os/unix/ngx_setproctitle.h @@ -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/src/os/unix/ngx_shmem.c b/src/os/unix/ngx_shmem.c new file mode 100644 index 0000000..3ec7cbf --- /dev/null +++ b/src/os/unix/ngx_shmem.c @@ -0,0 +1,126 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +#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 +#include + + +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/src/os/unix/ngx_shmem.h b/src/os/unix/ngx_shmem.h new file mode 100644 index 0000000..566a7d3 --- /dev/null +++ b/src/os/unix/ngx_shmem.h @@ -0,0 +1,29 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_SHMEM_H_INCLUDED_ +#define _NGX_SHMEM_H_INCLUDED_ + + +#include +#include + + +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/src/os/unix/ngx_socket.c b/src/os/unix/ngx_socket.c new file mode 100644 index 0000000..3978f65 --- /dev/null +++ b/src/os/unix/ngx_socket.c @@ -0,0 +1,116 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +/* + * 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 ioctl(s, FIONBIO, &nb); +} + + +int +ngx_blocking(ngx_socket_t s) +{ + int nb; + + nb = 0; + + return ioctl(s, FIONBIO, &nb); +} + +#endif + + +#if (NGX_FREEBSD) + +int +ngx_tcp_nopush(ngx_socket_t s) +{ + int tcp_nopush; + + tcp_nopush = 1; + + return 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 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 setsockopt(s, IPPROTO_TCP, TCP_CORK, + (const void *) &cork, sizeof(int)); +} + + +int +ngx_tcp_push(ngx_socket_t s) +{ + int cork; + + cork = 0; + + return 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/src/os/unix/ngx_socket.h b/src/os/unix/ngx_socket.h new file mode 100644 index 0000000..fcc5153 --- /dev/null +++ b/src/os/unix/ngx_socket.h @@ -0,0 +1,64 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_SOCKET_H_INCLUDED_ +#define _NGX_SOCKET_H_INCLUDED_ + + +#include + + +#define NGX_WRITE_SHUTDOWN SHUT_WR + +typedef int ngx_socket_t; + +#define ngx_socket 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) 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_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 shutdown +#define ngx_shutdown_socket_n "shutdown()" + +#define ngx_close_socket close +#define ngx_close_socket_n "close() socket" + + +#endif /* _NGX_SOCKET_H_INCLUDED_ */ diff --git a/src/os/unix/ngx_solaris.h b/src/os/unix/ngx_solaris.h new file mode 100644 index 0000000..7b167d8 --- /dev/null +++ b/src/os/unix/ngx_solaris.h @@ -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/src/os/unix/ngx_solaris_config.h b/src/os/unix/ngx_solaris_config.h new file mode 100644 index 0000000..ffa01c8 --- /dev/null +++ b/src/os/unix/ngx_solaris_config.h @@ -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 */ + +#include +#include +#include +#include +#include /* offsetof() */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* statvfs() */ + +#include /* FIONBIO */ +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include /* TCP_NODELAY */ +#include +#include +#include + +#include +#include /* IOV_MAX */ +#include +#include + +#include + +#define NGX_ALIGNMENT _MAX_ALIGNMENT + +#include + + +#if (NGX_HAVE_POSIX_SEM) +#include +#endif + + +#if (NGX_HAVE_POLL) +#include +#endif + + +#if (NGX_HAVE_DEVPOLL) +#include +#include +#endif + + +#if (NGX_HAVE_EVENTPORT) +#include +#endif + + +#if (NGX_HAVE_SENDFILE) +#include +#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/src/os/unix/ngx_solaris_init.c b/src/os/unix/ngx_solaris_init.c new file mode 100644 index 0000000..83acae1 --- /dev/null +++ b/src/os/unix/ngx_solaris_init.c @@ -0,0 +1,76 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +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, +#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/src/os/unix/ngx_solaris_sendfilev_chain.c b/src/os/unix/ngx_solaris_sendfilev_chain.c new file mode 100644 index 0000000..39bcafa --- /dev/null +++ b/src/os/unix/ngx_solaris_sendfilev_chain.c @@ -0,0 +1,228 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#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/src/os/unix/ngx_sunpro_amd64.il b/src/os/unix/ngx_sunpro_amd64.il new file mode 100644 index 0000000..07f3210 --- /dev/null +++ b/src/os/unix/ngx_sunpro_amd64.il @@ -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/src/os/unix/ngx_sunpro_atomic_sparc64.h b/src/os/unix/ngx_sunpro_atomic_sparc64.h new file mode 100644 index 0000000..5f28055 --- /dev/null +++ b/src/os/unix/ngx_sunpro_atomic_sparc64.h @@ -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/src/os/unix/ngx_sunpro_sparc64.il b/src/os/unix/ngx_sunpro_sparc64.il new file mode 100644 index 0000000..bdeef61 --- /dev/null +++ b/src/os/unix/ngx_sunpro_sparc64.il @@ -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/src/os/unix/ngx_sunpro_x86.il b/src/os/unix/ngx_sunpro_x86.il new file mode 100644 index 0000000..d7e127c --- /dev/null +++ b/src/os/unix/ngx_sunpro_x86.il @@ -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/src/os/unix/ngx_thread.h b/src/os/unix/ngx_thread.h new file mode 100644 index 0000000..1b52dd7 --- /dev/null +++ b/src/os/unix/ngx_thread.h @@ -0,0 +1,71 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_THREAD_H_INCLUDED_ +#define _NGX_THREAD_H_INCLUDED_ + + +#include +#include + +#if (NGX_THREADS) + +#include + + +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/src/os/unix/ngx_thread_cond.c b/src/os/unix/ngx_thread_cond.c new file mode 100644 index 0000000..f524696 --- /dev/null +++ b/src/os/unix/ngx_thread_cond.c @@ -0,0 +1,87 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +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) { + ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, + "pthread_cond_init(%p)", cond); + 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) { + ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, + "pthread_cond_destroy(%p)", cond); + 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) { + ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, + "pthread_cond_signal(%p)", cond); + 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; + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, + "pthread_cond_wait(%p) enter", cond); + + err = pthread_cond_wait(cond, mtx); + +#if 0 + ngx_time_update(); +#endif + + if (err == 0) { + ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, + "pthread_cond_wait(%p) exit", cond); + return NGX_OK; + } + + ngx_log_error(NGX_LOG_ALERT, log, err, "pthread_cond_wait() failed"); + + return NGX_ERROR; +} diff --git a/src/os/unix/ngx_thread_id.c b/src/os/unix/ngx_thread_id.c new file mode 100644 index 0000000..5174f1a --- /dev/null +++ b/src/os/unix/ngx_thread_id.c @@ -0,0 +1,70 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#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 + +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/src/os/unix/ngx_thread_mutex.c b/src/os/unix/ngx_thread_mutex.c new file mode 100644 index 0000000..6e8385e --- /dev/null +++ b/src/os/unix/ngx_thread_mutex.c @@ -0,0 +1,174 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + +#include +#include + + +/* + * 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 overriden 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"); + } + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, + "pthread_mutex_init(%p)", mtx); + 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; + } + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, + "pthread_mutex_destroy(%p)", mtx); + return NGX_OK; +} + + +ngx_int_t +ngx_thread_mutex_lock(ngx_thread_mutex_t *mtx, ngx_log_t *log) +{ + ngx_err_t err; + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, + "pthread_mutex_lock(%p) enter", mtx); + + 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) { + ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, + "pthread_mutex_unlock(%p) exit", mtx); + return NGX_OK; + } + + ngx_log_error(NGX_LOG_ALERT, log, err, "pthread_mutex_unlock() failed"); + + return NGX_ERROR; +} diff --git a/src/os/unix/ngx_time.c b/src/os/unix/ngx_time.c new file mode 100644 index 0000000..cc760b2 --- /dev/null +++ b/src/os/unix/ngx_time.c @@ -0,0 +1,104 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +/* + * 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/src/os/unix/ngx_time.h b/src/os/unix/ngx_time.h new file mode 100644 index 0000000..c128c9a --- /dev/null +++ b/src/os/unix/ngx_time.h @@ -0,0 +1,66 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_TIME_H_INCLUDED_ +#define _NGX_TIME_H_INCLUDED_ + + +#include +#include + + +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/src/os/unix/ngx_udp_recv.c b/src/os/unix/ngx_udp_recv.c new file mode 100644 index 0000000..6d544c2 --- /dev/null +++ b/src/os/unix/ngx_udp_recv.c @@ -0,0 +1,72 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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 = 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/src/os/unix/ngx_udp_send.c b/src/os/unix/ngx_udp_send.c new file mode 100644 index 0000000..aabbc8e --- /dev/null +++ b/src/os/unix/ngx_udp_send.c @@ -0,0 +1,56 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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 = 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/src/os/unix/ngx_user.c b/src/os/unix/ngx_user.c new file mode 100644 index 0000000..27c76ef --- /dev/null +++ b/src/os/unix/ngx_user.c @@ -0,0 +1,90 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +/* + * Solaris has thread-safe crypt() + * Linux has crypt_r(); "struct crypt_data" is more than 128K + * FreeBSD needs the mutex to protect crypt() + * + * TODO: + * ngx_crypt_init() to init mutex + */ + + +#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; +#ifdef __GLIBC__ + /* work around the glibc bug */ + cd.current_salt[0] = ~salt[0]; +#endif + + 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/src/os/unix/ngx_user.h b/src/os/unix/ngx_user.h new file mode 100644 index 0000000..6e82204 --- /dev/null +++ b/src/os/unix/ngx_user.h @@ -0,0 +1,24 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_USER_H_INCLUDED_ +#define _NGX_USER_H_INCLUDED_ + + +#include +#include + + +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/src/os/unix/ngx_writev_chain.c b/src/os/unix/ngx_writev_chain.c new file mode 100644 index 0000000..e38a3aa --- /dev/null +++ b/src/os/unix/ngx_writev_chain.c @@ -0,0 +1,216 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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 = 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/src/os/win32/nginx.ico b/src/os/win32/nginx.ico new file mode 100644 index 0000000..e69de29 diff --git a/src/os/win32/nginx.rc b/src/os/win32/nginx.rc new file mode 100644 index 0000000..dc8b7ab --- /dev/null +++ b/src/os/win32/nginx.rc @@ -0,0 +1,6 @@ + +// Copyright (C) Igor Sysoev +// Copyright (C) Nginx, Inc. + + +nginx icon discardable "src\\os\\win32\\nginx.ico" diff --git a/src/os/win32/nginx_icon16.xpm b/src/os/win32/nginx_icon16.xpm new file mode 100644 index 0000000..45e4bad --- /dev/null +++ b/src/os/win32/nginx_icon16.xpm @@ -0,0 +1,24 @@ +/* XPM */ +static char * nginx_xpm[] = { +"16 16 2 2", +/* colors */ +" c none", +"GG c #009900", +/* pixels */ +" ", +" GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGG ", +" GGGGGG GGGGGG ", +" GGGGGG GGGGGG ", +" GGGGGG ", +" GGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGG GGGGGGGGGGGGGGGGGG ", +" GGGGGG GGGGGGGGGGGGGG ", +" GGGGGG GGGGGG ", +" GGGGGG GGGGGG ", +" GGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG ", +" " +}; diff --git a/src/os/win32/nginx_icon32.xpm b/src/os/win32/nginx_icon32.xpm new file mode 100644 index 0000000..eb26638 --- /dev/null +++ b/src/os/win32/nginx_icon32.xpm @@ -0,0 +1,39 @@ +/* XPM */ +static char * nginx_xpm[] = { +"32 32 2 2", +/* colors */ +" c none", +"GG c #009900", +/* pixels */ +" ", +" ", +" ", +" ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGG ", +" GGGGGGGGGG ", +" GGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" ", +" ", +" ", +" " diff --git a/src/os/win32/nginx_icon48.xpm b/src/os/win32/nginx_icon48.xpm new file mode 100644 index 0000000..c25ba0f --- /dev/null +++ b/src/os/win32/nginx_icon48.xpm @@ -0,0 +1,55 @@ +/* XPM */ +static char * nginx_xpm[] = { +"48 48 2 2", +/* colors */ +" c none", +"GG c #009900", +/* pixels */ +" ", +" ", +" ", +" ", +" ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" ", +" ", +" ", +" ", +" ", diff --git a/src/os/win32/ngx_alloc.c b/src/os/win32/ngx_alloc.c new file mode 100644 index 0000000..0c0ef30 --- /dev/null +++ b/src/os/win32/ngx_alloc.c @@ -0,0 +1,44 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +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; +} diff --git a/src/os/win32/ngx_alloc.h b/src/os/win32/ngx_alloc.h new file mode 100644 index 0000000..5a0fa3f --- /dev/null +++ b/src/os/win32/ngx_alloc.h @@ -0,0 +1,27 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_ALLOC_H_INCLUDED_ +#define _NGX_ALLOC_H_INCLUDED_ + + +#include +#include + + +void *ngx_alloc(size_t size, ngx_log_t *log); +void *ngx_calloc(size_t size, ngx_log_t *log); + +#define ngx_free free +#define ngx_memalign(alignment, size, log) ngx_alloc(size, log) + +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/src/os/win32/ngx_atomic.h b/src/os/win32/ngx_atomic.h new file mode 100644 index 0000000..113f561 --- /dev/null +++ b/src/os/win32/ngx_atomic.h @@ -0,0 +1,69 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_ATOMIC_H_INCLUDED_ +#define _NGX_ATOMIC_H_INCLUDED_ + + +#include +#include + + +#define NGX_HAVE_ATOMIC_OPS 1 + +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 defined( __WATCOMC__ ) || defined( __BORLANDC__ ) || defined(__GNUC__) \ + || ( _MSC_VER >= 1300 ) + +/* the new SDK headers */ + +#define ngx_atomic_cmp_set(lock, old, set) \ + ((ngx_atomic_uint_t) InterlockedCompareExchange((long *) lock, set, old) \ + == old) + +#else + +/* the old MS VC6.0SP2 SDK headers */ + +#define ngx_atomic_cmp_set(lock, old, set) \ + (InterlockedCompareExchange((void **) lock, (void *) set, (void *) old) \ + == (void *) old) + +#endif + + +#define ngx_atomic_fetch_add(p, add) InterlockedExchangeAdd((long *) p, add) + + +#define ngx_memory_barrier() + + +#if defined( __BORLANDC__ ) || ( __WATCOMC__ < 1230 ) + +/* + * Borland C++ 5.5 (tasm32) and Open Watcom C prior to 1.3 + * do not understand the "pause" instruction + */ + +#define ngx_cpu_pause() +#else +#define ngx_cpu_pause() __asm { 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/src/os/win32/ngx_dlopen.c b/src/os/win32/ngx_dlopen.c new file mode 100644 index 0000000..804f49d --- /dev/null +++ b/src/os/win32/ngx_dlopen.c @@ -0,0 +1,22 @@ + +/* + * Copyright (C) Maxim Dounin + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +char * +ngx_dlerror(void) +{ + u_char *p; + static u_char errstr[NGX_MAX_ERROR_STR]; + + p = ngx_strerror(ngx_errno, errstr, NGX_MAX_ERROR_STR); + *p = '\0'; + + return (char *) errstr; +} diff --git a/src/os/win32/ngx_dlopen.h b/src/os/win32/ngx_dlopen.h new file mode 100644 index 0000000..0d6b405 --- /dev/null +++ b/src/os/win32/ngx_dlopen.h @@ -0,0 +1,32 @@ + +/* + * Copyright (C) Maxim Dounin + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_DLOPEN_H_INCLUDED_ +#define _NGX_DLOPEN_H_INCLUDED_ + + +#include +#include + + +#define NGX_HAVE_DLOPEN 1 + + +#define ngx_dlopen(path) LoadLibrary((char *) path) +#define ngx_dlopen_n "LoadLibrary()" + +#define ngx_dlsym(handle, symbol) (void *) GetProcAddress(handle, symbol) +#define ngx_dlsym_n "GetProcAddress()" + +#define ngx_dlclose(handle) (FreeLibrary(handle) ? 0 : -1) +#define ngx_dlclose_n "FreeLibrary()" + + +char *ngx_dlerror(void); + + +#endif /* _NGX_DLOPEN_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_errno.c b/src/os/win32/ngx_errno.c new file mode 100644 index 0000000..b732bf4 --- /dev/null +++ b/src/os/win32/ngx_errno.c @@ -0,0 +1,60 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +u_char * +ngx_strerror(ngx_err_t err, u_char *errstr, size_t size) +{ + u_int len; + static u_long lang = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US); + + if (size == 0) { + return errstr; + } + + len = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, + NULL, err, lang, (char *) errstr, size, NULL); + + if (len == 0 && lang && GetLastError() == ERROR_RESOURCE_LANG_NOT_FOUND) { + + /* + * Try to use English messages first and fallback to a language, + * based on locale: non-English Windows have no English messages + * at all. This way allows to use English messages at least on + * Windows with MUI. + */ + + lang = 0; + + len = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, + NULL, err, lang, (char *) errstr, size, NULL); + } + + if (len == 0) { + return ngx_snprintf(errstr, size, + "FormatMessage() error:(%d)", GetLastError()); + } + + /* remove ".\r\n\0" */ + while (errstr[len] == '\0' || errstr[len] == CR + || errstr[len] == LF || errstr[len] == '.') + { + --len; + } + + return &errstr[++len]; +} + + +ngx_int_t +ngx_strerror_init(void) +{ + return NGX_OK; +} diff --git a/src/os/win32/ngx_errno.h b/src/os/win32/ngx_errno.h new file mode 100644 index 0000000..255a39d --- /dev/null +++ b/src/os/win32/ngx_errno.h @@ -0,0 +1,71 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_ERRNO_H_INCLUDED_ +#define _NGX_ERRNO_H_INCLUDED_ + + +#include +#include + + +typedef DWORD ngx_err_t; + +#define ngx_errno GetLastError() +#define ngx_set_errno(err) SetLastError(err) +#define ngx_socket_errno WSAGetLastError() +#define ngx_set_socket_errno(err) WSASetLastError(err) + +#define NGX_EPERM ERROR_ACCESS_DENIED +#define NGX_ENOENT ERROR_FILE_NOT_FOUND +#define NGX_ENOPATH ERROR_PATH_NOT_FOUND +#define NGX_ENOMEM ERROR_NOT_ENOUGH_MEMORY +#define NGX_EACCES ERROR_ACCESS_DENIED +/* + * there are two EEXIST error codes: + * ERROR_FILE_EXISTS used by CreateFile(CREATE_NEW), + * and ERROR_ALREADY_EXISTS used by CreateDirectory(); + * MoveFile() uses both + */ +#define NGX_EEXIST ERROR_ALREADY_EXISTS +#define NGX_EEXIST_FILE ERROR_FILE_EXISTS +#define NGX_EXDEV ERROR_NOT_SAME_DEVICE +#define NGX_ENOTDIR ERROR_PATH_NOT_FOUND +#define NGX_EISDIR ERROR_CANNOT_MAKE +#define NGX_ENOSPC ERROR_DISK_FULL +#define NGX_EPIPE EPIPE +#define NGX_EAGAIN WSAEWOULDBLOCK +#define NGX_EINPROGRESS WSAEINPROGRESS +#define NGX_ENOPROTOOPT WSAENOPROTOOPT +#define NGX_EOPNOTSUPP WSAEOPNOTSUPP +#define NGX_EADDRINUSE WSAEADDRINUSE +#define NGX_ECONNABORTED WSAECONNABORTED +#define NGX_ECONNRESET WSAECONNRESET +#define NGX_ENOTCONN WSAENOTCONN +#define NGX_ETIMEDOUT WSAETIMEDOUT +#define NGX_ECONNREFUSED WSAECONNREFUSED +#define NGX_ENAMETOOLONG ERROR_BAD_PATHNAME +#define NGX_ENETDOWN WSAENETDOWN +#define NGX_ENETUNREACH WSAENETUNREACH +#define NGX_EHOSTDOWN WSAEHOSTDOWN +#define NGX_EHOSTUNREACH WSAEHOSTUNREACH +#define NGX_ENOMOREFILES ERROR_NO_MORE_FILES +#define NGX_EILSEQ ERROR_NO_UNICODE_TRANSLATION +#define NGX_ELOOP 0 +#define NGX_EBADF WSAEBADF + +#define NGX_EALREADY WSAEALREADY +#define NGX_EINVAL WSAEINVAL +#define NGX_EMFILE WSAEMFILE +#define NGX_ENFILE WSAEMFILE + + +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/src/os/win32/ngx_event_log.c b/src/os/win32/ngx_event_log.c new file mode 100644 index 0000000..e11ed1e --- /dev/null +++ b/src/os/win32/ngx_event_log.c @@ -0,0 +1,99 @@ +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +#define NGX_MAX_ERROR_STR 2048 + + +void ngx_cdecl +ngx_event_log(ngx_err_t err, const char *fmt, ...) +{ + u_char *p, *last; + long types; + HKEY key; + HANDLE ev; + va_list args; + u_char text[NGX_MAX_ERROR_STR]; + const char *msgarg[9]; + static u_char netmsg[] = "%SystemRoot%\\System32\\netmsg.dll"; + + last = text + NGX_MAX_ERROR_STR; + p = text + GetModuleFileName(NULL, (char *) text, NGX_MAX_ERROR_STR - 50); + + *p++ = ':'; + ngx_linefeed(p); + + 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 - 1) { + p = last - NGX_LINEFEED_SIZE - 1; + } + + ngx_linefeed(p); + + *p = '\0'; + + /* + * we do not log errors here since we use + * Event Log only to log our own logs open errors + */ + + if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, + "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\nginx", + 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &key, NULL) + != 0) + { + return; + } + + if (RegSetValueEx(key, "EventMessageFile", 0, REG_EXPAND_SZ, + netmsg, sizeof(netmsg) - 1) + != 0) + { + return; + } + + types = EVENTLOG_ERROR_TYPE; + + if (RegSetValueEx(key, "TypesSupported", 0, REG_DWORD, + (u_char *) &types, sizeof(long)) + != 0) + { + return; + } + + RegCloseKey(key); + + ev = RegisterEventSource(NULL, "nginx"); + + msgarg[0] = (char *) text; + msgarg[1] = NULL; + msgarg[2] = NULL; + msgarg[3] = NULL; + msgarg[4] = NULL; + msgarg[5] = NULL; + msgarg[6] = NULL; + msgarg[7] = NULL; + msgarg[8] = NULL; + + /* + * the 3299 event id in netmsg.dll has the generic message format: + * "%1 %2 %3 %4 %5 %6 %7 %8 %9" + */ + + ReportEvent(ev, EVENTLOG_ERROR_TYPE, 0, 3299, NULL, 9, 0, msgarg, NULL); + + DeregisterEventSource(ev); +} diff --git a/src/os/win32/ngx_files.c b/src/os/win32/ngx_files.c new file mode 100644 index 0000000..9ef22a5 --- /dev/null +++ b/src/os/win32/ngx_files.c @@ -0,0 +1,883 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +#define NGX_UTF16_BUFLEN 256 + +static ngx_int_t ngx_win32_check_filename(u_char *name, u_short *u, + size_t len); +static u_short *ngx_utf8_to_utf16(u_short *utf16, u_char *utf8, size_t *len); + + +/* FILE_FLAG_BACKUP_SEMANTICS allows to obtain a handle to a directory */ + +ngx_fd_t +ngx_open_file(u_char *name, u_long mode, u_long create, u_long access) +{ + size_t len; + u_short *u; + ngx_fd_t fd; + ngx_err_t err; + u_short utf16[NGX_UTF16_BUFLEN]; + + len = NGX_UTF16_BUFLEN; + u = ngx_utf8_to_utf16(utf16, name, &len); + + if (u == NULL) { + return INVALID_HANDLE_VALUE; + } + + fd = INVALID_HANDLE_VALUE; + + if (create == NGX_FILE_OPEN + && ngx_win32_check_filename(name, u, len) != NGX_OK) + { + goto failed; + } + + fd = CreateFileW(u, mode, + FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, + NULL, create, FILE_FLAG_BACKUP_SEMANTICS, NULL); + +failed: + + if (u != utf16) { + err = ngx_errno; + ngx_free(u); + ngx_set_errno(err); + } + + return fd; +} + + +ssize_t +ngx_read_file(ngx_file_t *file, u_char *buf, size_t size, off_t offset) +{ + u_long n; + ngx_err_t err; + OVERLAPPED ovlp, *povlp; + + ovlp.Internal = 0; + ovlp.InternalHigh = 0; + ovlp.Offset = (u_long) offset; + ovlp.OffsetHigh = (u_long) (offset >> 32); + ovlp.hEvent = NULL; + + povlp = &ovlp; + + if (ReadFile(file->fd, buf, size, &n, povlp) == 0) { + err = ngx_errno; + + if (err == ERROR_HANDLE_EOF) { + return 0; + } + + ngx_log_error(NGX_LOG_ERR, file->log, err, + "ReadFile() \"%s\" failed", file->name.data); + return NGX_ERROR; + } + + file->offset += n; + + return n; +} + + +ssize_t +ngx_write_file(ngx_file_t *file, u_char *buf, size_t size, off_t offset) +{ + u_long n; + OVERLAPPED ovlp, *povlp; + + ovlp.Internal = 0; + ovlp.InternalHigh = 0; + ovlp.Offset = (u_long) offset; + ovlp.OffsetHigh = (u_long) (offset >> 32); + ovlp.hEvent = NULL; + + povlp = &ovlp; + + if (WriteFile(file->fd, buf, size, &n, povlp) == 0) { + ngx_log_error(NGX_LOG_ERR, file->log, ngx_errno, + "WriteFile() \"%s\" failed", file->name.data); + return NGX_ERROR; + } + + if (n != size) { + ngx_log_error(NGX_LOG_CRIT, file->log, 0, + "WriteFile() \"%s\" has written only %ul of %uz", + file->name.data, n, size); + return NGX_ERROR; + } + + file->offset += n; + + return n; +} + + +ssize_t +ngx_write_chain_to_file(ngx_file_t *file, ngx_chain_t *cl, off_t offset, + ngx_pool_t *pool) +{ + u_char *buf, *prev; + size_t size; + ssize_t total, n; + + total = 0; + + while (cl) { + buf = cl->buf->pos; + prev = buf; + size = 0; + + /* coalesce the neighbouring bufs */ + + while (cl && prev == cl->buf->pos) { + size += cl->buf->last - cl->buf->pos; + prev = cl->buf->last; + cl = cl->next; + } + + n = ngx_write_file(file, buf, size, offset); + + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + total += n; + offset += n; + } + + return total; +} + + +ssize_t +ngx_read_fd(ngx_fd_t fd, void *buf, size_t size) +{ + u_long n; + + if (ReadFile(fd, buf, size, &n, NULL) != 0) { + return (size_t) n; + } + + return -1; +} + + +ssize_t +ngx_write_fd(ngx_fd_t fd, void *buf, size_t size) +{ + u_long n; + + if (WriteFile(fd, buf, size, &n, NULL) != 0) { + return (size_t) n; + } + + return -1; +} + + +ssize_t +ngx_write_console(ngx_fd_t fd, void *buf, size_t size) +{ + u_long n; + + (void) CharToOemBuff(buf, buf, size); + + if (WriteFile(fd, buf, size, &n, NULL) != 0) { + return (size_t) n; + } + + return -1; +} + + +ngx_err_t +ngx_win32_rename_file(ngx_str_t *from, ngx_str_t *to, ngx_log_t *log) +{ + u_char *name; + ngx_err_t err; + ngx_uint_t collision; + ngx_atomic_uint_t num; + + name = ngx_alloc(to->len + 1 + NGX_ATOMIC_T_LEN + 1 + sizeof("DELETE"), + log); + if (name == NULL) { + return NGX_ENOMEM; + } + + ngx_memcpy(name, to->data, to->len); + + collision = 0; + + /* mutex_lock() (per cache or single ?) */ + + for ( ;; ) { + num = ngx_next_temp_number(collision); + + ngx_sprintf(name + to->len, ".%0muA.DELETE%Z", num); + + if (MoveFile((const char *) to->data, (const char *) name) != 0) { + break; + } + + collision = 1; + + ngx_log_error(NGX_LOG_CRIT, log, ngx_errno, + "MoveFile() \"%s\" to \"%s\" failed", to->data, name); + } + + if (MoveFile((const char *) from->data, (const char *) to->data) == 0) { + err = ngx_errno; + + } else { + err = 0; + } + + if (DeleteFile((const char *) name) == 0) { + ngx_log_error(NGX_LOG_CRIT, log, ngx_errno, + "DeleteFile() \"%s\" failed", name); + } + + /* mutex_unlock() */ + + ngx_free(name); + + return err; +} + + +ngx_int_t +ngx_file_info(u_char *file, ngx_file_info_t *sb) +{ + size_t len; + long rc; + u_short *u; + ngx_err_t err; + WIN32_FILE_ATTRIBUTE_DATA fa; + u_short utf16[NGX_UTF16_BUFLEN]; + + len = NGX_UTF16_BUFLEN; + + u = ngx_utf8_to_utf16(utf16, file, &len); + + if (u == NULL) { + return NGX_FILE_ERROR; + } + + rc = NGX_FILE_ERROR; + + if (ngx_win32_check_filename(file, u, len) != NGX_OK) { + goto failed; + } + + rc = GetFileAttributesExW(u, GetFileExInfoStandard, &fa); + + sb->dwFileAttributes = fa.dwFileAttributes; + sb->ftCreationTime = fa.ftCreationTime; + sb->ftLastAccessTime = fa.ftLastAccessTime; + sb->ftLastWriteTime = fa.ftLastWriteTime; + sb->nFileSizeHigh = fa.nFileSizeHigh; + sb->nFileSizeLow = fa.nFileSizeLow; + +failed: + + if (u != utf16) { + err = ngx_errno; + ngx_free(u); + ngx_set_errno(err); + } + + return rc; +} + + +ngx_int_t +ngx_set_file_time(u_char *name, ngx_fd_t fd, time_t s) +{ + uint64_t intervals; + FILETIME ft; + + /* 116444736000000000 is commented in src/os/win32/ngx_time.c */ + + intervals = s * 10000000 + 116444736000000000; + + ft.dwLowDateTime = (DWORD) intervals; + ft.dwHighDateTime = (DWORD) (intervals >> 32); + + if (SetFileTime(fd, NULL, NULL, &ft) != 0) { + return NGX_OK; + } + + return NGX_ERROR; +} + + +ngx_int_t +ngx_create_file_mapping(ngx_file_mapping_t *fm) +{ + LARGE_INTEGER size; + + 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; + } + + fm->handle = NULL; + + size.QuadPart = fm->size; + + if (SetFilePointerEx(fm->fd, size, NULL, FILE_BEGIN) == 0) { + ngx_log_error(NGX_LOG_CRIT, fm->log, ngx_errno, + "SetFilePointerEx(\"%s\", %uz) failed", + fm->name, fm->size); + goto failed; + } + + if (SetEndOfFile(fm->fd) == 0) { + ngx_log_error(NGX_LOG_CRIT, fm->log, ngx_errno, + "SetEndOfFile() \"%s\" failed", fm->name); + goto failed; + } + + fm->handle = CreateFileMapping(fm->fd, NULL, PAGE_READWRITE, + (u_long) ((off_t) fm->size >> 32), + (u_long) ((off_t) fm->size & 0xffffffff), + NULL); + if (fm->handle == NULL) { + ngx_log_error(NGX_LOG_CRIT, fm->log, ngx_errno, + "CreateFileMapping(%s, %uz) failed", + fm->name, fm->size); + goto failed; + } + + fm->addr = MapViewOfFile(fm->handle, FILE_MAP_WRITE, 0, 0, 0); + + if (fm->addr != NULL) { + return NGX_OK; + } + + ngx_log_error(NGX_LOG_CRIT, fm->log, ngx_errno, + "MapViewOfFile(%uz) of file mapping \"%s\" failed", + fm->size, fm->name); + +failed: + + if (fm->handle) { + if (CloseHandle(fm->handle) == 0) { + ngx_log_error(NGX_LOG_ALERT, fm->log, ngx_errno, + "CloseHandle() of file mapping \"%s\" failed", + 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); + } + + return NGX_ERROR; +} + + +void +ngx_close_file_mapping(ngx_file_mapping_t *fm) +{ + if (UnmapViewOfFile(fm->addr) == 0) { + ngx_log_error(NGX_LOG_ALERT, fm->log, ngx_errno, + "UnmapViewOfFile(%p) of file mapping \"%s\" failed", + fm->addr, &fm->name); + } + + if (CloseHandle(fm->handle) == 0) { + ngx_log_error(NGX_LOG_ALERT, fm->log, ngx_errno, + "CloseHandle() of file mapping \"%s\" failed", + &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); + } +} + + +u_char * +ngx_realpath(u_char *path, u_char *resolved) +{ + /* STUB */ + return path; +} + + +ngx_int_t +ngx_open_dir(ngx_str_t *name, ngx_dir_t *dir) +{ + ngx_cpystrn(name->data + name->len, NGX_DIR_MASK, NGX_DIR_MASK_LEN + 1); + + dir->dir = FindFirstFile((const char *) name->data, &dir->finddata); + + name->data[name->len] = '\0'; + + if (dir->dir == INVALID_HANDLE_VALUE) { + return NGX_ERROR; + } + + dir->valid_info = 1; + dir->ready = 1; + + return NGX_OK; +} + + +ngx_int_t +ngx_read_dir(ngx_dir_t *dir) +{ + if (dir->ready) { + dir->ready = 0; + return NGX_OK; + } + + if (FindNextFile(dir->dir, &dir->finddata) != 0) { + dir->type = 1; + return NGX_OK; + } + + return NGX_ERROR; +} + + +ngx_int_t +ngx_close_dir(ngx_dir_t *dir) +{ + if (FindClose(dir->dir) == 0) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_open_glob(ngx_glob_t *gl) +{ + u_char *p; + size_t len; + ngx_err_t err; + + gl->dir = FindFirstFile((const char *) gl->pattern, &gl->finddata); + + if (gl->dir == INVALID_HANDLE_VALUE) { + + err = ngx_errno; + + if ((err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) + && gl->test) + { + gl->no_match = 1; + return NGX_OK; + } + + return NGX_ERROR; + } + + for (p = gl->pattern; *p; p++) { + if (*p == '/') { + gl->last = p + 1 - gl->pattern; + } + } + + len = ngx_strlen(gl->finddata.cFileName); + gl->name.len = gl->last + len; + + gl->name.data = ngx_alloc(gl->name.len + 1, gl->log); + if (gl->name.data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(gl->name.data, gl->pattern, gl->last); + ngx_cpystrn(gl->name.data + gl->last, (u_char *) gl->finddata.cFileName, + len + 1); + + gl->ready = 1; + + return NGX_OK; +} + + +ngx_int_t +ngx_read_glob(ngx_glob_t *gl, ngx_str_t *name) +{ + size_t len; + ngx_err_t err; + + if (gl->no_match) { + return NGX_DONE; + } + + if (gl->ready) { + *name = gl->name; + + gl->ready = 0; + return NGX_OK; + } + + ngx_free(gl->name.data); + gl->name.data = NULL; + + if (FindNextFile(gl->dir, &gl->finddata) != 0) { + + len = ngx_strlen(gl->finddata.cFileName); + gl->name.len = gl->last + len; + + gl->name.data = ngx_alloc(gl->name.len + 1, gl->log); + if (gl->name.data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(gl->name.data, gl->pattern, gl->last); + ngx_cpystrn(gl->name.data + gl->last, (u_char *) gl->finddata.cFileName, + len + 1); + + *name = gl->name; + + return NGX_OK; + } + + err = ngx_errno; + + if (err == NGX_ENOMOREFILES) { + return NGX_DONE; + } + + ngx_log_error(NGX_LOG_ALERT, gl->log, err, + "FindNextFile(%s) failed", gl->pattern); + + return NGX_ERROR; +} + + +void +ngx_close_glob(ngx_glob_t *gl) +{ + if (gl->name.data) { + ngx_free(gl->name.data); + } + + if (gl->dir == INVALID_HANDLE_VALUE) { + return; + } + + if (FindClose(gl->dir) == 0) { + ngx_log_error(NGX_LOG_ALERT, gl->log, ngx_errno, + "FindClose(%s) failed", gl->pattern); + } +} + + +ngx_int_t +ngx_de_info(u_char *name, ngx_dir_t *dir) +{ + return NGX_OK; +} + + +ngx_int_t +ngx_de_link_info(u_char *name, ngx_dir_t *dir) +{ + return NGX_OK; +} + + +ngx_int_t +ngx_read_ahead(ngx_fd_t fd, size_t n) +{ + return ~NGX_FILE_ERROR; +} + + +ngx_int_t +ngx_directio_on(ngx_fd_t fd) +{ + return ~NGX_FILE_ERROR; +} + + +ngx_int_t +ngx_directio_off(ngx_fd_t fd) +{ + return ~NGX_FILE_ERROR; +} + + +size_t +ngx_fs_bsize(u_char *name) +{ + u_char root[4]; + u_long sc, bs, nfree, ncl; + + if (name[2] == ':') { + ngx_cpystrn(root, name, 4); + name = root; + } + + if (GetDiskFreeSpace((const char *) name, &sc, &bs, &nfree, &ncl) == 0) { + return 512; + } + + return sc * bs; +} + + +static ngx_int_t +ngx_win32_check_filename(u_char *name, u_short *u, size_t len) +{ + u_char *p, ch; + u_long n; + u_short *lu; + ngx_err_t err; + enum { + sw_start = 0, + sw_normal, + sw_after_slash, + sw_after_colon, + sw_after_dot + } state; + + /* check for NTFS streams (":"), trailing dots and spaces */ + + lu = NULL; + state = sw_start; + + for (p = name; *p; p++) { + ch = *p; + + switch (state) { + + case sw_start: + + /* + * skip till first "/" to allow paths starting with drive and + * relative path, like "c:html/" + */ + + if (ch == '/' || ch == '\\') { + state = sw_after_slash; + } + + break; + + case sw_normal: + + if (ch == ':') { + state = sw_after_colon; + break; + } + + if (ch == '.' || ch == ' ') { + state = sw_after_dot; + break; + } + + if (ch == '/' || ch == '\\') { + state = sw_after_slash; + break; + } + + break; + + case sw_after_slash: + + if (ch == '/' || ch == '\\') { + break; + } + + if (ch == '.') { + break; + } + + if (ch == ':') { + state = sw_after_colon; + break; + } + + state = sw_normal; + break; + + case sw_after_colon: + + if (ch == '/' || ch == '\\') { + state = sw_after_slash; + break; + } + + goto invalid; + + case sw_after_dot: + + if (ch == '/' || ch == '\\') { + goto invalid; + } + + if (ch == ':') { + goto invalid; + } + + if (ch == '.' || ch == ' ') { + break; + } + + state = sw_normal; + break; + } + } + + if (state == sw_after_dot) { + goto invalid; + } + + /* check if long name match */ + + lu = malloc(len * 2); + if (lu == NULL) { + return NGX_ERROR; + } + + n = GetLongPathNameW(u, lu, len); + + if (n == 0) { + goto failed; + } + + if (n != len - 1 || _wcsicmp(u, lu) != 0) { + goto invalid; + } + + ngx_free(lu); + + return NGX_OK; + +invalid: + + ngx_set_errno(NGX_ENOENT); + +failed: + + if (lu) { + err = ngx_errno; + ngx_free(lu); + ngx_set_errno(err); + } + + return NGX_ERROR; +} + + +static u_short * +ngx_utf8_to_utf16(u_short *utf16, u_char *utf8, size_t *len) +{ + u_char *p; + u_short *u, *last; + uint32_t n; + + p = utf8; + u = utf16; + last = utf16 + *len; + + while (u < last) { + + if (*p < 0x80) { + *u++ = (u_short) *p; + + if (*p == 0) { + *len = u - utf16; + return utf16; + } + + p++; + + continue; + } + + if (u + 1 == last) { + *len = u - utf16; + break; + } + + n = ngx_utf8_decode(&p, 4); + + if (n > 0x10ffff) { + ngx_set_errno(NGX_EILSEQ); + return NULL; + } + + if (n > 0xffff) { + n -= 0x10000; + *u++ = (u_short) (0xd800 + (n >> 10)); + *u++ = (u_short) (0xdc00 + (n & 0x03ff)); + continue; + } + + *u++ = (u_short) n; + } + + /* the given buffer is not enough, allocate a new one */ + + u = malloc(((p - utf8) + ngx_strlen(p) + 1) * sizeof(u_short)); + if (u == NULL) { + return NULL; + } + + ngx_memcpy(u, utf16, *len * 2); + + utf16 = u; + u += *len; + + for ( ;; ) { + + if (*p < 0x80) { + *u++ = (u_short) *p; + + if (*p == 0) { + *len = u - utf16; + return utf16; + } + + p++; + + continue; + } + + n = ngx_utf8_decode(&p, 4); + + if (n > 0x10ffff) { + ngx_free(utf16); + ngx_set_errno(NGX_EILSEQ); + return NULL; + } + + if (n > 0xffff) { + n -= 0x10000; + *u++ = (u_short) (0xd800 + (n >> 10)); + *u++ = (u_short) (0xdc00 + (n & 0x03ff)); + continue; + } + + *u++ = (u_short) n; + } + + /* unreachable */ +} diff --git a/src/os/win32/ngx_files.h b/src/os/win32/ngx_files.h new file mode 100644 index 0000000..895daea --- /dev/null +++ b/src/os/win32/ngx_files.h @@ -0,0 +1,273 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_FILES_H_INCLUDED_ +#define _NGX_FILES_H_INCLUDED_ + + +#include +#include + + +typedef HANDLE ngx_fd_t; +typedef BY_HANDLE_FILE_INFORMATION ngx_file_info_t; +typedef uint64_t ngx_file_uniq_t; + + +typedef struct { + u_char *name; + size_t size; + void *addr; + ngx_fd_t fd; + HANDLE handle; + ngx_log_t *log; +} ngx_file_mapping_t; + + +typedef struct { + HANDLE dir; + WIN32_FIND_DATA finddata; + + unsigned valid_info:1; + unsigned type:1; + unsigned ready:1; +} ngx_dir_t; + + +typedef struct { + HANDLE dir; + WIN32_FIND_DATA finddata; + + unsigned ready:1; + unsigned test:1; + unsigned no_match:1; + + u_char *pattern; + ngx_str_t name; + size_t last; + ngx_log_t *log; +} ngx_glob_t; + + + +/* INVALID_FILE_ATTRIBUTES is specified but not defined at least in MSVC6SP2 */ +#ifndef INVALID_FILE_ATTRIBUTES +#define INVALID_FILE_ATTRIBUTES 0xffffffff +#endif + +/* INVALID_SET_FILE_POINTER is not defined at least in MSVC6SP2 */ +#ifndef INVALID_SET_FILE_POINTER +#define INVALID_SET_FILE_POINTER 0xffffffff +#endif + + +#define NGX_INVALID_FILE INVALID_HANDLE_VALUE +#define NGX_FILE_ERROR 0 + + +ngx_fd_t ngx_open_file(u_char *name, u_long mode, u_long create, u_long access); +#define ngx_open_file_n "CreateFile()" + +#define NGX_FILE_RDONLY GENERIC_READ +#define NGX_FILE_WRONLY GENERIC_WRITE +#define NGX_FILE_RDWR GENERIC_READ|GENERIC_WRITE +#define NGX_FILE_APPEND FILE_APPEND_DATA|SYNCHRONIZE +#define NGX_FILE_NONBLOCK 0 + +#define NGX_FILE_CREATE_OR_OPEN OPEN_ALWAYS +#define NGX_FILE_OPEN OPEN_EXISTING +#define NGX_FILE_TRUNCATE CREATE_ALWAYS + +#define NGX_FILE_DEFAULT_ACCESS 0 +#define NGX_FILE_OWNER_ACCESS 0 + + +#define ngx_open_tempfile(name, persistent, access) \ + CreateFile((const char *) name, \ + GENERIC_READ|GENERIC_WRITE, \ + FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, \ + NULL, \ + CREATE_NEW, \ + persistent ? 0: \ + FILE_ATTRIBUTE_TEMPORARY|FILE_FLAG_DELETE_ON_CLOSE, \ + NULL); + +#define ngx_open_tempfile_n "CreateFile()" + + +#define ngx_close_file CloseHandle +#define ngx_close_file_n "CloseHandle()" + + +ssize_t ngx_read_fd(ngx_fd_t fd, void *buf, size_t size); +#define ngx_read_fd_n "ReadFile()" + + +ssize_t ngx_write_fd(ngx_fd_t fd, void *buf, size_t size); +#define ngx_write_fd_n "WriteFile()" + + +ssize_t ngx_write_console(ngx_fd_t fd, void *buf, size_t size); + + +#define ngx_linefeed(p) *p++ = CR; *p++ = LF; +#define NGX_LINEFEED_SIZE 2 +#define NGX_LINEFEED CRLF + + +#define ngx_delete_file(name) DeleteFile((const char *) name) +#define ngx_delete_file_n "DeleteFile()" + + +#define ngx_rename_file(o, n) MoveFile((const char *) o, (const char *) n) +#define ngx_rename_file_n "MoveFile()" +ngx_err_t ngx_win32_rename_file(ngx_str_t *from, ngx_str_t *to, ngx_log_t *log); + + + +ngx_int_t ngx_set_file_time(u_char *name, ngx_fd_t fd, time_t s); +#define ngx_set_file_time_n "SetFileTime()" + + +ngx_int_t ngx_file_info(u_char *filename, ngx_file_info_t *fi); +#define ngx_file_info_n "GetFileAttributesEx()" + + +#define ngx_fd_info(fd, fi) GetFileInformationByHandle(fd, fi) +#define ngx_fd_info_n "GetFileInformationByHandle()" + + +#define ngx_link_info(name, fi) ngx_file_info(name, fi) +#define ngx_link_info_n "GetFileAttributesEx()" + + +#define ngx_is_dir(fi) \ + (((fi)->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) +#define ngx_is_file(fi) \ + (((fi)->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) +#define ngx_is_link(fi) 0 +#define ngx_is_exec(fi) 0 + +#define ngx_file_access(fi) 0 + +#define ngx_file_size(fi) \ + (((off_t) (fi)->nFileSizeHigh << 32) | (fi)->nFileSizeLow) +#define ngx_file_fs_size(fi) ngx_file_size(fi) + +#define ngx_file_uniq(fi) (*(ngx_file_uniq_t *) &(fi)->nFileIndexHigh) + + +/* 116444736000000000 is commented in src/os/win32/ngx_time.c */ + +#define ngx_file_mtime(fi) \ + (time_t) (((((unsigned __int64) (fi)->ftLastWriteTime.dwHighDateTime << 32) \ + | (fi)->ftLastWriteTime.dwLowDateTime) \ + - 116444736000000000) / 10000000) + +ngx_int_t ngx_create_file_mapping(ngx_file_mapping_t *fm); +void ngx_close_file_mapping(ngx_file_mapping_t *fm); + + +u_char *ngx_realpath(u_char *path, u_char *resolved); +#define ngx_realpath_n "" +#define ngx_getcwd(buf, size) GetCurrentDirectory(size, (char *) buf) +#define ngx_getcwd_n "GetCurrentDirectory()" +#define ngx_path_separator(c) ((c) == '/' || (c) == '\\') + +#define NGX_HAVE_MAX_PATH 1 +#define NGX_MAX_PATH MAX_PATH + +#define NGX_DIR_MASK (u_char *) "/*" +#define NGX_DIR_MASK_LEN 2 + + +ngx_int_t ngx_open_dir(ngx_str_t *name, ngx_dir_t *dir); +#define ngx_open_dir_n "FindFirstFile()" + + +ngx_int_t ngx_read_dir(ngx_dir_t *dir); +#define ngx_read_dir_n "FindNextFile()" + + +ngx_int_t ngx_close_dir(ngx_dir_t *dir); +#define ngx_close_dir_n "FindClose()" + + +#define ngx_create_dir(name, access) CreateDirectory((const char *) name, NULL) +#define ngx_create_dir_n "CreateDirectory()" + + +#define ngx_delete_dir(name) RemoveDirectory((const char *) name) +#define ngx_delete_dir_n "RemoveDirectory()" + + +#define ngx_dir_access(a) (a) + + +#define ngx_de_name(dir) ((u_char *) (dir)->finddata.cFileName) +#define ngx_de_namelen(dir) ngx_strlen((dir)->finddata.cFileName) + +ngx_int_t ngx_de_info(u_char *name, ngx_dir_t *dir); +#define ngx_de_info_n "dummy()" + +ngx_int_t ngx_de_link_info(u_char *name, ngx_dir_t *dir); +#define ngx_de_link_info_n "dummy()" + +#define ngx_de_is_dir(dir) \ + (((dir)->finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) +#define ngx_de_is_file(dir) \ + (((dir)->finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) +#define ngx_de_is_link(dir) 0 +#define ngx_de_access(dir) 0 +#define ngx_de_size(dir) \ + (((off_t) (dir)->finddata.nFileSizeHigh << 32) | (dir)->finddata.nFileSizeLow) +#define ngx_de_fs_size(dir) ngx_de_size(dir) + +/* 116444736000000000 is commented in src/os/win32/ngx_time.c */ + +#define ngx_de_mtime(dir) \ + (time_t) (((((unsigned __int64) \ + (dir)->finddata.ftLastWriteTime.dwHighDateTime << 32) \ + | (dir)->finddata.ftLastWriteTime.dwLowDateTime) \ + - 116444736000000000) / 10000000) + + +ngx_int_t ngx_open_glob(ngx_glob_t *gl); +#define ngx_open_glob_n "FindFirstFile()" + +ngx_int_t ngx_read_glob(ngx_glob_t *gl, ngx_str_t *name); +void ngx_close_glob(ngx_glob_t *gl); + + +ssize_t ngx_read_file(ngx_file_t *file, u_char *buf, size_t size, off_t offset); +#define ngx_read_file_n "ReadFile()" + +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); + +ngx_int_t ngx_read_ahead(ngx_fd_t fd, size_t n); +#define ngx_read_ahead_n "ngx_read_ahead_n" + +ngx_int_t ngx_directio_on(ngx_fd_t fd); +#define ngx_directio_on_n "ngx_directio_on_n" + +ngx_int_t ngx_directio_off(ngx_fd_t fd); +#define ngx_directio_off_n "ngx_directio_off_n" + +size_t ngx_fs_bsize(u_char *name); + + +#define ngx_stdout GetStdHandle(STD_OUTPUT_HANDLE) +#define ngx_stderr GetStdHandle(STD_ERROR_HANDLE) +#define ngx_set_stderr(fd) SetStdHandle(STD_ERROR_HANDLE, fd) +#define ngx_set_stderr_n "SetStdHandle(STD_ERROR_HANDLE)" + + +#endif /* _NGX_FILES_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_os.h b/src/os/win32/ngx_os.h new file mode 100644 index 0000000..98210a1 --- /dev/null +++ b/src/os/win32/ngx_os.h @@ -0,0 +1,66 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_OS_H_INCLUDED_ +#define _NGX_OS_H_INCLUDED_ + + +#include +#include + + +#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_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_signal_process(ngx_cycle_t *cycle, char *sig, ngx_pid_t pid); + +ssize_t ngx_wsarecv(ngx_connection_t *c, u_char *buf, size_t size); +ssize_t ngx_overlapped_wsarecv(ngx_connection_t *c, u_char *buf, size_t size); +ssize_t ngx_udp_wsarecv(ngx_connection_t *c, u_char *buf, size_t size); +ssize_t ngx_udp_overlapped_wsarecv(ngx_connection_t *c, u_char *buf, + size_t size); +ssize_t ngx_wsarecv_chain(ngx_connection_t *c, ngx_chain_t *chain, off_t limit); +ssize_t ngx_wsasend(ngx_connection_t *c, u_char *buf, size_t size); +ssize_t ngx_overlapped_wsasend(ngx_connection_t *c, u_char *buf, size_t size); +ngx_chain_t *ngx_wsasend_chain(ngx_connection_t *c, ngx_chain_t *in, + off_t limit); +ngx_chain_t *ngx_overlapped_wsasend_chain(ngx_connection_t *c, ngx_chain_t *in, + off_t limit); + +void ngx_cdecl ngx_event_log(ngx_err_t err, const char *fmt, ...); + + +extern ngx_os_io_t ngx_os_io; +extern ngx_uint_t ngx_ncpu; +extern ngx_uint_t ngx_max_wsabufs; +extern ngx_int_t ngx_max_sockets; +extern ngx_uint_t ngx_inherited_nonblocking; +extern ngx_uint_t ngx_tcp_nodelay_and_tcp_nopush; +extern ngx_uint_t ngx_win32_version; +extern char ngx_unique[]; + + +#endif /* _NGX_OS_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_process.c b/src/os/win32/ngx_process.c new file mode 100644 index 0000000..57b1ae9 --- /dev/null +++ b/src/os/win32/ngx_process.c @@ -0,0 +1,238 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +int ngx_argc; +char **ngx_argv; +char **ngx_os_argv; + +ngx_int_t ngx_last_process; +ngx_process_t ngx_processes[NGX_MAX_PROCESSES]; + + +ngx_pid_t +ngx_spawn_process(ngx_cycle_t *cycle, char *name, ngx_int_t respawn) +{ + u_long rc, n, code; + ngx_int_t s; + ngx_pid_t pid; + ngx_exec_ctx_t ctx; + HANDLE events[2]; + char file[MAX_PATH + 1]; + + if (respawn >= 0) { + s = respawn; + + } else { + for (s = 0; s < ngx_last_process; s++) { + if (ngx_processes[s].handle == NULL) { + 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; + } + } + + n = GetModuleFileName(NULL, file, MAX_PATH); + + if (n == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "GetModuleFileName() failed"); + return NGX_INVALID_PID; + } + + file[n] = '\0'; + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, cycle->log, 0, + "GetModuleFileName: \"%s\"", file); + + ctx.path = file; + ctx.name = name; + ctx.args = GetCommandLine(); + ctx.argv = NULL; + ctx.envp = NULL; + + pid = ngx_execute(cycle, &ctx); + + if (pid == NGX_INVALID_PID) { + return pid; + } + + ngx_memzero(&ngx_processes[s], sizeof(ngx_process_t)); + + ngx_processes[s].handle = ctx.child; + ngx_processes[s].pid = pid; + ngx_processes[s].name = name; + + ngx_sprintf(ngx_processes[s].term_event, "ngx_%s_term_%P%Z", name, pid); + ngx_sprintf(ngx_processes[s].quit_event, "ngx_%s_quit_%P%Z", name, pid); + ngx_sprintf(ngx_processes[s].reopen_event, "ngx_%s_reopen_%P%Z", + name, pid); + + events[0] = ngx_master_process_event; + events[1] = ctx.child; + + rc = WaitForMultipleObjects(2, events, 0, 5000); + + ngx_time_update(); + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, cycle->log, 0, + "WaitForMultipleObjects: %ul", rc); + + switch (rc) { + + case WAIT_OBJECT_0: + + ngx_processes[s].term = OpenEvent(EVENT_MODIFY_STATE, 0, + (char *) ngx_processes[s].term_event); + if (ngx_processes[s].term == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "OpenEvent(\"%s\") failed", + ngx_processes[s].term_event); + goto failed; + } + + ngx_processes[s].quit = OpenEvent(EVENT_MODIFY_STATE, 0, + (char *) ngx_processes[s].quit_event); + if (ngx_processes[s].quit == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "OpenEvent(\"%s\") failed", + ngx_processes[s].quit_event); + goto failed; + } + + ngx_processes[s].reopen = OpenEvent(EVENT_MODIFY_STATE, 0, + (char *) ngx_processes[s].reopen_event); + if (ngx_processes[s].reopen == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "OpenEvent(\"%s\") failed", + ngx_processes[s].reopen_event); + goto failed; + } + + if (ResetEvent(ngx_master_process_event) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "ResetEvent(\"%s\") failed", + ngx_master_process_event_name); + goto failed; + } + + break; + + case WAIT_OBJECT_0 + 1: + if (GetExitCodeProcess(ctx.child, &code) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "GetExitCodeProcess(%P) failed", pid); + } + + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "%s process %P exited with code %Xl", + name, pid, code); + + goto failed; + + case WAIT_TIMEOUT: + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "the event \"%s\" was not signaled for 5s", + ngx_master_process_event_name); + goto failed; + + case WAIT_FAILED: + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "WaitForSingleObject(\"%s\") failed", + ngx_master_process_event_name); + + goto failed; + } + + if (respawn >= 0) { + return pid; + } + + switch (respawn) { + + case NGX_PROCESS_RESPAWN: + ngx_processes[s].just_spawn = 0; + break; + + case NGX_PROCESS_JUST_RESPAWN: + ngx_processes[s].just_spawn = 1; + break; + } + + if (s == ngx_last_process) { + ngx_last_process++; + } + + return pid; + +failed: + + if (ngx_processes[s].reopen) { + ngx_close_handle(ngx_processes[s].reopen); + } + + if (ngx_processes[s].quit) { + ngx_close_handle(ngx_processes[s].quit); + } + + if (ngx_processes[s].term) { + ngx_close_handle(ngx_processes[s].term); + } + + TerminateProcess(ngx_processes[s].handle, 2); + + if (ngx_processes[s].handle) { + ngx_close_handle(ngx_processes[s].handle); + ngx_processes[s].handle = NULL; + } + + return NGX_INVALID_PID; +} + + +ngx_pid_t +ngx_execute(ngx_cycle_t *cycle, ngx_exec_ctx_t *ctx) +{ + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ngx_memzero(&si, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + + ngx_memzero(&pi, sizeof(PROCESS_INFORMATION)); + + if (CreateProcess(ctx->path, ctx->args, + NULL, NULL, 0, CREATE_NO_WINDOW, NULL, NULL, &si, &pi) + == 0) + { + ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_errno, + "CreateProcess(\"%s\") failed", ngx_argv[0]); + + return 0; + } + + ctx->child = pi.hProcess; + + if (CloseHandle(pi.hThread) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "CloseHandle(pi.hThread) failed"); + } + + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, + "start %s process %P", ctx->name, pi.dwProcessId); + + return pi.dwProcessId; +} diff --git a/src/os/win32/ngx_process.h b/src/os/win32/ngx_process.h new file mode 100644 index 0000000..a6a5aa2 --- /dev/null +++ b/src/os/win32/ngx_process.h @@ -0,0 +1,78 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_PROCESS_H_INCLUDED_ +#define _NGX_PROCESS_H_INCLUDED_ + + +typedef DWORD ngx_pid_t; +#define NGX_INVALID_PID 0 + + +#define ngx_getpid GetCurrentProcessId +#define ngx_log_pid ngx_pid + + +#define NGX_PROCESS_SYNC_NAME \ + (sizeof("ngx_cache_manager_mutex_") + NGX_INT32_LEN) + + +typedef uint64_t ngx_cpuset_t; + + +typedef struct { + HANDLE handle; + ngx_pid_t pid; + char *name; + + HANDLE term; + HANDLE quit; + HANDLE reopen; + + u_char term_event[NGX_PROCESS_SYNC_NAME]; + u_char quit_event[NGX_PROCESS_SYNC_NAME]; + u_char reopen_event[NGX_PROCESS_SYNC_NAME]; + + unsigned just_spawn:1; + unsigned exiting:1; +} ngx_process_t; + + +typedef struct { + char *path; + char *name; + char *args; + char *const *argv; + char *const *envp; + HANDLE child; +} ngx_exec_ctx_t; + + +ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle, char *name, ngx_int_t respawn); +ngx_pid_t ngx_execute(ngx_cycle_t *cycle, ngx_exec_ctx_t *ctx); + +#define ngx_debug_point() +#define ngx_sched_yield() SwitchToThread() + + +#define NGX_MAX_PROCESSES (MAXIMUM_WAIT_OBJECTS - 4) + +#define NGX_PROCESS_RESPAWN -2 +#define NGX_PROCESS_JUST_RESPAWN -3 + + +extern int ngx_argc; +extern char **ngx_argv; +extern char **ngx_os_argv; + +extern ngx_int_t ngx_last_process; +extern ngx_process_t ngx_processes[NGX_MAX_PROCESSES]; + +extern ngx_pid_t ngx_pid; + + +#endif /* _NGX_PROCESS_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_process_cycle.c b/src/os/win32/ngx_process_cycle.c new file mode 100644 index 0000000..795e41e --- /dev/null +++ b/src/os/win32/ngx_process_cycle.c @@ -0,0 +1,1041 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +static void ngx_console_init(ngx_cycle_t *cycle); +static int __stdcall ngx_console_handler(u_long type); +static ngx_int_t ngx_create_signal_events(ngx_cycle_t *cycle); +static ngx_int_t ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t type); +static void ngx_reopen_worker_processes(ngx_cycle_t *cycle); +static void ngx_quit_worker_processes(ngx_cycle_t *cycle, ngx_uint_t old); +static void ngx_terminate_worker_processes(ngx_cycle_t *cycle); +static ngx_uint_t ngx_reap_worker(ngx_cycle_t *cycle, HANDLE h); +static void ngx_master_process_exit(ngx_cycle_t *cycle); +static void ngx_worker_process_cycle(ngx_cycle_t *cycle, char *mevn); +static void ngx_worker_process_exit(ngx_cycle_t *cycle); +static ngx_thread_value_t __stdcall ngx_worker_thread(void *data); +static ngx_thread_value_t __stdcall ngx_cache_manager_thread(void *data); +static void ngx_cache_manager_process_handler(void); +static ngx_thread_value_t __stdcall ngx_cache_loader_thread(void *data); + + +ngx_uint_t ngx_process; +ngx_uint_t ngx_worker; +ngx_pid_t ngx_pid; + +ngx_uint_t ngx_inherited; +ngx_pid_t ngx_new_binary; + +sig_atomic_t ngx_terminate; +sig_atomic_t ngx_quit; +sig_atomic_t ngx_reopen; +sig_atomic_t ngx_reconfigure; +ngx_uint_t ngx_exiting; + + +HANDLE ngx_master_process_event; +char ngx_master_process_event_name[NGX_PROCESS_SYNC_NAME]; + +static HANDLE ngx_stop_event; +static char ngx_stop_event_name[NGX_PROCESS_SYNC_NAME]; +static HANDLE ngx_quit_event; +static char ngx_quit_event_name[NGX_PROCESS_SYNC_NAME]; +static HANDLE ngx_reopen_event; +static char ngx_reopen_event_name[NGX_PROCESS_SYNC_NAME]; +static HANDLE ngx_reload_event; +static char ngx_reload_event_name[NGX_PROCESS_SYNC_NAME]; + +HANDLE ngx_cache_manager_mutex; +char ngx_cache_manager_mutex_name[NGX_PROCESS_SYNC_NAME]; +HANDLE ngx_cache_manager_event; + + +void +ngx_master_process_cycle(ngx_cycle_t *cycle) +{ + u_long nev, ev, timeout; + ngx_err_t err; + ngx_int_t n; + ngx_msec_t timer; + ngx_uint_t live; + HANDLE events[MAXIMUM_WAIT_OBJECTS]; + + ngx_sprintf((u_char *) ngx_master_process_event_name, + "ngx_master_%s%Z", ngx_unique); + + if (ngx_process == NGX_PROCESS_WORKER) { + ngx_worker_process_cycle(cycle, ngx_master_process_event_name); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, cycle->log, 0, "master started"); + + ngx_console_init(cycle); + + SetEnvironmentVariable("ngx_unique", ngx_unique); + + ngx_master_process_event = CreateEvent(NULL, 1, 0, + ngx_master_process_event_name); + if (ngx_master_process_event == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "CreateEvent(\"%s\") failed", + ngx_master_process_event_name); + exit(2); + } + + if (ngx_create_signal_events(cycle) != NGX_OK) { + exit(2); + } + + ngx_sprintf((u_char *) ngx_cache_manager_mutex_name, + "ngx_cache_manager_mutex_%s%Z", ngx_unique); + + ngx_cache_manager_mutex = CreateMutex(NULL, 0, + ngx_cache_manager_mutex_name); + if (ngx_cache_manager_mutex == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "CreateMutex(\"%s\") failed", ngx_cache_manager_mutex_name); + exit(2); + } + + + events[0] = ngx_stop_event; + events[1] = ngx_quit_event; + events[2] = ngx_reopen_event; + events[3] = ngx_reload_event; + + ngx_close_listening_sockets(cycle); + + if (ngx_start_worker_processes(cycle, NGX_PROCESS_RESPAWN) == 0) { + exit(2); + } + + timer = 0; + timeout = INFINITE; + + for ( ;; ) { + + nev = 4; + for (n = 0; n < ngx_last_process; n++) { + if (ngx_processes[n].handle) { + events[nev++] = ngx_processes[n].handle; + } + } + + if (timer) { + timeout = timer > ngx_current_msec ? timer - ngx_current_msec : 0; + } + + ev = WaitForMultipleObjects(nev, events, 0, timeout); + + err = ngx_errno; + ngx_time_update(); + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, cycle->log, 0, + "master WaitForMultipleObjects: %ul", ev); + + if (ev == WAIT_OBJECT_0) { + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting"); + + if (ResetEvent(ngx_stop_event) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "ResetEvent(\"%s\") failed", ngx_stop_event_name); + } + + if (timer == 0) { + timer = ngx_current_msec + 5000; + } + + ngx_terminate = 1; + ngx_quit_worker_processes(cycle, 0); + + continue; + } + + if (ev == WAIT_OBJECT_0 + 1) { + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "shutting down"); + + if (ResetEvent(ngx_quit_event) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "ResetEvent(\"%s\") failed", ngx_quit_event_name); + } + + ngx_quit = 1; + ngx_quit_worker_processes(cycle, 0); + + continue; + } + + if (ev == WAIT_OBJECT_0 + 2) { + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs"); + + if (ResetEvent(ngx_reopen_event) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "ResetEvent(\"%s\") failed", + ngx_reopen_event_name); + } + + ngx_reopen_files(cycle, -1); + ngx_reopen_worker_processes(cycle); + + continue; + } + + if (ev == WAIT_OBJECT_0 + 3) { + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring"); + + if (ResetEvent(ngx_reload_event) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "ResetEvent(\"%s\") failed", + ngx_reload_event_name); + } + + cycle = ngx_init_cycle(cycle); + if (cycle == NULL) { + cycle = (ngx_cycle_t *) ngx_cycle; + continue; + } + + ngx_cycle = cycle; + + ngx_close_listening_sockets(cycle); + + if (ngx_start_worker_processes(cycle, NGX_PROCESS_JUST_RESPAWN)) { + ngx_quit_worker_processes(cycle, 1); + } + + continue; + } + + if (ev > WAIT_OBJECT_0 + 3 && ev < WAIT_OBJECT_0 + nev) { + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, cycle->log, 0, "reap worker"); + + live = ngx_reap_worker(cycle, events[ev]); + + if (!live && (ngx_terminate || ngx_quit)) { + ngx_master_process_exit(cycle); + } + + continue; + } + + if (ev == WAIT_TIMEOUT) { + ngx_terminate_worker_processes(cycle); + + ngx_master_process_exit(cycle); + } + + if (ev == WAIT_FAILED) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, err, + "WaitForMultipleObjects() failed"); + + continue; + } + + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "WaitForMultipleObjects() returned unexpected value %ul", ev); + } +} + + +static void +ngx_console_init(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->daemon) { + if (FreeConsole() == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "FreeConsole() failed"); + } + + return; + } + + if (SetConsoleCtrlHandler(ngx_console_handler, 1) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "SetConsoleCtrlHandler() failed"); + } +} + + +static int __stdcall +ngx_console_handler(u_long type) +{ + char *msg; + + switch (type) { + + case CTRL_C_EVENT: + msg = "Ctrl-C pressed, exiting"; + break; + + case CTRL_BREAK_EVENT: + msg = "Ctrl-Break pressed, exiting"; + break; + + case CTRL_CLOSE_EVENT: + msg = "console closing, exiting"; + break; + + case CTRL_LOGOFF_EVENT: + msg = "user logs off, exiting"; + break; + + default: + return 0; + } + + ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0, msg); + + if (ngx_stop_event == NULL) { + return 1; + } + + if (SetEvent(ngx_stop_event) == 0) { + ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, + "SetEvent(\"%s\") failed", ngx_stop_event_name); + } + + return 1; +} + + +static ngx_int_t +ngx_create_signal_events(ngx_cycle_t *cycle) +{ + ngx_sprintf((u_char *) ngx_stop_event_name, + "Global\\ngx_stop_%s%Z", ngx_unique); + + ngx_stop_event = CreateEvent(NULL, 1, 0, ngx_stop_event_name); + if (ngx_stop_event == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "CreateEvent(\"%s\") failed", ngx_stop_event_name); + return NGX_ERROR; + } + + + ngx_sprintf((u_char *) ngx_quit_event_name, + "Global\\ngx_quit_%s%Z", ngx_unique); + + ngx_quit_event = CreateEvent(NULL, 1, 0, ngx_quit_event_name); + if (ngx_quit_event == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "CreateEvent(\"%s\") failed", ngx_quit_event_name); + return NGX_ERROR; + } + + + ngx_sprintf((u_char *) ngx_reopen_event_name, + "Global\\ngx_reopen_%s%Z", ngx_unique); + + ngx_reopen_event = CreateEvent(NULL, 1, 0, ngx_reopen_event_name); + if (ngx_reopen_event == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "CreateEvent(\"%s\") failed", ngx_reopen_event_name); + return NGX_ERROR; + } + + + ngx_sprintf((u_char *) ngx_reload_event_name, + "Global\\ngx_reload_%s%Z", ngx_unique); + + ngx_reload_event = CreateEvent(NULL, 1, 0, ngx_reload_event_name); + if (ngx_reload_event == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "CreateEvent(\"%s\") failed", ngx_reload_event_name); + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t type) +{ + ngx_int_t n; + ngx_core_conf_t *ccf; + + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start worker processes"); + + ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); + + for (n = 0; n < ccf->worker_processes; n++) { + if (ngx_spawn_process(cycle, "worker", type) == NGX_INVALID_PID) { + break; + } + } + + return n; +} + + +static void +ngx_reopen_worker_processes(ngx_cycle_t *cycle) +{ + ngx_int_t n; + + for (n = 0; n < ngx_last_process; n++) { + + if (ngx_processes[n].handle == NULL) { + continue; + } + + if (SetEvent(ngx_processes[n].reopen) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "SetEvent(\"%s\") failed", + ngx_processes[n].reopen_event); + } + } +} + + +static void +ngx_quit_worker_processes(ngx_cycle_t *cycle, ngx_uint_t old) +{ + ngx_int_t n; + + for (n = 0; n < ngx_last_process; n++) { + + ngx_log_debug5(NGX_LOG_DEBUG_CORE, cycle->log, 0, + "process: %d %P %p e:%d j:%d", + n, + ngx_processes[n].pid, + ngx_processes[n].handle, + ngx_processes[n].exiting, + ngx_processes[n].just_spawn); + + if (old && ngx_processes[n].just_spawn) { + ngx_processes[n].just_spawn = 0; + continue; + } + + if (ngx_processes[n].handle == NULL) { + continue; + } + + if (SetEvent(ngx_processes[n].quit) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "SetEvent(\"%s\") failed", + ngx_processes[n].quit_event); + } + + ngx_processes[n].exiting = 1; + } +} + + +static void +ngx_terminate_worker_processes(ngx_cycle_t *cycle) +{ + ngx_int_t n; + + for (n = 0; n < ngx_last_process; n++) { + + if (ngx_processes[n].handle == NULL) { + continue; + } + + if (TerminateProcess(ngx_processes[n].handle, 0) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "TerminateProcess(\"%p\") failed", + ngx_processes[n].handle); + } + + ngx_processes[n].exiting = 1; + + ngx_close_handle(ngx_processes[n].reopen); + ngx_close_handle(ngx_processes[n].quit); + ngx_close_handle(ngx_processes[n].term); + ngx_close_handle(ngx_processes[n].handle); + } +} + + +static ngx_uint_t +ngx_reap_worker(ngx_cycle_t *cycle, HANDLE h) +{ + u_long code; + ngx_int_t n; + + for (n = 0; n < ngx_last_process; n++) { + + if (ngx_processes[n].handle != h) { + continue; + } + + if (GetExitCodeProcess(h, &code) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "GetExitCodeProcess(%P) failed", + ngx_processes[n].pid); + } + + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, + "%s process %P exited with code %Xl", + ngx_processes[n].name, ngx_processes[n].pid, code); + + ngx_close_handle(ngx_processes[n].reopen); + ngx_close_handle(ngx_processes[n].quit); + ngx_close_handle(ngx_processes[n].term); + ngx_close_handle(h); + + ngx_processes[n].handle = NULL; + ngx_processes[n].term = NULL; + ngx_processes[n].quit = NULL; + ngx_processes[n].reopen = NULL; + + if (!ngx_processes[n].exiting && !ngx_terminate && !ngx_quit) { + + if (ngx_spawn_process(cycle, ngx_processes[n].name, n) + == NGX_INVALID_PID) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "could not respawn %s", ngx_processes[n].name); + + if (n == ngx_last_process - 1) { + ngx_last_process--; + } + } + } + + goto found; + } + + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "unknown process handle %p", h); + +found: + + for (n = 0; n < ngx_last_process; n++) { + + ngx_log_debug5(NGX_LOG_DEBUG_CORE, cycle->log, 0, + "process: %d %P %p e:%d j:%d", + n, + ngx_processes[n].pid, + ngx_processes[n].handle, + ngx_processes[n].exiting, + ngx_processes[n].just_spawn); + + if (ngx_processes[n].handle) { + return 1; + } + } + + return 0; +} + + +static void +ngx_master_process_exit(ngx_cycle_t *cycle) +{ + ngx_uint_t i; + + ngx_delete_pidfile(cycle); + + ngx_close_handle(ngx_cache_manager_mutex); + ngx_close_handle(ngx_stop_event); + ngx_close_handle(ngx_quit_event); + ngx_close_handle(ngx_reopen_event); + ngx_close_handle(ngx_reload_event); + ngx_close_handle(ngx_master_process_event); + + 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_destroy_pool(cycle->pool); + + exit(0); +} + + +static void +ngx_worker_process_cycle(ngx_cycle_t *cycle, char *mevn) +{ + char wtevn[NGX_PROCESS_SYNC_NAME]; + char wqevn[NGX_PROCESS_SYNC_NAME]; + char wroevn[NGX_PROCESS_SYNC_NAME]; + HANDLE mev, events[3]; + u_long nev, ev; + ngx_err_t err; + ngx_tid_t wtid, cmtid, cltid; + ngx_log_t *log; + + log = cycle->log; + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, log, 0, "worker started"); + + ngx_sprintf((u_char *) wtevn, "ngx_worker_term_%P%Z", ngx_pid); + events[0] = CreateEvent(NULL, 1, 0, wtevn); + if (events[0] == NULL) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "CreateEvent(\"%s\") failed", wtevn); + goto failed; + } + + ngx_sprintf((u_char *) wqevn, "ngx_worker_quit_%P%Z", ngx_pid); + events[1] = CreateEvent(NULL, 1, 0, wqevn); + if (events[1] == NULL) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "CreateEvent(\"%s\") failed", wqevn); + goto failed; + } + + ngx_sprintf((u_char *) wroevn, "ngx_worker_reopen_%P%Z", ngx_pid); + events[2] = CreateEvent(NULL, 1, 0, wroevn); + if (events[2] == NULL) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "CreateEvent(\"%s\") failed", wroevn); + goto failed; + } + + mev = OpenEvent(EVENT_MODIFY_STATE, 0, mevn); + if (mev == NULL) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "OpenEvent(\"%s\") failed", mevn); + goto failed; + } + + if (SetEvent(mev) == 0) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "SetEvent(\"%s\") failed", mevn); + goto failed; + } + + + ngx_sprintf((u_char *) ngx_cache_manager_mutex_name, + "ngx_cache_manager_mutex_%s%Z", ngx_unique); + + ngx_cache_manager_mutex = OpenMutex(SYNCHRONIZE, 0, + ngx_cache_manager_mutex_name); + if (ngx_cache_manager_mutex == NULL) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "OpenMutex(\"%s\") failed", ngx_cache_manager_mutex_name); + goto failed; + } + + ngx_cache_manager_event = CreateEvent(NULL, 1, 0, NULL); + if (ngx_cache_manager_event == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "CreateEvent(\"ngx_cache_manager_event\") failed"); + goto failed; + } + + + if (ngx_create_thread(&wtid, ngx_worker_thread, NULL, log) != 0) { + goto failed; + } + + if (ngx_create_thread(&cmtid, ngx_cache_manager_thread, NULL, log) != 0) { + goto failed; + } + + if (ngx_create_thread(&cltid, ngx_cache_loader_thread, NULL, log) != 0) { + goto failed; + } + + for ( ;; ) { + ev = WaitForMultipleObjects(3, events, 0, INFINITE); + + err = ngx_errno; + ngx_time_update(); + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, + "worker WaitForMultipleObjects: %ul", ev); + + if (ev == WAIT_OBJECT_0) { + ngx_terminate = 1; + ngx_log_error(NGX_LOG_NOTICE, log, 0, "exiting"); + + if (ResetEvent(events[0]) == 0) { + ngx_log_error(NGX_LOG_ALERT, log, 0, + "ResetEvent(\"%s\") failed", wtevn); + } + + break; + } + + if (ev == WAIT_OBJECT_0 + 1) { + ngx_quit = 1; + ngx_log_error(NGX_LOG_NOTICE, log, 0, "gracefully shutting down"); + break; + } + + if (ev == WAIT_OBJECT_0 + 2) { + ngx_reopen = 1; + ngx_log_error(NGX_LOG_NOTICE, log, 0, "reopening logs"); + + if (ResetEvent(events[2]) == 0) { + ngx_log_error(NGX_LOG_ALERT, log, 0, + "ResetEvent(\"%s\") failed", wroevn); + } + + continue; + } + + if (ev == WAIT_FAILED) { + ngx_log_error(NGX_LOG_ALERT, log, err, + "WaitForMultipleObjects() failed"); + + goto failed; + } + } + + /* wait threads */ + + if (SetEvent(ngx_cache_manager_event) == 0) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "SetEvent(\"ngx_cache_manager_event\") failed"); + } + + events[1] = wtid; + events[2] = cmtid; + + nev = 3; + + for ( ;; ) { + ev = WaitForMultipleObjects(nev, events, 0, INFINITE); + + err = ngx_errno; + ngx_time_update(); + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, + "worker exit WaitForMultipleObjects: %ul", ev); + + if (ev == WAIT_OBJECT_0) { + break; + } + + if (ev == WAIT_OBJECT_0 + 1) { + if (nev == 2) { + break; + } + + events[1] = events[2]; + nev = 2; + continue; + } + + if (ev == WAIT_OBJECT_0 + 2) { + nev = 2; + continue; + } + + if (ev == WAIT_FAILED) { + ngx_log_error(NGX_LOG_ALERT, log, err, + "WaitForMultipleObjects() failed"); + break; + } + } + + ngx_close_handle(ngx_cache_manager_event); + ngx_close_handle(events[0]); + ngx_close_handle(events[1]); + ngx_close_handle(events[2]); + ngx_close_handle(mev); + + ngx_worker_process_exit(cycle); + +failed: + + exit(2); +} + + +static ngx_thread_value_t __stdcall +ngx_worker_thread(void *data) +{ + ngx_int_t n; + ngx_cycle_t *cycle; + + cycle = (ngx_cycle_t *) ngx_cycle; + + for (n = 0; cycle->modules[n]; n++) { + if (cycle->modules[n]->init_process) { + if (cycle->modules[n]->init_process(cycle) == NGX_ERROR) { + /* fatal */ + exit(2); + } + } + } + + while (!ngx_quit) { + + if (ngx_exiting) { + ngx_event_cancel_timers(); + + if (ngx_event_timer_rbtree.root + == ngx_event_timer_rbtree.sentinel) + { + break; + } + } + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, cycle->log, 0, "worker cycle"); + + ngx_process_events_and_timers(cycle); + + if (ngx_terminate) { + return 0; + } + + if (ngx_quit) { + ngx_quit = 0; + + if (!ngx_exiting) { + ngx_exiting = 1; + ngx_close_listening_sockets(cycle); + ngx_close_idle_connections(cycle); + } + } + + if (ngx_reopen) { + ngx_reopen = 0; + ngx_reopen_files(cycle, -1); + } + } + + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting"); + + return 0; +} + + +static void +ngx_worker_process_exit(ngx_cycle_t *cycle) +{ + ngx_uint_t i; + ngx_connection_t *c; + + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exit"); + + 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 != (ngx_socket_t) -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_destroy_pool(cycle->pool); + + exit(0); +} + + +static ngx_thread_value_t __stdcall +ngx_cache_manager_thread(void *data) +{ + u_long ev; + HANDLE events[2]; + ngx_err_t err; + ngx_cycle_t *cycle; + + cycle = (ngx_cycle_t *) ngx_cycle; + + events[0] = ngx_cache_manager_event; + events[1] = ngx_cache_manager_mutex; + + for ( ;; ) { + ev = WaitForMultipleObjects(2, events, 0, INFINITE); + + err = ngx_errno; + ngx_time_update(); + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, cycle->log, 0, + "cache manager WaitForMultipleObjects: %ul", ev); + + if (ev == WAIT_FAILED) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, err, + "WaitForMultipleObjects() failed"); + } + + /* + * ev == WAIT_OBJECT_0 + * ev == WAIT_OBJECT_0 + 1 + * ev == WAIT_ABANDONED_0 + 1 + */ + + if (ngx_terminate || ngx_quit || ngx_exiting) { + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting"); + return 0; + } + + break; + } + + for ( ;; ) { + + if (ngx_terminate || ngx_quit || ngx_exiting) { + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting"); + break; + } + + ngx_cache_manager_process_handler(); + } + + if (ReleaseMutex(ngx_cache_manager_mutex) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "ReleaseMutex() failed"); + } + + return 0; +} + + +static void +ngx_cache_manager_process_handler(void) +{ + u_long ev; + time_t next, n; + ngx_uint_t i; + ngx_path_t **path; + + next = 60 * 60; + + 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; + } + + ev = WaitForSingleObject(ngx_cache_manager_event, (u_long) next * 1000); + + if (ev != WAIT_TIMEOUT) { + + ngx_time_update(); + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, + "cache manager WaitForSingleObject: %ul", ev); + } +} + + +static ngx_thread_value_t __stdcall +ngx_cache_loader_thread(void *data) +{ + ngx_uint_t i; + ngx_path_t **path; + ngx_cycle_t *cycle; + + ngx_msleep(60000); + + cycle = (ngx_cycle_t *) ngx_cycle; + + path = cycle->paths.elts; + for (i = 0; i < cycle->paths.nelts; i++) { + + if (ngx_terminate || ngx_quit || ngx_exiting) { + break; + } + + if (path[i]->loader) { + path[i]->loader(path[i]->data); + ngx_time_update(); + } + } + + return 0; +} + + +void +ngx_single_process_cycle(ngx_cycle_t *cycle) +{ + ngx_tid_t tid; + + ngx_console_init(cycle); + + if (ngx_create_signal_events(cycle) != NGX_OK) { + exit(2); + } + + if (ngx_create_thread(&tid, ngx_worker_thread, NULL, cycle->log) != 0) { + /* fatal */ + exit(2); + } + + /* STUB */ + WaitForSingleObject(ngx_stop_event, INFINITE); +} + + +ngx_int_t +ngx_os_signal_process(ngx_cycle_t *cycle, char *sig, ngx_pid_t pid) +{ + HANDLE ev; + ngx_int_t rc; + char evn[NGX_PROCESS_SYNC_NAME]; + + ngx_sprintf((u_char *) evn, "Global\\ngx_%s_%P%Z", sig, pid); + + ev = OpenEvent(EVENT_MODIFY_STATE, 0, evn); + if (ev == NULL) { + ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno, + "OpenEvent(\"%s\") failed", evn); + return 1; + } + + if (SetEvent(ev) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "SetEvent(\"%s\") failed", evn); + rc = 1; + + } else { + rc = 0; + } + + ngx_close_handle(ev); + + return rc; +} + + +void +ngx_close_handle(HANDLE h) +{ + if (CloseHandle(h) == 0) { + ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno, + "CloseHandle(%p) failed", h); + } +} diff --git a/src/os/win32/ngx_process_cycle.h b/src/os/win32/ngx_process_cycle.h new file mode 100644 index 0000000..95d2743 --- /dev/null +++ b/src/os/win32/ngx_process_cycle.h @@ -0,0 +1,44 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_PROCESS_CYCLE_H_INCLUDED_ +#define _NGX_PROCESS_CYCLE_H_INCLUDED_ + + +#include +#include + + +#define NGX_PROCESS_SINGLE 0 +#define NGX_PROCESS_MASTER 1 +#define NGX_PROCESS_SIGNALLER 2 +#define NGX_PROCESS_WORKER 3 + + +void ngx_master_process_cycle(ngx_cycle_t *cycle); +void ngx_single_process_cycle(ngx_cycle_t *cycle); +void ngx_close_handle(HANDLE h); + + +extern ngx_uint_t ngx_process; +extern ngx_uint_t ngx_worker; +extern ngx_pid_t ngx_pid; +extern ngx_uint_t ngx_exiting; + +extern sig_atomic_t ngx_quit; +extern sig_atomic_t ngx_terminate; +extern sig_atomic_t ngx_reopen; + +extern ngx_uint_t ngx_inherited; +extern ngx_pid_t ngx_new_binary; + + +extern HANDLE ngx_master_process_event; +extern char ngx_master_process_event_name[]; + + +#endif /* _NGX_PROCESS_CYCLE_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_service.c b/src/os/win32/ngx_service.c new file mode 100644 index 0000000..835d9cf --- /dev/null +++ b/src/os/win32/ngx_service.c @@ -0,0 +1,134 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + + +#define NGX_SERVICE_CONTROL_SHUTDOWN 128 +#define NGX_SERVICE_CONTROL_REOPEN 129 + + +SERVICE_TABLE_ENTRY st[] = { + { "nginx", service_main }, + { NULL, NULL } +}; + + +ngx_int_t +ngx_service(ngx_log_t *log) +{ + /* primary thread */ + + /* StartServiceCtrlDispatcher() should be called within 30 seconds */ + + if (StartServiceCtrlDispatcher(st) == 0) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + "StartServiceCtrlDispatcher() failed"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +void +service_main(u_int argc, char **argv) +{ + SERVICE_STATUS status; + SERVICE_STATUS_HANDLE service; + + /* thread spawned by SCM */ + + service = RegisterServiceCtrlHandlerEx("nginx", service_handler, ctx); + if (service == INVALID_HANDLE_VALUE) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + "RegisterServiceCtrlHandlerEx() failed"); + return; + } + + status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + status.dwCurrentState = SERVICE_START_PENDING; + status.dwControlsAccepted = SERVICE_ACCEPT_STOP + |SERVICE_ACCEPT_PARAMCHANGE; + status.dwWin32ExitCode = NO_ERROR; + status.dwServiceSpecificExitCode = 0; + status.dwCheckPoint = 1; + status.dwWaitHint = 2000; + + /* SetServiceStatus() should be called within 80 seconds */ + + if (SetServiceStatus(service, &status) == 0) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + "SetServiceStatus() failed"); + return; + } + + /* init */ + + status.dwCurrentState = SERVICE_RUNNING; + status.dwCheckPoint = 0; + status.dwWaitHint = 0; + + if (SetServiceStatus(service, &status) == 0) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + "SetServiceStatus() failed"); + return; + } + + /* call master or worker loop */ + + /* + * master should use event notification and look status + * single should use iocp to get notifications from service handler + */ + +} + + +u_int +service_handler(u_int control, u_int type, void *data, void *ctx) +{ + /* primary thread */ + + switch (control) { + + case SERVICE_CONTROL_INTERROGATE: + status = NGX_IOCP_INTERROGATE; + break; + + case SERVICE_CONTROL_STOP: + status = NGX_IOCP_STOP; + break; + + case SERVICE_CONTROL_PARAMCHANGE: + status = NGX_IOCP_RECONFIGURE; + break; + + case NGX_SERVICE_CONTROL_SHUTDOWN: + status = NGX_IOCP_REOPEN; + break; + + case NGX_SERVICE_CONTROL_REOPEN: + status = NGX_IOCP_REOPEN; + break; + + default: + return ERROR_CALL_NOT_IMPLEMENTED; + } + + if (ngx_single) { + if (PostQueuedCompletionStatus(iocp, ... status, ...) == 0) { + err = ngx_errno; + ngx_log_error(NGX_LOG_ALERT, log, err, + "PostQueuedCompletionStatus() failed"); + return err; + } + + } else { + Event + } + + return NO_ERROR; +} diff --git a/src/os/win32/ngx_shmem.c b/src/os/win32/ngx_shmem.c new file mode 100644 index 0000000..c3ed699 --- /dev/null +++ b/src/os/win32/ngx_shmem.c @@ -0,0 +1,161 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +/* + * Base addresses selected by system for shared memory mappings are likely + * to be different on Windows Vista and later versions due to address space + * layout randomization. This is however incompatible with storing absolute + * addresses within the shared memory. + * + * To make it possible to store absolute addresses we create mappings + * at the same address in all processes by starting mappings at predefined + * addresses. The addresses were selected somewhat randomly in order to + * minimize the probability that some other library doing something similar + * conflicts with us. The addresses are from the following typically free + * blocks: + * + * - 0x10000000 .. 0x70000000 (about 1.5 GB in total) on 32-bit platforms + * - 0x000000007fff0000 .. 0x000007f68e8b0000 (about 8 TB) on 64-bit platforms + * + * Additionally, we allow to change the mapping address once it was detected + * to be different from one originally used. This is needed to support + * reconfiguration. + */ + + +#ifdef _WIN64 +#define NGX_SHMEM_BASE 0x0000047047e00000 +#else +#define NGX_SHMEM_BASE 0x2efe0000 +#endif + + +ngx_uint_t ngx_allocation_granularity; + + +ngx_int_t +ngx_shm_alloc(ngx_shm_t *shm) +{ + u_char *name; + uint64_t size; + static u_char *base = (u_char *) NGX_SHMEM_BASE; + + name = ngx_alloc(shm->name.len + 2 + NGX_INT32_LEN, shm->log); + if (name == NULL) { + return NGX_ERROR; + } + + (void) ngx_sprintf(name, "%V_%s%Z", &shm->name, ngx_unique); + + ngx_set_errno(0); + + size = shm->size; + + shm->handle = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, + (u_long) (size >> 32), + (u_long) (size & 0xffffffff), + (char *) name); + + if (shm->handle == NULL) { + ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, + "CreateFileMapping(%uz, %s) failed", + shm->size, name); + ngx_free(name); + + return NGX_ERROR; + } + + ngx_free(name); + + if (ngx_errno == ERROR_ALREADY_EXISTS) { + shm->exists = 1; + } + + shm->addr = MapViewOfFileEx(shm->handle, FILE_MAP_WRITE, 0, 0, 0, base); + + if (shm->addr != NULL) { + base += ngx_align(size, ngx_allocation_granularity); + return NGX_OK; + } + + ngx_log_debug3(NGX_LOG_DEBUG_CORE, shm->log, ngx_errno, + "MapViewOfFileEx(%uz, %p) of file mapping \"%V\" failed, " + "retry without a base address", + shm->size, base, &shm->name); + + /* + * Order of shared memory zones may be different in the master process + * and worker processes after reconfiguration. As a result, the above + * may fail due to a conflict with a previously created mapping remapped + * to a different address. Additionally, there may be a conflict with + * some other uses of the memory. In this case we retry without a base + * address to let the system assign the address itself. + */ + + shm->addr = MapViewOfFile(shm->handle, FILE_MAP_WRITE, 0, 0, 0); + + if (shm->addr != NULL) { + return NGX_OK; + } + + ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, + "MapViewOfFile(%uz) of file mapping \"%V\" failed", + shm->size, &shm->name); + + if (CloseHandle(shm->handle) == 0) { + ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, + "CloseHandle() of file mapping \"%V\" failed", + &shm->name); + } + + return NGX_ERROR; +} + + +ngx_int_t +ngx_shm_remap(ngx_shm_t *shm, u_char *addr) +{ + if (UnmapViewOfFile(shm->addr) == 0) { + ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, + "UnmapViewOfFile(%p) of file mapping \"%V\" failed", + shm->addr, &shm->name); + return NGX_ERROR; + } + + shm->addr = MapViewOfFileEx(shm->handle, FILE_MAP_WRITE, 0, 0, 0, addr); + + if (shm->addr != NULL) { + return NGX_OK; + } + + ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, + "MapViewOfFileEx(%uz, %p) of file mapping \"%V\" failed", + shm->size, addr, &shm->name); + + return NGX_ERROR; +} + + +void +ngx_shm_free(ngx_shm_t *shm) +{ + if (UnmapViewOfFile(shm->addr) == 0) { + ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, + "UnmapViewOfFile(%p) of file mapping \"%V\" failed", + shm->addr, &shm->name); + } + + if (CloseHandle(shm->handle) == 0) { + ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, + "CloseHandle() of file mapping \"%V\" failed", + &shm->name); + } +} diff --git a/src/os/win32/ngx_shmem.h b/src/os/win32/ngx_shmem.h new file mode 100644 index 0000000..ee47429 --- /dev/null +++ b/src/os/win32/ngx_shmem.h @@ -0,0 +1,33 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_SHMEM_H_INCLUDED_ +#define _NGX_SHMEM_H_INCLUDED_ + + +#include +#include + + +typedef struct { + u_char *addr; + size_t size; + ngx_str_t name; + HANDLE handle; + ngx_log_t *log; + ngx_uint_t exists; /* unsigned exists:1; */ +} ngx_shm_t; + + +ngx_int_t ngx_shm_alloc(ngx_shm_t *shm); +ngx_int_t ngx_shm_remap(ngx_shm_t *shm, u_char *addr); +void ngx_shm_free(ngx_shm_t *shm); + +extern ngx_uint_t ngx_allocation_granularity; + + +#endif /* _NGX_SHMEM_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_socket.c b/src/os/win32/ngx_socket.c new file mode 100644 index 0000000..05a39f4 --- /dev/null +++ b/src/os/win32/ngx_socket.c @@ -0,0 +1,34 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +int +ngx_nonblocking(ngx_socket_t s) +{ + unsigned long nb = 1; + + return ioctlsocket(s, FIONBIO, &nb); +} + + +int +ngx_blocking(ngx_socket_t s) +{ + unsigned long nb = 0; + + return ioctlsocket(s, FIONBIO, &nb); +} + + +int +ngx_tcp_push(ngx_socket_t s) +{ + return 0; +} diff --git a/src/os/win32/ngx_socket.h b/src/os/win32/ngx_socket.h new file mode 100644 index 0000000..a9e26c2 --- /dev/null +++ b/src/os/win32/ngx_socket.h @@ -0,0 +1,207 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_SOCKET_H_INCLUDED_ +#define _NGX_SOCKET_H_INCLUDED_ + + +#include +#include + + +#define NGX_WRITE_SHUTDOWN SD_SEND + + +typedef SOCKET ngx_socket_t; +typedef int socklen_t; + + +#define ngx_socket(af, type, proto) \ + WSASocketW(af, type, proto, NULL, 0, WSA_FLAG_OVERLAPPED) + +#define ngx_socket_n "WSASocketW()" + +int ngx_nonblocking(ngx_socket_t s); +int ngx_blocking(ngx_socket_t s); + +#define ngx_nonblocking_n "ioctlsocket(FIONBIO)" +#define ngx_blocking_n "ioctlsocket(!FIONBIO)" + +#define ngx_shutdown_socket shutdown +#define ngx_shutdown_socket_n "shutdown()" + +#define ngx_close_socket closesocket +#define ngx_close_socket_n "closesocket()" + + +#ifndef WSAID_ACCEPTEX + +typedef BOOL (PASCAL FAR * LPFN_ACCEPTEX)( + IN SOCKET sListenSocket, + IN SOCKET sAcceptSocket, + IN PVOID lpOutputBuffer, + IN DWORD dwReceiveDataLength, + IN DWORD dwLocalAddressLength, + IN DWORD dwRemoteAddressLength, + OUT LPDWORD lpdwBytesReceived, + IN LPOVERLAPPED lpOverlapped + ); + +#define WSAID_ACCEPTEX \ + {0xb5367df1,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}} + +#endif + + +#ifndef WSAID_GETACCEPTEXSOCKADDRS + +typedef VOID (PASCAL FAR * LPFN_GETACCEPTEXSOCKADDRS)( + IN PVOID lpOutputBuffer, + IN DWORD dwReceiveDataLength, + IN DWORD dwLocalAddressLength, + IN DWORD dwRemoteAddressLength, + OUT struct sockaddr **LocalSockaddr, + OUT LPINT LocalSockaddrLength, + OUT struct sockaddr **RemoteSockaddr, + OUT LPINT RemoteSockaddrLength + ); + +#define WSAID_GETACCEPTEXSOCKADDRS \ + {0xb5367df2,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}} + +#endif + + +#ifndef WSAID_TRANSMITFILE + +#ifndef TF_DISCONNECT + +#define TF_DISCONNECT 1 +#define TF_REUSE_SOCKET 2 +#define TF_WRITE_BEHIND 4 +#define TF_USE_DEFAULT_WORKER 0 +#define TF_USE_SYSTEM_THREAD 16 +#define TF_USE_KERNEL_APC 32 + +typedef struct _TRANSMIT_FILE_BUFFERS { + LPVOID Head; + DWORD HeadLength; + LPVOID Tail; + DWORD TailLength; +} TRANSMIT_FILE_BUFFERS, *PTRANSMIT_FILE_BUFFERS, FAR *LPTRANSMIT_FILE_BUFFERS; + +#endif + +typedef BOOL (PASCAL FAR * LPFN_TRANSMITFILE)( + IN SOCKET hSocket, + IN HANDLE hFile, + IN DWORD nNumberOfBytesToWrite, + IN DWORD nNumberOfBytesPerSend, + IN LPOVERLAPPED lpOverlapped, + IN LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers, + IN DWORD dwReserved + ); + +#define WSAID_TRANSMITFILE \ + {0xb5367df0,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}} + +#endif + + +#ifndef WSAID_TRANSMITPACKETS + +/* OpenWatcom has a swapped TP_ELEMENT_FILE and TP_ELEMENT_MEMORY definition */ + +#ifndef TP_ELEMENT_FILE + +#ifdef _MSC_VER +#pragma warning(disable:4201) /* Nonstandard extension, nameless struct/union */ +#endif + +typedef struct _TRANSMIT_PACKETS_ELEMENT { + ULONG dwElFlags; +#define TP_ELEMENT_MEMORY 1 +#define TP_ELEMENT_FILE 2 +#define TP_ELEMENT_EOP 4 + ULONG cLength; + union { + struct { + LARGE_INTEGER nFileOffset; + HANDLE hFile; + }; + PVOID pBuffer; + }; +} TRANSMIT_PACKETS_ELEMENT, *PTRANSMIT_PACKETS_ELEMENT, + FAR *LPTRANSMIT_PACKETS_ELEMENT; + +#ifdef _MSC_VER +#pragma warning(default:4201) +#endif + +#endif + +typedef BOOL (PASCAL FAR * LPFN_TRANSMITPACKETS) ( + SOCKET hSocket, + TRANSMIT_PACKETS_ELEMENT *lpPacketArray, + DWORD nElementCount, + DWORD nSendSize, + LPOVERLAPPED lpOverlapped, + DWORD dwFlags + ); + +#define WSAID_TRANSMITPACKETS \ + {0xd9689da0,0x1f90,0x11d3,{0x99,0x71,0x00,0xc0,0x4f,0x68,0xc8,0x76}} + +#endif + + +#ifndef WSAID_CONNECTEX + +typedef BOOL (PASCAL FAR * LPFN_CONNECTEX) ( + IN SOCKET s, + IN const struct sockaddr FAR *name, + IN int namelen, + IN PVOID lpSendBuffer OPTIONAL, + IN DWORD dwSendDataLength, + OUT LPDWORD lpdwBytesSent, + IN LPOVERLAPPED lpOverlapped + ); + +#define WSAID_CONNECTEX \ + {0x25a207b9,0xddf3,0x4660,{0x8e,0xe9,0x76,0xe5,0x8c,0x74,0x06,0x3e}} + +#endif + + +#ifndef WSAID_DISCONNECTEX + +typedef BOOL (PASCAL FAR * LPFN_DISCONNECTEX) ( + IN SOCKET s, + IN LPOVERLAPPED lpOverlapped, + IN DWORD dwFlags, + IN DWORD dwReserved + ); + +#define WSAID_DISCONNECTEX \ + {0x7fda2e11,0x8630,0x436f,{0xa0,0x31,0xf5,0x36,0xa6,0xee,0xc1,0x57}} + +#endif + + +extern LPFN_ACCEPTEX ngx_acceptex; +extern LPFN_GETACCEPTEXSOCKADDRS ngx_getacceptexsockaddrs; +extern LPFN_TRANSMITFILE ngx_transmitfile; +extern LPFN_TRANSMITPACKETS ngx_transmitpackets; +extern LPFN_CONNECTEX ngx_connectex; +extern LPFN_DISCONNECTEX ngx_disconnectex; + + +int ngx_tcp_push(ngx_socket_t s); +#define ngx_tcp_push_n "tcp_push()" + + +#endif /* _NGX_SOCKET_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_stat.c b/src/os/win32/ngx_stat.c new file mode 100644 index 0000000..51bcd96 --- /dev/null +++ b/src/os/win32/ngx_stat.c @@ -0,0 +1,34 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +int ngx_file_type(char *file, ngx_file_info_t *sb) +{ + sb->dwFileAttributes = GetFileAttributes(file); + + if (sb->dwFileAttributes == INVALID_FILE_ATTRIBUTES) { + return -1; + } + + return 0; +} + +/* +int ngx_stat(char *file, ngx_stat_t *sb) +{ + *sb = GetFileAttributes(file); + + if (*sb == INVALID_FILE_ATTRIBUTES) { + return -1; + } + + return 0; +} +*/ diff --git a/src/os/win32/ngx_thread.c b/src/os/win32/ngx_thread.c new file mode 100644 index 0000000..a13de2d --- /dev/null +++ b/src/os/win32/ngx_thread.c @@ -0,0 +1,30 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +ngx_err_t +ngx_create_thread(ngx_tid_t *tid, + ngx_thread_value_t (__stdcall *func)(void *arg), void *arg, ngx_log_t *log) +{ + u_long id; + ngx_err_t err; + + *tid = CreateThread(NULL, 0, func, arg, 0, &id); + + if (*tid != NULL) { + ngx_log_error(NGX_LOG_NOTICE, log, 0, + "create thread " NGX_TID_T_FMT, id); + return 0; + } + + err = ngx_errno; + ngx_log_error(NGX_LOG_ALERT, log, err, "CreateThread() failed"); + return err; +} diff --git a/src/os/win32/ngx_thread.h b/src/os/win32/ngx_thread.h new file mode 100644 index 0000000..4012276 --- /dev/null +++ b/src/os/win32/ngx_thread.h @@ -0,0 +1,27 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_THREAD_H_INCLUDED_ +#define _NGX_THREAD_H_INCLUDED_ + + +#include +#include + + +typedef HANDLE ngx_tid_t; +typedef DWORD ngx_thread_value_t; + + +ngx_err_t ngx_create_thread(ngx_tid_t *tid, + ngx_thread_value_t (__stdcall *func)(void *arg), void *arg, ngx_log_t *log); + +#define ngx_log_tid GetCurrentThreadId() +#define NGX_TID_T_FMT "%ud" + + +#endif /* _NGX_THREAD_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_time.c b/src/os/win32/ngx_time.c new file mode 100644 index 0000000..bd6d287 --- /dev/null +++ b/src/os/win32/ngx_time.c @@ -0,0 +1,83 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +void +ngx_gettimeofday(struct timeval *tp) +{ + uint64_t intervals; + FILETIME ft; + + GetSystemTimeAsFileTime(&ft); + + /* + * A file time is a 64-bit value that represents the number + * of 100-nanosecond intervals that have elapsed since + * January 1, 1601 12:00 A.M. UTC. + * + * Between January 1, 1970 (Epoch) and January 1, 1601 there were + * 134744 days, + * 11644473600 seconds or + * 11644473600,000,000,0 100-nanosecond intervals. + * + * See also MSKB Q167296. + */ + + intervals = ((uint64_t) ft.dwHighDateTime << 32) | ft.dwLowDateTime; + intervals -= 116444736000000000; + + tp->tv_sec = (long) (intervals / 10000000); + tp->tv_usec = (long) ((intervals % 10000000) / 10); +} + + +void +ngx_libc_localtime(time_t s, struct tm *tm) +{ + struct tm *t; + + t = localtime(&s); + *tm = *t; +} + + +void +ngx_libc_gmtime(time_t s, struct tm *tm) +{ + struct tm *t; + + t = gmtime(&s); + *tm = *t; +} + + +ngx_int_t +ngx_gettimezone(void) +{ + u_long n; + TIME_ZONE_INFORMATION tz; + + n = GetTimeZoneInformation(&tz); + + switch (n) { + + case TIME_ZONE_ID_UNKNOWN: + return -tz.Bias; + + case TIME_ZONE_ID_STANDARD: + return -(tz.Bias + tz.StandardBias); + + case TIME_ZONE_ID_DAYLIGHT: + return -(tz.Bias + tz.DaylightBias); + + default: /* TIME_ZONE_ID_INVALID */ + return 0; + } +} diff --git a/src/os/win32/ngx_time.h b/src/os/win32/ngx_time.h new file mode 100644 index 0000000..6c2f806 --- /dev/null +++ b/src/os/win32/ngx_time.h @@ -0,0 +1,51 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_TIME_H_INCLUDED_ +#define _NGX_TIME_H_INCLUDED_ + + +#include +#include + + +typedef ngx_rbtree_key_t ngx_msec_t; +typedef ngx_rbtree_key_int_t ngx_msec_int_t; + +typedef SYSTEMTIME ngx_tm_t; +typedef FILETIME ngx_mtime_t; + +#define ngx_tm_sec wSecond +#define ngx_tm_min wMinute +#define ngx_tm_hour wHour +#define ngx_tm_mday wDay +#define ngx_tm_mon wMonth +#define ngx_tm_year wYear +#define ngx_tm_wday wDayOfWeek + +#define ngx_tm_sec_t u_short +#define ngx_tm_min_t u_short +#define ngx_tm_hour_t u_short +#define ngx_tm_mday_t u_short +#define ngx_tm_mon_t u_short +#define ngx_tm_year_t u_short +#define ngx_tm_wday_t u_short + + +#define ngx_msleep Sleep + +#define NGX_HAVE_GETTIMEZONE 1 + +#define ngx_timezone_update() + +ngx_int_t ngx_gettimezone(void); +void ngx_libc_localtime(time_t s, struct tm *tm); +void ngx_libc_gmtime(time_t s, struct tm *tm); +void ngx_gettimeofday(struct timeval *tp); + + +#endif /* _NGX_TIME_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_udp_wsarecv.c b/src/os/win32/ngx_udp_wsarecv.c new file mode 100644 index 0000000..5424375 --- /dev/null +++ b/src/os/win32/ngx_udp_wsarecv.c @@ -0,0 +1,149 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +ssize_t +ngx_udp_wsarecv(ngx_connection_t *c, u_char *buf, size_t size) +{ + int rc; + u_long bytes, flags; + WSABUF wsabuf[1]; + ngx_err_t err; + ngx_event_t *rev; + + wsabuf[0].buf = (char *) buf; + wsabuf[0].len = size; + flags = 0; + bytes = 0; + + rc = WSARecv(c->fd, wsabuf, 1, &bytes, &flags, NULL, NULL); + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv: fd:%d rc:%d %ul of %z", c->fd, rc, bytes, size); + + rev = c->read; + + if (rc == -1) { + rev->ready = 0; + err = ngx_socket_errno; + + if (err == WSAEWOULDBLOCK) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, + "WSARecv() not ready"); + return NGX_AGAIN; + } + + rev->error = 1; + ngx_connection_error(c, err, "WSARecv() failed"); + + return NGX_ERROR; + } + + return bytes; +} + + +ssize_t +ngx_udp_overlapped_wsarecv(ngx_connection_t *c, u_char *buf, size_t size) +{ + int rc; + u_long bytes, flags; + WSABUF wsabuf[1]; + ngx_err_t err; + ngx_event_t *rev; + LPWSAOVERLAPPED ovlp; + + rev = c->read; + + if (!rev->ready) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, "second wsa post"); + return NGX_AGAIN; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "rev->complete: %d", rev->complete); + + if (rev->complete) { + rev->complete = 0; + + if (ngx_event_flags & NGX_USE_IOCP_EVENT) { + if (rev->ovlp.error) { + ngx_connection_error(c, rev->ovlp.error, "WSARecv() failed"); + return NGX_ERROR; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv ovlp: fd:%d %ul of %z", + c->fd, rev->available, size); + + return rev->available; + } + + if (WSAGetOverlappedResult(c->fd, (LPWSAOVERLAPPED) &rev->ovlp, + &bytes, 0, NULL) + == 0) + { + ngx_connection_error(c, ngx_socket_errno, + "WSARecv() or WSAGetOverlappedResult() failed"); + return NGX_ERROR; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv: fd:%d %ul of %z", c->fd, bytes, size); + + return bytes; + } + + ovlp = (LPWSAOVERLAPPED) &rev->ovlp; + ngx_memzero(ovlp, sizeof(WSAOVERLAPPED)); + wsabuf[0].buf = (char *) buf; + wsabuf[0].len = size; + flags = 0; + bytes = 0; + + rc = WSARecv(c->fd, wsabuf, 1, &bytes, &flags, ovlp, NULL); + + rev->complete = 0; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv ovlp: fd:%d rc:%d %ul of %z", + c->fd, rc, bytes, size); + + if (rc == -1) { + err = ngx_socket_errno; + if (err == WSA_IO_PENDING) { + rev->active = 1; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, + "WSARecv() posted"); + return NGX_AGAIN; + } + + rev->error = 1; + ngx_connection_error(c, err, "WSARecv() failed"); + return NGX_ERROR; + } + + if (ngx_event_flags & NGX_USE_IOCP_EVENT) { + + /* + * if a socket was bound with I/O completion port + * then GetQueuedCompletionStatus() would anyway return its status + * despite that WSARecv() was already complete + */ + + rev->active = 1; + return NGX_AGAIN; + } + + rev->active = 0; + + return bytes; +} diff --git a/src/os/win32/ngx_user.c b/src/os/win32/ngx_user.c new file mode 100644 index 0000000..ea6da5a --- /dev/null +++ b/src/os/win32/ngx_user.c @@ -0,0 +1,23 @@ +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +#if (NGX_CRYPT) + +ngx_int_t +ngx_libc_crypt(ngx_pool_t *pool, u_char *key, u_char *salt, u_char **encrypted) +{ + /* STUB: a plain text password */ + + *encrypted = key; + + return NGX_OK; +} + +#endif /* NGX_CRYPT */ diff --git a/src/os/win32/ngx_user.h b/src/os/win32/ngx_user.h new file mode 100644 index 0000000..61408e4 --- /dev/null +++ b/src/os/win32/ngx_user.h @@ -0,0 +1,25 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_USER_H_INCLUDED_ +#define _NGX_USER_H_INCLUDED_ + + +#include +#include + + +/* STUB */ +#define ngx_uid_t ngx_int_t +#define ngx_gid_t ngx_int_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/src/os/win32/ngx_win32_config.h b/src/os/win32/ngx_win32_config.h new file mode 100644 index 0000000..f5b5950 --- /dev/null +++ b/src/os/win32/ngx_win32_config.h @@ -0,0 +1,265 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_WIN32_CONFIG_H_INCLUDED_ +#define _NGX_WIN32_CONFIG_H_INCLUDED_ + + +#undef WIN32 +#define WIN32 0x0400 +#define _WIN32_WINNT 0x0501 + + +#define STRICT +#define WIN32_LEAN_AND_MEAN + +/* enable getenv() and gmtime() in msvc8 */ +#define _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_DEPRECATE + +/* enable gethostbyname() in msvc2015 */ +#if !(NGX_HAVE_INET6) +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#endif + +/* + * we need to include explicitly before because + * the warning 4201 is enabled in + */ +#include + +#ifdef _MSC_VER +#pragma warning(disable:4201) +#endif + +#include +#include /* ipv6 */ +#include +#include +#include /* offsetof() */ + +#ifdef __MINGW64_VERSION_MAJOR + +/* GCC MinGW-w64 supports _FILE_OFFSET_BITS */ +#define _FILE_OFFSET_BITS 64 + +#elif defined __GNUC__ + +/* GCC MinGW's stdio.h includes sys/types.h */ +#define _OFF_T_ + +#endif + +#include +#include +#include +#ifdef __MINGW64_VERSION_MAJOR +#include +#endif +#include +#include + +#ifdef __WATCOMC__ +#define _TIME_T_DEFINED +typedef long time_t; +/* OpenWatcom defines time_t as "unsigned long" */ +#endif + +#include /* localtime(), strftime() */ + + +#ifdef _MSC_VER + +/* the end of the precompiled headers */ +#pragma hdrstop + +#pragma warning(default:4201) + +/* disable some "-W4" level warnings */ + +/* 'type cast': from function pointer to data pointer */ +#pragma warning(disable:4054) + +/* 'type cast': from data pointer to function pointer */ +#pragma warning(disable:4055) + +/* unreferenced formal parameter */ +#pragma warning(disable:4100) + +/* FD_SET() and FD_CLR(): conditional expression is constant */ +#pragma warning(disable:4127) + +/* array is too small to include a terminating null character */ +#pragma warning(disable:4295) + +#endif + + +#ifdef __WATCOMC__ + +/* symbol 'ngx_rbtree_min' has been defined, but not referenced */ +#pragma disable_message(202) + +#endif + + +#ifdef __BORLANDC__ + +/* the end of the precompiled headers */ +#pragma hdrstop + +/* functions containing (for|while|some if) are not expanded inline */ +#pragma warn -8027 + +/* unreferenced formal parameter */ +#pragma warn -8057 + +#endif + + +#include + + +#define ngx_inline __inline +#define ngx_cdecl __cdecl + + +#ifdef _MSC_VER +typedef unsigned __int32 uint32_t; +typedef __int32 int32_t; +typedef unsigned __int16 uint16_t; +#define ngx_libc_cdecl __cdecl + +#elif defined __BORLANDC__ +typedef unsigned __int32 uint32_t; +typedef __int32 int32_t; +typedef unsigned __int16 uint16_t; +#define ngx_libc_cdecl __cdecl + +#else /* __WATCOMC__ */ +typedef unsigned int uint32_t; +typedef int int32_t; +typedef unsigned short int uint16_t; +#define ngx_libc_cdecl + +#endif + +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; + +#if !defined(__WATCOMC__) && !defined(__MINGW64_VERSION_MAJOR) +typedef int intptr_t; +typedef u_int uintptr_t; +#endif + + +#ifndef __MINGW64_VERSION_MAJOR + +/* Windows defines off_t as long, which is 32-bit */ +typedef __int64 off_t; +#define _OFF_T_DEFINED + +#endif + + +#ifdef __WATCOMC__ + +/* off_t is redefined by sys/types.h used by zlib.h */ +#define __TYPES_H_INCLUDED +typedef int dev_t; +typedef unsigned int ino_t; + +#elif __BORLANDC__ + +/* off_t is redefined by sys/types.h used by zlib.h */ +#define __TYPES_H + +typedef int dev_t; +typedef unsigned int ino_t; + +#endif + + +#ifndef __MINGW64_VERSION_MAJOR +typedef int ssize_t; +#endif + + +typedef uint32_t in_addr_t; +typedef u_short in_port_t; +typedef int sig_atomic_t; + + +#ifdef _WIN64 + +#define NGX_PTR_SIZE 8 +#define NGX_SIZE_T_LEN (sizeof("-9223372036854775808") - 1) +#define NGX_MAX_SIZE_T_VALUE 9223372036854775807 +#define NGX_TIME_T_LEN (sizeof("-9223372036854775808") - 1) +#define NGX_TIME_T_SIZE 8 +#define NGX_MAX_TIME_T_VALUE 9223372036854775807 + +#else + +#define NGX_PTR_SIZE 4 +#define NGX_SIZE_T_LEN (sizeof("-2147483648") - 1) +#define NGX_MAX_SIZE_T_VALUE 2147483647 +#define NGX_TIME_T_LEN (sizeof("-2147483648") - 1) +#define NGX_TIME_T_SIZE 4 +#define NGX_MAX_TIME_T_VALUE 2147483647 + +#endif + + +#define NGX_OFF_T_LEN (sizeof("-9223372036854775807") - 1) +#define NGX_MAX_OFF_T_VALUE 9223372036854775807 +#define NGX_SIG_ATOMIC_T_SIZE 4 + + +#define NGX_HAVE_LITTLE_ENDIAN 1 +#define NGX_HAVE_NONALIGNED 1 + + +#define NGX_WIN_NT 200000 + + +#define NGX_LISTEN_BACKLOG 511 + + +#ifndef NGX_HAVE_INHERITED_NONBLOCK +#define NGX_HAVE_INHERITED_NONBLOCK 1 +#endif + +#ifndef NGX_HAVE_CASELESS_FILESYSTEM +#define NGX_HAVE_CASELESS_FILESYSTEM 1 +#endif + +#ifndef NGX_HAVE_WIN32_TRANSMITPACKETS +#define NGX_HAVE_WIN32_TRANSMITPACKETS 1 +#define NGX_HAVE_WIN32_TRANSMITFILE 0 +#endif + +#ifndef NGX_HAVE_WIN32_TRANSMITFILE +#define NGX_HAVE_WIN32_TRANSMITFILE 1 +#endif + +#if (NGX_HAVE_WIN32_TRANSMITPACKETS) || (NGX_HAVE_WIN32_TRANSMITFILE) +#define NGX_HAVE_SENDFILE 1 +#endif + +#ifndef NGX_HAVE_SO_SNDLOWAT +/* setsockopt(SO_SNDLOWAT) returns error WSAENOPROTOOPT */ +#define NGX_HAVE_SO_SNDLOWAT 0 +#endif + +#define NGX_HAVE_GETADDRINFO 1 + +#define ngx_random rand +#define ngx_debug_init() + + +#endif /* _NGX_WIN32_CONFIG_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_win32_init.c b/src/os/win32/ngx_win32_init.c new file mode 100644 index 0000000..9b26db5 --- /dev/null +++ b/src/os/win32/ngx_win32_init.c @@ -0,0 +1,293 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +ngx_uint_t ngx_win32_version; +ngx_uint_t ngx_ncpu; +ngx_uint_t ngx_max_wsabufs; +ngx_int_t ngx_max_sockets; +ngx_uint_t ngx_inherited_nonblocking = 1; +ngx_uint_t ngx_tcp_nodelay_and_tcp_nopush; + +char ngx_unique[NGX_INT32_LEN + 1]; + + +ngx_os_io_t ngx_os_io = { + ngx_wsarecv, + ngx_wsarecv_chain, + ngx_udp_wsarecv, + ngx_wsasend, + ngx_wsasend_chain, + 0 +}; + + +typedef struct { + WORD wServicePackMinor; + WORD wSuiteMask; + BYTE wProductType; +} ngx_osviex_stub_t; + + +static u_int osviex; +static OSVERSIONINFOEX osvi; + +/* Should these pointers be per protocol ? */ +LPFN_ACCEPTEX ngx_acceptex; +LPFN_GETACCEPTEXSOCKADDRS ngx_getacceptexsockaddrs; +LPFN_TRANSMITFILE ngx_transmitfile; +LPFN_TRANSMITPACKETS ngx_transmitpackets; +LPFN_CONNECTEX ngx_connectex; +LPFN_DISCONNECTEX ngx_disconnectex; + +static GUID ax_guid = WSAID_ACCEPTEX; +static GUID as_guid = WSAID_GETACCEPTEXSOCKADDRS; +static GUID tf_guid = WSAID_TRANSMITFILE; +static GUID tp_guid = WSAID_TRANSMITPACKETS; +static GUID cx_guid = WSAID_CONNECTEX; +static GUID dx_guid = WSAID_DISCONNECTEX; + + +ngx_int_t +ngx_os_init(ngx_log_t *log) +{ + DWORD bytes; + SOCKET s; + WSADATA wsd; + ngx_err_t err; + ngx_uint_t n; + SYSTEM_INFO si; + + /* get Windows version */ + + ngx_memzero(&osvi, sizeof(OSVERSIONINFOEX)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + +#ifdef _MSC_VER +#pragma warning(disable:4996) +#endif + + osviex = GetVersionEx((OSVERSIONINFO *) &osvi); + + if (osviex == 0) { + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + if (GetVersionEx((OSVERSIONINFO *) &osvi) == 0) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + "GetVersionEx() failed"); + return NGX_ERROR; + } + } + +#ifdef _MSC_VER +#pragma warning(default:4996) +#endif + + /* + * Windows 3.1 Win32s 0xxxxx + * + * Windows 95 140000 + * Windows 98 141000 + * Windows ME 149000 + * Windows NT 3.51 235100 + * Windows NT 4.0 240000 + * Windows NT 4.0 SP5 240050 + * Windows 2000 250000 + * Windows XP 250100 + * Windows 2003 250200 + * Windows Vista/2008 260000 + * + * Windows CE x.x 3xxxxx + */ + + ngx_win32_version = osvi.dwPlatformId * 100000 + + osvi.dwMajorVersion * 10000 + + osvi.dwMinorVersion * 100; + + if (osviex) { + ngx_win32_version += osvi.wServicePackMajor * 10 + + osvi.wServicePackMinor; + } + + GetSystemInfo(&si); + ngx_pagesize = si.dwPageSize; + ngx_allocation_granularity = si.dwAllocationGranularity; + ngx_ncpu = si.dwNumberOfProcessors; + ngx_cacheline_size = NGX_CPU_CACHE_LINE; + + for (n = ngx_pagesize; n >>= 1; ngx_pagesize_shift++) { /* void */ } + + /* delete default "C" locale for _wcsicmp() */ + setlocale(LC_ALL, ""); + + + /* init Winsock */ + + if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, + "WSAStartup() failed"); + return NGX_ERROR; + } + + if (ngx_win32_version < NGX_WIN_NT) { + ngx_max_wsabufs = 16; + return NGX_OK; + } + + /* STUB: ngx_uint_t max */ + ngx_max_wsabufs = 1024 * 1024; + + /* + * get AcceptEx(), GetAcceptExSockAddrs(), TransmitFile(), + * TransmitPackets(), ConnectEx(), and DisconnectEx() addresses + */ + + s = ngx_socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + if (s == (ngx_socket_t) -1) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, + ngx_socket_n " failed"); + return NGX_ERROR; + } + + if (WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &ax_guid, sizeof(GUID), + &ngx_acceptex, sizeof(LPFN_ACCEPTEX), &bytes, NULL, NULL) + == -1) + { + ngx_log_error(NGX_LOG_NOTICE, log, ngx_socket_errno, + "WSAIoctl(SIO_GET_EXTENSION_FUNCTION_POINTER, " + "WSAID_ACCEPTEX) failed"); + } + + if (WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &as_guid, sizeof(GUID), + &ngx_getacceptexsockaddrs, sizeof(LPFN_GETACCEPTEXSOCKADDRS), + &bytes, NULL, NULL) + == -1) + { + ngx_log_error(NGX_LOG_NOTICE, log, ngx_socket_errno, + "WSAIoctl(SIO_GET_EXTENSION_FUNCTION_POINTER, " + "WSAID_GETACCEPTEXSOCKADDRS) failed"); + } + + if (WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &tf_guid, sizeof(GUID), + &ngx_transmitfile, sizeof(LPFN_TRANSMITFILE), &bytes, + NULL, NULL) + == -1) + { + ngx_log_error(NGX_LOG_NOTICE, log, ngx_socket_errno, + "WSAIoctl(SIO_GET_EXTENSION_FUNCTION_POINTER, " + "WSAID_TRANSMITFILE) failed"); + } + + if (WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &tp_guid, sizeof(GUID), + &ngx_transmitpackets, sizeof(LPFN_TRANSMITPACKETS), &bytes, + NULL, NULL) + == -1) + { + ngx_log_error(NGX_LOG_NOTICE, log, ngx_socket_errno, + "WSAIoctl(SIO_GET_EXTENSION_FUNCTION_POINTER, " + "WSAID_TRANSMITPACKETS) failed"); + } + + if (WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &cx_guid, sizeof(GUID), + &ngx_connectex, sizeof(LPFN_CONNECTEX), &bytes, + NULL, NULL) + == -1) + { + ngx_log_error(NGX_LOG_NOTICE, log, ngx_socket_errno, + "WSAIoctl(SIO_GET_EXTENSION_FUNCTION_POINTER, " + "WSAID_CONNECTEX) failed"); + } + + if (WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &dx_guid, sizeof(GUID), + &ngx_disconnectex, sizeof(LPFN_DISCONNECTEX), &bytes, + NULL, NULL) + == -1) + { + ngx_log_error(NGX_LOG_NOTICE, log, ngx_socket_errno, + "WSAIoctl(SIO_GET_EXTENSION_FUNCTION_POINTER, " + "WSAID_DISCONNECTEX) failed"); + } + + if (ngx_close_socket(s) == -1) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno, + ngx_close_socket_n " failed"); + } + + if (GetEnvironmentVariable("ngx_unique", ngx_unique, NGX_INT32_LEN + 1) + != 0) + { + ngx_process = NGX_PROCESS_WORKER; + + } else { + err = ngx_errno; + + if (err != ERROR_ENVVAR_NOT_FOUND) { + ngx_log_error(NGX_LOG_EMERG, log, err, + "GetEnvironmentVariable(\"ngx_unique\") failed"); + return NGX_ERROR; + } + + ngx_sprintf((u_char *) ngx_unique, "%P%Z", ngx_pid); + } + + srand((ngx_pid << 16) ^ (unsigned) ngx_time()); + + return NGX_OK; +} + + +void +ngx_os_status(ngx_log_t *log) +{ + ngx_osviex_stub_t *osviex_stub; + + ngx_log_error(NGX_LOG_NOTICE, log, 0, NGINX_VER_BUILD); + + if (osviex) { + + /* + * the MSVC 6.0 SP2 defines wSuiteMask and wProductType + * as WORD wReserved[2] + */ + osviex_stub = (ngx_osviex_stub_t *) &osvi.wServicePackMinor; + + ngx_log_error(NGX_LOG_INFO, log, 0, + "OS: %ud build:%ud, \"%s\", suite:%Xd, type:%ud", + ngx_win32_version, osvi.dwBuildNumber, osvi.szCSDVersion, + osviex_stub->wSuiteMask, osviex_stub->wProductType); + + } else { + if (osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) { + + /* Win9x build */ + + ngx_log_error(NGX_LOG_INFO, log, 0, + "OS: %u build:%ud.%ud.%ud, \"%s\"", + ngx_win32_version, + osvi.dwBuildNumber >> 24, + (osvi.dwBuildNumber >> 16) & 0xff, + osvi.dwBuildNumber & 0xffff, + osvi.szCSDVersion); + + } else { + + /* + * VER_PLATFORM_WIN32_NT + * + * we do not currently support VER_PLATFORM_WIN32_CE + * and we do not support VER_PLATFORM_WIN32s at all + */ + + ngx_log_error(NGX_LOG_INFO, log, 0, "OS: %ud build:%ud, \"%s\"", + ngx_win32_version, osvi.dwBuildNumber, + osvi.szCSDVersion); + } + } +} diff --git a/src/os/win32/ngx_wsarecv.c b/src/os/win32/ngx_wsarecv.c new file mode 100644 index 0000000..1925f0b --- /dev/null +++ b/src/os/win32/ngx_wsarecv.c @@ -0,0 +1,174 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +ssize_t +ngx_wsarecv(ngx_connection_t *c, u_char *buf, size_t size) +{ + int rc; + u_long bytes, flags; + WSABUF wsabuf[1]; + ngx_err_t err; + ngx_int_t n; + ngx_event_t *rev; + + wsabuf[0].buf = (char *) buf; + wsabuf[0].len = size; + flags = 0; + bytes = 0; + + rc = WSARecv(c->fd, wsabuf, 1, &bytes, &flags, NULL, NULL); + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv: fd:%d rc:%d %ul of %z", c->fd, rc, bytes, size); + + rev = c->read; + + if (rc == -1) { + rev->ready = 0; + err = ngx_socket_errno; + + if (err == WSAEWOULDBLOCK) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, + "WSARecv() not ready"); + return NGX_AGAIN; + } + + n = ngx_connection_error(c, err, "WSARecv() failed"); + + if (n == NGX_ERROR) { + rev->error = 1; + } + + return n; + } + + if (bytes < size) { + rev->ready = 0; + } + + if (bytes == 0) { + rev->eof = 1; + } + + return bytes; +} + + +ssize_t +ngx_overlapped_wsarecv(ngx_connection_t *c, u_char *buf, size_t size) +{ + int rc; + u_long bytes, flags; + WSABUF wsabuf[1]; + ngx_err_t err; + ngx_int_t n; + ngx_event_t *rev; + LPWSAOVERLAPPED ovlp; + + rev = c->read; + + if (!rev->ready) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, "second wsa post"); + return NGX_AGAIN; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "rev->complete: %d", rev->complete); + + if (rev->complete) { + rev->complete = 0; + + if (ngx_event_flags & NGX_USE_IOCP_EVENT) { + if (rev->ovlp.error) { + ngx_connection_error(c, rev->ovlp.error, "WSARecv() failed"); + return NGX_ERROR; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv ovlp: fd:%d %ul of %z", + c->fd, rev->available, size); + + return rev->available; + } + + if (WSAGetOverlappedResult(c->fd, (LPWSAOVERLAPPED) &rev->ovlp, + &bytes, 0, NULL) + == 0) + { + ngx_connection_error(c, ngx_socket_errno, + "WSARecv() or WSAGetOverlappedResult() failed"); + return NGX_ERROR; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv: fd:%d %ul of %z", c->fd, bytes, size); + + return bytes; + } + + ovlp = (LPWSAOVERLAPPED) &rev->ovlp; + ngx_memzero(ovlp, sizeof(WSAOVERLAPPED)); + wsabuf[0].buf = (char *) buf; + wsabuf[0].len = size; + flags = 0; + bytes = 0; + + rc = WSARecv(c->fd, wsabuf, 1, &bytes, &flags, ovlp, NULL); + + rev->complete = 0; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv ovlp: fd:%d rc:%d %ul of %z", + c->fd, rc, bytes, size); + + if (rc == -1) { + err = ngx_socket_errno; + if (err == WSA_IO_PENDING) { + rev->active = 1; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, + "WSARecv() posted"); + return NGX_AGAIN; + } + + n = ngx_connection_error(c, err, "WSARecv() failed"); + + if (n == NGX_ERROR) { + rev->error = 1; + } + + return n; + } + + if (ngx_event_flags & NGX_USE_IOCP_EVENT) { + + /* + * if a socket was bound with I/O completion port + * then GetQueuedCompletionStatus() would anyway return its status + * despite that WSARecv() was already complete + */ + + rev->active = 1; + return NGX_AGAIN; + } + + if (bytes == 0) { + rev->eof = 1; + rev->ready = 0; + + } else { + rev->ready = 1; + } + + rev->active = 0; + + return bytes; +} diff --git a/src/os/win32/ngx_wsarecv_chain.c b/src/os/win32/ngx_wsarecv_chain.c new file mode 100644 index 0000000..2598e09 --- /dev/null +++ b/src/os/win32/ngx_wsarecv_chain.c @@ -0,0 +1,106 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#define NGX_WSABUFS 8 + + +ssize_t +ngx_wsarecv_chain(ngx_connection_t *c, ngx_chain_t *chain, off_t limit) +{ + int rc; + u_char *prev; + u_long bytes, flags; + size_t n, size; + ngx_err_t err; + ngx_array_t vec; + ngx_event_t *rev; + LPWSABUF wsabuf; + WSABUF wsabufs[NGX_WSABUFS]; + + prev = NULL; + wsabuf = NULL; + flags = 0; + size = 0; + bytes = 0; + + vec.elts = wsabufs; + vec.nelts = 0; + vec.size = sizeof(WSABUF); + vec.nalloc = NGX_WSABUFS; + vec.pool = c->pool; + + /* coalesce the neighbouring bufs */ + + while (chain) { + n = chain->buf->end - chain->buf->last; + + if (limit) { + if (size >= (size_t) limit) { + break; + } + + if (size + n > (size_t) limit) { + n = (size_t) limit - size; + } + } + + if (prev == chain->buf->last) { + wsabuf->len += n; + + } else { + wsabuf = ngx_array_push(&vec); + if (wsabuf == NULL) { + return NGX_ERROR; + } + + wsabuf->buf = (char *) chain->buf->last; + wsabuf->len = n; + } + + size += n; + prev = chain->buf->end; + chain = chain->next; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv: %d:%d", vec.nelts, wsabuf->len); + + + rc = WSARecv(c->fd, vec.elts, vec.nelts, &bytes, &flags, NULL, NULL); + + rev = c->read; + + if (rc == -1) { + rev->ready = 0; + err = ngx_socket_errno; + + if (err == WSAEWOULDBLOCK) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, + "WSARecv() not ready"); + return NGX_AGAIN; + } + + rev->error = 1; + ngx_connection_error(c, err, "WSARecv() failed"); + return NGX_ERROR; + } + + if (bytes < size) { + rev->ready = 0; + } + + if (bytes == 0) { + rev->eof = 1; + } + + return bytes; +} diff --git a/src/os/win32/ngx_wsasend.c b/src/os/win32/ngx_wsasend.c new file mode 100644 index 0000000..d6a23d1 --- /dev/null +++ b/src/os/win32/ngx_wsasend.c @@ -0,0 +1,185 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +ssize_t +ngx_wsasend(ngx_connection_t *c, u_char *buf, size_t size) +{ + int n; + u_long sent; + ngx_err_t err; + ngx_event_t *wev; + WSABUF wsabuf; + + wev = c->write; + + if (!wev->ready) { + return NGX_AGAIN; + } + + /* + * WSABUF must be 4-byte aligned otherwise + * WSASend() will return undocumented WSAEINVAL error. + */ + + wsabuf.buf = (char *) buf; + wsabuf.len = size; + + sent = 0; + + n = WSASend(c->fd, &wsabuf, 1, &sent, 0, NULL, NULL); + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSASend: fd:%d, %d, %ul of %uz", c->fd, n, sent, size); + + if (n == 0) { + if (sent < size) { + wev->ready = 0; + } + + c->sent += sent; + + return sent; + } + + err = ngx_socket_errno; + + if (err == WSAEWOULDBLOCK) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, "WSASend() not ready"); + wev->ready = 0; + return NGX_AGAIN; + } + + wev->error = 1; + ngx_connection_error(c, err, "WSASend() failed"); + + return NGX_ERROR; +} + + +ssize_t +ngx_overlapped_wsasend(ngx_connection_t *c, u_char *buf, size_t size) +{ + int n; + u_long sent; + ngx_err_t err; + ngx_event_t *wev; + LPWSAOVERLAPPED ovlp; + WSABUF wsabuf; + + wev = c->write; + + if (!wev->ready) { + return NGX_AGAIN; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "wev->complete: %d", wev->complete); + + if (!wev->complete) { + + /* post the overlapped WSASend() */ + + /* + * WSABUFs must be 4-byte aligned otherwise + * WSASend() will return undocumented WSAEINVAL error. + */ + + wsabuf.buf = (char *) buf; + wsabuf.len = size; + + sent = 0; + + ovlp = (LPWSAOVERLAPPED) &c->write->ovlp; + ngx_memzero(ovlp, sizeof(WSAOVERLAPPED)); + + n = WSASend(c->fd, &wsabuf, 1, &sent, 0, ovlp, NULL); + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSASend: fd:%d, %d, %ul of %uz", c->fd, n, sent, size); + + wev->complete = 0; + + if (n == 0) { + if (ngx_event_flags & NGX_USE_IOCP_EVENT) { + + /* + * if a socket was bound with I/O completion port then + * GetQueuedCompletionStatus() would anyway return its status + * despite that WSASend() was already complete + */ + + wev->active = 1; + return NGX_AGAIN; + } + + if (sent < size) { + wev->ready = 0; + } + + c->sent += sent; + + return sent; + } + + err = ngx_socket_errno; + + if (err == WSA_IO_PENDING) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, + "WSASend() posted"); + wev->active = 1; + return NGX_AGAIN; + } + + wev->error = 1; + ngx_connection_error(c, err, "WSASend() failed"); + + return NGX_ERROR; + } + + /* the overlapped WSASend() complete */ + + wev->complete = 0; + wev->active = 0; + + if (ngx_event_flags & NGX_USE_IOCP_EVENT) { + + if (wev->ovlp.error) { + ngx_connection_error(c, wev->ovlp.error, "WSASend() failed"); + return NGX_ERROR; + } + + sent = wev->available; + + } else { + if (WSAGetOverlappedResult(c->fd, (LPWSAOVERLAPPED) &wev->ovlp, + &sent, 0, NULL) + == 0) + { + ngx_connection_error(c, ngx_socket_errno, + "WSASend() or WSAGetOverlappedResult() failed"); + + return NGX_ERROR; + } + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSAGetOverlappedResult: fd:%d, %ul of %uz", + c->fd, sent, size); + + if (sent < size) { + wev->ready = 0; + } + + c->sent += sent; + + return sent; +} diff --git a/src/os/win32/ngx_wsasend_chain.c b/src/os/win32/ngx_wsasend_chain.c new file mode 100644 index 0000000..e2dde22 --- /dev/null +++ b/src/os/win32/ngx_wsasend_chain.c @@ -0,0 +1,292 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#define NGX_WSABUFS 8 + + +ngx_chain_t * +ngx_wsasend_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) +{ + int rc; + u_char *prev; + u_long size, sent, send, prev_send; + ngx_err_t err; + ngx_event_t *wev; + ngx_array_t vec; + ngx_chain_t *cl; + LPWSABUF wsabuf; + WSABUF wsabufs[NGX_WSABUFS]; + + wev = c->write; + + if (!wev->ready) { + return in; + } + + /* the maximum limit size is the maximum u_long value - the page size */ + + if (limit == 0 || limit > (off_t) (NGX_MAX_UINT32_VALUE - ngx_pagesize)) { + limit = NGX_MAX_UINT32_VALUE - ngx_pagesize; + } + + send = 0; + + /* + * WSABUFs must be 4-byte aligned otherwise + * WSASend() will return undocumented WSAEINVAL error. + */ + + vec.elts = wsabufs; + vec.size = sizeof(WSABUF); + vec.nalloc = NGX_WSABUFS; + vec.pool = c->pool; + + for ( ;; ) { + prev = NULL; + wsabuf = NULL; + prev_send = send; + + vec.nelts = 0; + + /* create the WSABUF and coalesce the neighbouring bufs */ + + for (cl = in; + cl && vec.nelts < ngx_max_wsabufs && send < limit; + cl = cl->next) + { + if (ngx_buf_special(cl->buf)) { + continue; + } + + size = cl->buf->last - cl->buf->pos; + + if (send + size > limit) { + size = (u_long) (limit - send); + } + + if (prev == cl->buf->pos) { + wsabuf->len += cl->buf->last - cl->buf->pos; + + } else { + wsabuf = ngx_array_push(&vec); + if (wsabuf == NULL) { + return NGX_CHAIN_ERROR; + } + + wsabuf->buf = (char *) cl->buf->pos; + wsabuf->len = cl->buf->last - cl->buf->pos; + } + + prev = cl->buf->last; + send += size; + } + + sent = 0; + + rc = WSASend(c->fd, vec.elts, vec.nelts, &sent, 0, NULL, NULL); + + if (rc == -1) { + err = ngx_errno; + + if (err == WSAEWOULDBLOCK) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, + "WSASend() not ready"); + + } else { + wev->error = 1; + ngx_connection_error(c, err, "WSASend() failed"); + return NGX_CHAIN_ERROR; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSASend: fd:%d, s:%ul", c->fd, sent); + + 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_overlapped_wsasend_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) +{ + int rc; + u_char *prev; + u_long size, send, sent; + ngx_err_t err; + ngx_event_t *wev; + ngx_array_t vec; + ngx_chain_t *cl; + LPWSAOVERLAPPED ovlp; + LPWSABUF wsabuf; + WSABUF wsabufs[NGX_WSABUFS]; + + wev = c->write; + + if (!wev->ready) { + return in; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "wev->complete: %d", wev->complete); + + if (!wev->complete) { + + /* post the overlapped WSASend() */ + + /* the maximum limit size is the maximum u_long value - the page size */ + + if (limit == 0 || limit > (off_t) (NGX_MAX_UINT32_VALUE - ngx_pagesize)) + { + limit = NGX_MAX_UINT32_VALUE - ngx_pagesize; + } + + /* + * WSABUFs must be 4-byte aligned otherwise + * WSASend() will return undocumented WSAEINVAL error. + */ + + vec.elts = wsabufs; + vec.nelts = 0; + vec.size = sizeof(WSABUF); + vec.nalloc = NGX_WSABUFS; + vec.pool = c->pool; + + send = 0; + prev = NULL; + wsabuf = NULL; + + /* create the WSABUF and coalesce the neighbouring bufs */ + + for (cl = in; + cl && vec.nelts < ngx_max_wsabufs && send < limit; + cl = cl->next) + { + if (ngx_buf_special(cl->buf)) { + continue; + } + + size = cl->buf->last - cl->buf->pos; + + if (send + size > limit) { + size = (u_long) (limit - send); + } + + if (prev == cl->buf->pos) { + wsabuf->len += cl->buf->last - cl->buf->pos; + + } else { + wsabuf = ngx_array_push(&vec); + if (wsabuf == NULL) { + return NGX_CHAIN_ERROR; + } + + wsabuf->buf = (char *) cl->buf->pos; + wsabuf->len = cl->buf->last - cl->buf->pos; + } + + prev = cl->buf->last; + send += size; + } + + ovlp = (LPWSAOVERLAPPED) &c->write->ovlp; + ngx_memzero(ovlp, sizeof(WSAOVERLAPPED)); + + rc = WSASend(c->fd, vec.elts, vec.nelts, &sent, 0, ovlp, NULL); + + wev->complete = 0; + + if (rc == -1) { + err = ngx_errno; + + if (err == WSA_IO_PENDING) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, + "WSASend() posted"); + wev->active = 1; + return in; + + } else { + wev->error = 1; + ngx_connection_error(c, err, "WSASend() failed"); + return NGX_CHAIN_ERROR; + } + + } else if (ngx_event_flags & NGX_USE_IOCP_EVENT) { + + /* + * if a socket was bound with I/O completion port then + * GetQueuedCompletionStatus() would anyway return its status + * despite that WSASend() was already complete + */ + + wev->active = 1; + return in; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSASend: fd:%d, s:%ul", c->fd, sent); + + } else { + + /* the overlapped WSASend() complete */ + + wev->complete = 0; + wev->active = 0; + + if (ngx_event_flags & NGX_USE_IOCP_EVENT) { + if (wev->ovlp.error) { + ngx_connection_error(c, wev->ovlp.error, "WSASend() failed"); + return NGX_CHAIN_ERROR; + } + + sent = wev->available; + + } else { + if (WSAGetOverlappedResult(c->fd, (LPWSAOVERLAPPED) &wev->ovlp, + &sent, 0, NULL) + == 0) + { + ngx_connection_error(c, ngx_socket_errno, + "WSASend() or WSAGetOverlappedResult() failed"); + + return NGX_CHAIN_ERROR; + } + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSASend ovlp: fd:%d, s:%ul", c->fd, sent); + + c->sent += sent; + + in = ngx_chain_update_sent(in, sent); + + if (in) { + wev->ready = 0; + + } else { + wev->ready = 1; + } + + return in; +} diff --git a/src/stream/ngx_stream.c b/src/stream/ngx_stream.c new file mode 100644 index 0000000..3bd8f6d --- /dev/null +++ b/src/stream/ngx_stream.c @@ -0,0 +1,564 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +static char *ngx_stream_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +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; + + +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; + } + } + } + + + /* parse inside the stream{} block */ + + pcf = *cf; + cf->ctx = ctx; + + 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; + } + } + } + } + + 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; + } + } + } + + *cf = pcf; + + + 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_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; + struct sockaddr_in *sin; + ngx_stream_conf_port_t *port; + ngx_stream_conf_addr_t *addr; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + + sa = &listen->u.sockaddr; + + switch (sa->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = &listen->u.sockaddr_in6; + p = sin6->sin6_port; + break; +#endif + +#if (NGX_HAVE_UNIX_DOMAIN) + case AF_UNIX: + p = 0; + break; +#endif + + default: /* AF_INET */ + sin = &listen->u.sockaddr_in; + p = sin->sin_port; + break; + } + + 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.u.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->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 && defined IPV6_V6ONLY) + 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.u.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 + + len = ngx_sock_ntop(&addr[i].opt.u.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.u.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 + + len = ngx_sock_ntop(&addr[i].opt.u.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/src/stream/ngx_stream.h b/src/stream/ngx_stream.h new file mode 100644 index 0000000..49efa45 --- /dev/null +++ b/src/stream/ngx_stream.h @@ -0,0 +1,212 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_STREAM_H_INCLUDED_ +#define _NGX_STREAM_H_INCLUDED_ + + +#include +#include + +#if (NGX_STREAM_SSL) +#include +#endif + + +typedef struct ngx_stream_session_s ngx_stream_session_t; + + +#include +#include + + +typedef struct { + void **main_conf; + void **srv_conf; +} ngx_stream_conf_ctx_t; + + +typedef struct { + 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 + u_char sockaddr_data[NGX_SOCKADDRLEN]; + } u; + + socklen_t socklen; + + /* server ctx */ + ngx_stream_conf_ctx_t *ctx; + + unsigned bind:1; + unsigned wildcard:1; +#if (NGX_STREAM_SSL) + unsigned ssl:1; +#endif +#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) + unsigned ipv6only:1; +#endif +#if (NGX_HAVE_REUSEPORT) + unsigned reuseport:1; +#endif + unsigned so_keepalive:2; +#if (NGX_HAVE_KEEPALIVE_TUNABLE) + int tcp_keepidle; + int tcp_keepintvl; + int tcp_keepcnt; +#endif + int backlog; + int type; +} ngx_stream_listen_t; + + +typedef struct { + ngx_stream_conf_ctx_t *ctx; + ngx_str_t addr_text; +#if (NGX_STREAM_SSL) + ngx_uint_t ssl; /* unsigned ssl:1; */ +#endif +} 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 ngx_int_t (*ngx_stream_access_pt)(ngx_stream_session_t *s); + + +typedef struct { + ngx_array_t servers; /* ngx_stream_core_srv_conf_t */ + ngx_array_t listen; /* ngx_stream_listen_t */ + ngx_stream_access_pt limit_conn_handler; + ngx_stream_access_pt access_handler; +} ngx_stream_core_main_conf_t; + + +typedef void (*ngx_stream_handler_pt)(ngx_stream_session_t *s); + + +typedef struct { + ngx_stream_handler_pt handler; + ngx_stream_conf_ctx_t *ctx; + u_char *file_name; + ngx_int_t line; + ngx_log_t *error_log; + ngx_flag_t tcp_nodelay; +} ngx_stream_core_srv_conf_t; + + +struct ngx_stream_session_s { + uint32_t signature; /* "STRM" */ + + ngx_connection_t *connection; + + off_t received; + + ngx_log_handler_pt log_handler; + + void **ctx; + void **main_conf; + void **srv_conf; + + ngx_stream_upstream_t *upstream; +}; + + +typedef struct { + 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) + + +void ngx_stream_init_connection(ngx_connection_t *c); +void ngx_stream_close_connection(ngx_connection_t *c); + + +extern ngx_module_t ngx_stream_module; +extern ngx_uint_t ngx_stream_max_module; +extern ngx_module_t ngx_stream_core_module; + + +#endif /* _NGX_STREAM_H_INCLUDED_ */ diff --git a/src/stream/ngx_stream_access_module.c b/src/stream/ngx_stream_access_module.c new file mode 100644 index 0000000..64869d2 --- /dev/null +++ b/src/stream/ngx_stream_access_module.c @@ -0,0 +1,451 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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 = { + 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_ABORT; + } + + 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 + + ngx_memzero(&cidr, sizeof(ngx_cidr_t)); + + value = cf->args->elts; + + all = (value[1].len == 3 && ngx_strcmp(value[1].data, "all") == 0); + + if (!all) { + +#if (NGX_HAVE_UNIX_DOMAIN) + + if (value[1].len == 5 && ngx_strcmp(value[1].data, "unix:") == 0) { + cidr.family = AF_UNIX; + rc = NGX_OK; + + } else { + rc = ngx_ptocidr(&value[1], &cidr); + } + +#else + rc = ngx_ptocidr(&value[1], &cidr); +#endif + + 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_core_main_conf_t *cmcf; + + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + cmcf->access_handler = ngx_stream_access_handler; + + return NGX_OK; +} diff --git a/src/stream/ngx_stream_core_module.c b/src/stream/ngx_stream_core_module.c new file mode 100644 index 0000000..ebc2b1c --- /dev/null +++ b/src/stream/ngx_stream_core_module.c @@ -0,0 +1,562 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +static void *ngx_stream_core_create_main_conf(ngx_conf_t *cf); +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 ngx_command_t ngx_stream_core_commands[] = { + + { 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("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_null_command +}; + + +static ngx_stream_module_t ngx_stream_core_module_ctx = { + NULL, /* postconfiguration */ + + ngx_stream_core_create_main_conf, /* create main configuration */ + NULL, /* 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 +}; + + +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; + } + + return cmcf; +} + + +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->tcp_nodelay = NGX_CONF_UNSET; + + 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; + + 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_value(conf->tcp_nodelay, prev->tcp_nodelay, 1); + + 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; + + return rv; +} + + +static char * +ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + size_t len, off; + in_port_t port; + ngx_str_t *value; + ngx_url_t u; + ngx_uint_t i, backlog; + struct sockaddr *sa; + struct sockaddr_in *sin; + ngx_stream_listen_t *ls; + ngx_stream_core_main_conf_t *cmcf; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + + 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 = cmcf->listen.elts; + + for (i = 0; i < cmcf->listen.nelts; i++) { + + sa = &ls[i].u.sockaddr; + + if (sa->sa_family != u.family) { + continue; + } + + switch (sa->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + off = offsetof(struct sockaddr_in6, sin6_addr); + len = 16; + sin6 = &ls[i].u.sockaddr_in6; + port = sin6->sin6_port; + break; +#endif + +#if (NGX_HAVE_UNIX_DOMAIN) + case AF_UNIX: + off = offsetof(struct sockaddr_un, sun_path); + len = sizeof(((struct sockaddr_un *) sa)->sun_path); + port = 0; + break; +#endif + + default: /* AF_INET */ + off = offsetof(struct sockaddr_in, sin_addr); + len = 4; + sin = &ls[i].u.sockaddr_in; + port = sin->sin_port; + break; + } + + if (ngx_memcmp(ls[i].u.sockaddr_data + off, u.sockaddr + off, len) + != 0) + { + continue; + } + + if (port != u.port) { + 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_stream_listen_t)); + + ngx_memcpy(&ls->u.sockaddr, u.sockaddr, u.socklen); + + ls->socklen = u.socklen; + ls->backlog = NGX_LISTEN_BACKLOG; + ls->type = SOCK_STREAM; + ls->wildcard = u.wildcard; + ls->ctx = cf->ctx; + +#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) + 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, "ipv6only=o", 10) == 0) { +#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) + u_char buf[NGX_SOCKADDR_STRLEN]; + + sa = &ls->u.sockaddr; + + if (sa->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(sa, 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 + } + + 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\""; + } + } + + return NGX_CONF_OK; +} diff --git a/src/stream/ngx_stream_handler.c b/src/stream/ngx_stream_handler.c new file mode 100644 index 0000000..aa69e44 --- /dev/null +++ b/src/stream/ngx_stream_handler.c @@ -0,0 +1,344 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +static u_char *ngx_stream_log_error(ngx_log_t *log, u_char *buf, size_t len); +static void ngx_stream_init_session(ngx_connection_t *c); + +#if (NGX_STREAM_SSL) +static void ngx_stream_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c); +static void ngx_stream_ssl_handshake_handler(ngx_connection_t *c); +#endif + + +void +ngx_stream_init_connection(ngx_connection_t *c) +{ + int tcp_nodelay; + u_char text[NGX_SOCKADDR_STRLEN]; + size_t len; + ngx_int_t rc; + ngx_uint_t i; + 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; + + 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 connection"; + c->log_error = NGX_ERROR_INFO; + + cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module); + + if (cmcf->limit_conn_handler) { + rc = cmcf->limit_conn_handler(s); + + if (rc != NGX_DECLINED) { + ngx_stream_close_connection(c); + return; + } + } + + if (cmcf->access_handler) { + rc = cmcf->access_handler(s); + + if (rc != NGX_OK && rc != NGX_DECLINED) { + ngx_stream_close_connection(c); + return; + } + } + + if (c->type == SOCK_STREAM + && cscf->tcp_nodelay + && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) + { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, "tcp_nodelay"); + + tcp_nodelay = 1; + + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) == -1) + { + ngx_connection_error(c, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + ngx_stream_close_connection(c); + return; + } + + c->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + + +#if (NGX_STREAM_SSL) + { + ngx_stream_ssl_conf_t *sslcf; + + sslcf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_module); + + 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_stream_close_connection(c); + return; + } + + ngx_stream_ssl_init_connection(&sslcf->ssl, c); + return; + } + } +#endif + + ngx_stream_init_session(c); +} + + +static void +ngx_stream_init_session(ngx_connection_t *c) +{ + ngx_stream_session_t *s; + ngx_stream_core_srv_conf_t *cscf; + + s = c->data; + c->log->action = "handling client connection"; + + cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module); + + s->ctx = ngx_pcalloc(c->pool, sizeof(void *) * ngx_stream_max_module); + if (s->ctx == NULL) { + ngx_stream_close_connection(c); + return; + } + + cscf->handler(s); +} + + +#if (NGX_STREAM_SSL) + +static void +ngx_stream_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c) +{ + ngx_stream_session_t *s; + ngx_stream_ssl_conf_t *sslcf; + + if (ngx_ssl_create_connection(ssl, c, 0) == NGX_ERROR) { + ngx_stream_close_connection(c); + return; + } + + if (ngx_ssl_handshake(c) == NGX_AGAIN) { + + s = c->data; + + 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_stream_ssl_handshake_handler(c); +} + + +static void +ngx_stream_ssl_handshake_handler(ngx_connection_t *c) +{ + if (!c->ssl->handshaked) { + ngx_stream_close_connection(c); + return; + } + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + ngx_stream_init_session(c); +} + +#endif + + +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/src/stream/ngx_stream_limit_conn_module.c b/src/stream/ngx_stream_limit_conn_module.c new file mode 100644 index 0000000..f1d8a37 --- /dev/null +++ b/src/stream/ngx_stream_limit_conn_module.c @@ -0,0 +1,632 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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_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 = { + 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; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + 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; + + switch (s->connection->sockaddr->sa_family) { + + case AF_INET: + sin = (struct sockaddr_in *) s->connection->sockaddr; + + key.len = sizeof(in_addr_t); + key.data = (u_char *) &sin->sin_addr; + + break; + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) s->connection->sockaddr; + + key.len = sizeof(struct in6_addr); + key.data = sin6->sin6_addr.s6_addr; + + break; +#endif + + default: + return NGX_DECLINED; + } + + hash = ngx_crc32_short(key.data, key.len); + + 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; + + 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_ABORT; + } + + 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_ABORT; + } + + 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) { + 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; + + value = cf->args->elts; + + ctx = ngx_pcalloc(cf->pool, sizeof(ngx_stream_limit_conn_ctx_t)); + if (ctx == NULL) { + 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) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "%V \"%V\" is already bound to key " + "\"$binary_remote_addr\"", + &cmd->name, &name); + return NGX_CONF_ERROR; + } + + if (ngx_strcmp(value[1].data, "$binary_remote_addr") != 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "unsupported key \"%V\", use " + "$binary_remote_addr", &value[1]); + 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_core_main_conf_t *cmcf; + + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + + cmcf->limit_conn_handler = ngx_stream_limit_conn_handler; + + return NGX_OK; +} diff --git a/src/stream/ngx_stream_proxy_module.c b/src/stream/ngx_stream_proxy_module.c new file mode 100644 index 0000000..6c535fd --- /dev/null +++ b/src/stream/ngx_stream_proxy_module.c @@ -0,0 +1,1674 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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_addr_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_str_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_proxy_srv_conf_t; + + +static void ngx_stream_proxy_handler(ngx_stream_session_t *s); +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_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_int_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); +static ngx_int_t ngx_stream_proxy_send_proxy_protocol(ngx_stream_session_t *s); + +#if (NGX_STREAM_SSL) + +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_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_TAKE1, + 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_conf_set_str_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, /* 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_connection_t *c; + ngx_stream_upstream_t *u; + ngx_stream_proxy_srv_conf_t *pscf; + ngx_stream_upstream_srv_conf_t *uscf; + + 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_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; + + u->peer.local = pscf->local; + u->peer.type = c->type; + + uscf = pscf->upstream; + + if (uscf->peer.init(s, uscf) != NGX_OK) { + ngx_stream_proxy_finalize(s, NGX_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; + } + + u->proxy_protocol = pscf->proxy_protocol; + u->start_sec = ngx_time(); + + c->write->handler = ngx_stream_proxy_downstream_handler; + c->read->handler = ngx_stream_proxy_downstream_handler; + + if (c->type == SOCK_DGRAM) { + ngx_stream_proxy_connect(s); + return; + } + + p = ngx_pnalloc(c->pool, pscf->buffer_size); + if (p == NULL) { + ngx_stream_proxy_finalize(s, NGX_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 (u->proxy_protocol +#if (NGX_STREAM_SSL) + && pscf->ssl == NULL +#endif + && pscf->buffer_size >= NGX_PROXY_PROTOCOL_MAX_HEADER) + { + /* optimization for a typical case */ + + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, + "stream proxy send PROXY protocol header"); + + p = ngx_proxy_protocol_write(c, u->downstream_buf.last, + u->downstream_buf.end); + if (p == NULL) { + ngx_stream_proxy_finalize(s, NGX_ERROR); + return; + } + + u->downstream_buf.last = p; + u->proxy_protocol = 0; + } + + if (c->read->ready) { + ngx_post_event(c->read, &ngx_posted_events); + } + + ngx_stream_proxy_connect(s); +} + + +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"; + + u = s->upstream; + + 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_ERROR); + return; + } + + if (rc == NGX_BUSY) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "no live upstreams"); + ngx_stream_proxy_finalize(s, NGX_DECLINED); + 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; + + pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module); + + ngx_add_timer(pc->write, pscf->connect_timeout); +} + + +static void +ngx_stream_proxy_init_upstream(ngx_stream_session_t *s) +{ + int tcp_nodelay; + u_char *p; + 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 + && pc->tcp_nodelay == NGX_TCP_NODELAY_UNSET) + { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, pc->log, 0, "tcp_nodelay"); + + tcp_nodelay = 1; + + if (setsockopt(pc->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) == -1) + { + ngx_connection_error(pc, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + ngx_stream_proxy_next_upstream(s); + return; + } + + pc->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + + if (u->proxy_protocol) { + if (ngx_stream_proxy_send_proxy_protocol(s) != NGX_OK) { + return; + } + + u->proxy_protocol = 0; + } + + pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module); + +#if (NGX_STREAM_SSL) + if (pc->type == SOCK_STREAM && pscf->ssl && 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; + } + } + + c->log->action = "proxying connection"; + + if (u->upstream_buf.start == NULL) { + p = ngx_pnalloc(c->pool, pscf->buffer_size); + if (p == NULL) { + ngx_stream_proxy_finalize(s, NGX_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->type == SOCK_DGRAM) { + s->received = c->buffer->last - c->buffer->pos; + u->downstream_buf = *c->buffer; + + if (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); +} + + +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_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_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_DECLINED); + 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_DECLINED); + + return NGX_ERROR; + } + + return NGX_OK; +} + + +#if (NGX_STREAM_SSL) + +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_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_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_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; + + name = pscf->ssl_name; + + if (name.len == 0) { + name = pscf->upstream->host; + } + + 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, 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_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; + + 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_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; + } + + if (u->received == 0) { + ngx_stream_proxy_next_upstream(s); + return; + } + } + + ngx_connection_error(c, NGX_ETIMEDOUT, "connection timed out"); + ngx_stream_proxy_finalize(s, NGX_DECLINED); + 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_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 (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) +{ + off_t *received, limit; + size_t size, limit_rate; + ssize_t n; + ngx_buf_t *b; + ngx_uint_t flags; + ngx_msec_t delay; + 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_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; + + } else { + src = c; + dst = pc; + b = &u->downstream_buf; + limit_rate = pscf->upload_rate; + received = &s->received; + } + + for ( ;; ) { + + if (do_write) { + + size = b->last - b->pos; + + if (size && dst && dst->write->ready) { + + n = dst->send(dst, b->pos, size); + + if (n == NGX_AGAIN && dst->shared) { + /* cannot wait on a shared socket */ + n = NGX_ERROR; + } + + if (n == NGX_ERROR) { + if (c->type == SOCK_DGRAM && !from_upstream) { + ngx_stream_proxy_next_upstream(s); + return; + } + + ngx_stream_proxy_finalize(s, NGX_DECLINED); + 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 && !src->read->delayed) { + + 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; + } + } + + n = src->recv(src, b->last, size); + + if (n == NGX_AGAIN || n == 0) { + break; + } + + 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 (c->type == SOCK_DGRAM && ++u->responses == pscf->responses) + { + src->read->ready = 0; + src->read->eof = 1; + } + + *received += n; + b->last += n; + do_write = 1; + + continue; + } + + if (n == NGX_ERROR) { + if (c->type == SOCK_DGRAM && u->received == 0) { + ngx_stream_proxy_next_upstream(s); + return; + } + + src->read->eof = 1; + } + } + + break; + } + + if (src->read->eof && (b->pos == b->last || (dst && dst->read->eof))) { + 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_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_ERROR); + return; + } + + if (dst) { + if (!dst->shared && ngx_handle_write_event(dst->write, 0) != NGX_OK) { + ngx_stream_proxy_finalize(s, NGX_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; + + 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_DECLINED); + return; + } + + pc = u->peer.connection; + + 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 + + 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_int_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->peer.free && u->peer.sockaddr) { + u->peer.free(&u->peer, u->peer.data, 0); + u->peer.sockaddr = NULL; + } + + pc = u->peer.connection; + + 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_close_connection(s->connection); +} + + +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 = { 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->ssl = NULL; + * conf->upstream = 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"); + + ngx_conf_merge_str_value(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 (SSL_CTX_set_cipher_list(pscf->ssl->ctx, + (const char *) pscf->ssl_ciphers.data) + == 0) + { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "SSL_CTX_set_cipher_list(\"%V\") failed", + &pscf->ssl_ciphers); + 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_core_srv_conf_t *cscf; + + if (pscf->upstream) { + 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(&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; + + if (pscf->local != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + value = cf->args->elts; + + if (ngx_strcmp(value[1].data, "off") == 0) { + pscf->local = NULL; + return NGX_CONF_OK; + } + + pscf->local = ngx_palloc(cf->pool, sizeof(ngx_addr_t)); + if (pscf->local == NULL) { + return NGX_CONF_ERROR; + } + + rc = ngx_parse_addr(cf->pool, pscf->local, value[1].data, value[1].len); + + switch (rc) { + case NGX_OK: + pscf->local->name = value[1]; + return NGX_CONF_OK; + + case NGX_DECLINED: + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid address \"%V\"", &value[1]); + /* fall through */ + + default: + return NGX_CONF_ERROR; + } +} diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c new file mode 100644 index 0000000..e12da1b --- /dev/null +++ b/src/stream/ngx_stream_ssl_module.c @@ -0,0 +1,460 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#define NGX_DEFAULT_CIPHERS "HIGH:!aNULL:!MD5" +#define NGX_DEFAULT_ECDH_CURVE "prime256v1" + + +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_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_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_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, certificate), + NULL }, + + { ngx_string("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_ssl_conf_t, certificate_key), + 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_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_null_command +}; + + +static ngx_stream_module_t ngx_stream_ssl_module_ctx = { + NULL, /* 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_str_t ngx_stream_ssl_sess_id_ctx = ngx_string("STREAM"); + + +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->certificate = { 0, NULL }; + * scf->certificate_key = { 0, NULL }; + * scf->dhparam = { 0, NULL }; + * scf->ecdh_curve = { 0, NULL }; + * scf->ciphers = { 0, NULL }; + * scf->shm_zone = NULL; + */ + + scf->handshake_timeout = NGX_CONF_UNSET_MSEC; + scf->passwords = NGX_CONF_UNSET_PTR; + scf->prefer_server_ciphers = NGX_CONF_UNSET; + 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_str_value(conf->certificate, prev->certificate, ""); + ngx_conf_merge_str_value(conf->certificate_key, prev->certificate_key, ""); + + 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->ciphers, prev->ciphers, NGX_DEFAULT_CIPHERS); + + + conf->ssl.log = cf->log; + + if (conf->certificate.len == 0) { + return NGX_CONF_OK; + } + + if (conf->certificate_key.len == 0) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no \"ssl_certificate_key\" is defined " + "for certificate \"%V\"", + &conf->certificate); + 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_certificate(cf, &conf->ssl, &conf->certificate, + &conf->certificate_key, conf->passwords) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + if (SSL_CTX_set_cipher_list(conf->ssl.ctx, + (const char *) conf->ciphers.data) + == 0) + { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "SSL_CTX_set_cipher_list(\"%V\") failed", + &conf->ciphers); + return NGX_CONF_ERROR; + } + + if (conf->prefer_server_ciphers) { + SSL_CTX_set_options(conf->ssl.ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); + } + +#if (OPENSSL_VERSION_NUMBER < 0x10100001L && !defined LIBRESSL_VERSION_NUMBER) + SSL_CTX_set_tmp_rsa_callback(conf->ssl.ctx, ngx_ssl_rsa512_key_callback); +#endif + + 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; +} diff --git a/src/stream/ngx_stream_ssl_module.h b/src/stream/ngx_stream_ssl_module.h new file mode 100644 index 0000000..85e8b6e --- /dev/null +++ b/src/stream/ngx_stream_ssl_module.h @@ -0,0 +1,49 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_STREAM_SSL_H_INCLUDED_ +#define _NGX_STREAM_SSL_H_INCLUDED_ + + +#include +#include +#include + + +typedef struct { + ngx_msec_t handshake_timeout; + + ngx_flag_t prefer_server_ciphers; + + ngx_ssl_t ssl; + + ngx_uint_t protocols; + + ssize_t builtin_session_cache; + + time_t session_timeout; + + ngx_str_t certificate; + ngx_str_t certificate_key; + ngx_str_t dhparam; + ngx_str_t ecdh_curve; + + 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/src/stream/ngx_stream_upstream.c b/src/stream/ngx_stream_upstream.c new file mode 100644 index 0000000..69dddc5 --- /dev/null +++ b/src/stream/ngx_stream_upstream.c @@ -0,0 +1,464 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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 = { + 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 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_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_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_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_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_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_WARN, 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_WARN, 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/src/stream/ngx_stream_upstream.h b/src/stream/ngx_stream_upstream.h new file mode 100644 index 0000000..1f4810c --- /dev/null +++ b/src/stream/ngx_stream_upstream.h @@ -0,0 +1,107 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_STREAM_UPSTREAM_H_INCLUDED_ +#define _NGX_STREAM_UPSTREAM_H_INCLUDED_ + + +#include +#include +#include +#include + + +#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 + + +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_fails; + time_t fail_timeout; + + unsigned down:1; + unsigned backup:1; +} 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_peer_connection_t peer; + ngx_buf_t downstream_buf; + ngx_buf_t upstream_buf; + off_t received; + time_t start_sec; + ngx_uint_t responses; +#if (NGX_STREAM_SSL) + ngx_str_t ssl_name; +#endif + 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/src/stream/ngx_stream_upstream_hash_module.c b/src/stream/ngx_stream_upstream_hash_module.c new file mode 100644 index 0000000..56ff7d6 --- /dev/null +++ b/src/stream/ngx_stream_upstream_hash_module.c @@ -0,0 +1,656 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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_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, /* 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); + + hp->key = s->connection->addr_text; + + 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; + } + + 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); + + 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->server.len != server->len + || ngx_strncmp(peer->server.data, server->data, server->len) + != 0) + { + continue; + } + + if (peer->max_fails + && peer->fails >= peer->max_fails + && now - peer->checked <= peer->fail_timeout) + { + 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 >= points->number) { + ngx_stream_upstream_rr_peers_unlock(hp->rrp.peers); + return NGX_BUSY; + } + } + + 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_str_t *value; + ngx_stream_upstream_srv_conf_t *uscf; + + value = cf->args->elts; + + if (ngx_strcmp(value[1].data, "$remote_addr")) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "unsupported hash key \"%V\", use $remote_addr", + &value[1]); + 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_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/src/stream/ngx_stream_upstream_least_conn_module.c b/src/stream/ngx_stream_upstream_least_conn_module.c new file mode 100644 index 0000000..c9719f9 --- /dev/null +++ b/src/stream/ngx_stream_upstream_least_conn_module.c @@ -0,0 +1,307 @@ + +/* + * Copyright (C) Maxim Dounin + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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, /* 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; + } + + /* + * 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; + } + + 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); + } + + /* all peers failed, mark them as live for quick recovery */ + + for (peer = peers->peer; peer; peer = peer->next) { + peer->fails = 0; + } + + 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_FAILS + |NGX_STREAM_UPSTREAM_FAIL_TIMEOUT + |NGX_STREAM_UPSTREAM_DOWN + |NGX_STREAM_UPSTREAM_BACKUP; + + return NGX_CONF_OK; +} diff --git a/src/stream/ngx_stream_upstream_round_robin.c b/src/stream/ngx_stream_upstream_round_robin.c new file mode 100644 index 0000000..e1ab592 --- /dev/null +++ b/src/stream/ngx_stream_upstream_round_robin.c @@ -0,0 +1,702 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#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); + +#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); + +#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_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_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_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; + + 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.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_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; + } + + 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); + } + + /* all peers failed, mark them as live for quick recovery */ + + for (peer = peers->peer; peer; peer = peer->next) { + peer->fails = 0; + } + + 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; + } + + 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--; + } +} + + +#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); + } +} + +#endif diff --git a/src/stream/ngx_stream_upstream_round_robin.h b/src/stream/ngx_stream_upstream_round_robin.h new file mode 100644 index 0000000..77ee0ab --- /dev/null +++ b/src/stream/ngx_stream_upstream_round_robin.h @@ -0,0 +1,139 @@ + +/* + * 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 +#include +#include + + +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 fails; + time_t accessed; + time_t checked; + + ngx_uint_t max_fails; + time_t fail_timeout; + + ngx_uint_t down; /* unsigned down:1; */ + +#if (NGX_STREAM_SSL) + void *ssl_session; + int ssl_session_len; +#endif + + ngx_stream_upstream_rr_peer_t *next; + +#if (NGX_STREAM_UPSTREAM_ZONE) + ngx_atomic_t lock; +#endif +}; + + +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_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_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/src/stream/ngx_stream_upstream_zone_module.c b/src/stream/ngx_stream_upstream_zone_module.c new file mode 100644 index 0000000..ffc9e8a --- /dev/null +++ b/src/stream/ngx_stream_upstream_zone_module.c @@ -0,0 +1,242 @@ + +/* + * Copyright (C) Ruslan Ermilov + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +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_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, /* 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_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)); + + peers->shpool = shpool; + + for (peerp = &peers->peer; *peerp; peerp = &peer->next) { + /* pool is unlocked */ + peer = ngx_slab_calloc_locked(shpool, + sizeof(ngx_stream_upstream_rr_peer_t)); + if (peer == NULL) { + return NULL; + } + + ngx_memcpy(peer, *peerp, sizeof(ngx_stream_upstream_rr_peer_t)); + + *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->shpool = shpool; + + for (peerp = &backup->peer; *peerp; peerp = &peer->next) { + /* pool is unlocked */ + peer = ngx_slab_calloc_locked(shpool, + sizeof(ngx_stream_upstream_rr_peer_t)); + if (peer == NULL) { + return NULL; + } + + ngx_memcpy(peer, *peerp, sizeof(ngx_stream_upstream_rr_peer_t)); + + *peerp = peer; + } + + peers->next = backup; + +done: + + uscf->peer.data = peers; + + return peers; +}