nginx-1.24.0/000755 001751 001751 00000000000 14415135702 014111 5ustar00mdouninmdounin000000 000000 nginx-1.24.0/auto/000755 001751 001751 00000000000 14415135700 015057 5ustar00mdouninmdounin000000 000000 nginx-1.24.0/conf/000755 001751 001751 00000000000 14415135677 015051 5ustar00mdouninmdounin000000 000000 nginx-1.24.0/contrib/000755 001751 001751 00000000000 14415135677 015564 5ustar00mdouninmdounin000000 000000 nginx-1.24.0/src/000755 001751 001751 00000000000 14415135700 014676 5ustar00mdouninmdounin000000 000000 nginx-1.24.0/configure000755 001751 001751 00000005063 14415135676 016036 0ustar00mdouninmdounin000000 000000 #!/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_* | MINGW64_* | MSYS_*) NGX_PLATFORM=win32 ;; esac else echo "building for $NGX_PLATFORM" NGX_SYSTEM=$NGX_PLATFORM NGX_MACHINE=i386 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 if [ ".$NGX_ERROR_LOG_PATH" = "." ]; then have=NGX_ERROR_LOG_STDERR . auto/have fi have=NGX_HTTP_LOG_PATH value="\"$NGX_HTTP_LOG_PATH\"" . auto/define have=NGX_HTTP_CLIENT_TEMP_PATH value="\"$NGX_HTTP_CLIENT_TEMP_PATH\"" . auto/define 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 nginx-1.24.0/LICENSE000644 001751 001751 00000002565 14415135676 015140 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) 2002-2021 Igor Sysoev * Copyright (C) 2011-2022 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. */ nginx-1.24.0/README000644 001751 001751 00000000061 14415135676 015000 0ustar00mdouninmdounin000000 000000 Documentation is available at http://nginx.org nginx-1.24.0/html/000755 001751 001751 00000000000 14415135700 015053 5ustar00mdouninmdounin000000 000000 nginx-1.24.0/man/000755 001751 001751 00000000000 14415135700 014662 5ustar00mdouninmdounin000000 000000 nginx-1.24.0/CHANGES.ru000644 001751 001751 00001705232 14415135701 015542 0ustar00mdouninmdounin000000 000000 Изменения в nginx 1.24.0 11.04.2023 *) Стабильная ветка 1.24.x. Изменения в nginx 1.23.4 28.03.2023 *) Изменение: теперь протокол TLSv1.3 разрешён по умолчанию. *) Изменение: теперь nginx выдаёт предупреждение при переопределении параметров listen-сокета, задающих используемые протоколы. *) Изменение: теперь, если клиент использует pipelining, nginx закрывает соединения с ожиданием дополнительных данных (lingering close). *) Добавление: поддержка byte ranges для ответов модуля ngx_http_gzip_static_module. *) Исправление: диапазоны портов в директиве listen не работали; ошибка появилась в 1.23.3. Спасибо Валентину Бартеневу. *) Исправление: для обработки запроса мог быть выбран неверный location, если в конфигурации использовался префиксный location длиннее 255 символов. *) Исправление: не-ASCII символы в именах файлов на Windows не поддерживались модулями ngx_http_autoindex_module и ngx_http_dav_module, а также директивой include. *) Изменение: уровень логгирования ошибок SSL "data length too long", "length too short", "bad legacy version", "no shared signature algorithms", "bad digest length", "missing sigalgs extension", "encrypted length too long", "bad length", "bad key update", "mixed handshake and non handshake data", "ccs received early", "data between ccs and finished", "packet length too long", "too many warn alerts", "record too small", и "got a fin before a ccs" понижен с уровня crit до info. *) Исправление: при использовании HTTP/2 и директивы error_page для перенаправления ошибок с кодом 400 могла происходить утечка сокетов. *) Исправление: сообщения об ошибках записи в syslog не содержали информации о том, что ошибки происходили в процессе записи в syslog. Спасибо Safar Safarly. *) Изменение: при использовании zlib-ng в логах появлялись сообщения "gzip filter failed to use preallocated memory". *) Исправление: в почтовом прокси-сервере. Изменения в nginx 1.23.3 13.12.2022 *) Исправление: при чтении заголовка протокола PROXY версии 2, содержащего большое количество TLV, могла возникать ошибка. *) Исправление: при использовании SSI для обработки подзапросов, созданных другими модулями, в рабочем процессе мог произойти segmentation fault. Спасибо Ciel Zhao. *) Изменение: теперь, если при преобразовании в адреса имени хоста, указанного в директиве listen, возвращается несколько адресов, nginx игнорирует дубликаты среди этих адресов. *) Исправление: nginx мог нагружать процессор при небуферизированном проксировании, если использовались SSL-соединения с бэкендами. Изменения в nginx 1.23.2 19.10.2022 *) Безопасность: обработка специально созданного mp4-файла модулем ngx_http_mp4_module могла приводить к падению рабочего процесса, отправке клиенту части содержимого памяти рабочего процесса, а также потенциально могла иметь другие последствия (CVE-2022-41741, CVE-2022-41742). *) Добавление: переменные "$proxy_protocol_tlv_...". *) Добавление: ключи шифрования TLS session tickets теперь автоматически меняются при использовании разделяемой памяти в ssl_session_cache. *) Изменение: уровень логгирования ошибок SSL "bad record type" понижен с уровня crit до info. Спасибо Murilo Andrade. *) Изменение: теперь при использовании разделяемой памяти в ssl_session_cache сообщения "could not allocate new session" логгируются на уровне warn вместо alert и не чаще одного раза в секунду. *) Исправление: nginx/Windows не собирался с OpenSSL 3.0.x. *) Исправление: в логгировании ошибок протокола PROXY. Спасибо Сергею Брестеру. *) Изменение: при использовании TLSv1.3 с OpenSSL разделяемая память из ssl_session_cache расходовалась в том числе на сессии, использующие TLS session tickets. *) Изменение: таймаут, заданный с помощью директивы ssl_session_timeout, не работал при использовании TLSv1.3 с OpenSSL или BoringSSL. Изменения в nginx 1.23.1 19.07.2022 *) Добавление: оптимизация использования памяти в конфигурациях с SSL-проксированием. *) Добавление: теперь с помощью параметра "ipv4=off" директивы "resolver" можно запретить поиск IPv4-адресов при преобразовании имён в адреса. *) Изменение: уровень логгирования ошибок SSL "bad key share", "bad extension", "bad cipher" и "bad ecpoint" понижен с уровня crit до info. *) Исправление: при возврате диапазонов nginx не удалял строку заголовка "Content-Range", если она присутствовала в исходном ответе бэкенда. *) Исправление: проксированный ответ мог быть отправлен не полностью при переконфигурации на Linux; ошибка появилась в 1.17.5. Изменения в nginx 1.23.0 21.06.2022 *) Изменение во внутреннем API: теперь строки заголовков представлены связными списками. *) Изменение: теперь nginx объединяет произвольные строки заголовков с одинаковыми именами при отправке на FastCGI-, SCGI- и uwsgi-бэкенды, в методе $r->header_in() модуля ngx_http_perl_module, и при доступе через переменные "$http_...", "$sent_http_...", "$sent_trailer_...", "$upstream_http_..." и "$upstream_trailer_...". *) Исправление: если в заголовке ответа бэкенда было несколько строк "Vary", при кэшировании nginx учитывал только последнюю из них. *) Исправление: если в заголовке ответа бэкенда было несколько строк "WWW-Authenticate" и использовался перехват ошибок с кодом 401 от бэкенда или директива auth_request, nginx пересылал клиенту только первую из этих строк. *) Изменение: уровень логгирования ошибок SSL "application data after close notify" понижен с уровня crit до info. *) Исправление: соединения могли зависать, если nginx был собран на Linux 2.6.17 и новее, а использовался на системах без поддержки EPOLLRDHUP, в частности, на системах с эмуляцией epoll; ошибка появилась в 1.17.5. Спасибо Marcus Ball. *) Исправление: nginx не кэшировал ответ, если строка заголовка ответа "Expires" запрещала кэширование, а последующая строка заголовка "Cache-Control" разрешала кэширование. Изменения в nginx 1.21.6 25.01.2022 *) Исправление: при использование EPOLLEXCLUSIVE на Linux распределение клиентских соединений между рабочими процессами было неравномерным. *) Исправление: во время плавного завершения старых рабочих процессов nginx возвращал в ответах строку заголовка "Connection: keep-alive". *) Исправление: в директиве ssl_session_ticket_key при использовании TLSv1.3. Изменения в nginx 1.21.5 28.12.2021 *) Изменение: теперь nginx по умолчанию собирается с библиотекой PCRE2. *) Изменение: теперь nginx всегда использует sendfile(SF_NODISKIO) на FreeBSD. *) Добавление: поддержка sendfile(SF_NOCACHE) на FreeBSD. *) Добавление: переменная $ssl_curve. *) Исправление: при использовании HTTP/2 без SSL вместе с директивами sendfile и aio соединения могли зависать. Изменения в nginx 1.21.4 02.11.2021 *) Изменение: поддержка NPN вместо ALPN для установления HTTP/2-соединений упразднена. *) Изменение: теперь nginx закрывает SSL соединение, если клиент использует ALPN, но nginx не поддерживает ни один из присланных клиентом протоколов. *) Изменение: в директиве sendfile_max_chunk значение по умолчанию изменено на 2 мегабайта. *) Добавление: директива proxy_half_close в модуле stream. *) Добавление: директива ssl_alpn в модуле stream. *) Добавление: переменная $ssl_alpn_protocol. *) Добавление: поддержка SSL_sendfile() при использовании OpenSSL 3.0. *) Добавление: директива mp4_start_key_frame в модуле ngx_http_mp4_module. Спасибо Tracey Jaquith. *) Исправление: в переменной $content_length при использовании chunked transfer encoding. *) Исправление: при получении ответа некорректной длины от проксируемого бэкенда nginx мог тем не менее закэшировать соединение. Спасибо Awdhesh Mathpal. *) Исправление: некорректные заголовки от бэкендов логгировались на уровне info вместо error; ошибка появилась в 1.21.1. *) Исправление: при использовании HTTP/2 и директивы aio_write запросы могли зависать. Изменения в nginx 1.21.3 07.09.2021 *) Изменение: оптимизация чтения тела запроса при использовании HTTP/2. *) Исправление: во внутреннем API для обработки тела запроса при использовании HTTP/2 и буферизации обрабатываемых данных. Изменения в nginx 1.21.2 31.08.2021 *) Изменение: теперь nginx возвращает ошибку, если в запросе по протоколу HTTP/1.0 присутствует строка заголовка "Transfer-Encoding". *) Изменение: экспортные шифры больше не поддерживаются. *) Добавление: совместимость с OpenSSL 3.0. *) Добавление: теперь серверу аутентификации почтового прокси-сервера передаются строки заголовка "Auth-SSL-Protocol" и "Auth-SSL-Cipher". Спасибо Rob Mueller. *) Добавление: API для обработки тела запроса теперь позволяет буферизировать обрабатываемые данные. *) Исправление: SSL-соединения к бэкендам в модуле stream могли зависать после SSL handshake. *) Исправление: уровень безопасности, доступный в OpenSSL 1.1.0 и новее, не учитывался при загрузке сертификатов сервера, если был задан через "@SECLEVEL=N" в директиве ssl_ciphers. *) Исправление: SSL-соединения с gRPC-бэкендами могли зависать, если использовались методы select, poll или /dev/poll. *) Исправление: при использовании HTTP/2 тело запроса всегда записывалось на диск, если в запросе не было строки заголовка "Content-Length". Изменения в nginx 1.21.1 06.07.2021 *) Изменение: теперь nginx для метода CONNECT всегда возвращает ошибку. *) Изменение: теперь nginx всегда возвращает ошибку, если в запросе одновременно присутствуют строки заголовка "Content-Length" и "Transfer-Encoding". *) Изменение: теперь nginx всегда возвращает ошибку, если в строке запроса используются пробелы или управляющие символы. *) Изменение: теперь nginx всегда возвращает ошибку, если в имени заголовка используются пробелы или управляющие символы. *) Изменение: теперь nginx всегда возвращает ошибку, если в строке "Host" заголовка запроса используются пробелы или управляющие символы. *) Изменение: оптимизация тестирования конфигурации при использовании большого количества listen-сокетов. *) Исправление: nginx не экранировал символы """, "<", ">", "\", "^", "`", "{", "|", и "}" при проксировании с изменением URI запроса. *) Исправление: SSL-переменные могли быть пустыми при записи в лог; ошибка появилась в 1.19.5. *) Исправление: keepalive-соединения с gRPC-бэкендами могли не закрываться после получения GOAWAY-фрейма. *) Исправление: уменьшено потребление памяти для долгоживущих запросов при проксировании с использованием более 64 буферов. Изменения в nginx 1.21.0 25.05.2021 *) Безопасность: при использовании директивы resolver во время обработки ответа DNS-сервера могла происходить перезапись одного байта памяти, что позволяло атакующему, имеющему возможность подделывать UDP-пакеты от DNS-сервера, вызвать падение рабочего процесса или, потенциально, выполнение произвольного кода (CVE-2021-23017). *) Добавление: директивы proxy_ssl_certificate, proxy_ssl_certificate_key, grpc_ssl_certificate, grpc_ssl_certificate_key, uwsgi_ssl_certificate и uwsgi_ssl_certificate_key поддерживают переменные. *) Добавление: директива max_errors в почтовом прокси-сервере. *) Добавление: почтовый прокси-сервер поддерживает POP3 и IMAP pipelining. *) Добавление: параметр fastopen директивы listen в модуле stream. Спасибо Anbang Wen. *) Исправление: специальные символы не экранировались при автоматическом перенаправлении с добавлением завершающего слэша. *) Исправление: при использовании SMTP pipelining соединения с клиентами в почтовом прокси-сервере могли неожиданно закрываться. Изменения в nginx 1.19.10 13.04.2021 *) Изменение: в директиве keepalive_requests значение по умолчанию изменено на 1000. *) Добавление: директива keepalive_time. *) Добавление: переменная $connection_time. *) Изменение: при использовании zlib-ng в логах появлялись сообщения "gzip filter failed to use preallocated memory". Изменения в nginx 1.19.9 30.03.2021 *) Исправление: nginx не собирался с почтовым прокси-сервером, но без модуля ngx_mail_ssl_module; ошибка появилась в 1.19.8. *) Исправление: при работе с gRPC-бэкендами могли возникать ошибки "upstream sent response body larger than indicated content length"; ошибка появилась в 1.19.1. *) Исправление: если клиент закрывал соединение в момент отбрасывания тела запроса, nginx мог не закрыть соединение до истечения keepalive-таймаута. *) Исправление: при ожидании задержки limit_req или auth_delay, а также при работе с бэкендами nginx мог не обнаружить, что соединение уже закрыто клиентом. *) Исправление: в методе обработки соединений eventport. Изменения в nginx 1.19.8 09.03.2021 *) Добавление: в директиве proxy_cookie_flags теперь флаги можно задавать с помощью переменных. *) Добавление: параметр proxy_protocol в директиве listen, директивы proxy_protocol и set_real_ip_from в почтовом прокси-сервере. *) Исправление: HTTP/2-соединения сразу закрывались при использовании "keepalive_timeout 0"; ошибка появилась в 1.19.7. *) Исправление: некоторые ошибки логгировались как неизвестные, если nginx был собран с glibc 2.32. *) Исправление: в методе обработки соединений eventport. Изменения в nginx 1.19.7 16.02.2021 *) Изменение: обработка соединений в HTTP/2 была изменена и теперь более соответствует HTTP/1.x; директивы http2_recv_timeout, http2_idle_timeout и http2_max_requests упразднены, вместо них следует использовать директивы keepalive_timeout и keepalive_requests. *) Изменение: директивы http2_max_field_size и http2_max_header_size упразднены, вместо них следует использовать директиву large_client_header_buffers. *) Добавление: теперь при исчерпании свободных соединений nginx закрывает не только keepalive-соединения, но и соединения в lingering close. *) Исправление: в логах могли появляться сообщения "zero size buf in output", если бэкенд возвращал некорректный ответ при небуферизированном проксировании; ошибка появилась в 1.19.1. *) Исправление: при использовании директивы return вместе с image_filter или xslt_stylesheet HEAD-запросы обрабатывались некорректно. *) Исправление: в директиве add_trailer. Изменения в nginx 1.19.6 15.12.2020 *) Исправление: ошибки "no live upstreams", если server в блоке upstream был помечен как down. *) Исправление: при использовании HTTPS в рабочем процессе мог произойти segmentation fault; ошибка появилась в 1.19.5. *) Исправление: nginx возвращал ошибку 400 на запросы вида "GET http://example.com?args HTTP/1.0". *) Исправление: в модулях ngx_http_flv_module и ngx_http_mp4_module. Спасибо Chris Newton. Изменения в nginx 1.19.5 24.11.2020 *) Добавление: ключ -e. *) Добавление: при сборке дополнительных модулей теперь можно указывать одни и те же исходные файлы в разных модулях. *) Исправление: SSL shutdown не работал при закрытии соединений с ожиданием дополнительных данных (lingering close). *) Исправление: при работе с gRPC-бэкендами могли возникать ошибки "upstream sent frame for closed stream". *) Исправление: во внутреннем API для обработки тела запроса. Изменения в nginx 1.19.4 27.10.2020 *) Добавление: директивы ssl_conf_command, proxy_ssl_conf_command, grpc_ssl_conf_command и uwsgi_ssl_conf_command. *) Добавление: директива ssl_reject_handshake. *) Добавление: директива proxy_smtp_auth в почтовом прокси-сервере. Изменения в nginx 1.19.3 29.09.2020 *) Добавление: модуль ngx_stream_set_module. *) Добавление: директива proxy_cookie_flags. *) Добавление: директива userid_flags. *) Исправление: расширение управления кэшированием stale-if-error ошибочно применялось, если бэкенд возвращал ответ с кодом 500, 502, 503, 504, 403, 404 или 429. *) Исправление: если использовалось кэширование и бэкенд возвращал ответы с строкой заголовка Vary, в логах могли появляться сообщения "[crit] cache file ... has too long header". *) Изменение: при использовании OpenSSL 1.1.1 в логах могли появляться сообщения "[crit] SSL_write() failed". *) Исправление: в логах могли появляться сообщения "SSL_shutdown() failed (SSL: ... bad write retry)"; ошибка появилась в 1.19.2. *) Исправление: при использовании HTTP/2 в рабочем процессе мог произойти segmentation fault, если ошибки с кодом 400 с помощью директивы error_page перенаправлялись в проксируемый location. *) Исправление: утечки сокетов при использовании HTTP/2 и подзапросов в модуле njs. Изменения в nginx 1.19.2 11.08.2020 *) Изменение: теперь nginx начинает закрывать keepalive-соединения, не дожидаясь исчерпания всех свободных соединений, а также пишет об этом предупреждение в лог ошибок. *) Изменение: оптимизация чтения тела запроса при использовании chunked transfer encoding. *) Исправление: утечки памяти при использовании директивы ssl_ocsp. *) Исправление: в логах могли появляться сообщения "zero size buf in output", если FastCGI-сервер возвращал некорректный ответ; ошибка появилась в 1.19.1. *) Исправление: в рабочем процессе мог произойти segmentation fault, если размеры large_client_header_buffers отличались в разных виртуальных серверах. *) Исправление: SSL shutdown мог не работать. *) Исправление: в логах могли появляться сообщения "SSL_shutdown() failed (SSL: ... bad write retry)". *) Исправление: в модуле ngx_http_slice_module. *) Исправление: в модуле ngx_http_xslt_filter_module. Изменения в nginx 1.19.1 07.07.2020 *) Изменение: директивы lingering_close, lingering_time и lingering_timeout теперь работают при использовании HTTP/2. *) Изменение: теперь лишние данные, присланные бэкендом, всегда отбрасываются. *) Изменение: теперь при получении слишком короткого ответа от FastCGI-сервера nginx пытается отправить клиенту доступную часть ответа, после чего закрывает соединение с клиентом. *) Изменение: теперь при получении ответа некорректной длины от gRPC-бэкенда nginx прекращает обработку ответа с ошибкой. *) Добавление: параметр min_free в директивах proxy_cache_path, fastcgi_cache_path, scgi_cache_path и uwsgi_cache_path. Спасибо Adam Bambuch. *) Исправление: nginx не удалял unix domain listen-сокеты при плавном завершении по сигналу SIGQUIT. *) Исправление: UDP-пакеты нулевого размера не проксировались. *) Исправление: проксирование на uwsgi-бэкенды с использованием SSL могло не работать. Спасибо Guanzhong Chen. *) Исправление: в обработке ошибок при использовании директивы ssl_ocsp. *) Исправление: при использовании файловых систем XFS и NFS размер кэша на диске мог считаться некорректно. *) Исправление: если сервер memcached возвращал некорректный ответ, в логах могли появляться сообщения "negative size buf in writer". Изменения в nginx 1.19.0 26.05.2020 *) Добавление: проверка клиентских сертификатов с помощью OCSP. *) Исправление: при работе с gRPC-бэкендами могли возникать ошибки "upstream sent frame for closed stream". *) Исправление: OCSP stapling мог не работать, если не была указана директива resolver. *) Исправление: соединения с некорректным HTTP/2 preface не логгировались. Изменения в nginx 1.17.10 14.04.2020 *) Добавление: директива auth_delay. Изменения в nginx 1.17.9 03.03.2020 *) Изменение: теперь nginx не разрешает несколько строк "Host" в заголовке запроса. *) Исправление: nginx игнорировал дополнительные строки "Transfer-Encoding" в заголовке запроса. *) Исправление: утечки сокетов при использовании HTTP/2. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовался OCSP stapling. *) Исправление: в модуле ngx_http_mp4_module. *) Исправление: при перенаправлении ошибок с кодом 494 с помощью директивы error_page nginx возвращал ответ с кодом 494 вместо 400. *) Исправление: утечки сокетов при использовании подзапросов в модуле njs и директивы aio. Изменения в nginx 1.17.8 21.01.2020 *) Добавление: директива grpc_pass поддерживает переменные. *) Исправление: при обработке pipelined-запросов по SSL-соединению мог произойти таймаут; ошибка появилась в 1.17.5. *) Исправление: в директиве debug_points при использовании HTTP/2. Спасибо Даниилу Бондареву. Изменения в nginx 1.17.7 24.12.2019 *) Исправление: на старте или во время переконфигурации мог произойти segmentation fault, если в конфигурации использовалась директива rewrite с пустой строкой замены. *) Исправление: в рабочем процессе мог произойти segmentation fault, если директива break использовалась совместно с директивой alias или директивой proxy_pass с URI. *) Исправление: строка Location заголовка ответа могла содержать мусор, если URI запроса был изменён на URI, содержащий нулевой символ. *) Исправление: при возврате перенаправлений с помощью директивы error_page запросы с телом обрабатывались некорректно; ошибка появилась в 0.7.12. *) Исправление: утечки сокетов при использовании HTTP/2. *) Исправление: при обработке pipelined-запросов по SSL-соединению мог произойти таймаут; ошибка появилась в 1.17.5. *) Исправление: в модуле ngx_http_dav_module. Изменения в nginx 1.17.6 19.11.2019 *) Добавление: переменные $proxy_protocol_server_addr и $proxy_protocol_server_port. *) Добавление: директива limit_conn_dry_run. *) Добавление: переменные $limit_req_status и $limit_conn_status. Изменения в nginx 1.17.5 22.10.2019 *) Добавление: теперь nginx использует вызов ioctl(FIONREAD), если он доступен, чтобы избежать чтения из быстрого соединения в течение долгого времени. *) Исправление: неполные закодированные символы в конце URI запроса игнорировались. *) Исправление: "/." и "/.." в конце URI запроса не нормализовывались. *) Исправление: в директиве merge_slashes. *) Исправление: в директиве ignore_invalid_headers. Спасибо Alan Kemp. *) Исправление: nginx не собирался с MinGW-w64 gcc 8.1 и новее. Изменения в nginx 1.17.4 24.09.2019 *) Изменение: улучшено детектирование некорректного поведения клиентов в HTTP/2. *) Изменение: в обработке непрочитанного тела запроса при возврате ошибок в HTTP/2. *) Исправление: директива worker_shutdown_timeout могла не работать при использовании HTTP/2. *) Исправление: при использовании HTTP/2 и директивы proxy_request_buffering в рабочем процессе мог произойти segmentation fault. *) Исправление: на Windows при использовании SSL уровень записи в лог ошибки ECONNABORTED был "crit" вместо "error". *) Исправление: nginx игнорировал лишние данные при использовании chunked transfer encoding. *) Исправление: если использовалась директива return и при чтении тела запроса возникала ошибка, nginx всегда возвращал ошибку 500. *) Исправление: в обработке ошибок выделения памяти. Изменения в nginx 1.17.3 13.08.2019 *) Безопасность: при использовании HTTP/2 клиент мог вызвать чрезмерное потребление памяти и ресурсов процессора (CVE-2019-9511, CVE-2019-9513, CVE-2019-9516). *) Исправление: при использовании сжатия в логах могли появляться сообщения "zero size buf"; ошибка появилась в 1.17.2. *) Исправление: при использовании директивы resolver в SMTP прокси-сервере в рабочем процессе мог произойти segmentation fault. Изменения в nginx 1.17.2 23.07.2019 *) Изменение: минимальная поддерживаемая версия zlib - 1.2.0.4. Спасибо Илье Леошкевичу. *) Изменение: метод $r->internal_redirect() встроенного перла теперь ожидает закодированный URI. *) Добавление: теперь с помощью метода $r->internal_redirect() встроенного перла можно перейти в именованный location. *) Исправление: в обработке ошибок во встроенном перле. *) Исправление: на старте или во время переконфигурации мог произойти segmentation fault, если в конфигурации использовалось значение hash bucket size больше 64 килобайт. *) Исправление: при использовании методов обработки соединений select, poll и /dev/poll nginx мог нагружать процессор во время небуферизованного проксирования и при проксировании WebSocket-соединений. *) Исправление: в модуле ngx_http_xslt_filter_module. *) Исправление: в модуле ngx_http_ssi_filter_module. Изменения в nginx 1.17.1 25.06.2019 *) Добавление: директива limit_req_dry_run. *) Добавление: при использовании директивы hash в блоке upstream пустой ключ хэширования теперь приводит к переключению на round-robin балансировку. Спасибо Niklas Keller. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовалось кэширование и директива image_filter, а ошибки с кодом 415 перенаправлялись с помощью директивы error_page; ошибка появилась в 1.11.10. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовался встроенный перл; ошибка появилась в 1.7.3. Изменения в nginx 1.17.0 21.05.2019 *) Добавление: директивы limit_rate и limit_rate_after поддерживают переменные. *) Добавление: директивы proxy_upload_rate и proxy_download_rate в модуле stream поддерживают переменные. *) Изменение: минимальная поддерживаемая версия OpenSSL - 0.9.8. *) Изменение: теперь postpone-фильтр собирается всегда. *) Исправление: директива include не работала в блоках if и limit_except. *) Исправление: в обработке byte ranges. Изменения в nginx 1.15.12 16.04.2019 *) Исправление: в рабочем процессе мог произойти segmentation fault, если в директивах ssl_certificate или ssl_certificate_key использовались переменные и был включён OCSP stapling. Изменения в nginx 1.15.11 09.04.2019 *) Исправление: в директиве ssl_stapling_file на Windows. Изменения в nginx 1.15.10 26.03.2019 *) Изменение: теперь при использовании имени хоста в директиве listen nginx создаёт listen-сокеты для всех адресов, соответствующих этому имени (ранее использовался только первый адрес). *) Добавление: диапазоны портов в директиве listen. *) Добавление: возможность загрузки SSL-сертификатов и секретных ключей из переменных. *) Изменение: переменная $ssl_server_name могла быть пустой при использовании OpenSSL 1.1.1. *) Исправление: nginx/Windows не собирался с Visual Studio 2015 и новее; ошибка появилась в 1.15.9. Изменения в nginx 1.15.9 26.02.2019 *) Добавление: директивы ssl_certificate и ssl_certificate_key поддерживают переменные. *) Добавление: метод poll теперь доступен на Windows при использовании Windows Vista и новее. *) Исправление: если при использовании метода select на Windows происходила ошибка при установлении соединения с бэкендом, nginx ожидал истечения таймаута на установление соединения. *) Исправление: директивы proxy_upload_rate и proxy_download_rate в модуле stream работали некорректно при проксировании UDP-пакетов. Изменения в nginx 1.15.8 25.12.2018 *) Добавление: переменная $upstream_bytes_sent. Спасибо Piotr Sikora. *) Добавление: новые директивы в скриптах подсветки синтаксиса для vim. Спасибо Геннадию Махомеду. *) Исправление: в директиве proxy_cache_background_update. *) Исправление: в директиве geo при использовании unix domain listen-сокетов. *) Изменение: при использовании директивы ssl_early_data с OpenSSL в логах могли появляться сообщения "ignoring stale global SSL error ... bad length". *) Исправление: в nginx/Windows. *) Исправление: в модуле ngx_http_autoindex_module на 32-битных платформах. Изменения в nginx 1.15.7 27.11.2018 *) Добавление: директива proxy_requests в модуле stream. *) Добавление: параметр "delay" директивы "limit_req". Спасибо Владиславу Шабанову и Петру Щучкину. *) Исправление: утечки памяти в случае ошибок при переконфигурации. *) Исправление: в переменных $upstream_response_time, $upstream_connect_time и $upstream_header_time. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовался модуль ngx_http_mp4_module на 32-битных платформах. Изменения в nginx 1.15.6 06.11.2018 *) Безопасность: при использовании HTTP/2 клиент мог вызвать чрезмерное потреблению памяти (CVE-2018-16843) и ресурсов процессора (CVE-2018-16844). *) Безопасность: при обработке специально созданного mp4-файла модулем ngx_http_mp4_module содержимое памяти рабочего процесса могло быть отправлено клиенту (CVE-2018-16845). *) Добавление: директивы proxy_socket_keepalive, fastcgi_socket_keepalive, grpc_socket_keepalive, memcached_socket_keepalive, scgi_socket_keepalive и uwsgi_socket_keepalive. *) Исправление: если nginx был собран с OpenSSL 1.1.0, а использовался с OpenSSL 1.1.1, протокол TLS 1.3 всегда был разрешён. *) Исправление: при работе с gRPC-бэкендами могло расходоваться большое количество памяти. Изменения в nginx 1.15.5 02.10.2018 *) Исправление: при использовании OpenSSL 1.1.0h и новее в рабочем процессе мог произойти segmentation fault; ошибка появилась в 1.15.4. *) Исправление: незначительных потенциальных ошибок. Изменения в nginx 1.15.4 25.09.2018 *) Добавление: теперь директиву ssl_early_data можно использовать с OpenSSL. *) Исправление: в модуле ngx_http_uwsgi_module. Спасибо Chris Caputo. *) Исправление: соединения к некоторым gRPC-бэкендам могли не кэшироваться при использовании директивы keepalive. *) Исправление: при использовании директивы error_page для перенаправления ошибок, возникающих на ранних этапах обработки запроса, в частности ошибок с кодом 400, могла происходить утечка сокетов. *) Исправление: директива return при возврате ошибок не изменяла код ответа, если запрос был перенаправлен с помощью директивы error_page. *) Исправление: стандартные сообщения об ошибках и ответы модуля ngx_http_autoindex_module содержали атрибут bgcolor, что могло приводить к их некорректному отображению при использовании пользовательских настроек цветов в браузерах. Спасибо Nova DasSarma. *) Изменение: уровень логгирования ошибок SSL "no suitable key share" и "no suitable signature algorithm" понижен с уровня crit до info. Изменения в nginx 1.15.3 28.08.2018 *) Добавление: теперь TLSv1.3 можно использовать с BoringSSL. *) Добавление: директива ssl_early_data, сейчас доступна при использовании BoringSSL. *) Добавление: директивы keepalive_timeout и keepalive_requests в блоке upstream. *) Исправление: модуль ngx_http_dav_module при копировании файла поверх существующего файла с помощью метода COPY не обнулял целевой файл. *) Исправление: модуль ngx_http_dav_module при перемещении файла между файловыми системами с помощью метода MOVE устанавливал нулевые права доступа на результирующий файл и не сохранял время изменения файла. *) Исправление: модуль ngx_http_dav_module при копировании файла с помощью метода COPY для результирующего файла использовал права доступа по умолчанию. *) Изменение: некоторые клиенты могли не работать при использовании HTTP/2; ошибка появилась в 1.13.5. *) Исправление: nginx не собирался с LibreSSL 2.8.0. Изменения в nginx 1.15.2 24.07.2018 *) Добавление: переменная $ssl_preread_protocol в модуле ngx_stream_ssl_preread_module. *) Добавление: теперь при использовании директивы reset_timedout_connection nginx сбрасывает соединения, закрываемые с кодом 444. *) Изменение: уровень логгирования ошибок SSL "http request", "https proxy request", "unsupported protocol" и "version too low" понижен с уровня crit до info. *) Исправление: запросы к DNS-серверу не отправлялись повторно, если при первой попытке отправки происходила ошибка. *) Исправление: параметр reuseport директивы listen игнорировался, если количество рабочих процессов было задано после директивы listen. *) Исправление: при использовании OpenSSL 1.1.0 и новее директиву ssl_prefer_server_ciphers нельзя было выключить в виртуальном сервере, если она была включена в сервере по умолчанию. *) Исправление: повторное использование SSL-сессий к бэкендам не работало с протоколом TLS 1.3. Изменения в nginx 1.15.1 03.07.2018 *) Добавление: директива random в блоке upstream. *) Добавление: улучшена производительность при использовании директив hash и ip_hash совместно с директивой zone. *) Добавление: параметр reuseport директивы listen теперь использует SO_REUSEPORT_LB на FreeBSD 12. *) Исправление: HTTP/2 server push не работал, если SSL терминировался прокси-сервером перед nginx'ом. *) Исправление: директива tcp_nopush всегда использовалась для соединений к бэкендам. *) Исправление: при отправке сохранённого на диск тела запроса на gRPC-бэкенд могли возникать ошибки. Изменения в nginx 1.15.0 05.06.2018 *) Изменение: директива "ssl" теперь считается устаревшей; вместо неё следует использовать параметр ssl директивы listen. *) Изменение: теперь при использовании директивы listen с параметром ssl nginx определяет отсутствие SSL-сертификатов при тестировании конфигурации. *) Добавление: теперь модуль stream умеет обрабатывать несколько входящих UDP-пакетов от клиента в рамках одной сессии. *) Исправление: в директиве proxy_cache_valid можно было указать некорректный код ответа. *) Исправление: nginx не собирался gcc 8.1. *) Исправление: логгирование в syslog останавливалось при изменении локального IP-адреса. *) Исправление: nginx не собирался компилятором clang, если был установлен CUDA SDK; ошибка появилась в 1.13.8. *) Исправление: при использовании unix domain listen-сокетов на FreeBSD в процессе обновления исполняемого файла в логе могли появляться сообщения "getsockopt(TCP_FASTOPEN) ... failed". *) Исправление: nginx не собирался на Fedora 28 Linux. *) Исправление: при использовании директивы limit_req заданная скорость обработки запросов могла не соблюдаться. *) Исправление: в обработке адресов клиентов при использовании unix domain listen-сокетов для работы с датаграммами на Linux. *) Исправление: в обработке ошибок выделения памяти. Изменения в nginx 1.13.12 10.04.2018 *) Исправление: при возврате большого ответа соединения с gRPC-бэкендами могли неожиданно закрываться. Изменения в nginx 1.13.11 03.04.2018 *) Добавление: параметр proxy_protocol директивы listen теперь поддерживает протокол PROXY версии 2. *) Исправление: nginx не собирался с OpenSSL 1.1.1 статически на Linux. *) Исправление: в параметрах http_404, http_500 и им подобных директивы proxy_next_upstream. Изменения в nginx 1.13.10 20.03.2018 *) Добавление: теперь параметр set в SSI-директиве include позволяет сохранять в переменную любые ответы; максимальный размер ответа задаётся директивой subrequest_output_buffer_size. *) Добавление: теперь nginx использует вызов clock_gettime(CLOCK_MONOTONIC), если он доступен, что позволяет избежать некорректного срабатывания таймаутов при изменениях системного времени. *) Добавление: параметр "escape=none" директивы log_format. Спасибо Johannes Baiter и Calin Don. *) Добавление: переменная $ssl_preread_alpn_protocols в модуле ngx_stream_ssl_preread_module. *) Добавление: модуль ngx_http_grpc_module. *) Исправление: в обработке ошибок выделения памяти в директиве geo. *) Исправление: при использовании переменных в директиве auth_basic_user_file в лог мог выводиться символ '\0'. Спасибо Вадиму Филимонову. Изменения в nginx 1.13.9 20.02.2018 *) Добавление: поддержка HTTP/2 server push; директивы http2_push и http2_push_preload. *) Исправление: при использовании кэша в логах могли появляться сообщения "header already sent"; ошибка появилась в 1.9.13. *) Исправление: при использовании директивы ssl_verify_client в рабочем процессе мог произойти segmentation fault, если в виртуальном сервере не был указан SSL-сертификат. *) Исправление: в модуле ngx_http_v2_module. *) Исправление: в модуле ngx_http_dav_module. Изменения в nginx 1.13.8 26.12.2017 *) Добавление: теперь при использовании параметра transparent директив proxy_bind, fastcgi_bind, memcached_bind, scgi_bind и uwsgi_bind nginx автоматически сохраняет capability CAP_NET_RAW в рабочих процессах. *) Добавление: улучшения в определении размера строки кэша процессора. Спасибо Debayan Ghosh. *) Добавление: новые директивы в скриптах подсветки синтаксиса для vim. Спасибо Геннадию Махомеду. *) Исправление: процедура обновления исполняемого файла не работала, если после завершения родительского процесса новым родительским процессом nginx'а становился процесс с PID, отличным от 1. *) Исправление: модуль ngx_http_autoindex_module неправильно обрабатывал запросы с телом. *) Исправление: в директиве proxy_limit_rate при использовании с директивой keepalive. *) Исправление: при использовании "proxy_buffering off" часть ответа могла буферизироваться, если клиентское соединение использовало SSL. Спасибо Patryk Lesiewicz. *) Исправление: в директиве proxy_cache_background_update. *) Исправление: переменную вида "${name}" с именем в фигурных скобках нельзя было использовать в начале параметра не заключив весь параметр в кавычки. Изменения в nginx 1.13.7 21.11.2017 *) Исправление: в переменной $upstream_status. *) Исправление: в рабочем процессе мог произойти segmentation fault, если бэкенд возвращал ответ "101 Switching Protocols" на подзапрос. *) Исправление: если при переконфигурации изменялся размер зоны разделяемой памяти и переконфигурация завершалась неудачно, то в главном процессе происходил segmentation fault. *) Исправление: в модуле ngx_http_fastcgi_module. *) Исправление: nginx возвращал ошибку 500, если в директиве xslt_stylesheet были заданы параметры без использования переменных. *) Изменение: при использовании варианта библиотеки zlib от Intel в лог писались сообщения "gzip filter failed to use preallocated memory". *) Исправление: директива worker_shutdown_timeout не работала при использовании почтового прокси-сервера и при проксировании WebSocket-соединений. Изменения в nginx 1.13.6 10.10.2017 *) Исправление: при использовании директивы ssl_preread в модуле stream не работало переключение на следующий бэкенд. *) Исправление: в модуле ngx_http_v2_module. Спасибо Piotr Sikora. *) Исправление: nginx не поддерживал даты после 2038 года на 32-битных платформах с 64-битным time_t. *) Исправление: в обработке дат до 1970 года и после 10000 года. *) Исправление: в модуле stream таймауты ожидания UDP-пакетов от бэкендов не логгировались или логгировались на уровне info вместо error. *) Исправление: при использовании HTTP/2 nginx мог вернуть ошибку 400, не указав в логе причину. *) Исправление: в обработке повреждённых файлов кэша. *) Исправление: при кэшировании ошибок, перехваченных error_page, не учитывались заголовки управления кэшированием. *) Исправление: при использовании HTTP/2 тело запроса могло быть повреждено. *) Исправление: в обработке адресов клиентов при использовании unix domain сокетов. *) Исправление: при использовании директивы "hash ... consistent" в блоке upstream nginx нагружал процессор, если использовались большие веса и все или почти все бэкенды были недоступны. Изменения в nginx 1.13.5 05.09.2017 *) Добавление: переменная $ssl_client_escaped_cert. *) Исправление: директива ssl_session_ticket_key и параметр include директивы geo не работали на Windows. *) Исправление: на 32-битных платформах при запросе более 4 гигабайт с помощью нескольких диапазонов возвращалась некорректная длина ответа. *) Исправление: директива "expires modified" и обработка строки If-Range заголовка запроса не учитывали время последнего изменения ответа, если использовалось проксирование без кэширования. Изменения в nginx 1.13.4 08.08.2017 *) Добавление: модуль ngx_http_mirror_module. *) Исправление: клиентские соединения могли сбрасываться при тестировании конфигурации, если использовался параметр reuseport директивы listen на Linux. *) Исправление: тело запроса могло быть недоступно в подзапросах, если оно было сохранено в файл и использовалось проксирование. *) Исправление: очистка кэша по max_size не работала на Windows. *) Исправление: любое выделение разделяемой памяти на Windows требовало 4096 байт памяти. *) Исправление: при использовании директивы zone в блоке upstream на Windows рабочий процесс мог завершаться аварийно. Изменения в nginx 1.13.3 11.07.2017 *) Безопасность: специально созданный запрос мог вызвать целочисленное переполнение в range-фильтре и последующую некорректную обработку запрошенных диапазонов, что потенциально могло привести к утечке конфиденциальной информации (CVE-2017-7529). Изменения в nginx 1.13.2 27.06.2017 *) Изменение: теперь при запросе диапазона, начинающегося с 0, из пустого файла nginx возвращает ответ 200 вместо 416. *) Добавление: директива add_trailer. Спасибо Piotr Sikora. *) Исправление: nginx не собирался под Cygwin и NetBSD; ошибка появилась в 1.13.0. *) Исправление: nginx не собирался под MSYS2 / MinGW 64-bit. Спасибо Orgad Shaneh. *) Исправление: при использовании SSI с большим количеством подзапросов и proxy_pass с переменными в рабочем процессе мог произойти segmentation fault. *) Исправление: в модуле ngx_http_v2_module. Спасибо Piotr Sikora. Изменения в nginx 1.13.1 30.05.2017 *) Добавление: теперь в качестве параметра директивы set_real_ip_from можно указывать имя хоста. *) Добавление: улучшения в скриптах подсветки синтаксиса для vim. *) Добавление: директива worker_cpu_affinity теперь работает на DragonFly BSD. Спасибо Sepherosa Ziehau. *) Исправление: SSL renegotiation в соединениях к бэкендам не работал при использовании OpenSSL до 1.1.0. *) Изменение: nginx не собирался с Oracle Developer Studio 12.5. *) Изменение: теперь cache manager пропускает заблокированные записи при очистке кэша по max_size. *) Исправление: клиентские SSL-соединения сразу закрывались, если использовался отложенный accept и параметр proxy_protocol директивы listen. *) Исправление: в директиве proxy_cache_background_update. *) Изменение: теперь директива tcp_nodelay устанавливает опцию TCP_NODELAY перед SSL handshake. Изменения в nginx 1.13.0 25.04.2017 *) Изменение: теперь SSL renegotiation допускается в соединениях к бэкендам. *) Добавление: параметры rcvbuf и sndbuf директив listen в почтовом прокси-сервере и модуле stream. *) Добавление: директивы return и error_page теперь могут использоваться для возврата перенаправлений с кодом 308. Спасибо Simon Leblanc. *) Добавление: параметр TLSv1.3 в директиве ssl_protocols. *) Добавление: при логгировании сигналов теперь указывается PID отправившего сигнал процесса. *) Исправление: в обработке ошибок выделения памяти. *) Исправление: если сервер в модуле stream слушал на wildcard-адресе, исходящий адрес ответного UDP-пакета мог отличаться от адреса назначения исходного пакета. Изменения в nginx 1.11.13 04.04.2017 *) Добавление: параметр http_429 в директивах proxy_next_upstream, fastcgi_next_upstream, scgi_next_upstream и uwsgi_next_upstream. Спасибо Piotr Sikora. *) Исправление: в обработке ошибок выделения памяти. *) Исправление: при использовании директив sendfile и timer_resolution на Linux запросы могли зависать. *) Исправление: при использовании с подзапросами директив sendfile и aio_write запросы могли зависать. *) Исправление: в модуле ngx_http_v2_module. Спасибо Piotr Sikora. *) Исправление: при использовании HTTP/2 в рабочем процессе мог произойти segmentation fault. *) Исправление: запросы могли зависать при использовании с подзапросами директив limit_rate, sendfile_max_chunk, limit_req или метода $r->sleep() встроенного перла. *) Исправление: в модуле ngx_http_slice_module. Изменения в nginx 1.11.12 24.03.2017 *) Исправление: nginx мог нагружать процессор; ошибка появилась в 1.11.11. Изменения в nginx 1.11.11 21.03.2017 *) Добавление: директива worker_shutdown_timeout. *) Добавление: улучшения в скриптах подсветки синтаксиса для vim. Спасибо Wei-Ko Kao. *) Исправление: при попытке установить переменную $limit_rate в пустую строку в рабочем процессе мог произойти segmentation fault. *) Исправление: директивы proxy_cache_background_update, fastcgi_cache_background_update, scgi_cache_background_update и uwsgi_cache_background_update могли работать некорректно, если использовалась директива if. *) Исправление: в рабочем процессе мог произойти segmentation fault, если количество large_client_header_buffers в виртуальном сервере отличалось от такового в сервере по умолчанию. *) Исправление: в почтовом прокси-сервере. Изменения в nginx 1.11.10 14.02.2017 *) Изменение: формат заголовка кэша был изменен, ранее закэшированные ответы будут загружены заново. *) Добавление: поддержка расширений stale-while-revalidate и stale-if-error в строке "Cache-Control" в заголовке ответа бэкенда. *) Добавление: директивы proxy_cache_background_update, fastcgi_cache_background_update, scgi_cache_background_update и uwsgi_cache_background_update. *) Добавление: теперь nginx может кэшировать ответы со строкой Vary заголовка длиной до 128 символов (вместо 42 символов в предыдущих версиях). *) Добавление: параметр build директивы server_tokens. Спасибо Tom Thorogood. *) Исправление: при обработке запросов со строкой "Expect: 100-continue" в заголовке запроса в логах могли появляться сообщения "[crit] SSL_write() failed". *) Исправление: модуль ngx_http_slice_module не работал в именованных location'ах. *) Исправление: при использовании AIO после перенаправления запроса с помощью X-Accel-Redirect в рабочем процессе мог произойти segmentation fault. *) Исправление: уменьшено потребление памяти для долгоживущих запросов, использующих сжатие. Изменения в nginx 1.11.9 24.01.2017 *) Исправление: при использовании модуля stream nginx мог нагружать процессор; ошибка появилась в 1.11.5. *) Исправление: метод аутентификации EXTERNAL в почтовом прокси-сервере можно было использовать, даже если он не был разрешён в конфигурации. *) Исправление: при использовании директивы ssl_verify_client модуля stream в рабочем процессе мог произойти segmentation fault. *) Исправление: директива ssl_verify_client модуля stream могла не работать. *) Исправление: при исчерпании рабочим процессом свободных соединений keepalive-соединения могли закрываться излишне агрессивно. Спасибо Joel Cunningham. *) Исправление: при использовании директивы sendfile на FreeBSD и macOS мог возвращаться некорректный ответ; ошибка появилась в 1.7.8. *) Исправление: при использовании директивы aio_write ответ мог сохраняться в кэш не полностью. *) Исправление: при использовании директивы aio_write могла происходить утечка сокетов. Изменения в nginx 1.11.8 27.12.2016 *) Добавление: директива absolute_redirect. *) Добавление: параметр escape директивы log_format. *) Добавление: проверка клиентских SSL-сертификатов в модуле stream. *) Добавление: директива ssl_session_ticket_key поддерживает шифрование TLS session tickets с помощью AES256 при использовании с 80-байтными ключами. *) Добавление: поддержка vim-commentary в скриптах для vim. Спасибо Armin Grodon. *) Исправление: рекурсия при получении значений переменных не ограничивалась. *) Исправление: в модуле ngx_stream_ssl_preread_module. *) Исправление: если сервер, описанный в блоке upstream в модуле stream, был признан неработающим, то после истечения fail_timeout он признавался работающим только после завершения тестового соединения; теперь достаточно, чтобы соединение было успешно установлено. *) Исправление: nginx/Windows не собирался с 64-битным Visual Studio. *) Исправление: nginx/Windows не собирался с OpenSSL 1.1.0. Изменения в nginx 1.11.7 13.12.2016 *) Изменение: переменная $ssl_client_verify теперь в случае ошибки проверки клиентского сертификата содержит строку с описанием ошибки, например, "FAILED:certificate has expired". *) Добавление: переменные $ssl_ciphers, $ssl_curves, $ssl_client_v_start, $ssl_client_v_end и $ssl_client_v_remain. *) Добавление: параметр volatile директивы map. *) Исправление: при сборке динамических модулей не учитывались заданные для модуля зависимости. *) Исправление: при использовании HTTP/2 и директив limit_req или auth_request тело запроса могло быть повреждено; ошибка появилась в 1.11.0. *) Исправление: при использовании HTTP/2 в рабочем процессе мог произойти segmentation fault; ошибка появилась в 1.11.3. *) Исправление: в модуле ngx_http_mp4_module. Спасибо Congcong Hu. *) Исправление: в модуле ngx_http_perl_module. Изменения в nginx 1.11.6 15.11.2016 *) Изменение: формат переменных $ssl_client_s_dn и $ssl_client_i_dn изменён на соответствующий RFC 2253 (RFC 4514); значения в старом формате доступны через переменные $ssl_client_s_dn_legacy и $ssl_client_i_dn_legacy. *) Изменение: при сохранении временных файлов в каталоге кэша они теперь располагаются не в отдельном подкаталоге для временных файлов, а в том же подкаталоге, что и соответствующие файлы в кэше. *) Добавление: поддержка метода аутентификации EXTERNAL в почтовом прокси-сервере. Спасибо Robert Norris. *) Добавление: поддержка WebP в модуле ngx_http_image_filter_module. *) Добавление: директива proxy_method поддерживает переменные. Спасибо Дмитрию Лазуркину. *) Добавление: директива http2_max_requests в модуле ngx_http_v2_module. *) Добавление: директивы proxy_cache_max_range_offset, fastcgi_cache_max_range_offset, scgi_cache_max_range_offset и uwsgi_cache_max_range_offset. *) Исправление: плавное завершение старых рабочих процессов могло занимать бесконечное время при использовании HTTP/2. *) Исправление: в модуле ngx_http_mp4_module. *) Исправление: при проксировании WebSocket-соединений и включённом кэшировании в логах могли появляться сообщения "ignore long locked inactive cache entry". *) Исправление: если во время SSL handshake с бэкендом происходил таймаут, nginx ничего не писал в лог и возвращал ответ с кодом 502 вместо 504. Изменения в nginx 1.11.5 11.10.2016 *) Изменение: параметр configure --with-ipv6 упразднён, поддержка IPv6 теперь собирается автоматически. *) Изменение: теперь, если в блоке upstream не оказалось доступных серверов, nginx не сбрасывает статистику ошибок всех серверов, как делал ранее, а ожидает истечения fail_timeout. *) Добавление: модуль ngx_stream_ssl_preread_module. *) Добавление: директива server в блоке upstream поддерживает параметр max_conns. *) Добавление: параметр configure --with-compat. *) Добавление: параметры manager_files, manager_threshold и manager_sleep директив proxy_cache_path, fastcgi_cache_path, scgi_cache_path и uwsgi_cache_path. *) Исправление: при сборке perl-модуля не использовались флаги, заданные с помощью параметра configure --with-ld-opt. *) Исправление: в директиве add_after_body при использовании совместно с директивой sub_filter. *) Исправление: в переменной $realip_remote_addr. *) Исправление: директивы dav_access, proxy_store_access, fastcgi_store_access, scgi_store_access и uwsgi_store_access игнорировали права, заданные для пользователя. *) Исправление: unix domain listen-сокеты могли не наследоваться при обновлении исполняемого файла на Linux. *) Исправление: nginx возвращал ошибку 400 на запросы с символом "-" в HTTP-методе. Изменения в nginx 1.11.4 13.09.2016 *) Добавление: переменная $upstream_bytes_received. *) Добавление: переменные $bytes_received, $session_time, $protocol, $status, $upstream_addr, $upstream_bytes_sent, $upstream_bytes_received, $upstream_connect_time, $upstream_first_byte_time и $upstream_session_time в модуле stream. *) Добавление: модуль ngx_stream_log_module. *) Добавление: параметр proxy_protocol в директиве listen, переменные $proxy_protocol_addr и $proxy_protocol_port в модуле stream. *) Добавление: модуль ngx_stream_realip_module. *) Исправление: nginx не собирался с модулем stream и модулем ngx_http_ssl_module, но без модуля ngx_stream_ssl_module; ошибка появилась в 1.11.3. *) Добавление: опция сокета IP_BIND_ADDRESS_NO_PORT не использовалась; ошибка появилась в 1.11.2. *) Исправление: в параметре ranges директивы geo. *) Исправление: при использовании директив "aio threads" и sendfile мог возвращаться некорректный ответ; ошибка появилась в 1.9.13. Изменения в nginx 1.11.3 26.07.2016 *) Изменение: теперь accept_mutex по умолчанию выключен. *) Добавление: теперь nginx использует EPOLLEXCLUSIVE на Linux. *) Добавление: модуль ngx_stream_geo_module. *) Добавление: модуль ngx_stream_geoip_module. *) Добавление: модуль ngx_stream_split_clients_module. *) Добавление: директивы proxy_pass и proxy_ssl_name в модуле stream поддерживают переменные. *) Исправление: утечки сокетов при использовании HTTP/2. *) Исправление: в configure. Спасибо Piotr Sikora. Изменения в nginx 1.11.2 05.07.2016 *) Изменение: теперь nginx всегда использует внутренние реализации MD5 и SHA1; параметры configure --with-md5 и --with-sha1 упразднены. *) Добавление: поддержка переменных в модуле stream. *) Добавление: модуль ngx_stream_map_module. *) Добавление: модуль ngx_stream_return_module. *) Добавление: в директивах proxy_bind, fastcgi_bind, memcached_bind, scgi_bind и uwsgi_bind теперь можно указывать порт. *) Добавление: теперь nginx использует опцию сокета IP_BIND_ADDRESS_NO_PORT, если она доступна. *) Исправление: при использовании HTTP/2 и директивы proxy_request_buffering в рабочем процессе мог произойти segmentation fault. *) Исправление: при использовании HTTP/2 к запросам, передаваемым на бэкенд, всегда добавлялась строка заголовка "Content-Length", даже если у запроса не было тела. *) Исправление: при использовании HTTP/2 в логах могли появляться сообщения "http request count is zero". *) Исправление: при использовании директивы sub_filter могло буферизироваться больше данных, чем это необходимо; проблема появилась в 1.9.4. Изменения в nginx 1.11.1 31.05.2016 *) Безопасность: при записи тела специально созданного запроса во временный файл в рабочем процессе мог происходить segmentation fault (CVE-2016-4450); ошибка появилась в 1.3.9. Изменения в nginx 1.11.0 24.05.2016 *) Добавление: параметр transparent директив proxy_bind, fastcgi_bind, memcached_bind, scgi_bind и uwsgi_bind. *) Добавление: переменная $request_id. *) Добавление: директива map поддерживает комбинации нескольких переменных в качестве результирующих значений. *) Добавление: теперь при использовании метода epoll nginx проверяет, поддерживает ли ядро события EPOLLRDHUP, и соответственно оптимизирует обработку соединений. *) Добавление: директивы ssl_certificate и ssl_certificate_key теперь можно указывать несколько раз для загрузки сертификатов разных типов (например, RSA и ECDSA). *) Добавление: при использовании OpenSSL 1.0.2 и новее с помощью директивы ssl_ecdh_curve теперь можно задать список кривых; по умолчанию используется встроенный в OpenSSL список кривых. *) Изменение: для использования DHE-шифров теперь надо явно задавать файл параметров с помощью директивы ssl_dhparam. *) Добавление: переменная $proxy_protocol_port. *) Добавление: переменная $realip_remote_port в модуле ngx_http_realip_module. *) Добавление: модуль ngx_http_realip_module теперь позволяет устанавливать не только адрес, но и порт клиента. *) Изменение: при попытке запросить виртуальный сервер, отличающийся от согласованного в процессе SSL handshake, теперь возвращается ответ "421 Misdirected Request"; это улучшает совместимость с некоторыми HTTP/2-клиентами в случае использования клиентских сертификатов. *) Изменение: HTTP/2-клиенты теперь могут сразу присылать тело запроса; директива http2_body_preread_size позволяет указать размер буфера, который будет использоваться до того, как nginx начнёт читать тело. *) Исправление: при использовании директивы proxy_cache_bypass не обновлялись закэшированные ошибочные ответы. Изменения в nginx 1.9.15 19.04.2016 *) Исправление: при использовании HHVM в качестве FastCGI-сервера могли возникать ошибки "recv() failed". *) Исправление: при использовании HTTP/2 и директив limit_req или auth_request при чтении тела запроса мог произойти таймаут или ошибка "client violated flow control"; ошибка появилась в 1.9.14. *) Изменение: при использовании HTTP/2 ответ мог не показываться некоторыми браузерами, если тело запроса было прочитано не целиком; ошибка появилась в 1.9.14. *) Исправление: при использовании директивы "aio threads" соединения могли зависать. Спасибо Mindaugas Rasiukevicius. Изменения в nginx 1.9.14 05.04.2016 *) Добавление: совместимость с OpenSSL 1.1.0. *) Добавление: директивы proxy_request_buffering, fastcgi_request_buffering, scgi_request_buffering и uwsgi_request_buffering теперь работают при использовании HTTP/2. *) Исправление: при использовании HTTP/2 в логах могли появляться сообщения "zero size buf in output". *) Исправление: при использовании HTTP/2 директива client_max_body_size могла работать неверно. *) Исправление: незначительных ошибок логгирования. Изменения в nginx 1.9.13 29.03.2016 *) Изменение: неидемпотентные запросы (POST, LOCK, PATCH) теперь по умолчанию не передаются на другой сервер, если запрос уже был отправлен на бэкенд; параметр non_idempotent директивы proxy_next_upstream явно разрешает повторять такие запросы. *) Добавление: модуль ngx_http_perl_module теперь можно собрать динамически. *) Добавление: поддержка UDP в модуле stream. *) Добавление: директива aio_write. *) Добавление: теперь cache manager следит за количеством элементов в кэше и старается не допускать переполнений зоны разделяемой памяти. *) Исправление: при использовании директив sendfile и aio с подзапросами в логах могли появляться сообщения "task already active" и "second aio post". *) Исправление: при использовании кэширования в логах могли появляться сообщения "zero size buf in output", если клиент закрывал соединение преждевременно. *) Исправление: при использовании кэширования соединения с клиентами могли закрываться без необходимости. Спасибо Justin Li. *) Исправление: nginx мог нагружать процессор при использовании директивы sendfile на Linux и Solaris, если отправляемый файл был изменён в процессе отправки. *) Исправление: при использовании директив sendfile и "aio threads" соединения могли зависать. *) Исправление: в директивах proxy_pass, fastcgi_pass, scgi_pass и uwsgi_pass при использовании переменных. Спасибо Piotr Sikora. *) Исправление: в модуле ngx_http_sub_filter_module. *) Исправление: если в закэшированном соединении к бэкенду происходила ошибка, запрос передавался на другой сервер без учёта директивы proxy_next_upstream. *) Исправление: ошибки "CreateFile() failed" при создании временных файлов на Windows. Изменения в nginx 1.9.12 24.02.2016 *) Добавление: кодирование Хаффмана заголовков ответов в HTTP/2. Спасибо Владу Краснову. *) Добавление: директива worker_cpu_affinity теперь поддерживает более 64 процессоров. *) Исправление: совместимость со сторонними модулями на C++; ошибка появилась в 1.9.11. Спасибо Piotr Sikora. *) Исправление: nginx не собирался статически с OpenSSL на Linux; ошибка появилась в 1.9.11. *) Исправление: директива "add_header ... always" с пустым значением не удаляла из заголовков ошибочных ответов строки Last-Modified и ETag. *) Изменение: при использовании OpenSSL 1.0.2f в логах могли появляться сообщения "called a function you should not call" и "shutdown while in init". *) Исправление: ошибочные заголовки могли логгироваться некорректно. *) Исправление: утечки сокетов при использовании HTTP/2. *) Исправление: в модуле ngx_http_v2_module. Изменения в nginx 1.9.11 09.02.2016 *) Добавление: теперь resolver поддерживает TCP. *) Добавление: динамические модули. *) Исправление: при использовании HTTP/2 переменная $request_length не учитывала размер заголовков запроса. *) Исправление: в модуле ngx_http_v2_module. Изменения в nginx 1.9.10 26.01.2016 *) Безопасность: при использовании директивы resolver во время обработки ответов DNS-сервера могло происходить разыменование некорректного адреса, что позволяло атакующему, имеющему возможность подделывать UDP-пакеты от DNS-сервера, вызвать segmentation fault в рабочем процессе (CVE-2016-0742). *) Безопасность: при использовании директивы resolver во время обработки CNAME-записей могло произойти обращение к ранее освобождённой памяти, что позволяло атакующему, имеющему возможность инициировать преобразование произвольных имён в адреса, вызвать segmentation fault в рабочем процессе, а также потенциально могло иметь другие последствия (CVE-2016-0746). *) Безопасность: при использовании директивы resolver во время обработки CNAME-записей не во всех случаях проверялось ограничение на максимальное количество записей в цепочке, что позволяло атакующему, имеющему возможность инициировать преобразование произвольных имён в адреса, вызвать чрезмерное потребление ресурсов рабочими процессами (CVE-2016-0747). *) Добавление: параметр auto директивы worker_cpu_affinity. *) Исправление: параметр proxy_protocol директивы listen не работал с IPv6 listen-сокетами. *) Исправление: при использовании директивы keepalive соединения к бэкендам могли кэшироваться некорректно. *) Исправление: после перенаправления запроса с помощью X-Accel-Redirect при проксировании использовался HTTP-метод оригинального запроса. Изменения в nginx 1.9.9 09.12.2015 *) Исправление: проксирование в unix domain сокеты не работало при использовании переменных; ошибка появилась в 1.9.8. Изменения в nginx 1.9.8 08.12.2015 *) Добавление: поддержка pwritev(). *) Добавление: директива include в блоке upstream. *) Добавление: модуль ngx_http_slice_module. *) Исправление: при использовании LibreSSL в рабочем процессе мог произойти segmentation fault; ошибка появилась в 1.9.6. *) Исправление: nginx мог не собираться на OS X. Изменения в nginx 1.9.7 17.11.2015 *) Добавление: параметр nohostname логгирования в syslog. *) Добавление: директива proxy_cache_convert_head. *) Добавление: переменная $realip_remote_addr в модуле ngx_http_realip_module. *) Исправление: директива expires могла не срабатывать при использовании переменных. *) Исправление: при использовании HTTP/2 в рабочем процессе мог произойти segmentation fault; ошибка появилась в 1.9.6. *) Исправление: если nginx был собран с модулем ngx_http_v2_module, протокол HTTP/2 мог быть использован клиентом, даже если не был указан параметр http2 директивы listen. *) Исправление: в модуле ngx_http_v2_module. Изменения в nginx 1.9.6 27.10.2015 *) Исправление: при использовании HTTP/2 в рабочем процессе мог произойти segmentation fault. Спасибо Piotr Sikora и Denis Andzakovic. *) Исправление: при использовании HTTP/2 переменная $server_protocol была пустой. *) Исправление: SSL-соединения к бэкендам в модуле stream могли неожиданно завершаться по таймауту. *) Исправление: при использовании различных настроек ssl_session_cache в разных виртуальных серверах в рабочем процессе мог произойти segmentation fault. *) Исправление: nginx/Windows не собирался с MinGW gcc; ошибка появилась в 1.9.4. Спасибо Kouhei Sutou. *) Исправление: при использовании директивы timer_resolution на Windows время не обновлялось. *) Незначительные исправления и улучшения. Спасибо Markus Linnala, Kurtis Nusbaum и Piotr Sikora. Изменения в nginx 1.9.5 22.09.2015 *) Добавление: модуль ngx_http_v2_module (заменяет модуль ngx_http_spdy_module). Спасибо Dropbox и Automattic за спонсирование разработки. *) Изменение: теперь по умолчанию директива output_buffers использует два буфера. *) Изменение: теперь nginx ограничивает максимальную вложенность подзапросов, а не количество одновременных подзапросов. *) Изменение: теперь при возврате ответов из кэша nginx проверяет ключ полностью. Спасибо Геннадию Махомеду и Сергею Брестеру. *) Исправление: при использовании кэша в логах могли появляться сообщения "header already sent"; ошибка появилась в 1.7.5. *) Исправление: при использовании CephFS и директивы timer_resolution на Linux в логах могли появляться сообщения "writev() failed (4: Interrupted system call)". *) Исправление: в обработке ошибок конфигурации. Спасибо Markus Linnala. *) Исправление: при использовании директивы sub_filter на уровне http в рабочем процессе происходил segmentation fault; ошибка появилась в 1.9.4. Изменения в nginx 1.9.4 18.08.2015 *) Изменение: директивы proxy_downstream_buffer и proxy_upstream_buffer в модуле stream заменены директивой proxy_buffer_size. *) Добавление: директива tcp_nodelay в модуле stream. *) Добавление: теперь можно указать несколько директив sub_filter одновременно. *) Добавление: директива sub_filter поддерживает переменные в строке поиска. *) Изменение: тестирование конфигурации могло не работать под Linux OpenVZ. Спасибо Геннадию Махомеду. *) Исправление: после переконфигурации старые рабочие процессы могли сильно нагружать процессор при больших значениях worker_connections. *) Исправление: при совместном использовании директив try_files и alias внутри location'а, заданного регулярным выражением, в рабочем процессе мог произойти segmentation fault; ошибка появилась в 1.7.1. *) Исправление: директива try_files внутри вложенного location'а, заданного регулярным выражением, работала неправильно, если во внешнем location'е использовалась директива alias. *) Исправление: в обработке ошибок при построении хэш-таблиц. *) Исправление: nginx не собирался с Visual Studio 2015. Изменения в nginx 1.9.3 14.07.2015 *) Изменение: дублирующиеся блоки http, mail и stream теперь запрещены. *) Добавление: ограничение количества соединений в модуле stream. *) Добавление: ограничение скорости в модуле stream. *) Исправление: директива zone в блоке upstream не работала на Windows. *) Исправление: совместимость с LibreSSL в модуле stream. Спасибо Piotr Sikora. *) Исправление: в параметре --builddir в configure. Спасибо Piotr Sikora. *) Исправление: директива ssl_stapling_file не работала; ошибка появилась в 1.9.2. Спасибо Faidon Liambotis и Brandon Black. *) Исправление: при использовании директивы ssl_stapling в рабочем процессе мог произойти segmentation fault; ошибка появилась в 1.9.2. Спасибо Matthew Baldwin. Изменения в nginx 1.9.2 16.06.2015 *) Добавление: параметр backlog директивы listen в почтовом прокси-сервере и модуле stream. *) Добавление: директивы allow и deny в модуле stream. *) Добавление: директива proxy_bind в модуле stream. *) Добавление: директива proxy_protocol в модуле stream. *) Добавление: ключ -T. *) Добавление: параметр REQUEST_SCHEME добавлен в стандартные конфигурационные файлы fastcgi.conf, fastcgi_params, scgi_params и uwsgi_params. *) Исправление: параметр reuseport директивы listen в модуле stream не работал. *) Исправление: OCSP stapling в некоторых случаях мог вернуть устаревший OCSP-ответ. Изменения в nginx 1.9.1 26.05.2015 *) Изменение: теперь протокол SSLv3 по умолчанию запрещён. *) Изменение: некоторые давно устаревшие директивы больше не поддерживаются. *) Добавление: параметр reuseport директивы listen. Спасибо Yingqi Lu из Intel и Sepherosa Ziehau. *) Добавление: переменная $upstream_connect_time. *) Исправление: в директиве hash на big-endian платформах. *) Исправление: nginx мог не запускаться на некоторых старых версиях Linux; ошибка появилась в 1.7.11. *) Исправление: в парсинге IP-адресов. Спасибо Сергею Половко. Изменения в nginx 1.9.0 28.04.2015 *) Изменение: устаревшие методы обработки соединений aio и rtsig больше не поддерживаются. *) Добавление: директива zone в блоке upstream. *) Добавление: модуль stream. *) Добавление: поддержка byte ranges для ответов модуля ngx_http_memcached_module. Спасибо Martin Mlynář. *) Добавление: разделяемую память теперь можно использовать на версиях Windows с рандомизацией адресного пространства. Спасибо Сергею Брестеру. *) Добавление: директиву error_log теперь можно использовать на уровнях mail и server в почтовом прокси-сервере. *) Исправление: параметр proxy_protocol директивы listen не работал, если не был указан в первой директиве listen для данного listen-сокета. Изменения в nginx 1.7.12 07.04.2015 *) Добавление: теперь директива tcp_nodelay работает для SSL-соединений с бэкендами. *) Добавление: теперь потоки могут использоваться для чтения заголовков файлов в кэше. *) Исправление: в директиве proxy_request_buffering. *) Исправление: при использовании потоков на Linux в рабочем процессе мог произойти segmentation fault. *) Исправление: в обработке ошибок при использовании директивы ssl_stapling. Спасибо Filipe da Silva. *) Исправление: в модуле ngx_http_spdy_module. Изменения в nginx 1.7.11 24.03.2015 *) Изменение: параметр sendfile директивы aio более не нужен; теперь nginx автоматически использует AIO для подгрузки данных для sendfile, если одновременно используются директивы aio и sendfile. *) Добавление: экспериментальная поддержка потоков. *) Добавление: директивы proxy_request_buffering, fastcgi_request_buffering, scgi_request_buffering и uwsgi_request_buffering. *) Добавление: экспериментальное API для обработки тела запроса. *) Добавление: проверка клиентских SSL-сертификатов в почтовом прокси-сервере. Спасибо Sven Peter, Franck Levionnois и Filipe Da Silva. *) Добавление: уменьшение времени запуска при использовании директивы "hash ... consistent" в блоке upstream. Спасибо Wai Keen Woon. *) Добавление: отладочное логгирование в кольцевой буфер в памяти. *) Исправление: в обработке хэш-таблиц. Спасибо Chris West. *) Исправление: в директиве proxy_cache_revalidate. *) Исправление: SSL-соединения могли зависать, если использовался отложенный accept или параметр proxy_protocol директивы listen. Спасибо James Hamlin. *) Исправление: переменная $upstream_response_time могла содержать неверное значение при использовании директивы image_filter. *) Исправление: в обработке целочисленных переполнений. Спасибо Régis Leroy. *) Исправление: при использовании LibreSSL было невозможно включить поддержку SSLv3. *) Исправление: при использовании LibreSSL в логах появлялись сообщения "ignoring stale global SSL error ... called a function you should not call". *) Исправление: сертификаты, указанные в директивах ssl_client_certificate и ssl_trusted_certificate, использовались для автоматического построения цепочек сертификатов. Изменения в nginx 1.7.10 10.02.2015 *) Добавление: параметр use_temp_path директив proxy_cache_path, fastcgi_cache_path, scgi_cache_path и uwsgi_cache_path. *) Добавление: переменная $upstream_header_time. *) Изменение: теперь при переполнении диска nginx пытается писать error_log'и только раз в секунду. *) Исправление: директива try_files при тестировании каталогов не игнорировала обычные файлы. Спасибо Damien Tournoud. *) Исправление: при использовании директивы sendfile на OS X возникали ошибки "sendfile() failed"; ошибка появилась в nginx 1.7.8. *) Исправление: в лог могли писаться сообщения "sem_post() failed". *) Исправление: nginx не собирался с musl libc. Спасибо James Taylor. *) Исправление: nginx не собирался на Tru64 UNIX. Спасибо Goetz T. Fischer. Изменения в nginx 1.7.9 23.12.2014 *) Добавление: директивы proxy_cache, fastcgi_cache, scgi_cache и uwsgi_cache поддерживают переменные. *) Добавление: директива expires поддерживает переменные. *) Добавление: возможность загрузки секретных ключей с аппаратных устройств с помощью OpenSSL engines. Спасибо Дмитрию Пичулину. *) Добавление: директива autoindex_format. *) Исправление: ревалидация элементов кэша теперь используется только для ответов с кодами 200 и 206. Спасибо Piotr Sikora. *) Исправление: строка "TE" заголовка запроса клиента передавалась на бэкенд при проксировании. *) Исправление: директивы proxy_pass, fastcgi_pass, scgi_pass и uwsgi_pass могли неправильно работать внутри блоков if и limit_except. *) Исправление: директива proxy_store с параметром "on" игнорировалась, если на предыдущем уровне использовалась директива proxy_store с явно заданным путём к файлам. *) Исправление: nginx не собирался с BoringSSL. Спасибо Lukas Tribus. Изменения в nginx 1.7.8 02.12.2014 *) Изменение: теперь строки "If-Modified-Since", "If-Range" и им подобные в заголовке запроса клиента передаются бэкенду при включённом кэшировании, если nginx заранее знает, что не будет кэшировать ответ (например, при использовании proxy_cache_min_uses). *) Изменение: теперь после истечения proxy_cache_lock_timeout nginx отправляет запрос на бэкенд без кэширования; новые директивы proxy_cache_lock_age, fastcgi_cache_lock_age, scgi_cache_lock_age и uwsgi_cache_lock_age позволяют указать, через какое время блокировка будет принудительно снята и будет сделана ещё одна попытка закэшировать ответ. *) Изменение: директива log_format теперь может использоваться только на уровне http. *) Добавление: директивы proxy_ssl_certificate, proxy_ssl_certificate_key, proxy_ssl_password_file, uwsgi_ssl_certificate, uwsgi_ssl_certificate_key и uwsgi_ssl_password_file. Спасибо Piotr Sikora. *) Добавление: теперь с помощью X-Accel-Redirect можно перейти в именованный location. Спасибо Toshikuni Fukaya. *) Добавление: теперь директива tcp_nodelay работает для SPDY-соединений. *) Добавление: новые директивы в скриптах подсветки синтаксиса для vim. Спасибо Peter Wu. *) Исправление: nginx игнорировал значение "s-maxage" в строке "Cache-Control" в заголовке ответа бэкенда. Спасибо Piotr Sikora. *) Исправление: в модуле ngx_http_spdy_module. Спасибо Piotr Sikora. *) Исправление: в директиве ssl_password_file при использовании OpenSSL 0.9.8zc, 1.0.0o, 1.0.1j. *) Исправление: при использовании директивы post_action в лог писались сообщения "header already sent"; ошибка появилась в nginx 1.5.4. *) Исправление: при использовании директивы "postpone_output 0" с SSI-подзапросами в лог могли писаться сообщения "the http output chain is empty". *) Исправление: в директиве proxy_cache_lock при использовании SSI-подзапросов. Спасибо Yichun Zhang. Изменения в nginx 1.7.7 28.10.2014 *) Изменение: теперь nginx учитывает при кэшировании строку "Vary" в заголовке ответа бэкенда. *) Добавление: директивы proxy_force_ranges, fastcgi_force_ranges, scgi_force_ranges и uwsgi_force_ranges. *) Добавление: директивы proxy_limit_rate, fastcgi_limit_rate, scgi_limit_rate и uwsgi_limit_rate. *) Добавление: параметр Vary директив proxy_ignore_headers, fastcgi_ignore_headers, scgi_ignore_headers и uwsgi_ignore_headers. *) Исправление: последняя часть ответа, полученного от бэкенда при небуферизированном проксировании, могла не отправляться клиенту, если использовались директивы gzip или gunzip. *) Исправление: в директиве proxy_cache_revalidate. Спасибо Piotr Sikora. *) Исправление: в обработке ошибок. Спасибо Yichun Zhang и Даниилу Бондареву. *) Исправление: в директивах proxy_next_upstream_tries и proxy_next_upstream_timeout. Спасибо Feng Gu. *) Исправление: nginx/Windows не собирался с MinGW-w64 gcc. Спасибо Kouhei Sutou. Изменения в nginx 1.7.6 30.09.2014 *) Изменение: устаревшая директива limit_zone больше не поддерживается. *) Добавление: в директивах limit_conn_zone и limit_req_zone теперь можно использовать комбинации нескольких переменных. *) Исправление: при повторной отправке FastCGI-запроса на бэкенд тело запроса могло передаваться неправильно. *) Исправление: в логгировании в syslog. Изменения в nginx 1.7.5 16.09.2014 *) Безопасность: при использовании общего для нескольких блоков server разделяемого кэша SSL-сессий или общего ключа для шифрования TLS session tickets было возможно повторно использовать SSL-сессию в контексте другого блока server (CVE-2014-3616). Спасибо Antoine Delignat-Lavaud. *) Изменение: директиву stub_status теперь можно указывать без параметров. *) Добавление: параметр always директивы add_header. *) Добавление: директивы proxy_next_upstream_tries, proxy_next_upstream_timeout, fastcgi_next_upstream_tries, fastcgi_next_upstream_timeout, memcached_next_upstream_tries, memcached_next_upstream_timeout, scgi_next_upstream_tries, scgi_next_upstream_timeout, uwsgi_next_upstream_tries и uwsgi_next_upstream_timeout. *) Исправление: в параметре if директивы access_log. *) Исправление: в модуле ngx_http_perl_module. Спасибо Piotr Sikora. *) Исправление: директива listen почтового прокси-сервера не позволяла указать более двух параметров. *) Исправление: директива sub_filter не работала с заменяемой строкой из одного символа. *) Исправление: запросы могли зависать, если использовался resolver и в процессе обращения к DNS-серверу происходил таймаут. *) Исправление: в модуле ngx_http_spdy_module при использовании совместно с AIO. *) Исправление: в рабочем процессе мог произойти segmentation fault, если с помощью директивы set изменялись переменные "$http_...", "$sent_http_..." или "$upstream_http_...". *) Исправление: в обработке ошибок выделения памяти. Спасибо Markus Linnala и Feng Gu. Изменения в nginx 1.7.4 05.08.2014 *) Безопасность: pipelined-команды не отбрасывались после команды STARTTLS в SMTP прокси-сервере (CVE-2014-3556); ошибка появилась в 1.5.6. Спасибо Chris Boulton. *) Изменение: экранирование символов в URI теперь использует шестнадцатеричные цифры в верхнем регистре. Спасибо Piotr Sikora. *) Добавление: теперь nginx можно собрать с BoringSSL и LibreSSL. Спасибо Piotr Sikora. *) Исправление: запросы могли зависать, если использовался resolver и DNS-сервер возвращал некорректный ответ; ошибка появилась в 1.5.8. *) Исправление: в модуле ngx_http_spdy_module. Спасибо Piotr Sikora. *) Исправление: переменная $uri могла содержать мусор при возврате ошибок с кодом 400. Спасибо Сергею Боброву. *) Исправление: в обработке ошибок в директиве proxy_store и в модуле ngx_http_dav_module. Спасибо Feng Gu. *) Исправление: при логгировании ошибок в syslog мог происходить segmentation fault; ошибка появилась в 1.7.1. *) Исправление: переменные $geoip_latitude, $geoip_longitude, $geoip_dma_code и $geoip_area_code могли не работать. Спасибо Yichun Zhang. *) Исправление: в обработке ошибок выделения памяти. Спасибо Tatsuhiko Kubo и Piotr Sikora. Изменения в nginx 1.7.3 08.07.2014 *) Добавление: weak entity tags теперь не удаляются при изменениях ответа, а strong entity tags преобразуются в weak. *) Добавление: ревалидация элементов кэша теперь, если это возможно, использует заголовок If-None-Match. *) Добавление: директива ssl_password_file. *) Исправление: при возврате ответа из кэша заголовок запроса If-None-Match игнорировался, если в ответе не было заголовка Last-Modified. *) Исправление: сообщения "peer closed connection in SSL handshake" при соединении с бэкендами логгировались на уровне info вместо error. *) Исправление: в модуле ngx_http_dav_module в nginx/Windows. *) Исправление: SPDY-соединения могли неожиданно закрываться, если использовалось кэширование. Изменения в nginx 1.7.2 17.06.2014 *) Добавление: директива hash в блоке upstream. *) Добавление: дефрагментация свободных блоков разделяемой памяти. Спасибо Wandenberg Peixoto и Yichun Zhang. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовалось значение access_log по умолчанию; ошибка появилась в 1.7.0. Спасибо Piotr Sikora. *) Исправление: завершающий слэш ошибочно удалялся из последнего параметра директивы try_files. *) Исправление: nginx мог не собираться на OS X. *) Исправление: в модуле ngx_http_spdy_module. Изменения в nginx 1.7.1 27.05.2014 *) Добавление: переменные "$upstream_cookie_...". *) Добавление: переменная $ssl_client_fingerprint. *) Добавление: директивы error_log и access_log теперь поддерживают логгирование в syslog. *) Добавление: почтовый прокси-сервер теперь логгирует порт клиента при соединении. *) Исправление: утечки памяти при использовании директивы "ssl_stapling". Спасибо Filipe da Silva. *) Исправление: директива alias внутри location'а, заданного регулярным выражением, работала неправильно, если использовались директивы if или limit_except. *) Исправление: директива charset не ставила кодировку для сжатых ответов бэкендов. *) Исправление: директива proxy_pass без URI могла использовать оригинальный запрос после установки переменной $args. Спасибо Yichun Zhang. *) Исправление: в работе параметра none директивы smtp_auth; ошибка появилась в 1.5.6. Спасибо Святославу Никольскому. *) Исправление: при совместном использовании sub_filter и SSI ответы могли передаваться неверно. *) Исправление: nginx не собирался с параметром --with-file-aio на Linux/aarch64. Изменения в nginx 1.7.0 24.04.2014 *) Добавление: проверка SSL-сертификатов бэкендов. *) Добавление: поддержка SNI при работе с бэкендами по SSL. *) Добавление: переменная $ssl_server_name. *) Добавление: параметр if директивы access_log. Изменения в nginx 1.5.13 08.04.2014 *) Изменение: улучшена обработка хэш-таблиц; в директивах variables_hash_max_size и types_hash_bucket_size значения по умолчанию изменены на 1024 и 64 соответственно. *) Добавление: модуль ngx_http_mp4_module теперь понимает аргумент end. *) Добавление: поддержка byte ranges модулем ngx_http_mp4_module и при сохранении ответов в кэш. *) Исправление: теперь nginx не пишет в лог сообщения "ngx_slab_alloc() failed: no memory" при использовании разделяемой памяти в ssl_session_cache и в модуле ngx_http_limit_req_module. *) Исправление: директива underscores_in_headers не разрешала подчёркивание в первом символе заголовка. Спасибо Piotr Sikora. *) Исправление: cache manager мог нагружать процессор при выходе в nginx/Windows. *) Исправление: при использовании ssl_session_cache с параметром shared рабочий процесс nginx/Windows завершался аварийно. *) Исправление: в модуле ngx_http_spdy_module. Изменения в nginx 1.5.12 18.03.2014 *) Безопасность: при обработке специально созданного запроса модулем ngx_http_spdy_module могло происходить переполнение буфера в рабочем процессе, что потенциально могло приводить к выполнению произвольного кода (CVE-2014-0133). Спасибо Lucas Molas из Programa STIC, Fundación Dr. Manuel Sadosky, Buenos Aires, Argentina. *) Добавление: параметр proxy_protocol в директивах listen и real_ip_header, переменная $proxy_protocol_addr. *) Исправление: в директиве fastcgi_next_upstream. Спасибо Lucas Molas. Изменения в nginx 1.5.11 04.03.2014 *) Безопасность: при обработке специально созданного запроса модулем ngx_http_spdy_module на 32-битных платформах могла повреждаться память рабочего процесса, что потенциально могло приводить к выполнению произвольного кода (CVE-2014-0088); ошибка появилась в 1.5.10. Спасибо Lucas Molas из Programa STIC, Fundación Dr. Manuel Sadosky, Buenos Aires, Argentina. *) Добавление: переменная $ssl_session_reused. *) Исправление: директива client_max_body_size могла не работать при чтении тела запроса с использованием chunked transfer encoding; ошибка появилась в 1.3.9. Спасибо Lucas Molas. *) Исправление: при проксировании WebSocket-соединений в рабочем процессе мог произойти segmentation fault. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовался модуль ngx_http_spdy_module на 32-битных платформах; ошибка появилась в 1.5.10. *) Исправление: значение переменной $upstream_status могло быть неверным, если использовались директивы proxy_cache_use_stale или proxy_cache_revalidate. Спасибо Piotr Sikora. *) Исправление: в рабочем процессе мог произойти segmentation fault, если ошибки с кодом 400 с помощью директивы error_page перенаправлялись в именованный location. *) Исправление: nginx/Windows не собирался с Visual Studio 2013. Изменения в nginx 1.5.10 04.02.2014 *) Добавление: модуль ngx_http_spdy_module теперь использует протокол SPDY 3.1. Спасибо Automattic и MaxCDN за спонсирование разработки. *) Добавление: модуль ngx_http_mp4_module теперь пропускает дорожки, имеющие меньшую длину, чем запрошенная перемотка. *) Исправление: в рабочем процессе мог произойти segmentation fault, если переменная $ssl_session_id использовалась при логгировании; ошибка появилась в 1.5.9. *) Исправление: переменные $date_local и $date_gmt использовали неверный формат вне модуля ngx_http_ssi_filter_module. *) Исправление: клиентские соединения могли сразу закрываться, если использовался отложенный accept; ошибка появилась в 1.3.15. *) Исправление: сообщения "getsockopt(TCP_FASTOPEN) ... failed" записывались в лог в процессе обновления исполняемого файла на Linux; ошибка появилась в 1.5.8. Спасибо Piotr Sikora. Изменения в nginx 1.5.9 22.01.2014 *) Изменение: теперь в заголовке X-Accel-Redirect nginx ожидает закодированный URI. *) Добавление: директива ssl_buffer_size. *) Добавление: директиву limit_rate теперь можно использовать для ограничения скорости передачи ответов клиенту в SPDY-соединениях. *) Добавление: директива spdy_chunk_size. *) Добавление: директива ssl_session_tickets. Спасибо Dirkjan Bussink. *) Исправление: переменная $ssl_session_id содержала всю сессию в сериализованном виде вместо её идентификатора. Спасибо Ivan Ristić. *) Исправление: nginx неправильно обрабатывал закодированный символ "?" в команде SSI include. *) Исправление: модуль ngx_http_dav_module не раскодировал целевой URI при обработке методов COPY и MOVE. *) Исправление: resolver не понимал доменные имена с точкой в конце. Спасибо Yichun Zhang. *) Исправление: при проксировании в логах могли появляться сообщения "zero size buf in output"; ошибка появилась в 1.3.9. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовался модуль ngx_http_spdy_module. *) Исправление: при использовании методов обработки соединений select, poll и /dev/poll проксируемые WebSocket-соединения могли зависать сразу после открытия. *) Исправление: директива xclient почтового прокси-сервера некорректно передавала IPv6-адреса. Изменения в nginx 1.5.8 17.12.2013 *) Добавление: теперь resolver поддерживает IPv6. *) Добавление: директива listen поддерживает параметр fastopen. Спасибо Mathew Rodley. *) Добавление: поддержка SSL в модуле ngx_http_uwsgi_module. Спасибо Roberto De Ioris. *) Добавление: скрипты подсветки синтаксиса для vim добавлены в contrib. Спасибо Evan Miller. *) Исправление: при чтении тела запроса с использованием chunked transfer encoding по SSL-соединению мог произойти таймаут. *) Исправление: директива master_process работала неправильно в nginx/Windows. *) Исправление: параметр setfib директивы listen мог не работать. *) Исправление: в модуле ngx_http_spdy_module. Изменения в nginx 1.5.7 19.11.2013 *) Безопасность: символ, следующий за незакодированным пробелом в строке запроса, обрабатывался неправильно (CVE-2013-4547); ошибка появилась в 0.8.41. Спасибо Ivan Fratric из Google Security Team. *) Изменение: уровень логгирования ошибок auth_basic об отсутствии пароля понижен с уровня error до info. *) Добавление: директивы proxy_cache_revalidate, fastcgi_cache_revalidate, scgi_cache_revalidate и uwsgi_cache_revalidate. *) Добавление: директива ssl_session_ticket_key. Спасибо Piotr Sikora. *) Исправление: директива "add_header Cache-Control ''" добавляла строку заголовка ответа "Cache-Control" с пустым значением. *) Исправление: директива "satisfy any" могла вернуть ошибку 403 вместо 401 при использовании директив auth_request и auth_basic. Спасибо Jan Marc Hoffmann. *) Исправление: параметры accept_filter и deferred директивы listen игнорировались для listen-сокетов, создаваемых в процессе обновления исполняемого файла. Спасибо Piotr Sikora. *) Исправление: часть данных, полученных от бэкенда при небуферизированном проксировании, могла не отправляться клиенту сразу, если использовались директивы gzip или gunzip. Спасибо Yichun Zhang. *) Исправление: в обработке ошибок в модуле ngx_http_gunzip_filter_module. *) Исправление: ответы могли зависать, если использовался модуль ngx_http_spdy_module и директива auth_request. *) Исправление: утечки памяти в nginx/Windows. Изменения в nginx 1.5.6 01.10.2013 *) Добавление: директива fastcgi_buffering. *) Добавление: директивы proxy_ssl_protocols и proxy_ssl_ciphers. Спасибо Piotr Sikora. *) Добавление: оптимизация SSL handshake при использовании длинных цепочек сертификатов. *) Добавление: почтовый прокси-сервер поддерживает SMTP pipelining. *) Исправление: в модуле ngx_http_auth_basic_module при использовании метода шифрования паролей "$apr1$". Спасибо Markus Linnala. *) Исправление: на MacOSX, Cygwin и nginx/Windows для обработки запроса мог использоваться неверный location, если для задания location'ов использовались символы разных регистров. *) Исправление: автоматическое перенаправление с добавлением завершающего слэша для проксированных location'ов могло не работать. *) Исправление: в почтовом прокси-сервере. *) Исправление: в модуле ngx_http_spdy_module. Изменения в nginx 1.5.5 17.09.2013 *) Изменение: теперь nginx по умолчанию использует HTTP/1.0, если точно определить протокол не удалось. *) Добавление: директива disable_symlinks теперь использует O_PATH на Linux. *) Добавление: для определения того, что клиент закрыл соединение, при использовании метода epoll теперь используются события EPOLLRDHUP. *) Исправление: в директиве valid_referers при использовании параметра server_names. *) Исправление: переменная $request_time не работала в nginx/Windows. *) Исправление: в директиве image_filter. Спасибо Lanshun Zhou. *) Исправление: совместимость с OpenSSL 1.0.1f. Спасибо Piotr Sikora. Изменения в nginx 1.5.4 27.08.2013 *) Изменение: MIME-тип для расширения js изменён на "application/javascript"; значение по умолчанию директивы charset_types изменено соответственно. *) Изменение: теперь директива image_filter с параметром size возвращает ответ с MIME-типом "application/json". *) Добавление: модуль ngx_http_auth_request_module. *) Исправление: на старте или во время переконфигурации мог произойти segmentation fault, если использовалась директива try_files с пустым параметром. *) Исправление: утечки памяти при использовании в директивах root и auth_basic_user_file относительных путей, заданных с помощью переменных. *) Исправление: директива valid_referers неправильно выполняла регулярные выражения, если заголовок Referer начинался с "https://". Спасибо Liangbin Li. *) Исправление: ответы могли зависать, если использовались подзапросы и при обработке подзапроса происходила ошибка во время SSL handshake с бэкендом. Спасибо Aviram Cohen. *) Исправление: в модуле ngx_http_autoindex_module. *) Исправление: в модуле ngx_http_spdy_module. Изменения в nginx 1.5.3 30.07.2013 *) Изменение во внутреннем API: теперь при небуферизированной работе с бэкендами u->length по умолчанию устанавливается в -1. *) Изменение: теперь при получении неполного ответа от бэкенда nginx отправляет полученную часть ответа, после чего закрывает соединение с клиентом. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовался модуль ngx_http_spdy_module и директива client_body_in_file_only. *) Исправление: параметр so_keepalive директивы listen мог работать некорректно на DragonFlyBSD. Спасибо Sepherosa Ziehau. *) Исправление: в модуле ngx_http_xslt_filter_module. *) Исправление: в модуле ngx_http_sub_filter_module. Изменения в nginx 1.5.2 02.07.2013 *) Добавление: теперь можно использовать несколько директив error_log. *) Исправление: метод $r->header_in() встроенного перла не возвращал значения строк "Cookie" и "X-Forwarded-For" из заголовка запроса; ошибка появилась в 1.3.14. *) Исправление: в модуле ngx_http_spdy_module. Спасибо Jim Radford. *) Исправление: nginx не собирался на Linux при использовании x32 ABI. Спасибо Сергею Иванцову. Изменения в nginx 1.5.1 04.06.2013 *) Добавление: директивы ssi_last_modified, sub_filter_last_modified и xslt_last_modified. Спасибо Алексею Колпакову. *) Добавление: параметр http_403 в директивах proxy_next_upstream, fastcgi_next_upstream, scgi_next_upstream и uwsgi_next_upstream. *) Добавление: директивы allow и deny теперь поддерживают unix domain сокеты. *) Исправление: nginx не собирался с модулем ngx_mail_ssl_module, но без модуля ngx_http_ssl_module; ошибка появилась в 1.3.14. *) Исправление: в директиве proxy_set_body. Спасибо Lanshun Zhou. *) Исправление: в директиве lingering_time. Спасибо Lanshun Zhou. *) Исправление: параметр fail_timeout директивы server в блоке upstream мог не работать, если использовался параметр max_fails; ошибка появилась в 1.3.0. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовалась директива ssl_stapling. Спасибо Piotr Sikora. *) Исправление: в почтовом прокси-сервере. Спасибо Filipe Da Silva. *) Исправление: nginx/Windows мог перестать принимать соединения, если использовалось несколько рабочих процессов. Изменения в nginx 1.5.0 07.05.2013 *) Безопасность: при обработке специально созданного запроса мог перезаписываться стек рабочего процесса, что могло приводить к выполнению произвольного кода (CVE-2013-2028); ошибка появилась в 1.3.9. Спасибо Greg MacManus, iSIGHT Partners Labs. Изменения в nginx 1.4.0 24.04.2013 *) Исправление: nginx не собирался с модулем ngx_http_perl_module, если использовался параметр --with-openssl; ошибка появилась в 1.3.16. *) Исправление: в работе с телом запроса из модуля ngx_http_perl_module; ошибка появилась в 1.3.9. Изменения в nginx 1.3.16 16.04.2013 *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовались подзапросы; ошибка появилась в 1.3.9. *) Исправление: директива tcp_nodelay вызывала ошибку при проксировании WebSocket-соединений в unix domain сокет. *) Исправление: переменная $upstream_response_length возвращала значение "0", если не использовалась буферизация. Спасибо Piotr Sikora. *) Исправление: в методах обработки соединений eventport и /dev/poll. Изменения в nginx 1.3.15 26.03.2013 *) Изменение: открытие и закрытие соединения без отправки в нём каких-либо данных больше не записывается в access_log с кодом ошибки 400. *) Добавление: модуль ngx_http_spdy_module. Спасибо Automattic за спонсирование разработки. *) Добавление: директивы limit_req_status и limit_conn_status. Спасибо Nick Marden. *) Добавление: директива image_filter_interlace. Спасибо Ивану Боброву. *) Добавление: переменная $connections_waiting в модуле ngx_http_stub_status_module. *) Добавление: теперь почтовый прокси-сервер поддерживает IPv6-бэкенды. *) Исправление: при повторной отправке запроса на бэкенд тело запроса могло передаваться неправильно; ошибка появилась в 1.3.9. Спасибо Piotr Sikora. *) Исправление: в директиве client_body_in_file_only; ошибка появилась в 1.3.9. *) Исправление: ответы могли зависать, если использовались подзапросы и при обработке подзапроса происходила DNS-ошибка. Спасибо Lanshun Zhou. *) Исправление: в процедуре учёта использования бэкендов. Изменения в nginx 1.3.14 05.03.2013 *) Добавление: переменные $connections_active, $connections_reading и $connections_writing в модуле ngx_http_stub_status_module. *) Добавление: поддержка WebSocket-соединений в модулях ngx_http_uwsgi_module и ngx_http_scgi_module. *) Исправление: в обработке виртуальных серверов при использовании SNI. *) Исправление: при использовании директивы "ssl_session_cache shared" новые сессии могли не сохраняться, если заканчивалось место в разделяемой памяти. Спасибо Piotr Sikora. *) Исправление: несколько заголовков X-Forwarded-For обрабатывались неправильно. Спасибо Neal Poole за спонсирование разработки. *) Исправление: в модуле ngx_http_mp4_module. Спасибо Gernot Vormayr. Изменения в nginx 1.3.13 19.02.2013 *) Изменение: теперь для сборки по умолчанию используется компилятор с именем "cc". *) Добавление: поддержка проксирования WebSocket-соединений. Спасибо Apcera и CloudBees за спонсирование разработки. *) Добавление: директива auth_basic_user_file поддерживает шифрование паролей методом "{SHA}". Спасибо Louis Opter. Изменения в nginx 1.3.12 05.02.2013 *) Добавление: директивы proxy_bind, fastcgi_bind, memcached_bind, scgi_bind и uwsgi_bind поддерживают переменные. *) Добавление: переменные $pipe, $request_length, $time_iso8601 и $time_local теперь можно использовать не только в директиве log_format. Спасибо Kiril Kalchev. *) Добавление: поддержка IPv6 в модуле ngx_http_geoip_module. Спасибо Gregor Kališnik. *) Исправление: директива proxy_method работала неверно, если была указана на уровне http. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовался resolver и метод poll. *) Исправление: nginx мог нагружать процессор во время SSL handshake с бэкендом при использовании методов обработки соединений select, poll и /dev/poll. *) Исправление: ошибка "[crit] SSL_write() failed (SSL:)". *) Исправление: в директиве client_body_in_file_only; ошибка появилась в 1.3.9. *) Исправление: в директиве fastcgi_keep_conn. Изменения в nginx 1.3.11 10.01.2013 *) Исправление: при записи в лог мог происходить segmentation fault; ошибка появилась в 1.3.10. *) Исправление: директива proxy_pass не работала с IP-адресами без явного указания порта; ошибка появилась в 1.3.10. *) Исправление: на старте или во время переконфигурации происходил segmentation fault, если директива keepalive была указана несколько раз в одном блоке upstream. *) Исправление: параметр default директивы geo не определял значение по умолчанию для IPv6-адресов. Изменения в nginx 1.3.10 25.12.2012 *) Изменение: для указанных в конфигурационном файле доменных имён теперь используются не только IPv4, но и IPv6 адреса. *) Изменение: теперь при использовании директивы include с маской на Unix-системах включаемые файлы сортируются в алфавитном порядке. *) Изменение: директива add_header добавляет строки в ответы с кодом 201. *) Добавление: директива geo теперь поддерживает IPv6 адреса в формате CIDR. *) Добавление: параметры flush и gzip в директиве access_log. *) Добавление: директива auth_basic поддерживает переменные. *) Исправление: nginx в некоторых случаях не собирался с модулем ngx_http_perl_module. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовался модуль ngx_http_xslt_module. *) Исправление: nginx мог не собираться на MacOSX. Спасибо Piotr Sikora. *) Исправление: при использовании директивы limit_rate с большими значениями скорости на 32-битных системах ответ мог возвращаться не целиком. Спасибо Алексею Антропову. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовалась директива if. Спасибо Piotr Sikora. *) Исправление: ответ "100 Continue" выдавался вместе с ответом "413 Request Entity Too Large". *) Исправление: директивы image_filter, image_filter_jpeg_quality и image_filter_sharpen могли наследоваться некорректно. Спасибо Ивану Боброву. *) Исправление: при использовании директивы auth_basic под Linux могли возникать ошибки "crypt_r() failed". *) Исправление: в обработке backup-серверов. Спасибо Thomas Chen. *) Исправление: при проксировании HEAD-запросов мог возвращаться некорректный ответ, если использовалась директива gzip. Изменения в nginx 1.3.9 27.11.2012 *) Добавление: поддержка chunked transfer encoding при получении тела запроса. *) Добавление: переменные $request_time и $msec теперь можно использовать не только в директиве log_format. *) Исправление: cache manager и cache loader могли не запускаться, если использовалось более 512 listen-сокетов. *) Исправление: в модуле ngx_http_dav_module. Изменения в nginx 1.3.8 30.10.2012 *) Добавление: параметр optional_no_ca директивы ssl_verify_client. Спасибо Михаилу Казанцеву и Eric O'Connor. *) Добавление: переменные $bytes_sent, $connection и $connection_requests теперь можно использовать не только в директиве log_format. Спасибо Benjamin Grössing. *) Добавление: параметр auto директивы worker_processes. *) Исправление: сообщения "cache file ... has md5 collision". *) Исправление: в модуле ngx_http_gunzip_filter_module. *) Исправление: в директиве ssl_stapling. Изменения в nginx 1.3.7 02.10.2012 *) Добавление: поддержка OCSP stapling. Спасибо Comodo, DigiCert и GlobalSign за спонсирование разработки. *) Добавление: директива ssl_trusted_certificate. *) Добавление: теперь resolver случайным образом меняет порядок возвращаемых закэшированных адресов. Спасибо Антону Жулину. *) Исправление: совместимость с OpenSSL 0.9.7. Изменения в nginx 1.3.6 12.09.2012 *) Добавление: модуль ngx_http_gunzip_filter_module. *) Добавление: директива memcached_gzip_flag. *) Добавление: параметр always директивы gzip_static. *) Исправление: в директиве "limit_req"; ошибка появилась в 1.1.14. Спасибо Charles Chen. *) Исправление: nginx не собирался gcc 4.7 с оптимизацией -O2 если использовался параметр --with-ipv6. Изменения в nginx 1.3.5 21.08.2012 *) Изменение: модуль ngx_http_mp4_module больше не отфильтровывает дорожки в форматах, отличных от H.264 и AAC. *) Исправление: в рабочем процессе мог произойти segmentation fault, если в директиве map в качестве значений использовались переменные. *) Исправление: в рабочем процессе мог произойти segmentation fault при использовании директивы geo с параметром ranges, но без параметра default; ошибка появилась в 0.8.43. Спасибо Zhen Chen и Weibin Yao. *) Исправление: в обработке параметра командной строки -p. *) Исправление: в почтовом прокси-сервере. *) Исправление: незначительных потенциальных ошибок. Спасибо Coverity. *) Исправление: nginx/Windows не собирался с Visual Studio 2005 Express. Спасибо HAYASHI Kentaro. Изменения в nginx 1.3.4 31.07.2012 *) Изменение: теперь на слушающих IPv6-сокетах параметр ipv6only включён по умолчанию. *) Добавление: поддержка компилятора Clang. *) Исправление: могли создаваться лишние слушающие сокеты. Спасибо Роману Одайскому. *) Исправление: nginx/Windows мог нагружать процессор, если при запуске рабочего процесса происходила ошибка. Спасибо Ricardo Villalobos Guevara. *) Исправление: директивы proxy_pass_header, fastcgi_pass_header, scgi_pass_header, uwsgi_pass_header, proxy_hide_header, fastcgi_hide_header, scgi_hide_header и uwsgi_hide_header могли наследоваться некорректно. Изменения в nginx 1.3.3 10.07.2012 *) Добавление: поддержка entity tags и директива etag. *) Исправление: при использовании директивы map с параметром hostnames не игнорировалась конечная точка в исходном значении. *) Исправление: для обработки запроса мог использоваться неверный location, если переход в именованный location происходил после изменения URI с помощью директивы rewrite. Изменения в nginx 1.3.2 26.06.2012 *) Изменение: параметр single директивы keepalive теперь игнорируется. *) Изменение: сжатие SSL теперь отключено в том числе при использовании OpenSSL старее 1.0.0. *) Добавление: директиву "ip_hash" теперь можно использовать для балансировки IPv6 клиентов. *) Добавление: переменную $status теперь можно использовать не только в директиве log_format. *) Исправление: при завершении рабочего процесса мог произойти segmentation fault, если использовалась директива resolver. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовался модуль ngx_http_mp4_module. *) Исправление: в модуле ngx_http_mp4_module. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовались конфликтующие имена серверов с масками. *) Исправление: на платформе ARM nginx мог аварийно завершаться по сигналу SIGBUS. *) Исправление: во время переконфигурации на HP-UX в лог записывался alert "sendmsg() failed (9: Bad file number)". Изменения в nginx 1.3.1 05.06.2012 *) Безопасность: теперь nginx/Windows игнорирует точку в конце компонента URI и не разрешает URI, содержащие последовательность ":$". Спасибо Владимиру Кочеткову, Positive Research Center. *) Добавление: директивы proxy_pass, fastcgi_pass, scgi_pass, uwsgi_pass и директива server в блоке upstream теперь поддерживают IPv6-адреса. *) Добавление: в директиве resolver теперь можно указывать порт и задавать IPv6-адреса DNS-серверов. *) Добавление: директива least_conn в блоке upstream. *) Добавление: при использовании директивы ip_hash теперь можно задавать веса серверов. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовалась директива image_filter; ошибка появилась в 1.3.0. *) Исправление: nginx не собирался с модулем ngx_cpp_test_module; ошибка появилась в 1.1.12. *) Исправление: доступ к переменным из SSI и встроенного перла мог не работать после переконфигурации. Спасибо Yichun Zhang. *) Исправление: в модуле ngx_http_xslt_filter_module. Спасибо Kuramoto Eiji. *) Исправление: утечки памяти при использовании переменной $geoip_org. Спасибо Денису Латыпову. *) Исправление: в директивах proxy_cookie_domain и proxy_cookie_path. Изменения в nginx 1.3.0 15.05.2012 *) Добавление: директива debug_connection теперь поддерживает IPv6-адреса и параметр "unix:". *) Добавление: директива set_real_ip_from и параметр proxy директивы geo теперь поддерживают IPv6-адреса. *) Добавление: директивы real_ip_recursive, geoip_proxy и geoip_proxy_recursive. *) Добавление: параметр proxy_recursive директивы geo. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовалась директива resolver. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовались директивы fastcgi_pass, scgi_pass или uwsgi_pass и бэкенд возвращал некорректный ответ. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовалась директива rewrite и в новых аргументах запроса в строке замены использовались переменные. *) Исправление: nginx мог нагружать процессор, если было достигнуто ограничение на количество открытых файлов. *) Исправление: при использовании директивы proxy_next_upstream с параметром http_404 nginx мог бесконечно перебирать бэкенды, если в блоке upstream был хотя бы один сервер с флагом backup. *) Исправление: при использовании директивы ip_hash установка параметра down директивы server могла приводить к ненужному перераспределению клиентов между бэкендами. *) Исправление: утечки сокетов. Спасибо Yichun Zhang. *) Исправление: в модуле ngx_http_fastcgi_module. Изменения в nginx 1.2.0 23.04.2012 *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовалась директива try_files; ошибка появилась в 1.1.19. *) Исправление: ответ мог быть передан не полностью, если использовалось больше IOV_MAX буферов. *) Исправление: в работе параметра crop директивы image_filter. Спасибо Maxim Bublis. Изменения в nginx 1.1.19 12.04.2012 *) Безопасность: при обработке специально созданного mp4 файла модулем ngx_http_mp4_module могли перезаписываться области памяти рабочего процесса, что могло приводить к выполнению произвольного кода (CVE-2012-2089). Спасибо Matthew Daley. *) Исправление: nginx/Windows мог завершаться аварийно. Спасибо Vincent Lee. *) Исправление: nginx нагружал процессор, если все серверы в upstream'е были помечены флагом backup. *) Исправление: директивы allow и deny могли наследоваться некорректно, если в них использовались IPv6 адреса. *) Исправление: директивы modern_browser и ancient_browser могли наследоваться некорректно. *) Исправление: таймауты могли работать некорректно на Solaris/SPARC. *) Исправление: в модуле ngx_http_mp4_module. Изменения в nginx 1.1.18 28.03.2012 *) Изменение: теперь keepalive соединения не запрещены для Safari по умолчанию. *) Добавление: переменная $connection_requests. *) Добавление: переменные $tcpinfo_rtt, $tcpinfo_rttvar, $tcpinfo_snd_cwnd и $tcpinfo_rcv_space. *) Добавление: директива worker_cpu_affinity теперь работает на FreeBSD. *) Добавление: директивы xslt_param и xslt_string_param. Спасибо Samuel Behan. *) Исправление: в configure. Спасибо Piotr Sikora. *) Исправление: в модуле ngx_http_xslt_filter_module. *) Исправление: nginx не собирался на Debian GNU/Hurd. Изменения в nginx 1.1.17 15.03.2012 *) Безопасность: содержимое ранее освобождённой памяти могло быть отправлено клиенту, если бэкенд возвращал специально созданный ответ. Спасибо Matthew Daley. *) Исправление: при использовании встроенного перла из SSI. Спасибо Matthew Daley. *) Исправление: в модуле ngx_http_uwsgi_module. Изменения в nginx 1.1.16 29.02.2012 *) Изменение: ограничение на количество одновременных подзапросов поднято до 200. *) Добавление: параметр from в директиве disable_symlinks. *) Добавление: директивы return и error_page теперь могут использоваться для возврата перенаправлений с кодом 307. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовалась директива resolver и на глобальном уровне не была задана директива error_log. Спасибо Роману Арутюняну. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовались директивы "proxy_http_version 1.1" или "fastcgi_keep_conn on". *) Исправление: утечек памяти. Спасибо Lanshun Zhou. *) Исправление: в директиве disable_symlinks. *) Исправление: при использовании ZFS размер кэша на диске мог считаться некорректно; ошибка появилась в 1.0.1. *) Исправление: nginx не собирался компилятором icc 12.1. *) Исправление: nginx не собирался gcc на Solaris; ошибка появилась в 1.1.15. Изменения в nginx 1.1.15 15.02.2012 *) Добавление: директива disable_symlinks. *) Добавление: директивы proxy_cookie_domain и proxy_cookie_path. *) Исправление: nginx мог некорректно сообщать об ошибке "upstream prematurely closed connection" вместо "upstream sent too big header". Спасибо Feibo Li. *) Исправление: nginx не собирался с модулем ngx_http_perl_module, если использовался параметр --with-openssl. *) Исправление: количество внутренних перенаправлений в именованные location'ы не ограничивалось. *) Исправление: вызов $r->flush() несколько раз подряд мог приводить к ошибкам в модуле ngx_http_gzip_filter_module. *) Исправление: при использовании директивы proxy_store с SSI-подзапросами временные файлы могли не удаляться. *) Исправление: в некоторых случаях некэшируемые переменные (такие, как $args) возвращали старое пустое закэшированное значение. *) Исправление: в рабочем процессе мог произойти segmentation fault, если одновременно создавалось слишком много SSI-подзапросов; ошибка появилась в 0.7.25. Изменения в nginx 1.1.14 30.01.2012 *) Добавление: теперь можно указать несколько ограничений limit_req одновременно. *) Исправление: в обработке ошибок при соединении с бэкендом. Спасибо Piotr Sikora. *) Исправление: в обработке ошибок при использовании AIO на FreeBSD. *) Исправление: в инициализации библиотеки OpenSSL. *) Исправление: директивы proxy_redirect могли наследоваться некорректно. *) Исправление: утечки памяти при переконфигурации, если использовалась директива pcre_jit. Изменения в nginx 1.1.13 16.01.2012 *) Добавление: параметры TLSv1.1 и TLSv1.2 в директиве ssl_protocols. *) Исправление: параметры директивы limit_req наследовались некорректно; ошибка появилась в 1.1.12. *) Исправление: директива proxy_redirect некорректно обрабатывала заголовок Refresh при использовании регулярных выражений. *) Исправление: директива proxy_cache_use_stale с параметром error не возвращала ответ из кэша, если все бэкенды были признаны неработающими. *) Исправление: директива worker_cpu_affinity могла не работать. *) Исправление: nginx не собирался на Solaris; ошибка появилась в 1.1.12. *) Исправление: в модуле ngx_http_mp4_module. Изменения в nginx 1.1.12 26.12.2011 *) Изменение: после перенаправления запроса с помощью директивы error_page директива proxy_pass без URI теперь использует изменённый URI. Спасибо Lanshun Zhou. *) Добавление: директивы proxy/fastcgi/scgi/uwsgi_cache_lock, proxy/fastcgi/scgi/uwsgi_cache_lock_timeout. *) Добавление: директива pcre_jit. *) Добавление: SSI команда if поддерживает выделения в регулярных выражениях. *) Исправление: SSI команда if не работала внутри команды block. *) Исправление: директивы limit_conn_log_level и limit_req_log_level могли не работать. *) Исправление: директива limit_rate не позволяла передавать на полной скорости, даже если был указан очень большой лимит. *) Исправление: директива sendfile_max_chunk не работала, если использовалась директива limit_rate. *) Исправление: если в директиве proxy_pass использовались переменные и не был указан URI, всегда использовался URI исходного запроса. *) Исправление: после перенаправления запроса с помощью директивы try_files директива proxy_pass без URI могла использовать URI исходного запроса. Спасибо Lanshun Zhou. *) Исправление: в модуле ngx_http_scgi_module. *) Исправление: в модуле ngx_http_mp4_module. *) Исправление: nginx не собирался на Solaris; ошибка появилась в 1.1.9. Изменения в nginx 1.1.11 12.12.2011 *) Добавление: параметр so_keepalive в директиве listen. Спасибо Всеволоду Стахову. *) Добавление: параметр if_not_empty в директивах fastcgi/scgi/uwsgi_param. *) Добавление: переменная $https. *) Добавление: директива proxy_redirect поддерживает переменные в первом параметре. *) Добавление: директива proxy_redirect поддерживает регулярные выражения. *) Исправление: переменная $sent_http_cache_control могла содержать неверное значение при использовании директивы expires. Спасибо Yichun Zhang. *) Исправление: директива read_ahead могла не работать при использовании совместно с try_files и open_file_cache. *) Исправление: если в параметре inactive директивы proxy_cache_path было указано малое время, в рабочем процессе мог произойти segmentation fault. *) Исправление: ответы из кэша могли зависать. Изменения в nginx 1.1.10 30.11.2011 *) Исправление: при использовании AIO на Linux в рабочем процессе происходил segmentation fault; ошибка появилась в 1.1.9. Изменения в nginx 1.1.9 28.11.2011 *) Изменение: теперь двойные кавычки экранируется при выводе SSI-командой echo. Спасибо Зауру Абасмирзоеву. *) Добавление: параметр valid в директиве resolver. По умолчанию теперь используется TTL, возвращённый DNS-сервером. Спасибо Кириллу Коринскому. *) Исправление: nginx мог перестать отвечать, если рабочий процесс завершался аварийно. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовалось SNI; ошибка появилась в 1.1.2. *) Исправление: в директиве keepalive_disable; ошибка появилась в 1.1.8. Спасибо Александру Усову. *) Исправление: сигнал SIGWINCH переставал работать после первого обновления исполняемого файла; ошибка появилась в 1.1.1. *) Исправление: теперь ответы бэкендов, длина которых не соответствует заголовку Content-Length, не кэширутся. *) Исправление: в директиве scgi_param при использовании составных параметров. *) Исправление: в методе epoll. Спасибо Yichun Zhang. *) Исправление: в модуле ngx_http_flv_module. Спасибо Piotr Sikora. *) Исправление: в модуле ngx_http_mp4_module. *) Исправление: теперь nginx понимает IPv6-адреса в строке запроса и в заголовке Host. *) Исправление: директивы add_header и expires не работали для ответов с кодом 206, если запрос проксировался. *) Исправление: nginx не собирался на FreeBSD 10. *) Исправление: nginx не собирался на AIX. Изменения в nginx 1.1.8 14.11.2011 *) Изменение: модуль ngx_http_limit_zone_module переименован в ngx_http_limit_conn_module. *) Изменение: директива limit_zone заменена директивой limit_conn_zone с новым синтаксисом. *) Добавление: поддержка ограничения по нескольким limit_conn на одном уровне. *) Добавление: директива image_filter_sharpen. *) Исправление: в рабочем процессе мог произойти segmentation fault, если resolver получил большой DNS-ответ. Спасибо Ben Hawkes. *) Исправление: в вычислении ключа для кэширования, если использовалась внутренняя реализация MD5; ошибка появилась в 1.0.4. *) Исправление: строки "If-Modified-Since", "If-Range" и им подобные в заголовке запроса клиента могли передаваться бэкенду при кэшировании; или не передаваться при выключенном кэшировании, если кэширование было включено в другой части конфигурации. *) Исправление: модуль ngx_http_mp4_module выдавал неверную строку "Content-Length" в заголовке ответа, использовался аргумент start. Спасибо Piotr Sikora. Изменения в nginx 1.1.7 31.10.2011 *) Добавление: поддержка нескольких DNS серверов в директиве "resolver". Спасибо Кириллу Коринскому. *) Исправление: на старте или во время переконфигурации происходил segmentation fault, если директива ssl использовалась на уровне http и не был указан ssl_certificate. *) Исправление: уменьшено потребление памяти при проксировании больших файлов, если они буферизировались на диск. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовалась директива "proxy_http_version 1.1". *) Исправление: в директиве "expires @time". Изменения в nginx 1.1.6 17.10.2011 *) Изменение во внутреннем API: теперь при внутреннем редиректе в именованный location контексты модулей очищаются. По запросу Yichun Zhang. *) Изменение: теперь если сервер, описанный в блоке upstream, был признан неработающим, то после истечения fail_timeout на него будет отправлен только один запрос; сервер будет считаться работающим, если успешно ответит на этот запрос. *) Изменение: теперь символы 0x7F-0xFF в access_log записываются в виде \xXX. *) Добавление: директивы "proxy/fastcgi/scgi/uwsgi_ignore_headers" теперь поддерживают значения X-Accel-Limit-Rate, X-Accel-Buffering и X-Accel-Charset. *) Добавление: уменьшение потребления памяти при использовании SSL. *) Исправление: некоторые UTF-8 символы обрабатывались неправильно. Спасибо Алексею Куцу. *) Исправление: директивы модуля ngx_http_rewrite_module, заданные на уровне server, применялись повторно, если для запроса не находилось ни одного location'а. *) Исправление: при использовании "aio sendfile" могла происходить утечка сокетов. *) Исправление: при использовании файлового AIO соединения с быстрыми клиентами могли быть закрыты по истечению send_timeout. *) Исправление: в модуле ngx_http_autoindex_module. *) Исправление: модуль ngx_http_mp4_module не поддерживал перемотку на 32-битных платформах. Изменения в nginx 1.1.5 05.10.2011 *) Добавление: директивы uwsgi_buffering и scgi_buffering. Спасибо Peter Smit. *) Исправление: при использовании proxy_cache_bypass могли быть закэшированы некэшируемые ответы. Спасибо John Ferlito. *) Исправление: в модуле ngx_http_proxy_module при работе с бэкендами по HTTP/1.1. *) Исправление: закэшированные ответы с пустым телом возвращались некорректно; ошибка появилась в 0.8.31. *) Исправление: ответы с кодом 201 модуля ngx_http_dav_module были некорректны; ошибка появилась в 0.8.32. *) Исправление: в директиве return. *) Исправление: при использовании директивы "ssl_session_cache builtin" происходил segmentation fault; ошибка появилась в 1.1.1. Изменения в nginx 1.1.4 20.09.2011 *) Добавление: модуль ngx_http_upstream_keepalive. *) Добавление: директива proxy_http_version. *) Добавление: директива fastcgi_keep_conn. *) Добавление: директива worker_aio_requests. *) Исправление: если nginx был собран с файловым AIO, он не мог запускаться на Linux без поддержки AIO. *) Исправление: в обработке ошибок при работе с Linux AIO. Спасибо Hagai Avrahami. *) Исправление: уменьшено потребление памяти для долгоживущих запросов. *) Исправление: модуль ngx_http_mp4_module не поддерживал 64-битный MP4-атом co64. Изменения в nginx 1.1.3 14.09.2011 *) Добавление: модуль ngx_http_mp4_module. *) Исправление: в Linux AIO, используемым совместно с open_file_cache. *) Исправление: open_file_cache не обновлял информацию о файле, если файл был изменён не атомарно. *) Исправление: nginx не собирался на MacOSX 10.7. Изменения в nginx 1.1.2 05.09.2011 *) Изменение: теперь, если суммарный размер всех диапазонов больше размера исходного ответа, то nginx возвращает только исходный ответ, не обрабатывая диапазоны. *) Добавление: директива max_ranges. *) Исправление: директивы ssl_verify_client, ssl_verify_depth и ssl_prefer_server_cipher могли работать некорректно, если использовался SNI. *) Исправление: в директивах proxy/fastcgi/scgi/ uwsgi_ignore_client_abort. Изменения в nginx 1.1.1 22.08.2011 *) Изменение: теперь загрузчик кэша за каждую итерацию либо обрабатывает число файлов, указанное в параметре load_files, либо работает не дольше времени, указанного в параметре loader_threshold. *) Изменение: SIGWINCH сигнал теперь работает только в режиме демона. *) Добавление: теперь разделяемые зоны и кэши используют семафоры POSIX на Solaris. Спасибо Денису Иванову. *) Добавление: теперь на NetBSD поддерживаются accept фильтры. *) Исправление: nginx не собирался на Linux 3.0. *) Исправление: в некоторых случаях nginx не использовал сжатие; ошибка появилась в 1.1.0. *) Исправление: обработка тела запроса могла быть неверной, если клиент использовал pipelining. *) Исправление: в директиве request_body_in_single_buf. *) Исправление: в директивах proxy_set_body и proxy_pass_request_body при использовании SSL-соединения с бэкендом. *) Исправление: nginx нагружал процессор, если все серверы в upstream'е были помечены флагом down. *) Исправление: при переконфигурации мог произойти segmentation fault, если в предыдущей конфигурации был определён, но не использовался ssl_session_cache. *) Исправление: при использовании большого количества backup-серверов в рабочем процессе мог произойти segmentation fault. *) Исправление: при использовании директив fastcgi/scgi/uwsgi_param со значениями, начинающимися со строки "HTTP_", в рабочем процессе мог произойти segmentation fault; ошибка появилась в 0.8.40. Изменения в nginx 1.1.0 01.08.2011 *) Добавление: уменьшение времени работы загрузчика кэша. *) Добавление: параметры loader_files, loader_sleep и loader_threshold директив proxy/fastcgi/scgi/uwsgi_cache_path. *) Добавление: уменьшение времени загрузки конфигураций с большим количеством HTTPS серверов. *) Добавление: теперь nginx поддерживает шифры с обменом ECDHE-ключами. Спасибо Adrian Kotelba. *) Добавление: директива lingering_close. Спасибо Максиму Дунину. *) Исправление: закрытия соединения для pipelined-запросов. Спасибо Максиму Дунину. *) Исправление: nginx не запрещал сжатие при получении значения "gzip;q=0" в строке "Accept-Encoding" в заголовке запроса клиента. *) Исправление: таймаута при небуферизированном проксировании. Спасибо Максиму Дунину. *) Исправление: утечки памяти при использовании переменных в директиве proxy_pass при работе с бэкендом по HTTPS. Спасибо Максиму Дунину. *) Исправление: в проверке параметра директивы proxy_pass, заданного переменными. Спасибо Lanshun Zhou. *) Исправление: SSL не работал на QNX. Спасибо Максиму Дунину. *) Исправление: SSL модули не собирались gcc 4.6 без параметра --with-debug. Изменения в nginx 1.0.5 19.07.2011 *) Изменение: теперь по умолчанию используются следующие шифры SSL: "HIGH:!aNULL:!MD5". Спасибо Rob Stradling. *) Добавление: директивы referer_hash_max_size и referer_hash_bucket_size. Спасибо Witold Filipczyk. *) Добавление: переменная $uid_reset. *) Исправление: при использовании кэширования в рабочем процессе мог произойти segmentation fault. Спасибо Lanshun Zhou. *) Исправление: при использовании кэширования рабочие процессы могли зациклиться во время переконфигурации; ошибка появилась в 0.8.48. Спасибо Максиму Дунину. *) Исправление: сообщения "stalled cache updating". Спасибо Максиму Дунину. Изменения в nginx 1.0.4 01.06.2011 *) Изменение: теперь в регулярных выражениях в директиве map можно задать чувствительность к регистру с помощью префиксов "~" и "~*". *) Добавление: теперь разделяемые зоны и кэши используют семафоры POSIX на Linux. Спасибо Денису Латыпову. *) Исправление: сообщения "stalled cache updating". *) Исправление: nginx не собирался с параметром --without-http_auth_basic_module; ошибка появилась в 1.0.3. Изменения в nginx 1.0.3 25.05.2011 *) Добавление: директива auth_basic_user_file поддерживает шифрование пароля методами "$apr1", "{PLAIN}" и "{SSHA}". Спасибо Максиму Дунину. *) Добавление: директива geoip_org и переменная $geoip_org. Спасибо Александру Ускову, Arnaud Granal и Денису Латыпову. *) Добавление: модули ngx_http_geo_module и ngx_http_geoip_module поддерживают адреса IPv4, отображённые на IPv6 адреса. *) Исправление: при проверке адреса IPv4, отображённого на адрес IPv6, в рабочем процессе происходил segmentation fault, если директивы access или deny были определены только для адресов IPv6; ошибка появилась в 0.8.22. *) Исправление: закэшированный ответ мог быть испорчен, если значения директив proxy/fastcgi/scgi/uwsgi_cache_bypass и proxy/fastcgi/scgi/ uwsgi_no_cache были разными; ошибка появилась в 0.8.46. Изменения в nginx 1.0.2 10.05.2011 *) Добавление: теперь разделяемые зоны и кэши используют семафоры POSIX. *) Исправление: в работе параметра rotate директивы image_filter. Спасибо Adam Bocim. *) Исправление: nginx не собирался на Solaris; ошибка появилась в 1.0.1. Изменения в nginx 1.0.1 03.05.2011 *) Изменение: теперь директива split_clients использует алгоритм MurmurHash2 из-за лучшего распределения. Спасибо Олегу Мамонтову. *) Изменение: теперь длинные строки, начинающиеся с нуля, не считаются ложными значениями. Спасибо Максиму Дунину. *) Изменение: теперь по умолчанию nginx использует значение 511 для listen backlog на Linux. *) Добавление: переменные $upstream_... можно использовать в SSI и перловом модулях. *) Исправление: теперь nginx лучше ограничивает размер кэша на диске. Спасибо Олегу Мамонтову. *) Исправление: при парсинге неправильного IPv4 адреса мог произойти segmentation fault; ошибка появилась в 0.8.22. Спасибо Максиму Дунину. *) Исправление: nginx не собирался gcc 4.6 без параметра --with-debug. *) Исправление: nginx не собирался на Solaris 9 и более ранних; ошибка появилась в 0.9.3. Спасибо Dagobert Michelsen. *) Исправление: переменная $request_time имела неверные значения, если использовались подзапросы; ошибка появилась в 0.8.47. Спасибо Игорю А. Валькову. Изменения в nginx 1.0.0 12.04.2011 *) Исправление: cache manager мог нагружать процессор после переконфигурации. Спасибо Максиму Дунину. *) Исправление: директива "image_filter crop" неправильно работала в сочетании с "image_filter rotate 180". *) Исправление: директива "satisfy any" запрещала выдачу пользовательской страницы для 401 кода. Изменения в nginx 0.9.7 04.04.2011 *) Добавление: теперь соединения в состоянии keepalive могут быть закрыты преждевременно, если у воркера нет свободных соединений. Спасибо Максиму Дунину. *) Добавление: параметр rotate директивы image_filter. Спасибо Adam Bocim. *) Исправление: ситуации, когда бэкенд в директивах fastcgi_pass, scgi_pass или uwsgi_pass задан выражением и ссылается на описанный upstream. Изменения в nginx 0.9.6 21.03.2011 *) Добавление: директива map поддерживает регулярные выражения в качестве значения первого параметра. *) Добавление: переменная $time_iso8601 для access_log. Спасибо Michael Lustfield. Изменения в nginx 0.9.5 21.02.2011 *) Изменение: теперь по умолчанию nginx использует значение -1 для listen backlog на Linux. Спасибо Андрею Нигматулину. *) Добавление: параметр utf8 в директивах geoip_country и geoip_city. Спасибо Денису Латыпову. *) Исправление: исправление в умолчательной директиве proxy_redirect, если в директиве proxy_pass не был описан URI. Спасибо Максиму Дунину. *) Исправление: директива error_page не работала с нестандартными кодами ошибок; ошибка появилась в 0.8.53. Спасибо Максиму Дунину. Изменения в nginx 0.9.4 21.01.2011 *) Добавление: директива server_name поддерживает переменную $hostname. *) Добавление: 494 код для ошибки "Request Header Too Large". Изменения в nginx 0.9.3 13.12.2010 *) Исправление: если для пары IPv6-адрес:порт описан только один сервер, то выделения в регулярных выражениях в директиве server_name не работали. *) Исправление: nginx не собирался под Solaris; ошибка появилась в 0.9.0. Изменения в nginx 0.9.2 06.12.2010 *) Добавление: поддержка строки "If-Unmodified-Since" в заголовке запроса клиента. *) Изменение: использование accept(), если accept4() не реализован; ошибка появилась в 0.9.0. *) Исправление: nginx не собирался под Cygwin; ошибка появилась в 0.9.0. *) Исправление: уязвимости в OpenSSL CVE-2010-4180. Спасибо Максиму Дунину. Изменения в nginx 0.9.1 30.11.2010 *) Исправление: директивы вида "return CODE message" не работали; ошибка появилась в 0.9.0. Изменения в nginx 0.9.0 29.11.2010 *) Добавление: директива keepalive_disable. *) Добавление: директива map поддерживает переменные в качестве значения определяемой переменной. *) Добавление: директива map поддерживает пустые строки в качестве значения первого параметра. *) Добавление: директива map поддерживает выражения в первом параметре. *) Добавление: страница руководства nginx(8). Спасибо Сергею Осокину. *) Добавление: поддержка accept4() в Linux. Спасибо Simon Liu. *) Изменение: устранение предупреждения линкера о "sys_errlist" и "sys_nerr" под Linux; предупреждение появилось в 0.8.35. *) Исправление: при использовании директивы auth_basic в рабочем процессе мог произойти segmentation fault. Спасибо Михаилу Лалетину. *) Исправление: совместимость с модулем ngx_http_eval_module; ошибка появилась в 0.8.42. Изменения в nginx 0.8.53 18.10.2010 *) Добавление: теперь директива error_page позволяет менять код статуса у редиректа. *) Добавление: директива gzip_disable поддерживает специальную маску degradation. *) Исправление: при использовании файлового AIO могла происходить утечка сокетов. Спасибо Максиму Дунину. *) Исправление: если в первом сервере не была описана директива listen и нигде явно не описан сервер по умолчанию, то сервером по умолчанию становился следующий сервер с директивой listen; ошибка появилась в 0.8.21. Изменения в nginx 0.8.52 28.09.2010 *) Исправление: nginx использовал режим SSL для listen сокета, если для него был установлен любой listen-параметр; ошибка появилась в 0.8.51. Изменения в nginx 0.8.51 27.09.2010 *) Изменение: директива secure_link_expires упразднена. *) Изменение: уровень логгирования ошибок resolver'а понижен с уровня alert на error. *) Добавление: теперь параметр "ssl" listen-сокета можно устанавливать несколько раз. Изменения в nginx 0.8.50 02.09.2010 *) Добавление: директивы secure_link, secure_link_md5 и secure_link_expires модуля ngx_http_secure_link_module. *) Добавление: ключ -q. Спасибо Геннадию Махомеду. *) Исправление: при использовании кэширования рабочие процессы и могли зациклиться во время переконфигурации; ошибка появилась в 0.8.48. *) Исправление: в директиве gzip_disable. Спасибо Derrick Petzold. *) Исправление: nginx/Windows не мог посылать сигналы stop, quit, reopen, reload процессу, запущенному в другой сессии. Изменения в nginx 0.8.49 09.08.2010 *) Добавление: директива image_filter_jpeg_quality поддерживает переменные. *) Исправление: при использовании переменной $geoip_region_name в рабочем процессе мог произойти segmentation fault; ошибка появилась в 0.8.48. *) Исправление: ошибки, перехваченные error_page, кэшировались только до следующего запроса; ошибка появилась в 0.8.48. Изменения в nginx 0.8.48 03.08.2010 *) Изменение: теперь по умолчанию директива server_name имеет значение пустое имя "". Спасибо Геннадию Махомеду. *) Изменение: теперь по умолчанию директива server_name_in_redirect имеет значение off. *) Добавление: переменные $geoip_dma_code, $geoip_area_code и $geoip_region_name. Спасибо Christine McGonagle. *) Исправление: директивы proxy_pass, fastcgi_pass, uwsgi_pass и scgi_pass не наследовались в блоки limit_except. *) Исправление: директивы proxy_cache_min_uses, fastcgi_cache_min_uses uwsgi_cache_min_uses и scgi_cache_min_uses не работали; ошибка появилась в 0.8.46. *) Исправление: директива fastcgi_split_path_info неверно использовала выделения, если в выделения попадала только часть URI. Спасибо Юрию Тарадаю и Frank Enderle. *) Исправление: директива rewrite не экранировала символ ";" при копировании из URI в аргументы. Спасибо Daisuke Murase. *) Исправление: модуль ngx_http_image_filter_module закрывал соединение, если изображение было больше размера image_filter_buffer. Изменения в nginx 0.8.47 28.07.2010 *) Исправление: переменная $request_time имела неверные значения для подзапросов. *) Исправление: ошибки, перехваченные error_page, не кэшировались. *) Исправление: если использовался параметр max_size, то cache manager мог зациклиться; ошибка появилась в 0.8.46. Изменения в nginx 0.8.46 19.07.2010 *) Изменение: директивы proxy_no_cache, fastcgi_no_cache, uwsgi_no_cache и scgi_no_cache теперь влияют только на сохранение закэшированного ответа. *) Добавление: директивы proxy_cache_bypass, fastcgi_cache_bypass, uwsgi_cache_bypass и scgi_cache_bypass. *) Исправление: nginx не освобождал память в keys_zone кэшей в случае ошибки работы с бэкендом: память освобождалась только по истечении времени неактивности или при недостатке памяти. Изменения в nginx 0.8.45 13.07.2010 *) Добавление: улучшения в модуле ngx_http_xslt_filter. Спасибо Laurence Rowe. *) Исправление: ответ SSI модуля мог передаваться не полностью после команды include с параметром wait="yes"; ошибка появилась в 0.7.25. Спасибо Максиму Дунину. *) Исправление: директива listen не поддерживала параметр setfib=0. Изменения в nginx 0.8.44 05.07.2010 *) Изменение: теперь nginx по умолчанию не кэширует ответы бэкендов, в заголовке которых есть строка "Set-Cookie". *) Добавление: директива listen поддерживает параметр setfib. Спасибо Андрею Филонову. *) Исправление: директива sub_filter могла изменять регистр букв при частичном совпадении. *) Исправление: совместимость с HP/UX. *) Исправление: совместимость с компилятором AIX xlC_r. *) Исправление: nginx считал большие пакеты SSLv2 как обычные текстовые запросы. Спасибо Miroslaw Jaworski. Изменения в nginx 0.8.43 30.06.2010 *) Добавление: ускорение загрузки больших баз geo-диапазонов. *) Исправление: перенаправление ошибки в "location /zero {return 204;}" без изменения кода ответа оставляло тело ошибки; ошибка появилась в 0.8.42. *) Исправление: nginx мог закрывать IPv6 listen сокет во время переконфигурации. Спасибо Максиму Дунину. *) Исправление: переменную $uid_set можно использовать на любой стадии обработки запроса. Изменения в nginx 0.8.42 21.06.2010 *) Изменение: теперь nginx проверяет location'ы, заданные регулярными выражениями, если запрос полностью совпал с location'ом, заданным строкой префикса. Предыдущее поведение появилось в 0.7.1. *) Добавление: модуль ngx_http_scgi_module. Спасибо Manlio Perillo. *) Добавление: в директиве return можно добавлять текст ответа. Изменения в nginx 0.8.41 15.06.2010 *) Безопасность: рабочий процесс nginx/Windows мог завершаться аварийно при запросе файла с неверной кодировкой UTF-8. *) Изменение: теперь nginx разрешает использовать пробелы в строке запроса. *) Исправление: директива proxy_redirect неправильно изменяла строку "Refresh" в заголовке ответа бэкенда. Спасибо Андрею Андрееву и Максиму Согину. *) Исправление: nginx не поддерживал путь без имени хоста в строке "Destination" в заголовке запроса. Изменения в nginx 0.8.40 07.06.2010 *) Безопасность: теперь nginx/Windows игнорирует имя потока файла по умолчанию. Спасибо Jose Antonio Vazquez Gonzalez. *) Добавление: модуль ngx_http_uwsgi_module. Спасибо Roberto De Ioris. *) Добавление: директива fastcgi_param со значением, начинающимся со строки "HTTP_", изменяет строку заголовка в запросе клиента. *) Исправление: строки "If-Modified-Since", "If-Range" и им подобные в заголовке запроса клиента передавались FastCGI-серверу при кэшировании. *) Исправление: listen unix domain сокет нельзя было изменить во время переконфигурации. Спасибо Максиму Дунину. Изменения в nginx 0.8.39 31.05.2010 *) Исправление: наследуемая директива alias неправильно работала во вложенном location'е. *) Исправление: в комбинации директив alias с переменными и try_files; *) Исправление: listen unix domain и IPv6 сокеты не наследовались во время обновления без перерыва. Спасибо Максиму Дунину. Изменения в nginx 0.8.38 24.05.2010 *) Добавление: директивы proxy_no_cache и fastcgi_no_cache. *) Добавление: теперь при использовании переменной $scheme в директиве rewrite автоматически делается редирект. Спасибо Piotr Sikora. *) Исправление: теперь задержки в директиве limit_req соответствует описанному алгоритму. Спасибо Максиму Дунину. *) Исправление: переменную $uid_got нельзя было использовать в SSI и перловом модулях. Изменения в nginx 0.8.37 17.05.2010 *) Добавление: модуль ngx_http_split_clients_module. *) Добавление: директива map поддерживает ключи больше 255 символов. *) Исправление: nginx игнорировал значения "private" и "no-store" в строке "Cache-Control" в заголовке ответа бэкенда. *) Исправление: параметр stub в SSI-директиве include не использовался, если пустой ответ имел код 200. *) Исправление: если проксированный или FastCGI запрос внутренне перенаправлялся в другой проксированный или FastCGI location, то в рабочем процессе мог произойти segmentation fault; ошибка появилась в 0.8.33. Спасибо Yichun Zhang. *) Исправление: соединения IMAP к серверу Zimbra могло зависнуть до таймаута. Спасибо Alan Batie. Изменения в nginx 0.8.36 22.04.2010 *) Исправление: модуль ngx_http_dav_module неправильно обрабатывал методы DELETE, COPY и MOVE для симлинков. *) Исправление: модуль SSI в подзапросах использовал закэшированные в основном запросе значения переменных $query_string, $arg_... и им подобных. *) Исправление: значение переменной повторно экранировалось после каждого вывода SSI-команды echo; ошибка появилась в 0.6.14. *) Исправление: рабочий процесс зависал при запросе файла FIFO. Спасибо Vicente Aguilar и Максиму Дунину. *) Исправление: совместимость с OpenSSL-1.0.0 на 64-битном Linux. Спасибо Максиму Дунину. *) Исправление: nginx не собирался с параметром --without-http-cache; ошибка появилась в 0.8.35. Изменения в nginx 0.8.35 01.04.2010 *) Изменение: теперь charset-фильтр работает до SSI-фильтра. *) Добавление: директива chunked_transfer_encoding. *) Исправление: символ "&" при копировании в аргументы в правилах rewrite не экранировался. *) Исправление: nginx мог завершаться аварийно во время обработки сигнала или при использовании директивы timer_resolution на платформах, не поддерживающих методы kqueue или eventport. Спасибо George Xie и Максиму Дунину. *) Исправление: если временные файлы и постоянное место хранения располагались на разных файловых системах, то у постоянных файлов время изменения было неверным. Спасибо Максиму Дунину. *) Исправление: модуль ngx_http_memcached_module мог выдавать ошибку "memcached sent invalid trailer". Спасибо Максиму Дунину. *) Исправление: nginx не мог собрать библиотеку zlib-1.2.4 из исходных текстов. Спасибо Максиму Дунину. *) Исправление: в рабочем процессе происходил segmentation fault, если перед ответом FastCGI-сервера было много вывода в stderr; ошибка появилась в 0.8.34. Спасибо Максиму Дунину. Изменения в nginx 0.8.34 03.03.2010 *) Исправление: nginx не поддерживал все шифры, используемые в клиентских сертификатах. Спасибо Иннокентию Еникееву. *) Исправление: nginx неправильно кэшировал FastCGI-ответы, если перед ответом было много вывода в stderr. *) Исправление: nginx не поддерживал HTTPS-рефереры. *) Исправление: nginx/Windows мог не находить файлы, если путь в конфигурации был задан в другом регистре; ошибка появилась в 0.8.33. *) Исправление: переменная $date_local выдавала неверное время, если использовался формат "%s". Спасибо Максиму Дунину. *) Исправление: если ssl_session_cache не был установлен или установлен в none, то при проверке клиентского сертификаты могла происходить ошибка "session id context uninitialized"; ошибка появилась в 0.7.1. *) Исправление: geo-диапазон возвращал значение по умолчанию, если диапазон включал в себя одну и более сетей размером /16 и не начинался на границе сети размером /16. *) Исправление: блок, используемый в параметре stub в SSI-директиве include, выводился с MIME-типом "text/plain". *) Исправление: $r->sleep() не работал; ошибка появилась в 0.8.11. Изменения в nginx 0.8.33 01.02.2010 *) Безопасность: теперь nginx/Windows игнорирует пробелы в конце URI. Спасибо Dan Crowley, Core Security Technologies. *) Безопасность: теперь nginx/Windows игнорирует короткие имена файлов. Спасибо Dan Crowley, Core Security Technologies. *) Изменение: теперь keepalive соединения после запросов POST не запрещаются для MSIE 7.0+. Спасибо Adam Lounds. *) Изменение: теперь keepalive соединения запрещены для Safari. Спасибо Joshua Sierles. *) Исправление: если проксированный или FastCGI запрос внутренне перенаправлялся в другой проксированный или FastCGI location, то переменная $upstream_response_time могла иметь ненормально большое значение; ошибка появилась в 0.8.7. *) Исправление: в рабочем процессе мог произойти segmentation fault при отбрасывания тела запроса; ошибка появилась в 0.8.11. Изменения в nginx 0.8.32 11.01.2010 *) Исправление: ошибки при использовании кодировки UTF-8 в ngx_http_autoindex_module. Спасибо Максиму Дунину. *) Исправление: именованные выделения в регулярных выражениях работали только для двух переменных. Спасибо Максиму Дунину. *) Исправление: теперь в строке заголовка запроса "Host" используется имя "localhost", если в директиве auth_http указан unix domain сокет. Спасибо Максиму Дунину. *) Исправление: nginx не поддерживал передачу chunk'ами для 201-ых ответов. Спасибо Julian Reich. *) Исправление: если директива "expires modified" выставляла дату в прошлом, то в строке заголовка ответа "Cache-Control" выдавалось отрицательное число. Спасибо Алексею Капранову. Изменения в nginx 0.8.31 23.12.2009 *) Добавление: теперь директива error_page может перенаправлять ответы со статусом 301 и 302. *) Добавление: переменные $geoip_city_continent_code, $geoip_latitude и $geoip_longitude. Спасибо Arvind Sundararajan. *) Добавление: модуль ngx_http_image_filter_module теперь всегда удаляет EXIF и другие данные, если они занимают больше 5% в JPEG-файле. *) Исправление: nginx закрывал соединение при запросе закэшированного ответа с пустым телом. Спасибо Piotr Sikora. *) Исправление: nginx мог не собираться gcc 4.x при использовании оптимизации -O2 и выше. Спасибо Максиму Дунину и Денису Латыпову. *) Исправление: регулярные выражения в location всегда тестировались с учётом регистра; ошибка появилась в 0.8.25. *) Исправление: nginx кэшировал 304 ответ, если в заголовке проксируемого запроса была строка "If-None-Match". Спасибо Tim Dettrick и David Kostal. *) Исправление: nginx/Windows пытался дважды удалить временный файл при перезаписи уже существующего файла. Изменения в nginx 0.8.30 15.12.2009 *) Изменение: теперь по умолчанию размер буфера директивы large_client_header_buffers равен 8K. Спасибо Andrew Cholakian. *) Добавление: файл conf/fastcgi.conf для простых конфигураций FastCGI. *) Исправление: nginx/Windows пытался дважды переименовать временный файл при перезаписи уже существующего файла. *) Исправление: ошибки double free or corruption, возникающей, если имя хоста не было найдено; ошибка появилась в 0.8.22. Спасибо Константину Свисту. *) Исправление: в использовании libatomic на некоторых платформах. Спасибо W-Mark Kubacki. Изменения в nginx 0.8.29 30.11.2009 *) Изменение: теперь для проксируемых ответов HTTP/0.9 в лог пишется код ответа "009". *) Добавление: директивы addition_types, charset_types, gzip_types, ssi_types, sub_filter_types и xslt_types поддерживают параметр "*". *) Добавление: использование встроенных атомарных операций GCC 4.1+. Спасибо W-Mark Kubacki. *) Добавление: параметр --with-libatomic[=DIR] в configure. Спасибо W-Mark Kubacki. *) Исправление: listen unix domain сокет имели ограниченные права доступа. *) Исправление: закэшированные ответы ответов HTTP/0.9 неправильно обрабатывались. *) Исправление: именованные выделения в регулярных выражениях, заданные как "?P<...>", не работали в директиве server_name. Спасибо Максиму Дунину. Изменения в nginx 0.8.28 23.11.2009 *) Исправление: nginx не собирался с параметром --without-pcre; ошибка появилась в 0.8.25. Изменения в nginx 0.8.27 17.11.2009 *) Исправление: регулярные выражения не работали в nginx/Windows; ошибка появилась в 0.8.25. Изменения в nginx 0.8.26 16.11.2009 *) Исправление: ошибки при использовании выделений в директиве rewrite; ошибка появилась в 0.8.25. *) Исправление: nginx не собирался без параметра --with-debug; ошибка появилась в 0.8.25. Изменения в nginx 0.8.25 16.11.2009 *) Изменение: теперь в лог ошибок не пишется сообщение, если переменная не найдена с помощью метода $r->variable(). *) Добавление: модуль ngx_http_degradation_module. *) Добавление: именованные выделения в регулярных выражениях. *) Добавление: теперь при использовании переменных в директиве proxy_pass не требуется задавать URI. *) Добавление: теперь директива msie_padding работает и для Chrome. *) Исправление: в рабочем процессе происходил segmentation fault при недостатке памяти; ошибка появилась в 0.8.18. *) Исправление: nginx передавал сжатые ответы клиентам, не поддерживающим сжатие, при настройках gzip_static on и gzip_vary off; ошибка появилась в 0.8.16. Изменения в nginx 0.8.24 11.11.2009 *) Исправление: nginx всегда добавлял строку "Content-Encoding: gzip" в заголовок 304-ых ответов модуля ngx_http_gzip_static_module. *) Исправление: nginx не собирался без параметра --with-debug; ошибка появилась в 0.8.23. *) Исправление: параметр "unix:" в директиве set_real_ip_from неправильно наследовался с предыдущего уровня. *) Исправление: в resolver'е при определении пустого имени. Изменения в nginx 0.8.23 11.11.2009 *) Безопасность: теперь SSL/TLS renegotiation запрещён. Спасибо Максиму Дунину. *) Исправление: listen unix domain сокет не наследовался во время обновления без перерыва. *) Исправление: параметр "unix:" в директиве set_real_ip_from не работал без ещё одной директивы с любым IP-адресом. *) Исправление: segmentation fault и зацикливания в resolver'е. *) Исправление: в resolver'е. Спасибо Артёму Бохану. Изменения в nginx 0.8.22 03.11.2009 *) Добавление: директивы proxy_bind, fastcgi_bind и memcached_bind. *) Добавление: директивы access и deny поддерживают IPv6. *) Добавление: директива set_real_ip_from поддерживает IPv6 адреса в заголовках запроса. *) Добавление: параметр "unix:" в директиве set_real_ip_from. *) Исправление: nginx не удалял unix domain сокет после тестирования конфигурации. *) Исправление: nginx удалял unix domain сокет во время обновления без перерыва. *) Исправление: оператор "!-x" не работал. Спасибо Максиму Дунину. *) Исправление: в рабочем процессе мог произойти segmentation fault при использовании limit_rate в HTTPS сервере. Спасибо Максиму Дунину. *) Исправление: при записи в лог переменной $limit_rate в рабочем процессе происходил segmentation fault. Спасибо Максиму Дунину. *) Исправление: в рабочем процессе мог произойти segmentation fault, если внутри блока server не было директивы listen; ошибка появилась в 0.8.21. Изменения в nginx 0.8.21 26.10.2009 *) Добавление: теперь ключ -V показывает статус поддержки TLS SNI. *) Добавление: директива listen модуля HTTP поддерживает unix domain сокеты. Спасибо Hongli Lai. *) Добавление: параметр "default_server" в директиве listen. *) Добавление: теперь параметр "default" не обязателен для установки параметров listen-сокета. *) Исправление: nginx не поддерживал даты в 2038 году на 32-битных платформах; *) Исправление: утечки сокетов; ошибка появилась в 0.8.11. Изменения в nginx 0.8.20 14.10.2009 *) Изменение: теперь по умолчанию используются следующие шифры SSL: "HIGH:!ADH:!MD5". *) Исправление: модуль ngx_http_autoindex_module не показывал последний слэш для линков на каталоги; ошибка появилась в 0.7.15. *) Исправление: nginx не закрывал лог, заданный параметром конфигурации --error-log-path; ошибка появилась в 0.7.53. *) Исправление: nginx не считал запятую разделителем в строке "Cache-Control" в заголовке ответа бэкенда. *) Исправление: nginx/Windows мог не создать временный файл, файл в кэше или файл с помощью директив proxy/fastcgi_store, если рабочий процесс не имел достаточно прав для работы с каталогами верхнего уровня. *) Исправление: строки "Set-Cookie" и "P3P" в заголовке ответа FastCGI-сервера не скрывались при кэшировании, если не использовались директивы fastcgi_hide_header с любыми параметрами. *) Исправление: nginx неверно считал размер кэша на диске. Изменения в nginx 0.8.19 06.10.2009 *) Изменение: теперь протокол SSLv2 по умолчанию запрещён. *) Изменение: теперь по умолчанию используются следующие шифры SSL: "ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM". *) Исправление: директива limit_req не работала; ошибка появилась в 0.8.18. Изменения в nginx 0.8.18 06.10.2009 *) Добавление: директива read_ahead. *) Добавление: теперь можно использовать несколько директив perl_modules. *) Добавление: директивы limit_req_log_level и limit_conn_log_level. *) Исправление: теперь директива limit_req соответствует алгоритму leaky bucket. Спасибо Максиму Дунину. *) Исправление: nginx не работал на Linux/sparc. Спасибо Marcus Ramberg. *) Исправление: nginx слал символ '\0' в строке "Location" в заголовке в ответе на запрос MKCOL. Спасибо Xie Zhenye. *) Исправление: вместо кода ответа 499 в лог записывался код 0; ошибка появилась в 0.8.11. *) Исправление: утечки сокетов; ошибка появилась в 0.8.11. Изменения в nginx 0.8.17 28.09.2009 *) Безопасность: теперь символы "/../" запрещены в строке "Destination" в заголовке запроса. *) Изменение: теперь значение переменной $host всегда в нижнем регистре. *) Добавление: переменная $ssl_session_id. *) Исправление: утечки сокетов; ошибка появилась в 0.8.11. Изменения в nginx 0.8.16 22.09.2009 *) Добавление: директива image_filter_transparency. *) Исправление: директива "addition_types" была неверно названа "addtion_types". *) Исправление: порчи кэша resolver'а. Спасибо Matthew Dempsky. *) Исправление: утечки памяти в resolver'е. Спасибо Matthew Dempsky. *) Исправление: неверная строка запроса в переменной $request записывалась в access_log только при использовании error_log на уровне info или debug. *) Исправление: в поддержке альфа-канала PNG в модуле ngx_http_image_filter_module. *) Исправление: nginx всегда добавлял строку "Vary: Accept-Encoding" в заголовок ответа, если обе директивы gzip_static и gzip_vary были включены. *) Исправление: в поддержке кодировки UTF-8 директивой try_files в nginx/Windows. *) Исправление: ошибки при использовании post_action; ошибка появилась в 0.8.11. Спасибо Игорю Артемьеву. Изменения в nginx 0.8.15 14.09.2009 *) Безопасность: при обработке специально созданного запроса в рабочем процессе мог произойти segmentation fault. Спасибо Chris Ries. *) Исправление: если были описаны имена .domain.tld, .sub.domain.tld и .domain-some.tld, то имя .sub.domain.tld попадало под маску .domain.tld. *) Исправление: в поддержке прозрачности в модуле ngx_http_image_filter_module. *) Исправление: в файловом AIO. *) Исправление: ошибки при использовании X-Accel-Redirect; ошибка появилась в 0.8.11. *) Исправление: ошибки при использовании встроенного перла; ошибка появилась в 0.8.11. Изменения в nginx 0.8.14 07.09.2009 *) Исправление: устаревший закэшированный запрос мог залипнуть в состоянии "UPDATING". *) Исправление: при использовании error_log на уровне info или debug в рабочем процессе мог произойти segmentation fault. Спасибо Сергею Боченкову. *) Исправление: ошибки при использовании встроенного перла; ошибка появилась в 0.8.11. *) Исправление: директива error_page не перенаправляла ошибку 413; ошибка появилась в 0.6.10. Изменения в nginx 0.8.13 31.08.2009 *) Исправление: в директиве "aio sendfile"; ошибка появилась в 0.8.12. *) Исправление: nginx не собирался без параметра --with-file-aio на FreeBSD; ошибка появилась в 0.8.12. Изменения в nginx 0.8.12 31.08.2009 *) Добавление: параметр sendfile в директиве aio во FreeBSD. *) Исправление: ошибки при использовании try_files; ошибка появилась в 0.8.11. *) Исправление: ошибки при использовании memcached; ошибка появилась в 0.8.11. Изменения в nginx 0.8.11 28.08.2009 *) Изменение: теперь директива "gzip_disable msie6" не запрещает сжатие для MSIE 6.0 SV1. *) Добавление: поддержка файлового AIO во FreeBSD и Linux. *) Добавление: директива directio_alignment. Изменения в nginx 0.8.10 24.08.2009 *) Исправление: утечек памяти при использовании базы GeoIP City. *) Исправление: ошибки при копировании временных файлов в постоянное место хранения; ошибка появилась в 0.8.9. Изменения в nginx 0.8.9 17.08.2009 *) Добавление: теперь стартовый загрузчик кэша работает в отдельном процесс; это должно улучшить обработку больших кэшей. *) Добавление: теперь временные файлы и постоянное место хранения могут располагаться на разных файловых системах. Изменения в nginx 0.8.8 10.08.2009 *) Исправление: в обработке заголовков ответа, разделённых в FastCGI-записях. *) Исправление: если запрос обрабатывался в двух проксированных или FastCGI location'ах и в первом из них использовалось кэширование, то в рабочем процессе происходил segmentation fault; ошибка появилась в 0.8.7. Изменения в nginx 0.8.7 27.07.2009 *) Изменение: минимальная поддерживаемая версия OpenSSL - 0.9.7. *) Изменение: параметр ask директивы ssl_verify_client изменён на параметр optional и теперь он проверяет клиентский сертификат, если он был предложен. Спасибо Brice Figureau. *) Добавление: переменная $ssl_client_verify. Спасибо Brice Figureau. *) Добавление: директива ssl_crl. Спасибо Brice Figureau. *) Добавление: параметр proxy директивы geo. *) Добавление: директива image_filter поддерживает переменные для задания размеров. *) Исправление: использование переменной $ssl_client_cert портило память; ошибка появилась в 0.7.7. Спасибо Сергею Журавлёву. *) Исправление: директивы proxy_pass_header и fastcgi_pass_header" не передавали клиенту строки "X-Accel-Redirect", "X-Accel-Limit-Rate", "X-Accel-Buffering" и "X-Accel-Charset" из заголовка ответа бэкенда. Спасибо Максиму Дунину. *) Исправление: в обработке строк "Last-Modified" и "Accept-Ranges" в заголовке ответа бэкенда; ошибка появилась в 0.7.44. Спасибо Максиму Дунину. *) Исправление: ошибки "[alert] zero size buf" при получении пустых ответы в подзапросах; ошибка появилась в 0.8.5. Изменения в nginx 0.8.6 20.07.2009 *) Добавление: модуль ngx_http_geoip_module. *) Исправление: XSLT-фильтр мог выдавать ошибку "not well formed XML document" для правильного документа. Спасибо Kuramoto Eiji. *) Исправление: в MacOSX, Cygwin и nginx/Windows при проверке location'ов, заданных регулярным выражением, теперь всегда делается сравнение без учёта регистра символов. *) Исправление: теперь nginx/Windows игнорирует точки в конце URI. Спасибо Hugo Leisink. *) Исправление: имя файла указанного в --conf-path игнорировалось при установке; ошибка появилась в 0.6.6. Спасибо Максиму Дунину. Изменения в nginx 0.8.5 13.07.2009 *) Исправление: теперь nginx разрешает подчёркивания в методе запроса. *) Исправление: при использовании HTTP Basic-аутентификации на Windows для неверных имени/пароля возвращалась 500-ая ошибка. *) Исправление: ответы модуля ngx_http_perl_module не работали в подзапросах. *) Исправление: в модуле ngx_http_limit_req_module. Спасибо Максиму Дунину. Изменения в nginx 0.8.4 22.06.2009 *) Исправление: nginx не собирался с параметром --without-http-cache; ошибка появилась в 0.8.3. Изменения в nginx 0.8.3 19.06.2009 *) Добавление: переменная $upstream_cache_status. *) Исправление: nginx не собирался на MacOSX 10.6. *) Исправление: nginx не собирался с параметром --without-http-cache; ошибка появилась в 0.8.2. *) Исправление: если использовался перехват 401 ошибки от бэкенда и бэкенд не возвращал строку "WWW-Authenticate" в заголовке ответа, то в рабочем процессе происходил segmentation fault. Спасибо Евгению Мычло. Изменения в nginx 0.8.2 15.06.2009 *) Исправление: во взаимодействии open_file_cache и proxy/fastcgi кэша на старте. *) Исправление: open_file_cache мог кэшировать открытые файлы очень долго; ошибка появилась в 0.7.4. Изменения в nginx 0.8.1 08.06.2009 *) Добавление: параметр updating в директивах proxy_cache_use_stale и fastcgi_cache_use_stale. *) Исправление: строки "If-Modified-Since", "If-Range" и им подобные в заголовке запроса клиента передавались бэкенду при кэшировании, если не использовалась директива proxy_set_header с любыми параметрами. *) Исправление: строки "Set-Cookie" и "P3P" в заголовке ответа бэкенда не скрывались при кэшировании, если не использовались директивы proxy_hide_header/fastcgi_hide_header с любыми параметрами. *) Исправление: модуль ngx_http_image_filter_module не понимал формат GIF87a. Спасибо Денису Ильиных. *) Исправление: nginx не собирался на Solaris 10 и более ранних; ошибка появилась в 0.7.56. Изменения в nginx 0.8.0 02.06.2009 *) Добавление: директива keepalive_requests. *) Добавление: директива limit_rate_after. Спасибо Ivan Debnar. *) Исправление: XSLT-фильтр не работал в подзапросах. *) Исправление: обработке относительных путей в nginx/Windows. *) Исправление: в proxy_store, fastcgi_store, proxy_cache и fastcgi_cache в nginx/Windows. *) Исправление: в обработке ошибок выделения памяти. Спасибо Максиму Дунину и Кириллу Коринскому. Изменения в nginx 0.7.59 25.05.2009 *) Добавление: директивы proxy_cache_methods и fastcgi_cache_methods. *) Исправление: утечки сокетов; ошибка появилась в 0.7.25. Спасибо Максиму Дунину. *) Исправление: при использовании переменной $request_body в рабочем процессе происходил segmentation fault, если в запросе не было тела; ошибка появилась в 0.7.58. *) Исправление: SSL-модули могли не собираться на Solaris и Linux; ошибка появилась в 0.7.56. *) Исправление: ответы модуля ngx_http_xslt_filter_module не обрабатывались SSI-, charset- и gzip-фильтрами. *) Исправление: директива charset не ставила кодировку для ответов модуля ngx_http_gzip_static_module. Изменения в nginx 0.7.58 18.05.2009 *) Добавление: директива listen почтового прокси-сервера поддерживает IPv6. *) Добавление: директива image_filter_jpeg_quality. *) Добавление: директива client_body_in_single_buffer. *) Добавление: переменная $request_body. *) Исправление: в модуле ngx_http_autoindex_module в ссылках на имена файлов, содержащих символ ":". *) Исправление: процедура "make upgrade" не работала; ошибка появилась в 0.7.53. Спасибо Денису Латыпову. Изменения в nginx 0.7.57 12.05.2009 *) Исправление: при перенаправлении ошибок модуля ngx_http_image_filter_module в именованный location в рабочем процессе происходил floating-point fault; ошибка появилась в 0.7.56. Изменения в nginx 0.7.56 11.05.2009 *) Добавление: nginx/Windows поддерживает IPv6 в директиве listen модуля HTTP. *) Исправление: в модуле ngx_http_image_filter_module. Изменения в nginx 0.7.55 06.05.2009 *) Исправление: параметры http_XXX в директивах proxy_cache_use_stale и fastcgi_cache_use_stale не работали. *) Исправление: fastcgi кэш не кэшировал ответы, состоящие только из заголовка. *) Исправление: ошибки "select() failed (9: Bad file descriptor)" в nginx/Unix и "select() failed (10038: ...)" в nginx/Windows. *) Исправление: при использовании директивы debug_connection в рабочем процессе мог произойти segmentation fault; ошибка появилась в 0.7.54. *) Исправление: в сборке модуля ngx_http_image_filter_module. *) Исправление: файлы больше 2G не передавались с использованием $r->sendfile. Спасибо Максиму Дунину. Изменения в nginx 0.7.54 01.05.2009 *) Добавление: модуль ngx_http_image_filter_module. *) Добавление: директивы proxy_ignore_headers и fastcgi_ignore_headers. *) Исправление: при использовании переменных "open_file_cache_errors on" в рабочем процессе мог произойти segmentation fault; ошибка появилась в 0.7.53. *) Исправление: директива "port_in_redirect off" не работала; ошибка появилась в 0.7.39. *) Исправление: улучшение обработки ошибок метода select. *) Исправление: ошибки "select() failed (10022: ...)" в nginx/Windows. *) Исправление: в текстовых сообщениях об ошибках в nginx/Windows; ошибка появилась в 0.7.53. Изменения в nginx 0.7.53 27.04.2009 *) Изменение: теперь лог, указанный в --error-log-path, создаётся с самого начала работы. *) Добавление: теперь ошибки и предупреждения при старте записываются в error_log и выводятся на stderr. *) Добавление: при сборке с пустым параметром --prefix= nginx использует как префикс каталог, в котором он был запущен. *) Добавление: ключ -p. *) Добавление: ключ -s на Unix-платформах. *) Добавление: ключи -? и -h. Спасибо Jerome Loyet. *) Добавление: теперь ключи можно задавать в сжатой форме. *) Исправление: nginx/Windows не работал, если файл конфигурации был задан ключом -c. *) Исправление: при использовании директив proxy_store, fastcgi_store, proxy_cache или fastcgi_cache временные файлы могли не удаляться. Спасибо Максиму Дунину. *) Исправление: в заголовке Auth-Method запроса серверу аутентификации почтового прокси-сервера передавалось неверное значение; ошибка появилась в 0.7.34. Спасибо Simon Lecaille. *) Исправление: при логгировании на Linux не писались текстовые описания системных ошибок; ошибка появилась в 0.7.45. *) Исправление: директива fastcgi_cache_min_uses не работала. Спасибо Андрею Воробьёву. Изменения в nginx 0.7.52 20.04.2009 *) Добавление: первая бинарная версия под Windows. *) Исправление: корректная обработка метода HEAD при кэшировании. *) Исправление: корректная обработка строк "If-Modified-Since", "If-Range" и им подобных в заголовке запроса клиента при кэшировании. *) Исправление: теперь строки "Set-Cookie" и "P3P" скрываются в заголовке ответа для закэшированных ответов. *) Исправление: если nginx был собран с модулем ngx_http_perl_module и perl поддерживал потоки, то при выходе основного процесса могла выдаваться ошибка "panic: MUTEX_LOCK". *) Исправление: nginx не собирался с параметром --without-http-cache; ошибка появилась в 0.7.48. *) Исправление: nginx не собирался на платформах, отличных от i386, amd64, sparc и ppc; ошибка появилась в 0.7.42. Изменения в nginx 0.7.51 12.04.2009 *) Добавление: директива try_files поддерживает код ответа в последнем параметре. *) Добавление: теперь в директиве return можно использовать любой код ответа. *) Исправление: директива error_page делала внешний редирект без строки запроса; ошибка появилась в 0.7.44. *) Исправление: если сервера слушали на нескольких явно описанных адресах, то виртуальные сервера могли не работать; ошибка появилась в 0.7.39. Изменения в nginx 0.7.50 06.04.2009 *) Исправление: переменные $arg_... не работали; ошибка появилась в 0.7.49. Изменения в nginx 0.7.49 06.04.2009 *) Исправление: при использовании переменных $arg_... в рабочем процессе мог произойти segmentation fault; ошибка появилась в 0.7.48. Изменения в nginx 0.7.48 06.04.2009 *) Добавление: директива proxy_cache_key. *) Исправление: теперь nginx учитывает при кэшировании строки "X-Accel-Expires", "Expires" и "Cache-Control" в заголовке ответа бэкенда. *) Исправление: теперь nginx кэширует только ответы на запросы GET. *) Исправление: директива fastcgi_cache_key не наследовалась. *) Исправление: переменные $arg_... не работали с SSI-подзапросами. Спасибо Максиму Дунину. *) Исправление: nginx не собирался с библиотекой uclibc. Спасибо Timothy Redaelli. *) Исправление: nginx не собирался на OpenBSD; ошибка появилась в 0.7.46. Изменения в nginx 0.7.47 01.04.2009 *) Исправление: nginx не собирался на FreeBSD 6 и более ранних версиях; ошибка появилась в 0.7.46. *) Исправление: nginx не собирался на MacOSX; ошибка появилась в 0.7.46. *) Исправление: если использовался параметр max_size, то cache manager мог удалить весь кэш; ошибка появилась в 0.7.46. *) Изменение: в рабочем процессе мог произойти segmentation fault, если директивы proxy_cache/fastcgi_cache и proxy_cache_valid/ fastcgi_cache_valid не были заданы на одном уровне; ошибка появилась в 0.7.46. *) Исправление: в рабочем процессе мог произойти segmentation fault при перенаправлении запроса проксированному или FastCGI-серверу с помощью error_page или try_files; ошибка появилась в 0.7.44. Изменения в nginx 0.7.46 30.03.2009 *) Исправление: архив предыдущего релиза был неверным. Изменения в nginx 0.7.45 30.03.2009 *) Изменение: теперь директивы proxy_cache и proxy_cache_valid можно задавать на разных уровнях. *) Изменение: параметр clean_time в директиве proxy_cache_path удалён. *) Добавление: параметр max_size в директиве proxy_cache_path. *) Добавление: предварительная поддержка кэширования в модуле ngx_http_fastcgi_module. *) Добавление: теперь при ошибках выделения в разделяемой памяти в логе указываются названия директивы и зоны. *) Исправление: директива "add_header last-modified ''" не удаляла в заголовке ответа строку "Last-Modified"; ошибка появилась в 0.7.44. *) Исправление: в директиве auth_basic_user_file не работал относительный путь, заданный строкой без переменных; ошибка появилась в 0.7.44. Спасибо Jerome Loyet. *) Исправление: в директиве alias, заданной переменными без ссылок на выделения в регулярных выражениях; ошибка появилась в 0.7.42. Изменения в nginx 0.7.44 23.03.2009 *) Добавление: предварительная поддержка кэширования в модуле ngx_http_proxy_module. *) Добавление: параметр --with-pcre в configure. *) Добавление: теперь директива try_files может быть использована на уровне server. *) Исправление: директива try_files неправильно обрабатывала строку запроса в последнем параметре. *) Исправление: директива try_files могла неверно тестировать каталоги. *) Исправление: если для пары адрес:порт описан только один сервер, то выделения в регулярных выражениях в директиве server_name не работали. Изменения в nginx 0.7.43 18.03.2009 *) Исправление: запрос обрабатывался неверно, если директива root использовала переменные; ошибка появилась в 0.7.42. *) Исправление: если сервер слушал на адресах типа "*", то значение переменной $server_addr было "0.0.0.0"; ошибка появилась в 0.7.36. Изменения в nginx 0.7.42 16.03.2009 *) Изменение: ошибка "Invalid argument", возвращаемая setsockopt(TCP_NODELAY) на Solaris, теперь игнорируется. *) Изменение: при отсутствии файла, указанного в директиве auth_basic_user_file, теперь возвращается ошибка 403 вместо 500. *) Добавление: директива auth_basic_user_file поддерживает переменные. Спасибо Кириллу Коринскому. *) Добавление: директива listen поддерживает параметр ipv6only. Спасибо Zhang Hua. *) Исправление: в директиве alias со ссылками на выделения в регулярных выражениях; ошибка появилась в 0.7.40. *) Исправление: совместимость с Tru64 UNIX. Спасибо Dustin Marquess. *) Исправление: nginx не собирался без библиотеки PCRE; ошибка появилась в 0.7.41. Изменения в nginx 0.7.41 11.03.2009 *) Исправление: в рабочем процессе мог произойти segmentation fault, если в server_name или location были выделения в регулярных выражениях; ошибка появилась в 0.7.40. Спасибо Владимиру Сопоту. Изменения в nginx 0.7.40 09.03.2009 *) Добавление: директива location поддерживает выделения в регулярных выражениях. *) Добавление: директиву alias с ссылками на выделения в регулярных выражениях можно использовать внутри location'а, заданного регулярным выражением с выделениями. *) Добавление: директива server_name поддерживает выделения в регулярных выражениях. *) Изменение: модуль ngx_http_autoindex_module не показывал последний слэш для каталогов на файловой системе XFS; ошибка появилась в 0.7.15. Спасибо Дмитрию Кузьменко. Изменения в nginx 0.7.39 02.03.2009 *) Исправление: при включённом сжатии большие ответы с использованием SSI могли зависать; ошибка появилась в 0.7.28. Спасибо Артёму Бохану. *) Исправление: при использовании коротких статических вариантов в директиве try_files в рабочем процессе мог произойти segmentation fault. Изменения в nginx 0.7.38 23.02.2009 *) Добавление: логгирование ошибок аутентификации. *) Исправление: имя/пароль, заданные в auth_basic_user_file, игнорировались после нечётного числа пустых строк. Спасибо Александру Загребину. *) Исправление: при использовании длинного пути в unix domain сокете в главном процессе происходил segmentation fault; ошибка появилась в 0.7.36. Изменения в nginx 0.7.37 21.02.2009 *) Исправление: директивы, использующие upstream'ы, не работали; ошибка появилась в 0.7.36. Изменения в nginx 0.7.36 21.02.2009 *) Добавление: предварительная поддержка IPv6; директива listen модуля HTTP поддерживает IPv6. *) Исправление: переменная $ancient_browser не работала для браузеров, заданных директивами modern_browser. Изменения в nginx 0.7.35 16.02.2009 *) Исправление: директива ssl_engine не использовала SSL-акселератор для асимметричных шифров. Спасибо Marcin Gozdalik. *) Исправление: директива try_files выставляла MIME-type, исходя из расширения первоначального запроса. *) Исправление: в директивах server_name, valid_referers и map неправильно обрабатывались имена вида "*domain.tld", если использовались маски вида ".domain.tld" и ".subdomain.domain.tld"; ошибка появилась в 0.7.9. Изменения в nginx 0.7.34 10.02.2009 *) Добавление: параметр off в директиве if_modified_since. *) Добавление: теперь после команды XCLIENT nginx посылает команду HELO/EHLO. Спасибо Максиму Дунину. *) Добавление: поддержка Microsoft-специфичного режима "AUTH LOGIN with User Name" в почтовом прокси-сервере. Спасибо Максиму Дунину. *) Исправление: в директиве rewrite, возвращающей редирект, старые аргументы присоединялись к новым через символ "?" вместо "&"; ошибка появилась в 0.1.18. Спасибо Максиму Дунину. *) Исправление: nginx не собирался на AIX. Изменения в nginx 0.7.33 02.02.2009 *) Исправление: если на запрос с телом возвращался редирект, то ответ мог быть двойным при использовании методов epoll или rtsig. Спасибо Eden Li. *) Исправление: для некоторых типов редиректов в переменной $sent_http_location было пустое значение. *) Исправление: при использовании директивы resolver в SMTP прокси-сервере в рабочем процессе мог произойти segmentation fault. Изменения в nginx 0.7.32 26.01.2009 *) Добавление: теперь в директиве try_files можно явно указать проверку каталога. *) Исправление: fastcgi_store не всегда сохранял файлы. *) Исправление: в гео-диапазонах. *) Исправление: ошибки выделения больших блоков в разделяемой памяти, если nginx был собран без отладки. Спасибо Андрею Квасову. Изменения в nginx 0.7.31 19.01.2009 *) Изменение: теперь директива try_files проверяет только файлы, игнорируя каталоги. *) Добавление: директива fastcgi_split_path_info. *) Исправления в поддержке строки "Expect" в заголовке запроса. *) Исправления в гео-диапазонах. *) Исправление: при отсутствии ответа ngx_http_memcached_module возвращал в теле ответа строку "END" вместо 404-ой страницы по умолчанию; ошибка появилась в 0.7.18. Спасибо Максиму Дунину. *) Исправление: при проксировании SMTP nginx выдавал сообщение "250 2.0.0 OK" вместо "235 2.0.0 OK"; ошибка появилась в 0.7.22. Спасибо Максиму Дунину. Изменения в nginx 0.7.30 24.12.2008 *) Исправление: в рабочем процессе происходил segmentation fault, если в директивах fastcgi_pass или proxy_pass использовались переменные и имя хоста должно было резолвиться; ошибка появилась в 0.7.29. Изменения в nginx 0.7.29 24.12.2008 *) Исправление: директивы fastcgi_pass и proxy_pass не поддерживали переменные при использовании unix domain сокетов. *) Исправления в обработке подзапросов; ошибки появились в 0.7.25. *) Исправление: ответ "100 Continue" выдавался для запросов версии HTTP/1.0; Спасибо Максиму Дунину. *) Исправление: в выделении памяти в модуле ngx_http_gzip_filter_module под Cygwin. Изменения в nginx 0.7.28 22.12.2008 *) Изменение: в выделении памяти в модуле ngx_http_gzip_filter_module. *) Изменение: значения по умолчанию для директивы gzip_buffers изменены с 4 4k/8k на 32 4k или 16 8k. Изменения в nginx 0.7.27 15.12.2008 *) Добавление: директива try_files. *) Добавление: директива fastcgi_pass поддерживает переменные. *) Добавление: теперь директива geo может брать адрес из переменной. Спасибо Андрею Нигматулину. *) Добавление: теперь модификатор location'а можно указывать без пробела перед названием. *) Добавление: переменная $upstream_response_length. *) Исправление: теперь директива add_header не добавляет пустое значение. *) Исправление: при запросе файла нулевой длины nginx закрывал соединение, ничего не передав; ошибка появилась в 0.7.25. *) Исправление: метод MOVE не мог перемещать файл в несуществующий каталог. *) Исправление: если в сервере не был описан ни один именованный location, но такой location использовался в директиве error_page, то в рабочем процессе происходил segmentation fault. Спасибо Сергею Боченкову. Изменения в nginx 0.7.26 08.12.2008 *) Исправление: в обработке подзапросов; ошибка появилась в 0.7.25. Изменения в nginx 0.7.25 08.12.2008 *) Изменение: в обработке подзапросов. *) Изменение: теперь разрешаются POST'ы без строки "Content-Length" в заголовке запроса. *) Исправление: теперь директивы limit_req и limit_conn указывают причину запрета запроса. *) Исправление: в параметре delete директивы geo. Изменения в nginx 0.7.24 01.12.2008 *) Добавление: директива if_modified_since. *) Исправление: nginx не обрабатывал ответ FastCGI-сервера, если перед ответом сервер передавал много сообщений в stderr. *) Исправление: переменные "$cookie_..." не работали в SSI and в перловом модуле. Изменения в nginx 0.7.23 27.11.2008 *) Добавление: параметры delete и ranges в директиве geo. *) Добавление: ускорение загрузки geo-базы с большим числом значений. *) Добавление: уменьшение памяти, необходимой для загрузки geo-базы. Изменения в nginx 0.7.22 20.11.2008 *) Добавление: параметр none в директиве smtp_auth. Спасибо Максиму Дунину. *) Добавление: переменные "$cookie_...". *) Исправление: директива directio не работала с файловой системой XFS. *) Исправление: resolver не понимал большие DNS-ответы. Спасибо Zyb. Изменения в nginx 0.7.21 11.11.2008 *) Изменения в модуле ngx_http_limit_req_module. *) Добавление: поддержка EXSLT в модуле ngx_http_xslt_module. Спасибо Денису Латыпову. *) Изменение: совместимость с glibc 2.3. Спасибо Eric Benson и Максиму Дунину. *) Исправление: nginx не запускался на MacOSX 10.4 и более ранних; ошибка появилась в 0.7.6. Изменения в nginx 0.7.20 10.11.2008 *) Изменения в модуле ngx_http_gzip_filter_module. *) Добавление: модуль ngx_http_limit_req_module. *) Исправление: на платформах sparc и ppc рабочие процессы могли выходить по сигналу SIGBUS; ошибка появилась в 0.7.3. Спасибо Максиму Дунину. *) Исправление: директивы вида "proxy_pass http://host/some:uri" не работали; ошибка появилась в 0.7.12. *) Исправление: при использовании HTTPS запросы могли завершаться с ошибкой "bad write retry". *) Исправление: модуль ngx_http_secure_link_module не работал внутри location'ов с именами меньше 3 символов. *) Исправление: переменная $server_addr могла не иметь значения. Изменения в nginx 0.7.19 13.10.2008 *) Исправление: обновление номера версии. Изменения в nginx 0.7.18 13.10.2008 *) Изменение: директива underscores_in_headers; теперь nginx по умолчанию не разрешает подчёркивания в именах строк в заголовке запроса клиента. *) Добавление: модуль ngx_http_secure_link_module. *) Добавление: директива real_ip_header поддерживает любой заголовок. *) Добавление: директива log_subrequest. *) Добавление: переменная $realpath_root. *) Добавление: параметры http_502 и http_504 в директиве proxy_next_upstream. *) Исправление: параметр http_503 в директивах proxy_next_upstream или fastcgi_next_upstream не работал. *) Исправление: nginx мог выдавать строку "Transfer-Encoding: chunked" для запросов HEAD. *) Исправление: теперь accept-лимит зависит от числа worker_connections. Изменения в nginx 0.7.17 15.09.2008 *) Добавление: директива directio теперь работает на Linux. *) Добавление: переменная $pid. *) Исправление: оптимизация directio, появившаяся в 0.7.15, не работала при использовании open_file_cache. *) Исправление: access_log с переменными не работал на Linux; ошибка появилась в 0.7.7. *) Исправление: модуль ngx_http_charset_module не понимал название кодировки в кавычках, полученное от бэкенда. Изменения в nginx 0.7.16 08.09.2008 *) Исправление: nginx не собирался на 64-битных платформах; ошибка появилась в 0.7.15. Изменения в nginx 0.7.15 08.09.2008 *) Добавление: модуль ngx_http_random_index_module. *) Добавление: директива directio оптимизирована для запросов файлов, начинающихся с произвольной позиции. *) Добавление: директива directio при необходимости запрещает использование sendfile. *) Добавление: теперь nginx разрешает подчёркивания в именах строк в заголовке запроса клиента. Изменения в nginx 0.7.14 01.09.2008 *) Изменение: теперь директивы ssl_certificate и ssl_certificate_key не имеют значений по умолчанию. *) Добавление: директива listen поддерживает параметр ssl. *) Добавление: теперь при переконфигурации nginx учитывает изменение временной зоны на FreeBSD и Linux. *) Исправление: параметры директивы listen, такие как backlog, rcvbuf и прочие, не устанавливались, если сервером по умолчанию был не первый сервер. *) Исправление: при использовании в качестве аргументов части URI, выделенного с помощью директивы rewrite, эти аргументы не экранировались. *) Исправление: улучшения тестирования правильности конфигурационного файла. Изменения в nginx 0.7.13 26.08.2008 *) Исправление: nginx не собирался на Linux и Solaris; ошибка появилась в 0.7.12. Изменения в nginx 0.7.12 26.08.2008 *) Добавление: директива server_name поддерживает пустое имя "". *) Добавление: директива gzip_disable поддерживает специальную маску msie6. *) Исправление: при использовании параметра max_fails=0 в upstream'е с несколькими серверами рабочий процесс выходил по сигналу SIGFPE. Спасибо Максиму Дунину. *) Исправление: при перенаправлении запроса с помощью директивы error_page терялось тело запроса. *) Исправление: при перенаправлении запроса с методом HEAD с помощью директивы error_page возвращался полный ответ. *) Исправление: метод $r->header_in() не возвращал значения строк "Host", "User-Agent", и "Connection" из заголовка запроса; ошибка появилась в 0.7.0. Изменения в nginx 0.7.11 18.08.2008 *) Изменение: теперь ngx_http_charset_module по умолчанию не работает MIME-типом text/css. *) Добавление: теперь nginx возвращает код 405 для метода POST при запросе статического файла, только если файл существует. *) Добавление: директива proxy_ssl_session_reuse. *) Исправление: после перенаправления запроса с помощью "X-Accel-Redirect" директива proxy_pass без URI могла использовать оригинальный запрос. *) Исправление: если у каталога были права доступа только на поиск файлов и первый индексный файл отсутствовал, то nginx возвращал ошибку 500. *) Исправление: ошибок во вложенных location'ах; ошибки появились в 0.7.1. Изменения в nginx 0.7.10 13.08.2008 *) Исправление: ошибок в директивах addition_types, charset_types, gzip_types, ssi_types, sub_filter_types и xslt_types; ошибки появились в 0.7.9. *) Исправление: рекурсивной error_page для 500 ошибки. *) Исправление: теперь модуль ngx_http_realip_module устанавливает адрес не для всего keepalive соединения, а для каждого запроса по этому соединению. Изменения в nginx 0.7.9 12.08.2008 *) Изменение: теперь ngx_http_charset_module по умолчанию работает со следующими MIME-типами: text/html, text/css, text/xml, text/plain, text/vnd.wap.wml, application/x-javascript и application/rss+xml. *) Добавление: директивы charset_types и addition_types. *) Добавление: теперь директивы gzip_types, ssi_types и sub_filter_types используют хэш. *) Добавление: модуль ngx_cpp_test_module. *) Добавление: директива expires поддерживает суточное время. *) Добавление: улучшения и исправления в модуле ngx_http_xslt_module. Спасибо Денису Латыпову и Максиму Дунину. *) Исправление: директива log_not_found не работала при поиске индексных файлов. *) Исправление: HTTPS-соединения могли зависнуть, если использовались методы kqueue, epoll, rtsig или eventport; ошибка появилась в 0.7.7. *) Исправление: если в директивах server_name, valid_referers и map использовалась маска вида "*.domain.tld" и при этом полное имя вида "domain.tld" не было описано, то это имя попадало под маску; ошибка появилась в 0.3.18. Изменения в nginx 0.7.8 04.08.2008 *) Добавление: модуль ngx_http_xslt_module. *) Добавление: переменные "$arg_...". *) Добавление: поддержка directio в Solaris. Спасибо Ivan Debnar. *) Исправление: теперь, если FastCGI-сервер присылает строку "Location" в заголовке ответа без строки статуса, то nginx использует код статуса 302. Спасибо Максиму Дунину. Изменения в nginx 0.7.7 30.07.2008 *) Изменение: теперь ошибка EAGAIN при вызове connect() не считается временной. *) Изменение: значением переменной $ssl_client_cert теперь является сертификат, перед каждой строкой которого, кроме первой, вставляется символ табуляции; неизменённый сертификат доступен через переменную $ssl_client_raw_cert. *) Добавление: параметр ask директивы ssl_verify_client. *) Добавление: улучшения в обработке byte-range. Спасибо Максиму Дунину. *) Добавление: директива directio. Спасибо Jiang Hong. *) Добавление: поддержка sendfile() в MacOSX 10.5. *) Исправление: в MacOSX и Cygwin при проверке location'ов теперь делается сравнение без учёта регистра символов; однако, сравнение ограничено только однобайтными locale'ями. *) Исправление: соединения почтового прокси-сервера зависали в режиме SSL, если использовались методы select, poll или /dev/poll. *) Исправление: ошибки при использовании кодировки UTF-8 в ngx_http_autoindex_module. Изменения в nginx 0.7.6 07.07.2008 *) Исправление: теперь при использовании переменных в директиве access_log всегда проверяется существовании root'а для запроса. *) Исправление: модуль ngx_http_flv_module не поддерживал несколько значений в аргументах запроса. Изменения в nginx 0.7.5 01.07.2008 *) Исправления в поддержке переменных в директиве access_log; ошибки появились в 0.7.4. *) Исправление: nginx не собирался с параметром --without-http_gzip_module; ошибка появилась в 0.7.3. Спасибо Кириллу Коринскому. *) Исправление: при совместном использовании sub_filter и SSI ответы могли передаваться неверно. Изменения в nginx 0.7.4 30.06.2008 *) Добавление: директива access_log поддерживает переменные. *) Добавление: директива open_log_file_cache. *) Добавление: ключ -g. *) Добавление: поддержка строки "Expect" в заголовке запроса. *) Исправление: большие включения в SSI могли передавались не полностью. Изменения в nginx 0.7.3 23.06.2008 *) Изменение: MIME-тип для расширения rss изменён на "application/rss+xml". *) Изменение: теперь директива "gzip_vary on" выдаёт строку "Vary: Accept-Encoding" в заголовке ответа и для несжатых ответов. *) Добавление: теперь при использовании протокола "https://" в директиве rewrite автоматически делается редирект. *) Исправление: директива proxy_pass не работала с протоколом HTTPS; ошибка появилась в 0.6.9. Изменения в nginx 0.7.2 16.06.2008 *) Добавление: теперь nginx поддерживает шифры с обменом EDH-ключами. *) Добавление: директива ssl_dhparam. *) Добавление: переменная $ssl_client_cert. Спасибо Manlio Perillo. *) Исправление: после изменения URI с помощью директивы rewrite nginx не искал новый location; ошибка появилась в 0.7.1. Спасибо Максиму Дунину. *) Исправление: nginx не собирался без библиотеки PCRE; ошибка появилась в 0.7.1. *) Исправление: при редиректе запроса к каталогу с добавлением слэша nginx не добавлял аргументы из оригинального запроса. Изменения в nginx 0.7.1 26.05.2008 *) Изменение: теперь поиск location'а делается с помощью дерева. *) Изменение: директива optimize_server_names упразднена в связи с появлением директивы server_name_in_redirect. *) Изменение: некоторые давно устаревшие директивы больше не поддерживаются. *) Изменение: параметр "none" в директиве ssl_session_cache; теперь этот параметр используется по умолчанию. Спасибо Rob Mueller. *) Исправление: рабочие процессы могли не реагировать на сигналы переконфигурации и ротации логов. *) Исправление: nginx не собирался на последних Fedora 9 Linux. Спасибо Roxis. Изменения в nginx 0.7.0 19.05.2008 *) Изменение: теперь символы 0x00-0x1F, '"' и '\' в access_log записываются в виде \xXX. Спасибо Максиму Дунину. *) Изменение: теперь nginx разрешает несколько строк "Host" в заголовке запроса. *) Добавление: директива expires поддерживает флаг modified. *) Добавление: переменные $uid_got и $uid_set можно использовать на любой стадии обработки запроса. *) Добавление: переменная $hostname. Спасибо Андрею Нигматулину. *) Добавление: поддержка DESTDIR. Спасибо Todd A. Fisher и Andras Voroskoi. *) Исправление: при использовании keepalive на Linux в рабочем процессе мог произойти segmentation fault. Изменения в nginx 0.6.31 12.05.2008 *) Исправление: nginx не обрабатывал ответ FastCGI-сервера, если строка заголовка ответа была в конце записи FastCGI; ошибка появилась в 0.6.2. Спасибо Сергею Серову. *) Исправление: при удалении файла и использовании директивы open_file_cache_errors off в рабочем процессе мог произойти segmentation fault. Изменения в nginx 0.6.30 29.04.2008 *) Изменение: теперь, если маске, заданной в директиве include, не соответствует ни один файл, то nginx не выдаёт ошибку. *) Добавление: теперь время в директивах можно задавать без пробела, например, "1h50m". *) Исправление: утечек памяти, если директива ssl_verify_client имела значение on. Спасибо Chavelle Vincent. *) Исправление: директива sub_filter могла вставлять заменяемый текст в вывод. *) Исправление: директива error_page не воспринимала параметры в перенаправляемом URI. *) Исправление: теперь при сборке с Cygwin nginx всегда открывает файлы в бинарном режиме. *) Исправление: nginx не собирался под OpenBSD; ошибка появилась в 0.6.15. Изменения в nginx 0.6.29 18.03.2008 *) Добавление: модуль ngx_google_perftools_module. *) Исправление: модуль ngx_http_perl_module не собирался на 64-битных платформах; ошибка появилась в 0.6.27. Изменения в nginx 0.6.28 13.03.2008 *) Исправление: метод rtsig не собирался; ошибка появилась в 0.6.27. Изменения в nginx 0.6.27 12.03.2008 *) Изменение: теперь на Linux 2.6.18+ по умолчанию не собирается метод rtsig. *) Изменение: теперь при перенаправлении запроса в именованный location с помощью директивы error_page метод запроса не изменяется. *) Добавление: директивы resolver и resolver_timeout в SMTP прокси-сервере. *) Добавление: директива post_action поддерживает именованные location'ы. *) Исправление: при перенаправлении запроса из location'а c обработчиком proxy, FastCGI или memcached в именованный location со статическим обработчиком в рабочем процессе происходил segmentation fault. *) Исправление: браузеры не повторяли SSL handshake, если при первом handshake не оказалось правильного клиентского сертификата. Спасибо Александру Инюхину. *) Исправление: при перенаправлении ошибок 495-497 с помощью директивы error_page без изменения кода ошибки nginx пытался выделить очень много памяти. *) Исправление: утечки памяти в долгоживущих небуфферизированных соединениях. *) Исправление: утечки памяти в resolver'е. *) Исправление: при перенаправлении запроса из location'а c обработчиком proxy в другой location с обработчиком proxy в рабочем процессе происходил segmentation fault. *) Исправление: ошибки в кэшировании переменных $proxy_host и $proxy_port. Спасибо Сергею Боченкову. *) Исправление: директива proxy_pass с переменными использовала порт, описанной в другой директиве proxy_pass без переменных, но с таким же именем хоста. Спасибо Сергею Боченкову. *) Исправление: во время переконфигурации на некоторых 64-битном платформах в лог записывался alert "sendmsg() failed (9: Bad file descriptor)". *) Исправление: при повторном использовании в SSI пустого block'а в качестве заглушки в рабочем процессе происходил segmentation fault. *) Исправление: ошибки при копировании части URI, содержащего экранированные символы, в аргументы. Изменения в nginx 0.6.26 11.02.2008 *) Исправление: директивы proxy_store и fastcgi_store не проверяли длину ответа. *) Исправление: при использовании большого значения в директиве expires в рабочем процессе происходил segmentation fault. Спасибо Joaquin Cuenca Abela. *) Исправление: nginx неверно определял длину строки кэша на Pentium 4. Спасибо Геннадию Махомеду. *) Исправление: в проксированных подзапросах и подзапросах к FastCGI-серверу вместо метода GET использовался оригинальный метод клиента. *) Исправление: утечки сокетов в режиме HTTPS при использовании отложенного accept'а. Спасибо Ben Maurer. *) Исправление: nginx выдавал ошибочное сообщение "SSL_shutdown() failed (SSL: )"; ошибка появилась в 0.6.23. *) Исправление: при использовании HTTPS запросы могли завершаться с ошибкой "bad write retry"; ошибка появилась в 0.6.23. Изменения в nginx 0.6.25 08.01.2008 *) Изменение: вместо специального параметра "*" в директиве server_name теперь используется директива server_name_in_redirect. *) Изменение: в качестве основного имени в директиве server_name теперь можно использовать имена с масками и регулярными выражениями. *) Изменение: директива satisfy_any заменена директивой satisfy. *) Изменение: после переконфигурации старые рабочие процесс могли сильно нагружать процессор при запуске под Linux OpenVZ. *) Добавление: директива min_delete_depth. *) Исправление: методы COPY и MOVE не работали с одиночными файлами. *) Исправление: модуль ngx_http_gzip_static_module не позволял работать модулю ngx_http_dav_module; ошибка появилась в 0.6.23. *) Исправление: утечки сокетов в режиме HTTPS при использовании отложенного accept'а. Спасибо Ben Maurer. *) Исправление: nginx не собирался без библиотеки PCRE; ошибка появилась в 0.6.23. Изменения в nginx 0.6.24 27.12.2007 *) Исправление: при использовании HTTPS в рабочем процессе мог произойти segmentation fault; ошибка появилась в 0.6.23. Изменения в nginx 0.6.23 27.12.2007 *) Изменение: параметр "off" в директиве ssl_session_cache; теперь этот параметр используется по умолчанию. *) Изменение: директива open_file_cache_retest переименована в open_file_cache_valid. *) Добавление: директива open_file_cache_min_uses. *) Добавление: модуль ngx_http_gzip_static_module. *) Добавление: директива gzip_disable. *) Добавление: директиву memcached_pass можно использовать внутри блока if. *) Исправление: если внутри одного location'а использовались директивы "memcached_pass" и "if", то в рабочем процессе происходил segmentation fault. *) Исправление: если при использовании директивы satisfy_any on" были заданы директивы не всех модулей доступа, то заданные директивы не проверялись. *) Исправление: параметры, заданные регулярным выражением в директиве valid_referers, не наследовалась с предыдущего уровня. *) Исправление: директива post_action не работала, если запрос завершался с кодом 499. *) Исправление: оптимизация использования 16K буфера для SSL-соединения. Спасибо Ben Maurer. *) Исправление: STARTTLS в режиме SMTP не работал. Спасибо Олегу Мотиенко. *) Исправление: при использовании HTTPS запросы могли завершаться с ошибкой "bad write retry"; ошибка появилась в 0.5.13. Изменения в nginx 0.6.22 19.12.2007 *) Изменение: теперь все методы модуля ngx_http_perl_module возвращают значения, скопированные в память, выделенную perl'ом. *) Исправление: если nginx был собран с модулем ngx_http_perl_module, использовался perl до версии 5.8.6 и perl поддерживал потоки, то во время переконфигурации основной процесс аварийно выходил; ошибка появилась в 0.5.9. Спасибо Борису Жмурову. *) Исправление: в методы модуля ngx_http_perl_module могли передаваться неверные результаты выделения в регулярных выражениях. *) Исправление: если метод $r->has_request_body() вызывался для запроса, у которого небольшое тело запроса было уже полностью получено, то в рабочем процессе происходил segmentation fault. *) Исправление: large_client_header_buffers не освобождались перед переходом в состояние keep-alive. Спасибо Олександру Штепе. *) Исправление: в переменной $upstream_addr не записывался последний адрес; ошибка появилась в 0.6.18. *) Исправление: директива fastcgi_catch_stderr не возвращала ошибку; теперь она возвращает ошибку 502, которую можно направить на следующий сервер с помощью "fastcgi_next_upstream invalid_header". *) Исправление: при использовании директивы fastcgi_catch_stderr в основном процессе происходил segmentation fault; ошибка появилась в 0.6.10. Спасибо Manlio Perillo. Изменения в nginx 0.6.21 03.12.2007 *) Изменение: если в значениях переменных директивы proxy_pass используются только IP-адреса, то указывать resolver не нужно. *) Исправление: при использовании директивы proxy_pass c URI-частью в рабочем процессе мог произойти segmentation fault; ошибка появилась в 0.6.19. *) Исправление: если resolver использовался на платформах, не поддерживающих метод kqueue, то nginx выдавал alert "name is out of response". Спасибо Андрею Нигматулину. *) Исправление: При использовании переменной $server_protocol в FastCGI-параметрах и запросе, длина которого была близка к значению директивы client_header_buffer_size, nginx выдавал alert "fastcgi: the request record is too big". *) Исправление: при обычном запросе версии HTTP/0.9 к HTTPS серверу nginx возвращал обычный ответ. Изменения в nginx 0.6.20 28.11.2007 *) Исправление: при использовании директивы proxy_pass c URI-частью в рабочем процессе мог произойти segmentation fault; ошибка появилась в 0.6.19. Изменения в nginx 0.6.19 27.11.2007 *) Исправление: версия 0.6.18 не собиралась. Изменения в nginx 0.6.18 27.11.2007 *) Изменение: теперь модуль ngx_http_userid_module в поле куки с номером процесса добавляет микросекунды на время старта. *) Изменение: в error_log теперь записывается полная строка запроса вместо только URI. *) Добавление: директива proxy_pass поддерживает переменные. *) Добавление: директивы resolver и resolver_timeout. *) Добавление: теперь директива "add_header last-modified ''" удаляет в заголовке ответа строку "Last-Modified". *) Исправление: директива limit_rate не позволяла передавать на полной скорости, даже если был указан очень большой лимит. Изменения в nginx 0.6.17 15.11.2007 *) Добавление: поддержка строки "If-Range" в заголовке запроса. Спасибо Александру Инюхину. *) Исправление: при использовании директивы msie_refresh повторно экранировались уже экранированные символы; ошибка появилась в 0.6.4. *) Исправление: директива autoindex не работала при использовании "alias /". *) Исправление: при использовании подзапросов в рабочем процессе мог произойти segmentation fault. *) Исправление: при использовании SSL и gzip большие ответы могли передаваться не полностью. *) Исправление: если ответ проксированного сервера был версии HTTP/0.9, то переменная $status была равна 0. Изменения в nginx 0.6.16 29.10.2007 *) Изменение: теперь на Linux используется uname(2) вместо procfs. Спасибо Илье Новикову. *) Исправление: если в директиве error_page использовался символ "?", то он экранировался при проксировании запроса; ошибка появилась в 0.6.11. *) Исправление: совместимость с mget. Изменения в nginx 0.6.15 22.10.2007 *) Добавление: совместимость с Cygwin. Спасибо Владимиру Кутакову. *) Добавление: директива merge_slashes. *) Добавление: директива gzip_vary. *) Добавление: директива server_tokens. *) Исправление: nginx не раскодировал URI в команде SSI include. *) Исправление: при использовании переменной в директивах charset или source_charset на старте или во время переконфигурации происходил segmentation fault, *) Исправление: nginx возвращал ошибку 400 на запросы вида "GET http://www.domain.com HTTP/1.0". Спасибо James Oakley. *) Исправление: после перенаправления запроса с телом запроса с помощью директивы error_page nginx пытался снова прочитать тело запроса; ошибка появилась в 0.6.7. *) Исправление: в рабочем процессе происходил segmentation fault, если у сервера, обрабатывающему запрос, не был явно определён server_name; ошибка появилась в 0.6.7. Изменения в nginx 0.6.14 15.10.2007 *) Изменение: теперь по умолчанию команда SSI echo использует кодирование entity. *) Добавление: параметр encoding в команде SSI echo. *) Добавление: директиву access_log можно использовать внутри блока limit_except. *) Исправление: если все сервера апстрима оказывались недоступными, то до восстановления работоспособности у всех серверов вес становился равным одному; ошибка появилась в 0.6.6. *) Исправление: при использовании переменных $date_local и $date_gmt вне модуля ngx_http_ssi_filter_module в рабочем процессе происходил segmentation fault. *) Исправление: при использовании включённом отладочном логе в рабочем процессе мог произойти segmentation fault. Спасибо Андрею Нигматулину. *) Исправление: ngx_http_memcached_module не устанавливал $upstream_response_time. Спасибо Максиму Дунину. *) Исправление: рабочий процесс мог зациклиться при использовании memcached. *) Исправление: nginx распознавал параметры "close" и "keep-alive" в строке "Connection" в заголовке запроса только, если они были в нижнем регистре; ошибка появилась в 0.6.11. *) Исправление: sub_filter не работал с пустой строкой замены. *) Исправление: в парсинге sub_filter. Изменения в nginx 0.6.13 24.09.2007 *) Исправление: nginx не закрывал файл каталога для запроса HEAD, если использовался autoindex Спасибо Arkadiusz Patyk. Изменения в nginx 0.6.12 21.09.2007 *) Изменение: почтовый прокси-сервер разделён на три модуля: pop3, imap и smtp. *) Добавление: параметры конфигурации --without-mail_pop3_module, --without-mail_imap_module и --without-mail_smtp_module. *) Добавление: директивы smtp_greeting_delay и smtp_client_buffer модуля ngx_mail_smtp_module. *) Исправление: wildcard в конце имени сервера не работали; ошибка появилась в 0.6.9. *) Исправление: при использовании разделяемой библиотеки PCRE, расположенной в нестандартном месте, nginx не запускался на Solaris. *) Исправление: директивы proxy_hide_header и fastcgi_hide_header не скрывали строки заголовка ответа с именем больше 32 символов. Спасибо Manlio Perillo. Изменения в nginx 0.6.11 11.09.2007 *) Исправление: счётчик активных соединений всегда рос при использовании почтового прокси-сервера. *) Исправление: если бэкенд возвращал только заголовок ответа при небуферизированном проксировании, то nginx закрывал соединение с бэкендом по таймауту. *) Исправление: nginx не поддерживал несколько строк "Connection" в заголовке запроса. *) Исправление: если в сервере апстрима был задан max_fails, то после первой же неудачной попытки вес сервера навсегда становился равным одному; ошибка появилась в 0.6.6. Изменения в nginx 0.6.10 03.09.2007 *) Добавление: директивы open_file_cache, open_file_cache_retest и open_file_cache_errors. *) Исправление: утечки сокетов; ошибка появилась в 0.6.7. *) Исправление: В строку заголовка ответа "Content-Type", указанную в методе $r->send_http_header(), не добавлялась кодировка, указанная в директиве charset. *) Исправление: при использовании метода /dev/poll в рабочем процессе мог произойти segmentation fault. Изменения в nginx 0.6.9 28.08.2007 *) Исправление: рабочий процесс мог зациклиться при использовании протокола HTTPS; ошибка появилась в 0.6.7. *) Исправление: если сервер слушал на двух адресах или портах, то nginx не запускался при использовании wildcard в конце имени сервера. *) Исправление: директива ip_hash могла неверно помечать сервера как нерабочие. *) Исправление: nginx не собирался на amd64; ошибка появилась в 0.6.8. Изменения в nginx 0.6.8 20.08.2007 *) Изменение: теперь nginx пытается установить директивы worker_priority, worker_rlimit_nofile, worker_rlimit_core, worker_rlimit_sigpending без привилегий root'а. *) Изменение: теперь nginx экранирует символы пробела и "%" при передаче запроса серверу аутентификации почтового прокси-сервера. *) Изменение: теперь nginx экранирует символ "%" в переменной $memcached_key. *) Исправление: при указании относительного пути к конфигурационному файлу в качестве параметра ключа -c nginx определял путь относительно конфигурационного префикса; ошибка появилась в 0.6.6. *) Исправление: nginx не работал на FreeBSD/sparc64. Изменения в nginx 0.6.7 15.08.2007 *) Изменение: теперь пути, указанные в директивах include, auth_basic_user_file, perl_modules, ssl_certificate, ssl_certificate_key и ssl_client_certificate, определяются относительно каталога конфигурационного файла nginx.conf, а не относительно префикса. *) Изменение: параметр --sysconfdir=PATH в configure упразднён. *) Изменение: для обновления на лету версий 0.1.x создан специальный сценарий make upgrade1. *) Добавление: директивы server_name и valid_referers поддерживают регулярные выражения. *) Добавление: директива server в блоке upstream поддерживает параметр backup. *) Добавление: модуль ngx_http_perl_module поддерживает метод $r->discard_request_body. *) Добавление: директива "add_header Last-Modified ..." меняет строку "Last-Modified" в заголовке ответа. *) Исправление: если на запрос с телом возвращался ответ с кодом HTTP отличным от 200, и после этого запроса соединение переходило в состояние keep-alive, то на следующий запрос nginx возвращал 400. *) Исправление: если в директиве auth_http был задан неправильный адрес, то в рабочем процессе происходил segmentation fault. *) Исправление: теперь по умолчанию nginx использует значение 511 для listen backlog на всех платформах, кроме FreeBSD. Спасибо Jiang Hong. *) Исправление: рабочий процесс мог зациклиться, если server в блоке upstream был помечен как down; ошибка появилась в 0.6.6. *) Исправление: sendfilev() в Solaris теперь не используется при передаче тела запроса FastCGI-серверу через unix domain сокет. Изменения в nginx 0.6.6 30.07.2007 *) Добавление: параметр --sysconfdir=PATH в configure. *) Добавление: именованные location'ы. *) Добавление: переменную $args можно устанавливать с помощью set. *) Добавление: переменная $is_args. *) Исправление: равномерное распределение запросов к апстримам с большими весами. *) Исправление: если клиент в почтовом прокси-сервере закрывал соединение, то nginx мог не закрывать соединение с бэкендом. *) Исправление: при использовании одного хоста в качестве бэкендов для протоколов HTTP и HTTPS без явного указания портов, nginx использовал только один порт - 80 или 443. *) Исправление: nginx не собирался на Solaris/amd64 Sun Studio 11 и более ранними версиями; ошибка появилась в 0.6.4. Изменения в nginx 0.6.5 23.07.2007 *) Добавление: переменная $nginx_version. Спасибо Николаю Гречуху. *) Добавление: почтовый прокси-сервер поддерживает AUTHENTICATE в режиме IMAP. Спасибо Максиму Дунину. *) Добавление: почтовый прокси-сервер поддерживает STARTTLS в режиме SMTP. Спасибо Максиму Дунину. *) Исправление: теперь nginx экранирует пробел в переменной $memcached_key. *) Исправление: nginx неправильно собирался Sun Studio на Solaris/amd64. Спасибо Jiang Hong. *) Исправление: незначительных потенциальных ошибок. Спасибо Coverity's Scan. Изменения в nginx 0.6.4 17.07.2007 *) Безопасность: при использовании директивы msie_refresh был возможен XSS. Спасибо Максиму Богуку. *) Изменение: директивы proxy_store и fastcgi_store изменены. *) Добавление: директивы proxy_store_access и fastcgi_store_access. *) Исправление: nginx не работал на Solaris/sparc64, если был собран Sun Studio. Спасибо Андрею Нигматулину. *) Изменение: обход ошибки в Sun Studio 12. Спасибо Jiang Hong. Изменения в nginx 0.6.3 12.07.2007 *) Добавление: директивы proxy_store и fastcgi_store. *) Исправление: при использовании директивы auth_http_header в рабочем процессе мог произойти segmentation fault. Спасибо Максиму Дунину. *) Исправление: если использовался метод аутентификации CRAM-MD5, но он не был разрешён, то в рабочем процессе происходил segmentation fault. *) Исправление: при использовании протокола HTTPS в директиве proxy_pass в рабочем процессе мог произойти segmentation fault. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовался метод eventport. *) Исправление: директивы proxy_ignore_client_abort и fastcgi_ignore_client_abort не работали; ошибка появилась в 0.5.13. Изменения в nginx 0.6.2 09.07.2007 *) Исправление: если заголовок ответа был разделён в FastCGI-записях, то nginx передавал клиенту мусор в таких заголовках. Изменения в nginx 0.6.1 17.06.2007 *) Исправление: в парсинге SSI. *) Исправление: при использовании удалённого подзапроса в SSI последующий подзапрос локального файла мог отдаваться клиенту в неверном порядке. *) Исправление: большие включения в SSI, сохранённые во временные файлы, передавались не полностью. *) Исправление: значение perl'овой переменной $$ модуля ngx_http_perl_module было равно номеру главного процесса. Изменения в nginx 0.6.0 14.06.2007 *) Добавление: директивы "server_name", "map", and "valid_referers" поддерживают маски вида "www.example.*". Изменения в nginx 0.5.25 11.06.2007 *) Исправление: nginx не собирался с параметром --without-http_rewrite_module; ошибка появилась в 0.5.24. Изменения в nginx 0.5.24 06.06.2007 *) Безопасность: директива ssl_verify_client не работала, если запрос выполнялся по протоколу HTTP/0.9. *) Исправление: при использовании сжатия часть ответа могла передаваться несжатой; ошибка появилась в 0.5.23. Изменения в nginx 0.5.23 04.06.2007 *) Добавление: модуль ngx_http_ssl_module поддерживает расширение TLS Server Name Indication. *) Добавление: директива fastcgi_catch_stderr. Спасибо Николаю Гречуху, проект OWOX. *) Исправление: на Линуксе в основном процессе происходил segmentation fault, если два виртуальных сервера должны bind()ится к пересекающимся портам. *) Исправление: если nginx был собран с модулем ngx_http_perl_module и perl поддерживал потоки, то во время второй переконфигурации выдавались ошибки "panic: MUTEX_LOCK" и "perl_parse() failed". *) Исправление: в использовании протокола HTTPS в директиве proxy_pass. Изменения в nginx 0.5.22 29.05.2007 *) Исправление: большое тело запроса могло не передаваться бэкенду; ошибка появилась в 0.5.21. Изменения в nginx 0.5.21 28.05.2007 *) Исправление: если внутри сервера описано больше примерно десяти location'ов, то location'ы, заданные с помощью регулярного выражения, могли выполняться не в том, порядке, в каком они описаны. *) Исправление: на 64-битной платформе рабочий процесс мог зациклиться, если 33-тий по счёту или последующий бэкенд упал. Спасибо Антону Поварову. *) Исправление: при использовании библиотеки PCRE на Solaris/sparc64 мог произойти bus error. Спасибо Андрею Нигматулину. *) Исправление: в использовании протокола HTTPS в директиве proxy_pass. Изменения в nginx 0.5.20 07.05.2007 *) Добавление: директива sendfile_max_chunk. *) Добавление: переменные "$http_...", "$sent_http_..." и "$upstream_http_..." можно менять директивой set. *) Исправление: при использовании SSI-команды 'if expr="$var = /"' в рабочем процессе мог произойти segmentation fault. *) Исправление: завершающая строка multipart range ответа передавалась неверно. Спасибо Evan Miller. *) Исправление: nginx не работал на Solaris/sparc64, если был собран Sun Studio. Спасибо Андрею Нигматулину. *) Исправление: модуль ngx_http_perl_module не собирался make в Solaris. Спасибо Андрею Нигматулину. Изменения в nginx 0.5.19 24.04.2007 *) Изменение: значение переменной $request_time теперь записывается с точностью до миллисекунд. *) Изменение: метод $r->rflush в модуле ngx_http_perl_module переименован в $r->flush. *) Добавление: переменная $upstream_addr. *) Добавление: директивы proxy_headers_hash_max_size и proxy_headers_hash_bucket_size. Спасибо Володымыру Костырко. *) Исправление: при использовании sendfile и limit_rate на 64-битных платформах нельзя было передавать файлы больше 2G. *) Исправление: при использовании sendfile на 64-битном Linux нельзя было передавать файлы больше 2G. Изменения в nginx 0.5.18 19.04.2007 *) Добавление: модуль ngx_http_sub_filter_module. *) Добавление: переменные "$upstream_http_...". *) Добавление: теперь переменные $upstream_status и $upstream_response_time содержат данные о всех обращениях к апстримам, сделанным до X-Accel-Redirect. *) Исправление: если nginx был собран с модулем ngx_http_perl_module и perl не поддерживал multiplicity, то после первой переконфигурации и после получения любого сигнала в основном процессе происходил segmentation fault; ошибка появилась в 0.5.9. *) Исправление: если perl не поддерживал multiplicity, то после переконфигурации перловый код не работал; ошибка появилась в 0.3.38. Изменения в nginx 0.5.17 02.04.2007 *) Изменение: теперь nginx для метода TRACE всегда возвращает код 405. *) Добавление: теперь nginx поддерживает директиву include внутри блока types. *) Исправление: использование переменной $document_root в директиве root и alias запрещено: оно вызывало рекурсивное переполнение стека. *) Исправление: в использовании протокола HTTPS в директиве proxy_pass. *) Исправление: в некоторых случаях некэшируемые переменные (такие, как $uri) возвращали старое закэшированное значение. Изменения в nginx 0.5.16 26.03.2007 *) Исправление: в качестве ключа для хэша в директиве ip_hash не использовалась сеть класса С. Спасибо Павлу Ярковому. *) Исправление: если в строке "Content-Type" в заголовке ответа бэкенда был указан charset и строка завершалась символом ";", то в рабочем процессе мог произойти segmentation fault; ошибка появилась в 0.3.50. *) Исправление: ошибки "[alert] zero size buf" при работе с FastCGI-сервером, если тело запроса, записанное во временный файл, было кратно 32K. *) Исправление: nginx не собирался на Solaris без параметра --with-debug; ошибка появилась в 0.5.15. Изменения в nginx 0.5.15 19.03.2007 *) Добавление: почтовый прокси-сервер поддерживает аутентифицированное SMTP-проксирование и директивы smtp_auth, smtp_capabilities и xclient. Спасибо Антону Южанинову и Максиму Дунину. *) Добавление: теперь keep-alive соединения закрываются сразу же по получении сигнала переконфигурации. *) Изменение: директивы imap и auth переименованы соответственно в mail и pop3_auth. *) Исправление: если использовался метод аутентификации CRAM-MD5 и не был разрешён метод APOP, то в рабочем процессе происходил segmentation fault. *) Исправление: при использовании директивы starttls only в протоколе POP3 nginx разрешал аутентификацию без перехода в режим SSL. *) Исправление: рабочие процессы не выходили после переконфигурации и не переоткрывали логи, если использовался метод eventport. *) Исправление: при использовании директивы ip_hash рабочий процесс мог зациклиться. *) Исправление: теперь nginx не пишет в лог некоторые alert'ы, если используются методы eventport или /dev/poll. Изменения в nginx 0.5.14 23.02.2007 *) Исправление: nginx игнорировал лишние закрывающие скобки "}" в конце конфигурационного файла. Изменения в nginx 0.5.13 19.02.2007 *) Добавление: методы COPY и MOVE. *) Исправление: модуль ngx_http_realip_module устанавливал мусор для запросов, переданных по keep-alive соединению. *) Исправление: nginx не работал на 64-битном big-endian Linux. Спасибо Андрею Нигматулину. *) Исправление: при получении слишком длинной команды IMAP/POP3-прокси теперь сразу закрывает соединение, а не по таймауту. *) Исправление: если при использовании метода epoll клиент закрывал преждевременно соединение со своей стороны, то nginx закрывал это соединение только по истечении таймаута на передачу. *) Исправление: nginx не собирался на платформах, отличных от i386, amd64, sparc и ppc; ошибка появилась в 0.5.8. Изменения в nginx 0.5.12 12.02.2007 *) Исправление: nginx не собирался на платформах, отличных от i386, amd64, sparc и ppc; ошибка появилась в 0.5.8. *) Исправление: при использовании временных файлов в время работы с FastCGI-сервером в рабочем процессе мог произойти segmentation fault; ошибка появилась в 0.5.8. *) Исправление: если переменная $fastcgi_script_name записывалась в лог, то в рабочем процессе мог произойти segmentation fault. *) Исправление: ngx_http_perl_module не собирался на Solaris. Изменения в nginx 0.5.11 05.02.2007 *) Добавление: теперь configure определяет библиотеку PCRE в MacPorts. Спасибо Chris McGrath. *) Исправление: ответ был неверным, если запрашивалось несколько диапазонов; ошибка появилась в 0.5.6. *) Исправление: директива create_full_put_path не могла создавать промежуточные каталоги, если не была установлена директива dav_access. Спасибо Evan Miller. *) Исправление: вместо кодов ошибок "400" и "408" в access_log мог записываться код "0". *) Исправление: при сборке с оптимизацией -O2 в рабочем процессе мог произойти segmentation fault. Изменения в nginx 0.5.10 26.01.2007 *) Исправление: во время обновления исполняемого файла новый процесс не наследовал слушающие сокеты; ошибка появилась в 0.5.9. *) Исправление: при сборке с оптимизацией -O2 в рабочем процессе мог произойти segmentation fault; ошибка появилась в 0.5.1. Изменения в nginx 0.5.9 25.01.2007 *) Изменение: модуль ngx_http_memcached_module теперь в качестве ключа использует значение переменной $memcached_key. *) Добавление: переменная $memcached_key. *) Добавление: параметр clean в директиве client_body_in_file_only. *) Добавление: директива env. *) Добавление: директива sendfile работает внутри блока if. *) Добавление: теперь при ошибке записи в access_log nginx записывает сообщение в error_log, но не чаще одного раза в минуту. *) Исправление: директива "access_log off" не всегда запрещала запись в лог. Изменения в nginx 0.5.8 19.01.2007 *) Исправление: если использовалась директива "client_body_in_file_only on" и тело запроса было небольшое, то мог произойти segmentation fault. *) Исправление: происходил segmentation fault, если использовались директивы "client_body_in_file_only on" и "proxy_pass_request_body off" или "fastcgi_pass_request_body off", и делался переход к следующему бэкенду. *) Исправление: если при использовании директивы "proxy_buffering off" соединение с клиентом было неактивно, то оно закрывалось по таймауту, заданному директивой send_timeout; ошибка появилась в 0.4.7. *) Исправление: если при использовании метода epoll клиент закрывал преждевременно соединение со своей стороны, то nginx закрывал это соединение только по истечении таймаута на передачу. *) Исправление: ошибки "[alert] zero size buf" при работе с FastCGI-сервером. *) Исправление ошибок в директиве limit_zone. Изменения в nginx 0.5.7 15.01.2007 *) Добавление: оптимизация использования памяти в ssl_session_cache. *) Исправление ошибок в директивах ssl_session_cache и limit_zone. *) Исправление: на старте или во время переконфигурации происходил segmentation fault, если директивы ssl_session_cache или limit_zone использовались на 64-битных платформах. *) Исправление: при использовании директив add_before_body или add_after_body происходил segmentation fault, если в заголовке ответа нет строки "Content-Type". *) Исправление: библиотека OpenSSL всегда собиралась с поддержкой потоков. Спасибо Дену Иванову. *) Исправление: совместимость библиотеки PCRE-6.5+ и компилятора icc. Изменения в nginx 0.5.6 09.01.2007 *) Изменение: теперь модуль ngx_http_index_module игнорирует все методы, кроме GET, HEAD и POST. *) Добавление: модуль ngx_http_limit_zone_module. *) Добавление: переменная $binary_remote_addr. *) Добавление: директивы ssl_session_cache модулей ngx_http_ssl_module и ngx_imap_ssl_module. *) Добавление: метод DELETE поддерживает рекурсивное удаление. *) Исправление: при использовании $r->sendfile() byte-ranges передавались неверно. Изменения в nginx 0.5.5 24.12.2006 *) Изменение: ключ -v больше не выводит информацию о компиляторе. *) Добавление: ключ -V. *) Добавление: директива worker_rlimit_core поддерживает указание размера в K, M и G. *) Исправление: модуль nginx.pm теперь может устанавливаться непривилегированным пользователем. *) Исправление: при использовании методов $r->request_body или $r->request_body_file мог произойти segmentation fault. *) Исправление: ошибок, специфичных для платформы ppc. Изменения в nginx 0.5.4 15.12.2006 *) Добавление: директиву perl можно использовать внутри блока limit_except. *) Исправление: модуль ngx_http_dav_module требовал строку "Date" в заголовке запроса для метода DELETE. *) Исправление: при использовании одного параметра в директиве dav_access nginx мог сообщить об ошибке в конфигурации. *) Исправление: при использовании переменной $host мог произойти segmentation fault; ошибка появилась в 0.4.14. Изменения в nginx 0.5.3 13.12.2006 *) Добавление: модуль ngx_http_perl_module поддерживает методы $r->status, $r->log_error и $r->sleep. *) Добавление: метод $r->variable поддерживает переменные, неописанные в конфигурации nginx'а. *) Исправление: метод $r->has_request_body не работал. Изменения в nginx 0.5.2 11.12.2006 *) Исправление: если в директивах proxy_pass использовалось имя, указанное в upstream, то nginx пытался найти IP-адрес этого имени; ошибка появилась в 0.5.1. Изменения в nginx 0.5.1 11.12.2006 *) Исправление: директива post_action могла не работать после неудачного завершения запроса. *) Изменение: обход ошибки в Eudora для Mac; ошибка появилась в 0.4.11. Спасибо Bron Gondwana. *) Исправление: при указании в директиве fastcgi_pass имени описанного upstream'а выдавалось сообщение "no port in upstream"; ошибка появилась в 0.5.0. *) Исправление: если в директивах proxy_pass и fastcgi_pass использовались одинаковых имена серверов, но с разными портами, то эти директивы использовали первый описанный порт; ошибка появилась в 0.5.0. *) Исправление: если в директивах proxy_pass и fastcgi_pass использовались unix domain сокеты, то эти директивы использовали первый описанный сокет; ошибка появилась в 0.5.0. *) Исправление: ngx_http_auth_basic_module игнорировал пользователя, если он был указан в последней строке файла паролей и после пароля не было перевода строки, возврата каретки или символа ":". *) Исправление: переменная $upstream_response_time могла быть равна "0.000", хотя время обработки было больше 1 миллисекунды. Изменения в nginx 0.5.0 04.12.2006 *) Изменение: параметры в виде "%name" в директиве log_format больше не поддерживаются. *) Изменение: директивы proxy_upstream_max_fails, proxy_upstream_fail_timeout, fastcgi_upstream_max_fails, и fastcgi_upstream_fail_timeout, memcached_upstream_max_fails и memcached_upstream_fail_timeout больше не поддерживаются. *) Добавление: директива server в блоке upstream поддерживает параметры max_fails, fail_timeout и down. *) Добавление: директива ip_hash в блоке upstream. *) Добавление: статус WAIT в строке "Auth-Status" в заголовке ответа сервера аутентификации IMAP/POP3 прокси. *) Исправление: nginx не собирался на 64-битных платформах; ошибка появилась в 0.4.14. Изменения в nginx 0.4.14 27.11.2006 *) Добавление: директива proxy_pass_error_message в IMAP/POP3 прокси. *) Добавление: теперь configure определяет библиотеку PCRE на FreeBSD, Linux и NetBSD. *) Исправление: ngx_http_perl_module не работал с перлом, собранным с поддержкой потоков; ошибка появилась в 0.3.38. *) Исправление: ngx_http_perl_module не работал корректно, если перл вызывался рекурсивно. *) Исправление: nginx игнорировал имя сервера в строке запроса. *) Исправление: если FastCGI сервер передавал много в stderr, то рабочий процесс мог зациклиться. *) Исправление: при изменении системного времени переменная $upstream_response_time могла быть отрицательной. *) Исправление: при использовании POP3 серверу аутентификации IMAP/POP3 прокси не передавался параметр Auth-Login-Attempt. *) Исправление: при ошибке соединения с сервером аутентификации IMAP/POP3 прокси мог произойти segmentation fault. Изменения в nginx 0.4.13 15.11.2006 *) Добавление: директиву proxy_pass можно использовать внутри блока limit_except. *) Добавление: директива limit_except поддерживает все WebDAV методы. *) Исправление: при использовании директивы add_before_body без директивы add_after_body ответ передавался не полностью. *) Исправление: большое тело запроса не принималось, если использовались метод epoll и deferred accept(). *) Исправление: для ответов модуля ngx_http_autoindex_module не выставлялась кодировка; ошибка появилась в 0.3.50. *) Исправление: ошибки "[alert] zero size buf" при работе с FastCGI-сервером; *) Исправление: параметр конфигурации --group= игнорировался. Спасибо Thomas Moschny. *) Исправление: 50-й подзапрос в SSI ответе не работал; ошибка появилась в 0.3.50. Изменения в nginx 0.4.12 31.10.2006 *) Добавление: модуль ngx_http_perl_module поддерживает метод $r->variable. *) Исправление: при включении в ответ большого статического файла с помощью SSI ответ мог передаваться не полностью. *) Исправление: nginx не убирал "#fragment" в URI. Изменения в nginx 0.4.11 25.10.2006 *) Добавление: POP3 прокси поддерживает AUTH LOGIN PLAIN и CRAM-MD5. *) Добавление: модуль ngx_http_perl_module поддерживает метод $r->allow_ranges. *) Исправление: при включённой поддержке команды APOP в POP3 прокси могли не работать команды USER/PASS; ошибка появилась в 0.4.10. Изменения в nginx 0.4.10 23.10.2006 *) Добавление: POP3 прокси поддерживает APOP. *) Исправление: при использовании методов select, poll и /dev/poll во время ожидания ответа от сервера аутентификации IMAP/POP3 прокси нагружал процессор. *) Исправление: при использовании переменной $server_addr в директиве map мог произойти segmentation fault. *) Исправление: модуль ngx_http_flv_module не поддерживал byte ranges для полных ответов; ошибка появилась в 0.4.7. *) Исправление: nginx не собирался на Debian amd64; ошибка появилась в 0.4.9. Изменения в nginx 0.4.9 13.10.2006 *) Добавление: параметр set в команде SSI include. *) Добавление: модуль ngx_http_perl_module теперь проверяет версию модуля nginx.pm. Изменения в nginx 0.4.8 11.10.2006 *) Исправление: если до команды SSI include с параметром wait выполнялась ещё одна команда SSI include, то параметр wait мог не работать. *) Исправление: модуль ngx_http_flv_module добавлял FLV-заголовок для полных ответов. Спасибо Алексею Ковырину. Изменения в nginx 0.4.7 10.10.2006 *) Добавление: модуль ngx_http_flv_module. *) Добавление: переменная $request_body_file. *) Добавление: директивы charset и source_charset поддерживают переменные. *) Исправление: если до команды SSI include с параметром wait выполнялась ещё одна команда SSI include, то параметр wait мог не работать. *) Исправление: при использовании директивы "proxy_buffering off" или при работе с memcached соединения могли не закрываться по таймауту. *) Исправление: nginx не запускался на 64-битных платформах, отличных от amd64, sparc64 и ppc64. Изменения в nginx 0.4.6 06.10.2006 *) Исправление: nginx не запускался на 64-битных платформах, отличных от amd64, sparc64 и ppc64. *) Исправление: при запросе версии HTTP/1.1 nginx передавал ответ chunk'ами, если длина ответа в методе $r->headers_out("Content-Length", ...) была задана текстовой строкой. *) Исправление: после перенаправления ошибки с помощью директивы error_page любая директива модуля ngx_http_rewrite_module возвращала эту ошибку; ошибка появилась в 0.4.4. Изменения в nginx 0.4.5 02.10.2006 *) Исправление: nginx не собирался на Linux и Solaris; ошибка появилась в 0.4.4. Изменения в nginx 0.4.4 02.10.2006 *) Добавление: переменная $scheme. *) Добавление: директива expires поддерживает параметр max. *) Добавление: директива include поддерживает маску "*". Спасибо Jonathan Dance. *) Исправление: директива return всегда изменяла код ответа, перенаправленного директивой error_page. *) Исправление: происходил segmentation fault, если в методе PUT передавалось тело нулевой длины. *) Исправление: при использовании переменных в директиве proxy_redirect редирект изменялся неверно. Изменения в nginx 0.4.3 26.09.2006 *) Изменение: ошибку 499 теперь нельзя перенаправить с помощью директивы error_page. *) Добавление: поддержка Solaris 10 event ports. *) Добавление: модуль ngx_http_browser_module. *) Исправление: при перенаправлении ошибки 400 проксированному серверу помощью директивы error_page мог произойти segmentation fault. *) Исправление: происходил segmentation fault, если в директиве proxy_pass использовался unix domain сокет; ошибка появилась в 0.3.47. *) Исправление: SSI не работал с ответами memcached и небуферизированными проксированными ответами. *) Изменение: обход ошибки PAUSE hardware capability в Sun Studio. Изменения в nginx 0.4.2 14.09.2006 *) Исправление: убрана поддержка флага O_NOATIME на Linux; ошибка появилась в 0.4.1. Изменения в nginx 0.4.1 14.09.2006 *) Исправление: совместимость с DragonFlyBSD. Спасибо Павлу Назарову. *) Изменение: обход ошибки в sendfile() в 64-битном Linux при передаче файлов больше 2G. *) Добавление: теперь на Linux nginx для статических запросов использует флаг O_NOATIME. Спасибо Yusuf Goolamabbas. Изменения в nginx 0.4.0 30.08.2006 *) Изменение во внутреннем API: инициализация модулей HTTP перенесена из фазы init module в фазу HTTP postconfiguration. *) Изменение: теперь тело запроса в модуле ngx_http_perl_module не считывается заранее: нужно явно инициировать чтение с помощью метода $r->has_request_body. *) Добавление: модуль ngx_http_perl_module поддерживает код возврата DECLINED. *) Добавление: модуль ngx_http_dav_module поддерживает входящую строку заголовка "Date" для метода PUT. *) Добавление: директива ssi работает внутри блока if. *) Исправление: происходил segmentation fault, если в директиве index использовалась переменные и при этом первое имя индексного файла было без переменных; ошибка появилась в 0.1.29. Изменения в nginx 0.3.61 28.08.2006 *) Изменение: директива tcp_nodelay теперь по умолчанию включена. *) Добавление: директива msie_refresh. *) Добавление: директива recursive_error_pages. *) Исправление: директива rewrite возвращала неправильный редирект, если редирект включал в себя выделенные закодированные символы из оригинального URI. Изменения в nginx 0.3.60 18.08.2006 *) Исправление: во время перенаправления ошибки рабочий процесс мог зациклиться; ошибка появилась в 0.3.59. Изменения в nginx 0.3.59 16.08.2006 *) Добавление: теперь можно делать несколько перенаправлений через директиву error_page. *) Исправление: директива dav_access не поддерживала три параметра. *) Исправление: директива error_page не изменяла строку "Content-Type" после перенаправления с помощью "X-Accel-Redirect"; ошибка появилась в 0.3.58. Изменения в nginx 0.3.58 14.08.2006 *) Добавление: директива error_page поддерживает переменные. *) Изменение: теперь на Linux используется интерфейс procfs вместо sysctl. *) Изменение: теперь при использовании "X-Accel-Redirect" строка "Content-Type" наследуется из первоначального ответа. *) Исправление: директива error_page не перенаправляла ошибку 413. *) Исправление: завершающий "?" не удалял старые аргументы, если в переписанном URI не было новых аргументов. *) Исправление: nginx не запускался на 64-битной FreeBSD 7.0-CURRENT. Изменения в nginx 0.3.57 09.08.2006 *) Добавление: переменная $ssl_client_serial. *) Исправление: в операторе "!-e" в директиве if. Спасибо Андриану Буданцову. *) Исправление: при проверке клиентского сертификата nginx не передавал клиенту информацию о требуемых сертификатах. *) Исправление: переменная $document_root не поддерживала переменные в директиве root. Изменения в nginx 0.3.56 04.08.2006 *) Добавление: директива dav_access. *) Добавление: директива if поддерживает операторы "-d", "!-d", "-e", "!-e", "-x" и "!-x". *) Исправление: при записи в access_log некоторых передаваемых клиенту строк заголовков происходил segmentation fault, если запрос возвращал редирект. Изменения в nginx 0.3.55 28.07.2006 *) Добавление: параметр stub в команде SSI include. *) Добавление: команда SSI block. *) Добавление: скрипт unicode2nginx добавлен в contrib. *) Исправление: если root был задан только переменной, то корень задавался относительно префикса сервера. *) Исправление: если в запросе был "//" или "/.", и после этого закодированные символы в виде "%XX", то проксируемый запрос передавался незакодированным. *) Исправление: метод $r->header_in("Cookie") модуля ngx_http_perl_module теперь возвращает все строки "Cookie" в заголовке запроса. *) Исправление: происходил segmentation fault, если использовался "client_body_in_file_only on" и делался переход к следующему бэкенду. *) Исправление: при некоторых условиях во время переконфигурации коды символов внутри директивы charset_map могли считаться неверными; ошибка появилась в 0.3.50. Изменения в nginx 0.3.54 11.07.2006 *) Добавление: nginx теперь записывает в лог информацию о подзапросах. *) Добавление: директивы proxy_next_upstream, fastcgi_next_upstream и memcached_next_upstream поддерживают параметр off. *) Добавление: директива debug_connection поддерживает запись адресов в формате CIDR. *) Исправление: при перекодировании ответа проксированного сервера или сервера FastCGI в UTF-8 или наоборот ответ мог передаваться не полностью. *) Исправление: переменная $upstream_response_time содержала время только первого обращения к бэкенду. *) Исправление: nginx не собирался на платформе amd64; ошибка появилась в 0.3.53. Изменения в nginx 0.3.53 07.07.2006 *) Изменение: директива add_header добавляет строки в ответы с кодом 204, 301 и 302. *) Добавление: директива server в блоке upstream поддерживает параметр weight. *) Добавление: директива server_name поддерживает маску "*". *) Добавление: nginx поддерживает тело запроса больше 2G. *) Исправление: если при использовании "satisfy_any on" клиент успешно проходил аутентификацию, в лог всё равно записалоcь сообщение "access forbidden by rule". *) Исправление: метод PUT мог ошибочно не создать файл и вернуть код 409. *) Исправление: если во время аутентификации IMAP/POP3 бэкенд возвращал ошибку, nginx продолжал проксирование. Изменения в nginx 0.3.52 03.07.2006 *) Изменение: восстановлено поведение модуля ngx_http_index_module для запросов "POST /": как в версии до 0.3.40, модуль теперь не выдаёт ошибку 405. *) Исправление: при использовании ограничения скорости рабочий процесс мог зациклиться; ошибка появилась в 0.3.37. *) Исправление: модуль ngx_http_charset_module записывал в лог ошибку "unknown charset", даже если перекодировка не требовалась; ошибка появилась в 0.3.50. *) Исправление: если в результате запроса PUT возвращался код 409, то временный файл не удалялся. Изменения в nginx 0.3.51 30.06.2006 *) Исправление: при некоторых условиях в SSI мог пропадать символы "<"; ошибка появилась в 0.3.50. Изменения в nginx 0.3.50 28.06.2006 *) Изменение: директивы proxy_redirect_errors и fastcgi_redirect_errors переименованы соответственно в proxy_intercept_errors и fastcgi_intercept_errors. *) Добавление: модуль ngx_http_charset_module поддерживает перекодирование из однобайтных кодировок в UTF-8 и обратно. *) Добавление: в режиме прокси и FastCGI поддерживается строка заголовка "X-Accel-Charset" в ответе бэкенда. *) Исправление: символ "\" в парах "\"" и "\'" в SSI командах убирался, только если также использовался символ "$". *) Исправление: при некоторых условиях в SSI после вставки могла быть добавлена строка "" 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_308_page[] = "" CRLF "308 Permanent Redirect" CRLF "" CRLF "

308 Permanent 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_429_page[] = "" CRLF "429 Too Many Requests" CRLF "" CRLF "

429 Too Many Requests

" 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_505_page[] = "" CRLF "505 HTTP Version Not Supported" CRLF "" CRLF "

505 HTTP Version Not Supported

" 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), ngx_string(ngx_http_error_308_page), #define NGX_HTTP_LAST_3XX 309 #define NGX_HTTP_OFF_4XX (NGX_HTTP_LAST_3XX - 301 + NGX_HTTP_OFF_3XX) ngx_string(ngx_http_error_400_page), ngx_string(ngx_http_error_401_page), ngx_string(ngx_http_error_402_page), ngx_string(ngx_http_error_403_page), ngx_string(ngx_http_error_404_page), ngx_string(ngx_http_error_405_page), ngx_string(ngx_http_error_406_page), ngx_null_string, /* 407 */ ngx_string(ngx_http_error_408_page), ngx_string(ngx_http_error_409_page), ngx_string(ngx_http_error_410_page), ngx_string(ngx_http_error_411_page), ngx_string(ngx_http_error_412_page), ngx_string(ngx_http_error_413_page), ngx_string(ngx_http_error_414_page), ngx_string(ngx_http_error_415_page), ngx_string(ngx_http_error_416_page), ngx_null_string, /* 417 */ ngx_null_string, /* 418 */ ngx_null_string, /* 419 */ ngx_null_string, /* 420 */ ngx_string(ngx_http_error_421_page), ngx_null_string, /* 422 */ ngx_null_string, /* 423 */ ngx_null_string, /* 424 */ ngx_null_string, /* 425 */ ngx_null_string, /* 426 */ ngx_null_string, /* 427 */ ngx_null_string, /* 428 */ ngx_string(ngx_http_error_429_page), #define NGX_HTTP_LAST_4XX 430 #define NGX_HTTP_OFF_5XX (NGX_HTTP_LAST_4XX - 400 + NGX_HTTP_OFF_4XX) ngx_string(ngx_http_error_494_page), /* 494, request header too large */ ngx_string(ngx_http_error_495_page), /* 495, https certificate error */ ngx_string(ngx_http_error_496_page), /* 496, https no certificate */ ngx_string(ngx_http_error_497_page), /* 497, http to https */ ngx_string(ngx_http_error_404_page), /* 498, canceled */ ngx_null_string, /* 499, client has closed connection */ ngx_string(ngx_http_error_500_page), ngx_string(ngx_http_error_501_page), ngx_string(ngx_http_error_502_page), ngx_string(ngx_http_error_503_page), ngx_string(ngx_http_error_504_page), ngx_string(ngx_http_error_505_page), ngx_null_string, /* 506 */ ngx_string(ngx_http_error_507_page) #define NGX_HTTP_LAST_5XX 508 }; ngx_int_t ngx_http_special_response_handler(ngx_http_request_t *r, ngx_int_t error) { ngx_uint_t i, err; ngx_http_err_page_t *err_page; ngx_http_core_loc_conf_t *clcf; ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http special response: %i, \"%V?%V\"", error, &r->uri, &r->args); r->err_status = error; if (r->keepalive) { switch (error) { case NGX_HTTP_BAD_REQUEST: case NGX_HTTP_REQUEST_ENTITY_TOO_LARGE: case NGX_HTTP_REQUEST_URI_TOO_LARGE: case NGX_HTTP_TO_HTTPS: case NGX_HTTPS_CERT_ERROR: case NGX_HTTPS_NO_CERT: case NGX_HTTP_INTERNAL_SERVER_ERROR: case NGX_HTTP_NOT_IMPLEMENTED: r->keepalive = 0; } } if (r->lingering_close) { switch (error) { case NGX_HTTP_BAD_REQUEST: case NGX_HTTP_TO_HTTPS: case NGX_HTTPS_CERT_ERROR: case NGX_HTTPS_NO_CERT: r->lingering_close = 0; } } r->headers_out.content_type.len = 0; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (!r->error_page && clcf->error_pages && r->uri_changes != 0) { if (clcf->recursive_error_pages == 0) { r->error_page = 1; } err_page = clcf->error_pages->elts; for (i = 0; i < clcf->error_pages->nelts; i++) { if (err_page[i].status == error) { return ngx_http_send_error_page(r, &err_page[i]); } } } r->expect_tested = 1; if (ngx_http_discard_request_body(r) != NGX_OK) { r->keepalive = 0; } if (clcf->msie_refresh && r->headers_in.msie && (error == NGX_HTTP_MOVED_PERMANENTLY || error == NGX_HTTP_MOVED_TEMPORARILY)) { return ngx_http_send_refresh(r); } if (error == NGX_HTTP_CREATED) { /* 201 */ err = 0; } else if (error == NGX_HTTP_NO_CONTENT) { /* 204 */ err = 0; } else if (error >= NGX_HTTP_MOVED_PERMANENTLY && error < NGX_HTTP_LAST_3XX) { /* 3XX */ err = error - NGX_HTTP_MOVED_PERMANENTLY + NGX_HTTP_OFF_3XX; } else if (error >= NGX_HTTP_BAD_REQUEST && error < NGX_HTTP_LAST_4XX) { /* 4XX */ err = error - NGX_HTTP_BAD_REQUEST + NGX_HTTP_OFF_4XX; } else if (error >= NGX_HTTP_NGINX_CODES && error < NGX_HTTP_LAST_5XX) { /* 49X, 5XX */ err = error - NGX_HTTP_NGINX_CODES + NGX_HTTP_OFF_5XX; switch (error) { case NGX_HTTP_TO_HTTPS: case NGX_HTTPS_CERT_ERROR: case NGX_HTTPS_NO_CERT: case NGX_HTTP_REQUEST_HEADER_TOO_LARGE: r->err_status = NGX_HTTP_BAD_REQUEST; } } else { /* unknown code, zero body */ err = 0; } return ngx_http_send_special_response(r, clcf, err); } ngx_int_t ngx_http_filter_finalize_request(ngx_http_request_t *r, ngx_module_t *m, ngx_int_t error) { void *ctx; ngx_int_t rc; ngx_http_clean_header(r); ctx = NULL; if (m) { ctx = r->ctx[m->ctx_index]; } /* clear the modules contexts */ ngx_memzero(r->ctx, sizeof(void *) * ngx_http_max_module); if (m) { r->ctx[m->ctx_index] = ctx; } r->filter_finalize = 1; rc = ngx_http_special_response_handler(r, error); /* NGX_ERROR resets any pending data */ switch (rc) { case NGX_OK: case NGX_DONE: return NGX_ERROR; default: return rc; } } void ngx_http_clean_header(ngx_http_request_t *r) { ngx_memzero(&r->headers_out.status, sizeof(ngx_http_headers_out_t) - offsetof(ngx_http_headers_out_t, status)); r->headers_out.headers.part.nelts = 0; r->headers_out.headers.part.next = NULL; r->headers_out.headers.last = &r->headers_out.headers.part; r->headers_out.trailers.part.nelts = 0; r->headers_out.trailers.part.next = NULL; r->headers_out.trailers.last = &r->headers_out.trailers.part; r->headers_out.content_length_n = -1; r->headers_out.last_modified_time = -1; } 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); } r->expect_tested = 1; if (ngx_http_discard_request_body(r) != NGX_OK) { r->keepalive = 0; } location = ngx_list_push(&r->headers_out.headers); if (location == NULL) { return NGX_ERROR; } if (overwrite != NGX_HTTP_MOVED_PERMANENTLY && overwrite != NGX_HTTP_MOVED_TEMPORARILY && overwrite != NGX_HTTP_SEE_OTHER && overwrite != NGX_HTTP_TEMPORARY_REDIRECT && overwrite != NGX_HTTP_PERMANENT_REDIRECT) { r->err_status = NGX_HTTP_MOVED_TEMPORARILY; } location->hash = 1; location->next = NULL; ngx_str_set(&location->key, "Location"); location->value = uri; ngx_http_clear_location(r); r->headers_out.location = location; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (clcf->msie_refresh && r->headers_in.msie) { return ngx_http_send_refresh(r); } return ngx_http_send_special_response(r, clcf, r->err_status - NGX_HTTP_MOVED_PERMANENTLY + NGX_HTTP_OFF_3XX); } static ngx_int_t ngx_http_send_special_response(ngx_http_request_t *r, ngx_http_core_loc_conf_t *clcf, ngx_uint_t err) { u_char *tail; size_t len; ngx_int_t rc; ngx_buf_t *b; ngx_uint_t msie_padding; ngx_chain_t out[3]; if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { len = sizeof(ngx_http_error_full_tail) - 1; tail = ngx_http_error_full_tail; } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { len = sizeof(ngx_http_error_build_tail) - 1; tail = ngx_http_error_build_tail; } else { len = sizeof(ngx_http_error_tail) - 1; tail = ngx_http_error_tail; } msie_padding = 0; if (ngx_http_error_pages[err].len) { r->headers_out.content_length_n = ngx_http_error_pages[err].len + len; if (clcf->msie_padding && (r->headers_in.msie || r->headers_in.chrome) && r->http_version >= NGX_HTTP_VERSION_10 && err >= NGX_HTTP_OFF_4XX) { r->headers_out.content_length_n += sizeof(ngx_http_msie_padding) - 1; msie_padding = 1; } r->headers_out.content_type_len = sizeof("text/html") - 1; ngx_str_set(&r->headers_out.content_type, "text/html"); r->headers_out.content_type_lowcase = NULL; } else { r->headers_out.content_length_n = 0; } if (r->headers_out.content_length) { r->headers_out.content_length->hash = 0; r->headers_out.content_length = NULL; } ngx_http_clear_accept_ranges(r); ngx_http_clear_last_modified(r); ngx_http_clear_etag(r); rc = ngx_http_send_header(r); if (rc == NGX_ERROR || r->header_only) { return rc; } if (ngx_http_error_pages[err].len == 0) { return ngx_http_send_special(r, NGX_HTTP_LAST); } b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } b->memory = 1; b->pos = ngx_http_error_pages[err].data; b->last = ngx_http_error_pages[err].data + ngx_http_error_pages[err].len; out[0].buf = b; out[0].next = &out[1]; b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } b->memory = 1; b->pos = tail; b->last = tail + len; out[1].buf = b; out[1].next = NULL; if (msie_padding) { b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } b->memory = 1; b->pos = ngx_http_msie_padding; b->last = ngx_http_msie_padding + sizeof(ngx_http_msie_padding) - 1; out[1].next = &out[2]; out[2].buf = b; out[2].next = NULL; } if (r == r->main) { b->last_buf = 1; } b->last_in_chain = 1; return ngx_http_output_filter(r, &out[0]); } static ngx_int_t ngx_http_send_refresh(ngx_http_request_t *r) { u_char *p, *location; size_t len, size; uintptr_t escape; ngx_int_t rc; ngx_buf_t *b; ngx_chain_t out; len = r->headers_out.location->value.len; location = r->headers_out.location->value.data; escape = 2 * ngx_escape_uri(NULL, location, len, NGX_ESCAPE_REFRESH); size = sizeof(ngx_http_msie_refresh_head) - 1 + escape + len + sizeof(ngx_http_msie_refresh_tail) - 1; r->err_status = NGX_HTTP_OK; r->headers_out.content_type_len = sizeof("text/html") - 1; ngx_str_set(&r->headers_out.content_type, "text/html"); r->headers_out.content_type_lowcase = NULL; r->headers_out.location->hash = 0; r->headers_out.location = NULL; r->headers_out.content_length_n = size; if (r->headers_out.content_length) { r->headers_out.content_length->hash = 0; r->headers_out.content_length = NULL; } ngx_http_clear_accept_ranges(r); ngx_http_clear_last_modified(r); ngx_http_clear_etag(r); rc = ngx_http_send_header(r); if (rc == NGX_ERROR || r->header_only) { return rc; } b = ngx_create_temp_buf(r->pool, size); if (b == NULL) { return NGX_ERROR; } p = ngx_cpymem(b->pos, ngx_http_msie_refresh_head, sizeof(ngx_http_msie_refresh_head) - 1); if (escape == 0) { p = ngx_cpymem(p, location, len); } else { p = (u_char *) ngx_escape_uri(p, location, len, NGX_ESCAPE_REFRESH); } b->last = ngx_cpymem(p, ngx_http_msie_refresh_tail, sizeof(ngx_http_msie_refresh_tail) - 1); b->last_buf = (r == r->main) ? 1 : 0; b->last_in_chain = 1; out.buf = b; out.next = NULL; return ngx_http_output_filter(r, &out); } nginx-1.24.0/src/http/ngx_http_upstream.c000644 001751 001751 00000525223 14415135676 021621 0ustar00mdouninmdounin000000 000000 /* * 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_background_update( ngx_http_request_t *r, ngx_http_upstream_t *u); static ngx_int_t ngx_http_upstream_cache_check_range(ngx_http_request_t *r, ngx_http_upstream_t *u); static ngx_int_t ngx_http_upstream_cache_status(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_upstream_cache_last_modified(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_upstream_cache_etag(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); #endif static void ngx_http_upstream_init_request(ngx_http_request_t *r); static void ngx_http_upstream_resolve_handler(ngx_resolver_ctx_t *ctx); static void ngx_http_upstream_rd_check_broken_connection(ngx_http_request_t *r); static void ngx_http_upstream_wr_check_broken_connection(ngx_http_request_t *r); static void ngx_http_upstream_check_broken_connection(ngx_http_request_t *r, ngx_event_t *ev); static void ngx_http_upstream_connect(ngx_http_request_t *r, ngx_http_upstream_t *u); static ngx_int_t ngx_http_upstream_reinit(ngx_http_request_t *r, ngx_http_upstream_t *u); static void ngx_http_upstream_send_request(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_uint_t do_write); static ngx_int_t ngx_http_upstream_send_request_body(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_uint_t do_write); static void ngx_http_upstream_send_request_handler(ngx_http_request_t *r, ngx_http_upstream_t *u); static void ngx_http_upstream_read_request_handler(ngx_http_request_t *r); static void ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u); static ngx_int_t ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u); static ngx_int_t ngx_http_upstream_intercept_errors(ngx_http_request_t *r, ngx_http_upstream_t *u); static ngx_int_t ngx_http_upstream_test_connect(ngx_connection_t *c); static ngx_int_t ngx_http_upstream_process_headers(ngx_http_request_t *r, ngx_http_upstream_t *u); static ngx_int_t ngx_http_upstream_process_trailers(ngx_http_request_t *r, ngx_http_upstream_t *u); static void ngx_http_upstream_send_response(ngx_http_request_t *r, ngx_http_upstream_t *u); static void ngx_http_upstream_upgrade(ngx_http_request_t *r, ngx_http_upstream_t *u); static void ngx_http_upstream_upgraded_read_downstream(ngx_http_request_t *r); static void ngx_http_upstream_upgraded_write_downstream(ngx_http_request_t *r); static void ngx_http_upstream_upgraded_read_upstream(ngx_http_request_t *r, ngx_http_upstream_t *u); static void ngx_http_upstream_upgraded_write_upstream(ngx_http_request_t *r, ngx_http_upstream_t *u); static void ngx_http_upstream_process_upgraded(ngx_http_request_t *r, ngx_uint_t from_upstream, ngx_uint_t do_write); static void ngx_http_upstream_process_non_buffered_downstream(ngx_http_request_t *r); static void ngx_http_upstream_process_non_buffered_upstream(ngx_http_request_t *r, ngx_http_upstream_t *u); static void ngx_http_upstream_process_non_buffered_request(ngx_http_request_t *r, ngx_uint_t do_write); #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_multi_header_lines(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); static ngx_int_t ngx_http_upstream_add_variables(ngx_conf_t *cf); static ngx_int_t ngx_http_upstream_addr_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_upstream_status_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_upstream_response_time_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_upstream_response_length_variable( ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_upstream_header_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_upstream_trailer_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_upstream_cookie_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static char *ngx_http_upstream(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy); static char *ngx_http_upstream_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_int_t ngx_http_upstream_set_local(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_http_upstream_local_t *local); static void *ngx_http_upstream_create_main_conf(ngx_conf_t *cf); static char *ngx_http_upstream_init_main_conf(ngx_conf_t *cf, void *conf); #if (NGX_HTTP_SSL) static void ngx_http_upstream_ssl_init_connection(ngx_http_request_t *, ngx_http_upstream_t *u, ngx_connection_t *c); static void ngx_http_upstream_ssl_handshake_handler(ngx_connection_t *c); static void ngx_http_upstream_ssl_handshake(ngx_http_request_t *, ngx_http_upstream_t *u, ngx_connection_t *c); static void ngx_http_upstream_ssl_save_session(ngx_connection_t *c); static ngx_int_t ngx_http_upstream_ssl_name(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_connection_t *c); static ngx_int_t ngx_http_upstream_ssl_certificate(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_connection_t *c); #endif static ngx_http_upstream_header_t ngx_http_upstream_headers_in[] = { { ngx_string("Status"), ngx_http_upstream_process_header_line, offsetof(ngx_http_upstream_headers_in_t, status), ngx_http_upstream_copy_header_line, 0, 0 }, { ngx_string("Content-Type"), ngx_http_upstream_process_header_line, offsetof(ngx_http_upstream_headers_in_t, content_type), ngx_http_upstream_copy_content_type, 0, 1 }, { ngx_string("Content-Length"), ngx_http_upstream_process_content_length, 0, ngx_http_upstream_ignore_header_line, 0, 0 }, { ngx_string("Date"), ngx_http_upstream_process_header_line, offsetof(ngx_http_upstream_headers_in_t, date), ngx_http_upstream_copy_header_line, offsetof(ngx_http_headers_out_t, date), 0 }, { ngx_string("Last-Modified"), ngx_http_upstream_process_last_modified, 0, ngx_http_upstream_copy_last_modified, 0, 0 }, { ngx_string("ETag"), ngx_http_upstream_process_header_line, offsetof(ngx_http_upstream_headers_in_t, etag), ngx_http_upstream_copy_header_line, offsetof(ngx_http_headers_out_t, etag), 0 }, { ngx_string("Server"), ngx_http_upstream_process_header_line, offsetof(ngx_http_upstream_headers_in_t, server), ngx_http_upstream_copy_header_line, offsetof(ngx_http_headers_out_t, server), 0 }, { ngx_string("WWW-Authenticate"), ngx_http_upstream_process_multi_header_lines, 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_process_header_line, offsetof(ngx_http_upstream_headers_in_t, refresh), ngx_http_upstream_rewrite_refresh, 0, 0 }, { ngx_string("Set-Cookie"), ngx_http_upstream_process_set_cookie, offsetof(ngx_http_upstream_headers_in_t, set_cookie), 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_ignore_header_line, 0, ngx_http_upstream_copy_allow_ranges, offsetof(ngx_http_headers_out_t, accept_ranges), 1 }, { ngx_string("Content-Range"), ngx_http_upstream_ignore_header_line, 0, ngx_http_upstream_copy_header_line, offsetof(ngx_http_headers_out_t, content_range), 0 }, { ngx_string("Connection"), ngx_http_upstream_process_connection, 0, ngx_http_upstream_ignore_header_line, 0, 0 }, { ngx_string("Keep-Alive"), ngx_http_upstream_ignore_header_line, 0, ngx_http_upstream_ignore_header_line, 0, 0 }, { ngx_string("Vary"), ngx_http_upstream_process_vary, 0, ngx_http_upstream_copy_header_line, 0, 0 }, { ngx_string("Link"), ngx_http_upstream_ignore_header_line, 0, ngx_http_upstream_copy_multi_header_lines, offsetof(ngx_http_headers_out_t, link), 0 }, { ngx_string("X-Accel-Expires"), ngx_http_upstream_process_accel_expires, 0, ngx_http_upstream_copy_header_line, 0, 0 }, { ngx_string("X-Accel-Redirect"), ngx_http_upstream_process_header_line, offsetof(ngx_http_upstream_headers_in_t, x_accel_redirect), ngx_http_upstream_copy_header_line, 0, 0 }, { ngx_string("X-Accel-Limit-Rate"), ngx_http_upstream_process_limit_rate, 0, ngx_http_upstream_copy_header_line, 0, 0 }, { ngx_string("X-Accel-Buffering"), ngx_http_upstream_process_buffering, 0, ngx_http_upstream_copy_header_line, 0, 0 }, { ngx_string("X-Accel-Charset"), ngx_http_upstream_process_charset, 0, ngx_http_upstream_copy_header_line, 0, 0 }, { ngx_string("Transfer-Encoding"), ngx_http_upstream_process_transfer_encoding, 0, ngx_http_upstream_ignore_header_line, 0, 0 }, { ngx_string("Content-Encoding"), ngx_http_upstream_ignore_header_line, 0, ngx_http_upstream_copy_header_line, offsetof(ngx_http_headers_out_t, content_encoding), 0 }, { ngx_null_string, NULL, 0, NULL, 0, 0 } }; static ngx_command_t ngx_http_upstream_commands[] = { { ngx_string("upstream"), NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1, ngx_http_upstream, 0, 0, NULL }, { ngx_string("server"), NGX_HTTP_UPS_CONF|NGX_CONF_1MORE, ngx_http_upstream_server, NGX_HTTP_SRV_CONF_OFFSET, 0, NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_upstream_module_ctx = { ngx_http_upstream_add_variables, /* preconfiguration */ NULL, /* postconfiguration */ ngx_http_upstream_create_main_conf, /* create main configuration */ ngx_http_upstream_init_main_conf, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; ngx_module_t ngx_http_upstream_module = { NGX_MODULE_V1, &ngx_http_upstream_module_ctx, /* module context */ ngx_http_upstream_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_variable_t ngx_http_upstream_vars[] = { { ngx_string("upstream_addr"), NULL, ngx_http_upstream_addr_variable, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("upstream_status"), NULL, ngx_http_upstream_status_variable, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("upstream_connect_time"), NULL, ngx_http_upstream_response_time_variable, 2, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("upstream_header_time"), NULL, ngx_http_upstream_response_time_variable, 1, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("upstream_response_time"), NULL, ngx_http_upstream_response_time_variable, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("upstream_response_length"), NULL, ngx_http_upstream_response_length_variable, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("upstream_bytes_received"), NULL, ngx_http_upstream_response_length_variable, 1, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("upstream_bytes_sent"), NULL, ngx_http_upstream_response_length_variable, 2, NGX_HTTP_VAR_NOCACHEABLE, 0 }, #if (NGX_HTTP_CACHE) { ngx_string("upstream_cache_status"), NULL, ngx_http_upstream_cache_status, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("upstream_cache_last_modified"), NULL, ngx_http_upstream_cache_last_modified, 0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 }, { ngx_string("upstream_cache_etag"), NULL, ngx_http_upstream_cache_etag, 0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 }, #endif { ngx_string("upstream_http_"), NULL, ngx_http_upstream_header_variable, 0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_PREFIX, 0 }, { ngx_string("upstream_trailer_"), NULL, ngx_http_upstream_trailer_variable, 0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_PREFIX, 0 }, { ngx_string("upstream_cookie_"), NULL, ngx_http_upstream_cookie_variable, 0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_PREFIX, 0 }, ngx_http_null_variable }; static ngx_http_upstream_next_t ngx_http_upstream_next_errors[] = { { 500, NGX_HTTP_UPSTREAM_FT_HTTP_500 }, { 502, NGX_HTTP_UPSTREAM_FT_HTTP_502 }, { 503, NGX_HTTP_UPSTREAM_FT_HTTP_503 }, { 504, NGX_HTTP_UPSTREAM_FT_HTTP_504 }, { 403, NGX_HTTP_UPSTREAM_FT_HTTP_403 }, { 404, NGX_HTTP_UPSTREAM_FT_HTTP_404 }, { 429, NGX_HTTP_UPSTREAM_FT_HTTP_429 }, { 0, 0 } }; ngx_conf_bitmask_t ngx_http_upstream_cache_method_mask[] = { { ngx_string("GET"), NGX_HTTP_GET }, { ngx_string("HEAD"), NGX_HTTP_HEAD }, { ngx_string("POST"), NGX_HTTP_POST }, { ngx_null_string, 0 } }; ngx_conf_bitmask_t ngx_http_upstream_ignore_headers_masks[] = { { ngx_string("X-Accel-Redirect"), NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT }, { ngx_string("X-Accel-Expires"), NGX_HTTP_UPSTREAM_IGN_XA_EXPIRES }, { ngx_string("X-Accel-Limit-Rate"), NGX_HTTP_UPSTREAM_IGN_XA_LIMIT_RATE }, { ngx_string("X-Accel-Buffering"), NGX_HTTP_UPSTREAM_IGN_XA_BUFFERING }, { ngx_string("X-Accel-Charset"), NGX_HTTP_UPSTREAM_IGN_XA_CHARSET }, { ngx_string("Expires"), NGX_HTTP_UPSTREAM_IGN_EXPIRES }, { ngx_string("Cache-Control"), NGX_HTTP_UPSTREAM_IGN_CACHE_CONTROL }, { ngx_string("Set-Cookie"), NGX_HTTP_UPSTREAM_IGN_SET_COOKIE }, { ngx_string("Vary"), NGX_HTTP_UPSTREAM_IGN_VARY }, { ngx_null_string, 0 } }; ngx_int_t ngx_http_upstream_create(ngx_http_request_t *r) { ngx_http_upstream_t *u; u = r->upstream; if (u && u->cleanup) { r->main->count++; ngx_http_upstream_cleanup(r); } u = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_t)); if (u == NULL) { return NGX_ERROR; } r->upstream = u; u->peer.log = r->connection->log; u->peer.log_error = NGX_ERROR_ERR; #if (NGX_HTTP_CACHE) r->cache = NULL; #endif u->headers_in.content_length_n = -1; u->headers_in.last_modified_time = -1; return NGX_OK; } void ngx_http_upstream_init(ngx_http_request_t *r) { ngx_connection_t *c; c = r->connection; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http init upstream, client timer: %d", c->read->timer_set); #if (NGX_HTTP_V2) if (r->stream) { ngx_http_upstream_init_request(r); return; } #endif if (c->read->timer_set) { ngx_del_timer(c->read); } if (ngx_event_flags & NGX_USE_CLEAR_EVENT) { if (!c->write->active) { if (ngx_add_event(c->write, NGX_WRITE_EVENT, NGX_CLEAR_EVENT) == NGX_ERROR) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } } } ngx_http_upstream_init_request(r); } static void ngx_http_upstream_init_request(ngx_http_request_t *r) { ngx_str_t *host; ngx_uint_t i; ngx_resolver_ctx_t *ctx, temp; ngx_http_cleanup_t *cln; ngx_http_upstream_t *u; ngx_http_core_loc_conf_t *clcf; ngx_http_upstream_srv_conf_t *uscf, **uscfp; ngx_http_upstream_main_conf_t *umcf; if (r->aio) { return; } u = r->upstream; #if (NGX_HTTP_CACHE) if (u->conf->cache) { ngx_int_t rc; rc = ngx_http_upstream_cache(r, u); if (rc == NGX_BUSY) { r->write_event_handler = ngx_http_upstream_init_request; return; } r->write_event_handler = ngx_http_request_empty_handler; if (rc == NGX_ERROR) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (rc == NGX_OK) { rc = ngx_http_upstream_cache_send(r, u); if (rc == NGX_DONE) { return; } if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) { rc = NGX_DECLINED; r->cached = 0; u->buffer.start = NULL; u->cache_status = NGX_HTTP_CACHE_MISS; u->request_sent = 1; } } if (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) { if (r->connection->read->ready) { ngx_post_event(r->connection->read, &ngx_posted_events); } else { if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } } r->read_event_handler = ngx_http_upstream_rd_check_broken_connection; r->write_event_handler = ngx_http_upstream_wr_check_broken_connection; } if (r->request_body) { u->request_bufs = r->request_body->bufs; } if (u->create_request(r) != NGX_OK) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (ngx_http_upstream_set_local(r, u, u->conf->local) != NGX_OK) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (u->conf->socket_keepalive) { u->peer.so_keepalive = 1; } clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); u->output.alignment = clcf->directio_alignment; u->output.pool = r->pool; u->output.bufs.num = 1; u->output.bufs.size = clcf->client_body_buffer_size; if (u->output.output_filter == NULL) { u->output.output_filter = ngx_chain_writer; u->output.filter_ctx = &u->writer; } u->writer.pool = r->pool; if (r->upstream_states == NULL) { r->upstream_states = ngx_array_create(r->pool, 1, sizeof(ngx_http_upstream_state_t)); if (r->upstream_states == NULL) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } } else { u->state = ngx_array_push(r->upstream_states); if (u->state == NULL) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } ngx_memzero(u->state, sizeof(ngx_http_upstream_state_t)); } cln = ngx_http_cleanup_add(r, 0); if (cln == NULL) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } cln->handler = ngx_http_upstream_cleanup; cln->data = r; u->cleanup = &cln->handler; if (u->resolved == NULL) { uscf = u->conf->upstream; } else { #if (NGX_HTTP_SSL) u->ssl_name = u->resolved->host; #endif host = &u->resolved->host; umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module); uscfp = umcf->upstreams.elts; for (i = 0; i < umcf->upstreams.nelts; i++) { uscf = uscfp[i]; if (uscf->host.len == host->len && ((uscf->port == 0 && u->resolved->no_port) || uscf->port == u->resolved->port) && ngx_strncasecmp(uscf->host.data, host->data, host->len) == 0) { goto found; } } if (u->resolved->sockaddr) { if (u->resolved->port == 0 && u->resolved->sockaddr->sa_family != AF_UNIX) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no port in upstream \"%V\"", host); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (ngx_http_upstream_create_round_robin_peer(r, u->resolved) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } ngx_http_upstream_connect(r, u); return; } if (u->resolved->port == 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no port in upstream \"%V\"", host); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } temp.name = *host; ctx = ngx_resolve_start(clcf->resolver, &temp); if (ctx == NULL) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (ctx == NGX_NO_RESOLVER) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no resolver defined to resolve %V", host); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY); return; } ctx->name = *host; ctx->handler = ngx_http_upstream_resolve_handler; ctx->data = r; ctx->timeout = clcf->resolver_timeout; u->resolved->ctx = ctx; if (ngx_resolve_name(ctx) != NGX_OK) { u->resolved->ctx = NULL; ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } return; } found: if (uscf == NULL) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "no upstream configuration"); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } u->upstream = uscf; #if (NGX_HTTP_SSL) u->ssl_name = uscf->host; #endif if (uscf->peer.init(r, uscf) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } u->peer.start_time = ngx_current_msec; if (u->conf->next_upstream_tries && u->peer.tries > u->conf->next_upstream_tries) { u->peer.tries = u->conf->next_upstream_tries; } ngx_http_upstream_connect(r, u); } #if (NGX_HTTP_CACHE) static ngx_int_t ngx_http_upstream_cache(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_int_t rc; ngx_http_cache_t *c; ngx_http_file_cache_t *cache; c = r->cache; if (c == NULL) { if (!(r->method & u->conf->cache_methods)) { return NGX_DECLINED; } rc = ngx_http_upstream_cache_get(r, u, &cache); if (rc != NGX_OK) { return rc; } if (r->method == NGX_HTTP_HEAD && u->conf->cache_convert_head) { u->method = ngx_http_core_get_method; } if (ngx_http_file_cache_new(r) != NGX_OK) { return NGX_ERROR; } if (u->create_key(r) != NGX_OK) { return NGX_ERROR; } /* TODO: add keys */ ngx_http_file_cache_create_key(r); if (r->cache->header_start + 256 > u->conf->buffer_size) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "%V_buffer_size %uz is not enough for cache key, " "it should be increased to at least %uz", &u->conf->module, u->conf->buffer_size, ngx_align(r->cache->header_start + 256, 1024)); r->cache = NULL; return NGX_DECLINED; } u->cacheable = 1; c = r->cache; c->body_start = u->conf->buffer_size; c->min_uses = u->conf->cache_min_uses; c->file_cache = cache; switch (ngx_http_test_predicates(r, u->conf->cache_bypass)) { case NGX_ERROR: return NGX_ERROR; case NGX_DECLINED: u->cache_status = NGX_HTTP_CACHE_BYPASS; return NGX_DECLINED; default: /* NGX_OK */ break; } c->lock = u->conf->cache_lock; c->lock_timeout = u->conf->cache_lock_timeout; c->lock_age = u->conf->cache_lock_age; u->cache_status = NGX_HTTP_CACHE_MISS; } rc = ngx_http_file_cache_open(r); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http upstream cache: %i", rc); switch (rc) { case NGX_HTTP_CACHE_STALE: if (((u->conf->cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING) || c->stale_updating) && !r->background && u->conf->cache_background_update) { if (ngx_http_upstream_cache_background_update(r, u) == NGX_OK) { r->cache->background = 1; u->cache_status = rc; rc = NGX_OK; } else { rc = NGX_ERROR; } } break; case NGX_HTTP_CACHE_UPDATING: if (((u->conf->cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING) || c->stale_updating) && !r->background) { u->cache_status = rc; rc = NGX_OK; } else { rc = NGX_HTTP_CACHE_STALE; } break; case NGX_OK: u->cache_status = NGX_HTTP_CACHE_HIT; } switch (rc) { case NGX_OK: return NGX_OK; case NGX_HTTP_CACHE_STALE: c->valid_sec = 0; c->updating_sec = 0; c->error_sec = 0; u->buffer.start = NULL; u->cache_status = NGX_HTTP_CACHE_EXPIRED; break; case NGX_DECLINED: if ((size_t) (u->buffer.end - u->buffer.start) < u->conf->buffer_size) { u->buffer.start = NULL; } else { u->buffer.pos = u->buffer.start + c->header_start; u->buffer.last = u->buffer.pos; } break; case NGX_HTTP_CACHE_SCARCE: u->cacheable = 0; break; case NGX_AGAIN: return NGX_BUSY; case NGX_ERROR: return NGX_ERROR; default: /* cached NGX_HTTP_BAD_GATEWAY, NGX_HTTP_GATEWAY_TIME_OUT, etc. */ u->cache_status = NGX_HTTP_CACHE_HIT; return rc; } if (ngx_http_upstream_cache_check_range(r, u) == NGX_DECLINED) { u->cacheable = 0; } r->cached = 0; return NGX_DECLINED; } static ngx_int_t ngx_http_upstream_cache_get(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_http_file_cache_t **cache) { ngx_str_t *name, val; ngx_uint_t i; ngx_http_file_cache_t **caches; if (u->conf->cache_zone) { *cache = u->conf->cache_zone->data; return NGX_OK; } if (ngx_http_complex_value(r, u->conf->cache_value, &val) != NGX_OK) { return NGX_ERROR; } if (val.len == 0 || (val.len == 3 && ngx_strncmp(val.data, "off", 3) == 0)) { return NGX_DECLINED; } caches = u->caches->elts; for (i = 0; i < u->caches->nelts; i++) { name = &caches[i]->shm_zone->shm.name; if (name->len == val.len && ngx_strncmp(name->data, val.data, val.len) == 0) { *cache = caches[i]; return NGX_OK; } } ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "cache \"%V\" not found", &val); return NGX_ERROR; } static ngx_int_t ngx_http_upstream_cache_send(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_int_t rc; ngx_http_cache_t *c; r->cached = 1; c = r->cache; if (c->header_start == c->body_start) { r->http_version = NGX_HTTP_VERSION_9; return ngx_http_cache_send(r); } /* TODO: cache stack */ u->buffer = *c->buf; u->buffer.pos += c->header_start; ngx_memzero(&u->headers_in, sizeof(ngx_http_upstream_headers_in_t)); u->headers_in.content_length_n = -1; u->headers_in.last_modified_time = -1; if (ngx_list_init(&u->headers_in.headers, r->pool, 8, sizeof(ngx_table_elt_t)) != NGX_OK) { return NGX_ERROR; } if (ngx_list_init(&u->headers_in.trailers, r->pool, 2, sizeof(ngx_table_elt_t)) != NGX_OK) { return NGX_ERROR; } rc = u->process_header(r); if (rc == NGX_OK) { if (ngx_http_upstream_process_headers(r, u) != NGX_OK) { return NGX_DONE; } return ngx_http_cache_send(r); } if (rc == NGX_ERROR) { return NGX_ERROR; } if (rc == NGX_AGAIN) { rc = NGX_HTTP_UPSTREAM_INVALID_HEADER; } /* rc == NGX_HTTP_UPSTREAM_INVALID_HEADER */ ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0, "cache file \"%s\" contains invalid header", c->file.name.data); /* TODO: delete file */ return rc; } static ngx_int_t ngx_http_upstream_cache_background_update(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_http_request_t *sr; if (r == r->main) { r->preserve_body = 1; } if (ngx_http_subrequest(r, &r->uri, &r->args, &sr, NULL, NGX_HTTP_SUBREQUEST_CLONE |NGX_HTTP_SUBREQUEST_BACKGROUND) != NGX_OK) { return NGX_ERROR; } sr->header_only = 1; return NGX_OK; } static ngx_int_t ngx_http_upstream_cache_check_range(ngx_http_request_t *r, ngx_http_upstream_t *u) { off_t offset; u_char *p, *start; ngx_table_elt_t *h; h = r->headers_in.range; if (h == NULL || !u->cacheable || u->conf->cache_max_range_offset == NGX_MAX_OFF_T_VALUE) { return NGX_OK; } if (u->conf->cache_max_range_offset == 0) { return NGX_DECLINED; } if (h->value.len < 7 || ngx_strncasecmp(h->value.data, (u_char *) "bytes=", 6) != 0) { return NGX_OK; } p = h->value.data + 6; while (*p == ' ') { p++; } if (*p == '-') { return NGX_DECLINED; } start = p; while (*p >= '0' && *p <= '9') { p++; } offset = ngx_atoof(start, p - start); if (offset >= u->conf->cache_max_range_offset) { return NGX_DECLINED; } return NGX_OK; } #endif static void ngx_http_upstream_resolve_handler(ngx_resolver_ctx_t *ctx) { ngx_uint_t run_posted; ngx_connection_t *c; ngx_http_request_t *r; ngx_http_upstream_t *u; ngx_http_upstream_resolved_t *ur; run_posted = ctx->async; r = ctx->data; c = r->connection; u = r->upstream; ur = u->resolved; ngx_http_set_log_request(c->log, r); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream resolve: \"%V?%V\"", &r->uri, &r->args); if (ctx->state) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "%V could not be resolved (%i: %s)", &ctx->name, ctx->state, ngx_resolver_strerror(ctx->state)); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY); goto failed; } ur->naddrs = ctx->naddrs; ur->addrs = ctx->addrs; #if (NGX_DEBUG) { u_char text[NGX_SOCKADDR_STRLEN]; ngx_str_t addr; ngx_uint_t i; addr.data = text; for (i = 0; i < ctx->naddrs; i++) { addr.len = ngx_sock_ntop(ur->addrs[i].sockaddr, ur->addrs[i].socklen, text, NGX_SOCKADDR_STRLEN, 0); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "name was resolved to %V", &addr); } } #endif if (ngx_http_upstream_create_round_robin_peer(r, ur) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); goto failed; } ngx_resolve_name_done(ctx); ur->ctx = NULL; u->peer.start_time = ngx_current_msec; if (u->conf->next_upstream_tries && u->peer.tries > u->conf->next_upstream_tries) { u->peer.tries = u->conf->next_upstream_tries; } ngx_http_upstream_connect(r, u); failed: if (run_posted) { ngx_http_run_posted_requests(c); } } static void ngx_http_upstream_handler(ngx_event_t *ev) { ngx_connection_t *c; ngx_http_request_t *r; ngx_http_upstream_t *u; c = ev->data; r = c->data; u = r->upstream; c = r->connection; ngx_http_set_log_request(c->log, r); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream request: \"%V?%V\"", &r->uri, &r->args); if (ev->delayed && ev->timedout) { ev->delayed = 0; ev->timedout = 0; } if (ev->write) { u->write_event_handler(r, u); } else { u->read_event_handler(r, u); } ngx_http_run_posted_requests(c); } static void ngx_http_upstream_rd_check_broken_connection(ngx_http_request_t *r) { ngx_http_upstream_check_broken_connection(r, r->connection->read); } static void ngx_http_upstream_wr_check_broken_connection(ngx_http_request_t *r) { ngx_http_upstream_check_broken_connection(r, r->connection->write); } static void ngx_http_upstream_check_broken_connection(ngx_http_request_t *r, ngx_event_t *ev) { int n; char buf[1]; ngx_err_t err; ngx_int_t event; ngx_connection_t *c; ngx_http_upstream_t *u; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ev->log, 0, "http upstream check client, write event:%d, \"%V\"", ev->write, &r->uri); c = r->connection; u = r->upstream; if (c->error) { if ((ngx_event_flags & NGX_USE_LEVEL_EVENT) && ev->active) { event = ev->write ? NGX_WRITE_EVENT : NGX_READ_EVENT; if (ngx_del_event(ev, event, 0) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } } if (!u->cacheable) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_CLIENT_CLOSED_REQUEST); } return; } #if (NGX_HTTP_V2) if (r->stream) { return; } #endif #if (NGX_HAVE_KQUEUE) if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { if (!ev->pending_eof) { return; } ev->eof = 1; c->error = 1; if (ev->kq_errno) { ev->error = 1; } if (!u->cacheable && u->peer.connection) { ngx_log_error(NGX_LOG_INFO, ev->log, ev->kq_errno, "kevent() reported that client prematurely closed " "connection, so upstream connection is closed too"); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_CLIENT_CLOSED_REQUEST); return; } ngx_log_error(NGX_LOG_INFO, ev->log, ev->kq_errno, "kevent() reported that client prematurely closed " "connection"); if (u->peer.connection == NULL) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_CLIENT_CLOSED_REQUEST); } return; } #endif #if (NGX_HAVE_EPOLLRDHUP) if ((ngx_event_flags & NGX_USE_EPOLL_EVENT) && ngx_use_epoll_rdhup) { socklen_t len; if (!ev->pending_eof) { return; } ev->eof = 1; c->error = 1; err = 0; len = sizeof(ngx_err_t); /* * BSDs and Linux return 0 and set a pending error in err * Solaris returns -1 and sets errno */ if (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_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; ngx_http_core_loc_conf_t *clcf; r->connection->log->action = "connecting to upstream"; if (u->state && u->state->response_time == (ngx_msec_t) -1) { u->state->response_time = ngx_current_msec - u->start_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->start_time = ngx_current_msec; u->state->response_time = (ngx_msec_t) -1; 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->requests++; 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 (r->connection->tcp_nopush == NGX_TCP_NOPUSH_DISABLED) { c->tcp_nopush = NGX_TCP_NOPUSH_DISABLED; } 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 */ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); u->writer.out = NULL; u->writer.last = &u->writer.out; u->writer.connection = c; u->writer.limit = clcf->sendfile_max_chunk; if (u->request_sent) { if (ngx_http_upstream_reinit(r, u) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } } if (r->request_body && r->request_body->buf && r->request_body->temp_file && r == r->main) { /* * the r->request_body->buf can be reused for one request only, * the subrequests should allocate their own temporary bufs */ u->output.free = ngx_alloc_chain_link(r->pool); if (u->output.free == NULL) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } u->output.free->buf = r->request_body->buf; u->output.free->next = NULL; u->output.allocated = 1; r->request_body->buf->pos = r->request_body->buf->start; r->request_body->buf->last = r->request_body->buf->start; r->request_body->buf->tag = u->output.tag; } u->request_sent = 0; u->request_body_sent = 0; u->request_body_blocked = 0; if (rc == NGX_AGAIN) { ngx_add_timer(c->write, u->conf->connect_timeout); return; } #if (NGX_HTTP_SSL) if (u->ssl && c->ssl == NULL) { ngx_http_upstream_ssl_init_connection(r, u, c); return; } #endif ngx_http_upstream_send_request(r, u, 1); } #if (NGX_HTTP_SSL) static void ngx_http_upstream_ssl_init_connection(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_connection_t *c) { ngx_int_t rc; ngx_http_core_loc_conf_t *clcf; if (ngx_http_upstream_test_connect(c) != NGX_OK) { ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); return; } if (ngx_ssl_create_connection(u->conf->ssl, c, NGX_SSL_BUFFER|NGX_SSL_CLIENT) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } 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_certificate && u->conf->ssl_certificate->value.len && (u->conf->ssl_certificate->lengths || u->conf->ssl_certificate_key->lengths)) { if (ngx_http_upstream_ssl_certificate(r, u, c) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } } if (u->conf->ssl_session_reuse) { c->ssl->save_session = ngx_http_upstream_ssl_save_session; if (u->peer.set_session(&u->peer, u->peer.data) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } /* abbreviated SSL handshake may interact badly with Nagle */ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (clcf->tcp_nodelay && ngx_tcp_nodelay(c) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } } r->connection->log->action = "SSL handshaking to upstream"; rc = ngx_ssl_handshake(c); if (rc == NGX_AGAIN) { if (!c->write->timer_set) { ngx_add_timer(c->write, u->conf->connect_timeout); } c->ssl->handler = ngx_http_upstream_ssl_handshake_handler; return; } ngx_http_upstream_ssl_handshake(r, u, c); } static void ngx_http_upstream_ssl_handshake_handler(ngx_connection_t *c) { ngx_http_request_t *r; ngx_http_upstream_t *u; r = c->data; u = r->upstream; c = r->connection; ngx_http_set_log_request(c->log, r); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream ssl handshake: \"%V?%V\"", &r->uri, &r->args); ngx_http_upstream_ssl_handshake(r, u, u->peer.connection); ngx_http_run_posted_requests(c); } static void ngx_http_upstream_ssl_handshake(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_connection_t *c) { long rc; if (c->ssl->handshaked) { if (u->conf->ssl_verify) { rc = SSL_get_verify_result(c->ssl->connection); if (rc != X509_V_OK) { ngx_log_error(NGX_LOG_ERR, c->log, 0, "upstream SSL certificate verify error: (%l:%s)", rc, X509_verify_cert_error_string(rc)); goto failed; } if (ngx_ssl_check_host(c, &u->ssl_name) != NGX_OK) { ngx_log_error(NGX_LOG_ERR, c->log, 0, "upstream SSL certificate does not match \"%V\"", &u->ssl_name); goto failed; } } if (!c->ssl->sendfile) { c->sendfile = 0; u->output.sendfile = 0; } c->write->handler = ngx_http_upstream_handler; c->read->handler = ngx_http_upstream_handler; ngx_http_upstream_send_request(r, u, 1); return; } if (c->write->timedout) { ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT); return; } failed: ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); } static void ngx_http_upstream_ssl_save_session(ngx_connection_t *c) { ngx_http_request_t *r; ngx_http_upstream_t *u; if (c->idle) { return; } r = c->data; u = r->upstream; c = r->connection; ngx_http_set_log_request(c->log, r); u->peer.save_session(&u->peer, u->peer.data); } static ngx_int_t ngx_http_upstream_ssl_name(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_connection_t *c) { u_char *p, *last; ngx_str_t name; if (u->conf->ssl_name) { if (ngx_http_complex_value(r, u->conf->ssl_name, &name) != NGX_OK) { return NGX_ERROR; } } else { name = u->ssl_name; } if (name.len == 0) { goto done; } /* * ssl name here may contain port, notably if derived from $proxy_host * or $http_host; we have to strip it */ p = name.data; last = name.data + name.len; if (*p == '[') { p = ngx_strlchr(p, last, ']'); if (p == NULL) { p = name.data; } } p = ngx_strlchr(p, last, ':'); if (p != NULL) { name.len = p - name.data; } if (!u->conf->ssl_server_name) { goto done; } #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME /* as per RFC 6066, literal IPv4 and IPv6 addresses are not permitted */ if (name.len == 0 || *name.data == '[') { goto done; } if (ngx_inet_addr(name.data, name.len) != INADDR_NONE) { goto done; } /* * SSL_set_tlsext_host_name() needs a null-terminated string, * hence we explicitly null-terminate name here */ p = ngx_pnalloc(r->pool, name.len + 1); if (p == NULL) { return NGX_ERROR; } (void) ngx_cpystrn(p, name.data, name.len + 1); name.data = p; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "upstream SSL server name: \"%s\"", name.data); if (SSL_set_tlsext_host_name(c->ssl->connection, (char *) name.data) == 0) { ngx_ssl_error(NGX_LOG_ERR, r->connection->log, 0, "SSL_set_tlsext_host_name(\"%s\") failed", name.data); return NGX_ERROR; } #endif done: u->ssl_name = name; return NGX_OK; } static ngx_int_t ngx_http_upstream_ssl_certificate(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_connection_t *c) { ngx_str_t cert, key; if (ngx_http_complex_value(r, u->conf->ssl_certificate, &cert) != NGX_OK) { return NGX_ERROR; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream ssl cert: \"%s\"", cert.data); if (*cert.data == '\0') { return NGX_OK; } if (ngx_http_complex_value(r, u->conf->ssl_certificate_key, &key) != NGX_OK) { return NGX_ERROR; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream ssl key: \"%s\"", key.data); if (ngx_ssl_connection_certificate(c, r->pool, &cert, &key, u->conf->ssl_passwords) != NGX_OK) { return NGX_ERROR; } return NGX_OK; } #endif 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; u->error = 0; ngx_memzero(&u->headers_in, sizeof(ngx_http_upstream_headers_in_t)); u->headers_in.content_length_n = -1; u->headers_in.last_modified_time = -1; if (ngx_list_init(&u->headers_in.headers, r->pool, 8, sizeof(ngx_table_elt_t)) != NGX_OK) { return NGX_ERROR; } if (ngx_list_init(&u->headers_in.trailers, r->pool, 2, sizeof(ngx_table_elt_t)) != NGX_OK) { return NGX_ERROR; } /* reinit the request chain */ file_pos = 0; for (cl = u->request_bufs; cl; cl = cl->next) { cl->buf->pos = cl->buf->start; /* there is at most one file */ if (cl->buf->in_file) { cl->buf->file_pos = file_pos; file_pos = cl->buf->file_last; } } /* reinit the subrequest's ngx_output_chain() context */ if (r->request_body && r->request_body->temp_file && r != r->main && u->output.buf) { u->output.free = ngx_alloc_chain_link(r->pool); if (u->output.free == NULL) { return NGX_ERROR; } u->output.free->buf = u->output.buf; u->output.free->next = NULL; u->output.buf->pos = u->output.buf->start; u->output.buf->last = u->output.buf->start; } u->output.buf = NULL; u->output.in = NULL; u->output.busy = NULL; /* reinit u->buffer */ u->buffer.pos = u->buffer.start; #if (NGX_HTTP_CACHE) if (r->cache) { u->buffer.pos += r->cache->header_start; } #endif u->buffer.last = u->buffer.pos; return NGX_OK; } static void ngx_http_upstream_send_request(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_uint_t do_write) { ngx_int_t rc; ngx_connection_t *c; c = u->peer.connection; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream send request"); if (u->state->connect_time == (ngx_msec_t) -1) { u->state->connect_time = ngx_current_msec - u->start_time; } if (!u->request_sent && ngx_http_upstream_test_connect(c) != NGX_OK) { ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); return; } c->log->action = "sending request to upstream"; rc = ngx_http_upstream_send_request_body(r, u, do_write); if (rc == NGX_ERROR) { ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); return; } if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { ngx_http_upstream_finalize_request(r, u, rc); return; } if (rc == NGX_AGAIN) { if (!c->write->ready || u->request_body_blocked) { ngx_add_timer(c->write, u->conf->send_timeout); } else if (c->write->timer_set) { ngx_del_timer(c->write); } if (ngx_handle_write_event(c->write, u->conf->send_lowat) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (c->write->ready && c->tcp_nopush == NGX_TCP_NOPUSH_SET) { if (ngx_tcp_push(c->fd) == -1) { ngx_log_error(NGX_LOG_CRIT, c->log, ngx_socket_errno, ngx_tcp_push_n " failed"); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } c->tcp_nopush = NGX_TCP_NOPUSH_UNSET; } if (c->read->ready) { ngx_post_event(c->read, &ngx_posted_events); } return; } /* rc == NGX_OK */ if (c->write->timer_set) { ngx_del_timer(c->write); } if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) { if (ngx_tcp_push(c->fd) == -1) { ngx_log_error(NGX_LOG_CRIT, c->log, ngx_socket_errno, ngx_tcp_push_n " failed"); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } c->tcp_nopush = NGX_TCP_NOPUSH_UNSET; } if (!u->conf->preserve_output) { u->write_event_handler = ngx_http_upstream_dummy_handler; } if (ngx_handle_write_event(c->write, 0) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (!u->request_body_sent) { u->request_body_sent = 1; if (u->header_sent) { return; } ngx_add_timer(c->read, u->conf->read_timeout); if (c->read->ready) { ngx_http_upstream_process_header(r, u); return; } } } static ngx_int_t ngx_http_upstream_send_request_body(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_uint_t do_write) { ngx_int_t rc; ngx_chain_t *out, *cl, *ln; ngx_connection_t *c; ngx_http_core_loc_conf_t *clcf; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http upstream send request body"); if (!r->request_body_no_buffering) { /* buffered request body */ if (!u->request_sent) { u->request_sent = 1; out = u->request_bufs; } else { out = NULL; } rc = ngx_output_chain(&u->output, out); if (rc == NGX_AGAIN) { u->request_body_blocked = 1; } else { u->request_body_blocked = 0; } return rc; } if (!u->request_sent) { u->request_sent = 1; out = u->request_bufs; if (r->request_body->bufs) { for (cl = out; cl->next; cl = cl->next) { /* void */ } cl->next = r->request_body->bufs; r->request_body->bufs = NULL; } c = u->peer.connection; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (clcf->tcp_nodelay && ngx_tcp_nodelay(c) != NGX_OK) { return NGX_ERROR; } r->read_event_handler = ngx_http_upstream_read_request_handler; } else { out = NULL; } for ( ;; ) { if (do_write) { rc = ngx_output_chain(&u->output, out); if (rc == NGX_ERROR) { return NGX_ERROR; } while (out) { ln = out; out = out->next; ngx_free_chain(r->pool, ln); } if (rc == NGX_AGAIN) { u->request_body_blocked = 1; } else { u->request_body_blocked = 0; } if (rc == NGX_OK && !r->reading_body) { break; } } if (r->reading_body) { /* read client request body */ rc = ngx_http_read_unbuffered_request_body(r); if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { return rc; } out = r->request_body->bufs; r->request_body->bufs = NULL; } /* stop if there is nothing to send */ if (out == NULL) { rc = NGX_AGAIN; break; } do_write = 1; } if (!r->reading_body) { if (!u->store && !r->post_action && !u->conf->ignore_client_abort) { r->read_event_handler = ngx_http_upstream_rd_check_broken_connection; } } return rc; } static void ngx_http_upstream_send_request_handler(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_connection_t *c; c = u->peer.connection; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http upstream send request handler"); if (c->write->timedout) { ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT); return; } #if (NGX_HTTP_SSL) if (u->ssl && c->ssl == NULL) { ngx_http_upstream_ssl_init_connection(r, u, c); return; } #endif if (u->header_sent && !u->conf->preserve_output) { u->write_event_handler = ngx_http_upstream_dummy_handler; (void) ngx_handle_write_event(c->write, 0); return; } ngx_http_upstream_send_request(r, u, 1); } static void ngx_http_upstream_read_request_handler(ngx_http_request_t *r) { ngx_connection_t *c; ngx_http_upstream_t *u; c = r->connection; u = r->upstream; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http upstream read request handler"); if (c->read->timedout) { c->timedout = 1; ngx_http_upstream_finalize_request(r, u, NGX_HTTP_REQUEST_TIME_OUT); return; } ngx_http_upstream_send_request(r, u, 0); } static void ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u) { ssize_t n; ngx_int_t rc; ngx_connection_t *c; c = u->peer.connection; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream process header"); c->log->action = "reading response header from upstream"; if (c->read->timedout) { ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT); return; } if (!u->request_sent && ngx_http_upstream_test_connect(c) != NGX_OK) { ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); return; } if (u->buffer.start == NULL) { u->buffer.start = ngx_palloc(r->pool, u->conf->buffer_size); if (u->buffer.start == NULL) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } u->buffer.pos = u->buffer.start; u->buffer.last = u->buffer.start; u->buffer.end = u->buffer.start + u->conf->buffer_size; u->buffer.temporary = 1; u->buffer.tag = u->output.tag; if (ngx_list_init(&u->headers_in.headers, r->pool, 8, sizeof(ngx_table_elt_t)) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (ngx_list_init(&u->headers_in.trailers, r->pool, 2, sizeof(ngx_table_elt_t)) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } #if (NGX_HTTP_CACHE) if (r->cache) { u->buffer.pos += r->cache->header_start; u->buffer.last = u->buffer.pos; } #endif } for ( ;; ) { n = c->recv(c, u->buffer.last, u->buffer.end - u->buffer.last); if (n == NGX_AGAIN) { #if 0 ngx_add_timer(rev, u->read_timeout); #endif if (ngx_handle_read_event(c->read, 0) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } return; } if (n == 0) { ngx_log_error(NGX_LOG_ERR, c->log, 0, "upstream prematurely closed connection"); } if (n == NGX_ERROR || n == 0) { ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); return; } u->state->bytes_received += n; u->buffer.last += n; #if 0 u->valid_header_in = 0; u->peer.cached = 0; #endif rc = u->process_header(r); if (rc == NGX_AGAIN) { if (u->buffer.last == u->buffer.end) { ngx_log_error(NGX_LOG_ERR, c->log, 0, "upstream sent too big header"); ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_INVALID_HEADER); return; } continue; } break; } if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) { ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_INVALID_HEADER); return; } if (rc == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } /* rc == NGX_OK */ u->state->header_time = ngx_current_msec - u->start_time; if (u->headers_in.status_n >= NGX_HTTP_SPECIAL_RESPONSE) { if (ngx_http_upstream_test_next(r, u) == NGX_OK) { return; } if (ngx_http_upstream_intercept_errors(r, u) == NGX_OK) { return; } } if (ngx_http_upstream_process_headers(r, u) != NGX_OK) { return; } ngx_http_upstream_send_response(r, u); } static ngx_int_t ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_msec_t timeout; ngx_uint_t status, mask; ngx_http_upstream_next_t *un; status = u->headers_in.status_n; for (un = ngx_http_upstream_next_errors; un->status; un++) { if (status != un->status) { continue; } timeout = u->conf->next_upstream_timeout; if (u->request_sent && (r->method & (NGX_HTTP_POST|NGX_HTTP_LOCK|NGX_HTTP_PATCH))) { mask = un->mask | NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT; } else { mask = un->mask; } if (u->peer.tries > 1 && ((u->conf->next_upstream & mask) == mask) && !(u->request_sent && r->request_body_no_buffering) && !(timeout && ngx_current_msec - u->peer.start_time >= timeout)) { ngx_http_upstream_next(r, u, un->mask); return NGX_OK; } #if (NGX_HTTP_CACHE) if (u->cache_status == NGX_HTTP_CACHE_EXPIRED && (u->conf->cache_use_stale & un->mask)) { ngx_int_t rc; rc = u->reinit_request(r); if (rc != NGX_OK) { ngx_http_upstream_finalize_request(r, u, rc); return NGX_OK; } u->cache_status = NGX_HTTP_CACHE_STALE; rc = ngx_http_upstream_cache_send(r, u); if (rc == NGX_DONE) { return NGX_OK; } if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) { rc = NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_http_upstream_finalize_request(r, u, rc); return NGX_OK; } #endif break; } #if (NGX_HTTP_CACHE) if (status == NGX_HTTP_NOT_MODIFIED && u->cache_status == NGX_HTTP_CACHE_EXPIRED && u->conf->cache_revalidate) { time_t now, valid, updating, error; ngx_int_t rc; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http upstream not modified"); now = ngx_time(); valid = r->cache->valid_sec; updating = r->cache->updating_sec; error = r->cache->error_sec; rc = u->reinit_request(r); if (rc != NGX_OK) { ngx_http_upstream_finalize_request(r, u, rc); return NGX_OK; } u->cache_status = NGX_HTTP_CACHE_REVALIDATED; rc = ngx_http_upstream_cache_send(r, u); if (rc == NGX_DONE) { return NGX_OK; } if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) { rc = NGX_HTTP_INTERNAL_SERVER_ERROR; } if (valid == 0) { valid = r->cache->valid_sec; updating = r->cache->updating_sec; error = r->cache->error_sec; } if (valid == 0) { valid = ngx_http_file_cache_valid(u->conf->cache_valid, u->headers_in.status_n); if (valid) { valid = now + valid; } } if (valid) { r->cache->valid_sec = valid; r->cache->updating_sec = updating; r->cache->error_sec = error; r->cache->date = now; ngx_http_file_cache_update_header(r); } ngx_http_upstream_finalize_request(r, u, rc); return NGX_OK; } #endif return NGX_DECLINED; } static ngx_int_t ngx_http_upstream_intercept_errors(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_int_t status; ngx_uint_t i; ngx_table_elt_t *h, *ho, **ph; 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 = u->headers_in.www_authenticate; ph = &r->headers_out.www_authenticate; while (h) { ho = ngx_list_push(&r->headers_out.headers); if (ho == NULL) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return NGX_OK; } *ho = *h; ho->next = NULL; *ph = ho; ph = &ho->next; h = h->next; } } #if (NGX_HTTP_CACHE) if (r->cache) { if (u->headers_in.no_cache || u->headers_in.expired) { u->cacheable = 0; } if (u->cacheable) { time_t valid; valid = r->cache->valid_sec; if (valid == 0) { valid = ngx_http_file_cache_valid(u->conf->cache_valid, status); if (valid) { r->cache->valid_sec = ngx_time() + valid; } } if (valid) { r->cache->error = status; } } ngx_http_file_cache_free(r->cache, u->pipe->temp_file); } #endif ngx_http_upstream_finalize_request(r, u, status); return NGX_OK; } } return NGX_DECLINED; } static ngx_int_t ngx_http_upstream_test_connect(ngx_connection_t *c) { int err; socklen_t len; #if (NGX_HAVE_KQUEUE) if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { if (c->write->pending_eof || c->read->pending_eof) { if (c->write->pending_eof) { err = c->write->kq_errno; } else { err = c->read->kq_errno; } c->log->action = "connecting to upstream"; (void) ngx_connection_error(c, err, "kevent() reported that connect() failed"); return NGX_ERROR; } } else #endif { err = 0; len = sizeof(int); /* * BSDs and Linux return 0 and set a pending error in err * Solaris returns -1 and sets errno */ if (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.no_cache || u->headers_in.expired) { u->cacheable = 0; } 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; } if (h[i].hash == 0) { continue; } 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 (h[i].hash == 0) { continue; } if (ngx_hash_find(&u->conf->hide_headers_hash, h[i].hash, h[i].lowcase_key, h[i].key.len)) { continue; } hh = ngx_hash_find(&umcf->headers_in_hash, h[i].hash, h[i].lowcase_key, h[i].key.len); if (hh) { if (hh->copy_handler(r, &h[i], hh->conf) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return NGX_DONE; } continue; } if (ngx_http_upstream_copy_header_line(r, &h[i], 0) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return NGX_DONE; } } if (r->headers_out.server && r->headers_out.server->value.data == NULL) { r->headers_out.server->hash = 0; } if (r->headers_out.date && r->headers_out.date->value.data == NULL) { r->headers_out.date->hash = 0; } r->headers_out.status = u->headers_in.status_n; r->headers_out.status_line = u->headers_in.status_line; r->headers_out.content_length_n = u->headers_in.content_length_n; r->disable_not_modified = !u->cacheable; if (u->conf->force_ranges) { r->allow_ranges = 1; r->single_range = 1; #if (NGX_HTTP_CACHE) if (r->cached) { r->single_range = 0; } #endif } u->length = -1; return NGX_OK; } static ngx_int_t ngx_http_upstream_process_trailers(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_uint_t i; ngx_list_part_t *part; ngx_table_elt_t *h, *ho; if (!u->conf->pass_trailers) { return NGX_OK; } part = &u->headers_in.trailers.part; h = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; h = part->elts; i = 0; } if (ngx_hash_find(&u->conf->hide_headers_hash, h[i].hash, h[i].lowcase_key, h[i].key.len)) { continue; } ho = ngx_list_push(&r->headers_out.trailers); if (ho == NULL) { return NGX_ERROR; } *ho = h[i]; } return NGX_OK; } static void ngx_http_upstream_send_response(ngx_http_request_t *r, ngx_http_upstream_t *u) { ssize_t n; ngx_int_t rc; ngx_event_pipe_t *p; ngx_connection_t *c; ngx_http_core_loc_conf_t *clcf; rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->post_action) { ngx_http_upstream_finalize_request(r, u, rc); return; } u->header_sent = 1; if (u->upgrade) { #if (NGX_HTTP_CACHE) if (r->cache) { ngx_http_file_cache_free(r->cache, u->pipe->temp_file); } #endif ngx_http_upstream_upgrade(r, u); return; } c = r->connection; if (r->header_only) { if (!u->buffering) { ngx_http_upstream_finalize_request(r, u, rc); return; } if (!u->cacheable && !u->store) { ngx_http_upstream_finalize_request(r, u, rc); return; } u->pipe->downstream_error = 1; } if (r->request_body && r->request_body->temp_file && r == r->main && !r->preserve_body && !u->conf->preserve_output) { ngx_pool_run_cleanup_file(r->pool, r->request_body->temp_file->file.fd); r->request_body->temp_file->file.fd = NGX_INVALID_FILE; } clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (!u->buffering) { #if (NGX_HTTP_CACHE) if (r->cache) { ngx_http_file_cache_free(r->cache, u->pipe->temp_file); } #endif if (u->input_filter == NULL) { u->input_filter_init = ngx_http_upstream_non_buffered_filter_init; u->input_filter = ngx_http_upstream_non_buffered_filter; u->input_filter_ctx = r; } u->read_event_handler = ngx_http_upstream_process_non_buffered_upstream; r->write_event_handler = ngx_http_upstream_process_non_buffered_downstream; r->limit_rate = 0; r->limit_rate_set = 1; if (u->input_filter_init(u->input_filter_ctx) == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } if (clcf->tcp_nodelay && ngx_tcp_nodelay(c) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } n = u->buffer.last - u->buffer.pos; if (n) { u->buffer.last = u->buffer.pos; u->state->response_length += n; if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } ngx_http_upstream_process_non_buffered_downstream(r); } else { u->buffer.pos = u->buffer.start; u->buffer.last = u->buffer.start; if (ngx_http_send_special(r, NGX_HTTP_FLUSH) == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } ngx_http_upstream_process_non_buffered_upstream(r, u); } return; } /* TODO: preallocate event_pipe bufs, look "Content-Length" */ #if (NGX_HTTP_CACHE) if (r->cache && r->cache->file.fd != NGX_INVALID_FILE) { ngx_pool_run_cleanup_file(r->pool, r->cache->file.fd); r->cache->file.fd = NGX_INVALID_FILE; } switch (ngx_http_test_predicates(r, u->conf->no_cache)) { case NGX_ERROR: ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; case NGX_DECLINED: u->cacheable = 0; break; default: /* NGX_OK */ if (u->cache_status == NGX_HTTP_CACHE_BYPASS) { /* create cache if previously bypassed */ if (ngx_http_file_cache_create(r) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } } break; } if (u->cacheable) { time_t now, valid; now = ngx_time(); valid = r->cache->valid_sec; if (valid == 0) { valid = ngx_http_file_cache_valid(u->conf->cache_valid, u->headers_in.status_n); if (valid) { r->cache->valid_sec = now + valid; } } if (valid) { r->cache->date = now; r->cache->body_start = (u_short) (u->buffer.pos - u->buffer.start); if (u->headers_in.status_n == NGX_HTTP_OK || u->headers_in.status_n == NGX_HTTP_PARTIAL_CONTENT) { r->cache->last_modified = u->headers_in.last_modified_time; if (u->headers_in.etag) { r->cache->etag = u->headers_in.etag->value; } else { ngx_str_null(&r->cache->etag); } } else { r->cache->last_modified = -1; ngx_str_null(&r->cache->etag); } if (ngx_http_file_cache_set_header(r, u->buffer.start) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } } else { u->cacheable = 0; } } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http cacheable: %d", u->cacheable); if (u->cacheable == 0 && r->cache) { ngx_http_file_cache_free(r->cache, u->pipe->temp_file); } if (r->header_only && !u->cacheable && !u->store) { ngx_http_upstream_finalize_request(r, u, 0); return; } #endif p = u->pipe; p->output_filter = ngx_http_upstream_output_filter; p->output_ctx = r; p->tag = u->output.tag; p->bufs = u->conf->bufs; p->busy_size = u->conf->busy_buffers_size; p->upstream = u->peer.connection; p->downstream = c; p->pool = r->pool; p->log = c->log; p->limit_rate = u->conf->limit_rate; p->start_sec = ngx_time(); p->cacheable = u->cacheable || u->store; p->temp_file = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t)); if (p->temp_file == NULL) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } p->temp_file->file.fd = NGX_INVALID_FILE; p->temp_file->file.log = c->log; p->temp_file->path = u->conf->temp_path; p->temp_file->pool = r->pool; if (p->cacheable) { p->temp_file->persistent = 1; #if (NGX_HTTP_CACHE) if (r->cache && !r->cache->file_cache->use_temp_path) { p->temp_file->path = r->cache->file_cache->path; p->temp_file->file.name = r->cache->file.name; } #endif } else { p->temp_file->log_level = NGX_LOG_WARN; p->temp_file->warn = "an upstream response is buffered " "to a temporary file"; } p->max_temp_file_size = u->conf->max_temp_file_size; p->temp_file_write_size = u->conf->temp_file_write_size; #if (NGX_THREADS) if (clcf->aio == NGX_HTTP_AIO_THREADS && clcf->aio_write) { p->thread_handler = ngx_http_upstream_thread_handler; p->thread_ctx = r; } #endif p->preread_bufs = ngx_alloc_chain_link(r->pool); if (p->preread_bufs == NULL) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } p->preread_bufs->buf = &u->buffer; p->preread_bufs->next = NULL; u->buffer.recycled = 1; p->preread_size = u->buffer.last - u->buffer.pos; if (u->cacheable) { p->buf_to_file = ngx_calloc_buf(r->pool); if (p->buf_to_file == NULL) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } p->buf_to_file->start = u->buffer.start; p->buf_to_file->pos = u->buffer.start; p->buf_to_file->last = u->buffer.pos; p->buf_to_file->temporary = 1; } if (ngx_event_flags & NGX_USE_IOCP_EVENT) { /* the posted aio operation may corrupt a shadow buffer */ p->single_buf = 1; } /* TODO: p->free_bufs = 0 if use ngx_create_chain_of_bufs() */ p->free_bufs = 1; /* * event_pipe would do u->buffer.last += p->preread_size * as though these bytes were read */ u->buffer.last = u->buffer.pos; if (u->conf->cyclic_temp_file) { /* * we need to disable the use of sendfile() if we use cyclic temp file * because the writing a new data may interfere with sendfile() * that uses the same kernel file pages (at least on FreeBSD) */ p->cyclic_temp_file = 1; c->sendfile = 0; } else { p->cyclic_temp_file = 0; } p->read_timeout = u->conf->read_timeout; p->send_timeout = clcf->send_timeout; p->send_lowat = clcf->send_lowat; p->length = -1; if (u->input_filter_init && u->input_filter_init(p->input_ctx) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } u->read_event_handler = ngx_http_upstream_process_upstream; r->write_event_handler = ngx_http_upstream_process_downstream; ngx_http_upstream_process_upstream(r, u); } static void ngx_http_upstream_upgrade(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_connection_t *c; ngx_http_core_loc_conf_t *clcf; c = r->connection; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); /* TODO: prevent upgrade if not requested or not possible */ if (r != r->main) { ngx_log_error(NGX_LOG_ERR, c->log, 0, "connection upgrade in subrequest"); ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } r->keepalive = 0; c->log->action = "proxying upgraded connection"; u->read_event_handler = ngx_http_upstream_upgraded_read_upstream; u->write_event_handler = ngx_http_upstream_upgraded_write_upstream; r->read_event_handler = ngx_http_upstream_upgraded_read_downstream; r->write_event_handler = ngx_http_upstream_upgraded_write_downstream; if (clcf->tcp_nodelay) { if (ngx_tcp_nodelay(c) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } if (ngx_tcp_nodelay(u->peer.connection) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } } if (ngx_http_send_special(r, NGX_HTTP_FLUSH) == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } if (u->peer.connection->read->ready || u->buffer.pos != u->buffer.last) { ngx_post_event(c->read, &ngx_posted_events); ngx_http_upstream_process_upgraded(r, 1, 1); return; } ngx_http_upstream_process_upgraded(r, 0, 1); } static void ngx_http_upstream_upgraded_read_downstream(ngx_http_request_t *r) { ngx_http_upstream_process_upgraded(r, 0, 0); } static void ngx_http_upstream_upgraded_write_downstream(ngx_http_request_t *r) { ngx_http_upstream_process_upgraded(r, 1, 1); } static void ngx_http_upstream_upgraded_read_upstream(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_http_upstream_process_upgraded(r, 1, 0); } static void ngx_http_upstream_upgraded_write_upstream(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_http_upstream_process_upgraded(r, 0, 1); } static void ngx_http_upstream_process_upgraded(ngx_http_request_t *r, ngx_uint_t from_upstream, ngx_uint_t do_write) { size_t size; ssize_t n; ngx_buf_t *b; ngx_uint_t flags; ngx_connection_t *c, *downstream, *upstream, *dst, *src; ngx_http_upstream_t *u; ngx_http_core_loc_conf_t *clcf; c = r->connection; u = r->upstream; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream process upgraded, fu:%ui", from_upstream); downstream = c; upstream = u->peer.connection; if (downstream->write->timedout) { c->timedout = 1; ngx_connection_error(c, NGX_ETIMEDOUT, "client timed out"); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_REQUEST_TIME_OUT); return; } if (upstream->read->timedout || upstream->write->timedout) { ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out"); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_GATEWAY_TIME_OUT); return; } if (from_upstream) { src = upstream; dst = downstream; b = &u->buffer; } else { src = downstream; dst = upstream; b = &u->from_client; if (r->header_in->last > r->header_in->pos) { b = r->header_in; b->end = b->last; do_write = 1; } if (b->start == NULL) { b->start = ngx_palloc(r->pool, u->conf->buffer_size); if (b->start == NULL) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } b->pos = b->start; b->last = b->start; b->end = b->start + u->conf->buffer_size; b->temporary = 1; b->tag = u->output.tag; } } for ( ;; ) { if (do_write) { size = b->last - b->pos; if (size && dst->write->ready) { n = dst->send(dst, b->pos, size); if (n == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } if (n > 0) { b->pos += n; if (b->pos == b->last) { b->pos = b->start; b->last = b->start; } } } } size = b->end - b->last; if (size && src->read->ready) { n = src->recv(src, b->last, size); if (n == NGX_AGAIN || n == 0) { break; } if (n > 0) { do_write = 1; b->last += n; if (from_upstream) { u->state->bytes_received += n; } continue; } if (n == NGX_ERROR) { src->read->eof = 1; } } break; } if ((upstream->read->eof && u->buffer.pos == u->buffer.last) || (downstream->read->eof && u->from_client.pos == u->from_client.last) || (downstream->read->eof && upstream->read->eof)) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream upgraded done"); ngx_http_upstream_finalize_request(r, u, 0); return; } clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (ngx_handle_write_event(upstream->write, u->conf->send_lowat) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } if (upstream->write->active && !upstream->write->ready) { ngx_add_timer(upstream->write, u->conf->send_timeout); } else if (upstream->write->timer_set) { ngx_del_timer(upstream->write); } if (upstream->read->eof || upstream->read->error) { flags = NGX_CLOSE_EVENT; } else { flags = 0; } if (ngx_handle_read_event(upstream->read, flags) != 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 (downstream->read->eof || downstream->read->error) { flags = NGX_CLOSE_EVENT; } else { flags = 0; } if (ngx_handle_read_event(downstream->read, flags) != 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_uint_t flags; ngx_connection_t *downstream, *upstream; ngx_http_upstream_t *u; ngx_http_core_loc_conf_t *clcf; u = r->upstream; downstream = r->connection; upstream = u->peer.connection; b = &u->buffer; do_write = do_write || u->length == 0; for ( ;; ) { if (do_write) { if (u->out_bufs || u->busy_bufs || downstream->buffered) { rc = ngx_http_output_filter(r, u->out_bufs); if (rc == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } ngx_chain_update_chains(r->pool, &u->free_bufs, &u->busy_bufs, &u->out_bufs, u->output.tag); } if (u->busy_bufs == NULL) { if (u->length == 0 || (upstream->read->eof && u->length == -1)) { ngx_http_upstream_finalize_request(r, u, 0); return; } if (upstream->read->eof) { ngx_log_error(NGX_LOG_ERR, upstream->log, 0, "upstream prematurely closed connection"); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY); return; } if (upstream->read->error || u->error) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY); return; } b->pos = b->start; b->last = b->start; } } size = b->end - b->last; if (size && upstream->read->ready) { n = upstream->recv(upstream, b->last, size); if (n == NGX_AGAIN) { break; } if (n > 0) { u->state->bytes_received += n; u->state->response_length += n; if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } } do_write = 1; continue; } break; } clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (downstream->data == r) { if (ngx_handle_write_event(downstream->write, clcf->send_lowat) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } } if (downstream->write->active && !downstream->write->ready) { ngx_add_timer(downstream->write, clcf->send_timeout); } else if (downstream->write->timer_set) { ngx_del_timer(downstream->write); } if (upstream->read->eof || upstream->read->error) { flags = NGX_CLOSE_EVENT; } else { flags = 0; } if (ngx_handle_read_event(upstream->read, flags) != 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); } } ngx_int_t ngx_http_upstream_non_buffered_filter_init(void *data) { return NGX_OK; } 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; if (u->length == 0) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "upstream sent more data than specified in " "\"Content-Length\" header"); return NGX_OK; } for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) { ll = &cl->next; } 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; } if (bytes > u->length) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "upstream sent more data than specified in " "\"Content-Length\" header"); cl->buf->last = cl->buf->pos + u->length; u->length = 0; return NGX_OK; } u->length -= bytes; return NGX_OK; } #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_connection_t *c; 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; if (r->aio) { /* * tolerate sendfile() calls if another operation is already * running; this can happen due to subrequests, multiple calls * of the next body filter from a filter, or in HTTP/2 due to * a write event on the main connection */ c = r->connection; #if (NGX_HTTP_V2) if (r->stream) { c = r->stream->connection->connection; } #endif if (task == c->sendfile_task) { return NGX_OK; } } clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); tp = clcf->thread_pool; if (tp == NULL) { if (ngx_http_complex_value(r, clcf->thread_pool_value, &name) != NGX_OK) { return NGX_ERROR; } tp = ngx_thread_pool_get((ngx_cycle_t *) ngx_cycle, &name); if (tp == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "thread pool \"%V\" not found", &name); return NGX_ERROR; } } task->event.data = r; task->event.handler = ngx_http_upstream_thread_event_handler; if (ngx_thread_task_post(tp, task) != NGX_OK) { return NGX_ERROR; } r->main->blocked++; r->aio = 1; p->aio = 1; return NGX_OK; } static void ngx_http_upstream_thread_event_handler(ngx_event_t *ev) { ngx_connection_t *c; ngx_http_request_t *r; r = ev->data; c = r->connection; ngx_http_set_log_request(c->log, r); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream thread: \"%V?%V\"", &r->uri, &r->args); r->main->blocked--; r->aio = 0; #if (NGX_HTTP_V2) if (r->stream) { /* * for HTTP/2, update write event to make sure processing will * reach the main connection to handle sendfile() in threads */ c->write->ready = 1; c->write->active = 0; } #endif if (r->done) { /* * trigger connection event handler if the subrequest was * already finalized; this can happen if the handler is used * for sendfile() in threads */ c->write->handler(c->write); } else { r->write_event_handler(r); ngx_http_run_posted_requests(c); } } #endif static ngx_int_t ngx_http_upstream_output_filter(void *data, ngx_chain_t *chain) { ngx_int_t rc; ngx_event_pipe_t *p; ngx_http_request_t *r; r = data; p = r->upstream->pipe; rc = ngx_http_output_filter(r, chain); p->aio = r->aio; return rc; } static void ngx_http_upstream_process_downstream(ngx_http_request_t *r) { ngx_event_t *wev; ngx_connection_t *c; ngx_event_pipe_t *p; ngx_http_upstream_t *u; c = r->connection; u = r->upstream; p = u->pipe; wev = c->write; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream process downstream"); c->log->action = "sending to client"; #if (NGX_THREADS) p->aio = r->aio; #endif if (wev->timedout) { p->downstream_error = 1; c->timedout = 1; ngx_connection_error(c, NGX_ETIMEDOUT, "client timed out"); } else { if (wev->delayed) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http downstream delayed"); if (ngx_handle_write_event(wev, p->send_lowat) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); } return; } if (ngx_event_pipe(p, 1) == NGX_ABORT) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } } ngx_http_upstream_process_request(r, u); } static void ngx_http_upstream_process_upstream(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_event_t *rev; ngx_event_pipe_t *p; ngx_connection_t *c; c = u->peer.connection; p = u->pipe; rev = c->read; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream process upstream"); c->log->action = "reading upstream"; if (rev->timedout) { p->upstream_error = 1; ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out"); } else { if (rev->delayed) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream delayed"); if (ngx_handle_read_event(rev, 0) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); } return; } if (ngx_event_pipe(p, 0) == NGX_ABORT) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } } ngx_http_upstream_process_request(r, u); } static void ngx_http_upstream_process_request(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_temp_file_t *tf; ngx_event_pipe_t *p; p = u->pipe; #if (NGX_THREADS) if (p->writing && !p->aio) { /* * make sure to call ngx_event_pipe() * if there is an incomplete aio write */ if (ngx_event_pipe(p, 1) == NGX_ABORT) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } } if (p->writing) { return; } #endif if (u->peer.connection) { if (u->store) { if (p->upstream_eof || p->upstream_done) { tf = p->temp_file; if (u->headers_in.status_n == NGX_HTTP_OK && (p->upstream_done || p->length == -1) && (u->headers_in.content_length_n == -1 || u->headers_in.content_length_n == tf->offset)) { ngx_http_upstream_store(r, u); } } } #if (NGX_HTTP_CACHE) if (u->cacheable) { if (p->upstream_done) { ngx_http_file_cache_update(r, p->temp_file); } else if (p->upstream_eof) { tf = p->temp_file; if (p->length == -1 && (u->headers_in.content_length_n == -1 || u->headers_in.content_length_n == tf->offset - (off_t) r->cache->body_start)) { ngx_http_file_cache_update(r, tf); } else { ngx_http_file_cache_free(r->cache, tf); } } else if (p->upstream_error) { ngx_http_file_cache_free(r->cache, p->temp_file); } } #endif if (p->upstream_done || p->upstream_eof || p->upstream_error) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http upstream exit: %p", p->out); if (p->upstream_done || (p->upstream_eof && p->length == -1)) { ngx_http_upstream_finalize_request(r, u, 0); return; } if (p->upstream_eof) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream prematurely closed connection"); } ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY); return; } } if (p->downstream_error) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http upstream downstream error"); if (!u->cacheable && !u->store && u->peer.connection) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); } } } static void ngx_http_upstream_store(ngx_http_request_t *r, ngx_http_upstream_t *u) { size_t root; time_t lm; ngx_str_t path; ngx_temp_file_t *tf; ngx_ext_rename_file_t ext; tf = u->pipe->temp_file; if (tf->file.fd == NGX_INVALID_FILE) { /* create file for empty 200 response */ tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t)); if (tf == NULL) { return; } tf->file.fd = NGX_INVALID_FILE; tf->file.log = r->connection->log; tf->path = u->conf->temp_path; tf->pool = r->pool; tf->persistent = 1; if (ngx_create_temp_file(&tf->file, tf->path, tf->pool, tf->persistent, tf->clean, tf->access) != NGX_OK) { return; } u->pipe->temp_file = tf; } ext.access = u->conf->store_access; ext.path_access = u->conf->store_access; ext.time = -1; ext.create_path = 1; ext.delete_file = 1; ext.log = r->connection->log; if (u->headers_in.last_modified) { lm = ngx_parse_http_time(u->headers_in.last_modified->value.data, u->headers_in.last_modified->value.len); if (lm != NGX_ERROR) { ext.time = lm; ext.fd = tf->file.fd; } } if (u->conf->store_lengths == NULL) { if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) { return; } } else { if (ngx_http_script_run(r, &path, u->conf->store_lengths->elts, 0, u->conf->store_values->elts) == NULL) { return; } } path.len--; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "upstream stores \"%s\" to \"%s\"", tf->file.name.data, path.data); (void) ngx_ext_rename_file(&tf->file.name, &path, &ext); u->store = 0; } static void ngx_http_upstream_dummy_handler(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http upstream dummy handler"); } static void ngx_http_upstream_next(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_uint_t ft_type) { ngx_msec_t timeout; ngx_uint_t status, state; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http next upstream, %xi", ft_type); if (u->peer.sockaddr) { if (u->peer.connection) { u->state->bytes_sent = u->peer.connection->sent; } if (ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_403 || ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_404) { state = NGX_PEER_NEXT; } else { state = NGX_PEER_FAILED; } u->peer.free(&u->peer, u->peer.data, state); u->peer.sockaddr = NULL; } if (ft_type == NGX_HTTP_UPSTREAM_FT_TIMEOUT) { ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_ETIMEDOUT, "upstream timed out"); } if (u->peer.cached && ft_type == NGX_HTTP_UPSTREAM_FT_ERROR) { /* TODO: inform balancer instead */ u->peer.tries++; } switch (ft_type) { case NGX_HTTP_UPSTREAM_FT_TIMEOUT: case NGX_HTTP_UPSTREAM_FT_HTTP_504: status = NGX_HTTP_GATEWAY_TIME_OUT; break; case NGX_HTTP_UPSTREAM_FT_HTTP_500: status = NGX_HTTP_INTERNAL_SERVER_ERROR; break; case NGX_HTTP_UPSTREAM_FT_HTTP_503: status = NGX_HTTP_SERVICE_UNAVAILABLE; break; case NGX_HTTP_UPSTREAM_FT_HTTP_403: status = NGX_HTTP_FORBIDDEN; break; case NGX_HTTP_UPSTREAM_FT_HTTP_404: status = NGX_HTTP_NOT_FOUND; break; case NGX_HTTP_UPSTREAM_FT_HTTP_429: status = NGX_HTTP_TOO_MANY_REQUESTS; break; /* * NGX_HTTP_UPSTREAM_FT_BUSY_LOCK and NGX_HTTP_UPSTREAM_FT_MAX_WAITING * never reach here */ default: status = NGX_HTTP_BAD_GATEWAY; } if (r->connection->error) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_CLIENT_CLOSED_REQUEST); return; } u->state->status = status; timeout = u->conf->next_upstream_timeout; if (u->request_sent && (r->method & (NGX_HTTP_POST|NGX_HTTP_LOCK|NGX_HTTP_PATCH))) { ft_type |= NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT; } if (u->peer.tries == 0 || ((u->conf->next_upstream & ft_type) != ft_type) || (u->request_sent && r->request_body_no_buffering) || (timeout && ngx_current_msec - u->peer.start_time >= timeout)) { #if (NGX_HTTP_CACHE) if (u->cache_status == NGX_HTTP_CACHE_EXPIRED && ((u->conf->cache_use_stale & ft_type) || r->cache->stale_error)) { ngx_int_t rc; rc = u->reinit_request(r); if (rc != NGX_OK) { ngx_http_upstream_finalize_request(r, u, rc); return; } u->cache_status = NGX_HTTP_CACHE_STALE; rc = ngx_http_upstream_cache_send(r, u); if (rc == NGX_DONE) { return; } if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) { rc = NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_http_upstream_finalize_request(r, u, rc); return; } #endif ngx_http_upstream_finalize_request(r, u, status); return; } if (u->peer.connection) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "close http upstream connection: %d", u->peer.connection->fd); #if (NGX_HTTP_SSL) if (u->peer.connection->ssl) { u->peer.connection->ssl->no_wait_shutdown = 1; u->peer.connection->ssl->no_send_shutdown = 1; (void) ngx_ssl_shutdown(u->peer.connection); } #endif if (u->peer.connection->pool) { ngx_destroy_pool(u->peer.connection->pool); } ngx_close_connection(u->peer.connection); u->peer.connection = NULL; } ngx_http_upstream_connect(r, u); } static void ngx_http_upstream_cleanup(void *data) { ngx_http_request_t *r = data; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "cleanup http upstream request: \"%V\"", &r->uri); ngx_http_upstream_finalize_request(r, r->upstream, NGX_DONE); } static void ngx_http_upstream_finalize_request(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_int_t rc) { ngx_uint_t flush; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "finalize http upstream request: %i", rc); if (u->cleanup == NULL) { /* the request was already finalized */ ngx_http_finalize_request(r, NGX_DONE); return; } *u->cleanup = NULL; u->cleanup = NULL; if (u->resolved && u->resolved->ctx) { ngx_resolve_name_done(u->resolved->ctx); u->resolved->ctx = NULL; } if (u->state && u->state->response_time == (ngx_msec_t) -1) { u->state->response_time = ngx_current_msec - u->start_time; if (u->pipe && u->pipe->read_length) { u->state->bytes_received += u->pipe->read_length - u->pipe->preread_size; u->state->response_length = u->pipe->read_length; } if (u->peer.connection) { u->state->bytes_sent = u->peer.connection->sent; } } u->finalize_request(r, rc); if (u->peer.free && u->peer.sockaddr) { u->peer.free(&u->peer, u->peer.data, 0); u->peer.sockaddr = NULL; } if (u->peer.connection) { #if (NGX_HTTP_SSL) /* TODO: do not shutdown persistent connection */ if (u->peer.connection->ssl) { /* * We send the "close notify" shutdown alert to the upstream only * and do not wait its "close notify" shutdown alert. * It is acceptable according to the TLS standard. */ u->peer.connection->ssl->no_wait_shutdown = 1; (void) ngx_ssl_shutdown(u->peer.connection); } #endif ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "close http upstream connection: %d", u->peer.connection->fd); if (u->peer.connection->pool) { ngx_destroy_pool(u->peer.connection->pool); } ngx_close_connection(u->peer.connection); } u->peer.connection = NULL; if (u->pipe && u->pipe->temp_file) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http upstream temp fd: %d", u->pipe->temp_file->file.fd); } if (u->store && u->pipe && u->pipe->temp_file && u->pipe->temp_file->file.fd != NGX_INVALID_FILE) { if (ngx_delete_file(u->pipe->temp_file->file.name.data) == NGX_FILE_ERROR) { ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno, ngx_delete_file_n " \"%s\" failed", u->pipe->temp_file->file.name.data); } } #if (NGX_HTTP_CACHE) if (r->cache) { if (u->cacheable) { if (rc == NGX_HTTP_BAD_GATEWAY || rc == NGX_HTTP_GATEWAY_TIME_OUT) { time_t valid; valid = ngx_http_file_cache_valid(u->conf->cache_valid, rc); if (valid) { r->cache->valid_sec = ngx_time() + valid; r->cache->error = rc; } } } ngx_http_file_cache_free(r->cache, u->pipe->temp_file); } #endif r->read_event_handler = ngx_http_block_reading; if (rc == NGX_DECLINED) { return; } r->connection->log->action = "sending to client"; if (!u->header_sent || rc == NGX_HTTP_REQUEST_TIME_OUT || rc == NGX_HTTP_CLIENT_CLOSED_REQUEST) { ngx_http_finalize_request(r, rc); return; } flush = 0; if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { rc = NGX_ERROR; flush = 1; } if (r->header_only || (u->pipe && u->pipe->downstream_error)) { ngx_http_finalize_request(r, rc); return; } if (rc == 0) { if (ngx_http_upstream_process_trailers(r, u) != NGX_OK) { ngx_http_finalize_request(r, NGX_ERROR); return; } rc = ngx_http_send_special(r, NGX_HTTP_LAST); } else if (flush) { r->keepalive = 0; rc = ngx_http_send_special(r, NGX_HTTP_FLUSH); } ngx_http_finalize_request(r, rc); } static ngx_int_t ngx_http_upstream_process_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { ngx_table_elt_t **ph; ph = (ngx_table_elt_t **) ((char *) &r->upstream->headers_in + offset); if (*ph) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "upstream sent duplicate header line: \"%V: %V\", " "previous value: \"%V: %V\", ignored", &h->key, &h->value, &(*ph)->key, &(*ph)->value); h->hash = 0; return NGX_OK; } *ph = h; h->next = NULL; return NGX_OK; } static ngx_int_t ngx_http_upstream_process_multi_header_lines(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); while (*ph) { ph = &(*ph)->next; } *ph = h; h->next = NULL; 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; if (u->headers_in.content_length) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent duplicate header line: \"%V: %V\", " "previous value: \"%V: %V\"", &h->key, &h->value, &u->headers_in.content_length->key, &u->headers_in.content_length->value); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } if (u->headers_in.transfer_encoding) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent \"Content-Length\" and " "\"Transfer-Encoding\" headers at the same time"); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } h->next = NULL; u->headers_in.content_length = h; u->headers_in.content_length_n = ngx_atoof(h->value.data, h->value.len); if (u->headers_in.content_length_n == NGX_ERROR) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid \"Content-Length\" header: " "\"%V: %V\"", &h->key, &h->value); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } 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; if (u->headers_in.last_modified) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "upstream sent duplicate header line: \"%V: %V\", " "previous value: \"%V: %V\", ignored", &h->key, &h->value, &u->headers_in.last_modified->key, &u->headers_in.last_modified->value); h->hash = 0; return NGX_OK; } h->next = NULL; u->headers_in.last_modified = h; u->headers_in.last_modified_time = ngx_parse_http_time(h->value.data, h->value.len); return NGX_OK; } static ngx_int_t ngx_http_upstream_process_set_cookie(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { ngx_table_elt_t **ph; ngx_http_upstream_t *u; u = r->upstream; ph = &u->headers_in.set_cookie; while (*ph) { ph = &(*ph)->next; } *ph = h; h->next = NULL; #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_table_elt_t **ph; ngx_http_upstream_t *u; u = r->upstream; ph = &u->headers_in.cache_control; while (*ph) { ph = &(*ph)->next; } *ph = h; h->next = NULL; #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; } start = h->value.data; last = start + h->value.len; if (r->cache->valid_sec != 0 && u->headers_in.x_accel_expires != NULL) { goto extensions; } 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->headers_in.no_cache = 1; return NGX_OK; } p = ngx_strlcasestrn(start, last, (u_char *) "s-maxage=", 9 - 1); offset = 9; if (p == NULL) { p = ngx_strlcasestrn(start, last, (u_char *) "max-age=", 8 - 1); offset = 8; } if (p) { n = 0; for (p += offset; p < last; p++) { if (*p == ',' || *p == ';' || *p == ' ') { break; } if (*p >= '0' && *p <= '9') { n = n * 10 + (*p - '0'); continue; } u->cacheable = 0; return NGX_OK; } if (n == 0) { u->headers_in.no_cache = 1; return NGX_OK; } r->cache->valid_sec = ngx_time() + n; u->headers_in.expired = 0; } extensions: p = ngx_strlcasestrn(start, last, (u_char *) "stale-while-revalidate=", 23 - 1); if (p) { n = 0; for (p += 23; p < last; p++) { if (*p == ',' || *p == ';' || *p == ' ') { break; } if (*p >= '0' && *p <= '9') { n = n * 10 + (*p - '0'); continue; } u->cacheable = 0; return NGX_OK; } r->cache->updating_sec = n; r->cache->error_sec = n; } p = ngx_strlcasestrn(start, last, (u_char *) "stale-if-error=", 15 - 1); if (p) { n = 0; for (p += 15; p < last; p++) { if (*p == ',' || *p == ';' || *p == ' ') { break; } if (*p >= '0' && *p <= '9') { n = n * 10 + (*p - '0'); continue; } u->cacheable = 0; return NGX_OK; } r->cache->error_sec = n; } } #endif return NGX_OK; } static ngx_int_t ngx_http_upstream_process_expires(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { ngx_http_upstream_t *u; u = r->upstream; if (u->headers_in.expires) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "upstream sent duplicate header line: \"%V: %V\", " "previous value: \"%V: %V\", ignored", &h->key, &h->value, &u->headers_in.expires->key, &u->headers_in.expires->value); h->hash = 0; return NGX_OK; } u->headers_in.expires = h; h->next = NULL; #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->headers_in.expired = 1; 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; if (u->headers_in.x_accel_expires) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "upstream sent duplicate header line: \"%V: %V\", " "previous value: \"%V: %V\", ignored", &h->key, &h->value, &u->headers_in.x_accel_expires->key, &u->headers_in.x_accel_expires->value); h->hash = 0; return NGX_OK; } u->headers_in.x_accel_expires = h; h->next = NULL; #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; u->headers_in.no_cache = 0; u->headers_in.expired = 0; return NGX_OK; } } p++; len--; n = ngx_atoi(p, len); if (n != NGX_ERROR) { r->cache->valid_sec = n; u->headers_in.no_cache = 0; u->headers_in.expired = 0; } } #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; if (u->headers_in.x_accel_limit_rate) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "upstream sent duplicate header line: \"%V: %V\", " "previous value: \"%V: %V\", ignored", &h->key, &h->value, &u->headers_in.x_accel_limit_rate->key, &u->headers_in.x_accel_limit_rate->value); h->hash = 0; return NGX_OK; } u->headers_in.x_accel_limit_rate = h; h->next = NULL; 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; r->limit_rate_set = 1; } 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) { ngx_http_upstream_t *u; u = r->upstream; if (u->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) { ngx_table_elt_t **ph; ngx_http_upstream_t *u; u = r->upstream; ph = &u->headers_in.connection; while (*ph) { ph = &(*ph)->next; } *ph = h; h->next = NULL; if (ngx_strlcasestrn(h->value.data, h->value.data + h->value.len, (u_char *) "close", 5 - 1) != NULL) { u->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) { ngx_http_upstream_t *u; u = r->upstream; if (u->headers_in.transfer_encoding) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent duplicate header line: \"%V: %V\", " "previous value: \"%V: %V\"", &h->key, &h->value, &u->headers_in.transfer_encoding->key, &u->headers_in.transfer_encoding->value); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } if (u->headers_in.content_length) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent \"Content-Length\" and " "\"Transfer-Encoding\" headers at the same time"); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } u->headers_in.transfer_encoding = h; h->next = NULL; if (h->value.len == 7 && ngx_strncasecmp(h->value.data, (u_char *) "chunked", 7) == 0) { u->headers_in.chunked = 1; } else { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent unknown \"Transfer-Encoding\": \"%V\"", &h->value); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } 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_table_elt_t **ph; ngx_http_upstream_t *u; u = r->upstream; ph = &u->headers_in.vary; while (*ph) { ph = &(*ph)->next; } *ph = h; h->next = NULL; #if (NGX_HTTP_CACHE) { u_char *p; size_t len; ngx_str_t vary; if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_VARY) { return NGX_OK; } if (r->cache == NULL || !u->cacheable) { return NGX_OK; } if (h->value.len == 1 && h->value.data[0] == '*') { u->cacheable = 0; return NGX_OK; } if (u->headers_in.vary->next) { len = 0; for (h = u->headers_in.vary; h; h = h->next) { len += h->value.len + 2; } len -= 2; p = ngx_pnalloc(r->pool, len); if (p == NULL) { return NGX_ERROR; } vary.len = len; vary.data = p; for (h = u->headers_in.vary; h; h = h->next) { p = ngx_copy(p, h->value.data, h->value.len); if (h->next == NULL) { break; } *p++ = ','; *p++ = ' '; } } else { vary = h->value; } if (vary.len > NGX_HTTP_CACHE_VARY_LEN) { u->cacheable = 0; } r->cache->vary = vary; } #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; ho->next = NULL; } 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_table_elt_t *ho, **ph; ho = ngx_list_push(&r->headers_out.headers); if (ho == NULL) { return NGX_ERROR; } *ho = *h; ph = (ngx_table_elt_t **) ((char *) &r->headers_out + offset); while (*ph) { ph = &(*ph)->next; } *ph = ho; ho->next = NULL; 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; ho->next = NULL; r->headers_out.last_modified = ho; r->headers_out.last_modified_time = r->upstream->headers_in.last_modified_time; return NGX_OK; } static ngx_int_t ngx_http_upstream_rewrite_location(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { ngx_int_t rc; ngx_table_elt_t *ho; ho = ngx_list_push(&r->headers_out.headers); if (ho == NULL) { return NGX_ERROR; } *ho = *h; ho->next = NULL; if (r->upstream->rewrite_redirect) { rc = r->upstream->rewrite_redirect(r, ho, 0); if (rc == NGX_DECLINED) { return NGX_OK; } if (rc == NGX_OK) { r->headers_out.location = ho; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "rewritten location: \"%V\"", &ho->value); } return rc; } if (ho->value.data[0] != '/') { r->headers_out.location = ho; } /* * we do not set r->headers_out.location here to avoid handling * relative redirects in ngx_http_header_filter() */ return NGX_OK; } static ngx_int_t ngx_http_upstream_rewrite_refresh(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { u_char *p; ngx_int_t rc; ngx_table_elt_t *ho; ho = ngx_list_push(&r->headers_out.headers); if (ho == NULL) { return NGX_ERROR; } *ho = *h; ho->next = NULL; 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; ho->next = NULL; 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; ho->next = NULL; r->headers_out.accept_ranges = ho; return NGX_OK; } 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 (data == 1) { ms = state[i].header_time; } else if (data == 2) { ms = state[i].connect_time; } else { ms = state[i].response_time; } if (ms != -1) { ms = ngx_max(ms, 0); p = ngx_sprintf(p, "%T.%03M", (time_t) ms / 1000, ms % 1000); } else { *p++ = '-'; } if (++i == r->upstream_states->nelts) { break; } if (state[i].peer) { *p++ = ','; *p++ = ' '; } else { *p++ = ' '; *p++ = ':'; *p++ = ' '; if (++i == r->upstream_states->nelts) { break; } continue; } } v->len = p - v->data; return NGX_OK; } static ngx_int_t ngx_http_upstream_response_length_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; size_t len; ngx_uint_t i; ngx_http_upstream_state_t *state; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; if (r->upstream_states == NULL || r->upstream_states->nelts == 0) { v->not_found = 1; return NGX_OK; } len = r->upstream_states->nelts * (NGX_OFF_T_LEN + 2); p = ngx_pnalloc(r->pool, len); if (p == NULL) { return NGX_ERROR; } v->data = p; i = 0; state = r->upstream_states->elts; for ( ;; ) { if (data == 1) { p = ngx_sprintf(p, "%O", state[i].bytes_received); } else if (data == 2) { p = ngx_sprintf(p, "%O", state[i].bytes_sent); } else { p = ngx_sprintf(p, "%O", state[i].response_length); } if (++i == r->upstream_states->nelts) { break; } if (state[i].peer) { *p++ = ','; *p++ = ' '; } else { *p++ = ' '; *p++ = ':'; *p++ = ' '; if (++i == r->upstream_states->nelts) { break; } continue; } } v->len = p - v->data; return NGX_OK; } static ngx_int_t ngx_http_upstream_header_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { if (r->upstream == NULL) { v->not_found = 1; return NGX_OK; } return ngx_http_variable_unknown_header(r, v, (ngx_str_t *) data, &r->upstream->headers_in.headers.part, sizeof("upstream_http_") - 1); } static ngx_int_t ngx_http_upstream_trailer_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { if (r->upstream == NULL) { v->not_found = 1; return NGX_OK; } return ngx_http_variable_unknown_header(r, v, (ngx_str_t *) data, &r->upstream->headers_in.trailers.part, sizeof("upstream_trailer_") - 1); } static ngx_int_t ngx_http_upstream_cookie_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_str_t *name = (ngx_str_t *) data; ngx_str_t cookie, s; if (r->upstream == NULL) { v->not_found = 1; return NGX_OK; } s.len = name->len - (sizeof("upstream_cookie_") - 1); s.data = name->data + sizeof("upstream_cookie_") - 1; if (ngx_http_parse_set_cookie_lines(r, r->upstream->headers_in.set_cookie, &s, &cookie) == NULL) { v->not_found = 1; return NGX_OK; } v->len = cookie.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = cookie.data; return NGX_OK; } #if (NGX_HTTP_CACHE) static ngx_int_t ngx_http_upstream_cache_status(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_uint_t n; if (r->upstream == NULL || r->upstream->cache_status == 0) { v->not_found = 1; return NGX_OK; } n = r->upstream->cache_status - 1; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->len = ngx_http_cache_status[n].len; v->data = ngx_http_cache_status[n].data; return NGX_OK; } static ngx_int_t ngx_http_upstream_cache_last_modified(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; if (r->upstream == NULL || !r->upstream->conf->cache_revalidate || r->upstream->cache_status != NGX_HTTP_CACHE_EXPIRED || r->cache->last_modified == -1) { v->not_found = 1; return NGX_OK; } p = ngx_pnalloc(r->pool, sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); if (p == NULL) { return NGX_ERROR; } v->len = ngx_http_time(p, r->cache->last_modified) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } static ngx_int_t ngx_http_upstream_cache_etag(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { if (r->upstream == NULL || !r->upstream->conf->cache_revalidate || r->upstream->cache_status != NGX_HTTP_CACHE_EXPIRED || r->cache->etag.len == 0) { v->not_found = 1; return NGX_OK; } v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->len = r->cache->etag.len; v->data = r->cache->etag.data; return NGX_OK; } #endif static char * ngx_http_upstream(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy) { char *rv; void *mconf; ngx_str_t *value; ngx_url_t u; ngx_uint_t m; ngx_conf_t pcf; ngx_http_module_t *module; ngx_http_conf_ctx_t *ctx, *http_ctx; ngx_http_upstream_srv_conf_t *uscf; ngx_memzero(&u, sizeof(ngx_url_t)); value = cf->args->elts; u.host = value[1]; u.no_resolve = 1; u.no_port = 1; uscf = ngx_http_upstream_add(cf, &u, NGX_HTTP_UPSTREAM_CREATE |NGX_HTTP_UPSTREAM_WEIGHT |NGX_HTTP_UPSTREAM_MAX_CONNS |NGX_HTTP_UPSTREAM_MAX_FAILS |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT |NGX_HTTP_UPSTREAM_DOWN |NGX_HTTP_UPSTREAM_BACKUP); if (uscf == NULL) { return NGX_CONF_ERROR; } ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t)); if (ctx == NULL) { return NGX_CONF_ERROR; } http_ctx = cf->ctx; ctx->main_conf = http_ctx->main_conf; /* the upstream{}'s srv_conf */ ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); if (ctx->srv_conf == NULL) { return NGX_CONF_ERROR; } ctx->srv_conf[ngx_http_upstream_module.ctx_index] = uscf; uscf->srv_conf = ctx->srv_conf; /* the upstream{}'s loc_conf */ ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); if (ctx->loc_conf == NULL) { return NGX_CONF_ERROR; } for (m = 0; cf->cycle->modules[m]; m++) { if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) { continue; } module = cf->cycle->modules[m]->ctx; if (module->create_srv_conf) { mconf = module->create_srv_conf(cf); if (mconf == NULL) { return NGX_CONF_ERROR; } ctx->srv_conf[cf->cycle->modules[m]->ctx_index] = mconf; } if (module->create_loc_conf) { mconf = module->create_loc_conf(cf); if (mconf == NULL) { return NGX_CONF_ERROR; } ctx->loc_conf[cf->cycle->modules[m]->ctx_index] = mconf; } } uscf->servers = ngx_array_create(cf->pool, 4, sizeof(ngx_http_upstream_server_t)); if (uscf->servers == NULL) { return NGX_CONF_ERROR; } /* parse inside upstream{} */ pcf = *cf; cf->ctx = ctx; cf->cmd_type = NGX_HTTP_UPS_CONF; rv = ngx_conf_parse(cf, NULL); *cf = pcf; if (rv != NGX_CONF_OK) { return rv; } if (uscf->servers->nelts == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "no servers are inside upstream"); return NGX_CONF_ERROR; } return rv; } static char * ngx_http_upstream_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_upstream_srv_conf_t *uscf = conf; time_t fail_timeout; ngx_str_t *value, s; ngx_url_t u; ngx_int_t weight, max_conns, max_fails; ngx_uint_t i; ngx_http_upstream_server_t *us; us = ngx_array_push(uscf->servers); if (us == NULL) { return NGX_CONF_ERROR; } ngx_memzero(us, sizeof(ngx_http_upstream_server_t)); value = cf->args->elts; weight = 1; max_conns = 0; max_fails = 1; fail_timeout = 10; for (i = 2; i < cf->args->nelts; i++) { if (ngx_strncmp(value[i].data, "weight=", 7) == 0) { if (!(uscf->flags & NGX_HTTP_UPSTREAM_WEIGHT)) { goto not_supported; } weight = ngx_atoi(&value[i].data[7], value[i].len - 7); if (weight == NGX_ERROR || weight == 0) { goto invalid; } continue; } if (ngx_strncmp(value[i].data, "max_conns=", 10) == 0) { if (!(uscf->flags & NGX_HTTP_UPSTREAM_MAX_CONNS)) { goto not_supported; } max_conns = ngx_atoi(&value[i].data[10], value[i].len - 10); if (max_conns == NGX_ERROR) { goto invalid; } continue; } if (ngx_strncmp(value[i].data, "max_fails=", 10) == 0) { if (!(uscf->flags & NGX_HTTP_UPSTREAM_MAX_FAILS)) { goto not_supported; } max_fails = ngx_atoi(&value[i].data[10], value[i].len - 10); if (max_fails == NGX_ERROR) { goto invalid; } continue; } if (ngx_strncmp(value[i].data, "fail_timeout=", 13) == 0) { if (!(uscf->flags & NGX_HTTP_UPSTREAM_FAIL_TIMEOUT)) { goto not_supported; } s.len = value[i].len - 13; s.data = &value[i].data[13]; fail_timeout = ngx_parse_time(&s, 1); if (fail_timeout == (time_t) NGX_ERROR) { goto invalid; } continue; } if (ngx_strcmp(value[i].data, "backup") == 0) { if (!(uscf->flags & NGX_HTTP_UPSTREAM_BACKUP)) { goto not_supported; } us->backup = 1; continue; } if (ngx_strcmp(value[i].data, "down") == 0) { if (!(uscf->flags & NGX_HTTP_UPSTREAM_DOWN)) { goto not_supported; } us->down = 1; continue; } goto invalid; } ngx_memzero(&u, sizeof(ngx_url_t)); u.url = value[1]; u.default_port = 80; if (ngx_parse_url(cf->pool, &u) != NGX_OK) { if (u.err) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%s in upstream \"%V\"", u.err, &u.url); } return NGX_CONF_ERROR; } us->name = u.url; us->addrs = u.addrs; us->naddrs = u.naddrs; us->weight = weight; us->max_conns = max_conns; us->max_fails = max_fails; us->fail_timeout = fail_timeout; return NGX_CONF_OK; invalid: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[i]); return NGX_CONF_ERROR; not_supported: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "balancing method does not support parameter \"%V\"", &value[i]); return NGX_CONF_ERROR; } ngx_http_upstream_srv_conf_t * ngx_http_upstream_add(ngx_conf_t *cf, ngx_url_t *u, ngx_uint_t flags) { ngx_uint_t i; ngx_http_upstream_server_t *us; ngx_http_upstream_srv_conf_t *uscf, **uscfp; ngx_http_upstream_main_conf_t *umcf; if (!(flags & NGX_HTTP_UPSTREAM_CREATE)) { if (ngx_parse_url(cf->pool, u) != NGX_OK) { if (u->err) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%s in upstream \"%V\"", u->err, &u->url); } return NULL; } } umcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_upstream_module); uscfp = umcf->upstreams.elts; for (i = 0; i < umcf->upstreams.nelts; i++) { if (uscfp[i]->host.len != u->host.len || ngx_strncasecmp(uscfp[i]->host.data, u->host.data, u->host.len) != 0) { continue; } if ((flags & NGX_HTTP_UPSTREAM_CREATE) && (uscfp[i]->flags & NGX_HTTP_UPSTREAM_CREATE)) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "duplicate upstream \"%V\"", &u->host); return NULL; } if ((uscfp[i]->flags & NGX_HTTP_UPSTREAM_CREATE) && !u->no_port) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "upstream \"%V\" may not have port %d", &u->host, u->port); return NULL; } if ((flags & NGX_HTTP_UPSTREAM_CREATE) && !uscfp[i]->no_port) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "upstream \"%V\" may not have port %d in %s:%ui", &u->host, uscfp[i]->port, uscfp[i]->file_name, uscfp[i]->line); return NULL; } if (uscfp[i]->port && u->port && uscfp[i]->port != u->port) { continue; } if (flags & NGX_HTTP_UPSTREAM_CREATE) { uscfp[i]->flags = flags; uscfp[i]->port = 0; } return uscfp[i]; } uscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_srv_conf_t)); if (uscf == NULL) { return NULL; } uscf->flags = flags; uscf->host = u->host; uscf->file_name = cf->conf_file->file.name.data; uscf->line = cf->conf_file->line; uscf->port = u->port; uscf->no_port = u->no_port; if (u->naddrs == 1 && (u->port || u->family == AF_UNIX)) { uscf->servers = ngx_array_create(cf->pool, 1, sizeof(ngx_http_upstream_server_t)); if (uscf->servers == NULL) { return NULL; } us = ngx_array_push(uscf->servers); if (us == NULL) { return NULL; } ngx_memzero(us, sizeof(ngx_http_upstream_server_t)); us->addrs = u->addrs; us->naddrs = 1; } uscfp = ngx_array_push(&umcf->upstreams); if (uscfp == NULL) { return NULL; } *uscfp = uscf; return uscf; } char * ngx_http_upstream_bind_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { char *p = conf; ngx_int_t rc; ngx_str_t *value; ngx_http_complex_value_t cv; ngx_http_upstream_local_t **plocal, *local; ngx_http_compile_complex_value_t ccv; plocal = (ngx_http_upstream_local_t **) (p + cmd->offset); if (*plocal != NGX_CONF_UNSET_PTR) { return "is duplicate"; } value = cf->args->elts; if (cf->args->nelts == 2 && ngx_strcmp(value[1].data, "off") == 0) { *plocal = NULL; return NGX_CONF_OK; } ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = &cv; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } local = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_local_t)); if (local == NULL) { return NGX_CONF_ERROR; } *plocal = local; if (cv.lengths) { local->value = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); if (local->value == NULL) { return NGX_CONF_ERROR; } *local->value = cv; } else { local->addr = ngx_palloc(cf->pool, sizeof(ngx_addr_t)); if (local->addr == NULL) { return NGX_CONF_ERROR; } rc = ngx_parse_addr_port(cf->pool, local->addr, value[1].data, value[1].len); switch (rc) { case NGX_OK: local->addr->name = value[1]; break; case NGX_DECLINED: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid address \"%V\"", &value[1]); /* fall through */ default: return NGX_CONF_ERROR; } } if (cf->args->nelts > 2) { if (ngx_strcmp(value[2].data, "transparent") == 0) { #if (NGX_HAVE_TRANSPARENT_PROXY) ngx_core_conf_t *ccf; ccf = (ngx_core_conf_t *) ngx_get_conf(cf->cycle->conf_ctx, ngx_core_module); ccf->transparent = 1; local->transparent = 1; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "transparent proxying is not supported " "on this platform, ignored"); #endif } else { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[2]); return NGX_CONF_ERROR; } } return NGX_CONF_OK; } static ngx_int_t ngx_http_upstream_set_local(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_http_upstream_local_t *local) { ngx_int_t rc; ngx_str_t val; ngx_addr_t *addr; if (local == NULL) { u->peer.local = NULL; return NGX_OK; } #if (NGX_HAVE_TRANSPARENT_PROXY) u->peer.transparent = local->transparent; #endif if (local->value == NULL) { u->peer.local = local->addr; return NGX_OK; } if (ngx_http_complex_value(r, local->value, &val) != NGX_OK) { return NGX_ERROR; } if (val.len == 0) { return NGX_OK; } addr = ngx_palloc(r->pool, sizeof(ngx_addr_t)); if (addr == NULL) { return NGX_ERROR; } rc = ngx_parse_addr_port(r->pool, addr, val.data, val.len); if (rc == NGX_ERROR) { return NGX_ERROR; } if (rc != NGX_OK) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid local address \"%V\"", &val); return NGX_OK; } addr->name = val; u->peer.local = addr; return NGX_OK; } char * ngx_http_upstream_param_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { char *p = conf; ngx_str_t *value; ngx_array_t **a; ngx_http_upstream_param_t *param; a = (ngx_array_t **) (p + cmd->offset); if (*a == NULL) { *a = ngx_array_create(cf->pool, 4, sizeof(ngx_http_upstream_param_t)); if (*a == NULL) { return NGX_CONF_ERROR; } } param = ngx_array_push(*a); if (param == NULL) { return NGX_CONF_ERROR; } value = cf->args->elts; param->key = value[1]; param->value = value[2]; param->skip_empty = 0; if (cf->args->nelts == 4) { if (ngx_strcmp(value[3].data, "if_not_empty") != 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[3]); return NGX_CONF_ERROR; } param->skip_empty = 1; } return NGX_CONF_OK; } ngx_int_t ngx_http_upstream_hide_headers_hash(ngx_conf_t *cf, ngx_http_upstream_conf_t *conf, ngx_http_upstream_conf_t *prev, ngx_str_t *default_hide_headers, ngx_hash_init_t *hash) { ngx_str_t *h; ngx_uint_t i, j; ngx_array_t hide_headers; ngx_hash_key_t *hk; if (conf->hide_headers == NGX_CONF_UNSET_PTR && conf->pass_headers == NGX_CONF_UNSET_PTR) { conf->hide_headers = prev->hide_headers; conf->pass_headers = prev->pass_headers; conf->hide_headers_hash = prev->hide_headers_hash; if (conf->hide_headers_hash.buckets) { return NGX_OK; } } else { if (conf->hide_headers == NGX_CONF_UNSET_PTR) { conf->hide_headers = prev->hide_headers; } if (conf->pass_headers == NGX_CONF_UNSET_PTR) { conf->pass_headers = prev->pass_headers; } } if (ngx_array_init(&hide_headers, cf->temp_pool, 4, sizeof(ngx_hash_key_t)) != NGX_OK) { return NGX_ERROR; } for (h = default_hide_headers; h->len; h++) { hk = ngx_array_push(&hide_headers); if (hk == NULL) { return NGX_ERROR; } hk->key = *h; hk->key_hash = ngx_hash_key_lc(h->data, h->len); hk->value = (void *) 1; } if (conf->hide_headers != NGX_CONF_UNSET_PTR) { h = conf->hide_headers->elts; for (i = 0; i < conf->hide_headers->nelts; i++) { hk = hide_headers.elts; for (j = 0; j < hide_headers.nelts; j++) { if (ngx_strcasecmp(h[i].data, hk[j].key.data) == 0) { goto exist; } } hk = ngx_array_push(&hide_headers); if (hk == NULL) { return NGX_ERROR; } hk->key = h[i]; hk->key_hash = ngx_hash_key_lc(h[i].data, h[i].len); hk->value = (void *) 1; exist: continue; } } if (conf->pass_headers != NGX_CONF_UNSET_PTR) { h = conf->pass_headers->elts; hk = hide_headers.elts; for (i = 0; i < conf->pass_headers->nelts; i++) { for (j = 0; j < hide_headers.nelts; j++) { if (hk[j].key.data == NULL) { continue; } if (ngx_strcasecmp(h[i].data, hk[j].key.data) == 0) { hk[j].key.data = NULL; break; } } } } hash->hash = &conf->hide_headers_hash; hash->key = ngx_hash_key_lc; hash->pool = cf->pool; hash->temp_pool = NULL; if (ngx_hash_init(hash, hide_headers.elts, hide_headers.nelts) != NGX_OK) { return NGX_ERROR; } /* * special handling to preserve conf->hide_headers_hash * in the "http" section to inherit it to all servers */ if (prev->hide_headers_hash.buckets == NULL && conf->hide_headers == prev->hide_headers && conf->pass_headers == prev->pass_headers) { prev->hide_headers_hash = conf->hide_headers_hash; } return NGX_OK; } static void * ngx_http_upstream_create_main_conf(ngx_conf_t *cf) { ngx_http_upstream_main_conf_t *umcf; umcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_main_conf_t)); if (umcf == NULL) { return NULL; } if (ngx_array_init(&umcf->upstreams, cf->pool, 4, sizeof(ngx_http_upstream_srv_conf_t *)) != NGX_OK) { return NULL; } return umcf; } static char * ngx_http_upstream_init_main_conf(ngx_conf_t *cf, void *conf) { ngx_http_upstream_main_conf_t *umcf = conf; ngx_uint_t i; ngx_array_t headers_in; ngx_hash_key_t *hk; ngx_hash_init_t hash; ngx_http_upstream_init_pt init; ngx_http_upstream_header_t *header; ngx_http_upstream_srv_conf_t **uscfp; uscfp = umcf->upstreams.elts; for (i = 0; i < umcf->upstreams.nelts; i++) { init = uscfp[i]->peer.init_upstream ? uscfp[i]->peer.init_upstream: ngx_http_upstream_init_round_robin; if (init(cf, uscfp[i]) != NGX_OK) { return NGX_CONF_ERROR; } } /* upstream_headers_in_hash */ if (ngx_array_init(&headers_in, cf->temp_pool, 32, sizeof(ngx_hash_key_t)) != NGX_OK) { return NGX_CONF_ERROR; } for (header = ngx_http_upstream_headers_in; header->name.len; header++) { hk = ngx_array_push(&headers_in); if (hk == NULL) { return NGX_CONF_ERROR; } hk->key = header->name; hk->key_hash = ngx_hash_key_lc(header->name.data, header->name.len); hk->value = header; } hash.hash = &umcf->headers_in_hash; hash.key = ngx_hash_key_lc; hash.max_size = 512; hash.bucket_size = ngx_align(64, ngx_cacheline_size); hash.name = "upstream_headers_in_hash"; hash.pool = cf->pool; hash.temp_pool = NULL; if (ngx_hash_init(&hash, headers_in.elts, headers_in.nelts) != NGX_OK) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } nginx-1.24.0/src/http/ngx_http_upstream.h000644 001751 001751 00000036602 14415135676 021624 0ustar00mdouninmdounin000000 000000 /* * 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_HTTP_429 0x00000400 #define NGX_HTTP_UPSTREAM_FT_UPDATING 0x00000800 #define NGX_HTTP_UPSTREAM_FT_BUSY_LOCK 0x00001000 #define NGX_HTTP_UPSTREAM_FT_MAX_WAITING 0x00002000 #define NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT 0x00004000 #define NGX_HTTP_UPSTREAM_FT_NOLIVE 0x40000000 #define NGX_HTTP_UPSTREAM_FT_OFF 0x80000000 #define NGX_HTTP_UPSTREAM_FT_STATUS (NGX_HTTP_UPSTREAM_FT_HTTP_500 \ |NGX_HTTP_UPSTREAM_FT_HTTP_502 \ |NGX_HTTP_UPSTREAM_FT_HTTP_503 \ |NGX_HTTP_UPSTREAM_FT_HTTP_504 \ |NGX_HTTP_UPSTREAM_FT_HTTP_403 \ |NGX_HTTP_UPSTREAM_FT_HTTP_404 \ |NGX_HTTP_UPSTREAM_FT_HTTP_429) #define NGX_HTTP_UPSTREAM_INVALID_HEADER 40 #define NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT 0x00000002 #define NGX_HTTP_UPSTREAM_IGN_XA_EXPIRES 0x00000004 #define NGX_HTTP_UPSTREAM_IGN_EXPIRES 0x00000008 #define NGX_HTTP_UPSTREAM_IGN_CACHE_CONTROL 0x00000010 #define NGX_HTTP_UPSTREAM_IGN_SET_COOKIE 0x00000020 #define NGX_HTTP_UPSTREAM_IGN_XA_LIMIT_RATE 0x00000040 #define NGX_HTTP_UPSTREAM_IGN_XA_BUFFERING 0x00000080 #define NGX_HTTP_UPSTREAM_IGN_XA_CHARSET 0x00000100 #define NGX_HTTP_UPSTREAM_IGN_VARY 0x00000200 typedef struct { ngx_uint_t status; ngx_msec_t response_time; ngx_msec_t connect_time; ngx_msec_t header_time; ngx_msec_t queue_time; off_t response_length; off_t bytes_received; off_t bytes_sent; ngx_str_t *peer; } ngx_http_upstream_state_t; typedef struct { ngx_hash_t headers_in_hash; ngx_array_t upstreams; /* ngx_http_upstream_srv_conf_t */ } ngx_http_upstream_main_conf_t; typedef struct ngx_http_upstream_srv_conf_s ngx_http_upstream_srv_conf_t; typedef ngx_int_t (*ngx_http_upstream_init_pt)(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us); typedef ngx_int_t (*ngx_http_upstream_init_peer_pt)(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us); typedef struct { ngx_http_upstream_init_pt init_upstream; ngx_http_upstream_init_peer_pt init; void *data; } ngx_http_upstream_peer_t; typedef struct { ngx_str_t name; ngx_addr_t *addrs; ngx_uint_t naddrs; ngx_uint_t weight; ngx_uint_t max_conns; ngx_uint_t max_fails; time_t fail_timeout; ngx_msec_t slow_start; ngx_uint_t down; unsigned backup:1; NGX_COMPAT_BEGIN(6) NGX_COMPAT_END } ngx_http_upstream_server_t; #define NGX_HTTP_UPSTREAM_CREATE 0x0001 #define NGX_HTTP_UPSTREAM_WEIGHT 0x0002 #define NGX_HTTP_UPSTREAM_MAX_FAILS 0x0004 #define NGX_HTTP_UPSTREAM_FAIL_TIMEOUT 0x0008 #define NGX_HTTP_UPSTREAM_DOWN 0x0010 #define NGX_HTTP_UPSTREAM_BACKUP 0x0020 #define NGX_HTTP_UPSTREAM_MAX_CONNS 0x0100 struct ngx_http_upstream_srv_conf_s { ngx_http_upstream_peer_t peer; void **srv_conf; ngx_array_t *servers; /* ngx_http_upstream_server_t */ ngx_uint_t flags; ngx_str_t host; u_char *file_name; ngx_uint_t line; in_port_t port; ngx_uint_t no_port; /* unsigned no_port:1 */ #if (NGX_HTTP_UPSTREAM_ZONE) ngx_shm_zone_t *shm_zone; #endif }; typedef struct { ngx_addr_t *addr; ngx_http_complex_value_t *value; #if (NGX_HAVE_TRANSPARENT_PROXY) ngx_uint_t transparent; /* unsigned transparent:1; */ #endif } ngx_http_upstream_local_t; typedef struct { ngx_http_upstream_srv_conf_t *upstream; ngx_msec_t connect_timeout; ngx_msec_t send_timeout; ngx_msec_t read_timeout; ngx_msec_t next_upstream_timeout; size_t send_lowat; size_t buffer_size; size_t limit_rate; size_t busy_buffers_size; size_t max_temp_file_size; size_t temp_file_write_size; size_t busy_buffers_size_conf; size_t max_temp_file_size_conf; size_t temp_file_write_size_conf; ngx_bufs_t bufs; ngx_uint_t ignore_headers; ngx_uint_t next_upstream; ngx_uint_t store_access; ngx_uint_t next_upstream_tries; ngx_flag_t buffering; ngx_flag_t request_buffering; ngx_flag_t pass_request_headers; ngx_flag_t pass_request_body; ngx_flag_t ignore_client_abort; ngx_flag_t intercept_errors; ngx_flag_t cyclic_temp_file; ngx_flag_t force_ranges; ngx_path_t *temp_path; ngx_hash_t hide_headers_hash; ngx_array_t *hide_headers; ngx_array_t *pass_headers; ngx_http_upstream_local_t *local; ngx_flag_t socket_keepalive; #if (NGX_HTTP_CACHE) ngx_shm_zone_t *cache_zone; ngx_http_complex_value_t *cache_value; ngx_uint_t cache_min_uses; ngx_uint_t cache_use_stale; ngx_uint_t cache_methods; off_t cache_max_range_offset; ngx_flag_t cache_lock; ngx_msec_t cache_lock_timeout; ngx_msec_t cache_lock_age; ngx_flag_t cache_revalidate; ngx_flag_t cache_convert_head; ngx_flag_t cache_background_update; ngx_array_t *cache_valid; ngx_array_t *cache_bypass; ngx_array_t *cache_purge; ngx_array_t *no_cache; #endif ngx_array_t *store_lengths; ngx_array_t *store_values; #if (NGX_HTTP_CACHE) signed cache:2; #endif signed store:2; unsigned intercept_404:1; unsigned change_buffering:1; unsigned pass_trailers:1; unsigned preserve_output:1; #if (NGX_HTTP_SSL || NGX_COMPAT) ngx_ssl_t *ssl; ngx_flag_t ssl_session_reuse; ngx_http_complex_value_t *ssl_name; ngx_flag_t ssl_server_name; ngx_flag_t ssl_verify; ngx_http_complex_value_t *ssl_certificate; ngx_http_complex_value_t *ssl_certificate_key; ngx_array_t *ssl_passwords; #endif ngx_str_t module; NGX_COMPAT_BEGIN(2) NGX_COMPAT_END } ngx_http_upstream_conf_t; typedef struct { ngx_str_t name; ngx_http_header_handler_pt handler; ngx_uint_t offset; ngx_http_header_handler_pt copy_handler; ngx_uint_t conf; ngx_uint_t redirect; /* unsigned redirect:1; */ } ngx_http_upstream_header_t; typedef struct { ngx_list_t headers; ngx_list_t trailers; ngx_uint_t status_n; ngx_str_t status_line; ngx_table_elt_t *status; ngx_table_elt_t *date; ngx_table_elt_t *server; ngx_table_elt_t *connection; ngx_table_elt_t *expires; ngx_table_elt_t *etag; ngx_table_elt_t *x_accel_expires; ngx_table_elt_t *x_accel_redirect; ngx_table_elt_t *x_accel_limit_rate; ngx_table_elt_t *content_type; ngx_table_elt_t *content_length; ngx_table_elt_t *last_modified; ngx_table_elt_t *location; ngx_table_elt_t *refresh; ngx_table_elt_t *www_authenticate; ngx_table_elt_t *transfer_encoding; ngx_table_elt_t *vary; ngx_table_elt_t *cache_control; ngx_table_elt_t *set_cookie; off_t content_length_n; time_t last_modified_time; unsigned connection_close:1; unsigned chunked:1; unsigned no_cache:1; unsigned expired:1; } ngx_http_upstream_headers_in_t; typedef struct { ngx_str_t host; in_port_t port; ngx_uint_t no_port; /* unsigned no_port:1 */ ngx_uint_t naddrs; ngx_resolver_addr_t *addrs; struct sockaddr *sockaddr; socklen_t socklen; ngx_str_t name; ngx_resolver_ctx_t *ctx; } ngx_http_upstream_resolved_t; typedef void (*ngx_http_upstream_handler_pt)(ngx_http_request_t *r, ngx_http_upstream_t *u); struct ngx_http_upstream_s { ngx_http_upstream_handler_pt read_event_handler; ngx_http_upstream_handler_pt write_event_handler; ngx_peer_connection_t peer; ngx_event_pipe_t *pipe; ngx_chain_t *request_bufs; ngx_output_chain_ctx_t output; ngx_chain_writer_ctx_t writer; ngx_http_upstream_conf_t *conf; ngx_http_upstream_srv_conf_t *upstream; #if (NGX_HTTP_CACHE) ngx_array_t *caches; #endif ngx_http_upstream_headers_in_t headers_in; ngx_http_upstream_resolved_t *resolved; ngx_buf_t from_client; ngx_buf_t buffer; off_t length; ngx_chain_t *out_bufs; ngx_chain_t *busy_bufs; ngx_chain_t *free_bufs; ngx_int_t (*input_filter_init)(void *data); ngx_int_t (*input_filter)(void *data, ssize_t bytes); void *input_filter_ctx; #if (NGX_HTTP_CACHE) ngx_int_t (*create_key)(ngx_http_request_t *r); #endif ngx_int_t (*create_request)(ngx_http_request_t *r); ngx_int_t (*reinit_request)(ngx_http_request_t *r); ngx_int_t (*process_header)(ngx_http_request_t *r); void (*abort_request)(ngx_http_request_t *r); void (*finalize_request)(ngx_http_request_t *r, ngx_int_t rc); ngx_int_t (*rewrite_redirect)(ngx_http_request_t *r, ngx_table_elt_t *h, size_t prefix); ngx_int_t (*rewrite_cookie)(ngx_http_request_t *r, ngx_table_elt_t *h); ngx_msec_t start_time; ngx_http_upstream_state_t *state; ngx_str_t method; ngx_str_t schema; ngx_str_t uri; #if (NGX_HTTP_SSL || NGX_COMPAT) ngx_str_t ssl_name; #endif ngx_http_cleanup_pt *cleanup; unsigned store:1; unsigned cacheable:1; unsigned accel:1; unsigned ssl:1; #if (NGX_HTTP_CACHE) unsigned cache_status:3; #endif unsigned buffering:1; unsigned keepalive:1; unsigned upgrade:1; unsigned error:1; unsigned request_sent:1; unsigned request_body_sent:1; unsigned request_body_blocked:1; unsigned header_sent:1; }; typedef struct { ngx_uint_t status; ngx_uint_t mask; } ngx_http_upstream_next_t; typedef struct { ngx_str_t key; ngx_str_t value; ngx_uint_t skip_empty; } ngx_http_upstream_param_t; ngx_int_t ngx_http_upstream_create(ngx_http_request_t *r); void ngx_http_upstream_init(ngx_http_request_t *r); ngx_int_t ngx_http_upstream_non_buffered_filter_init(void *data); ngx_int_t ngx_http_upstream_non_buffered_filter(void *data, ssize_t bytes); ngx_http_upstream_srv_conf_t *ngx_http_upstream_add(ngx_conf_t *cf, ngx_url_t *u, ngx_uint_t flags); char *ngx_http_upstream_bind_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, 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_ */ nginx-1.24.0/src/http/ngx_http_upstream_round_robin.c000644 001751 001751 00000052255 14415135676 024221 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ #include #include #include #define ngx_http_upstream_tries(p) ((p)->tries \ + ((p)->next ? (p)->next->tries : 0)) static ngx_http_upstream_rr_peer_t *ngx_http_upstream_get_peer( 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, t; 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; t = 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 (!server[i].down) { t += server[i].naddrs; } } 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->tries = t; peers->name = &us->host; n = 0; peerp = &peers->peer; for (i = 0; i < us->servers->nelts; i++) { if (server[i].backup) { continue; } for (j = 0; j < server[i].naddrs; j++) { peer[n].sockaddr = server[i].addrs[j].sockaddr; peer[n].socklen = server[i].addrs[j].socklen; peer[n].name = server[i].addrs[j].name; peer[n].weight = server[i].weight; peer[n].effective_weight = server[i].weight; peer[n].current_weight = 0; peer[n].max_conns = server[i].max_conns; peer[n].max_fails = server[i].max_fails; peer[n].fail_timeout = server[i].fail_timeout; peer[n].down = server[i].down; peer[n].server = server[i].name; *peerp = &peer[n]; peerp = &peer[n].next; n++; } } us->peer.data = peers; /* backup servers */ n = 0; w = 0; t = 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 (!server[i].down) { t += server[i].naddrs; } } 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->tries = t; backup->name = &us->host; n = 0; peerp = &backup->peer; for (i = 0; i < us->servers->nelts; i++) { if (!server[i].backup) { continue; } for (j = 0; j < server[i].naddrs; j++) { peer[n].sockaddr = server[i].addrs[j].sockaddr; peer[n].socklen = server[i].addrs[j].socklen; peer[n].name = server[i].addrs[j].name; peer[n].weight = server[i].weight; peer[n].effective_weight = server[i].weight; peer[n].current_weight = 0; peer[n].max_conns = server[i].max_conns; peer[n].max_fails = server[i].max_fails; peer[n].fail_timeout = server[i].fail_timeout; peer[n].down = server[i].down; peer[n].server = server[i].name; *peerp = &peer[n]; peerp = &peer[n].next; n++; } } peers->next = backup; return NGX_OK; } /* an upstream implicitly defined by proxy_pass, etc. */ if (us->port == 0) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no port in upstream \"%V\" in %s:%ui", &us->host, us->file_name, us->line); return NGX_ERROR; } ngx_memzero(&u, sizeof(ngx_url_t)); u.host = us->host; u.port = us->port; if (ngx_inet_resolve_host(cf->pool, &u) != NGX_OK) { if (u.err) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "%s in upstream \"%V\" in %s:%ui", u.err, &us->host, us->file_name, us->line); } return NGX_ERROR; } n = u.naddrs; peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t)); if (peers == NULL) { return NGX_ERROR; } peer = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peer_t) * n); if (peer == NULL) { return NGX_ERROR; } peers->single = (n == 1); peers->number = n; peers->weighted = 0; peers->total_weight = n; peers->tries = n; peers->name = &us->host; peerp = &peers->peer; for (i = 0; i < u.naddrs; i++) { peer[i].sockaddr = u.addrs[i].sockaddr; peer[i].socklen = u.addrs[i].socklen; peer[i].name = u.addrs[i].name; peer[i].weight = 1; peer[i].effective_weight = 1; peer[i].current_weight = 0; peer[i].max_conns = 0; peer[i].max_fails = 1; peer[i].fail_timeout = 10; *peerp = &peer[i]; peerp = &peer[i].next; } us->peer.data = peers; /* implicitly defined upstream has no backup servers */ return NGX_OK; } ngx_int_t ngx_http_upstream_init_round_robin_peer(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us) { ngx_uint_t n; ngx_http_upstream_rr_peer_data_t *rrp; rrp = r->upstream->peer.data; if (rrp == NULL) { rrp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_rr_peer_data_t)); if (rrp == NULL) { return NGX_ERROR; } r->upstream->peer.data = rrp; } rrp->peers = us->peer.data; rrp->current = NULL; rrp->config = 0; n = rrp->peers->number; if (rrp->peers->next && rrp->peers->next->number > n) { n = rrp->peers->next->number; } if (n <= 8 * sizeof(uintptr_t)) { rrp->tried = &rrp->data; rrp->data = 0; } else { n = (n + (8 * sizeof(uintptr_t) - 1)) / (8 * sizeof(uintptr_t)); rrp->tried = ngx_pcalloc(r->pool, n * sizeof(uintptr_t)); if (rrp->tried == NULL) { return NGX_ERROR; } } r->upstream->peer.get = ngx_http_upstream_get_round_robin_peer; r->upstream->peer.free = ngx_http_upstream_free_round_robin_peer; r->upstream->peer.tries = ngx_http_upstream_tries(rrp->peers); #if (NGX_HTTP_SSL) r->upstream->peer.set_session = ngx_http_upstream_set_round_robin_peer_session; r->upstream->peer.save_session = ngx_http_upstream_save_round_robin_peer_session; #endif return NGX_OK; } ngx_int_t ngx_http_upstream_create_round_robin_peer(ngx_http_request_t *r, ngx_http_upstream_resolved_t *ur) { u_char *p; size_t len; socklen_t socklen; ngx_uint_t i, n; struct sockaddr *sockaddr; ngx_http_upstream_rr_peer_t *peer, **peerp; ngx_http_upstream_rr_peers_t *peers; ngx_http_upstream_rr_peer_data_t *rrp; rrp = r->upstream->peer.data; if (rrp == NULL) { rrp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_rr_peer_data_t)); if (rrp == NULL) { return NGX_ERROR; } r->upstream->peer.data = rrp; } peers = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_rr_peers_t)); if (peers == NULL) { return NGX_ERROR; } peer = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_rr_peer_t) * ur->naddrs); if (peer == NULL) { return NGX_ERROR; } peers->single = (ur->naddrs == 1); peers->number = ur->naddrs; peers->tries = ur->naddrs; peers->name = &ur->host; if (ur->sockaddr) { peer[0].sockaddr = ur->sockaddr; peer[0].socklen = ur->socklen; peer[0].name = ur->name.data ? ur->name : ur->host; peer[0].weight = 1; peer[0].effective_weight = 1; peer[0].current_weight = 0; peer[0].max_conns = 0; peer[0].max_fails = 1; peer[0].fail_timeout = 10; peers->peer = peer; } else { peerp = &peers->peer; for (i = 0; i < ur->naddrs; i++) { socklen = ur->addrs[i].socklen; sockaddr = ngx_palloc(r->pool, socklen); if (sockaddr == NULL) { return NGX_ERROR; } ngx_memcpy(sockaddr, ur->addrs[i].sockaddr, socklen); ngx_inet_set_port(sockaddr, ur->port); p = ngx_pnalloc(r->pool, NGX_SOCKADDR_STRLEN); if (p == NULL) { return NGX_ERROR; } len = ngx_sock_ntop(sockaddr, socklen, p, NGX_SOCKADDR_STRLEN, 1); peer[i].sockaddr = sockaddr; peer[i].socklen = socklen; peer[i].name.len = len; peer[i].name.data = p; peer[i].weight = 1; peer[i].effective_weight = 1; peer[i].current_weight = 0; peer[i].max_conns = 0; peer[i].max_fails = 1; peer[i].fail_timeout = 10; *peerp = &peer[i]; peerp = &peer[i].next; } } rrp->peers = peers; rrp->current = NULL; rrp->config = 0; if (rrp->peers->number <= 8 * sizeof(uintptr_t)) { rrp->tried = &rrp->data; rrp->data = 0; } else { n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1)) / (8 * sizeof(uintptr_t)); rrp->tried = ngx_pcalloc(r->pool, n * sizeof(uintptr_t)); if (rrp->tried == NULL) { return NGX_ERROR; } } r->upstream->peer.get = ngx_http_upstream_get_round_robin_peer; r->upstream->peer.free = ngx_http_upstream_free_round_robin_peer; r->upstream->peer.tries = ngx_http_upstream_tries(rrp->peers); #if (NGX_HTTP_SSL) r->upstream->peer.set_session = ngx_http_upstream_empty_set_session; r->upstream->peer.save_session = ngx_http_upstream_empty_save_session; #endif return NGX_OK; } ngx_int_t ngx_http_upstream_get_round_robin_peer(ngx_peer_connection_t *pc, void *data) { ngx_http_upstream_rr_peer_data_t *rrp = data; ngx_int_t rc; ngx_uint_t i, n; ngx_http_upstream_rr_peer_t *peer; ngx_http_upstream_rr_peers_t *peers; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, "get rr peer, try: %ui", pc->tries); pc->cached = 0; pc->connection = NULL; peers = rrp->peers; ngx_http_upstream_rr_peers_wlock(peers); if (peers->single) { peer = peers->peer; if (peer->down) { goto failed; } if (peer->max_conns && peer->conns >= peer->max_conns) { goto failed; } rrp->current = peer; } else { /* there are several peers */ peer = ngx_http_upstream_get_peer(rrp); if (peer == NULL) { goto failed; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0, "get rr peer, current: %p %i", peer, peer->current_weight); } pc->sockaddr = peer->sockaddr; pc->socklen = peer->socklen; pc->name = &peer->name; peer->conns++; ngx_http_upstream_rr_peers_unlock(peers); return NGX_OK; failed: if (peers->next) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0, "backup servers"); rrp->peers = peers->next; n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1)) / (8 * sizeof(uintptr_t)); for (i = 0; i < n; i++) { rrp->tried[i] = 0; } ngx_http_upstream_rr_peers_unlock(peers); rc = ngx_http_upstream_get_round_robin_peer(pc, rrp); if (rc != NGX_BUSY) { return rc; } ngx_http_upstream_rr_peers_wlock(peers); } ngx_http_upstream_rr_peers_unlock(peers); pc->name = peers->name; return NGX_BUSY; } static ngx_http_upstream_rr_peer_t * ngx_http_upstream_get_peer(ngx_http_upstream_rr_peer_data_t *rrp) { time_t now; uintptr_t m; ngx_int_t total; ngx_uint_t i, n, p; ngx_http_upstream_rr_peer_t *peer, *best; now = ngx_time(); best = NULL; total = 0; #if (NGX_SUPPRESS_WARN) p = 0; #endif for (peer = rrp->peers->peer, i = 0; peer; peer = peer->next, i++) { n = i / (8 * sizeof(uintptr_t)); m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t)); if (rrp->tried[n] & m) { continue; } if (peer->down) { continue; } if (peer->max_fails && peer->fails >= peer->max_fails && now - peer->checked <= peer->fail_timeout) { continue; } if (peer->max_conns && peer->conns >= peer->max_conns) { continue; } peer->current_weight += peer->effective_weight; total += peer->effective_weight; if (peer->effective_weight < peer->weight) { peer->effective_weight++; } if (best == NULL || peer->current_weight > best->current_weight) { best = peer; p = i; } } if (best == NULL) { return NULL; } rrp->current = best; n = p / (8 * sizeof(uintptr_t)); m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t)); rrp->tried[n] |= m; best->current_weight -= total; if (now - best->checked > best->fail_timeout) { best->checked = now; } return best; } void ngx_http_upstream_free_round_robin_peer(ngx_peer_connection_t *pc, void *data, ngx_uint_t state) { ngx_http_upstream_rr_peer_data_t *rrp = data; time_t now; ngx_http_upstream_rr_peer_t *peer; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0, "free rr peer %ui %ui", pc->tries, state); /* TODO: NGX_PEER_KEEPALIVE */ peer = rrp->current; ngx_http_upstream_rr_peers_rlock(rrp->peers); ngx_http_upstream_rr_peer_lock(rrp->peers, peer); if (rrp->peers->single) { peer->conns--; ngx_http_upstream_rr_peer_unlock(rrp->peers, peer); ngx_http_upstream_rr_peers_unlock(rrp->peers); pc->tries = 0; return; } if (state & NGX_PEER_FAILED) { now = ngx_time(); peer->fails++; peer->accessed = now; peer->checked = now; if (peer->max_fails) { peer->effective_weight -= peer->weight / peer->max_fails; if (peer->fails >= peer->max_fails) { ngx_log_error(NGX_LOG_WARN, pc->log, 0, "upstream server temporarily disabled"); } } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0, "free rr peer failed: %p %i", peer, peer->effective_weight); if (peer->effective_weight < 0) { peer->effective_weight = 0; } } else { /* mark peer live if check passed */ if (peer->accessed < peer->checked) { peer->fails = 0; } } peer->conns--; ngx_http_upstream_rr_peer_unlock(rrp->peers, peer); ngx_http_upstream_rr_peers_unlock(rrp->peers); if (pc->tries) { pc->tries--; } } #if (NGX_HTTP_SSL) ngx_int_t ngx_http_upstream_set_round_robin_peer_session(ngx_peer_connection_t *pc, void *data) { ngx_http_upstream_rr_peer_data_t *rrp = data; ngx_int_t rc; ngx_ssl_session_t *ssl_session; ngx_http_upstream_rr_peer_t *peer; #if (NGX_HTTP_UPSTREAM_ZONE) int len; const 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 = ngx_ssl_get0_session(pc->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 nginx-1.24.0/src/http/ngx_http_upstream_round_robin.h000644 001751 001751 00000012014 14415135676 024213 0ustar00mdouninmdounin000000 000000 /* * 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 max_conns; ngx_uint_t fails; time_t accessed; time_t checked; ngx_uint_t max_fails; time_t fail_timeout; ngx_msec_t slow_start; ngx_msec_t start_time; ngx_uint_t down; #if (NGX_HTTP_SSL || NGX_COMPAT) void *ssl_session; int ssl_session_len; #endif #if (NGX_HTTP_UPSTREAM_ZONE) ngx_atomic_t lock; #endif ngx_http_upstream_rr_peer_t *next; NGX_COMPAT_BEGIN(32) NGX_COMPAT_END }; typedef struct ngx_http_upstream_rr_peers_s ngx_http_upstream_rr_peers_t; struct ngx_http_upstream_rr_peers_s { ngx_uint_t number; #if (NGX_HTTP_UPSTREAM_ZONE) ngx_slab_pool_t *shpool; ngx_atomic_t rwlock; ngx_http_upstream_rr_peers_t *zone_next; #endif ngx_uint_t total_weight; ngx_uint_t tries; unsigned single:1; unsigned weighted:1; ngx_str_t *name; ngx_http_upstream_rr_peers_t *next; ngx_http_upstream_rr_peer_t *peer; }; #if (NGX_HTTP_UPSTREAM_ZONE) #define ngx_http_upstream_rr_peers_rlock(peers) \ \ if (peers->shpool) { \ ngx_rwlock_rlock(&peers->rwlock); \ } #define ngx_http_upstream_rr_peers_wlock(peers) \ \ if (peers->shpool) { \ ngx_rwlock_wlock(&peers->rwlock); \ } #define ngx_http_upstream_rr_peers_unlock(peers) \ \ if (peers->shpool) { \ ngx_rwlock_unlock(&peers->rwlock); \ } #define ngx_http_upstream_rr_peer_lock(peers, peer) \ \ if (peers->shpool) { \ ngx_rwlock_wlock(&peer->lock); \ } #define ngx_http_upstream_rr_peer_unlock(peers, peer) \ \ if (peers->shpool) { \ ngx_rwlock_unlock(&peer->lock); \ } #else #define ngx_http_upstream_rr_peers_rlock(peers) #define ngx_http_upstream_rr_peers_wlock(peers) #define ngx_http_upstream_rr_peers_unlock(peers) #define ngx_http_upstream_rr_peer_lock(peers, peer) #define ngx_http_upstream_rr_peer_unlock(peers, peer) #endif typedef struct { ngx_uint_t config; ngx_http_upstream_rr_peers_t *peers; ngx_http_upstream_rr_peer_t *current; uintptr_t *tried; uintptr_t data; } ngx_http_upstream_rr_peer_data_t; ngx_int_t ngx_http_upstream_init_round_robin(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us); ngx_int_t ngx_http_upstream_init_round_robin_peer(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us); ngx_int_t ngx_http_upstream_create_round_robin_peer(ngx_http_request_t *r, ngx_http_upstream_resolved_t *ur); ngx_int_t ngx_http_upstream_get_round_robin_peer(ngx_peer_connection_t *pc, void *data); void ngx_http_upstream_free_round_robin_peer(ngx_peer_connection_t *pc, void *data, ngx_uint_t state); #if (NGX_HTTP_SSL) ngx_int_t ngx_http_upstream_set_round_robin_peer_session(ngx_peer_connection_t *pc, void *data); void ngx_http_upstream_save_round_robin_peer_session(ngx_peer_connection_t *pc, void *data); #endif #endif /* _NGX_HTTP_UPSTREAM_ROUND_ROBIN_H_INCLUDED_ */ nginx-1.24.0/src/http/ngx_http_variables.c000644 001751 001751 00000206136 14415135676 021730 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ #include #include #include #include static ngx_http_variable_t *ngx_http_add_prefix_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags); static ngx_int_t ngx_http_variable_request(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); #if 0 static void ngx_http_variable_request_set(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); #endif static ngx_int_t ngx_http_variable_request_get_size(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static 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_internal(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data, u_char sep); static ngx_int_t ngx_http_variable_unknown_header_in(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_unknown_header_out(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_unknown_trailer_out(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_request_line(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_cookie(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_argument(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); #if (NGX_HAVE_TCP_INFO) static ngx_int_t ngx_http_variable_tcpinfo(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); #endif static ngx_int_t ngx_http_variable_content_length(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_host(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_binary_remote_addr(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_remote_addr(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_remote_port(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_proxy_protocol_addr(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_proxy_protocol_port(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_proxy_protocol_tlv(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_server_addr(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_server_port(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_scheme(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_https(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static void ngx_http_variable_set_args(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_is_args(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_document_root(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_realpath_root(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_request_filename(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_server_name(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_request_method(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_remote_user(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_bytes_sent(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_body_bytes_sent(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_pipe(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_request_completion(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_request_body(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_request_body_file(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_request_length(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_request_time(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_request_id(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_status(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_sent_content_type(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_sent_content_length(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_sent_location(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_sent_last_modified(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_sent_connection(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_sent_keep_alive(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_sent_transfer_encoding(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static void ngx_http_variable_set_limit_rate(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_connection_time(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_nginx_version(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); 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_header, 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.cookie), 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, offsetof(ngx_proxy_protocol_t, src_addr), 0, 0 }, { ngx_string("proxy_protocol_port"), NULL, ngx_http_variable_proxy_protocol_port, offsetof(ngx_proxy_protocol_t, src_port), 0, 0 }, { ngx_string("proxy_protocol_server_addr"), NULL, ngx_http_variable_proxy_protocol_addr, offsetof(ngx_proxy_protocol_t, dst_addr), 0, 0 }, { ngx_string("proxy_protocol_server_port"), NULL, ngx_http_variable_proxy_protocol_port, offsetof(ngx_proxy_protocol_t, dst_port), 0, 0 }, { ngx_string("proxy_protocol_tlv_"), NULL, ngx_http_variable_proxy_protocol_tlv, 0, NGX_HTTP_VAR_PREFIX, 0 }, { ngx_string("server_addr"), NULL, ngx_http_variable_server_addr, 0, 0, 0 }, { ngx_string("server_port"), NULL, ngx_http_variable_server_port, 0, 0, 0 }, { ngx_string("server_protocol"), NULL, ngx_http_variable_request, offsetof(ngx_http_request_t, http_protocol), 0, 0 }, { ngx_string("scheme"), NULL, ngx_http_variable_scheme, 0, 0, 0 }, { ngx_string("https"), NULL, ngx_http_variable_https, 0, 0, 0 }, { ngx_string("request_uri"), NULL, ngx_http_variable_request, offsetof(ngx_http_request_t, unparsed_uri), 0, 0 }, { ngx_string("uri"), NULL, ngx_http_variable_request, offsetof(ngx_http_request_t, uri), NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("document_uri"), NULL, ngx_http_variable_request, offsetof(ngx_http_request_t, uri), NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("request"), NULL, ngx_http_variable_request_line, 0, 0, 0 }, { ngx_string("document_root"), NULL, ngx_http_variable_document_root, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("realpath_root"), NULL, ngx_http_variable_realpath_root, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("query_string"), NULL, ngx_http_variable_request, offsetof(ngx_http_request_t, args), NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("args"), ngx_http_variable_set_args, ngx_http_variable_request, offsetof(ngx_http_request_t, args), NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("is_args"), NULL, ngx_http_variable_is_args, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("request_filename"), NULL, ngx_http_variable_request_filename, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("server_name"), NULL, ngx_http_variable_server_name, 0, 0, 0 }, { ngx_string("request_method"), NULL, ngx_http_variable_request_method, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("remote_user"), NULL, ngx_http_variable_remote_user, 0, 0, 0 }, { ngx_string("bytes_sent"), NULL, ngx_http_variable_bytes_sent, 0, 0, 0 }, { ngx_string("body_bytes_sent"), NULL, ngx_http_variable_body_bytes_sent, 0, 0, 0 }, { ngx_string("pipe"), NULL, ngx_http_variable_pipe, 0, 0, 0 }, { ngx_string("request_completion"), NULL, ngx_http_variable_request_completion, 0, 0, 0 }, { ngx_string("request_body"), NULL, ngx_http_variable_request_body, 0, 0, 0 }, { ngx_string("request_body_file"), NULL, ngx_http_variable_request_body_file, 0, 0, 0 }, { ngx_string("request_length"), NULL, ngx_http_variable_request_length, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("request_time"), NULL, ngx_http_variable_request_time, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("request_id"), NULL, ngx_http_variable_request_id, 0, 0, 0 }, { ngx_string("status"), NULL, ngx_http_variable_status, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("sent_http_content_type"), NULL, ngx_http_variable_sent_content_type, 0, 0, 0 }, { ngx_string("sent_http_content_length"), NULL, ngx_http_variable_sent_content_length, 0, 0, 0 }, { ngx_string("sent_http_location"), NULL, ngx_http_variable_sent_location, 0, 0, 0 }, { ngx_string("sent_http_last_modified"), NULL, ngx_http_variable_sent_last_modified, 0, 0, 0 }, { ngx_string("sent_http_connection"), NULL, ngx_http_variable_sent_connection, 0, 0, 0 }, { ngx_string("sent_http_keep_alive"), NULL, ngx_http_variable_sent_keep_alive, 0, 0, 0 }, { ngx_string("sent_http_transfer_encoding"), NULL, ngx_http_variable_sent_transfer_encoding, 0, 0, 0 }, { ngx_string("sent_http_cache_control"), NULL, ngx_http_variable_header, offsetof(ngx_http_request_t, headers_out.cache_control), 0, 0 }, { ngx_string("sent_http_link"), NULL, ngx_http_variable_header, offsetof(ngx_http_request_t, headers_out.link), 0, 0 }, { ngx_string("limit_rate"), ngx_http_variable_set_limit_rate, 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("connection_time"), NULL, ngx_http_variable_connection_time, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("nginx_version"), NULL, ngx_http_variable_nginx_version, 0, 0, 0 }, { ngx_string("hostname"), NULL, ngx_http_variable_hostname, 0, 0, 0 }, { ngx_string("pid"), NULL, ngx_http_variable_pid, 0, 0, 0 }, { ngx_string("msec"), NULL, ngx_http_variable_msec, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("time_iso8601"), NULL, ngx_http_variable_time_iso8601, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("time_local"), NULL, ngx_http_variable_time_local, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, #if (NGX_HAVE_TCP_INFO) { ngx_string("tcpinfo_rtt"), NULL, ngx_http_variable_tcpinfo, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("tcpinfo_rttvar"), NULL, ngx_http_variable_tcpinfo, 1, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("tcpinfo_snd_cwnd"), NULL, ngx_http_variable_tcpinfo, 2, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("tcpinfo_rcv_space"), NULL, ngx_http_variable_tcpinfo, 3, NGX_HTTP_VAR_NOCACHEABLE, 0 }, #endif { ngx_string("http_"), NULL, ngx_http_variable_unknown_header_in, 0, NGX_HTTP_VAR_PREFIX, 0 }, { ngx_string("sent_http_"), NULL, ngx_http_variable_unknown_header_out, 0, NGX_HTTP_VAR_PREFIX, 0 }, { ngx_string("sent_trailer_"), NULL, ngx_http_variable_unknown_trailer_out, 0, NGX_HTTP_VAR_PREFIX, 0 }, { ngx_string("cookie_"), NULL, ngx_http_variable_cookie, 0, NGX_HTTP_VAR_PREFIX, 0 }, { ngx_string("arg_"), NULL, ngx_http_variable_argument, 0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_PREFIX, 0 }, ngx_http_null_variable }; ngx_http_variable_value_t ngx_http_variable_null_value = ngx_http_variable(""); ngx_http_variable_value_t ngx_http_variable_true_value = ngx_http_variable("1"); static ngx_uint_t ngx_http_variable_depth = 100; ngx_http_variable_t * ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags) { ngx_int_t rc; ngx_uint_t i; ngx_hash_key_t *key; ngx_http_variable_t *v; ngx_http_core_main_conf_t *cmcf; if (name->len == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid variable name \"$\""); return NULL; } if (flags & NGX_HTTP_VAR_PREFIX) { return ngx_http_add_prefix_variable(cf, name, flags); } cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); key = cmcf->variables_keys->keys.elts; for (i = 0; i < cmcf->variables_keys->keys.nelts; i++) { if (name->len != key[i].key.len || ngx_strncasecmp(name->data, key[i].key.data, name->len) != 0) { continue; } v = key[i].value; if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the duplicate \"%V\" variable", name); return NULL; } if (!(flags & NGX_HTTP_VAR_WEAK)) { v->flags &= ~NGX_HTTP_VAR_WEAK; } return v; } v = ngx_palloc(cf->pool, sizeof(ngx_http_variable_t)); if (v == NULL) { return NULL; } v->name.len = name->len; v->name.data = ngx_pnalloc(cf->pool, name->len); if (v->name.data == NULL) { return NULL; } ngx_strlow(v->name.data, name->data, name->len); v->set_handler = NULL; v->get_handler = NULL; v->data = 0; v->flags = flags; v->index = 0; rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v, 0); if (rc == NGX_ERROR) { return NULL; } if (rc == NGX_BUSY) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "conflicting variable name \"%V\"", name); return NULL; } return v; } static ngx_http_variable_t * ngx_http_add_prefix_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags) { ngx_uint_t i; ngx_http_variable_t *v; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); v = cmcf->prefix_variables.elts; for (i = 0; i < cmcf->prefix_variables.nelts; i++) { if (name->len != v[i].name.len || ngx_strncasecmp(name->data, v[i].name.data, name->len) != 0) { continue; } v = &v[i]; if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the duplicate \"%V\" variable", name); return NULL; } if (!(flags & NGX_HTTP_VAR_WEAK)) { v->flags &= ~NGX_HTTP_VAR_WEAK; } return v; } v = ngx_array_push(&cmcf->prefix_variables); if (v == NULL) { return NULL; } v->name.len = name->len; v->name.data = ngx_pnalloc(cf->pool, name->len); if (v->name.data == NULL) { return NULL; } ngx_strlow(v->name.data, name->data, name->len); v->set_handler = NULL; v->get_handler = NULL; v->data = 0; v->flags = flags; v->index = 0; return v; } ngx_int_t ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name) { ngx_uint_t i; ngx_http_variable_t *v; ngx_http_core_main_conf_t *cmcf; if (name->len == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid variable name \"$\""); return NGX_ERROR; } cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); v = cmcf->variables.elts; if (v == NULL) { if (ngx_array_init(&cmcf->variables, cf->pool, 4, sizeof(ngx_http_variable_t)) != NGX_OK) { return NGX_ERROR; } } else { for (i = 0; i < cmcf->variables.nelts; i++) { if (name->len != v[i].name.len || ngx_strncasecmp(name->data, v[i].name.data, name->len) != 0) { continue; } return i; } } v = ngx_array_push(&cmcf->variables); if (v == NULL) { return NGX_ERROR; } v->name.len = name->len; v->name.data = ngx_pnalloc(cf->pool, name->len); if (v->name.data == NULL) { return NGX_ERROR; } ngx_strlow(v->name.data, name->data, name->len); v->set_handler = NULL; v->get_handler = NULL; v->data = 0; v->flags = 0; v->index = cmcf->variables.nelts - 1; return v->index; } ngx_http_variable_value_t * ngx_http_get_indexed_variable(ngx_http_request_t *r, ngx_uint_t index) { ngx_http_variable_t *v; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); if (cmcf->variables.nelts <= index) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "unknown variable index: %ui", index); return NULL; } if (r->variables[index].not_found || r->variables[index].valid) { return &r->variables[index]; } v = cmcf->variables.elts; if (ngx_http_variable_depth == 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "cycle while evaluating variable \"%V\"", &v[index].name); return NULL; } ngx_http_variable_depth--; if (v[index].get_handler(r, &r->variables[index], v[index].data) == NGX_OK) { ngx_http_variable_depth++; if (v[index].flags & NGX_HTTP_VAR_NOCACHEABLE) { r->variables[index].no_cacheable = 1; } return &r->variables[index]; } ngx_http_variable_depth++; r->variables[index].valid = 0; r->variables[index].not_found = 1; return NULL; } ngx_http_variable_value_t * ngx_http_get_flushed_variable(ngx_http_request_t *r, ngx_uint_t index) { ngx_http_variable_value_t *v; v = &r->variables[index]; if (v->valid || v->not_found) { if (!v->no_cacheable) { return v; } v->valid = 0; v->not_found = 0; } return ngx_http_get_indexed_variable(r, index); } ngx_http_variable_value_t * ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key) { size_t len; ngx_uint_t i, n; ngx_http_variable_t *v; ngx_http_variable_value_t *vv; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); v = ngx_hash_find(&cmcf->variables_hash, key, name->data, name->len); if (v) { if (v->flags & NGX_HTTP_VAR_INDEXED) { return ngx_http_get_flushed_variable(r, v->index); } if (ngx_http_variable_depth == 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "cycle while evaluating variable \"%V\"", name); return NULL; } ngx_http_variable_depth--; vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t)); if (vv && v->get_handler(r, vv, v->data) == NGX_OK) { ngx_http_variable_depth++; return vv; } ngx_http_variable_depth++; return NULL; } vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t)); if (vv == NULL) { return NULL; } len = 0; v = cmcf->prefix_variables.elts; n = cmcf->prefix_variables.nelts; for (i = 0; i < cmcf->prefix_variables.nelts; i++) { if (name->len >= v[i].name.len && name->len > len && ngx_strncmp(name->data, v[i].name.data, v[i].name.len) == 0) { len = v[i].name.len; n = i; } } if (n != cmcf->prefix_variables.nelts) { if (v[n].get_handler(r, vv, (uintptr_t) name) == NGX_OK) { return vv; } return NULL; } vv->not_found = 1; return vv; } static ngx_int_t ngx_http_variable_request(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_str_t *s; s = (ngx_str_t *) ((char *) r + data); if (s->data) { v->len = s->len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = s->data; } else { v->not_found = 1; } return NGX_OK; } #if 0 static void ngx_http_variable_request_set(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_str_t *s; s = (ngx_str_t *) ((char *) r + data); s->len = v->len; s->data = v->data; } #endif static ngx_int_t ngx_http_variable_request_get_size(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { size_t *sp; sp = (size_t *) ((char *) r + data); v->data = ngx_pnalloc(r->pool, NGX_SIZE_T_LEN); if (v->data == NULL) { return NGX_ERROR; } v->len = ngx_sprintf(v->data, "%uz", *sp) - v->data; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; return NGX_OK; } static ngx_int_t ngx_http_variable_header(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_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_internal(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data, u_char sep) { size_t len; u_char *p; ngx_table_elt_t *h, *th; h = *(ngx_table_elt_t **) ((char *) r + data); len = 0; for (th = h; th; th = th->next) { if (th->hash == 0) { continue; } len += th->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 (h->next == NULL) { 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; for (th = h; th; th = th->next) { if (th->hash == 0) { continue; } p = ngx_copy(p, th->value.data, th->value.len); if (th->next == NULL) { 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(r, 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(r, v, (ngx_str_t *) data, &r->headers_out.headers.part, sizeof("sent_http_") - 1); } static ngx_int_t ngx_http_variable_unknown_trailer_out(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { return ngx_http_variable_unknown_header(r, v, (ngx_str_t *) data, &r->headers_out.trailers.part, sizeof("sent_trailer_") - 1); } ngx_int_t ngx_http_variable_unknown_header(ngx_http_request_t *r, ngx_http_variable_value_t *v, ngx_str_t *var, ngx_list_part_t *part, size_t prefix) { u_char *p, ch; size_t len; ngx_uint_t i, n; ngx_table_elt_t *header, *h, **ph; ph = &h; #if (NGX_SUPPRESS_WARN) len = 0; #endif 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 != var->len - prefix) { continue; } for (n = 0; n < var->len - prefix; 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 != var->len - prefix) { continue; } len += header[i].value.len + 2; *ph = &header[i]; ph = &header[i].next; } *ph = NULL; if (h == NULL) { v->not_found = 1; return NGX_OK; } len -= 2; if (h->next == NULL) { v->len = h->value.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = h->value.data; return NGX_OK; } p = ngx_pnalloc(r->pool, len); if (p == NULL) { return NGX_ERROR; } v->len = len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; for ( ;; ) { p = ngx_copy(p, h->value.data, h->value.len); if (h->next == NULL) { break; } *p++ = ','; *p++ = ' '; h = h->next; } 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, r->headers_in.cookie, &s, &cookie) == NULL) { 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 (len == 0 || 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 if (r->headers_in.chunked) { v->not_found = 1; v->no_cacheable = 1; } else { v->not_found = 1; } return NGX_OK; } static ngx_int_t ngx_http_variable_host(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_http_core_srv_conf_t *cscf; if (r->headers_in.server.len) { v->len = r->headers_in.server.len; v->data = r->headers_in.server.data; } else { cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); v->len = cscf->server_name.len; v->data = cscf->server_name.data; } v->valid = 1; v->no_cacheable = 0; v->not_found = 0; return NGX_OK; } static ngx_int_t ngx_http_variable_binary_remote_addr(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { struct sockaddr_in *sin; #if (NGX_HAVE_INET6) struct sockaddr_in6 *sin6; #endif switch (r->connection->sockaddr->sa_family) { #if (NGX_HAVE_INET6) case AF_INET6: sin6 = (struct sockaddr_in6 *) r->connection->sockaddr; v->len = sizeof(struct in6_addr); v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = sin6->sin6_addr.s6_addr; break; #endif #if (NGX_HAVE_UNIX_DOMAIN) case AF_UNIX: v->len = r->connection->addr_text.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = r->connection->addr_text.data; break; #endif default: /* AF_INET */ sin = (struct sockaddr_in *) r->connection->sockaddr; v->len = sizeof(in_addr_t); v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = (u_char *) &sin->sin_addr; break; } return NGX_OK; } static ngx_int_t ngx_http_variable_remote_addr(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { v->len = r->connection->addr_text.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = r->connection->addr_text.data; return NGX_OK; } static ngx_int_t ngx_http_variable_remote_port(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_uint_t port; v->len = 0; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = ngx_pnalloc(r->pool, sizeof("65535") - 1); if (v->data == NULL) { return NGX_ERROR; } port = ngx_inet_get_port(r->connection->sockaddr); if (port > 0 && port < 65536) { v->len = ngx_sprintf(v->data, "%ui", port) - v->data; } return NGX_OK; } static ngx_int_t ngx_http_variable_proxy_protocol_addr(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_str_t *addr; ngx_proxy_protocol_t *pp; pp = r->connection->proxy_protocol; if (pp == NULL) { v->not_found = 1; return NGX_OK; } addr = (ngx_str_t *) ((char *) pp + data); v->len = addr->len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = addr->data; return NGX_OK; } static ngx_int_t ngx_http_variable_proxy_protocol_port(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_uint_t port; ngx_proxy_protocol_t *pp; pp = r->connection->proxy_protocol; if (pp == NULL) { v->not_found = 1; return NGX_OK; } v->len = 0; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = ngx_pnalloc(r->pool, sizeof("65535") - 1); if (v->data == NULL) { return NGX_ERROR; } port = *(in_port_t *) ((char *) pp + data); 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_tlv(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_str_t *name = (ngx_str_t *) data; ngx_int_t rc; ngx_str_t tlv, value; tlv.len = name->len - (sizeof("proxy_protocol_tlv_") - 1); tlv.data = name->data + sizeof("proxy_protocol_tlv_") - 1; rc = ngx_proxy_protocol_get_tlv(r->connection, &tlv, &value); if (rc == NGX_ERROR) { return NGX_ERROR; } if (rc == NGX_DECLINED) { v->not_found = 1; return NGX_OK; } v->len = value.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = value.data; return NGX_OK; } static ngx_int_t ngx_http_variable_server_addr(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_str_t s; u_char addr[NGX_SOCKADDR_STRLEN]; s.len = NGX_SOCKADDR_STRLEN; s.data = addr; if (ngx_connection_local_sockaddr(r->connection, &s, 0) != NGX_OK) { return NGX_ERROR; } s.data = ngx_pnalloc(r->pool, s.len); if (s.data == NULL) { return NGX_ERROR; } ngx_memcpy(s.data, addr, s.len); v->len = s.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = s.data; return NGX_OK; } static ngx_int_t ngx_http_variable_server_port(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_uint_t port; v->len = 0; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; if (ngx_connection_local_sockaddr(r->connection, NULL, 0) != NGX_OK) { return NGX_ERROR; } v->data = ngx_pnalloc(r->pool, sizeof("65535") - 1); if (v->data == NULL) { return NGX_ERROR; } port = ngx_inet_get_port(r->connection->local_sockaddr); if (port > 0 && port < 65536) { v->len = ngx_sprintf(v->data, "%ui", port) - v->data; } return NGX_OK; } static ngx_int_t ngx_http_variable_scheme(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { #if (NGX_HTTP_SSL) if (r->connection->ssl) { v->len = sizeof("https") - 1; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = (u_char *) "https"; return NGX_OK; } #endif v->len = sizeof("http") - 1; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = (u_char *) "http"; return NGX_OK; } static ngx_int_t ngx_http_variable_https(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { #if (NGX_HTTP_SSL) if (r->connection->ssl) { v->len = sizeof("on") - 1; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = (u_char *) "on"; return NGX_OK; } #endif *v = ngx_http_variable_null_value; return NGX_OK; } static void ngx_http_variable_set_args(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { r->args.len = v->len; r->args.data = v->data; r->valid_unparsed_uri = 0; } static ngx_int_t ngx_http_variable_is_args(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { if (r->args.len == 0) { *v = ngx_http_variable_null_value; return NGX_OK; } v->len = 1; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = (u_char *) "?"; return NGX_OK; } static ngx_int_t ngx_http_variable_document_root(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_str_t path; ngx_http_core_loc_conf_t *clcf; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (clcf->root_lengths == NULL) { v->len = clcf->root.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = clcf->root.data; } else { if (ngx_http_script_run(r, &path, clcf->root_lengths->elts, 0, clcf->root_values->elts) == NULL) { return NGX_ERROR; } if (ngx_get_full_name(r->pool, (ngx_str_t *) &ngx_cycle->prefix, &path) != NGX_OK) { return NGX_ERROR; } v->len = path.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = path.data; } return NGX_OK; } static ngx_int_t ngx_http_variable_realpath_root(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *real; size_t len; ngx_str_t path; ngx_http_core_loc_conf_t *clcf; #if (NGX_HAVE_MAX_PATH) u_char buffer[NGX_MAX_PATH]; #endif clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (clcf->root_lengths == NULL) { path = clcf->root; } else { if (ngx_http_script_run(r, &path, clcf->root_lengths->elts, 1, clcf->root_values->elts) == NULL) { return NGX_ERROR; } path.data[path.len - 1] = '\0'; if (ngx_get_full_name(r->pool, (ngx_str_t *) &ngx_cycle->prefix, &path) != NGX_OK) { return NGX_ERROR; } } #if (NGX_HAVE_MAX_PATH) real = buffer; #else real = NULL; #endif real = ngx_realpath(path.data, real); if (real == NULL) { ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno, ngx_realpath_n " \"%s\" failed", path.data); return NGX_ERROR; } len = ngx_strlen(real); v->data = ngx_pnalloc(r->pool, len); if (v->data == NULL) { #if !(NGX_HAVE_MAX_PATH) ngx_free(real); #endif return NGX_ERROR; } v->len = len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; ngx_memcpy(v->data, real, len); #if !(NGX_HAVE_MAX_PATH) ngx_free(real); #endif return NGX_OK; } static ngx_int_t ngx_http_variable_request_filename(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { size_t root; ngx_str_t path; if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) { return NGX_ERROR; } /* ngx_http_map_uri_to_path() allocates memory for terminating '\0' */ v->len = path.len - 1; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = path.data; return NGX_OK; } static ngx_int_t ngx_http_variable_server_name(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_http_core_srv_conf_t *cscf; cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); v->len = cscf->server_name.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = cscf->server_name.data; return NGX_OK; } static ngx_int_t ngx_http_variable_request_method(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { if (r->main->method_name.data) { v->len = r->main->method_name.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = r->main->method_name.data; } else { v->not_found = 1; } return NGX_OK; } static ngx_int_t ngx_http_variable_remote_user(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_int_t rc; rc = ngx_http_auth_basic_user(r); if (rc == NGX_DECLINED) { v->not_found = 1; return NGX_OK; } if (rc == NGX_ERROR) { return NGX_ERROR; } v->len = r->headers_in.user.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = r->headers_in.user.data; return NGX_OK; } static ngx_int_t ngx_http_variable_bytes_sent(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN); if (p == NULL) { return NGX_ERROR; } v->len = ngx_sprintf(p, "%O", r->connection->sent) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } static ngx_int_t ngx_http_variable_body_bytes_sent(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { off_t sent; u_char *p; sent = r->connection->sent - r->header_size; if (sent < 0) { sent = 0; } p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN); if (p == NULL) { return NGX_ERROR; } v->len = ngx_sprintf(p, "%O", sent) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } static ngx_int_t ngx_http_variable_pipe(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { v->data = (u_char *) (r->pipeline ? "p" : "."); v->len = 1; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; return NGX_OK; } static ngx_int_t ngx_http_variable_status(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_uint_t status; v->data = ngx_pnalloc(r->pool, NGX_INT_T_LEN); if (v->data == NULL) { return NGX_ERROR; } if (r->err_status) { status = r->err_status; } else if (r->headers_out.status) { status = r->headers_out.status; } else if (r->http_version == NGX_HTTP_VERSION_9) { status = 9; } else { status = 0; } v->len = ngx_sprintf(v->data, "%03ui", status) - v->data; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; return NGX_OK; } static ngx_int_t ngx_http_variable_sent_content_type(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { if (r->headers_out.content_type.len) { v->len = r->headers_out.content_type.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = r->headers_out.content_type.data; } else { v->not_found = 1; } return NGX_OK; } static ngx_int_t ngx_http_variable_sent_content_length(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; if (r->headers_out.content_length) { v->len = r->headers_out.content_length->value.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = r->headers_out.content_length->value.data; return NGX_OK; } if (r->headers_out.content_length_n >= 0) { p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN); if (p == NULL) { return NGX_ERROR; } v->len = ngx_sprintf(p, "%O", r->headers_out.content_length_n) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } v->not_found = 1; return NGX_OK; } static ngx_int_t ngx_http_variable_sent_location(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_str_t name; if (r->headers_out.location) { v->len = r->headers_out.location->value.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = r->headers_out.location->value.data; return NGX_OK; } ngx_str_set(&name, "sent_http_location"); return ngx_http_variable_unknown_header(r, 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 void ngx_http_variable_set_limit_rate(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ssize_t s; 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 $limit_rate \"%V\"", &val); return; } r->limit_rate = s; r->limit_rate_set = 1; } static ngx_int_t ngx_http_variable_request_completion(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { if (r->request_complete) { v->len = 2; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = (u_char *) "OK"; return NGX_OK; } *v = ngx_http_variable_null_value; return NGX_OK; } static ngx_int_t ngx_http_variable_request_body(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; size_t len; ngx_buf_t *buf; ngx_chain_t *cl; if (r->request_body == NULL || r->request_body->bufs == NULL || r->request_body->temp_file) { v->not_found = 1; return NGX_OK; } cl = r->request_body->bufs; buf = cl->buf; if (cl->next == NULL) { v->len = buf->last - buf->pos; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = buf->pos; return NGX_OK; } len = buf->last - buf->pos; cl = cl->next; for ( /* void */ ; cl; cl = cl->next) { buf = cl->buf; len += buf->last - buf->pos; } p = ngx_pnalloc(r->pool, len); if (p == NULL) { return NGX_ERROR; } v->data = p; cl = r->request_body->bufs; for ( /* void */ ; cl; cl = cl->next) { buf = cl->buf; p = ngx_cpymem(p, buf->pos, buf->last - buf->pos); } v->len = len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; return NGX_OK; } static ngx_int_t ngx_http_variable_request_body_file(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { if (r->request_body == NULL || r->request_body->temp_file == NULL) { v->not_found = 1; return NGX_OK; } v->len = r->request_body->temp_file->file.name.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = r->request_body->temp_file->file.name.data; return NGX_OK; } static ngx_int_t ngx_http_variable_request_length(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN); if (p == NULL) { return NGX_ERROR; } v->len = ngx_sprintf(p, "%O", r->request_length) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } static ngx_int_t ngx_http_variable_request_time(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; ngx_time_t *tp; ngx_msec_int_t ms; p = ngx_pnalloc(r->pool, NGX_TIME_T_LEN + 4); if (p == NULL) { return NGX_ERROR; } tp = ngx_timeofday(); ms = (ngx_msec_int_t) ((tp->sec - r->start_sec) * 1000 + (tp->msec - r->start_msec)); ms = ngx_max(ms, 0); v->len = ngx_sprintf(p, "%T.%03M", (time_t) ms / 1000, ms % 1000) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } static ngx_int_t ngx_http_variable_request_id(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *id; #if (NGX_OPENSSL) u_char random_bytes[16]; #endif id = ngx_pnalloc(r->pool, 32); if (id == NULL) { return NGX_ERROR; } v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->len = 32; v->data = id; #if (NGX_OPENSSL) if (RAND_bytes(random_bytes, 16) == 1) { ngx_hex_dump(id, random_bytes, 16); return NGX_OK; } ngx_ssl_error(NGX_LOG_ERR, r->connection->log, 0, "RAND_bytes() failed"); #endif ngx_sprintf(id, "%08xD%08xD%08xD%08xD", (uint32_t) ngx_random(), (uint32_t) ngx_random(), (uint32_t) ngx_random(), (uint32_t) ngx_random()); return NGX_OK; } static ngx_int_t ngx_http_variable_connection(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; p = ngx_pnalloc(r->pool, NGX_ATOMIC_T_LEN); if (p == NULL) { return NGX_ERROR; } v->len = ngx_sprintf(p, "%uA", r->connection->number) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } static ngx_int_t ngx_http_variable_connection_requests(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; p = ngx_pnalloc(r->pool, NGX_INT_T_LEN); if (p == NULL) { return NGX_ERROR; } v->len = ngx_sprintf(p, "%ui", r->connection->requests) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } static ngx_int_t ngx_http_variable_connection_time(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; ngx_msec_int_t ms; p = ngx_pnalloc(r->pool, NGX_TIME_T_LEN + 4); if (p == NULL) { return NGX_ERROR; } ms = ngx_current_msec - r->connection->start_time; ms = ngx_max(ms, 0); v->len = ngx_sprintf(p, "%T.%03M", (time_t) ms / 1000, ms % 1000) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } static ngx_int_t ngx_http_variable_nginx_version(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { 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->realloc_captures) { r->realloc_captures = 0; r->captures = ngx_palloc(r->pool, len * sizeof(int)); if (r->captures == NULL) { return NGX_ERROR; } } } else { len = 0; } rc = ngx_regex_exec(re->regex, s, r->captures, len); if (rc == NGX_REGEX_NO_MATCHED) { return NGX_DECLINED; } if (rc < 0) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, ngx_regex_exec_n " failed: %i on \"%V\" using \"%V\"", rc, s, &re->name); return NGX_ERROR; } for (i = 0; i < re->nvariables; i++) { n = re->variables[i].capture; index = re->variables[i].index; vv = &r->variables[index]; vv->len = r->captures[n + 1] - r->captures[n]; vv->valid = 1; vv->no_cacheable = 0; vv->not_found = 0; vv->data = &s->data[r->captures[n]]; #if (NGX_DEBUG) { ngx_http_variable_t *v; v = cmcf->variables.elts; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http regex set $%V to \"%v\"", &v[index].name, vv); } #endif } r->ncaptures = rc * 2; r->captures_data = s->data; return NGX_OK; } #endif ngx_int_t ngx_http_variables_add_core_vars(ngx_conf_t *cf) { ngx_http_variable_t *cv, *v; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); cmcf->variables_keys = ngx_pcalloc(cf->temp_pool, sizeof(ngx_hash_keys_arrays_t)); if (cmcf->variables_keys == NULL) { return NGX_ERROR; } cmcf->variables_keys->pool = cf->pool; cmcf->variables_keys->temp_pool = cf->pool; if (ngx_hash_keys_array_init(cmcf->variables_keys, NGX_HASH_SMALL) != NGX_OK) { return NGX_ERROR; } if (ngx_array_init(&cmcf->prefix_variables, cf->pool, 8, sizeof(ngx_http_variable_t)) != NGX_OK) { return NGX_ERROR; } for (cv = ngx_http_core_variables; cv->name.len; cv++) { v = ngx_http_add_variable(cf, &cv->name, cv->flags); if (v == NULL) { return NGX_ERROR; } *v = *cv; } return NGX_OK; } ngx_int_t ngx_http_variables_init_vars(ngx_conf_t *cf) { size_t len; ngx_uint_t i, n; ngx_hash_key_t *key; ngx_hash_init_t hash; ngx_http_variable_t *v, *av, *pv; ngx_http_core_main_conf_t *cmcf; /* set the handlers for the indexed http variables */ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); v = cmcf->variables.elts; pv = cmcf->prefix_variables.elts; key = cmcf->variables_keys->keys.elts; for (i = 0; i < cmcf->variables.nelts; i++) { for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) { av = key[n].value; if (v[i].name.len == key[n].key.len && ngx_strncmp(v[i].name.data, key[n].key.data, v[i].name.len) == 0) { v[i].get_handler = av->get_handler; v[i].data = av->data; av->flags |= NGX_HTTP_VAR_INDEXED; v[i].flags = av->flags; av->index = i; if (av->get_handler == NULL || (av->flags & NGX_HTTP_VAR_WEAK)) { break; } goto next; } } len = 0; av = NULL; for (n = 0; n < cmcf->prefix_variables.nelts; n++) { if (v[i].name.len >= pv[n].name.len && v[i].name.len > len && ngx_strncmp(v[i].name.data, pv[n].name.data, pv[n].name.len) == 0) { av = &pv[n]; len = pv[n].name.len; } } if (av) { v[i].get_handler = av->get_handler; v[i].data = (uintptr_t) &v[i].name; v[i].flags = av->flags; goto next; } if (v[i].get_handler == NULL) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "unknown \"%V\" variable", &v[i].name); return NGX_ERROR; } next: continue; } for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) { av = key[n].value; if (av->flags & NGX_HTTP_VAR_NOHASH) { key[n].key.data = NULL; } } hash.hash = &cmcf->variables_hash; hash.key = ngx_hash_key; hash.max_size = cmcf->variables_hash_max_size; hash.bucket_size = cmcf->variables_hash_bucket_size; hash.name = "variables_hash"; hash.pool = cf->pool; hash.temp_pool = NULL; if (ngx_hash_init(&hash, cmcf->variables_keys->keys.elts, cmcf->variables_keys->keys.nelts) != NGX_OK) { return NGX_ERROR; } cmcf->variables_keys = NULL; return NGX_OK; } nginx-1.24.0/src/http/ngx_http_variables.h000644 001751 001751 00000006217 14415135676 021733 0ustar00mdouninmdounin000000 000000 /* * 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 #define NGX_HTTP_VAR_WEAK 16 #define NGX_HTTP_VAR_PREFIX 32 struct ngx_http_variable_s { ngx_str_t name; /* must be first to build the hash */ ngx_http_set_variable_pt set_handler; ngx_http_get_variable_pt get_handler; uintptr_t data; ngx_uint_t flags; ngx_uint_t index; }; #define ngx_http_null_variable { ngx_null_string, NULL, NULL, 0, 0, 0 } ngx_http_variable_t *ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags); ngx_int_t ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name); ngx_http_variable_value_t *ngx_http_get_indexed_variable(ngx_http_request_t *r, ngx_uint_t index); ngx_http_variable_value_t *ngx_http_get_flushed_variable(ngx_http_request_t *r, ngx_uint_t index); ngx_http_variable_value_t *ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key); ngx_int_t ngx_http_variable_unknown_header(ngx_http_request_t *r, 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_ */ nginx-1.24.0/src/http/ngx_http_write_filter_module.c000644 001751 001751 00000024432 14415135676 024021 0ustar00mdouninmdounin000000 000000 /* * 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 (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; } if (ngx_buf_size(cl->buf) < 0) { ngx_log_error(NGX_LOG_ALERT, c->log, 0, "negative 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; } 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 (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; } if (ngx_buf_size(cl->buf) < 0) { ngx_log_error(NGX_LOG_ALERT, c->log, 0, "negative 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; } 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) && !(flush && c->need_flush_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_set) { r->limit_rate = ngx_http_complex_value_size(r, clcf->limit_rate, 0); r->limit_rate_set = 1; } if (r->limit_rate) { if (!r->limit_rate_after_set) { r->limit_rate_after = ngx_http_complex_value_size(r, clcf->limit_rate_after, 0); r->limit_rate_after_set = 1; } 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) { c->write->delayed = 1; ngx_add_timer(c->write, delay); } } if (chain && c->write->ready && !c->write->delayed) { ngx_post_event(c->write, &ngx_posted_next_events); } for (cl = r->out; cl && cl != chain; /* void */) { 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; } nginx-1.24.0/src/http/v2/000755 001751 001751 00000000000 14415135700 016204 5ustar00mdouninmdounin000000 000000 nginx-1.24.0/src/http/v2/ngx_http_v2.c000644 001751 001751 00000423771 14415135676 020644 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Nginx, Inc. * Copyright (C) Valentin V. Bartenev */ #include #include #include #include typedef struct { ngx_str_t name; ngx_uint_t offset; ngx_uint_t hash; ngx_http_header_t *hh; } ngx_http_v2_parse_header_t; /* errors */ #define NGX_HTTP_V2_NO_ERROR 0x0 #define NGX_HTTP_V2_PROTOCOL_ERROR 0x1 #define NGX_HTTP_V2_INTERNAL_ERROR 0x2 #define NGX_HTTP_V2_FLOW_CTRL_ERROR 0x3 #define NGX_HTTP_V2_SETTINGS_TIMEOUT 0x4 #define NGX_HTTP_V2_STREAM_CLOSED 0x5 #define NGX_HTTP_V2_SIZE_ERROR 0x6 #define NGX_HTTP_V2_REFUSED_STREAM 0x7 #define NGX_HTTP_V2_CANCEL 0x8 #define NGX_HTTP_V2_COMP_ERROR 0x9 #define NGX_HTTP_V2_CONNECT_ERROR 0xa #define NGX_HTTP_V2_ENHANCE_YOUR_CALM 0xb #define NGX_HTTP_V2_INADEQUATE_SECURITY 0xc #define NGX_HTTP_V2_HTTP_1_1_REQUIRED 0xd /* frame sizes */ #define NGX_HTTP_V2_SETTINGS_ACK_SIZE 0 #define NGX_HTTP_V2_RST_STREAM_SIZE 4 #define NGX_HTTP_V2_PRIORITY_SIZE 5 #define NGX_HTTP_V2_PING_SIZE 8 #define NGX_HTTP_V2_GOAWAY_SIZE 8 #define NGX_HTTP_V2_WINDOW_UPDATE_SIZE 4 #define NGX_HTTP_V2_SETTINGS_PARAM_SIZE 6 /* settings fields */ #define NGX_HTTP_V2_HEADER_TABLE_SIZE_SETTING 0x1 #define NGX_HTTP_V2_ENABLE_PUSH_SETTING 0x2 #define NGX_HTTP_V2_MAX_STREAMS_SETTING 0x3 #define NGX_HTTP_V2_INIT_WINDOW_SIZE_SETTING 0x4 #define NGX_HTTP_V2_MAX_FRAME_SIZE_SETTING 0x5 #define NGX_HTTP_V2_FRAME_BUFFER_SIZE 24 #define NGX_HTTP_V2_ROOT (void *) -1 static void ngx_http_v2_read_handler(ngx_event_t *rev); static void ngx_http_v2_write_handler(ngx_event_t *wev); static void ngx_http_v2_handle_connection(ngx_http_v2_connection_t *h2c); static void ngx_http_v2_lingering_close(ngx_connection_t *c); static void ngx_http_v2_lingering_close_handler(ngx_event_t *rev); static u_char *ngx_http_v2_state_proxy_protocol(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_preface(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_preface_end(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_head(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_data(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_read_data(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_headers(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_header_block(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_field_len(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_field_huff(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_field_raw(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_field_skip(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_header_complete(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_handle_continuation(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end, ngx_http_v2_handler_pt handler); static u_char *ngx_http_v2_state_priority(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_rst_stream(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_settings(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_settings_params(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_push_promise(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_ping(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_goaway(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_window_update(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_continuation(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_complete(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_skip_padded(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_skip(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_save(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end, ngx_http_v2_handler_pt handler); static u_char *ngx_http_v2_state_headers_save(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end, ngx_http_v2_handler_pt handler); static u_char *ngx_http_v2_connection_error(ngx_http_v2_connection_t *h2c, ngx_uint_t err); static ngx_int_t ngx_http_v2_parse_int(ngx_http_v2_connection_t *h2c, u_char **pos, u_char *end, ngx_uint_t prefix); static ngx_http_v2_stream_t *ngx_http_v2_create_stream( ngx_http_v2_connection_t *h2c, ngx_uint_t push); static ngx_http_v2_node_t *ngx_http_v2_get_node_by_id( ngx_http_v2_connection_t *h2c, ngx_uint_t sid, ngx_uint_t alloc); static ngx_http_v2_node_t *ngx_http_v2_get_closed_node( ngx_http_v2_connection_t *h2c); #define ngx_http_v2_index_size(h2scf) (h2scf->streams_index_mask + 1) #define ngx_http_v2_index(h2scf, sid) ((sid >> 1) & h2scf->streams_index_mask) static ngx_int_t ngx_http_v2_send_settings(ngx_http_v2_connection_t *h2c); static ngx_int_t ngx_http_v2_settings_frame_handler( ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame); static ngx_int_t ngx_http_v2_send_window_update(ngx_http_v2_connection_t *h2c, ngx_uint_t sid, size_t window); static ngx_int_t ngx_http_v2_send_rst_stream(ngx_http_v2_connection_t *h2c, ngx_uint_t sid, ngx_uint_t status); static ngx_int_t ngx_http_v2_send_goaway(ngx_http_v2_connection_t *h2c, ngx_uint_t status); static ngx_http_v2_out_frame_t *ngx_http_v2_get_frame( ngx_http_v2_connection_t *h2c, size_t length, ngx_uint_t type, u_char flags, ngx_uint_t sid); static ngx_int_t ngx_http_v2_frame_handler(ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame); static ngx_int_t ngx_http_v2_validate_header(ngx_http_request_t *r, ngx_http_v2_header_t *header); static ngx_int_t ngx_http_v2_pseudo_header(ngx_http_request_t *r, ngx_http_v2_header_t *header); static ngx_int_t ngx_http_v2_parse_path(ngx_http_request_t *r, ngx_str_t *value); static ngx_int_t ngx_http_v2_parse_method(ngx_http_request_t *r, ngx_str_t *value); static ngx_int_t ngx_http_v2_parse_scheme(ngx_http_request_t *r, ngx_str_t *value); static ngx_int_t ngx_http_v2_parse_authority(ngx_http_request_t *r, ngx_str_t *value); static ngx_int_t ngx_http_v2_parse_header(ngx_http_request_t *r, ngx_http_v2_parse_header_t *header, ngx_str_t *value); static ngx_int_t ngx_http_v2_construct_request_line(ngx_http_request_t *r); static ngx_int_t ngx_http_v2_cookie(ngx_http_request_t *r, ngx_http_v2_header_t *header); static ngx_int_t ngx_http_v2_construct_cookie_header(ngx_http_request_t *r); static void ngx_http_v2_run_request(ngx_http_request_t *r); static void ngx_http_v2_run_request_handler(ngx_event_t *ev); static ngx_int_t ngx_http_v2_process_request_body(ngx_http_request_t *r, u_char *pos, size_t size, ngx_uint_t last, ngx_uint_t flush); static ngx_int_t ngx_http_v2_filter_request_body(ngx_http_request_t *r); static void ngx_http_v2_read_client_request_body_handler(ngx_http_request_t *r); 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_retry_close_stream_handler(ngx_event_t *ev); static void ngx_http_v2_handle_connection_handler(ngx_event_t *rev); static void ngx_http_v2_idle_handler(ngx_event_t *rev); static void ngx_http_v2_finalize_connection(ngx_http_v2_connection_t *h2c, ngx_uint_t status); static ngx_int_t ngx_http_v2_adjust_windows(ngx_http_v2_connection_t *h2c, ssize_t delta); static void ngx_http_v2_set_dependency(ngx_http_v2_connection_t *h2c, ngx_http_v2_node_t *node, ngx_uint_t depend, ngx_uint_t exclusive); static void ngx_http_v2_node_children_update(ngx_http_v2_node_t *node); static void ngx_http_v2_pool_cleanup(void *data); static ngx_http_v2_handler_pt ngx_http_v2_frame_states[] = { ngx_http_v2_state_data, /* NGX_HTTP_V2_DATA_FRAME */ ngx_http_v2_state_headers, /* NGX_HTTP_V2_HEADERS_FRAME */ ngx_http_v2_state_priority, /* NGX_HTTP_V2_PRIORITY_FRAME */ ngx_http_v2_state_rst_stream, /* NGX_HTTP_V2_RST_STREAM_FRAME */ ngx_http_v2_state_settings, /* NGX_HTTP_V2_SETTINGS_FRAME */ ngx_http_v2_state_push_promise, /* NGX_HTTP_V2_PUSH_PROMISE_FRAME */ ngx_http_v2_state_ping, /* NGX_HTTP_V2_PING_FRAME */ ngx_http_v2_state_goaway, /* NGX_HTTP_V2_GOAWAY_FRAME */ ngx_http_v2_state_window_update, /* NGX_HTTP_V2_WINDOW_UPDATE_FRAME */ ngx_http_v2_state_continuation /* NGX_HTTP_V2_CONTINUATION_FRAME */ }; #define NGX_HTTP_V2_FRAME_STATES \ (sizeof(ngx_http_v2_frame_states) / sizeof(ngx_http_v2_handler_pt)) static ngx_http_v2_parse_header_t ngx_http_v2_parse_headers[] = { { ngx_string("host"), offsetof(ngx_http_headers_in_t, host), 0, NULL }, { ngx_string("accept-encoding"), offsetof(ngx_http_headers_in_t, accept_encoding), 0, NULL }, { ngx_string("accept-language"), offsetof(ngx_http_headers_in_t, accept_language), 0, NULL }, { ngx_string("user-agent"), offsetof(ngx_http_headers_in_t, user_agent), 0, NULL }, { ngx_null_string, 0, 0, NULL } }; void ngx_http_v2_init(ngx_event_t *rev) { ngx_connection_t *c; ngx_pool_cleanup_t *cln; ngx_http_connection_t *hc; ngx_http_v2_srv_conf_t *h2scf; ngx_http_v2_main_conf_t *h2mcf; ngx_http_v2_connection_t *h2c; ngx_http_core_srv_conf_t *cscf; 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->concurrent_pushes = h2scf->concurrent_pushes; h2c->priority_limit = ngx_max(h2scf->concurrent_streams, 100); h2c->pool = ngx_create_pool(h2scf->pool_size, h2c->connection->log); if (h2c->pool == NULL) { ngx_http_close_connection(c); return; } cln = ngx_pool_cleanup_add(c->pool, 0); if (cln == NULL) { ngx_http_close_connection(c); return; } cln->handler = ngx_http_v2_pool_cleanup; cln->data = h2c; h2c->streams_index = ngx_pcalloc(c->pool, ngx_http_v2_index_size(h2scf) * sizeof(ngx_http_v2_node_t *)); if (h2c->streams_index == NULL) { ngx_http_close_connection(c); return; } if (ngx_http_v2_send_settings(h2c) == NGX_ERROR) { ngx_http_close_connection(c); return; } if (ngx_http_v2_send_window_update(h2c, 0, NGX_HTTP_V2_MAX_WINDOW - NGX_HTTP_V2_DEFAULT_WINDOW) == NGX_ERROR) { ngx_http_close_connection(c); return; } h2c->state.handler = hc->proxy_protocol ? ngx_http_v2_state_proxy_protocol : ngx_http_v2_state_preface; ngx_queue_init(&h2c->waiting); ngx_queue_init(&h2c->dependencies); ngx_queue_init(&h2c->closed); c->data = h2c; rev->handler = ngx_http_v2_read_handler; c->write->handler = ngx_http_v2_write_handler; if (!rev->timer_set) { cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); ngx_add_timer(rev, cscf->client_header_timeout); } c->idle = 1; ngx_reusable_connection(c, 0); ngx_http_v2_read_handler(rev); } static void ngx_http_v2_read_handler(ngx_event_t *rev) { u_char *p, *end; size_t available; ssize_t n; ngx_connection_t *c; ngx_http_v2_main_conf_t *h2mcf; ngx_http_v2_connection_t *h2c; c = rev->data; h2c = c->data; if (rev->timedout) { ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); return; } ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http2 read handler"); h2c->blocked = 1; if (c->close) { c->close = 0; if (c->error) { ngx_http_v2_finalize_connection(h2c, 0); return; } if (!h2c->processing && !h2c->pushing) { ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_NO_ERROR); return; } if (!h2c->goaway) { h2c->goaway = 1; if (ngx_http_v2_send_goaway(h2c, NGX_HTTP_V2_NO_ERROR) == NGX_ERROR) { ngx_http_v2_finalize_connection(h2c, 0); return; } if (ngx_http_v2_send_output_queue(h2c) == NGX_ERROR) { ngx_http_v2_finalize_connection(h2c, 0); return; } } h2c->blocked = 0; return; } h2mcf = ngx_http_get_module_main_conf(h2c->http_connection->conf_ctx, ngx_http_v2_module); available = h2mcf->recv_buffer_size - 2 * NGX_HTTP_V2_STATE_BUFFER_SIZE; do { p = h2mcf->recv_buffer; ngx_memcpy(p, h2c->state.buffer, NGX_HTTP_V2_STATE_BUFFER_SIZE); end = p + h2c->state.buffer_used; n = c->recv(c, end, available); if (n == NGX_AGAIN) { break; } if (n == 0 && (h2c->state.incomplete || h2c->processing || h2c->pushing)) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client prematurely closed connection"); } if (n == 0 || n == NGX_ERROR) { c->error = 1; ngx_http_v2_finalize_connection(h2c, 0); return; } end += n; h2c->state.buffer_used = 0; h2c->state.incomplete = 0; do { p = h2c->state.handler(h2c, p, end); if (p == NULL) { return; } } while (p != end); h2c->total_bytes += n; if (h2c->total_bytes / 8 > h2c->payload_bytes + 1048576) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "http2 flood detected"); ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_NO_ERROR); return; } } 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; 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; c->timedout = 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; wev = c->write; if (c->error) { goto error; } if (!wev->ready) { return NGX_AGAIN; } cl = NULL; out = NULL; for (frame = h2c->last_out; frame; frame = fn) { frame->last->next = cl; cl = frame->first; fn = frame->next; frame->next = out; out = frame; ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, "http2 frame out: %p sid:%ui bl:%d len:%uz", out, out->stream ? out->stream->node->id : 0, out->blocked, out->length); } cl = c->send_chain(c, cl, 0); if (cl == NGX_CHAIN_ERROR) { goto error; } clcf = ngx_http_get_module_loc_conf(h2c->http_connection->conf_ctx, ngx_http_core_module); if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) { goto error; } if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) { if (ngx_tcp_push(c->fd) == -1) { ngx_connection_error(c, ngx_socket_errno, ngx_tcp_push_n " failed"); goto error; } c->tcp_nopush = NGX_TCP_NOPUSH_UNSET; tcp_nodelay = ngx_tcp_nodelay_and_tcp_nopush ? 1 : 0; } else { tcp_nodelay = 1; } if (tcp_nodelay && clcf->tcp_nodelay && ngx_tcp_nodelay(c) != NGX_OK) { goto error; } for ( /* void */ ; out; out = fn) { fn = out->next; if (out->handler(h2c, out) != NGX_OK) { out->blocked = 1; break; } ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, "http2 frame sent: %p sid:%ui bl:%d len:%uz", out, out->stream ? out->stream->node->id : 0, out->blocked, out->length); } frame = NULL; for ( /* void */ ; out; out = fn) { fn = out->next; out->next = frame; frame = out; } h2c->last_out = frame; if (!wev->ready) { ngx_add_timer(wev, clcf->send_timeout); return NGX_AGAIN; } if (wev->timer_set) { ngx_del_timer(wev); } return NGX_OK; error: c->error = 1; if (!h2c->blocked) { ngx_post_event(wev, &ngx_posted_events); } return NGX_ERROR; } static void ngx_http_v2_handle_connection(ngx_http_v2_connection_t *h2c) { ngx_int_t rc; ngx_connection_t *c; ngx_http_core_loc_conf_t *clcf; if (h2c->last_out || h2c->processing || h2c->pushing) { return; } c = h2c->connection; if (c->error) { ngx_http_close_connection(c); return; } if (c->buffered) { h2c->blocked = 1; rc = ngx_http_v2_send_output_queue(h2c); h2c->blocked = 0; if (rc == NGX_ERROR) { ngx_http_close_connection(c); return; } if (rc == NGX_AGAIN) { return; } /* rc == NGX_OK */ } if (h2c->goaway) { ngx_http_v2_lingering_close(c); return; } clcf = ngx_http_get_module_loc_conf(h2c->http_connection->conf_ctx, ngx_http_core_module); if (!c->read->timer_set) { ngx_add_timer(c->read, clcf->keepalive_timeout); } ngx_reusable_connection(c, 1); if (h2c->state.incomplete) { return; } ngx_destroy_pool(h2c->pool); h2c->pool = NULL; h2c->free_frames = NULL; h2c->frames = 0; h2c->free_fake_connections = NULL; #if (NGX_HTTP_SSL) if (c->ssl) { ngx_ssl_free_buffer(c); } #endif c->destroyed = 1; 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); } } static void ngx_http_v2_lingering_close(ngx_connection_t *c) { ngx_event_t *rev, *wev; ngx_http_v2_connection_t *h2c; ngx_http_core_loc_conf_t *clcf; h2c = c->data; clcf = ngx_http_get_module_loc_conf(h2c->http_connection->conf_ctx, ngx_http_core_module); if (clcf->lingering_close == NGX_HTTP_LINGERING_OFF) { ngx_http_close_connection(c); return; } if (h2c->lingering_time == 0) { h2c->lingering_time = ngx_time() + (time_t) (clcf->lingering_time / 1000); } #if (NGX_HTTP_SSL) if (c->ssl) { ngx_int_t rc; rc = ngx_ssl_shutdown(c); if (rc == NGX_ERROR) { ngx_http_close_connection(c); return; } if (rc == NGX_AGAIN) { c->ssl->handler = ngx_http_v2_lingering_close; return; } } #endif rev = c->read; rev->handler = ngx_http_v2_lingering_close_handler; if (ngx_handle_read_event(rev, 0) != NGX_OK) { ngx_http_close_connection(c); return; } wev = c->write; wev->handler = ngx_http_empty_handler; if (wev->active && (ngx_event_flags & NGX_USE_LEVEL_EVENT)) { if (ngx_del_event(wev, NGX_WRITE_EVENT, 0) != NGX_OK) { ngx_http_close_connection(c); return; } } if (ngx_shutdown_socket(c->fd, NGX_WRITE_SHUTDOWN) == -1) { ngx_connection_error(c, ngx_socket_errno, ngx_shutdown_socket_n " failed"); ngx_http_close_connection(c); return; } c->close = 0; ngx_reusable_connection(c, 1); ngx_add_timer(rev, clcf->lingering_timeout); if (rev->ready) { ngx_http_v2_lingering_close_handler(rev); } } static void ngx_http_v2_lingering_close_handler(ngx_event_t *rev) { ssize_t n; ngx_msec_t timer; ngx_connection_t *c; ngx_http_core_loc_conf_t *clcf; ngx_http_v2_connection_t *h2c; u_char buffer[NGX_HTTP_LINGERING_BUFFER_SIZE]; c = rev->data; h2c = c->data; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http2 lingering close handler"); if (rev->timedout || c->close) { ngx_http_close_connection(c); return; } timer = (ngx_msec_t) h2c->lingering_time - (ngx_msec_t) ngx_time(); if ((ngx_msec_int_t) timer <= 0) { ngx_http_close_connection(c); return; } do { n = c->recv(c, buffer, NGX_HTTP_LINGERING_BUFFER_SIZE); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "lingering read: %z", n); if (n == NGX_AGAIN) { break; } if (n == NGX_ERROR || n == 0) { ngx_http_close_connection(c); return; } } while (rev->ready); if (ngx_handle_read_event(rev, 0) != NGX_OK) { ngx_http_close_connection(c); return; } clcf = ngx_http_get_module_loc_conf(h2c->http_connection->conf_ctx, ngx_http_core_module); timer *= 1000; if (timer > clcf->lingering_timeout) { timer = clcf->lingering_timeout; } ngx_add_timer(rev, timer); } 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_error(NGX_LOG_INFO, h2c->connection->log, 0, "invalid connection preface"); 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_error(NGX_LOG_INFO, h2c->connection->log, 0, "invalid connection preface"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); } ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 preface verified"); return ngx_http_v2_state_head(h2c, pos + sizeof(preface) - 1, end); } static u_char * ngx_http_v2_state_head(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) { uint32_t head; ngx_uint_t type; if (end - pos < NGX_HTTP_V2_FRAME_HEADER_SIZE) { return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_head); } head = ngx_http_v2_parse_uint32(pos); h2c->state.length = ngx_http_v2_parse_length(head); h2c->state.flags = pos[4]; h2c->state.sid = ngx_http_v2_parse_sid(&pos[5]); pos += NGX_HTTP_V2_FRAME_HEADER_SIZE; type = ngx_http_v2_parse_type(head); ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 frame type:%ui f:%Xd l:%uz sid:%ui", type, h2c->state.flags, h2c->state.length, h2c->state.sid); if (type >= NGX_HTTP_V2_FRAME_STATES) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent frame with unknown type %ui", type); return ngx_http_v2_state_skip(h2c, pos, end); } return ngx_http_v2_frame_states[type](h2c, pos, end); } static u_char * ngx_http_v2_state_data(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) { size_t size; ngx_http_v2_node_t *node; ngx_http_v2_stream_t *stream; size = h2c->state.length; if (h2c->state.flags & NGX_HTTP_V2_PADDED_FLAG) { if (h2c->state.length == 0) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent padded DATA frame " "with incorrect length: 0"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); } if (end - pos == 0) { return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_data); } h2c->state.padding = *pos++; if (h2c->state.padding >= size) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent padded DATA frame " "with incorrect length: %uz, padding: %uz", size, h2c->state.padding); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); } h2c->state.length -= 1 + h2c->state.padding; } ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 DATA frame"); if (h2c->state.sid == 0) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent DATA frame with incorrect identifier"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); } if (size > h2c->recv_window) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client violated connection flow control: " "received DATA frame length %uz, available window %uz", size, h2c->recv_window); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_FLOW_CTRL_ERROR); } h2c->recv_window -= size; if (h2c->recv_window < NGX_HTTP_V2_MAX_WINDOW / 4) { if (ngx_http_v2_send_window_update(h2c, 0, NGX_HTTP_V2_MAX_WINDOW - h2c->recv_window) == NGX_ERROR) { return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } h2c->recv_window = NGX_HTTP_V2_MAX_WINDOW; } node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 0); if (node == NULL || node->stream == NULL) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "unknown http2 stream"); return ngx_http_v2_state_skip_padded(h2c, pos, end); } stream = node->stream; if (size > stream->recv_window) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client violated flow control for stream %ui: " "received DATA frame length %uz, available window %uz", node->id, size, stream->recv_window); if (ngx_http_v2_terminate_stream(h2c, stream, NGX_HTTP_V2_FLOW_CTRL_ERROR) == NGX_ERROR) { return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } return ngx_http_v2_state_skip_padded(h2c, pos, end); } stream->recv_window -= size; if (stream->no_flow_control && stream->recv_window < NGX_HTTP_V2_MAX_WINDOW / 4) { if (ngx_http_v2_send_window_update(h2c, node->id, NGX_HTTP_V2_MAX_WINDOW - stream->recv_window) == NGX_ERROR) { return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } stream->recv_window = NGX_HTTP_V2_MAX_WINDOW; } if (stream->in_closed) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent DATA frame for half-closed stream %ui", node->id); if (ngx_http_v2_terminate_stream(h2c, stream, NGX_HTTP_V2_STREAM_CLOSED) == NGX_ERROR) { return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } return ngx_http_v2_state_skip_padded(h2c, pos, end); } h2c->state.stream = stream; return ngx_http_v2_state_read_data(h2c, pos, end); } static u_char * ngx_http_v2_state_read_data(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) { size_t size; ngx_buf_t *buf; ngx_int_t rc; ngx_connection_t *fc; 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); } r = stream->request; fc = r->connection; if (r->reading_body && !r->request_body_no_buffering) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "skipping http2 DATA frame"); return ngx_http_v2_state_skip_padded(h2c, pos, end); } if (r->headers_in.content_length_n < 0 && !r->headers_in.chunked) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "skipping http2 DATA frame"); return ngx_http_v2_state_skip_padded(h2c, pos, end); } size = end - pos; if (size >= h2c->state.length) { size = h2c->state.length; stream->in_closed = h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG; } h2c->payload_bytes += size; if (r->request_body) { rc = ngx_http_v2_process_request_body(r, pos, size, stream->in_closed, 0); if (rc != NGX_OK && rc != NGX_AGAIN) { stream->skip_data = 1; ngx_http_finalize_request(r, rc); } ngx_http_run_posted_requests(fc); } else if (size) { buf = stream->preread; 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; ngx_http_core_srv_conf_t *cscf; ngx_http_core_loc_conf_t *clcf; padded = h2c->state.flags & NGX_HTTP_V2_PADDED_FLAG; priority = h2c->state.flags & NGX_HTTP_V2_PRIORITY_FLAG; size = 0; if (padded) { size++; } if (priority) { size += sizeof(uint32_t) + 1; } if (h2c->state.length < size) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent HEADERS frame with incorrect length %uz", h2c->state.length); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); } if (h2c->state.length == size) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent HEADERS frame with empty header block"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); } if (h2c->goaway) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "skipping http2 HEADERS frame"); return ngx_http_v2_state_skip(h2c, pos, end); } if ((size_t) (end - pos) < size) { return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_headers); } h2c->state.length -= size; if (padded) { h2c->state.padding = *pos++; if (h2c->state.padding > h2c->state.length) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent padded HEADERS frame " "with incorrect length: %uz, padding: %uz", h2c->state.length, h2c->state.padding); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); } h2c->state.length -= h2c->state.padding; } depend = 0; excl = 0; weight = NGX_HTTP_V2_DEFAULT_WEIGHT; if (priority) { dependency = ngx_http_v2_parse_uint32(pos); depend = dependency & 0x7fffffff; excl = dependency >> 31; weight = pos[4] + 1; pos += sizeof(uint32_t) + 1; } ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 HEADERS frame sid:%ui " "depends on %ui excl:%ui weight:%ui", h2c->state.sid, depend, excl, weight); if (h2c->state.sid % 2 == 0 || h2c->state.sid <= h2c->last_sid) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent HEADERS frame with incorrect identifier " "%ui, the last was %ui", h2c->state.sid, h2c->last_sid); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); } 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); 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); } cscf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, ngx_http_core_module); h2c->state.header_limit = cscf->large_client_header_buffers.size * cscf->large_client_header_buffers.num; h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, ngx_http_v2_module); if (h2c->processing >= h2scf->concurrent_streams) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "concurrent streams exceeded %ui", h2c->processing); status = NGX_HTTP_V2_REFUSED_STREAM; goto rst_stream; } if (!h2c->settings_ack && !(h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG) && h2scf->preread_size < NGX_HTTP_V2_DEFAULT_WINDOW) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent stream with data " "before settings were acknowledged"); status = NGX_HTTP_V2_REFUSED_STREAM; goto rst_stream; } node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 1); if (node == NULL) { return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } if (node->parent) { ngx_queue_remove(&node->reuse); h2c->closed_nodes--; } stream = ngx_http_v2_create_stream(h2c, 0); if (stream == NULL) { return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } h2c->state.stream = stream; stream->pool = h2c->state.pool; h2c->state.keep_pool = 1; stream->request->request_length = h2c->state.length; stream->in_closed = h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG; stream->node = node; node->stream = stream; if (priority || node->parent == NULL) { node->weight = weight; ngx_http_v2_set_dependency(h2c, node, depend, excl); } clcf = ngx_http_get_module_loc_conf(h2c->http_connection->conf_ctx, ngx_http_core_module); if (clcf->keepalive_timeout == 0 || h2c->connection->requests >= clcf->keepalive_requests || ngx_current_msec - h2c->connection->start_time > clcf->keepalive_time) { h2c->goaway = 1; if (ngx_http_v2_send_goaway(h2c, NGX_HTTP_V2_NO_ERROR) == NGX_ERROR) { return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } } return ngx_http_v2_state_header_block(h2c, pos, end); rst_stream: if (ngx_http_v2_send_rst_stream(h2c, h2c->state.sid, status) != NGX_OK) { return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } return ngx_http_v2_state_header_block(h2c, pos, end); } static u_char * ngx_http_v2_state_header_block(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) { u_char ch; ngx_int_t value; ngx_uint_t indexed, size_update, prefix; if (end - pos < 1) { return ngx_http_v2_state_headers_save(h2c, pos, end, ngx_http_v2_state_header_block); } if (!(h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG) && h2c->state.length < NGX_HTTP_V2_INT_OCTETS) { return ngx_http_v2_handle_continuation(h2c, pos, end, ngx_http_v2_state_header_block); } size_update = 0; indexed = 0; ch = *pos; if (ch >= (1 << 7)) { /* indexed header field */ indexed = 1; prefix = ngx_http_v2_prefix(7); } else if (ch >= (1 << 6)) { /* literal header field with incremental indexing */ h2c->state.index = 1; prefix = ngx_http_v2_prefix(6); } else if (ch >= (1 << 5)) { /* dynamic table size update */ size_update = 1; prefix = ngx_http_v2_prefix(5); } else if (ch >= (1 << 4)) { /* literal header field never indexed */ prefix = ngx_http_v2_prefix(4); } else { /* literal header field without indexing */ prefix = ngx_http_v2_prefix(4); } value = ngx_http_v2_parse_int(h2c, &pos, end, prefix); if (value < 0) { if (value == NGX_AGAIN) { return ngx_http_v2_state_headers_save(h2c, pos, end, ngx_http_v2_state_header_block); } if (value == NGX_DECLINED) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent header block with too long %s value", size_update ? "size update" : "header index"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR); } ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent header block with incorrect length"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); } if (indexed) { if (ngx_http_v2_get_indexed_header(h2c, value, 0) != NGX_OK) { return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR); } return ngx_http_v2_state_process_header(h2c, pos, end); } if (size_update) { if (ngx_http_v2_table_size(h2c, value) != NGX_OK) { return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR); } return ngx_http_v2_state_header_complete(h2c, pos, end); } if (value == 0) { h2c->state.parse_name = 1; } else if (ngx_http_v2_get_indexed_header(h2c, value, 1) != NGX_OK) { return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR); } h2c->state.parse_value = 1; return ngx_http_v2_state_field_len(h2c, pos, end); } static u_char * ngx_http_v2_state_field_len(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) { size_t alloc; ngx_int_t len; ngx_uint_t huff; ngx_http_core_srv_conf_t *cscf; if (!(h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG) && h2c->state.length < NGX_HTTP_V2_INT_OCTETS) { return ngx_http_v2_handle_continuation(h2c, pos, end, ngx_http_v2_state_field_len); } if (h2c->state.length < 1) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent header block with incorrect length"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); } if (end - pos < 1) { return ngx_http_v2_state_headers_save(h2c, pos, end, ngx_http_v2_state_field_len); } huff = *pos >> 7; len = ngx_http_v2_parse_int(h2c, &pos, end, ngx_http_v2_prefix(7)); if (len < 0) { if (len == NGX_AGAIN) { return ngx_http_v2_state_headers_save(h2c, pos, end, ngx_http_v2_state_field_len); } if (len == NGX_DECLINED) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent header field with too long length value"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR); } ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent header block with incorrect length"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 %s string, len:%i", huff ? "encoded" : "raw", len); cscf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, ngx_http_core_module); if ((size_t) len > cscf->large_client_header_buffers.size) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent too large header field"); 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_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_connection_t *fc; 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; if (header->name.len == 0) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent zero header name length"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); } 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 sent too large header"); 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; fc = r->connection; /* TODO Optimization: validate headers while parsing. */ if (ngx_http_v2_validate_header(r, header) != NGX_OK) { ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); goto error; } if (header->name.data[0] == ':') { rc = ngx_http_v2_pseudo_header(r, header); if (rc == NGX_OK) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http2 header: \":%V: %V\"", &header->name, &header->value); return ngx_http_v2_state_header_complete(h2c, pos, end); } if (rc == NGX_ABORT) { goto error; } if (rc == NGX_DECLINED) { ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); goto error; } return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } if (r->invalid_header) { cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); if (cscf->ignore_invalid_headers) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent invalid header: \"%V\"", &header->name); return ngx_http_v2_state_header_complete(h2c, pos, end); } } if (header->name.len == cookie.len && ngx_memcmp(header->name.data, cookie.data, cookie.len) == 0) { if (ngx_http_v2_cookie(r, header) != NGX_OK) { return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } } else { h = ngx_list_push(&r->headers_in.headers); if (h == NULL) { return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } h->key.len = header->name.len; h->key.data = header->name.data; /* * TODO Optimization: precalculate hash * and handler for indexed headers. */ h->hash = ngx_hash_key(h->key.data, h->key.len); h->value.len = header->value.len; h->value.data = header->value.data; h->lowcase_key = h->key.data; cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len); if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { goto error; } } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http2 header: \"%V: %V\"", &header->name, &header->value); return ngx_http_v2_state_header_complete(h2c, pos, end); error: h2c->state.stream = NULL; ngx_http_run_posted_requests(fc); 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) { if (end - pos > 0) { h2c->state.handler = ngx_http_v2_state_header_block; return pos; } 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)) { 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 (--h2c->priority_limit == 0) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent too many PRIORITY frames"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_ENHANCE_YOUR_CALM); } if (end - pos < NGX_HTTP_V2_PRIORITY_SIZE) { return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_priority); } dependency = ngx_http_v2_parse_uint32(pos); depend = dependency & 0x7fffffff; excl = dependency >> 31; weight = pos[4] + 1; pos += NGX_HTTP_V2_PRIORITY_SIZE; ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 PRIORITY frame sid:%ui " "depends on %ui excl:%ui weight:%ui", h2c->state.sid, depend, excl, weight); if (h2c->state.sid == 0) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent PRIORITY frame with incorrect identifier"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); } if (depend == h2c->state.sid) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent PRIORITY frame for stream %ui " "with incorrect dependency", h2c->state.sid); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); } node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 1); if (node == NULL) { return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } node->weight = weight; if (node->stream == NULL) { if (node->parent == NULL) { h2c->closed_nodes++; } else { ngx_queue_remove(&node->reuse); } ngx_queue_insert_tail(&h2c->closed, &node->reuse); } ngx_http_v2_set_dependency(h2c, node, depend, excl); return ngx_http_v2_state_complete(h2c, pos, end); } static u_char * ngx_http_v2_state_rst_stream(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) { ngx_uint_t status; ngx_event_t *ev; ngx_connection_t *fc; ngx_http_v2_node_t *node; ngx_http_v2_stream_t *stream; if (h2c->state.length != NGX_HTTP_V2_RST_STREAM_SIZE) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent RST_STREAM frame with incorrect length %uz", h2c->state.length); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); } if (end - pos < NGX_HTTP_V2_RST_STREAM_SIZE) { return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_rst_stream); } status = ngx_http_v2_parse_uint32(pos); pos += NGX_HTTP_V2_RST_STREAM_SIZE; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 RST_STREAM frame, sid:%ui status:%ui", h2c->state.sid, status); if (h2c->state.sid == 0) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent RST_STREAM frame with incorrect identifier"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); } node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 0); if (node == NULL || node->stream == NULL) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "unknown http2 stream"); return ngx_http_v2_state_complete(h2c, pos, end); } stream = node->stream; stream->in_closed = 1; stream->out_closed = 1; fc = stream->request->connection; fc->error = 1; switch (status) { case NGX_HTTP_V2_CANCEL: ngx_log_error(NGX_LOG_INFO, fc->log, 0, "client canceled stream %ui", h2c->state.sid); break; case NGX_HTTP_V2_REFUSED_STREAM: ngx_log_error(NGX_LOG_INFO, fc->log, 0, "client refused stream %ui", h2c->state.sid); break; case NGX_HTTP_V2_INTERNAL_ERROR: ngx_log_error(NGX_LOG_INFO, fc->log, 0, "client terminated stream %ui due to internal error", h2c->state.sid); break; default: ngx_log_error(NGX_LOG_INFO, fc->log, 0, "client terminated stream %ui with status %ui", h2c->state.sid, status); break; } ev = fc->read; ev->handler(ev); return ngx_http_v2_state_complete(h2c, pos, end); } static u_char * ngx_http_v2_state_settings(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 SETTINGS frame"); if (h2c->state.sid) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent SETTINGS frame with incorrect identifier"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); } if (h2c->state.flags == NGX_HTTP_V2_ACK_FLAG) { if (h2c->state.length != 0) { 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); } return ngx_http_v2_state_settings_params(h2c, pos, end); } static u_char * ngx_http_v2_state_settings_params(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) { ssize_t window_delta; ngx_uint_t id, value; ngx_http_v2_srv_conf_t *h2scf; ngx_http_v2_out_frame_t *frame; window_delta = 0; while (h2c->state.length) { if (end - pos < NGX_HTTP_V2_SETTINGS_PARAM_SIZE) { return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_settings_params); } h2c->state.length -= NGX_HTTP_V2_SETTINGS_PARAM_SIZE; id = ngx_http_v2_parse_uint16(pos); value = ngx_http_v2_parse_uint32(&pos[2]); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 setting %ui:%ui", id, value); switch (id) { case NGX_HTTP_V2_INIT_WINDOW_SIZE_SETTING: if (value > NGX_HTTP_V2_MAX_WINDOW) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent SETTINGS frame with incorrect " "INITIAL_WINDOW_SIZE value %ui", value); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_FLOW_CTRL_ERROR); } window_delta = value - h2c->init_window; break; case NGX_HTTP_V2_MAX_FRAME_SIZE_SETTING: if (value > NGX_HTTP_V2_MAX_FRAME_SIZE || value < NGX_HTTP_V2_DEFAULT_FRAME_SIZE) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent SETTINGS frame with incorrect " "MAX_FRAME_SIZE value %ui", value); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); } h2c->frame_size = value; break; case NGX_HTTP_V2_ENABLE_PUSH_SETTING: if (value > 1) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent SETTINGS frame with incorrect " "ENABLE_PUSH value %ui", value); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); } h2c->push_disabled = !value; break; case NGX_HTTP_V2_MAX_STREAMS_SETTING: h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, ngx_http_v2_module); h2c->concurrent_pushes = ngx_min(value, h2scf->concurrent_pushes); break; case NGX_HTTP_V2_HEADER_TABLE_SIZE_SETTING: h2c->table_update = 1; break; default: break; } pos += NGX_HTTP_V2_SETTINGS_PARAM_SIZE; } frame = ngx_http_v2_get_frame(h2c, NGX_HTTP_V2_SETTINGS_ACK_SIZE, NGX_HTTP_V2_SETTINGS_FRAME, NGX_HTTP_V2_ACK_FLAG, 0); if (frame == NULL) { return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } ngx_http_v2_queue_ordered_frame(h2c, frame); if (window_delta) { h2c->init_window += window_delta; if (ngx_http_v2_adjust_windows(h2c, window_delta) != NGX_OK) { return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } } return ngx_http_v2_state_complete(h2c, pos, end); } static u_char * ngx_http_v2_state_push_promise(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent PUSH_PROMISE frame"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); } static u_char * ngx_http_v2_state_ping(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) { ngx_buf_t *buf; ngx_http_v2_out_frame_t *frame; if (h2c->state.length != NGX_HTTP_V2_PING_SIZE) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent PING frame with incorrect length %uz", h2c->state.length); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); } if (end - pos < NGX_HTTP_V2_PING_SIZE) { return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_ping); } ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 PING frame"); if (h2c->state.sid) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent PING frame with incorrect identifier"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); } if (h2c->state.flags & NGX_HTTP_V2_ACK_FLAG) { return ngx_http_v2_state_skip(h2c, pos, end); } 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 (h2c->state.sid) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent GOAWAY frame with incorrect identifier"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); } #if (NGX_DEBUG) h2c->state.length -= NGX_HTTP_V2_GOAWAY_SIZE; last_sid = ngx_http_v2_parse_sid(pos); error = ngx_http_v2_parse_uint32(&pos[4]); pos += NGX_HTTP_V2_GOAWAY_SIZE; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 GOAWAY frame: last sid %ui, error %ui", last_sid, error); #endif return ngx_http_v2_state_skip(h2c, pos, end); } static u_char * ngx_http_v2_state_window_update(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) { size_t window; ngx_event_t *wev; ngx_queue_t *q; ngx_http_v2_node_t *node; ngx_http_v2_stream_t *stream; if (h2c->state.length != NGX_HTTP_V2_WINDOW_UPDATE_SIZE) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent WINDOW_UPDATE frame " "with incorrect length %uz", h2c->state.length); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); } if (end - pos < NGX_HTTP_V2_WINDOW_UPDATE_SIZE) { return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_window_update); } window = ngx_http_v2_parse_window(pos); pos += NGX_HTTP_V2_WINDOW_UPDATE_SIZE; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 WINDOW_UPDATE frame sid:%ui window:%uz", h2c->state.sid, window); if (window == 0) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent WINDOW_UPDATE frame " "with incorrect window increment 0"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); } 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"); ngx_http_v2_finalize_connection(h2c, err); return NULL; } static ngx_int_t ngx_http_v2_parse_int(ngx_http_v2_connection_t *h2c, u_char **pos, u_char *end, ngx_uint_t prefix) { u_char *start, *p; ngx_uint_t value, octet, shift; start = *pos; p = start; value = *p++ & prefix; if (value != prefix) { if (h2c->state.length == 0) { return NGX_ERROR; } h2c->state.length--; *pos = p; return value; } if (end - start > NGX_HTTP_V2_INT_OCTETS) { end = start + NGX_HTTP_V2_INT_OCTETS; } for (shift = 0; p != end; shift += 7) { octet = *p++; value += (octet & 0x7f) << shift; if (octet < 128) { if ((size_t) (p - start) > h2c->state.length) { return NGX_ERROR; } h2c->state.length -= p - start; *pos = p; return value; } } if ((size_t) (end - start) >= h2c->state.length) { return NGX_ERROR; } if (end == start + NGX_HTTP_V2_INT_OCTETS) { return NGX_DECLINED; } return NGX_AGAIN; } ngx_http_v2_stream_t * ngx_http_v2_push_stream(ngx_http_v2_stream_t *parent, ngx_str_t *path) { ngx_int_t rc; ngx_str_t value; ngx_pool_t *pool; ngx_uint_t index; ngx_table_elt_t **h; ngx_connection_t *fc; ngx_http_request_t *r; ngx_http_v2_node_t *node; ngx_http_v2_stream_t *stream; ngx_http_v2_srv_conf_t *h2scf; ngx_http_v2_connection_t *h2c; ngx_http_v2_parse_header_t *header; h2c = parent->connection; pool = ngx_create_pool(1024, h2c->connection->log); if (pool == NULL) { goto rst_stream; } node = ngx_http_v2_get_node_by_id(h2c, h2c->last_push, 1); if (node == NULL) { ngx_destroy_pool(pool); goto rst_stream; } stream = ngx_http_v2_create_stream(h2c, 1); if (stream == NULL) { if (node->parent == NULL) { h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, ngx_http_v2_module); index = ngx_http_v2_index(h2scf, h2c->last_push); h2c->streams_index[index] = node->index; ngx_queue_insert_tail(&h2c->closed, &node->reuse); h2c->closed_nodes++; } ngx_destroy_pool(pool); goto rst_stream; } if (node->parent) { ngx_queue_remove(&node->reuse); h2c->closed_nodes--; } stream->pool = pool; r = stream->request; fc = r->connection; stream->in_closed = 1; stream->node = node; node->stream = stream; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 push stream sid:%ui " "depends on %ui excl:0 weight:16", h2c->last_push, parent->node->id); node->weight = NGX_HTTP_V2_DEFAULT_WEIGHT; ngx_http_v2_set_dependency(h2c, node, parent->node->id, 0); r->method_name = ngx_http_core_get_method; r->method = NGX_HTTP_GET; r->schema.data = ngx_pstrdup(pool, &parent->request->schema); if (r->schema.data == NULL) { goto close; } r->schema.len = parent->request->schema.len; value.data = ngx_pstrdup(pool, path); if (value.data == NULL) { goto close; } value.len = path->len; rc = ngx_http_v2_parse_path(r, &value); if (rc != NGX_OK) { goto error; } for (header = ngx_http_v2_parse_headers; header->name.len; header++) { h = (ngx_table_elt_t **) ((char *) &parent->request->headers_in + header->offset); if (*h == NULL) { continue; } value.len = (*h)->value.len; value.data = ngx_pnalloc(pool, value.len + 1); if (value.data == NULL) { goto close; } ngx_memcpy(value.data, (*h)->value.data, value.len); value.data[value.len] = '\0'; rc = ngx_http_v2_parse_header(r, header, &value); if (rc != NGX_OK) { goto error; } } fc->write->handler = ngx_http_v2_run_request_handler; ngx_post_event(fc->write, &ngx_posted_events); return stream; error: if (rc == NGX_ABORT) { /* header handler has already finalized request */ ngx_http_run_posted_requests(fc); return NULL; } if (rc == NGX_DECLINED) { ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); ngx_http_run_posted_requests(fc); return NULL; } close: ngx_http_v2_close_stream(stream, NGX_HTTP_INTERNAL_SERVER_ERROR); return NULL; rst_stream: if (ngx_http_v2_send_rst_stream(h2c, h2c->last_push, NGX_HTTP_INTERNAL_SERVER_ERROR) != NGX_OK) { h2c->connection->error = 1; } return NULL; } static ngx_int_t ngx_http_v2_send_settings(ngx_http_v2_connection_t *h2c) { size_t len; ngx_buf_t *buf; ngx_chain_t *cl; ngx_http_v2_srv_conf_t *h2scf; ngx_http_v2_out_frame_t *frame; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 send SETTINGS frame"); frame = ngx_palloc(h2c->pool, sizeof(ngx_http_v2_out_frame_t)); if (frame == NULL) { return NGX_ERROR; } cl = ngx_alloc_chain_link(h2c->pool); if (cl == NULL) { return NGX_ERROR; } len = NGX_HTTP_V2_SETTINGS_PARAM_SIZE * 3; buf = ngx_create_temp_buf(h2c->pool, NGX_HTTP_V2_FRAME_HEADER_SIZE + len); if (buf == NULL) { return NGX_ERROR; } buf->last_buf = 1; cl->buf = buf; cl->next = NULL; frame->first = cl; frame->last = cl; frame->handler = ngx_http_v2_settings_frame_handler; frame->stream = NULL; #if (NGX_DEBUG) frame->length = len; #endif frame->blocked = 0; buf->last = ngx_http_v2_write_len_and_type(buf->last, len, NGX_HTTP_V2_SETTINGS_FRAME); *buf->last++ = NGX_HTTP_V2_NO_FLAG; buf->last = ngx_http_v2_write_sid(buf->last, 0); h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, ngx_http_v2_module); buf->last = ngx_http_v2_write_uint16(buf->last, NGX_HTTP_V2_MAX_STREAMS_SETTING); buf->last = ngx_http_v2_write_uint32(buf->last, h2scf->concurrent_streams); buf->last = ngx_http_v2_write_uint16(buf->last, NGX_HTTP_V2_INIT_WINDOW_SIZE_SETTING); buf->last = ngx_http_v2_write_uint32(buf->last, h2scf->preread_size); buf->last = ngx_http_v2_write_uint16(buf->last, NGX_HTTP_V2_MAX_FRAME_SIZE_SETTING); buf->last = ngx_http_v2_write_uint32(buf->last, NGX_HTTP_V2_MAX_FRAME_SIZE); ngx_http_v2_queue_blocked_frame(h2c, frame); return NGX_OK; } static ngx_int_t ngx_http_v2_settings_frame_handler(ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame) { ngx_buf_t *buf; buf = frame->first->buf; if (buf->pos != buf->last) { return NGX_AGAIN; } ngx_free_chain(h2c->pool, frame->first); return NGX_OK; } static ngx_int_t ngx_http_v2_send_window_update(ngx_http_v2_connection_t *h2c, ngx_uint_t sid, size_t window) { ngx_buf_t *buf; ngx_http_v2_out_frame_t *frame; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 send WINDOW_UPDATE frame sid:%ui, window:%uz", sid, window); frame = ngx_http_v2_get_frame(h2c, NGX_HTTP_V2_WINDOW_UPDATE_SIZE, NGX_HTTP_V2_WINDOW_UPDATE_FRAME, NGX_HTTP_V2_NO_FLAG, sid); if (frame == NULL) { return NGX_ERROR; } buf = frame->first->buf; buf->last = ngx_http_v2_write_uint32(buf->last, window); ngx_http_v2_queue_blocked_frame(h2c, frame); return NGX_OK; } static ngx_int_t ngx_http_v2_send_rst_stream(ngx_http_v2_connection_t *h2c, ngx_uint_t sid, ngx_uint_t status) { ngx_buf_t *buf; ngx_http_v2_out_frame_t *frame; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 send RST_STREAM frame sid:%ui, status:%ui", sid, status); frame = ngx_http_v2_get_frame(h2c, NGX_HTTP_V2_RST_STREAM_SIZE, NGX_HTTP_V2_RST_STREAM_FRAME, NGX_HTTP_V2_NO_FLAG, sid); if (frame == NULL) { return NGX_ERROR; } buf = frame->first->buf; buf->last = ngx_http_v2_write_uint32(buf->last, status); ngx_http_v2_queue_blocked_frame(h2c, frame); return NGX_OK; } static ngx_int_t ngx_http_v2_send_goaway(ngx_http_v2_connection_t *h2c, ngx_uint_t status) { ngx_buf_t *buf; ngx_http_v2_out_frame_t *frame; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 send GOAWAY frame: last sid %ui, error %ui", h2c->last_sid, status); frame = ngx_http_v2_get_frame(h2c, NGX_HTTP_V2_GOAWAY_SIZE, NGX_HTTP_V2_GOAWAY_FRAME, NGX_HTTP_V2_NO_FLAG, 0); if (frame == NULL) { return NGX_ERROR; } buf = frame->first->buf; buf->last = ngx_http_v2_write_sid(buf->last, h2c->last_sid); buf->last = ngx_http_v2_write_uint32(buf->last, status); ngx_http_v2_queue_blocked_frame(h2c, frame); return NGX_OK; } static ngx_http_v2_out_frame_t * ngx_http_v2_get_frame(ngx_http_v2_connection_t *h2c, size_t length, ngx_uint_t type, u_char flags, ngx_uint_t sid) { ngx_buf_t *buf; ngx_pool_t *pool; ngx_http_v2_out_frame_t *frame; frame = h2c->free_frames; if (frame) { h2c->free_frames = frame->next; buf = frame->first->buf; buf->pos = buf->start; frame->blocked = 0; } else if (h2c->frames < 10000) { pool = h2c->pool ? h2c->pool : h2c->connection->pool; frame = ngx_pcalloc(pool, sizeof(ngx_http_v2_out_frame_t)); if (frame == NULL) { return NULL; } frame->first = ngx_alloc_chain_link(pool); if (frame->first == NULL) { return NULL; } buf = ngx_create_temp_buf(pool, NGX_HTTP_V2_FRAME_BUFFER_SIZE); if (buf == NULL) { return NULL; } buf->last_buf = 1; frame->first->buf = buf; frame->last = frame->first; frame->handler = ngx_http_v2_frame_handler; h2c->frames++; } else { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "http2 flood detected"); h2c->connection->error = 1; return NULL; } #if (NGX_DEBUG) if (length > NGX_HTTP_V2_FRAME_BUFFER_SIZE - NGX_HTTP_V2_FRAME_HEADER_SIZE) { ngx_log_error(NGX_LOG_ALERT, h2c->connection->log, 0, "requested control frame is too large: %uz", length); return NULL; } #endif frame->length = length; 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; h2c->total_bytes += NGX_HTTP_V2_FRAME_HEADER_SIZE + frame->length; return NGX_OK; } static ngx_http_v2_stream_t * ngx_http_v2_create_stream(ngx_http_v2_connection_t *h2c, ngx_uint_t push) { ngx_log_t *log; ngx_event_t *rev, *wev; ngx_connection_t *fc; ngx_http_log_ctx_t *ctx; ngx_http_request_t *r; ngx_http_v2_stream_t *stream; ngx_http_v2_srv_conf_t *h2scf; ngx_http_core_srv_conf_t *cscf; fc = h2c->free_fake_connections; if (fc) { h2c->free_fake_connections = fc->data; rev = fc->read; wev = fc->write; log = fc->log; ctx = log->data; } else { fc = ngx_palloc(h2c->pool, sizeof(ngx_connection_t)); if (fc == NULL) { return NULL; } rev = ngx_palloc(h2c->pool, sizeof(ngx_event_t)); if (rev == NULL) { return NULL; } wev = ngx_palloc(h2c->pool, sizeof(ngx_event_t)); if (wev == NULL) { return NULL; } log = ngx_palloc(h2c->pool, sizeof(ngx_log_t)); if (log == NULL) { return NULL; } ctx = ngx_palloc(h2c->pool, sizeof(ngx_http_log_ctx_t)); if (ctx == NULL) { return NULL; } ctx->connection = fc; ctx->request = NULL; ctx->current_request = NULL; } ngx_memcpy(log, h2c->connection->log, sizeof(ngx_log_t)); log->data = ctx; if (push) { log->action = "processing pushed request headers"; } else { log->action = "reading client request headers"; } ngx_memzero(rev, sizeof(ngx_event_t)); rev->data = fc; rev->ready = 1; rev->handler = ngx_http_v2_close_stream_handler; rev->log = log; ngx_memcpy(wev, rev, sizeof(ngx_event_t)); wev->write = 1; ngx_memcpy(fc, h2c->connection, sizeof(ngx_connection_t)); fc->data = h2c->http_connection; fc->read = rev; fc->write = wev; fc->sent = 0; fc->log = log; fc->buffered = 0; fc->sndlowat = 1; fc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED; r = ngx_http_create_request(fc); if (r == NULL) { return NULL; } ngx_str_set(&r->http_protocol, "HTTP/2.0"); r->http_version = NGX_HTTP_VERSION_20; r->valid_location = 1; fc->data = r; h2c->connection->requests++; cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); r->header_in = ngx_create_temp_buf(r->pool, cscf->client_header_buffer_size); if (r->header_in == NULL) { ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return NULL; } if (ngx_list_init(&r->headers_in.headers, r->pool, 20, sizeof(ngx_table_elt_t)) != NGX_OK) { ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return NULL; } r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; stream = ngx_pcalloc(r->pool, sizeof(ngx_http_v2_stream_t)); if (stream == NULL) { ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return NULL; } r->stream = stream; stream->request = r; stream->connection = h2c; h2scf = ngx_http_get_module_srv_conf(r, ngx_http_v2_module); stream->send_window = h2c->init_window; stream->recv_window = h2scf->preread_size; if (push) { h2c->pushing++; } else { h2c->processing++; } h2c->priority_limit += h2scf->concurrent_streams; if (h2c->connection->read->timer_set) { ngx_del_timer(h2c->connection->read); } 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; r->invalid_header = 0; cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); for (i = (header->name.data[0] == ':'); i != header->name.len; i++) { ch = header->name.data[i]; if ((ch >= 'a' && ch <= 'z') || (ch == '-') || (ch >= '0' && ch <= '9') || (ch == '_' && cscf->underscores_in_headers)) { continue; } if (ch <= 0x20 || ch == 0x7f || ch == ':' || (ch >= 'A' && ch <= 'Z')) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent invalid header name: \"%V\"", &header->name); return NGX_ERROR; } r->invalid_header = 1; } for (i = 0; i != header->value.len; i++) { ch = header->value.data[i]; if (ch == '\0' || ch == LF || ch == CR) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent header \"%V\" with " "invalid value: \"%V\"", &header->name, &header->value); return NGX_ERROR; } } return NGX_OK; } static ngx_int_t ngx_http_v2_pseudo_header(ngx_http_request_t *r, ngx_http_v2_header_t *header) { header->name.len--; header->name.data++; switch (header->name.len) { case 4: if (ngx_memcmp(header->name.data, "path", sizeof("path") - 1) == 0) { return ngx_http_v2_parse_path(r, &header->value); } break; case 6: if (ngx_memcmp(header->name.data, "method", sizeof("method") - 1) == 0) { return ngx_http_v2_parse_method(r, &header->value); } if (ngx_memcmp(header->name.data, "scheme", sizeof("scheme") - 1) == 0) { return ngx_http_v2_parse_scheme(r, &header->value); } break; case 9: if (ngx_memcmp(header->name.data, "authority", sizeof("authority") - 1) == 0) { return ngx_http_v2_parse_authority(r, &header->value); } break; } ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent unknown pseudo-header \":%V\"", &header->name); return NGX_DECLINED; } static ngx_int_t ngx_http_v2_parse_path(ngx_http_request_t *r, ngx_str_t *value) { if (r->unparsed_uri.len) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent duplicate :path header"); return NGX_DECLINED; } if (value->len == 0) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent empty :path header"); return NGX_DECLINED; } r->uri_start = value->data; r->uri_end = value->data + value->len; if (ngx_http_parse_uri(r) != NGX_OK) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent invalid :path header: \"%V\"", value); return NGX_DECLINED; } if (ngx_http_process_request_uri(r) != NGX_OK) { /* * request has been finalized already * in ngx_http_process_request_uri() */ return NGX_ABORT; } return NGX_OK; } static ngx_int_t ngx_http_v2_parse_method(ngx_http_request_t *r, ngx_str_t *value) { size_t k, len; ngx_uint_t n; const u_char *p, *m; /* * This array takes less than 256 sequential bytes, * and if typical CPU cache line size is 64 bytes, * it is prefetched for 4 load operations. */ static const struct { u_char len; const u_char method[11]; uint32_t value; } tests[] = { { 3, "GET", NGX_HTTP_GET }, { 4, "POST", NGX_HTTP_POST }, { 4, "HEAD", NGX_HTTP_HEAD }, { 7, "OPTIONS", NGX_HTTP_OPTIONS }, { 8, "PROPFIND", NGX_HTTP_PROPFIND }, { 3, "PUT", NGX_HTTP_PUT }, { 5, "MKCOL", NGX_HTTP_MKCOL }, { 6, "DELETE", NGX_HTTP_DELETE }, { 4, "COPY", NGX_HTTP_COPY }, { 4, "MOVE", NGX_HTTP_MOVE }, { 9, "PROPPATCH", NGX_HTTP_PROPPATCH }, { 4, "LOCK", NGX_HTTP_LOCK }, { 6, "UNLOCK", NGX_HTTP_UNLOCK }, { 5, "PATCH", NGX_HTTP_PATCH }, { 5, "TRACE", NGX_HTTP_TRACE }, { 7, "CONNECT", NGX_HTTP_CONNECT } }, *test; if (r->method_name.len) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent duplicate :method header"); return NGX_DECLINED; } if (value->len == 0) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent empty :method header"); return NGX_DECLINED; } r->method_name.len = value->len; r->method_name.data = value->data; len = r->method_name.len; n = sizeof(tests) / sizeof(tests[0]); test = tests; do { if (len == test->len) { p = r->method_name.data; m = test->method; k = len; do { if (*p++ != *m++) { goto next; } } while (--k); r->method = test->value; return NGX_OK; } next: test++; } while (--n); p = r->method_name.data; do { if ((*p < 'A' || *p > 'Z') && *p != '_' && *p != '-') { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent invalid method: \"%V\"", &r->method_name); return NGX_DECLINED; } p++; } while (--len); return NGX_OK; } static ngx_int_t ngx_http_v2_parse_scheme(ngx_http_request_t *r, ngx_str_t *value) { u_char c, ch; ngx_uint_t i; if (r->schema.len) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent duplicate :scheme header"); return NGX_DECLINED; } if (value->len == 0) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent empty :scheme header"); return NGX_DECLINED; } for (i = 0; i < value->len; i++) { ch = value->data[i]; c = (u_char) (ch | 0x20); if (c >= 'a' && c <= 'z') { continue; } if (((ch >= '0' && ch <= '9') || ch == '+' || ch == '-' || ch == '.') && i > 0) { continue; } ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent invalid :scheme header: \"%V\"", value); return NGX_DECLINED; } r->schema = *value; return NGX_OK; } static ngx_int_t ngx_http_v2_parse_authority(ngx_http_request_t *r, ngx_str_t *value) { return ngx_http_v2_parse_header(r, &ngx_http_v2_parse_headers[0], value); } static ngx_int_t ngx_http_v2_parse_header(ngx_http_request_t *r, ngx_http_v2_parse_header_t *header, ngx_str_t *value) { ngx_table_elt_t *h; ngx_http_core_main_conf_t *cmcf; h = ngx_list_push(&r->headers_in.headers); if (h == NULL) { return NGX_ERROR; } h->key.len = header->name.len; h->key.data = header->name.data; h->lowcase_key = header->name.data; if (header->hh == NULL) { header->hash = ngx_hash_key(header->name.data, header->name.len); cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); header->hh = ngx_hash_find(&cmcf->headers_in_hash, header->hash, h->lowcase_key, h->key.len); if (header->hh == NULL) { return NGX_ERROR; } } h->hash = header->hash; h->value.len = value->len; h->value.data = value->data; if (header->hh->handler(r, h, header->hh->offset) != NGX_OK) { /* header handler has already finalized request */ return NGX_ABORT; } return NGX_OK; } static ngx_int_t ngx_http_v2_construct_request_line(ngx_http_request_t *r) { u_char *p; static const u_char ending[] = " HTTP/2.0"; if (r->method_name.len == 0 || r->schema.len == 0 || r->unparsed_uri.len == 0) { if (r->method_name.len == 0) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent no :method header"); } else if (r->schema.len == 0) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent no :scheme header"); } else { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent no :path header"); } ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return NGX_ERROR; } r->request_line.len = r->method_name.len + 1 + r->unparsed_uri.len + sizeof(ending) - 1; p = ngx_pnalloc(r->pool, r->request_line.len + 1); if (p == NULL) { ngx_http_v2_close_stream(r->stream, NGX_HTTP_INTERNAL_SERVER_ERROR); return NGX_ERROR; } r->request_line.data = p; p = ngx_cpymem(p, r->method_name.data, r->method_name.len); *p++ = ' '; p = ngx_cpymem(p, r->unparsed_uri.data, r->unparsed_uri.len); ngx_memcpy(p, ending, sizeof(ending)); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http2 request line: \"%V\"", &r->request_line); return NGX_OK; } static ngx_int_t ngx_http_v2_cookie(ngx_http_request_t *r, ngx_http_v2_header_t *header) { ngx_str_t *val; ngx_array_t *cookies; cookies = r->stream->cookies; if (cookies == NULL) { cookies = ngx_array_create(r->pool, 2, sizeof(ngx_str_t)); if (cookies == NULL) { return NGX_ERROR; } r->stream->cookies = cookies; } val = ngx_array_push(cookies); if (val == NULL) { return NGX_ERROR; } val->len = header->value.len; val->data = header->value.data; return NGX_OK; } static ngx_int_t ngx_http_v2_construct_cookie_header(ngx_http_request_t *r) { u_char *buf, *p, *end; size_t len; ngx_str_t *vals; ngx_uint_t i; ngx_array_t *cookies; ngx_table_elt_t *h; ngx_http_header_t *hh; ngx_http_core_main_conf_t *cmcf; static ngx_str_t cookie = ngx_string("cookie"); cookies = r->stream->cookies; if (cookies == NULL) { return NGX_OK; } vals = cookies->elts; i = 0; len = 0; do { len += vals[i].len + 2; } while (++i != cookies->nelts); len -= 2; buf = ngx_pnalloc(r->pool, len + 1); if (buf == NULL) { ngx_http_v2_close_stream(r->stream, NGX_HTTP_INTERNAL_SERVER_ERROR); return NGX_ERROR; } p = buf; end = buf + len; for (i = 0; /* void */ ; i++) { p = ngx_cpymem(p, vals[i].data, vals[i].len); if (p == end) { *p = '\0'; break; } *p++ = ';'; *p++ = ' '; } h = ngx_list_push(&r->headers_in.headers); if (h == NULL) { ngx_http_v2_close_stream(r->stream, NGX_HTTP_INTERNAL_SERVER_ERROR); return NGX_ERROR; } h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash( ngx_hash('c', 'o'), 'o'), 'k'), 'i'), 'e'); h->key.len = cookie.len; h->key.data = cookie.data; h->value.len = len; h->value.data = buf; h->lowcase_key = cookie.data; cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len); if (hh == NULL) { ngx_http_v2_close_stream(r->stream, NGX_HTTP_INTERNAL_SERVER_ERROR); return NGX_ERROR; } if (hh->handler(r, h, hh->offset) != NGX_OK) { /* * request has been finalized already * in ngx_http_process_multi_header_lines() */ return NGX_ERROR; } return NGX_OK; } static void ngx_http_v2_run_request(ngx_http_request_t *r) { ngx_connection_t *fc; ngx_http_v2_connection_t *h2c; fc = r->connection; if (ngx_http_v2_construct_request_line(r) != NGX_OK) { goto failed; } if (ngx_http_v2_construct_cookie_header(r) != NGX_OK) { goto failed; } r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE; if (ngx_http_process_request_header(r) != NGX_OK) { goto failed; } 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); goto failed; } if (r->headers_in.content_length_n == -1 && !r->stream->in_closed) { r->headers_in.chunked = 1; } h2c = r->stream->connection; h2c->payload_bytes += r->request_length; ngx_http_process_request(r); failed: ngx_http_run_posted_requests(fc); } static void ngx_http_v2_run_request_handler(ngx_event_t *ev) { ngx_connection_t *fc; ngx_http_request_t *r; fc = ev->data; r = fc->data; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 run request handler"); ngx_http_v2_run_request(r); } ngx_int_t ngx_http_v2_read_request_body(ngx_http_request_t *r) { off_t len; size_t size; ngx_buf_t *buf; ngx_int_t rc; ngx_http_v2_stream_t *stream; ngx_http_v2_srv_conf_t *h2scf; ngx_http_request_body_t *rb; ngx_http_core_loc_conf_t *clcf; ngx_http_v2_connection_t *h2c; stream = r->stream; rb = r->request_body; if (stream->skip_data) { r->request_body_no_buffering = 0; rb->post_handler(r); return NGX_OK; } rb->rest = 1; /* set rb->filter_need_buffering */ rc = ngx_http_top_request_body_filter(r, NULL); if (rc != NGX_OK) { stream->skip_data = 1; return rc; } h2scf = ngx_http_get_module_srv_conf(r, ngx_http_v2_module); clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); len = r->headers_in.content_length_n; if (len < 0 || len > (off_t) clcf->client_body_buffer_size) { len = clcf->client_body_buffer_size; } else { len++; } if (r->request_body_no_buffering || rb->filter_need_buffering) { /* * We need a room to store data up to the stream's initial window size, * 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); if (rb->buf == NULL) { stream->skip_data = 1; return NGX_HTTP_INTERNAL_SERVER_ERROR; } buf = stream->preread; if (stream->in_closed) { if (!rb->filter_need_buffering) { r->request_body_no_buffering = 0; } if (buf) { rc = ngx_http_v2_process_request_body(r, buf->pos, buf->last - buf->pos, 1, 0); ngx_pfree(r->pool, buf->start); } else { rc = ngx_http_v2_process_request_body(r, NULL, 0, 1, 0); } if (rc != NGX_AGAIN) { return rc; } r->read_event_handler = ngx_http_v2_read_client_request_body_handler; r->write_event_handler = ngx_http_request_empty_handler; return NGX_AGAIN; } if (buf) { rc = ngx_http_v2_process_request_body(r, buf->pos, buf->last - buf->pos, 0, 0); ngx_pfree(r->pool, buf->start); if (rc != NGX_OK && rc != NGX_AGAIN) { stream->skip_data = 1; return rc; } } if (r->request_body_no_buffering || rb->filter_need_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_uint_t flush) { size_t n; 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; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 process request body"); if (size == 0 && !last && !flush) { return NGX_AGAIN; } for ( ;; ) { for ( ;; ) { if (rb->buf->last == rb->buf->end && size) { if (r->request_body_no_buffering) { /* should never happen due to flow control */ ngx_log_error(NGX_LOG_ALERT, fc->log, 0, "no space in http2 body buffer"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } /* update chains */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 body update chains"); rc = ngx_http_v2_filter_request_body(r); if (rc != NGX_OK) { return rc; } if (rb->busy != NULL) { ngx_log_error(NGX_LOG_ALERT, fc->log, 0, "busy buffers after request body flush"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } rb->buf->pos = rb->buf->start; rb->buf->last = rb->buf->start; } /* copy body data to the buffer */ n = rb->buf->end - rb->buf->last; if (n > size) { n = size; } if (n > 0) { rb->buf->last = ngx_cpymem(rb->buf->last, pos, n); } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 request body recv %uz", n); pos += n; size -= n; if (size == 0 && last) { rb->rest = 0; } if (size == 0) { break; } } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 request body rest %O", rb->rest); if (flush) { rc = ngx_http_v2_filter_request_body(r); if (rc != NGX_OK) { return rc; } } if (rb->rest == 0 && rb->last_saved) { break; } if (size == 0) { clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); ngx_add_timer(fc->read, clcf->client_body_timeout); if (!flush) { ngx_post_event(fc->read, &ngx_posted_events); } return NGX_AGAIN; } } if (fc->read->timer_set) { ngx_del_timer(fc->read); } if (r->request_body_no_buffering) { if (!flush) { ngx_post_event(fc->read, &ngx_posted_events); } return NGX_OK; } 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; } 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 || rb->last_sent)) { 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; rb->last_sent = 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) { size_t window; ngx_buf_t *buf; ngx_int_t rc; ngx_connection_t *fc; ngx_http_v2_stream_t *stream; ngx_http_v2_connection_t *h2c; fc = r->connection; 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; } rc = ngx_http_v2_process_request_body(r, NULL, 0, r->stream->in_closed, 1); if (rc != NGX_OK && rc != NGX_AGAIN) { r->stream->skip_data = 1; ngx_http_finalize_request(r, rc); return; } if (rc == NGX_OK) { return; } if (r->stream->no_flow_control) { return; } if (r->request_body->rest == 0) { return; } if (r->request_body->busy != NULL) { return; } stream = r->stream; h2c = stream->connection; buf = r->request_body->buf; buf->pos = buf->start; buf->last = buf->start; window = buf->end - buf->start; if (h2c->state.stream == stream) { window -= h2c->state.length; } if (window <= stream->recv_window) { if (window < stream->recv_window) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "http2 negative window update"); stream->skip_data = 1; ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } return; } if (ngx_http_v2_send_window_update(h2c, stream->node->id, window - stream->recv_window) == NGX_ERROR) { stream->skip_data = 1; ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } stream->recv_window = window; if (ngx_http_v2_send_output_queue(h2c) == NGX_ERROR) { stream->skip_data = 1; ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } } 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; stream = r->stream; fc = r->connection; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 read unbuffered request body"); if (fc->read->timedout) { if (stream->recv_window) { stream->skip_data = 1; 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_process_request_body(r, NULL, 0, r->stream->in_closed, 1); if (rc != NGX_OK && rc != NGX_AGAIN) { stream->skip_data = 1; return rc; } if (rc == NGX_OK) { return NGX_OK; } if (r->request_body->rest == 0) { return NGX_AGAIN; } if (r->request_body->busy != NULL) { return NGX_AGAIN; } 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; } stream->recv_window = window; return NGX_AGAIN; } static ngx_int_t ngx_http_v2_terminate_stream(ngx_http_v2_connection_t *h2c, ngx_http_v2_stream_t *stream, ngx_uint_t status) { ngx_event_t *rev; ngx_connection_t *fc; if (stream->rst_sent) { return NGX_OK; } if (ngx_http_v2_send_rst_stream(h2c, stream->node->id, status) == NGX_ERROR) { return NGX_ERROR; } stream->rst_sent = 1; stream->skip_data = 1; fc = stream->request->connection; fc->error = 1; rev = fc->read; rev->handler(rev); return NGX_OK; } void ngx_http_v2_close_stream(ngx_http_v2_stream_t *stream, ngx_int_t rc) { ngx_pool_t *pool; ngx_uint_t push; ngx_event_t *ev; ngx_connection_t *fc; ngx_http_v2_node_t *node; ngx_http_v2_connection_t *h2c; h2c = stream->connection; node = stream->node; ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 close stream %ui, queued %ui, " "processing %ui, pushing %ui", node->id, stream->queued, h2c->processing, h2c->pushing); fc = stream->request->connection; if (stream->queued) { fc->error = 1; fc->write->handler = ngx_http_v2_retry_close_stream_handler; fc->read->handler = ngx_http_v2_retry_close_stream_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 (ngx_http_v2_send_rst_stream(h2c, node->id, NGX_HTTP_V2_NO_ERROR) != NGX_OK) { h2c->connection->error = 1; } } } if (h2c->state.stream == stream) { h2c->state.stream = NULL; } push = stream->node->id % 2 == 0; node->stream = NULL; ngx_queue_insert_tail(&h2c->closed, &node->reuse); h2c->closed_nodes++; /* * This pool keeps decoded request headers which can be used by log phase * handlers in ngx_http_free_request(). * * The pointer is stored into local variable because the stream object * will be destroyed after a call to ngx_http_free_request(). */ pool = stream->pool; h2c->frames -= stream->frames; ngx_http_free_request(stream->request, rc); if (pool != h2c->state.pool) { ngx_destroy_pool(pool); } else { /* pool will be destroyed when the complete header is parsed */ h2c->state.keep_pool = 0; } ev = fc->read; if (ev->timer_set) { ngx_del_timer(ev); } if (ev->posted) { ngx_delete_posted_event(ev); } ev = fc->write; if (ev->timer_set) { ngx_del_timer(ev); } if (ev->posted) { ngx_delete_posted_event(ev); } fc->data = h2c->free_fake_connections; h2c->free_fake_connections = fc; if (push) { h2c->pushing--; } else { h2c->processing--; } if (h2c->processing || h2c->pushing || h2c->blocked) { return; } ev = h2c->connection->read; ev->handler = ngx_http_v2_handle_connection_handler; ngx_post_event(ev, &ngx_posted_events); } static void ngx_http_v2_close_stream_handler(ngx_event_t *ev) { ngx_connection_t *fc; ngx_http_request_t *r; fc = ev->data; r = fc->data; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 close stream handler"); if (ev->timedout) { ngx_log_error(NGX_LOG_INFO, fc->log, NGX_ETIMEDOUT, "client timed out"); fc->timedout = 1; ngx_http_v2_close_stream(r->stream, NGX_HTTP_REQUEST_TIME_OUT); return; } ngx_http_v2_close_stream(r->stream, 0); } static void ngx_http_v2_retry_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 retry close stream handler"); ngx_http_v2_close_stream(r->stream, 0); } static void ngx_http_v2_handle_connection_handler(ngx_event_t *rev) { ngx_connection_t *c; ngx_http_v2_connection_t *h2c; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http2 handle connection handler"); c = rev->data; h2c = c->data; if (c->error) { ngx_http_v2_finalize_connection(h2c, 0); return; } rev->handler = ngx_http_v2_read_handler; if (rev->ready) { ngx_http_v2_read_handler(rev); return; } if (h2c->last_out && ngx_http_v2_send_output_queue(h2c) == NGX_ERROR) { ngx_http_v2_finalize_connection(h2c, 0); return; } ngx_http_v2_handle_connection(c->data); } static void ngx_http_v2_idle_handler(ngx_event_t *rev) { ngx_connection_t *c; ngx_http_v2_srv_conf_t *h2scf; ngx_http_v2_connection_t *h2c; ngx_http_core_loc_conf_t *clcf; 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 clcf = ngx_http_get_module_loc_conf(h2c->http_connection->conf_ctx, ngx_http_core_module); if (h2c->idle++ > 10 * clcf->keepalive_requests) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "http2 flood detected"); ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_NO_ERROR); return; } 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) { h2c->goaway = 1; if (ngx_http_v2_send_goaway(h2c, status) != NGX_ERROR) { (void) ngx_http_v2_send_output_queue(h2c); } } if (!h2c->processing && !h2c->pushing) { goto done; } c->read->handler = ngx_http_empty_handler; c->write->handler = ngx_http_empty_handler; h2c->last_out = NULL; h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, ngx_http_v2_module); size = ngx_http_v2_index_size(h2scf); for (i = 0; i < size; i++) { for (node = h2c->streams_index[i]; node; node = node->index) { stream = node->stream; if (stream == NULL) { continue; } stream->waiting = 0; r = stream->request; fc = r->connection; fc->error = 1; if (stream->queued) { stream->queued = 0; ev = fc->write; ev->active = 0; ev->ready = 1; } else { ev = fc->read; } ev->eof = 1; ev->handler(ev); } } h2c->blocked = 0; if (h2c->processing || h2c->pushing) { c->error = 1; return; } done: if (c->error) { ngx_http_close_connection(c); return; } ngx_http_v2_lingering_close(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); } } nginx-1.24.0/src/http/v2/ngx_http_v2.h000644 001751 001751 00000032005 14415135676 020633 0ustar00mdouninmdounin000000 000000 /* * 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_PROTO "\x02h2" #define NGX_HTTP_V2_STATE_BUFFER_SIZE 16 #define NGX_HTTP_V2_DEFAULT_FRAME_SIZE (1 << 14) #define NGX_HTTP_V2_MAX_FRAME_SIZE ((1 << 24) - 1) #define NGX_HTTP_V2_INT_OCTETS 4 #define NGX_HTTP_V2_MAX_FIELD \ (127 + (1 << (NGX_HTTP_V2_INT_OCTETS - 1) * 7) - 1) #define NGX_HTTP_V2_STREAM_ID_SIZE 4 #define NGX_HTTP_V2_FRAME_HEADER_SIZE 9 /* frame types */ #define NGX_HTTP_V2_DATA_FRAME 0x0 #define NGX_HTTP_V2_HEADERS_FRAME 0x1 #define NGX_HTTP_V2_PRIORITY_FRAME 0x2 #define NGX_HTTP_V2_RST_STREAM_FRAME 0x3 #define NGX_HTTP_V2_SETTINGS_FRAME 0x4 #define NGX_HTTP_V2_PUSH_PROMISE_FRAME 0x5 #define NGX_HTTP_V2_PING_FRAME 0x6 #define NGX_HTTP_V2_GOAWAY_FRAME 0x7 #define NGX_HTTP_V2_WINDOW_UPDATE_FRAME 0x8 #define NGX_HTTP_V2_CONTINUATION_FRAME 0x9 /* frame flags */ #define NGX_HTTP_V2_NO_FLAG 0x00 #define NGX_HTTP_V2_ACK_FLAG 0x01 #define NGX_HTTP_V2_END_STREAM_FLAG 0x01 #define NGX_HTTP_V2_END_HEADERS_FLAG 0x04 #define NGX_HTTP_V2_PADDED_FLAG 0x08 #define NGX_HTTP_V2_PRIORITY_FLAG 0x20 #define NGX_HTTP_V2_MAX_WINDOW ((1U << 31) - 1) #define NGX_HTTP_V2_DEFAULT_WINDOW 65535 #define NGX_HTTP_V2_DEFAULT_WEIGHT 16 typedef struct ngx_http_v2_connection_s ngx_http_v2_connection_t; typedef struct ngx_http_v2_node_s ngx_http_v2_node_t; typedef struct ngx_http_v2_out_frame_s ngx_http_v2_out_frame_t; typedef u_char *(*ngx_http_v2_handler_pt) (ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); typedef struct { ngx_str_t name; ngx_str_t value; } ngx_http_v2_header_t; typedef struct { ngx_uint_t sid; size_t length; size_t padding; unsigned flags:8; unsigned incomplete:1; unsigned keep_pool:1; /* HPACK */ unsigned parse_name:1; unsigned parse_value:1; unsigned index:1; ngx_http_v2_header_t header; size_t header_limit; u_char field_state; u_char *field_start; u_char *field_end; size_t field_rest; ngx_pool_t *pool; ngx_http_v2_stream_t *stream; u_char buffer[NGX_HTTP_V2_STATE_BUFFER_SIZE]; size_t buffer_used; ngx_http_v2_handler_pt handler; } ngx_http_v2_state_t; typedef struct { ngx_http_v2_header_t **entries; ngx_uint_t added; ngx_uint_t deleted; ngx_uint_t reused; ngx_uint_t allocated; size_t size; size_t free; u_char *storage; u_char *pos; } ngx_http_v2_hpack_t; struct ngx_http_v2_connection_s { ngx_connection_t *connection; ngx_http_connection_t *http_connection; off_t total_bytes; off_t payload_bytes; ngx_uint_t processing; ngx_uint_t frames; ngx_uint_t idle; ngx_uint_t priority_limit; ngx_uint_t pushing; ngx_uint_t concurrent_pushes; size_t send_window; size_t recv_window; size_t init_window; size_t frame_size; ngx_queue_t waiting; ngx_http_v2_state_t state; ngx_http_v2_hpack_t hpack; ngx_pool_t *pool; ngx_http_v2_out_frame_t *free_frames; ngx_connection_t *free_fake_connections; ngx_http_v2_node_t **streams_index; ngx_http_v2_out_frame_t *last_out; ngx_queue_t dependencies; ngx_queue_t closed; ngx_uint_t closed_nodes; ngx_uint_t last_sid; ngx_uint_t last_push; time_t lingering_time; unsigned settings_ack:1; unsigned table_update:1; unsigned blocked:1; unsigned goaway:1; unsigned push_disabled:1; }; struct ngx_http_v2_node_s { ngx_uint_t id; ngx_http_v2_node_t *index; ngx_http_v2_node_t *parent; ngx_queue_t queue; ngx_queue_t children; ngx_queue_t reuse; ngx_uint_t rank; ngx_uint_t weight; double rel_weight; ngx_http_v2_stream_t *stream; }; struct ngx_http_v2_stream_s { ngx_http_request_t *request; ngx_http_v2_connection_t *connection; ngx_http_v2_node_t *node; ngx_uint_t queued; /* * A change to SETTINGS_INITIAL_WINDOW_SIZE could cause the * send_window to become negative, hence it's signed. */ ssize_t send_window; size_t recv_window; ngx_buf_t *preread; ngx_uint_t frames; ngx_http_v2_out_frame_t *free_frames; ngx_chain_t *free_frame_headers; ngx_chain_t *free_bufs; ngx_queue_t queue; ngx_array_t *cookies; ngx_pool_t *pool; unsigned waiting:1; unsigned blocked:1; unsigned exhausted:1; unsigned in_closed:1; unsigned out_closed:1; unsigned rst_sent:1; unsigned no_flow_control:1; unsigned skip_data:1; }; struct ngx_http_v2_out_frame_s { ngx_http_v2_out_frame_t *next; ngx_chain_t *first; ngx_chain_t *last; ngx_int_t (*handler)(ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame); ngx_http_v2_stream_t *stream; size_t length; unsigned blocked:1; unsigned fin:1; }; static ngx_inline void ngx_http_v2_queue_frame(ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame) { ngx_http_v2_out_frame_t **out; for (out = &h2c->last_out; *out; out = &(*out)->next) { if ((*out)->blocked || (*out)->stream == NULL) { break; } if ((*out)->stream->node->rank < frame->stream->node->rank || ((*out)->stream->node->rank == frame->stream->node->rank && (*out)->stream->node->rel_weight >= frame->stream->node->rel_weight)) { break; } } frame->next = *out; *out = frame; } static ngx_inline void ngx_http_v2_queue_blocked_frame(ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame) { ngx_http_v2_out_frame_t **out; for (out = &h2c->last_out; *out; out = &(*out)->next) { if ((*out)->blocked || (*out)->stream == NULL) { break; } } frame->next = *out; *out = frame; } static ngx_inline void ngx_http_v2_queue_ordered_frame(ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame) { frame->next = h2c->last_out; h2c->last_out = frame; } void ngx_http_v2_init(ngx_event_t *rev); ngx_int_t ngx_http_v2_read_request_body(ngx_http_request_t *r); ngx_int_t ngx_http_v2_read_unbuffered_request_body(ngx_http_request_t *r); ngx_http_v2_stream_t *ngx_http_v2_push_stream(ngx_http_v2_stream_t *parent, ngx_str_t *path); void ngx_http_v2_close_stream(ngx_http_v2_stream_t *stream, ngx_int_t rc); ngx_int_t ngx_http_v2_send_output_queue(ngx_http_v2_connection_t *h2c); ngx_str_t *ngx_http_v2_get_static_name(ngx_uint_t index); ngx_str_t *ngx_http_v2_get_static_value(ngx_uint_t index); ngx_int_t ngx_http_v2_get_indexed_header(ngx_http_v2_connection_t *h2c, ngx_uint_t index, ngx_uint_t name_only); ngx_int_t ngx_http_v2_add_header(ngx_http_v2_connection_t *h2c, ngx_http_v2_header_t *header); ngx_int_t ngx_http_v2_table_size(ngx_http_v2_connection_t *h2c, size_t size); #define ngx_http_v2_prefix(bits) ((1 << (bits)) - 1) #if (NGX_HAVE_NONALIGNED) #define ngx_http_v2_parse_uint16(p) ntohs(*(uint16_t *) (p)) #define ngx_http_v2_parse_uint32(p) ntohl(*(uint32_t *) (p)) #else #define ngx_http_v2_parse_uint16(p) ((p)[0] << 8 | (p)[1]) #define ngx_http_v2_parse_uint32(p) \ ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3]) #endif #define ngx_http_v2_parse_length(p) ((p) >> 8) #define ngx_http_v2_parse_type(p) ((p) & 0xff) #define ngx_http_v2_parse_sid(p) (ngx_http_v2_parse_uint32(p) & 0x7fffffff) #define ngx_http_v2_parse_window(p) (ngx_http_v2_parse_uint32(p) & 0x7fffffff) #define ngx_http_v2_write_uint16_aligned(p, s) \ (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t)) #define ngx_http_v2_write_uint32_aligned(p, s) \ (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) #if (NGX_HAVE_NONALIGNED) #define ngx_http_v2_write_uint16 ngx_http_v2_write_uint16_aligned #define ngx_http_v2_write_uint32 ngx_http_v2_write_uint32_aligned #else #define ngx_http_v2_write_uint16(p, s) \ ((p)[0] = (u_char) ((s) >> 8), \ (p)[1] = (u_char) (s), \ (p) + sizeof(uint16_t)) #define ngx_http_v2_write_uint32(p, s) \ ((p)[0] = (u_char) ((s) >> 24), \ (p)[1] = (u_char) ((s) >> 16), \ (p)[2] = (u_char) ((s) >> 8), \ (p)[3] = (u_char) (s), \ (p) + sizeof(uint32_t)) #endif #define ngx_http_v2_write_len_and_type(p, l, t) \ ngx_http_v2_write_uint32_aligned(p, (l) << 8 | (t)) #define ngx_http_v2_write_sid ngx_http_v2_write_uint32 #define ngx_http_v2_indexed(i) (128 + (i)) #define ngx_http_v2_inc_indexed(i) (64 + (i)) #define ngx_http_v2_write_name(dst, src, len, tmp) \ ngx_http_v2_string_encode(dst, src, len, tmp, 1) #define ngx_http_v2_write_value(dst, src, len, tmp) \ ngx_http_v2_string_encode(dst, src, len, tmp, 0) #define NGX_HTTP_V2_ENCODE_RAW 0 #define NGX_HTTP_V2_ENCODE_HUFF 0x80 #define NGX_HTTP_V2_AUTHORITY_INDEX 1 #define NGX_HTTP_V2_METHOD_INDEX 2 #define NGX_HTTP_V2_METHOD_GET_INDEX 2 #define NGX_HTTP_V2_METHOD_POST_INDEX 3 #define NGX_HTTP_V2_PATH_INDEX 4 #define NGX_HTTP_V2_PATH_ROOT_INDEX 4 #define NGX_HTTP_V2_SCHEME_HTTP_INDEX 6 #define NGX_HTTP_V2_SCHEME_HTTPS_INDEX 7 #define NGX_HTTP_V2_STATUS_INDEX 8 #define NGX_HTTP_V2_STATUS_200_INDEX 8 #define NGX_HTTP_V2_STATUS_204_INDEX 9 #define NGX_HTTP_V2_STATUS_206_INDEX 10 #define NGX_HTTP_V2_STATUS_304_INDEX 11 #define NGX_HTTP_V2_STATUS_400_INDEX 12 #define NGX_HTTP_V2_STATUS_404_INDEX 13 #define NGX_HTTP_V2_STATUS_500_INDEX 14 #define NGX_HTTP_V2_ACCEPT_ENCODING_INDEX 16 #define NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX 17 #define NGX_HTTP_V2_CONTENT_LENGTH_INDEX 28 #define NGX_HTTP_V2_CONTENT_TYPE_INDEX 31 #define NGX_HTTP_V2_DATE_INDEX 33 #define NGX_HTTP_V2_LAST_MODIFIED_INDEX 44 #define NGX_HTTP_V2_LOCATION_INDEX 46 #define NGX_HTTP_V2_SERVER_INDEX 54 #define NGX_HTTP_V2_USER_AGENT_INDEX 58 #define NGX_HTTP_V2_VARY_INDEX 59 u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp, ngx_uint_t lower); #endif /* _NGX_HTTP_V2_H_INCLUDED_ */ nginx-1.24.0/src/http/v2/ngx_http_v2_encode.c000644 001751 001751 00000002244 14415135676 022145 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Nginx, Inc. * Copyright (C) Valentin V. Bartenev */ #include #include #include static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value); u_char * ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp, ngx_uint_t lower) { size_t hlen; hlen = ngx_http_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; } nginx-1.24.0/src/http/v2/ngx_http_v2_filter_module.c000644 001751 001751 00000162036 14415135676 023550 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Nginx, Inc. * Copyright (C) Valentin V. Bartenev * Copyright (C) Ruslan Ermilov */ #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_NO_TRAILERS (ngx_http_v2_out_frame_t *) -1 typedef struct { ngx_str_t name; u_char index; ngx_uint_t offset; } ngx_http_v2_push_header_t; static ngx_http_v2_push_header_t ngx_http_v2_push_headers[] = { { ngx_string(":authority"), NGX_HTTP_V2_AUTHORITY_INDEX, offsetof(ngx_http_headers_in_t, host) }, { ngx_string("accept-encoding"), NGX_HTTP_V2_ACCEPT_ENCODING_INDEX, offsetof(ngx_http_headers_in_t, accept_encoding) }, { ngx_string("accept-language"), NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX, offsetof(ngx_http_headers_in_t, accept_language) }, { ngx_string("user-agent"), NGX_HTTP_V2_USER_AGENT_INDEX, offsetof(ngx_http_headers_in_t, user_agent) }, }; #define NGX_HTTP_V2_PUSH_HEADERS \ (sizeof(ngx_http_v2_push_headers) / sizeof(ngx_http_v2_push_header_t)) static ngx_int_t ngx_http_v2_push_resources(ngx_http_request_t *r); static ngx_int_t ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, ngx_str_t *binary); static ngx_http_v2_out_frame_t *ngx_http_v2_create_headers_frame( ngx_http_request_t *r, u_char *pos, u_char *end, ngx_uint_t fin); static ngx_http_v2_out_frame_t *ngx_http_v2_create_push_frame( ngx_http_request_t *r, u_char *pos, u_char *end); static ngx_http_v2_out_frame_t *ngx_http_v2_create_trailers_frame( ngx_http_request_t *r); static ngx_chain_t *ngx_http_v2_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit); static ngx_chain_t *ngx_http_v2_filter_get_shadow( ngx_http_v2_stream_t *stream, ngx_buf_t *buf, off_t offset, off_t size); static ngx_http_v2_out_frame_t *ngx_http_v2_filter_get_data_frame( ngx_http_v2_stream_t *stream, size_t len, ngx_chain_t *first, ngx_chain_t *last); static ngx_inline ngx_int_t ngx_http_v2_flow_control( ngx_http_v2_connection_t *h2c, ngx_http_v2_stream_t *stream); static void ngx_http_v2_waiting_queue(ngx_http_v2_connection_t *h2c, ngx_http_v2_stream_t *stream); static ngx_inline ngx_int_t ngx_http_v2_filter_send( ngx_connection_t *fc, ngx_http_v2_stream_t *stream); static ngx_int_t ngx_http_v2_headers_frame_handler( ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame); static ngx_int_t ngx_http_v2_push_frame_handler( ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame); static ngx_int_t ngx_http_v2_data_frame_handler( ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame); static ngx_inline void ngx_http_v2_handle_frame( ngx_http_v2_stream_t *stream, ngx_http_v2_out_frame_t *frame); static ngx_inline void ngx_http_v2_handle_stream( ngx_http_v2_connection_t *h2c, ngx_http_v2_stream_t *stream); static void ngx_http_v2_filter_cleanup(void *data); static ngx_int_t ngx_http_v2_filter_init(ngx_conf_t *cf); static ngx_http_module_t ngx_http_v2_filter_module_ctx = { NULL, /* preconfiguration */ ngx_http_v2_filter_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; ngx_module_t ngx_http_v2_filter_module = { NGX_MODULE_V1, &ngx_http_v2_filter_module_ctx, /* module context */ NULL, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_int_t ngx_http_v2_header_filter(ngx_http_request_t *r) { u_char status, *pos, *start, *p, *tmp; size_t len, tmp_len; ngx_str_t host, location; ngx_uint_t i, port, fin; ngx_list_part_t *part; ngx_table_elt_t *header; ngx_connection_t *fc; ngx_http_cleanup_t *cln; ngx_http_v2_stream_t *stream; ngx_http_v2_out_frame_t *frame; ngx_http_v2_connection_t *h2c; ngx_http_core_loc_conf_t *clcf; ngx_http_core_srv_conf_t *cscf; u_char addr[NGX_SOCKADDR_STRLEN]; static const u_char nginx[5] = "\x84\xaa\x63\x55\xe7"; #if (NGX_HTTP_GZIP) static const u_char accept_encoding[12] = "\x8b\x84\x84\x2d\x69\x5b\x05\x44\x3c\x86\xaa\x6f"; #endif static size_t nginx_ver_len = ngx_http_v2_literal_size(NGINX_VER); static u_char nginx_ver[ngx_http_v2_literal_size(NGINX_VER)]; static size_t nginx_ver_build_len = ngx_http_v2_literal_size(NGINX_VER_BUILD); static u_char nginx_ver_build[ngx_http_v2_literal_size(NGINX_VER_BUILD)]; stream = r->stream; if (!stream) { return ngx_http_next_header_filter(r); } ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http2 header filter"); if (r->header_sent) { return NGX_OK; } r->header_sent = 1; if (r != r->main) { return NGX_OK; } fc = r->connection; if (fc->error) { return NGX_ERROR; } if (r->method == NGX_HTTP_HEAD) { r->header_only = 1; } switch (r->headers_out.status) { case NGX_HTTP_OK: status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_200_INDEX); break; case NGX_HTTP_NO_CONTENT: r->header_only = 1; ngx_str_null(&r->headers_out.content_type); r->headers_out.content_length = NULL; r->headers_out.content_length_n = -1; r->headers_out.last_modified_time = -1; r->headers_out.last_modified = NULL; status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_204_INDEX); break; case NGX_HTTP_PARTIAL_CONTENT: status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_206_INDEX); break; case NGX_HTTP_NOT_MODIFIED: r->header_only = 1; status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_304_INDEX); break; default: r->headers_out.last_modified_time = -1; r->headers_out.last_modified = NULL; switch (r->headers_out.status) { case NGX_HTTP_BAD_REQUEST: status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_400_INDEX); break; case NGX_HTTP_NOT_FOUND: status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_404_INDEX); break; case NGX_HTTP_INTERNAL_SERVER_ERROR: status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_500_INDEX); break; default: status = 0; } } h2c = stream->connection; if (!h2c->push_disabled && !h2c->goaway && stream->node->id % 2 == 1 && r->method != NGX_HTTP_HEAD) { if (ngx_http_v2_push_resources(r) != NGX_OK) { return NGX_ERROR; } } len = h2c->table_update ? 1 : 0; len += status ? 1 : 1 + ngx_http_v2_literal_size("418"); clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (r->headers_out.server == NULL) { if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { len += 1 + nginx_ver_len; } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { len += 1 + nginx_ver_build_len; } else { len += 1 + sizeof(nginx); } } if (r->headers_out.date == NULL) { len += 1 + ngx_http_v2_literal_size("Wed, 31 Dec 1986 18:00:00 GMT"); } if (r->headers_out.content_type.len) { len += 1 + NGX_HTTP_V2_INT_OCTETS + r->headers_out.content_type.len; if (r->headers_out.content_type_len == r->headers_out.content_type.len && r->headers_out.charset.len) { len += sizeof("; charset=") - 1 + r->headers_out.charset.len; } } if (r->headers_out.content_length == NULL && r->headers_out.content_length_n >= 0) { len += 1 + ngx_http_v2_integer_octets(NGX_OFF_T_LEN) + NGX_OFF_T_LEN; } if (r->headers_out.last_modified == NULL && r->headers_out.last_modified_time != -1) { len += 1 + ngx_http_v2_literal_size("Wed, 31 Dec 1986 18:00:00 GMT"); } if (r->headers_out.location && r->headers_out.location->value.len) { if (r->headers_out.location->value.data[0] == '/' && clcf->absolute_redirect) { if (clcf->server_name_in_redirect) { cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); host = cscf->server_name; } else if (r->headers_in.server.len) { host = r->headers_in.server; } else { host.len = NGX_SOCKADDR_STRLEN; host.data = addr; if (ngx_connection_local_sockaddr(fc, &host, 0) != NGX_OK) { return NGX_ERROR; } } port = ngx_inet_get_port(fc->local_sockaddr); location.len = sizeof("https://") - 1 + host.len + r->headers_out.location->value.len; if (clcf->port_in_redirect) { #if (NGX_HTTP_SSL) if (fc->ssl) port = (port == 443) ? 0 : port; else #endif port = (port == 80) ? 0 : port; } else { port = 0; } if (port) { location.len += sizeof(":65535") - 1; } location.data = ngx_pnalloc(r->pool, location.len); if (location.data == NULL) { return NGX_ERROR; } p = ngx_cpymem(location.data, "http", sizeof("http") - 1); #if (NGX_HTTP_SSL) if (fc->ssl) { *p++ = 's'; } #endif *p++ = ':'; *p++ = '/'; *p++ = '/'; p = ngx_cpymem(p, host.data, host.len); if (port) { p = ngx_sprintf(p, ":%ui", port); } p = ngx_cpymem(p, r->headers_out.location->value.data, r->headers_out.location->value.len); /* update r->headers_out.location->value for possible logging */ r->headers_out.location->value.len = p - location.data; r->headers_out.location->value.data = location.data; ngx_str_set(&r->headers_out.location->key, "Location"); } r->headers_out.location->hash = 0; len += 1 + NGX_HTTP_V2_INT_OCTETS + r->headers_out.location->value.len; } tmp_len = len; #if (NGX_HTTP_GZIP) if (r->gzip_vary) { if (clcf->gzip_vary) { len += 1 + sizeof(accept_encoding); } else { r->gzip_vary = 0; } } #endif part = &r->headers_out.headers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (header[i].hash == 0) { continue; } if (header[i].key.len > NGX_HTTP_V2_MAX_FIELD) { ngx_log_error(NGX_LOG_CRIT, fc->log, 0, "too long response header name: \"%V\"", &header[i].key); return NGX_ERROR; } if (header[i].value.len > NGX_HTTP_V2_MAX_FIELD) { ngx_log_error(NGX_LOG_CRIT, fc->log, 0, "too long response header value: \"%V: %V\"", &header[i].key, &header[i].value); return NGX_ERROR; } len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len + NGX_HTTP_V2_INT_OCTETS + header[i].value.len; if (header[i].key.len > tmp_len) { tmp_len = header[i].key.len; } if (header[i].value.len > tmp_len) { tmp_len = header[i].value.len; } } tmp = ngx_palloc(r->pool, tmp_len); pos = ngx_pnalloc(r->pool, len); if (pos == NULL || tmp == NULL) { return NGX_ERROR; } start = pos; if (h2c->table_update) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 table size update: 0"); *pos++ = (1 << 5) | 0; h2c->table_update = 0; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 output header: \":status: %03ui\"", r->headers_out.status); if (status) { *pos++ = status; } else { *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX); *pos++ = NGX_HTTP_V2_ENCODE_RAW | 3; pos = ngx_sprintf(pos, "%03ui", r->headers_out.status); } if (r->headers_out.server == NULL) { if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 output header: \"server: %s\"", NGINX_VER); } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 output header: \"server: %s\"", NGINX_VER_BUILD); } else { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 output header: \"server: nginx\""); } *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX); if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { if (nginx_ver[0] == '\0') { p = ngx_http_v2_write_value(nginx_ver, (u_char *) NGINX_VER, sizeof(NGINX_VER) - 1, tmp); nginx_ver_len = p - nginx_ver; } pos = ngx_cpymem(pos, nginx_ver, nginx_ver_len); } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { if (nginx_ver_build[0] == '\0') { p = ngx_http_v2_write_value(nginx_ver_build, (u_char *) NGINX_VER_BUILD, sizeof(NGINX_VER_BUILD) - 1, tmp); nginx_ver_build_len = p - nginx_ver_build; } pos = ngx_cpymem(pos, nginx_ver_build, nginx_ver_build_len); } else { pos = ngx_cpymem(pos, nginx, sizeof(nginx)); } } if (r->headers_out.date == NULL) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 output header: \"date: %V\"", &ngx_cached_http_time); *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_DATE_INDEX); pos = ngx_http_v2_write_value(pos, ngx_cached_http_time.data, ngx_cached_http_time.len, tmp); } if (r->headers_out.content_type.len) { *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_TYPE_INDEX); if (r->headers_out.content_type_len == r->headers_out.content_type.len && r->headers_out.charset.len) { len = r->headers_out.content_type.len + sizeof("; charset=") - 1 + r->headers_out.charset.len; p = ngx_pnalloc(r->pool, len); if (p == NULL) { return NGX_ERROR; } p = ngx_cpymem(p, r->headers_out.content_type.data, r->headers_out.content_type.len); p = ngx_cpymem(p, "; charset=", sizeof("; charset=") - 1); p = ngx_cpymem(p, r->headers_out.charset.data, r->headers_out.charset.len); /* updated r->headers_out.content_type is also needed for logging */ r->headers_out.content_type.len = len; r->headers_out.content_type.data = p - len; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 output header: \"content-type: %V\"", &r->headers_out.content_type); pos = ngx_http_v2_write_value(pos, r->headers_out.content_type.data, r->headers_out.content_type.len, tmp); } if (r->headers_out.content_length == NULL && r->headers_out.content_length_n >= 0) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 output header: \"content-length: %O\"", r->headers_out.content_length_n); *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_LENGTH_INDEX); p = pos; pos = ngx_sprintf(pos + 1, "%O", r->headers_out.content_length_n); *p = NGX_HTTP_V2_ENCODE_RAW | (u_char) (pos - p - 1); } if (r->headers_out.last_modified == NULL && r->headers_out.last_modified_time != -1) { *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LAST_MODIFIED_INDEX); ngx_http_time(pos, r->headers_out.last_modified_time); len = sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 output header: \"last-modified: %*s\"", len, pos); /* * Date will always be encoded using huffman in the temporary buffer, * so it's safe here to use src and dst pointing to the same address. */ pos = ngx_http_v2_write_value(pos, pos, len, tmp); } if (r->headers_out.location && r->headers_out.location->value.len) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 output header: \"location: %V\"", &r->headers_out.location->value); *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LOCATION_INDEX); pos = ngx_http_v2_write_value(pos, r->headers_out.location->value.data, r->headers_out.location->value.len, tmp); } #if (NGX_HTTP_GZIP) if (r->gzip_vary) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 output header: \"vary: Accept-Encoding\""); *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_VARY_INDEX); pos = ngx_cpymem(pos, accept_encoding, sizeof(accept_encoding)); } #endif part = &r->headers_out.headers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (header[i].hash == 0) { continue; } #if (NGX_DEBUG) if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) { ngx_strlow(tmp, header[i].key.data, header[i].key.len); ngx_log_debug3(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 output header: \"%*s: %V\"", header[i].key.len, tmp, &header[i].value); } #endif *pos++ = 0; pos = ngx_http_v2_write_name(pos, header[i].key.data, header[i].key.len, tmp); pos = ngx_http_v2_write_value(pos, header[i].value.data, header[i].value.len, tmp); } fin = r->header_only || (r->headers_out.content_length_n == 0 && !r->expect_trailers); frame = ngx_http_v2_create_headers_frame(r, start, pos, fin); if (frame == NULL) { return NGX_ERROR; } ngx_http_v2_queue_blocked_frame(h2c, frame); stream->queued++; cln = ngx_http_cleanup_add(r, 0); if (cln == NULL) { return NGX_ERROR; } cln->handler = ngx_http_v2_filter_cleanup; cln->data = stream; fc->send_chain = ngx_http_v2_send_chain; fc->need_last_buf = 1; fc->need_flush_buf = 1; return ngx_http_v2_filter_send(fc, stream); } static ngx_int_t ngx_http_v2_push_resources(ngx_http_request_t *r) { u_char *start, *end, *last; ngx_int_t rc; ngx_str_t path; ngx_uint_t i, push; ngx_table_elt_t *h; ngx_http_v2_loc_conf_t *h2lcf; ngx_http_complex_value_t *pushes; ngx_str_t binary[NGX_HTTP_V2_PUSH_HEADERS]; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http2 push resources"); ngx_memzero(binary, NGX_HTTP_V2_PUSH_HEADERS * sizeof(ngx_str_t)); h2lcf = ngx_http_get_module_loc_conf(r, ngx_http_v2_module); if (h2lcf->pushes) { pushes = h2lcf->pushes->elts; for (i = 0; i < h2lcf->pushes->nelts; i++) { if (ngx_http_complex_value(r, &pushes[i], &path) != NGX_OK) { return NGX_ERROR; } if (path.len == 0) { continue; } if (path.len == 3 && ngx_strncmp(path.data, "off", 3) == 0) { continue; } rc = ngx_http_v2_push_resource(r, &path, binary); if (rc == NGX_ERROR) { return NGX_ERROR; } if (rc == NGX_ABORT) { return NGX_OK; } /* NGX_OK, NGX_DECLINED */ } } if (!h2lcf->push_preload) { return NGX_OK; } for (h = r->headers_out.link; h; h = h->next) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http2 parse link: \"%V\"", &h->value); start = h->value.data; end = h->value.data + h->value.len; next_link: while (start < end && *start == ' ') { start++; } if (start == end || *start++ != '<') { continue; } while (start < end && *start == ' ') { start++; } for (last = start; last < end && *last != '>'; last++) { /* void */ } if (last == start || last == end) { continue; } path.len = last - start; path.data = start; start = last + 1; while (start < end && *start == ' ') { start++; } if (start == end) { continue; } if (*start == ',') { start++; goto next_link; } if (*start++ != ';') { continue; } last = ngx_strlchr(start, end, ','); if (last == NULL) { last = end; } push = 0; for ( ;; ) { while (start < last && *start == ' ') { start++; } if (last - start >= 6 && ngx_strncasecmp(start, (u_char *) "nopush", 6) == 0) { start += 6; if (start == last || *start == ' ' || *start == ';') { push = 0; break; } goto next_param; } if (last - start >= 11 && ngx_strncasecmp(start, (u_char *) "rel=preload", 11) == 0) { start += 11; if (start == last || *start == ' ' || *start == ';') { push = 1; } goto next_param; } if (last - start >= 4 && ngx_strncasecmp(start, (u_char *) "rel=", 4) == 0) { start += 4; while (start < last && *start == ' ') { start++; } if (start == last || *start++ != '"') { goto next_param; } for ( ;; ) { while (start < last && *start == ' ') { start++; } if (last - start >= 7 && ngx_strncasecmp(start, (u_char *) "preload", 7) == 0) { start += 7; if (start < last && (*start == ' ' || *start == '"')) { push = 1; break; } } while (start < last && *start != ' ' && *start != '"') { start++; } if (start == last) { break; } if (*start == '"') { break; } start++; } } next_param: start = ngx_strlchr(start, last, ';'); if (start == NULL) { break; } start++; } if (push) { while (path.len && path.data[path.len - 1] == ' ') { path.len--; } } if (push && path.len && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/')) { rc = ngx_http_v2_push_resource(r, &path, binary); if (rc == NGX_ERROR) { return NGX_ERROR; } if (rc == NGX_ABORT) { return NGX_OK; } /* NGX_OK, NGX_DECLINED */ } if (last < end) { start = last + 1; goto next_link; } } return NGX_OK; } static ngx_int_t ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, ngx_str_t *binary) { u_char *start, *pos, *tmp; size_t len; ngx_str_t *value; ngx_uint_t i; ngx_table_elt_t **h; ngx_connection_t *fc; ngx_http_v2_stream_t *stream; ngx_http_v2_out_frame_t *frame; ngx_http_v2_connection_t *h2c; ngx_http_v2_push_header_t *ph; fc = r->connection; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push resource"); stream = r->stream; h2c = stream->connection; if (!ngx_path_separator(path->data[0])) { ngx_log_error(NGX_LOG_WARN, fc->log, 0, "non-absolute path \"%V\" not pushed", path); return NGX_DECLINED; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 pushing:%ui limit:%ui", h2c->pushing, h2c->concurrent_pushes); if (h2c->pushing >= h2c->concurrent_pushes) { return NGX_ABORT; } if (h2c->last_push == 0x7ffffffe) { return NGX_ABORT; } if (path->len > NGX_HTTP_V2_MAX_FIELD) { return NGX_DECLINED; } if (r->headers_in.host == NULL) { return NGX_ABORT; } ph = ngx_http_v2_push_headers; len = ngx_max(r->schema.len, path->len); if (binary[0].len) { tmp = ngx_palloc(r->pool, len); if (tmp == NULL) { return NGX_ERROR; } } else { for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { h = (ngx_table_elt_t **) ((char *) &r->headers_in + ph[i].offset); if (*h) { len = ngx_max(len, (*h)->value.len); } } tmp = ngx_palloc(r->pool, len); if (tmp == NULL) { return NGX_ERROR; } for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { h = (ngx_table_elt_t **) ((char *) &r->headers_in + ph[i].offset); if (*h == NULL) { continue; } value = &(*h)->value; len = 1 + NGX_HTTP_V2_INT_OCTETS + value->len; pos = ngx_pnalloc(r->pool, len); if (pos == NULL) { return NGX_ERROR; } binary[i].data = pos; *pos++ = ngx_http_v2_inc_indexed(ph[i].index); pos = ngx_http_v2_write_value(pos, value->data, value->len, tmp); binary[i].len = pos - binary[i].data; } } len = (h2c->table_update ? 1 : 0) + 1 + 1 + NGX_HTTP_V2_INT_OCTETS + path->len + 1 + NGX_HTTP_V2_INT_OCTETS + r->schema.len; for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { len += binary[i].len; } pos = ngx_pnalloc(r->pool, len); if (pos == NULL) { return NGX_ERROR; } start = pos; if (h2c->table_update) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 table size update: 0"); *pos++ = (1 << 5) | 0; h2c->table_update = 0; } ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":method: GET\""); *pos++ = ngx_http_v2_indexed(NGX_HTTP_V2_METHOD_GET_INDEX); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":path: %V\"", path); *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); pos = ngx_http_v2_write_value(pos, path->data, path->len, tmp); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":scheme: %V\"", &r->schema); if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { *pos++ = ngx_http_v2_indexed(NGX_HTTP_V2_SCHEME_HTTPS_INDEX); } else if (r->schema.len == 4 && ngx_strncmp(r->schema.data, "http", 4) == 0) { *pos++ = ngx_http_v2_indexed(NGX_HTTP_V2_SCHEME_HTTP_INDEX); } else { *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SCHEME_HTTP_INDEX); pos = ngx_http_v2_write_value(pos, r->schema.data, r->schema.len, tmp); } for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { h = (ngx_table_elt_t **) ((char *) &r->headers_in + ph[i].offset); if (*h == NULL) { continue; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \"%V: %V\"", &ph[i].name, &(*h)->value); pos = ngx_cpymem(pos, binary[i].data, binary[i].len); } frame = ngx_http_v2_create_push_frame(r, start, pos); if (frame == NULL) { return NGX_ERROR; } ngx_http_v2_queue_blocked_frame(h2c, frame); stream->queued++; stream = ngx_http_v2_push_stream(stream, path); if (stream) { stream->request->request_length = pos - start; return NGX_OK; } return NGX_ERROR; } static ngx_http_v2_out_frame_t * ngx_http_v2_create_headers_frame(ngx_http_request_t *r, u_char *pos, u_char *end, ngx_uint_t fin) { u_char type, flags; size_t rest, frame_size; ngx_buf_t *b; ngx_chain_t *cl, **ll; ngx_http_v2_stream_t *stream; ngx_http_v2_out_frame_t *frame; stream = r->stream; rest = end - pos; frame = ngx_palloc(r->pool, sizeof(ngx_http_v2_out_frame_t)); if (frame == NULL) { return NULL; } frame->handler = ngx_http_v2_headers_frame_handler; frame->stream = stream; frame->length = rest; frame->blocked = 1; frame->fin = fin; ll = &frame->first; type = NGX_HTTP_V2_HEADERS_FRAME; flags = fin ? NGX_HTTP_V2_END_STREAM_FLAG : NGX_HTTP_V2_NO_FLAG; frame_size = stream->connection->frame_size; for ( ;; ) { if (rest <= frame_size) { frame_size = rest; flags |= NGX_HTTP_V2_END_HEADERS_FLAG; } b = ngx_create_temp_buf(r->pool, NGX_HTTP_V2_FRAME_HEADER_SIZE); if (b == NULL) { return NULL; } b->last = ngx_http_v2_write_len_and_type(b->last, frame_size, type); *b->last++ = flags; b->last = ngx_http_v2_write_sid(b->last, stream->node->id); b->tag = (ngx_buf_tag_t) &ngx_http_v2_module; cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NULL; } cl->buf = b; *ll = cl; ll = &cl->next; b = ngx_calloc_buf(r->pool); if (b == NULL) { return NULL; } b->pos = pos; pos += frame_size; b->last = pos; b->start = b->pos; b->end = b->last; b->temporary = 1; cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NULL; } cl->buf = b; *ll = cl; ll = &cl->next; rest -= frame_size; if (rest) { frame->length += NGX_HTTP_V2_FRAME_HEADER_SIZE; type = NGX_HTTP_V2_CONTINUATION_FRAME; flags = NGX_HTTP_V2_NO_FLAG; continue; } b->last_buf = fin; cl->next = NULL; frame->last = cl; ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http2:%ui create HEADERS frame %p: len:%uz fin:%ui", stream->node->id, frame, frame->length, fin); return frame; } } static ngx_http_v2_out_frame_t * ngx_http_v2_create_push_frame(ngx_http_request_t *r, u_char *pos, u_char *end) { u_char type, flags; size_t rest, frame_size, len; ngx_buf_t *b; ngx_chain_t *cl, **ll; ngx_http_v2_stream_t *stream; ngx_http_v2_out_frame_t *frame; ngx_http_v2_connection_t *h2c; stream = r->stream; h2c = stream->connection; rest = NGX_HTTP_V2_STREAM_ID_SIZE + (end - pos); frame = ngx_palloc(r->pool, sizeof(ngx_http_v2_out_frame_t)); if (frame == NULL) { return NULL; } frame->handler = ngx_http_v2_push_frame_handler; frame->stream = stream; frame->length = rest; frame->blocked = 1; frame->fin = 0; ll = &frame->first; type = NGX_HTTP_V2_PUSH_PROMISE_FRAME; flags = NGX_HTTP_V2_NO_FLAG; frame_size = h2c->frame_size; for ( ;; ) { if (rest <= frame_size) { frame_size = rest; flags |= NGX_HTTP_V2_END_HEADERS_FLAG; } b = ngx_create_temp_buf(r->pool, NGX_HTTP_V2_FRAME_HEADER_SIZE + ((type == NGX_HTTP_V2_PUSH_PROMISE_FRAME) ? NGX_HTTP_V2_STREAM_ID_SIZE : 0)); if (b == NULL) { return NULL; } b->last = ngx_http_v2_write_len_and_type(b->last, frame_size, type); *b->last++ = flags; b->last = ngx_http_v2_write_sid(b->last, stream->node->id); b->tag = (ngx_buf_tag_t) &ngx_http_v2_module; if (type == NGX_HTTP_V2_PUSH_PROMISE_FRAME) { h2c->last_push += 2; b->last = ngx_http_v2_write_sid(b->last, h2c->last_push); len = frame_size - NGX_HTTP_V2_STREAM_ID_SIZE; } else { len = frame_size; } cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NULL; } cl->buf = b; *ll = cl; ll = &cl->next; b = ngx_calloc_buf(r->pool); if (b == NULL) { return NULL; } b->pos = pos; pos += len; b->last = pos; b->start = b->pos; b->end = b->last; b->temporary = 1; cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NULL; } cl->buf = b; *ll = cl; ll = &cl->next; rest -= frame_size; if (rest) { frame->length += NGX_HTTP_V2_FRAME_HEADER_SIZE; type = NGX_HTTP_V2_CONTINUATION_FRAME; continue; } cl->next = NULL; frame->last = cl; ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http2:%ui create PUSH_PROMISE frame %p: " "sid:%ui len:%uz", stream->node->id, frame, h2c->last_push, frame->length); return frame; } } static ngx_http_v2_out_frame_t * ngx_http_v2_create_trailers_frame(ngx_http_request_t *r) { u_char *pos, *start, *tmp; size_t len, tmp_len; ngx_uint_t i; ngx_list_part_t *part; ngx_table_elt_t *header; ngx_connection_t *fc; fc = r->connection; len = 0; tmp_len = 0; part = &r->headers_out.trailers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (header[i].hash == 0) { continue; } if (header[i].key.len > NGX_HTTP_V2_MAX_FIELD) { ngx_log_error(NGX_LOG_CRIT, fc->log, 0, "too long response trailer name: \"%V\"", &header[i].key); return NULL; } if (header[i].value.len > NGX_HTTP_V2_MAX_FIELD) { ngx_log_error(NGX_LOG_CRIT, fc->log, 0, "too long response trailer value: \"%V: %V\"", &header[i].key, &header[i].value); return NULL; } len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len + NGX_HTTP_V2_INT_OCTETS + header[i].value.len; if (header[i].key.len > tmp_len) { tmp_len = header[i].key.len; } if (header[i].value.len > tmp_len) { tmp_len = header[i].value.len; } } if (len == 0) { return NGX_HTTP_V2_NO_TRAILERS; } tmp = ngx_palloc(r->pool, tmp_len); pos = ngx_pnalloc(r->pool, len); if (pos == NULL || tmp == NULL) { return NULL; } start = pos; part = &r->headers_out.trailers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (header[i].hash == 0) { continue; } #if (NGX_DEBUG) if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) { ngx_strlow(tmp, header[i].key.data, header[i].key.len); ngx_log_debug3(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 output trailer: \"%*s: %V\"", header[i].key.len, tmp, &header[i].value); } #endif *pos++ = 0; pos = ngx_http_v2_write_name(pos, header[i].key.data, header[i].key.len, tmp); pos = ngx_http_v2_write_value(pos, header[i].value.data, header[i].value.len, tmp); } return ngx_http_v2_create_headers_frame(r, start, pos, 1); } static ngx_chain_t * ngx_http_v2_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit) { off_t size, offset; size_t rest, frame_size; ngx_chain_t *cl, *out, **ln; ngx_http_request_t *r; ngx_http_v2_stream_t *stream; ngx_http_v2_loc_conf_t *h2lcf; ngx_http_v2_out_frame_t *frame, *trailers; ngx_http_v2_connection_t *h2c; r = fc->data; stream = r->stream; #if (NGX_SUPPRESS_WARN) size = 0; #endif ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 send chain: %p", in); while (in) { size = ngx_buf_size(in->buf); if (size || in->buf->last_buf) { break; } in = in->next; } if (in == NULL || stream->out_closed) { if (size) { ngx_log_error(NGX_LOG_ERR, fc->log, 0, "output on closed stream"); return NGX_CHAIN_ERROR; } if (ngx_http_v2_filter_send(fc, stream) == NGX_ERROR) { return NGX_CHAIN_ERROR; } return NULL; } h2c = stream->connection; if (size && ngx_http_v2_flow_control(h2c, stream) == NGX_DECLINED) { if (ngx_http_v2_filter_send(fc, stream) == NGX_ERROR) { return NGX_CHAIN_ERROR; } if (ngx_http_v2_flow_control(h2c, stream) == NGX_DECLINED) { fc->write->active = 1; fc->write->ready = 0; return in; } } if (in->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) { cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_CHAIN_ERROR; } cl->buf = in->buf; in->buf = cl->buf->shadow; offset = ngx_buf_in_memory(in->buf) ? (cl->buf->pos - in->buf->pos) : (cl->buf->file_pos - in->buf->file_pos); cl->next = stream->free_bufs; stream->free_bufs = cl; } else { offset = 0; } if (limit == 0 || limit > (off_t) h2c->send_window) { limit = h2c->send_window; } if (limit > stream->send_window) { limit = (stream->send_window > 0) ? stream->send_window : 0; } h2lcf = ngx_http_get_module_loc_conf(r, ngx_http_v2_module); frame_size = (h2lcf->chunk_size < h2c->frame_size) ? h2lcf->chunk_size : h2c->frame_size; trailers = NGX_HTTP_V2_NO_TRAILERS; #if (NGX_SUPPRESS_WARN) cl = NULL; #endif for ( ;; ) { if ((off_t) frame_size > limit) { frame_size = (size_t) limit; } ln = &out; rest = frame_size; while ((off_t) rest >= size) { if (offset) { cl = ngx_http_v2_filter_get_shadow(stream, in->buf, offset, size); if (cl == NULL) { return NGX_CHAIN_ERROR; } offset = 0; } else { cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_CHAIN_ERROR; } cl->buf = in->buf; } *ln = cl; ln = &cl->next; rest -= (size_t) size; in = in->next; if (in == NULL) { frame_size -= rest; rest = 0; break; } size = ngx_buf_size(in->buf); } if (rest) { cl = ngx_http_v2_filter_get_shadow(stream, in->buf, offset, rest); if (cl == NULL) { return NGX_CHAIN_ERROR; } cl->buf->flush = 0; cl->buf->last_buf = 0; *ln = cl; offset += rest; size -= rest; } if (cl->buf->last_buf) { trailers = ngx_http_v2_create_trailers_frame(r); if (trailers == NULL) { return NGX_CHAIN_ERROR; } if (trailers != NGX_HTTP_V2_NO_TRAILERS) { cl->buf->last_buf = 0; } } if (frame_size || cl->buf->last_buf) { frame = ngx_http_v2_filter_get_data_frame(stream, frame_size, out, cl); if (frame == NULL) { return NGX_CHAIN_ERROR; } ngx_http_v2_queue_frame(h2c, frame); h2c->send_window -= frame_size; stream->send_window -= frame_size; stream->queued++; } if (in == NULL) { if (trailers != NGX_HTTP_V2_NO_TRAILERS) { ngx_http_v2_queue_frame(h2c, trailers); stream->queued++; } break; } limit -= frame_size; if (limit == 0) { break; } } if (offset) { cl = ngx_http_v2_filter_get_shadow(stream, in->buf, offset, size); if (cl == NULL) { return NGX_CHAIN_ERROR; } in->buf = cl->buf; ngx_free_chain(r->pool, cl); } if (ngx_http_v2_filter_send(fc, stream) == NGX_ERROR) { return NGX_CHAIN_ERROR; } if (in && ngx_http_v2_flow_control(h2c, stream) == NGX_DECLINED) { fc->write->active = 1; fc->write->ready = 0; } return in; } static ngx_chain_t * ngx_http_v2_filter_get_shadow(ngx_http_v2_stream_t *stream, ngx_buf_t *buf, off_t offset, off_t size) { ngx_buf_t *chunk; ngx_chain_t *cl; cl = ngx_chain_get_free_buf(stream->request->pool, &stream->free_bufs); if (cl == NULL) { return NULL; } chunk = cl->buf; ngx_memcpy(chunk, buf, sizeof(ngx_buf_t)); chunk->tag = (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow; chunk->shadow = buf; if (ngx_buf_in_memory(chunk)) { chunk->pos += offset; chunk->last = chunk->pos + size; } if (chunk->in_file) { chunk->file_pos += offset; chunk->file_last = chunk->file_pos + size; } return cl; } static ngx_http_v2_out_frame_t * ngx_http_v2_filter_get_data_frame(ngx_http_v2_stream_t *stream, size_t len, ngx_chain_t *first, ngx_chain_t *last) { u_char flags; ngx_buf_t *buf; ngx_chain_t *cl; ngx_http_v2_out_frame_t *frame; ngx_http_v2_connection_t *h2c; frame = stream->free_frames; h2c = stream->connection; if (frame) { stream->free_frames = frame->next; } else if (h2c->frames < 10000) { frame = ngx_palloc(stream->request->pool, sizeof(ngx_http_v2_out_frame_t)); if (frame == NULL) { return NULL; } stream->frames++; h2c->frames++; } else { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "http2 flood detected"); h2c->connection->error = 1; return NULL; } flags = last->buf->last_buf ? NGX_HTTP_V2_END_STREAM_FLAG : 0; ngx_log_debug4(NGX_LOG_DEBUG_HTTP, stream->request->connection->log, 0, "http2:%ui create DATA frame %p: len:%uz flags:%ui", stream->node->id, frame, len, (ngx_uint_t) flags); cl = ngx_chain_get_free_buf(stream->request->pool, &stream->free_frame_headers); if (cl == NULL) { return NULL; } buf = cl->buf; if (buf->start == NULL) { buf->start = ngx_palloc(stream->request->pool, NGX_HTTP_V2_FRAME_HEADER_SIZE); if (buf->start == NULL) { return NULL; } buf->end = buf->start + NGX_HTTP_V2_FRAME_HEADER_SIZE; buf->last = buf->end; buf->tag = (ngx_buf_tag_t) &ngx_http_v2_module; buf->memory = 1; } buf->pos = buf->start; buf->last = buf->pos; buf->last = ngx_http_v2_write_len_and_type(buf->last, len, NGX_HTTP_V2_DATA_FRAME); *buf->last++ = flags; buf->last = ngx_http_v2_write_sid(buf->last, stream->node->id); cl->next = first; first = cl; last->buf->flush = 1; frame->first = first; frame->last = last; frame->handler = ngx_http_v2_data_frame_handler; frame->stream = stream; frame->length = len; frame->blocked = 0; frame->fin = last->buf->last_buf; return frame; } static ngx_inline ngx_int_t ngx_http_v2_flow_control(ngx_http_v2_connection_t *h2c, ngx_http_v2_stream_t *stream) { ngx_log_debug3(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2:%ui windows: conn:%uz stream:%z", stream->node->id, h2c->send_window, stream->send_window); if (stream->send_window <= 0) { stream->exhausted = 1; return NGX_DECLINED; } if (h2c->send_window == 0) { ngx_http_v2_waiting_queue(h2c, stream); return NGX_DECLINED; } return NGX_OK; } static void ngx_http_v2_waiting_queue(ngx_http_v2_connection_t *h2c, ngx_http_v2_stream_t *stream) { ngx_queue_t *q; ngx_http_v2_stream_t *s; if (stream->waiting) { return; } stream->waiting = 1; for (q = ngx_queue_last(&h2c->waiting); q != ngx_queue_sentinel(&h2c->waiting); q = ngx_queue_prev(q)) { s = ngx_queue_data(q, ngx_http_v2_stream_t, queue); if (s->node->rank < stream->node->rank || (s->node->rank == stream->node->rank && s->node->rel_weight >= stream->node->rel_weight)) { break; } } ngx_queue_insert_after(q, &stream->queue); } static ngx_inline ngx_int_t ngx_http_v2_filter_send(ngx_connection_t *fc, ngx_http_v2_stream_t *stream) { ngx_connection_t *c; c = stream->connection->connection; if (stream->queued == 0 && !c->buffered) { fc->buffered &= ~NGX_HTTP_V2_BUFFERED; return NGX_OK; } stream->blocked = 1; if (ngx_http_v2_send_output_queue(stream->connection) == NGX_ERROR) { fc->error = 1; return NGX_ERROR; } stream->blocked = 0; if (stream->queued) { fc->buffered |= NGX_HTTP_V2_BUFFERED; fc->write->active = 1; fc->write->ready = 0; return NGX_AGAIN; } fc->buffered &= ~NGX_HTTP_V2_BUFFERED; return NGX_OK; } static ngx_int_t ngx_http_v2_headers_frame_handler(ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame) { ngx_chain_t *cl, *ln; ngx_http_v2_stream_t *stream; stream = frame->stream; cl = frame->first; for ( ;; ) { if (cl->buf->pos != cl->buf->last) { frame->first = cl; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2:%ui HEADERS frame %p was sent partially", stream->node->id, frame); return NGX_AGAIN; } ln = cl->next; if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_module) { cl->next = stream->free_frame_headers; stream->free_frame_headers = cl; } else { cl->next = stream->free_bufs; stream->free_bufs = cl; } if (cl == frame->last) { break; } cl = ln; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2:%ui HEADERS frame %p was sent", stream->node->id, frame); stream->request->header_size += NGX_HTTP_V2_FRAME_HEADER_SIZE + frame->length; h2c->payload_bytes += frame->length; ngx_http_v2_handle_frame(stream, frame); ngx_http_v2_handle_stream(h2c, stream); return NGX_OK; } static ngx_int_t ngx_http_v2_push_frame_handler(ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame) { ngx_chain_t *cl, *ln; ngx_http_v2_stream_t *stream; stream = frame->stream; cl = frame->first; for ( ;; ) { if (cl->buf->pos != cl->buf->last) { frame->first = cl; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2:%ui PUSH_PROMISE frame %p was sent partially", stream->node->id, frame); return NGX_AGAIN; } ln = cl->next; if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_module) { cl->next = stream->free_frame_headers; stream->free_frame_headers = cl; } else { cl->next = stream->free_bufs; stream->free_bufs = cl; } if (cl == frame->last) { break; } cl = ln; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2:%ui PUSH_PROMISE frame %p was sent", stream->node->id, frame); stream->request->header_size += NGX_HTTP_V2_FRAME_HEADER_SIZE + frame->length; h2c->payload_bytes += frame->length; ngx_http_v2_handle_frame(stream, frame); ngx_http_v2_handle_stream(h2c, stream); return NGX_OK; } static ngx_int_t ngx_http_v2_data_frame_handler(ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame) { ngx_buf_t *buf; ngx_chain_t *cl, *ln; ngx_http_v2_stream_t *stream; stream = frame->stream; cl = frame->first; if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_module) { if (cl->buf->pos != cl->buf->last) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2:%ui DATA frame %p was sent partially", stream->node->id, frame); return NGX_AGAIN; } ln = cl->next; cl->next = stream->free_frame_headers; stream->free_frame_headers = cl; if (cl == frame->last) { goto done; } cl = ln; } for ( ;; ) { if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) { buf = cl->buf->shadow; if (ngx_buf_in_memory(buf)) { buf->pos = cl->buf->pos; } if (buf->in_file) { buf->file_pos = cl->buf->file_pos; } } if (ngx_buf_size(cl->buf) != 0) { if (cl != frame->first) { frame->first = cl; ngx_http_v2_handle_stream(h2c, stream); } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2:%ui DATA frame %p was sent partially", stream->node->id, frame); return NGX_AGAIN; } ln = cl->next; if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) { cl->next = stream->free_bufs; stream->free_bufs = cl; } else { ngx_free_chain(stream->request->pool, cl); } if (cl == frame->last) { goto done; } cl = ln; } done: ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2:%ui DATA frame %p was sent", stream->node->id, frame); stream->request->header_size += NGX_HTTP_V2_FRAME_HEADER_SIZE; h2c->payload_bytes += frame->length; 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; ngx_http_v2_connection_t *h2c; r = stream->request; r->connection->sent += NGX_HTTP_V2_FRAME_HEADER_SIZE + frame->length; h2c = stream->connection; h2c->total_bytes += 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; } nginx-1.24.0/src/http/v2/ngx_http_v2_module.c000644 001751 001751 00000034650 14415135676 022203 0ustar00mdouninmdounin000000 000000 /* * 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_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_v2_recv_buffer_size(ngx_conf_t *cf, void *post, void *data); static char *ngx_http_v2_pool_size(ngx_conf_t *cf, void *post, void *data); static char *ngx_http_v2_preread_size(ngx_conf_t *cf, void *post, void *data); static char *ngx_http_v2_streams_index_mask(ngx_conf_t *cf, void *post, void *data); static char *ngx_http_v2_chunk_size(ngx_conf_t *cf, void *post, void *data); static char *ngx_http_v2_obsolete(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_conf_deprecated_t ngx_http_v2_recv_timeout_deprecated = { ngx_conf_deprecated, "http2_recv_timeout", "client_header_timeout" }; static ngx_conf_deprecated_t ngx_http_v2_idle_timeout_deprecated = { ngx_conf_deprecated, "http2_idle_timeout", "keepalive_timeout" }; static ngx_conf_deprecated_t ngx_http_v2_max_requests_deprecated = { ngx_conf_deprecated, "http2_max_requests", "keepalive_requests" }; static ngx_conf_deprecated_t ngx_http_v2_max_field_size_deprecated = { ngx_conf_deprecated, "http2_max_field_size", "large_client_header_buffers" }; static ngx_conf_deprecated_t ngx_http_v2_max_header_size_deprecated = { ngx_conf_deprecated, "http2_max_header_size", "large_client_header_buffers" }; static ngx_conf_post_t ngx_http_v2_recv_buffer_size_post = { ngx_http_v2_recv_buffer_size }; static ngx_conf_post_t ngx_http_v2_pool_size_post = { ngx_http_v2_pool_size }; static ngx_conf_post_t ngx_http_v2_preread_size_post = { ngx_http_v2_preread_size }; static ngx_conf_post_t ngx_http_v2_streams_index_mask_post = { ngx_http_v2_streams_index_mask }; static ngx_conf_post_t ngx_http_v2_chunk_size_post = { ngx_http_v2_chunk_size }; static ngx_command_t ngx_http_v2_commands[] = { { ngx_string("http2_recv_buffer_size"), NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_MAIN_CONF_OFFSET, offsetof(ngx_http_v2_main_conf_t, recv_buffer_size), &ngx_http_v2_recv_buffer_size_post }, { ngx_string("http2_pool_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_SRV_CONF_OFFSET, offsetof(ngx_http_v2_srv_conf_t, pool_size), &ngx_http_v2_pool_size_post }, { ngx_string("http2_max_concurrent_streams"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_SRV_CONF_OFFSET, offsetof(ngx_http_v2_srv_conf_t, concurrent_streams), NULL }, { ngx_string("http2_max_concurrent_pushes"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_SRV_CONF_OFFSET, offsetof(ngx_http_v2_srv_conf_t, concurrent_pushes), NULL }, { ngx_string("http2_max_requests"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_http_v2_obsolete, 0, 0, &ngx_http_v2_max_requests_deprecated }, { ngx_string("http2_max_field_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_http_v2_obsolete, 0, 0, &ngx_http_v2_max_field_size_deprecated }, { ngx_string("http2_max_header_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_http_v2_obsolete, 0, 0, &ngx_http_v2_max_header_size_deprecated }, { ngx_string("http2_body_preread_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, 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_http_v2_obsolete, 0, 0, &ngx_http_v2_recv_timeout_deprecated }, { ngx_string("http2_idle_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_http_v2_obsolete, 0, 0, &ngx_http_v2_idle_timeout_deprecated }, { ngx_string("http2_chunk_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_v2_loc_conf_t, chunk_size), &ngx_http_v2_chunk_size_post }, { ngx_string("http2_push_preload"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_v2_loc_conf_t, push_preload), NULL }, { ngx_string("http2_push"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_v2_push, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_v2_module_ctx = { ngx_http_v2_add_variables, /* preconfiguration */ NULL, /* postconfiguration */ ngx_http_v2_create_main_conf, /* create main configuration */ ngx_http_v2_init_main_conf, /* init main configuration */ ngx_http_v2_create_srv_conf, /* create server configuration */ ngx_http_v2_merge_srv_conf, /* merge server configuration */ ngx_http_v2_create_loc_conf, /* create location configuration */ ngx_http_v2_merge_loc_conf /* merge location configuration */ }; ngx_module_t ngx_http_v2_module = { NGX_MODULE_V1, &ngx_http_v2_module_ctx, /* module context */ ngx_http_v2_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ ngx_http_v2_module_init, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_variable_t ngx_http_v2_vars[] = { { ngx_string("http2"), NULL, ngx_http_v2_variable, 0, 0, 0 }, ngx_http_null_variable }; static ngx_int_t ngx_http_v2_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var, *v; for (v = ngx_http_v2_vars; v->name.len; v++) { var = ngx_http_add_variable(cf, &v->name, v->flags); if (var == NULL) { return NGX_ERROR; } var->get_handler = v->get_handler; var->data = v->data; } return NGX_OK; } static ngx_int_t ngx_http_v2_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { if (r->stream) { #if (NGX_HTTP_SSL) if (r->connection->ssl) { v->len = sizeof("h2") - 1; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = (u_char *) "h2"; return NGX_OK; } #endif v->len = sizeof("h2c") - 1; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = (u_char *) "h2c"; return NGX_OK; } *v = ngx_http_variable_null_value; return NGX_OK; } static ngx_int_t ngx_http_v2_module_init(ngx_cycle_t *cycle) { return NGX_OK; } static void * ngx_http_v2_create_main_conf(ngx_conf_t *cf) { ngx_http_v2_main_conf_t *h2mcf; h2mcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v2_main_conf_t)); if (h2mcf == NULL) { return NULL; } h2mcf->recv_buffer_size = NGX_CONF_UNSET_SIZE; return h2mcf; } static char * ngx_http_v2_init_main_conf(ngx_conf_t *cf, void *conf) { ngx_http_v2_main_conf_t *h2mcf = conf; ngx_conf_init_size_value(h2mcf->recv_buffer_size, 256 * 1024); return NGX_CONF_OK; } static void * ngx_http_v2_create_srv_conf(ngx_conf_t *cf) { ngx_http_v2_srv_conf_t *h2scf; h2scf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v2_srv_conf_t)); if (h2scf == NULL) { return NULL; } h2scf->pool_size = NGX_CONF_UNSET_SIZE; h2scf->concurrent_streams = NGX_CONF_UNSET_UINT; h2scf->concurrent_pushes = NGX_CONF_UNSET_UINT; h2scf->preread_size = NGX_CONF_UNSET_SIZE; h2scf->streams_index_mask = NGX_CONF_UNSET_UINT; return h2scf; } static char * ngx_http_v2_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_v2_srv_conf_t *prev = parent; ngx_http_v2_srv_conf_t *conf = child; ngx_conf_merge_size_value(conf->pool_size, prev->pool_size, 4096); ngx_conf_merge_uint_value(conf->concurrent_streams, prev->concurrent_streams, 128); ngx_conf_merge_uint_value(conf->concurrent_pushes, prev->concurrent_pushes, 10); ngx_conf_merge_size_value(conf->preread_size, prev->preread_size, 65536); ngx_conf_merge_uint_value(conf->streams_index_mask, prev->streams_index_mask, 32 - 1); return NGX_CONF_OK; } static void * ngx_http_v2_create_loc_conf(ngx_conf_t *cf) { ngx_http_v2_loc_conf_t *h2lcf; h2lcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v2_loc_conf_t)); if (h2lcf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * h2lcf->pushes = NULL; */ h2lcf->chunk_size = NGX_CONF_UNSET_SIZE; h2lcf->push_preload = NGX_CONF_UNSET; h2lcf->push = NGX_CONF_UNSET; return h2lcf; } static char * ngx_http_v2_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_v2_loc_conf_t *prev = parent; ngx_http_v2_loc_conf_t *conf = child; ngx_conf_merge_size_value(conf->chunk_size, prev->chunk_size, 8 * 1024); ngx_conf_merge_value(conf->push, prev->push, 1); if (conf->push && conf->pushes == NULL) { conf->pushes = prev->pushes; } ngx_conf_merge_value(conf->push_preload, prev->push_preload, 0); return NGX_CONF_OK; } static char * ngx_http_v2_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_v2_loc_conf_t *h2lcf = conf; ngx_str_t *value; ngx_http_complex_value_t *cv; ngx_http_compile_complex_value_t ccv; value = cf->args->elts; if (ngx_strcmp(value[1].data, "off") == 0) { if (h2lcf->pushes) { return "\"off\" parameter cannot be used with URI"; } if (h2lcf->push == 0) { return "is duplicate"; } h2lcf->push = 0; return NGX_CONF_OK; } if (h2lcf->push == 0) { return "URI cannot be used with \"off\" parameter"; } h2lcf->push = 1; if (h2lcf->pushes == NULL) { h2lcf->pushes = ngx_array_create(cf->pool, 1, sizeof(ngx_http_complex_value_t)); if (h2lcf->pushes == NULL) { return NGX_CONF_ERROR; } } cv = ngx_array_push(h2lcf->pushes); if (cv == NULL) { return NGX_CONF_ERROR; } ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = cv; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } static char * ngx_http_v2_recv_buffer_size(ngx_conf_t *cf, void *post, void *data) { size_t *sp = data; if (*sp <= 2 * NGX_HTTP_V2_STATE_BUFFER_SIZE) { return "value is too small"; } return NGX_CONF_OK; } static char * ngx_http_v2_pool_size(ngx_conf_t *cf, void *post, void *data) { size_t *sp = data; if (*sp < NGX_MIN_POOL_SIZE) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the pool size must be no less than %uz", NGX_MIN_POOL_SIZE); return NGX_CONF_ERROR; } if (*sp % NGX_POOL_ALIGNMENT) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the pool size must be a multiple of %uz", NGX_POOL_ALIGNMENT); return NGX_CONF_ERROR; } return NGX_CONF_OK; } static char * ngx_http_v2_preread_size(ngx_conf_t *cf, void *post, void *data) { size_t *sp = data; if (*sp > NGX_HTTP_V2_MAX_WINDOW) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the maximum body preread buffer size is %uz", NGX_HTTP_V2_MAX_WINDOW); return NGX_CONF_ERROR; } return NGX_CONF_OK; } static char * ngx_http_v2_streams_index_mask(ngx_conf_t *cf, void *post, void *data) { ngx_uint_t *np = data; ngx_uint_t mask; mask = *np - 1; if (*np == 0 || (*np & mask)) { return "must be a power of two"; } *np = mask; return NGX_CONF_OK; } static char * ngx_http_v2_chunk_size(ngx_conf_t *cf, void *post, void *data) { size_t *sp = data; if (*sp == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the http2 chunk size cannot be zero"); return NGX_CONF_ERROR; } if (*sp > NGX_HTTP_V2_MAX_FRAME_SIZE) { *sp = NGX_HTTP_V2_MAX_FRAME_SIZE; } return NGX_CONF_OK; } static char * ngx_http_v2_obsolete(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_conf_deprecated_t *d = cmd->post; ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "the \"%s\" directive is obsolete, " "use the \"%s\" directive instead", d->old_name, d->new_name); return NGX_CONF_OK; } nginx-1.24.0/src/http/v2/ngx_http_v2_module.h000644 001751 001751 00000001756 14415135676 022211 0ustar00mdouninmdounin000000 000000 /* * 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; ngx_uint_t concurrent_pushes; size_t preread_size; ngx_uint_t streams_index_mask; } ngx_http_v2_srv_conf_t; typedef struct { size_t chunk_size; ngx_flag_t push_preload; ngx_flag_t push; ngx_array_t *pushes; } ngx_http_v2_loc_conf_t; extern ngx_module_t ngx_http_v2_module; #endif /* _NGX_HTTP_V2_MODULE_H_INCLUDED_ */ nginx-1.24.0/src/http/v2/ngx_http_v2_table.c000644 001751 001751 00000026661 14415135676 022010 0ustar00mdouninmdounin000000 000000 /* * 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_str_t * ngx_http_v2_get_static_name(ngx_uint_t index) { return &ngx_http_v2_static_table[index - 1].name; } ngx_str_t * ngx_http_v2_get_static_value(ngx_uint_t index) { return &ngx_http_v2_static_table[index - 1].value; } ngx_int_t ngx_http_v2_get_indexed_header(ngx_http_v2_connection_t *h2c, ngx_uint_t index, ngx_uint_t name_only) { u_char *p; size_t rest; ngx_http_v2_header_t *entry; if (index == 0) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent invalid hpack table index 0"); return NGX_ERROR; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 get indexed %s: %ui", name_only ? "name" : "header", index); index--; if (index < NGX_HTTP_V2_STATIC_TABLE_ENTRIES) { h2c->state.header = ngx_http_v2_static_table[index]; return NGX_OK; } index -= NGX_HTTP_V2_STATIC_TABLE_ENTRIES; if (index < h2c->hpack.added - h2c->hpack.deleted) { index = (h2c->hpack.added - index - 1) % h2c->hpack.allocated; entry = h2c->hpack.entries[index]; p = ngx_pnalloc(h2c->state.pool, entry->name.len + 1); if (p == NULL) { return NGX_ERROR; } h2c->state.header.name.len = entry->name.len; h2c->state.header.name.data = p; rest = h2c->hpack.storage + NGX_HTTP_V2_TABLE_SIZE - entry->name.data; if (entry->name.len > rest) { p = ngx_cpymem(p, entry->name.data, rest); p = ngx_cpymem(p, h2c->hpack.storage, entry->name.len - rest); } else { p = ngx_cpymem(p, entry->name.data, entry->name.len); } *p = '\0'; if (name_only) { return NGX_OK; } p = ngx_pnalloc(h2c->state.pool, entry->value.len + 1); if (p == NULL) { return NGX_ERROR; } h2c->state.header.value.len = entry->value.len; h2c->state.header.value.data = p; rest = h2c->hpack.storage + NGX_HTTP_V2_TABLE_SIZE - entry->value.data; if (entry->value.len > rest) { p = ngx_cpymem(p, entry->value.data, rest); p = ngx_cpymem(p, h2c->hpack.storage, entry->value.len - rest); } else { p = ngx_cpymem(p, entry->value.data, entry->value.len); } *p = '\0'; return NGX_OK; } ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent out of bound hpack table index: %ui", index); return NGX_ERROR; } ngx_int_t ngx_http_v2_add_header(ngx_http_v2_connection_t *h2c, ngx_http_v2_header_t *header) { size_t avail; ngx_uint_t index; ngx_http_v2_header_t *entry, **entries; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 table add: \"%V: %V\"", &header->name, &header->value); if (h2c->hpack.entries == NULL) { h2c->hpack.allocated = 64; h2c->hpack.size = NGX_HTTP_V2_TABLE_SIZE; h2c->hpack.free = NGX_HTTP_V2_TABLE_SIZE; h2c->hpack.entries = ngx_palloc(h2c->connection->pool, sizeof(ngx_http_v2_header_t *) * h2c->hpack.allocated); if (h2c->hpack.entries == NULL) { return NGX_ERROR; } h2c->hpack.storage = ngx_palloc(h2c->connection->pool, h2c->hpack.free); if (h2c->hpack.storage == NULL) { return NGX_ERROR; } h2c->hpack.pos = h2c->hpack.storage; } if (ngx_http_v2_table_account(h2c, header->name.len + header->value.len) != NGX_OK) { return NGX_OK; } if (h2c->hpack.reused == h2c->hpack.deleted) { entry = ngx_palloc(h2c->connection->pool, sizeof(ngx_http_v2_header_t)); if (entry == NULL) { return NGX_ERROR; } } else { entry = h2c->hpack.entries[h2c->hpack.reused++ % h2c->hpack.allocated]; } avail = h2c->hpack.storage + NGX_HTTP_V2_TABLE_SIZE - h2c->hpack.pos; entry->name.len = header->name.len; entry->name.data = h2c->hpack.pos; if (avail >= header->name.len) { h2c->hpack.pos = ngx_cpymem(h2c->hpack.pos, header->name.data, header->name.len); } else { ngx_memcpy(h2c->hpack.pos, header->name.data, avail); h2c->hpack.pos = ngx_cpymem(h2c->hpack.storage, header->name.data + avail, header->name.len - avail); avail = NGX_HTTP_V2_TABLE_SIZE; } avail -= header->name.len; entry->value.len = header->value.len; entry->value.data = h2c->hpack.pos; if (avail >= header->value.len) { h2c->hpack.pos = ngx_cpymem(h2c->hpack.pos, header->value.data, header->value.len); } else { ngx_memcpy(h2c->hpack.pos, header->value.data, avail); h2c->hpack.pos = ngx_cpymem(h2c->hpack.storage, header->value.data + avail, header->value.len - avail); } if (h2c->hpack.allocated == h2c->hpack.added - h2c->hpack.deleted) { entries = ngx_palloc(h2c->connection->pool, sizeof(ngx_http_v2_header_t *) * (h2c->hpack.allocated + 64)); if (entries == NULL) { return NGX_ERROR; } index = h2c->hpack.deleted % h2c->hpack.allocated; ngx_memcpy(entries, &h2c->hpack.entries[index], (h2c->hpack.allocated - index) * sizeof(ngx_http_v2_header_t *)); ngx_memcpy(&entries[h2c->hpack.allocated - index], h2c->hpack.entries, index * sizeof(ngx_http_v2_header_t *)); (void) ngx_pfree(h2c->connection->pool, h2c->hpack.entries); h2c->hpack.entries = entries; h2c->hpack.added = h2c->hpack.allocated; h2c->hpack.deleted = 0; h2c->hpack.reused = 0; h2c->hpack.allocated += 64; } h2c->hpack.entries[h2c->hpack.added++ % h2c->hpack.allocated] = entry; return NGX_OK; } static ngx_int_t ngx_http_v2_table_account(ngx_http_v2_connection_t *h2c, size_t size) { ngx_http_v2_header_t *entry; size += 32; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 table account: %uz free:%uz", size, h2c->hpack.free); if (size <= h2c->hpack.free) { h2c->hpack.free -= size; return NGX_OK; } if (size > h2c->hpack.size) { h2c->hpack.deleted = h2c->hpack.added; h2c->hpack.free = h2c->hpack.size; return NGX_DECLINED; } do { entry = h2c->hpack.entries[h2c->hpack.deleted++ % h2c->hpack.allocated]; h2c->hpack.free += 32 + entry->name.len + entry->value.len; } while (size > h2c->hpack.free); h2c->hpack.free -= size; return NGX_OK; } ngx_int_t ngx_http_v2_table_size(ngx_http_v2_connection_t *h2c, size_t size) { ssize_t needed; ngx_http_v2_header_t *entry; if (size > NGX_HTTP_V2_TABLE_SIZE) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent invalid table size update: %uz", size); return NGX_ERROR; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 new hpack table size: %uz was:%uz", size, h2c->hpack.size); needed = h2c->hpack.size - size; while (needed > (ssize_t) h2c->hpack.free) { entry = h2c->hpack.entries[h2c->hpack.deleted++ % h2c->hpack.allocated]; h2c->hpack.free += 32 + entry->name.len + entry->value.len; } h2c->hpack.size = size; h2c->hpack.free -= needed; return NGX_OK; } nginx-1.24.0/src/http/modules/ngx_http_access_module.c000644 001751 001751 00000027132 14415135676 024233 0ustar00mdouninmdounin000000 000000 /* * 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 all = 0; ngx_memzero(&cidr, sizeof(ngx_cidr_t)); value = cf->args->elts; if (value[1].len == 3 && ngx_strcmp(value[1].data, "all") == 0) { all = 1; #if (NGX_HAVE_UNIX_DOMAIN) } else if (value[1].len == 5 && ngx_strcmp(value[1].data, "unix:") == 0) { cidr.family = AF_UNIX; #endif } else { rc = ngx_ptocidr(&value[1], &cidr); if (rc == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[1]); return NGX_CONF_ERROR; } if (rc == NGX_DONE) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "low address bits of %V are meaningless", &value[1]); } } if (cidr.family == AF_INET || all) { if (alcf->rules == NULL) { alcf->rules = ngx_array_create(cf->pool, 4, sizeof(ngx_http_access_rule_t)); if (alcf->rules == NULL) { return NGX_CONF_ERROR; } } rule = ngx_array_push(alcf->rules); if (rule == NULL) { return NGX_CONF_ERROR; } rule->mask = cidr.u.in.mask; rule->addr = cidr.u.in.addr; rule->deny = (value[0].data[0] == 'd') ? 1 : 0; } #if (NGX_HAVE_INET6) if (cidr.family == AF_INET6 || all) { if (alcf->rules6 == NULL) { alcf->rules6 = ngx_array_create(cf->pool, 4, sizeof(ngx_http_access_rule6_t)); if (alcf->rules6 == NULL) { return NGX_CONF_ERROR; } } rule6 = ngx_array_push(alcf->rules6); if (rule6 == NULL) { return NGX_CONF_ERROR; } rule6->mask = cidr.u.in6.mask; rule6->addr = cidr.u.in6.addr; rule6->deny = (value[0].data[0] == 'd') ? 1 : 0; } #endif #if (NGX_HAVE_UNIX_DOMAIN) if (cidr.family == AF_UNIX || all) { if (alcf->rules_un == NULL) { alcf->rules_un = ngx_array_create(cf->pool, 1, sizeof(ngx_http_access_rule_un_t)); if (alcf->rules_un == NULL) { return NGX_CONF_ERROR; } } rule_un = ngx_array_push(alcf->rules_un); if (rule_un == NULL) { return NGX_CONF_ERROR; } rule_un->deny = (value[0].data[0] == 'd') ? 1 : 0; } #endif return NGX_CONF_OK; } static void * ngx_http_access_create_loc_conf(ngx_conf_t *cf) { ngx_http_access_loc_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_access_loc_conf_t)); if (conf == NULL) { return NULL; } return conf; } static char * ngx_http_access_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_access_loc_conf_t *prev = parent; ngx_http_access_loc_conf_t *conf = child; if (conf->rules == NULL #if (NGX_HAVE_INET6) && conf->rules6 == NULL #endif #if (NGX_HAVE_UNIX_DOMAIN) && conf->rules_un == NULL #endif ) { conf->rules = prev->rules; #if (NGX_HAVE_INET6) conf->rules6 = prev->rules6; #endif #if (NGX_HAVE_UNIX_DOMAIN) conf->rules_un = prev->rules_un; #endif } return NGX_CONF_OK; } static ngx_int_t ngx_http_access_init(ngx_conf_t *cf) { ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } *h = ngx_http_access_handler; return NGX_OK; } nginx-1.24.0/src/http/modules/ngx_http_addition_filter_module.c000644 001751 001751 00000015522 14415135676 026132 0ustar00mdouninmdounin000000 000000 /* * 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); r->preserve_body = 1; return ngx_http_next_header_filter(r); } static ngx_int_t ngx_http_addition_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_int_t rc; ngx_uint_t last; ngx_chain_t *cl; ngx_http_request_t *sr; ngx_http_addition_ctx_t *ctx; ngx_http_addition_conf_t *conf; if (in == NULL || r->header_only) { return ngx_http_next_body_filter(r, in); } ctx = ngx_http_get_module_ctx(r, ngx_http_addition_filter_module); if (ctx == NULL) { return ngx_http_next_body_filter(r, in); } conf = ngx_http_get_module_loc_conf(r, ngx_http_addition_filter_module); if (!ctx->before_body_sent) { ctx->before_body_sent = 1; if (conf->before_body.len) { if (ngx_http_subrequest(r, &conf->before_body, NULL, &sr, NULL, 0) != NGX_OK) { return NGX_ERROR; } } } if (conf->after_body.len == 0) { ngx_http_set_ctx(r, NULL, ngx_http_addition_filter_module); return ngx_http_next_body_filter(r, in); } last = 0; for (cl = in; cl; cl = cl->next) { if (cl->buf->last_buf) { cl->buf->last_buf = 0; cl->buf->last_in_chain = 1; cl->buf->sync = 1; last = 1; } } rc = ngx_http_next_body_filter(r, in); if (rc == NGX_ERROR || !last || conf->after_body.len == 0) { return rc; } if (ngx_http_subrequest(r, &conf->after_body, NULL, &sr, NULL, 0) != NGX_OK) { return NGX_ERROR; } ngx_http_set_ctx(r, NULL, ngx_http_addition_filter_module); return ngx_http_send_special(r, NGX_HTTP_LAST); } static ngx_int_t ngx_http_addition_filter_init(ngx_conf_t *cf) { ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_addition_header_filter; ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_addition_body_filter; return NGX_OK; } static void * ngx_http_addition_create_conf(ngx_conf_t *cf) { ngx_http_addition_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_addition_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->before_body = { 0, NULL }; * conf->after_body = { 0, NULL }; * conf->types = { NULL }; * conf->types_keys = NULL; */ return conf; } static char * ngx_http_addition_merge_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_addition_conf_t *prev = parent; ngx_http_addition_conf_t *conf = child; ngx_conf_merge_str_value(conf->before_body, prev->before_body, ""); ngx_conf_merge_str_value(conf->after_body, prev->after_body, ""); if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types, &prev->types_keys, &prev->types, ngx_http_html_default_types) != NGX_OK) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } nginx-1.24.0/src/http/modules/ngx_http_auth_basic_module.c000644 001751 001751 00000027020 14415135676 025070 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ #include #include #include #include #define NGX_HTTP_AUTH_BUF_SIZE 2048 typedef struct { ngx_http_complex_value_t *realm; ngx_http_complex_value_t *user_file; } ngx_http_auth_basic_loc_conf_t; static ngx_int_t ngx_http_auth_basic_handler(ngx_http_request_t *r); static ngx_int_t ngx_http_auth_basic_crypt_handler(ngx_http_request_t *r, ngx_str_t *passwd, ngx_str_t *realm); static ngx_int_t ngx_http_auth_basic_set_realm(ngx_http_request_t *r, ngx_str_t *realm); static void *ngx_http_auth_basic_create_loc_conf(ngx_conf_t *cf); static char *ngx_http_auth_basic_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); static ngx_int_t ngx_http_auth_basic_init(ngx_conf_t *cf); static char *ngx_http_auth_basic_user_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_command_t ngx_http_auth_basic_commands[] = { { ngx_string("auth_basic"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF |NGX_CONF_TAKE1, ngx_http_set_complex_value_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_auth_basic_loc_conf_t, realm), NULL }, { ngx_string("auth_basic_user_file"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF |NGX_CONF_TAKE1, ngx_http_auth_basic_user_file, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_auth_basic_loc_conf_t, user_file), NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_auth_basic_module_ctx = { NULL, /* preconfiguration */ ngx_http_auth_basic_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_auth_basic_create_loc_conf, /* create location configuration */ ngx_http_auth_basic_merge_loc_conf /* merge location configuration */ }; ngx_module_t ngx_http_auth_basic_module = { NGX_MODULE_V1, &ngx_http_auth_basic_module_ctx, /* module context */ ngx_http_auth_basic_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_int_t ngx_http_auth_basic_handler(ngx_http_request_t *r) { off_t offset; ssize_t n; ngx_fd_t fd; ngx_int_t rc; ngx_err_t err; ngx_str_t pwd, realm, user_file; ngx_uint_t i, level, login, left, passwd; ngx_file_t file; ngx_http_auth_basic_loc_conf_t *alcf; u_char buf[NGX_HTTP_AUTH_BUF_SIZE]; enum { sw_login, sw_passwd, sw_skip } state; alcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_basic_module); if (alcf->realm == NULL || alcf->user_file == NULL) { return NGX_DECLINED; } if (ngx_http_complex_value(r, alcf->realm, &realm) != NGX_OK) { return NGX_ERROR; } if (realm.len == 3 && ngx_strncmp(realm.data, "off", 3) == 0) { return NGX_DECLINED; } rc = ngx_http_auth_basic_user(r); if (rc == NGX_DECLINED) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "no user/password was provided for basic authentication"); return ngx_http_auth_basic_set_realm(r, &realm); } if (rc == NGX_ERROR) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_http_complex_value(r, alcf->user_file, &user_file) != NGX_OK) { return NGX_ERROR; } fd = ngx_open_file(user_file.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); if (fd == NGX_INVALID_FILE) { err = ngx_errno; if (err == NGX_ENOENT) { level = NGX_LOG_ERR; rc = NGX_HTTP_FORBIDDEN; } else { level = NGX_LOG_CRIT; rc = NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_log_error(level, r->connection->log, err, ngx_open_file_n " \"%s\" failed", user_file.data); return rc; } ngx_memzero(&file, sizeof(ngx_file_t)); file.fd = fd; file.name = user_file; file.log = r->connection->log; state = sw_login; passwd = 0; login = 0; left = 0; offset = 0; for ( ;; ) { i = left; n = ngx_read_file(&file, buf + left, NGX_HTTP_AUTH_BUF_SIZE - left, offset); if (n == NGX_ERROR) { rc = NGX_HTTP_INTERNAL_SERVER_ERROR; goto cleanup; } 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'; pwd.len = i - passwd; pwd.data = &buf[passwd]; rc = ngx_http_auth_basic_crypt_handler(r, &pwd, &realm); goto cleanup; } 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; } 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); rc = ngx_http_auth_basic_crypt_handler(r, &pwd, &realm); goto cleanup; } ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "user \"%V\" was not found in \"%s\"", &r->headers_in.user, user_file.data); rc = ngx_http_auth_basic_set_realm(r, &realm); cleanup: if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno, ngx_close_file_n " \"%s\" failed", user_file.data); } ngx_explicit_memzero(buf, NGX_HTTP_AUTH_BUF_SIZE); return rc; } static ngx_int_t ngx_http_auth_basic_crypt_handler(ngx_http_request_t *r, ngx_str_t *passwd, ngx_str_t *realm) { ngx_int_t rc; u_char *encrypted; rc = ngx_crypt(r->pool, r->headers_in.passwd.data, passwd->data, &encrypted); ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "rc: %i user: \"%V\" salt: \"%s\"", rc, &r->headers_in.user, passwd->data); if (rc != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_strcmp(encrypted, passwd->data) == 0) { return NGX_OK; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "encrypted: \"%s\"", encrypted); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "user \"%V\": password mismatch", &r->headers_in.user); return ngx_http_auth_basic_set_realm(r, realm); } static ngx_int_t ngx_http_auth_basic_set_realm(ngx_http_request_t *r, ngx_str_t *realm) { size_t len; u_char *basic, *p; r->headers_out.www_authenticate = ngx_list_push(&r->headers_out.headers); if (r->headers_out.www_authenticate == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } len = sizeof("Basic realm=\"\"") - 1 + realm->len; basic = ngx_pnalloc(r->pool, len); if (basic == NULL) { r->headers_out.www_authenticate->hash = 0; r->headers_out.www_authenticate = NULL; return NGX_HTTP_INTERNAL_SERVER_ERROR; } p = ngx_cpymem(basic, "Basic realm=\"", sizeof("Basic realm=\"") - 1); p = ngx_cpymem(p, realm->data, realm->len); *p = '"'; r->headers_out.www_authenticate->hash = 1; r->headers_out.www_authenticate->next = NULL; 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_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; } conf->realm = NGX_CONF_UNSET_PTR; conf->user_file = NGX_CONF_UNSET_PTR; 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; ngx_conf_merge_ptr_value(conf->realm, prev->realm, NULL); ngx_conf_merge_ptr_value(conf->user_file, prev->user_file, NULL); return NGX_CONF_OK; } 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 != NGX_CONF_UNSET_PTR) { return "is duplicate"; } alcf->user_file = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); if (alcf->user_file == NULL) { return NGX_CONF_ERROR; } value = cf->args->elts; ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = alcf->user_file; ccv.zero = 1; ccv.conf_prefix = 1; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } nginx-1.24.0/src/http/modules/ngx_http_auth_request_module.c000644 001751 001751 00000026605 14415135676 025507 0ustar00mdouninmdounin000000 000000 /* * 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, **ph; 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; } ph = &r->headers_out.www_authenticate; while (h) { ho = ngx_list_push(&r->headers_out.headers); if (ho == NULL) { return NGX_ERROR; } *ho = *h; ho->next = NULL; *ph = ho; ph = &ho->next; h = h->next; } 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; } nginx-1.24.0/src/http/modules/ngx_http_autoindex_module.c000644 001751 001751 00000074610 14415135676 024775 0ustar00mdouninmdounin000000 000000 /* * 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; } rc = ngx_http_discard_request_body(r); if (rc != NGX_OK) { return rc; } 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, entry_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; } entry_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; if (len > NGX_MAX_SIZE_T_VALUE - entry_len) { return NULL; } len += entry_len; } 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, entry_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); entry_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) { entry_len += sizeof(", \"size\":") - 1 + NGX_OFF_T_LEN; } if (len > NGX_MAX_SIZE_T_VALUE - entry_len) { return NULL; } len += entry_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, entry_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); entry_len = sizeof("" CRLF) - 1 + entry[i].name.len + entry[i].escape + sizeof(" mtime=\"1986-12-31T10:00:00Z\"") - 1; if (entry[i].file) { entry_len += sizeof(" size=\"\"") - 1 + NGX_OFF_T_LEN; } if (len > NGX_MAX_SIZE_T_VALUE - entry_len) { return NULL; } len += entry_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; } nginx-1.24.0/src/http/modules/ngx_http_browser_module.c000644 001751 001751 00000046721 14415135676 024462 0ustar00mdouninmdounin000000 000000 /* * 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_array_t *modern_browsers; ngx_array_t *ancient_browsers; ngx_http_variable_value_t *modern_browser_value; ngx_http_variable_value_t *ancient_browser_value; unsigned modern_unlisted_browsers:1; unsigned netscape4:1; } ngx_http_browser_conf_t; static ngx_int_t ngx_http_msie_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_browser_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_uint_t ngx_http_browser(ngx_http_request_t *r, ngx_http_browser_conf_t *cf); static ngx_int_t ngx_http_browser_add_variables(ngx_conf_t *cf); static void *ngx_http_browser_create_conf(ngx_conf_t *cf); static char *ngx_http_browser_merge_conf(ngx_conf_t *cf, void *parent, void *child); static int ngx_libc_cdecl ngx_http_modern_browser_sort(const void *one, const void *two); static char *ngx_http_modern_browser(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_ancient_browser(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_modern_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_ancient_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_command_t ngx_http_browser_commands[] = { { ngx_string("modern_browser"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12, ngx_http_modern_browser, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("ancient_browser"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_ancient_browser, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("modern_browser_value"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_modern_browser_value, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("ancient_browser_value"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_ancient_browser_value, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_browser_module_ctx = { ngx_http_browser_add_variables, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_browser_create_conf, /* create location configuration */ ngx_http_browser_merge_conf /* merge location configuration */ }; ngx_module_t ngx_http_browser_module = { NGX_MODULE_V1, &ngx_http_browser_module_ctx, /* module context */ ngx_http_browser_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_modern_browser_mask_t ngx_http_modern_browser_masks[] = { /* Opera must be the first browser to check */ /* * "Opera/7.50 (X11; FreeBSD i386; U) [en]" * "Mozilla/5.0 (X11; FreeBSD i386; U) Opera 7.50 [en]" * "Mozilla/4.0 (compatible; MSIE 6.0; X11; FreeBSD i386) Opera 7.50 [en]" * "Opera/8.0 (Windows NT 5.1; U; ru)" * "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; en) Opera 8.0" * "Opera/9.01 (X11; FreeBSD 6 i386; U; en)" */ { "opera", 0, sizeof("Opera ") - 1, "Opera"}, /* "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)" */ { "msie", sizeof("Mozilla/4.0 (compatible; ") - 1, sizeof("MSIE ") - 1, "MSIE "}, /* * "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.0.0) Gecko/20020610" * "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU; rv:1.5) Gecko/20031006" * "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU; rv:1.6) Gecko/20040206 * Firefox/0.8" * "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU; rv:1.7.8) * Gecko/20050511 Firefox/1.0.4" * "Mozilla/5.0 (X11; U; FreeBSD i386; en-US; rv:1.8.0.5) Gecko/20060729 * Firefox/1.5.0.5" */ { "gecko", sizeof("Mozilla/5.0 (") - 1, sizeof("rv:") - 1, "rv:"}, /* * "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ru-ru) AppleWebKit/125.2 * (KHTML, like Gecko) Safari/125.7" * "Mozilla/5.0 (SymbianOS/9.1; U; en-us) AppleWebKit/413 * (KHTML, like Gecko) Safari/413" * "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418 * (KHTML, like Gecko) Safari/417.9.3" * "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ru-ru) AppleWebKit/418.8 * (KHTML, like Gecko) Safari/419.3" */ { "safari", sizeof("Mozilla/5.0 (") - 1, sizeof("Safari/") - 1, "Safari/"}, /* * "Mozilla/5.0 (compatible; Konqueror/3.1; Linux)" * "Mozilla/5.0 (compatible; Konqueror/3.4; Linux) KHTML/3.4.2 (like Gecko)" * "Mozilla/5.0 (compatible; Konqueror/3.5; FreeBSD) KHTML/3.5.1 * (like Gecko)" */ { "konqueror", sizeof("Mozilla/5.0 (compatible; ") - 1, sizeof("Konqueror/") - 1, "Konqueror/"}, { "", 0, 0, "" } }; static ngx_http_variable_t ngx_http_browser_vars[] = { { ngx_string("msie"), NULL, ngx_http_msie_variable, 0, NGX_HTTP_VAR_CHANGEABLE, 0 }, { ngx_string("modern_browser"), NULL, ngx_http_browser_variable, NGX_HTTP_MODERN_BROWSER, NGX_HTTP_VAR_CHANGEABLE, 0 }, { ngx_string("ancient_browser"), NULL, ngx_http_browser_variable, NGX_HTTP_ANCIENT_BROWSER, NGX_HTTP_VAR_CHANGEABLE, 0 }, ngx_http_null_variable }; static ngx_int_t ngx_http_browser_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_uint_t rc; ngx_http_browser_conf_t *cf; cf = ngx_http_get_module_loc_conf(r, ngx_http_browser_module); rc = ngx_http_browser(r, cf); if (data == NGX_HTTP_MODERN_BROWSER && rc == NGX_HTTP_MODERN_BROWSER) { *v = *cf->modern_browser_value; return NGX_OK; } if (data == NGX_HTTP_ANCIENT_BROWSER && rc == NGX_HTTP_ANCIENT_BROWSER) { *v = *cf->ancient_browser_value; return NGX_OK; } *v = ngx_http_variable_null_value; return NGX_OK; } static ngx_uint_t ngx_http_browser(ngx_http_request_t *r, ngx_http_browser_conf_t *cf) { size_t len; u_char *name, *ua, *last, c; ngx_str_t *ancient; ngx_uint_t i, version, ver, scale; ngx_http_modern_browser_t *modern; if (r->headers_in.user_agent == NULL) { if (cf->modern_unlisted_browsers) { return NGX_HTTP_MODERN_BROWSER; } return NGX_HTTP_ANCIENT_BROWSER; } ua = r->headers_in.user_agent->value.data; len = r->headers_in.user_agent->value.len; last = ua + len; if (cf->modern_browsers) { modern = cf->modern_browsers->elts; for (i = 0; i < cf->modern_browsers->nelts; i++) { name = ua + modern[i].skip; if (name >= last) { continue; } name = (u_char *) ngx_strstr(name, modern[i].name); if (name == NULL) { continue; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "browser: \"%s\"", name); name += modern[i].add; if (name >= last) { continue; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "version: \"%ui\" \"%s\"", modern[i].version, name); version = 0; ver = 0; scale = 1000000; while (name < last) { c = *name++; if (c >= '0' && c <= '9') { ver = ver * 10 + (c - '0'); continue; } if (c == '.') { version += ver * scale; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "version: \"%ui\" \"%ui\"", modern[i].version, version); if (version > modern[i].version) { return NGX_HTTP_MODERN_BROWSER; } ver = 0; scale /= 100; continue; } break; } version += ver * scale; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "version: \"%ui\" \"%ui\"", modern[i].version, version); if (version >= modern[i].version) { return NGX_HTTP_MODERN_BROWSER; } return NGX_HTTP_ANCIENT_BROWSER; } if (!cf->modern_unlisted_browsers) { return NGX_HTTP_ANCIENT_BROWSER; } } if (cf->netscape4) { if (len > sizeof("Mozilla/4.72 ") - 1 && ngx_strncmp(ua, "Mozilla/", sizeof("Mozilla/") - 1) == 0 && ua[8] > '0' && ua[8] < '5') { return NGX_HTTP_ANCIENT_BROWSER; } } if (cf->ancient_browsers) { ancient = cf->ancient_browsers->elts; for (i = 0; i < cf->ancient_browsers->nelts; i++) { if (len >= ancient[i].len && ngx_strstr(ua, ancient[i].data) != NULL) { return NGX_HTTP_ANCIENT_BROWSER; } } } if (cf->modern_unlisted_browsers) { return NGX_HTTP_MODERN_BROWSER; } return NGX_HTTP_ANCIENT_BROWSER; } static ngx_int_t ngx_http_msie_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { if (r->headers_in.msie) { *v = ngx_http_variable_true_value; return NGX_OK; } *v = ngx_http_variable_null_value; return NGX_OK; } static ngx_int_t ngx_http_browser_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var, *v; for (v = ngx_http_browser_vars; v->name.len; v++) { var = ngx_http_add_variable(cf, &v->name, v->flags); if (var == NULL) { return NGX_ERROR; } var->get_handler = v->get_handler; var->data = v->data; } return NGX_OK; } static void * ngx_http_browser_create_conf(ngx_conf_t *cf) { ngx_http_browser_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_browser_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->modern_browsers = NULL; * conf->ancient_browsers = NULL; * conf->modern_browser_value = NULL; * conf->ancient_browser_value = NULL; * * conf->modern_unlisted_browsers = 0; * conf->netscape4 = 0; */ return conf; } static char * ngx_http_browser_merge_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_browser_conf_t *prev = parent; ngx_http_browser_conf_t *conf = child; ngx_uint_t i, n; ngx_http_modern_browser_t *browsers, *opera; /* * At the merge the skip field is used to store the browser slot, * it will be used in sorting and then will overwritten * with a real skip value. The zero value means Opera. */ if (conf->modern_browsers == NULL && conf->modern_unlisted_browsers == 0) { conf->modern_browsers = prev->modern_browsers; conf->modern_unlisted_browsers = prev->modern_unlisted_browsers; } else if (conf->modern_browsers != NULL) { browsers = conf->modern_browsers->elts; for (i = 0; i < conf->modern_browsers->nelts; i++) { if (browsers[i].skip == 0) { goto found; } } /* * Opera may contain MSIE string, so if Opera was not enumerated * as modern browsers, then add it and set a unreachable version */ opera = ngx_array_push(conf->modern_browsers); if (opera == NULL) { return NGX_CONF_ERROR; } opera->skip = 0; opera->version = 4001000000U; browsers = conf->modern_browsers->elts; found: ngx_qsort(browsers, (size_t) conf->modern_browsers->nelts, sizeof(ngx_http_modern_browser_t), ngx_http_modern_browser_sort); for (i = 0; i < conf->modern_browsers->nelts; i++) { n = browsers[i].skip; browsers[i].skip = ngx_http_modern_browser_masks[n].skip; browsers[i].add = ngx_http_modern_browser_masks[n].add; (void) ngx_cpystrn(browsers[i].name, ngx_http_modern_browser_masks[n].name, 12); } } if (conf->ancient_browsers == NULL && conf->netscape4 == 0) { conf->ancient_browsers = prev->ancient_browsers; conf->netscape4 = prev->netscape4; } if (conf->modern_browser_value == NULL) { conf->modern_browser_value = prev->modern_browser_value; } if (conf->modern_browser_value == NULL) { conf->modern_browser_value = &ngx_http_variable_true_value; } if (conf->ancient_browser_value == NULL) { conf->ancient_browser_value = prev->ancient_browser_value; } if (conf->ancient_browser_value == NULL) { conf->ancient_browser_value = &ngx_http_variable_true_value; } return NGX_CONF_OK; } static int ngx_libc_cdecl ngx_http_modern_browser_sort(const void *one, const void *two) { ngx_http_modern_browser_t *first = (ngx_http_modern_browser_t *) one; ngx_http_modern_browser_t *second = (ngx_http_modern_browser_t *) two; return (first->skip - second->skip); } static char * ngx_http_modern_browser(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_browser_conf_t *bcf = conf; u_char c; ngx_str_t *value; ngx_uint_t i, n, version, ver, scale; ngx_http_modern_browser_t *browser; ngx_http_modern_browser_mask_t *mask; value = cf->args->elts; if (cf->args->nelts == 2) { if (ngx_strcmp(value[1].data, "unlisted") == 0) { bcf->modern_unlisted_browsers = 1; return NGX_CONF_OK; } return NGX_CONF_ERROR; } if (bcf->modern_browsers == NULL) { bcf->modern_browsers = ngx_array_create(cf->pool, 5, sizeof(ngx_http_modern_browser_t)); if (bcf->modern_browsers == NULL) { return NGX_CONF_ERROR; } } browser = ngx_array_push(bcf->modern_browsers); if (browser == NULL) { return NGX_CONF_ERROR; } mask = ngx_http_modern_browser_masks; for (n = 0; mask[n].browser[0] != '\0'; n++) { if (ngx_strcasecmp(mask[n].browser, value[1].data) == 0) { goto found; } } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unknown browser name \"%V\"", &value[1]); return NGX_CONF_ERROR; found: /* * at this stage the skip field is used to store the browser slot, * it will be used in sorting in merge stage and then will overwritten * with a real value */ browser->skip = n; version = 0; ver = 0; scale = 1000000; for (i = 0; i < value[2].len; i++) { c = value[2].data[i]; if (c >= '0' && c <= '9') { ver = ver * 10 + (c - '0'); continue; } if (c == '.') { version += ver * scale; ver = 0; scale /= 100; continue; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid browser version \"%V\"", &value[2]); return NGX_CONF_ERROR; } version += ver * scale; browser->version = version; return NGX_CONF_OK; } static char * ngx_http_ancient_browser(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_browser_conf_t *bcf = conf; ngx_str_t *value, *browser; ngx_uint_t i; value = cf->args->elts; for (i = 1; i < cf->args->nelts; i++) { if (ngx_strcmp(value[i].data, "netscape4") == 0) { bcf->netscape4 = 1; continue; } if (bcf->ancient_browsers == NULL) { bcf->ancient_browsers = ngx_array_create(cf->pool, 4, sizeof(ngx_str_t)); if (bcf->ancient_browsers == NULL) { return NGX_CONF_ERROR; } } browser = ngx_array_push(bcf->ancient_browsers); if (browser == NULL) { return NGX_CONF_ERROR; } *browser = value[i]; } return NGX_CONF_OK; } static char * ngx_http_modern_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_browser_conf_t *bcf = conf; ngx_str_t *value; bcf->modern_browser_value = ngx_palloc(cf->pool, sizeof(ngx_http_variable_value_t)); if (bcf->modern_browser_value == NULL) { return NGX_CONF_ERROR; } value = cf->args->elts; bcf->modern_browser_value->len = value[1].len; bcf->modern_browser_value->valid = 1; bcf->modern_browser_value->no_cacheable = 0; bcf->modern_browser_value->not_found = 0; bcf->modern_browser_value->data = value[1].data; return NGX_CONF_OK; } static char * ngx_http_ancient_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_browser_conf_t *bcf = conf; ngx_str_t *value; bcf->ancient_browser_value = ngx_palloc(cf->pool, sizeof(ngx_http_variable_value_t)); if (bcf->ancient_browser_value == NULL) { return NGX_CONF_ERROR; } value = cf->args->elts; bcf->ancient_browser_value->len = value[1].len; bcf->ancient_browser_value->valid = 1; bcf->ancient_browser_value->no_cacheable = 0; bcf->ancient_browser_value->not_found = 0; bcf->ancient_browser_value->data = value[1].data; return NGX_CONF_OK; } nginx-1.24.0/src/http/modules/ngx_http_charset_filter_module.c000644 001751 001751 00000120166 14415135676 025771 0ustar00mdouninmdounin000000 000000 /* * 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); static ngx_str_t ngx_http_charset_default_types[] = { ngx_string("text/html"), ngx_string("text/xml"), ngx_string("text/plain"), ngx_string("text/vnd.wap.wml"), ngx_string("application/javascript"), ngx_string("application/rss+xml"), ngx_null_string }; static ngx_command_t ngx_http_charset_filter_commands[] = { { ngx_string("charset"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF |NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, ngx_http_set_charset_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_charset_loc_conf_t, charset), NULL }, { ngx_string("source_charset"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF |NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, ngx_http_set_charset_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_charset_loc_conf_t, source_charset), NULL }, { ngx_string("override_charset"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF |NGX_HTTP_LIF_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_charset_loc_conf_t, override_charset), NULL }, { ngx_string("charset_types"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_types_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_charset_loc_conf_t, types_keys), &ngx_http_charset_default_types[0] }, { ngx_string("charset_map"), NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE2, ngx_http_charset_map_block, NGX_HTTP_MAIN_CONF_OFFSET, 0, NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_charset_filter_module_ctx = { NULL, /* preconfiguration */ ngx_http_charset_postconfiguration, /* postconfiguration */ ngx_http_charset_create_main_conf, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_charset_create_loc_conf, /* create location configuration */ ngx_http_charset_merge_loc_conf /* merge location configuration */ }; ngx_module_t ngx_http_charset_filter_module = { NGX_MODULE_V1, &ngx_http_charset_filter_module_ctx, /* module context */ ngx_http_charset_filter_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter; static ngx_int_t ngx_http_charset_header_filter(ngx_http_request_t *r) { ngx_int_t charset, source_charset; ngx_str_t dst, src; ngx_http_charset_t *charsets; ngx_http_charset_main_conf_t *mcf; if (r == r->main) { charset = ngx_http_destination_charset(r, &dst); } else { charset = ngx_http_main_request_charset(r, &dst); } if (charset == NGX_ERROR) { return NGX_ERROR; } if (charset == NGX_DECLINED) { return ngx_http_next_header_filter(r); } /* charset: charset index or NGX_HTTP_NO_CHARSET */ source_charset = ngx_http_source_charset(r, &src); if (source_charset == NGX_ERROR) { return NGX_ERROR; } /* * source_charset: charset index, NGX_HTTP_NO_CHARSET, * or NGX_HTTP_CHARSET_OFF */ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "charset: \"%V\" > \"%V\"", &src, &dst); if (source_charset == NGX_HTTP_CHARSET_OFF) { ngx_http_set_charset(r, &dst); return ngx_http_next_header_filter(r); } if (charset == NGX_HTTP_NO_CHARSET || source_charset == NGX_HTTP_NO_CHARSET) { if (source_charset != charset || ngx_strncasecmp(dst.data, src.data, dst.len) != 0) { goto no_charset_map; } ngx_http_set_charset(r, &dst); return ngx_http_next_header_filter(r); } if (source_charset == charset) { r->headers_out.content_type.len = r->headers_out.content_type_len; ngx_http_set_charset(r, &dst); return ngx_http_next_header_filter(r); } /* source_charset != charset */ if (r->headers_out.content_encoding && r->headers_out.content_encoding->value.len) { return ngx_http_next_header_filter(r); } mcf = ngx_http_get_module_main_conf(r, ngx_http_charset_filter_module); charsets = mcf->charsets.elts; if (charsets[source_charset].tables == NULL || charsets[source_charset].tables[charset] == NULL) { goto no_charset_map; } r->headers_out.content_type.len = r->headers_out.content_type_len; ngx_http_set_charset(r, &dst); return ngx_http_charset_ctx(r, charsets, charset, source_charset); no_charset_map: ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no \"charset_map\" between the charsets \"%V\" and \"%V\"", &src, &dst); return ngx_http_next_header_filter(r); } static ngx_int_t ngx_http_destination_charset(ngx_http_request_t *r, ngx_str_t *name) { ngx_int_t charset; ngx_http_charset_t *charsets; ngx_http_variable_value_t *vv; ngx_http_charset_loc_conf_t *mlcf; ngx_http_charset_main_conf_t *mcf; if (r->headers_out.content_type.len == 0) { return NGX_DECLINED; } if (r->headers_out.override_charset && r->headers_out.override_charset->len) { *name = *r->headers_out.override_charset; charset = ngx_http_get_charset(r, name); if (charset != NGX_HTTP_NO_CHARSET) { return charset; } ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "unknown charset \"%V\" to override", name); return NGX_DECLINED; } mlcf = ngx_http_get_module_loc_conf(r, ngx_http_charset_filter_module); charset = mlcf->charset; if (charset == NGX_HTTP_CHARSET_OFF) { return NGX_DECLINED; } if (r->headers_out.charset.len) { if (mlcf->override_charset == 0) { return NGX_DECLINED; } } else { if (ngx_http_test_content_type(r, &mlcf->types) == NULL) { return NGX_DECLINED; } } if (charset < NGX_HTTP_CHARSET_VAR) { mcf = ngx_http_get_module_main_conf(r, ngx_http_charset_filter_module); charsets = mcf->charsets.elts; *name = charsets[charset].name; return charset; } vv = ngx_http_get_indexed_variable(r, charset - NGX_HTTP_CHARSET_VAR); if (vv == NULL || vv->not_found) { return NGX_ERROR; } name->len = vv->len; name->data = vv->data; return ngx_http_get_charset(r, name); } static ngx_int_t ngx_http_main_request_charset(ngx_http_request_t *r, ngx_str_t *src) { ngx_int_t charset; ngx_str_t *main_charset; ngx_http_charset_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r->main, ngx_http_charset_filter_module); if (ctx) { *src = ctx->charset_name; return ctx->charset; } main_charset = &r->main->headers_out.charset; if (main_charset->len == 0) { return NGX_DECLINED; } ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_charset_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_http_set_ctx(r->main, ctx, ngx_http_charset_filter_module); charset = ngx_http_get_charset(r, main_charset); ctx->charset = charset; ctx->charset_name = *main_charset; *src = *main_charset; return charset; } static ngx_int_t ngx_http_source_charset(ngx_http_request_t *r, ngx_str_t *name) { ngx_int_t charset; ngx_http_charset_t *charsets; ngx_http_variable_value_t *vv; ngx_http_charset_loc_conf_t *lcf; ngx_http_charset_main_conf_t *mcf; if (r->headers_out.charset.len) { *name = r->headers_out.charset; return ngx_http_get_charset(r, name); } lcf = ngx_http_get_module_loc_conf(r, ngx_http_charset_filter_module); charset = lcf->source_charset; if (charset == NGX_HTTP_CHARSET_OFF) { name->len = 0; return charset; } if (charset < NGX_HTTP_CHARSET_VAR) { mcf = ngx_http_get_module_main_conf(r, ngx_http_charset_filter_module); charsets = mcf->charsets.elts; *name = charsets[charset].name; return charset; } vv = ngx_http_get_indexed_variable(r, charset - NGX_HTTP_CHARSET_VAR); if (vv == NULL || vv->not_found) { return NGX_ERROR; } name->len = vv->len; name->data = vv->data; return ngx_http_get_charset(r, name); } static ngx_int_t ngx_http_get_charset(ngx_http_request_t *r, ngx_str_t *name) { ngx_uint_t i, n; ngx_http_charset_t *charset; ngx_http_charset_main_conf_t *mcf; mcf = ngx_http_get_module_main_conf(r, ngx_http_charset_filter_module); charset = mcf->charsets.elts; n = mcf->charsets.nelts; for (i = 0; i < n; i++) { if (charset[i].name.len != name->len) { continue; } if (ngx_strncasecmp(charset[i].name.data, name->data, name->len) == 0) { return i; } } return NGX_HTTP_NO_CHARSET; } static ngx_inline void ngx_http_set_charset(ngx_http_request_t *r, ngx_str_t *charset) { if (r != r->main) { return; } if (r->headers_out.status == NGX_HTTP_MOVED_PERMANENTLY || r->headers_out.status == NGX_HTTP_MOVED_TEMPORARILY) { /* * do not set charset for the redirect because NN 4.x * use this charset instead of the next page charset */ r->headers_out.charset.len = 0; return; } r->headers_out.charset = *charset; } static ngx_int_t ngx_http_charset_ctx(ngx_http_request_t *r, ngx_http_charset_t *charsets, ngx_int_t charset, ngx_int_t source_charset) { ngx_http_charset_ctx_t *ctx; ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_charset_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_charset_filter_module); ctx->table = charsets[source_charset].tables[charset]; ctx->charset = charset; ctx->charset_name = charsets[charset].name; ctx->length = charsets[charset].length; ctx->from_utf8 = charsets[source_charset].utf8; ctx->to_utf8 = charsets[charset].utf8; r->filter_need_in_memory = 1; if ((ctx->to_utf8 || ctx->from_utf8) && r == r->main) { ngx_http_clear_content_length(r); } else { r->filter_need_temporary = 1; } return ngx_http_next_header_filter(r); } static ngx_int_t ngx_http_charset_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_int_t rc; ngx_buf_t *b; ngx_chain_t *cl, *out, **ll; ngx_http_charset_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_charset_filter_module); if (ctx == NULL || ctx->table == NULL) { return ngx_http_next_body_filter(r, in); } if ((ctx->to_utf8 || ctx->from_utf8) || ctx->busy) { out = NULL; ll = &out; for (cl = in; cl; cl = cl->next) { b = cl->buf; if (ngx_buf_size(b) == 0) { *ll = ngx_alloc_chain_link(r->pool); if (*ll == NULL) { return NGX_ERROR; } (*ll)->buf = b; (*ll)->next = NULL; ll = &(*ll)->next; continue; } if (ctx->to_utf8) { *ll = ngx_http_charset_recode_to_utf8(r->pool, b, ctx); } else { *ll = ngx_http_charset_recode_from_utf8(r->pool, b, ctx); } if (*ll == NULL) { return NGX_ERROR; } while (*ll) { ll = &(*ll)->next; } } rc = ngx_http_next_body_filter(r, out); if (out) { if (ctx->busy == NULL) { ctx->busy = out; } else { for (cl = ctx->busy; cl->next; cl = cl->next) { /* void */ } cl->next = out; } } while (ctx->busy) { cl = ctx->busy; b = cl->buf; if (ngx_buf_size(b) != 0) { break; } ctx->busy = cl->next; if (b->tag != (ngx_buf_tag_t) &ngx_http_charset_filter_module) { continue; } if (b->shadow) { b->shadow->pos = b->shadow->last; } if (b->pos) { cl->next = ctx->free_buffers; ctx->free_buffers = cl; continue; } cl->next = ctx->free_bufs; ctx->free_bufs = cl; } return rc; } for (cl = in; cl; cl = cl->next) { (void) ngx_http_charset_recode(cl->buf, ctx->table); } return ngx_http_next_body_filter(r, in); } static ngx_uint_t ngx_http_charset_recode(ngx_buf_t *b, u_char *table) { u_char *p, *last; last = b->last; for (p = b->pos; p < last; p++) { if (*p != table[*p]) { goto recode; } } return 0; recode: do { if (*p != table[*p]) { *p = table[*p]; } p++; } while (p < last); b->in_file = 0; return 1; } static ngx_chain_t * ngx_http_charset_recode_from_utf8(ngx_pool_t *pool, ngx_buf_t *buf, ngx_http_charset_ctx_t *ctx) { size_t len, size; u_char c, *p, *src, *dst, *saved, **table; uint32_t n; ngx_buf_t *b; ngx_uint_t i; ngx_chain_t *out, *cl, **ll; src = buf->pos; if (ctx->saved_len == 0) { for ( /* void */ ; src < buf->last; src++) { if (*src < 0x80) { continue; } len = src - buf->pos; if (len > 512) { out = ngx_http_charset_get_buf(pool, ctx); if (out == NULL) { return NULL; } b = out->buf; b->temporary = buf->temporary; b->memory = buf->memory; b->mmap = buf->mmap; b->flush = buf->flush; b->pos = buf->pos; b->last = src; out->buf = b; out->next = NULL; size = buf->last - src; saved = src; n = ngx_utf8_decode(&saved, size); if (n == 0xfffffffe) { /* incomplete UTF-8 symbol */ ngx_memcpy(ctx->saved, src, size); ctx->saved_len = size; b->shadow = buf; return out; } } else { out = NULL; size = len + buf->last - src; src = buf->pos; } if (size < NGX_HTML_ENTITY_LEN) { size += NGX_HTML_ENTITY_LEN; } cl = ngx_http_charset_get_buffer(pool, ctx, size); if (cl == NULL) { return NULL; } if (out) { out->next = cl; } else { out = cl; } b = cl->buf; dst = b->pos; goto recode; } out = ngx_alloc_chain_link(pool); if (out == NULL) { return NULL; } out->buf = buf; out->next = NULL; return out; } /* process incomplete UTF sequence from previous buffer */ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pool->log, 0, "http charset utf saved: %z", ctx->saved_len); p = src; for (i = ctx->saved_len; i < NGX_UTF_LEN; i++) { ctx->saved[i] = *p++; if (p == buf->last) { break; } } saved = ctx->saved; n = ngx_utf8_decode(&saved, i); c = '\0'; if (n < 0x10000) { table = (u_char **) ctx->table; p = table[n >> 8]; if (p) { c = p[n & 0xff]; } } else if (n == 0xfffffffe) { /* incomplete UTF-8 symbol */ if (i < NGX_UTF_LEN) { out = ngx_http_charset_get_buf(pool, ctx); if (out == NULL) { return NULL; } b = out->buf; b->pos = buf->pos; b->last = buf->last; b->sync = 1; b->shadow = buf; ngx_memcpy(&ctx->saved[ctx->saved_len], src, i); ctx->saved_len += i; return out; } } size = buf->last - buf->pos; if (size < NGX_HTML_ENTITY_LEN) { size += NGX_HTML_ENTITY_LEN; } cl = ngx_http_charset_get_buffer(pool, ctx, size); if (cl == NULL) { return NULL; } out = cl; b = cl->buf; dst = b->pos; if (c) { *dst++ = c; } else if (n == 0xfffffffe) { *dst++ = '?'; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pool->log, 0, "http charset invalid utf 0"); saved = &ctx->saved[NGX_UTF_LEN]; } else if (n > 0x10ffff) { *dst++ = '?'; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pool->log, 0, "http charset invalid utf 1"); } else { dst = ngx_sprintf(dst, "&#%uD;", n); } src += (saved - ctx->saved) - ctx->saved_len; ctx->saved_len = 0; recode: ll = &cl->next; table = (u_char **) ctx->table; while (src < buf->last) { if ((size_t) (b->end - dst) < NGX_HTML_ENTITY_LEN) { b->last = dst; size = buf->last - src + NGX_HTML_ENTITY_LEN; cl = ngx_http_charset_get_buffer(pool, ctx, size); if (cl == NULL) { return NULL; } *ll = cl; ll = &cl->next; b = cl->buf; dst = b->pos; } if (*src < 0x80) { *dst++ = *src++; continue; } len = buf->last - src; n = ngx_utf8_decode(&src, len); if (n < 0x10000) { p = table[n >> 8]; if (p) { c = p[n & 0xff]; if (c) { *dst++ = c; continue; } } dst = ngx_sprintf(dst, "&#%uD;", n); continue; } if (n == 0xfffffffe) { /* incomplete UTF-8 symbol */ ngx_memcpy(ctx->saved, src, len); ctx->saved_len = len; if (b->pos == dst) { b->sync = 1; b->temporary = 0; } break; } if (n > 0x10ffff) { *dst++ = '?'; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pool->log, 0, "http charset invalid utf 2"); continue; } /* n > 0xffff */ dst = ngx_sprintf(dst, "&#%uD;", n); } b->last = dst; b->last_buf = buf->last_buf; b->last_in_chain = buf->last_in_chain; b->flush = buf->flush; b->shadow = buf; return out; } static ngx_chain_t * ngx_http_charset_recode_to_utf8(ngx_pool_t *pool, ngx_buf_t *buf, ngx_http_charset_ctx_t *ctx) { size_t len, size; u_char *p, *src, *dst, *table; ngx_buf_t *b; ngx_chain_t *out, *cl, **ll; table = ctx->table; for (src = buf->pos; src < buf->last; src++) { if (table[*src * NGX_UTF_LEN] == '\1') { continue; } goto recode; } out = ngx_alloc_chain_link(pool); if (out == NULL) { return NULL; } out->buf = buf; out->next = NULL; return out; recode: /* * we assume that there are about half of characters to be recoded, * so we preallocate "size / 2 + size / 2 * ctx->length" */ len = src - buf->pos; if (len > 512) { out = ngx_http_charset_get_buf(pool, ctx); if (out == NULL) { return NULL; } b = out->buf; b->temporary = buf->temporary; b->memory = buf->memory; b->mmap = buf->mmap; b->flush = buf->flush; b->pos = buf->pos; b->last = src; out->buf = b; out->next = NULL; size = buf->last - src; size = size / 2 + size / 2 * ctx->length; } else { out = NULL; size = buf->last - src; size = len + size / 2 + size / 2 * ctx->length; src = buf->pos; } cl = ngx_http_charset_get_buffer(pool, ctx, size); if (cl == NULL) { return NULL; } if (out) { out->next = cl; } else { out = cl; } ll = &cl->next; b = cl->buf; dst = b->pos; while (src < buf->last) { p = &table[*src++ * NGX_UTF_LEN]; len = *p++; if ((size_t) (b->end - dst) < len) { b->last = dst; size = buf->last - src; size = len + size / 2 + size / 2 * ctx->length; cl = ngx_http_charset_get_buffer(pool, ctx, size); if (cl == NULL) { return NULL; } *ll = cl; ll = &cl->next; b = cl->buf; dst = b->pos; } while (len) { *dst++ = *p++; len--; } } b->last = dst; b->last_buf = buf->last_buf; b->last_in_chain = buf->last_in_chain; b->flush = buf->flush; b->shadow = buf; return out; } static ngx_chain_t * ngx_http_charset_get_buf(ngx_pool_t *pool, ngx_http_charset_ctx_t *ctx) { ngx_chain_t *cl; cl = ctx->free_bufs; if (cl) { ctx->free_bufs = cl->next; cl->buf->shadow = NULL; cl->next = NULL; return cl; } cl = ngx_alloc_chain_link(pool); if (cl == NULL) { return NULL; } cl->buf = ngx_calloc_buf(pool); if (cl->buf == NULL) { return NULL; } cl->next = NULL; cl->buf->tag = (ngx_buf_tag_t) &ngx_http_charset_filter_module; return cl; } static ngx_chain_t * ngx_http_charset_get_buffer(ngx_pool_t *pool, ngx_http_charset_ctx_t *ctx, size_t size) { ngx_buf_t *b; ngx_chain_t *cl, **ll; for (ll = &ctx->free_buffers, cl = ctx->free_buffers; cl; ll = &cl->next, cl = cl->next) { b = cl->buf; if ((size_t) (b->end - b->start) >= size) { *ll = cl->next; cl->next = NULL; b->pos = b->start; b->temporary = 1; b->shadow = NULL; return cl; } } cl = ngx_alloc_chain_link(pool); if (cl == NULL) { return NULL; } cl->buf = ngx_create_temp_buf(pool, size); if (cl->buf == NULL) { return NULL; } cl->next = NULL; cl->buf->temporary = 1; cl->buf->tag = (ngx_buf_tag_t) &ngx_http_charset_filter_module; return cl; } static char * ngx_http_charset_map_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_charset_main_conf_t *mcf = conf; char *rv; u_char *p, *dst2src, **pp; ngx_int_t src, dst; ngx_uint_t i, n; ngx_str_t *value; ngx_conf_t pvcf; ngx_http_charset_t *charset; ngx_http_charset_tables_t *table; ngx_http_charset_conf_ctx_t ctx; value = cf->args->elts; src = ngx_http_add_charset(&mcf->charsets, &value[1]); if (src == NGX_ERROR) { return NGX_CONF_ERROR; } dst = ngx_http_add_charset(&mcf->charsets, &value[2]); if (dst == NGX_ERROR) { return NGX_CONF_ERROR; } if (src == dst) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"charset_map\" between the same charsets " "\"%V\" and \"%V\"", &value[1], &value[2]); return NGX_CONF_ERROR; } table = mcf->tables.elts; for (i = 0; i < mcf->tables.nelts; i++) { if ((src == table->src && dst == table->dst) || (src == table->dst && dst == table->src)) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "duplicate \"charset_map\" between " "\"%V\" and \"%V\"", &value[1], &value[2]); return NGX_CONF_ERROR; } } table = ngx_array_push(&mcf->tables); if (table == NULL) { return NGX_CONF_ERROR; } table->src = src; table->dst = dst; if (ngx_strcasecmp(value[2].data, (u_char *) "utf-8") == 0) { table->src2dst = ngx_pcalloc(cf->pool, 256 * NGX_UTF_LEN); if (table->src2dst == NULL) { return NGX_CONF_ERROR; } table->dst2src = ngx_pcalloc(cf->pool, 256 * sizeof(void *)); if (table->dst2src == NULL) { return NGX_CONF_ERROR; } dst2src = ngx_pcalloc(cf->pool, 256); if (dst2src == NULL) { return NGX_CONF_ERROR; } pp = (u_char **) &table->dst2src[0]; pp[0] = dst2src; for (i = 0; i < 128; i++) { p = &table->src2dst[i * NGX_UTF_LEN]; p[0] = '\1'; p[1] = (u_char) i; dst2src[i] = (u_char) i; } for (/* void */; i < 256; i++) { p = &table->src2dst[i * NGX_UTF_LEN]; p[0] = '\1'; p[1] = '?'; } } else { table->src2dst = ngx_palloc(cf->pool, 256); if (table->src2dst == NULL) { return NGX_CONF_ERROR; } table->dst2src = ngx_palloc(cf->pool, 256); if (table->dst2src == NULL) { return NGX_CONF_ERROR; } for (i = 0; i < 128; i++) { table->src2dst[i] = (u_char) i; table->dst2src[i] = (u_char) i; } for (/* void */; i < 256; i++) { table->src2dst[i] = '?'; table->dst2src[i] = '?'; } } charset = mcf->charsets.elts; ctx.table = table; ctx.charset = &charset[dst]; ctx.characters = 0; pvcf = *cf; cf->ctx = &ctx; cf->handler = ngx_http_charset_map; cf->handler_conf = conf; rv = ngx_conf_parse(cf, NULL); *cf = pvcf; if (ctx.characters) { n = ctx.charset->length; ctx.charset->length /= ctx.characters; if (((n * 10) / ctx.characters) % 10 > 4) { ctx.charset->length++; } } return rv; } static char * ngx_http_charset_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) { u_char *p, *dst2src, **pp; uint32_t n; ngx_int_t src, dst; ngx_str_t *value; ngx_uint_t i; ngx_http_charset_tables_t *table; ngx_http_charset_conf_ctx_t *ctx; if (cf->args->nelts != 2) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameters number"); return NGX_CONF_ERROR; } value = cf->args->elts; src = ngx_hextoi(value[0].data, value[0].len); if (src == NGX_ERROR || src > 255) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid value \"%V\"", &value[0]); return NGX_CONF_ERROR; } ctx = cf->ctx; table = ctx->table; if (ctx->charset->utf8) { p = &table->src2dst[src * NGX_UTF_LEN]; *p++ = (u_char) (value[1].len / 2); for (i = 0; i < value[1].len; i += 2) { dst = ngx_hextoi(&value[1].data[i], 2); if (dst == NGX_ERROR || dst > 255) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid value \"%V\"", &value[1]); return NGX_CONF_ERROR; } *p++ = (u_char) dst; } i /= 2; ctx->charset->length += i; ctx->characters++; p = &table->src2dst[src * NGX_UTF_LEN] + 1; n = ngx_utf8_decode(&p, i); if (n > 0xffff) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid value \"%V\"", &value[1]); return NGX_CONF_ERROR; } pp = (u_char **) &table->dst2src[0]; dst2src = pp[n >> 8]; if (dst2src == NULL) { dst2src = ngx_pcalloc(cf->pool, 256); if (dst2src == NULL) { return NGX_CONF_ERROR; } pp[n >> 8] = dst2src; } dst2src[n & 0xff] = (u_char) src; } else { dst = ngx_hextoi(value[1].data, value[1].len); if (dst == NGX_ERROR || dst > 255) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid value \"%V\"", &value[1]); return NGX_CONF_ERROR; } table->src2dst[src] = (u_char) dst; table->dst2src[dst] = (u_char) src; } return NGX_CONF_OK; } static char * ngx_http_set_charset_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { char *p = conf; ngx_int_t *cp; ngx_str_t *value, var; ngx_http_charset_main_conf_t *mcf; cp = (ngx_int_t *) (p + cmd->offset); if (*cp != NGX_CONF_UNSET) { return "is duplicate"; } value = cf->args->elts; if (cmd->offset == offsetof(ngx_http_charset_loc_conf_t, charset) && ngx_strcmp(value[1].data, "off") == 0) { *cp = NGX_HTTP_CHARSET_OFF; return NGX_CONF_OK; } if (value[1].data[0] == '$') { var.len = value[1].len - 1; var.data = value[1].data + 1; *cp = ngx_http_get_variable_index(cf, &var); if (*cp == NGX_ERROR) { return NGX_CONF_ERROR; } *cp += NGX_HTTP_CHARSET_VAR; return NGX_CONF_OK; } mcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_charset_filter_module); *cp = ngx_http_add_charset(&mcf->charsets, &value[1]); if (*cp == NGX_ERROR) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } static ngx_int_t ngx_http_add_charset(ngx_array_t *charsets, ngx_str_t *name) { ngx_uint_t i; ngx_http_charset_t *c; c = charsets->elts; for (i = 0; i < charsets->nelts; i++) { if (name->len != c[i].name.len) { continue; } if (ngx_strcasecmp(name->data, c[i].name.data) == 0) { break; } } if (i < charsets->nelts) { return i; } c = ngx_array_push(charsets); if (c == NULL) { return NGX_ERROR; } c->tables = NULL; c->name = *name; c->length = 0; if (ngx_strcasecmp(name->data, (u_char *) "utf-8") == 0) { c->utf8 = 1; } else { c->utf8 = 0; } return i; } static void * ngx_http_charset_create_main_conf(ngx_conf_t *cf) { ngx_http_charset_main_conf_t *mcf; mcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_charset_main_conf_t)); if (mcf == NULL) { return NULL; } if (ngx_array_init(&mcf->charsets, cf->pool, 2, sizeof(ngx_http_charset_t)) != NGX_OK) { return NULL; } if (ngx_array_init(&mcf->tables, cf->pool, 1, sizeof(ngx_http_charset_tables_t)) != NGX_OK) { return NULL; } if (ngx_array_init(&mcf->recodes, cf->pool, 2, sizeof(ngx_http_charset_recode_t)) != NGX_OK) { return NULL; } return mcf; } static void * ngx_http_charset_create_loc_conf(ngx_conf_t *cf) { ngx_http_charset_loc_conf_t *lcf; lcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_charset_loc_conf_t)); if (lcf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * lcf->types = { NULL }; * lcf->types_keys = NULL; */ lcf->charset = NGX_CONF_UNSET; lcf->source_charset = NGX_CONF_UNSET; lcf->override_charset = NGX_CONF_UNSET; return lcf; } static char * ngx_http_charset_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_charset_loc_conf_t *prev = parent; ngx_http_charset_loc_conf_t *conf = child; ngx_uint_t i; ngx_http_charset_recode_t *recode; ngx_http_charset_main_conf_t *mcf; if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types, &prev->types_keys, &prev->types, ngx_http_charset_default_types) != NGX_OK) { return NGX_CONF_ERROR; } ngx_conf_merge_value(conf->override_charset, prev->override_charset, 0); ngx_conf_merge_value(conf->charset, prev->charset, NGX_HTTP_CHARSET_OFF); ngx_conf_merge_value(conf->source_charset, prev->source_charset, NGX_HTTP_CHARSET_OFF); if (conf->charset == NGX_HTTP_CHARSET_OFF || conf->source_charset == NGX_HTTP_CHARSET_OFF || conf->charset == conf->source_charset) { return NGX_CONF_OK; } if (conf->source_charset >= NGX_HTTP_CHARSET_VAR || conf->charset >= NGX_HTTP_CHARSET_VAR) { return NGX_CONF_OK; } mcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_charset_filter_module); recode = mcf->recodes.elts; for (i = 0; i < mcf->recodes.nelts; i++) { if (conf->source_charset == recode[i].src && conf->charset == recode[i].dst) { return NGX_CONF_OK; } } recode = ngx_array_push(&mcf->recodes); if (recode == NULL) { return NGX_CONF_ERROR; } recode->src = conf->source_charset; recode->dst = conf->charset; return NGX_CONF_OK; } static ngx_int_t ngx_http_charset_postconfiguration(ngx_conf_t *cf) { u_char **src, **dst; ngx_int_t c; ngx_uint_t i, t; ngx_http_charset_t *charset; ngx_http_charset_recode_t *recode; ngx_http_charset_tables_t *tables; ngx_http_charset_main_conf_t *mcf; mcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_charset_filter_module); recode = mcf->recodes.elts; tables = mcf->tables.elts; charset = mcf->charsets.elts; for (i = 0; i < mcf->recodes.nelts; i++) { c = recode[i].src; for (t = 0; t < mcf->tables.nelts; t++) { if (c == tables[t].src && recode[i].dst == tables[t].dst) { goto next; } if (c == tables[t].dst && recode[i].dst == tables[t].src) { goto next; } } ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no \"charset_map\" between the charsets \"%V\" and \"%V\"", &charset[c].name, &charset[recode[i].dst].name); return NGX_ERROR; next: continue; } for (t = 0; t < mcf->tables.nelts; t++) { src = charset[tables[t].src].tables; if (src == NULL) { src = ngx_pcalloc(cf->pool, sizeof(u_char *) * mcf->charsets.nelts); if (src == NULL) { return NGX_ERROR; } charset[tables[t].src].tables = src; } dst = charset[tables[t].dst].tables; if (dst == NULL) { dst = ngx_pcalloc(cf->pool, sizeof(u_char *) * mcf->charsets.nelts); if (dst == NULL) { return NGX_ERROR; } charset[tables[t].dst].tables = dst; } src[tables[t].dst] = tables[t].src2dst; dst[tables[t].src] = tables[t].dst2src; } ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_charset_header_filter; ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_charset_body_filter; return NGX_OK; } nginx-1.24.0/src/http/modules/ngx_http_chunked_filter_module.c000644 001751 001751 00000021041 14415135676 025751 0ustar00mdouninmdounin000000 000000 /* * 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_chain_t *ngx_http_chunked_create_trailers(ngx_http_request_t *r, ngx_http_chunked_filter_ctx_t *ctx); static ngx_http_module_t ngx_http_chunked_filter_module_ctx = { NULL, /* preconfiguration */ ngx_http_chunked_filter_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; ngx_module_t ngx_http_chunked_filter_module = { NGX_MODULE_V1, &ngx_http_chunked_filter_module_ctx, /* module context */ NULL, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter; static ngx_int_t ngx_http_chunked_header_filter(ngx_http_request_t *r) { ngx_http_core_loc_conf_t *clcf; ngx_http_chunked_filter_ctx_t *ctx; if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED || r->headers_out.status == NGX_HTTP_NO_CONTENT || r->headers_out.status < NGX_HTTP_OK || r != r->main || r->method == NGX_HTTP_HEAD) { return ngx_http_next_header_filter(r); } if (r->headers_out.content_length_n == -1 || r->expect_trailers) { clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (r->http_version >= NGX_HTTP_VERSION_11 && clcf->chunked_transfer_encoding) { if (r->expect_trailers) { ngx_http_clear_content_length(r); } r->chunked = 1; ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_chunked_filter_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_chunked_filter_module); } else if (r->headers_out.content_length_n == -1) { r->keepalive = 0; } } return ngx_http_next_header_filter(r); } static ngx_int_t ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { u_char *chunk; off_t size; ngx_int_t rc; ngx_buf_t *b; ngx_chain_t *out, *cl, *tl, **ll; ngx_http_chunked_filter_ctx_t *ctx; if (in == NULL || !r->chunked || r->header_only) { return ngx_http_next_body_filter(r, in); } ctx = ngx_http_get_module_ctx(r, ngx_http_chunked_filter_module); out = NULL; ll = &out; size = 0; cl = in; for ( ;; ) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http chunk: %O", ngx_buf_size(cl->buf)); size += ngx_buf_size(cl->buf); if (cl->buf->flush || cl->buf->sync || ngx_buf_in_memory(cl->buf) || cl->buf->in_file) { tl = ngx_alloc_chain_link(r->pool); if (tl == NULL) { return NGX_ERROR; } tl->buf = cl->buf; *ll = tl; ll = &tl->next; } if (cl->next == NULL) { break; } cl = cl->next; } if (size) { tl = ngx_chain_get_free_buf(r->pool, &ctx->free); if (tl == NULL) { return NGX_ERROR; } b = tl->buf; chunk = b->start; if (chunk == NULL) { /* the "0000000000000000" is 64-bit hexadecimal string */ chunk = ngx_palloc(r->pool, sizeof("0000000000000000" CRLF) - 1); if (chunk == NULL) { return NGX_ERROR; } b->start = chunk; b->end = chunk + sizeof("0000000000000000" CRLF) - 1; } b->tag = (ngx_buf_tag_t) &ngx_http_chunked_filter_module; b->memory = 0; b->temporary = 1; b->pos = chunk; b->last = ngx_sprintf(chunk, "%xO" CRLF, size); tl->next = out; out = tl; } if (cl->buf->last_buf) { tl = ngx_http_chunked_create_trailers(r, ctx); if (tl == NULL) { return NGX_ERROR; } cl->buf->last_buf = 0; *ll = tl; if (size == 0) { tl->buf->pos += 2; } } else if (size > 0) { tl = ngx_chain_get_free_buf(r->pool, &ctx->free); if (tl == NULL) { return NGX_ERROR; } b = tl->buf; b->tag = (ngx_buf_tag_t) &ngx_http_chunked_filter_module; b->temporary = 0; b->memory = 1; b->pos = (u_char *) CRLF; b->last = b->pos + 2; *ll = tl; } else { *ll = NULL; } rc = ngx_http_next_body_filter(r, out); ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, (ngx_buf_tag_t) &ngx_http_chunked_filter_module); return rc; } static ngx_chain_t * ngx_http_chunked_create_trailers(ngx_http_request_t *r, ngx_http_chunked_filter_ctx_t *ctx) { size_t len; ngx_buf_t *b; ngx_uint_t i; ngx_chain_t *cl; ngx_list_part_t *part; ngx_table_elt_t *header; len = 0; part = &r->headers_out.trailers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (header[i].hash == 0) { continue; } len += header[i].key.len + sizeof(": ") - 1 + header[i].value.len + sizeof(CRLF) - 1; } cl = ngx_chain_get_free_buf(r->pool, &ctx->free); if (cl == NULL) { return NULL; } b = cl->buf; b->tag = (ngx_buf_tag_t) &ngx_http_chunked_filter_module; b->temporary = 0; b->memory = 1; b->last_buf = 1; if (len == 0) { b->pos = (u_char *) CRLF "0" CRLF CRLF; b->last = b->pos + sizeof(CRLF "0" CRLF CRLF) - 1; return cl; } len += sizeof(CRLF "0" CRLF CRLF) - 1; b->pos = ngx_palloc(r->pool, len); if (b->pos == NULL) { return NULL; } b->last = b->pos; *b->last++ = CR; *b->last++ = LF; *b->last++ = '0'; *b->last++ = CR; *b->last++ = LF; part = &r->headers_out.trailers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (header[i].hash == 0) { continue; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http trailer: \"%V: %V\"", &header[i].key, &header[i].value); b->last = ngx_copy(b->last, header[i].key.data, header[i].key.len); *b->last++ = ':'; *b->last++ = ' '; b->last = ngx_copy(b->last, header[i].value.data, header[i].value.len); *b->last++ = CR; *b->last++ = LF; } *b->last++ = CR; *b->last++ = LF; return cl; } static ngx_int_t ngx_http_chunked_filter_init(ngx_conf_t *cf) { ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_chunked_header_filter; ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_chunked_body_filter; return NGX_OK; } nginx-1.24.0/src/http/modules/ngx_http_dav_module.c000644 001751 001751 00000077073 14415135676 023555 0ustar00mdouninmdounin000000 000000 /* * 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); static void *ngx_http_dav_create_loc_conf(ngx_conf_t *cf); static char *ngx_http_dav_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); static ngx_int_t ngx_http_dav_init(ngx_conf_t *cf); static ngx_conf_bitmask_t ngx_http_dav_methods_mask[] = { { ngx_string("off"), NGX_HTTP_DAV_OFF }, { ngx_string("put"), NGX_HTTP_PUT }, { ngx_string("delete"), NGX_HTTP_DELETE }, { ngx_string("mkcol"), NGX_HTTP_MKCOL }, { ngx_string("copy"), NGX_HTTP_COPY }, { ngx_string("move"), NGX_HTTP_MOVE }, { ngx_null_string, 0 } }; static ngx_command_t ngx_http_dav_commands[] = { { ngx_string("dav_methods"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_dav_loc_conf_t, methods), &ngx_http_dav_methods_mask }, { ngx_string("create_full_put_path"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_dav_loc_conf_t, create_full_put_path), NULL }, { ngx_string("min_delete_depth"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_dav_loc_conf_t, min_delete_depth), NULL }, { ngx_string("dav_access"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123, ngx_conf_set_access_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_dav_loc_conf_t, access), NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_dav_module_ctx = { NULL, /* preconfiguration */ ngx_http_dav_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_dav_create_loc_conf, /* create location configuration */ ngx_http_dav_merge_loc_conf /* merge location configuration */ }; ngx_module_t ngx_http_dav_module = { NGX_MODULE_V1, &ngx_http_dav_module_ctx, /* module context */ ngx_http_dav_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_int_t ngx_http_dav_handler(ngx_http_request_t *r) { ngx_int_t rc; ngx_http_dav_loc_conf_t *dlcf; dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module); if (!(r->method & dlcf->methods)) { return NGX_DECLINED; } switch (r->method) { case NGX_HTTP_PUT: if (r->uri.data[r->uri.len - 1] == '/') { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "cannot PUT to a collection"); return NGX_HTTP_CONFLICT; } if (r->headers_in.content_range) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "PUT with range is unsupported"); return NGX_HTTP_NOT_IMPLEMENTED; } r->request_body_in_file_only = 1; r->request_body_in_persistent_file = 1; r->request_body_in_clean_file = 1; r->request_body_file_group_access = 1; r->request_body_file_log_level = 0; rc = ngx_http_read_client_request_body(r, ngx_http_dav_put_handler); if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { return rc; } return NGX_DONE; case NGX_HTTP_DELETE: return ngx_http_dav_delete_handler(r); case NGX_HTTP_MKCOL: return ngx_http_dav_mkcol_handler(r, dlcf); case NGX_HTTP_COPY: return ngx_http_dav_copy_move_handler(r); case NGX_HTTP_MOVE: return ngx_http_dav_copy_move_handler(r); } return NGX_DECLINED; } static void ngx_http_dav_put_handler(ngx_http_request_t *r) { size_t root; time_t date; ngx_str_t *temp, path; ngx_uint_t status; ngx_file_info_t fi; ngx_ext_rename_file_t ext; ngx_http_dav_loc_conf_t *dlcf; if (r->request_body == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "PUT request body is unavailable"); ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (r->request_body->temp_file == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "PUT request body must be in a file"); ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } path.len--; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http put filename: \"%s\"", path.data); temp = &r->request_body->temp_file->file.name; if (ngx_file_info(path.data, &fi) == NGX_FILE_ERROR) { status = NGX_HTTP_CREATED; } else { status = NGX_HTTP_NO_CONTENT; if (ngx_is_dir(&fi)) { ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_EISDIR, "\"%s\" could not be created", path.data); if (ngx_delete_file(temp->data) == NGX_FILE_ERROR) { ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno, ngx_delete_file_n " \"%s\" failed", temp->data); } ngx_http_finalize_request(r, NGX_HTTP_CONFLICT); return; } } dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module); ext.access = dlcf->access; ext.path_access = dlcf->access; ext.time = -1; ext.create_path = dlcf->create_full_put_path; ext.delete_file = 1; ext.log = r->connection->log; if (r->headers_in.date) { date = ngx_parse_http_time(r->headers_in.date->value.data, r->headers_in.date->value.len); if (date != NGX_ERROR) { ext.time = date; ext.fd = r->request_body->temp_file->file.fd; } } if (ngx_ext_rename_file(temp, &path, &ext) != NGX_OK) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (status == NGX_HTTP_CREATED) { if (ngx_http_dav_location(r) != 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 || r->headers_in.chunked) { 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 || r->headers_in.chunked) { 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'; 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) != 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 || r->headers_in.chunked) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "COPY and MOVE with body are unsupported"); 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; } cf.size = ngx_file_size(&fi); cf.buf_size = 0; cf.access = ngx_file_access(&fi); 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 *p; size_t len; uintptr_t escape; r->headers_out.location = ngx_list_push(&r->headers_out.headers); if (r->headers_out.location == NULL) { return NGX_ERROR; } r->headers_out.location->hash = 1; r->headers_out.location->next = NULL; ngx_str_set(&r->headers_out.location->key, "Location"); escape = 2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len, NGX_ESCAPE_URI); if (escape) { len = r->uri.len + escape; p = ngx_pnalloc(r->pool, len); if (p == NULL) { ngx_http_clear_location(r); return NGX_ERROR; } r->headers_out.location->value.len = len; r->headers_out.location->value.data = p; ngx_escape_uri(p, r->uri.data, r->uri.len, NGX_ESCAPE_URI); } else { r->headers_out.location->value = r->uri; } return NGX_OK; } 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; } nginx-1.24.0/src/http/modules/ngx_http_degradation_module.c000644 001751 001751 00000014140 14415135676 025246 0ustar00mdouninmdounin000000 000000 /* * 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; } nginx-1.24.0/src/http/modules/ngx_http_empty_gif_module.c000644 001751 001751 00000012640 14415135676 024753 0ustar00mdouninmdounin000000 000000 /* * 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; } nginx-1.24.0/src/http/modules/ngx_http_fastcgi_module.c000644 001751 001751 00000340022 14415135676 024406 0ustar00mdouninmdounin000000 000000 /* * 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; off_t rest; ngx_chain_t *free; ngx_chain_t *busy; unsigned fastcgi_stdout:1; unsigned large_stderr:1; unsigned header_sent:1; unsigned closed:1; ngx_array_t *split_parts; ngx_str_t script_name; ngx_str_t path_info; } ngx_http_fastcgi_ctx_t; #define NGX_HTTP_FASTCGI_RESPONDER 1 #define NGX_HTTP_FASTCGI_KEEP_CONN 1 #define NGX_HTTP_FASTCGI_BEGIN_REQUEST 1 #define NGX_HTTP_FASTCGI_ABORT_REQUEST 2 #define NGX_HTTP_FASTCGI_END_REQUEST 3 #define NGX_HTTP_FASTCGI_PARAMS 4 #define NGX_HTTP_FASTCGI_STDIN 5 #define NGX_HTTP_FASTCGI_STDOUT 6 #define NGX_HTTP_FASTCGI_STDERR 7 #define NGX_HTTP_FASTCGI_DATA 8 typedef struct { u_char version; u_char type; u_char request_id_hi; u_char request_id_lo; u_char content_length_hi; u_char content_length_lo; u_char padding_length; u_char reserved; } ngx_http_fastcgi_header_t; typedef struct { u_char role_hi; u_char role_lo; u_char flags; u_char reserved[5]; } ngx_http_fastcgi_begin_request_t; typedef struct { u_char version; u_char type; u_char request_id_hi; u_char request_id_lo; } ngx_http_fastcgi_header_small_t; typedef struct { ngx_http_fastcgi_header_t h0; ngx_http_fastcgi_begin_request_t br; ngx_http_fastcgi_header_small_t h1; } ngx_http_fastcgi_request_start_t; static ngx_int_t ngx_http_fastcgi_eval(ngx_http_request_t *r, ngx_http_fastcgi_loc_conf_t *flcf); #if (NGX_HTTP_CACHE) static ngx_int_t ngx_http_fastcgi_create_key(ngx_http_request_t *r); #endif static ngx_int_t ngx_http_fastcgi_create_request(ngx_http_request_t *r); static ngx_int_t ngx_http_fastcgi_reinit_request(ngx_http_request_t *r); static ngx_int_t ngx_http_fastcgi_body_output_filter(void *data, ngx_chain_t *in); static ngx_int_t ngx_http_fastcgi_process_header(ngx_http_request_t *r); static ngx_int_t ngx_http_fastcgi_input_filter_init(void *data); static ngx_int_t ngx_http_fastcgi_input_filter(ngx_event_pipe_t *p, ngx_buf_t *buf); static ngx_int_t ngx_http_fastcgi_non_buffered_filter(void *data, ssize_t bytes); static ngx_int_t ngx_http_fastcgi_process_record(ngx_http_request_t *r, ngx_http_fastcgi_ctx_t *f); static void ngx_http_fastcgi_abort_request(ngx_http_request_t *r); static void ngx_http_fastcgi_finalize_request(ngx_http_request_t *r, ngx_int_t rc); static ngx_int_t ngx_http_fastcgi_add_variables(ngx_conf_t *cf); static void *ngx_http_fastcgi_create_main_conf(ngx_conf_t *cf); static void *ngx_http_fastcgi_create_loc_conf(ngx_conf_t *cf); static char *ngx_http_fastcgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); static ngx_int_t ngx_http_fastcgi_init_params(ngx_conf_t *cf, ngx_http_fastcgi_loc_conf_t *conf, ngx_http_fastcgi_params_t *params, ngx_keyval_t *default_params); static ngx_int_t ngx_http_fastcgi_script_name_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_fastcgi_path_info_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_http_fastcgi_ctx_t *ngx_http_fastcgi_split(ngx_http_request_t *r, ngx_http_fastcgi_loc_conf_t *flcf); static char *ngx_http_fastcgi_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_fastcgi_split_path_info(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_fastcgi_store(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); #if (NGX_HTTP_CACHE) static char *ngx_http_fastcgi_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_fastcgi_cache_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); #endif static char *ngx_http_fastcgi_lowat_check(ngx_conf_t *cf, void *post, void *data); static ngx_conf_post_t ngx_http_fastcgi_lowat_post = { ngx_http_fastcgi_lowat_check }; static ngx_conf_bitmask_t ngx_http_fastcgi_next_upstream_masks[] = { { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR }, { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT }, { ngx_string("invalid_header"), NGX_HTTP_UPSTREAM_FT_INVALID_HEADER }, { ngx_string("non_idempotent"), NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT }, { ngx_string("http_500"), NGX_HTTP_UPSTREAM_FT_HTTP_500 }, { ngx_string("http_503"), NGX_HTTP_UPSTREAM_FT_HTTP_503 }, { ngx_string("http_403"), NGX_HTTP_UPSTREAM_FT_HTTP_403 }, { ngx_string("http_404"), NGX_HTTP_UPSTREAM_FT_HTTP_404 }, { ngx_string("http_429"), NGX_HTTP_UPSTREAM_FT_HTTP_429 }, { ngx_string("updating"), NGX_HTTP_UPSTREAM_FT_UPDATING }, { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF }, { ngx_null_string, 0 } }; ngx_module_t ngx_http_fastcgi_module; static ngx_command_t ngx_http_fastcgi_commands[] = { { ngx_string("fastcgi_pass"), NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, ngx_http_fastcgi_pass, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("fastcgi_index"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, index), NULL }, { ngx_string("fastcgi_split_path_info"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_fastcgi_split_path_info, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("fastcgi_store"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_fastcgi_store, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("fastcgi_store_access"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123, ngx_conf_set_access_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.store_access), NULL }, { ngx_string("fastcgi_buffering"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.buffering), NULL }, { ngx_string("fastcgi_request_buffering"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.request_buffering), NULL }, { ngx_string("fastcgi_ignore_client_abort"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.ignore_client_abort), NULL }, { ngx_string("fastcgi_bind"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12, ngx_http_upstream_bind_set_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.local), NULL }, { ngx_string("fastcgi_socket_keepalive"), 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.socket_keepalive), NULL }, { ngx_string("fastcgi_connect_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.connect_timeout), NULL }, { ngx_string("fastcgi_send_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.send_timeout), NULL }, { ngx_string("fastcgi_send_lowat"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.send_lowat), &ngx_http_fastcgi_lowat_post }, { ngx_string("fastcgi_buffer_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.buffer_size), NULL }, { ngx_string("fastcgi_pass_request_headers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.pass_request_headers), NULL }, { ngx_string("fastcgi_pass_request_body"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.pass_request_body), NULL }, { ngx_string("fastcgi_intercept_errors"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.intercept_errors), NULL }, { ngx_string("fastcgi_read_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.read_timeout), NULL }, { ngx_string("fastcgi_buffers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, ngx_conf_set_bufs_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.bufs), NULL }, { ngx_string("fastcgi_busy_buffers_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.busy_buffers_size_conf), NULL }, { ngx_string("fastcgi_force_ranges"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.force_ranges), NULL }, { ngx_string("fastcgi_limit_rate"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.limit_rate), NULL }, #if (NGX_HTTP_CACHE) { ngx_string("fastcgi_cache"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_fastcgi_cache, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("fastcgi_cache_key"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_fastcgi_cache_key, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("fastcgi_cache_path"), NGX_HTTP_MAIN_CONF|NGX_CONF_2MORE, ngx_http_file_cache_set_slot, NGX_HTTP_MAIN_CONF_OFFSET, offsetof(ngx_http_fastcgi_main_conf_t, caches), &ngx_http_fastcgi_module }, { ngx_string("fastcgi_cache_bypass"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_set_predicate_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_bypass), NULL }, { ngx_string("fastcgi_no_cache"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_set_predicate_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.no_cache), NULL }, { ngx_string("fastcgi_cache_valid"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_file_cache_valid_set_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_valid), NULL }, { ngx_string("fastcgi_cache_min_uses"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_min_uses), NULL }, { ngx_string("fastcgi_cache_max_range_offset"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_off_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_max_range_offset), NULL }, { ngx_string("fastcgi_cache_use_stale"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_use_stale), &ngx_http_fastcgi_next_upstream_masks }, { ngx_string("fastcgi_cache_methods"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_methods), &ngx_http_upstream_cache_method_mask }, { ngx_string("fastcgi_cache_lock"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_lock), NULL }, { ngx_string("fastcgi_cache_lock_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_lock_timeout), NULL }, { ngx_string("fastcgi_cache_lock_age"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_lock_age), NULL }, { ngx_string("fastcgi_cache_revalidate"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_revalidate), NULL }, { ngx_string("fastcgi_cache_background_update"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_background_update), NULL }, #endif { ngx_string("fastcgi_temp_path"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234, ngx_conf_set_path_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.temp_path), NULL }, { ngx_string("fastcgi_max_temp_file_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.max_temp_file_size_conf), NULL }, { ngx_string("fastcgi_temp_file_write_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.temp_file_write_size_conf), NULL }, { ngx_string("fastcgi_next_upstream"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.next_upstream), &ngx_http_fastcgi_next_upstream_masks }, { ngx_string("fastcgi_next_upstream_tries"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.next_upstream_tries), NULL }, { ngx_string("fastcgi_next_upstream_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.next_upstream_timeout), NULL }, { ngx_string("fastcgi_param"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE23, ngx_http_upstream_param_set_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, params_source), NULL }, { ngx_string("fastcgi_pass_header"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_array_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.pass_headers), NULL }, { ngx_string("fastcgi_hide_header"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_array_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.hide_headers), NULL }, { ngx_string("fastcgi_ignore_headers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.ignore_headers), &ngx_http_upstream_ignore_headers_masks }, { ngx_string("fastcgi_catch_stderr"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_array_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, catch_stderr), NULL }, { ngx_string("fastcgi_keep_conn"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, keep_conn), NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_fastcgi_module_ctx = { ngx_http_fastcgi_add_variables, /* preconfiguration */ NULL, /* postconfiguration */ ngx_http_fastcgi_create_main_conf, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_fastcgi_create_loc_conf, /* create location configuration */ ngx_http_fastcgi_merge_loc_conf /* merge location configuration */ }; ngx_module_t ngx_http_fastcgi_module = { NGX_MODULE_V1, &ngx_http_fastcgi_module_ctx, /* module context */ ngx_http_fastcgi_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_fastcgi_request_start_t ngx_http_fastcgi_request_start = { { 1, /* version */ NGX_HTTP_FASTCGI_BEGIN_REQUEST, /* type */ 0, /* request_id_hi */ 1, /* request_id_lo */ 0, /* content_length_hi */ sizeof(ngx_http_fastcgi_begin_request_t), /* content_length_lo */ 0, /* padding_length */ 0 }, /* reserved */ { 0, /* role_hi */ NGX_HTTP_FASTCGI_RESPONDER, /* role_lo */ 0, /* NGX_HTTP_FASTCGI_KEEP_CONN */ /* flags */ { 0, 0, 0, 0, 0 } }, /* reserved[5] */ { 1, /* version */ NGX_HTTP_FASTCGI_PARAMS, /* type */ 0, /* request_id_hi */ 1 }, /* request_id_lo */ }; static ngx_http_variable_t ngx_http_fastcgi_vars[] = { { ngx_string("fastcgi_script_name"), NULL, ngx_http_fastcgi_script_name_variable, 0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 }, { ngx_string("fastcgi_path_info"), NULL, ngx_http_fastcgi_path_info_variable, 0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 }, ngx_http_null_variable }; static ngx_str_t ngx_http_fastcgi_hide_headers[] = { ngx_string("Status"), ngx_string("X-Accel-Expires"), ngx_string("X-Accel-Redirect"), ngx_string("X-Accel-Limit-Rate"), ngx_string("X-Accel-Buffering"), ngx_string("X-Accel-Charset"), ngx_null_string }; #if (NGX_HTTP_CACHE) static ngx_keyval_t ngx_http_fastcgi_cache_headers[] = { { ngx_string("HTTP_IF_MODIFIED_SINCE"), ngx_string("$upstream_cache_last_modified") }, { ngx_string("HTTP_IF_UNMODIFIED_SINCE"), ngx_string("") }, { ngx_string("HTTP_IF_NONE_MATCH"), ngx_string("$upstream_cache_etag") }, { ngx_string("HTTP_IF_MATCH"), ngx_string("") }, { ngx_string("HTTP_RANGE"), ngx_string("") }, { ngx_string("HTTP_IF_RANGE"), ngx_string("") }, { ngx_null_string, ngx_null_string } }; #endif static ngx_path_init_t ngx_http_fastcgi_temp_path = { ngx_string(NGX_HTTP_FASTCGI_TEMP_PATH), { 1, 2, 0 } }; static ngx_int_t ngx_http_fastcgi_handler(ngx_http_request_t *r) { ngx_int_t rc; ngx_http_upstream_t *u; ngx_http_fastcgi_ctx_t *f; ngx_http_fastcgi_loc_conf_t *flcf; #if (NGX_HTTP_CACHE) ngx_http_fastcgi_main_conf_t *fmcf; #endif if (ngx_http_upstream_create(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } f = ngx_pcalloc(r->pool, sizeof(ngx_http_fastcgi_ctx_t)); if (f == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_http_set_ctx(r, f, ngx_http_fastcgi_module); flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module); if (flcf->fastcgi_lengths) { if (ngx_http_fastcgi_eval(r, flcf) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } } u = r->upstream; ngx_str_set(&u->schema, "fastcgi://"); u->output.tag = (ngx_buf_tag_t) &ngx_http_fastcgi_module; u->conf = &flcf->upstream; #if (NGX_HTTP_CACHE) fmcf = ngx_http_get_module_main_conf(r, ngx_http_fastcgi_module); u->caches = &fmcf->caches; u->create_key = ngx_http_fastcgi_create_key; #endif u->create_request = ngx_http_fastcgi_create_request; u->reinit_request = ngx_http_fastcgi_reinit_request; u->process_header = ngx_http_fastcgi_process_header; u->abort_request = ngx_http_fastcgi_abort_request; u->finalize_request = ngx_http_fastcgi_finalize_request; r->state = 0; u->buffering = flcf->upstream.buffering; u->pipe = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t)); if (u->pipe == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } u->pipe->input_filter = ngx_http_fastcgi_input_filter; u->pipe->input_ctx = r; u->input_filter_init = ngx_http_fastcgi_input_filter_init; u->input_filter = ngx_http_fastcgi_non_buffered_filter; u->input_filter_ctx = r; if (!flcf->upstream.request_buffering && flcf->upstream.pass_request_body) { r->request_body_no_buffering = 1; } rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init); if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { return rc; } return NGX_DONE; } static ngx_int_t ngx_http_fastcgi_eval(ngx_http_request_t *r, ngx_http_fastcgi_loc_conf_t *flcf) { ngx_url_t url; ngx_http_upstream_t *u; ngx_memzero(&url, sizeof(ngx_url_t)); if (ngx_http_script_run(r, &url.url, flcf->fastcgi_lengths->elts, 0, flcf->fastcgi_values->elts) == NULL) { return NGX_ERROR; } url.no_resolve = 1; if (ngx_parse_url(r->pool, &url) != NGX_OK) { if (url.err) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "%s in upstream \"%V\"", url.err, &url.url); } return NGX_ERROR; } u = r->upstream; u->resolved = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t)); if (u->resolved == NULL) { return NGX_ERROR; } if (url.addrs) { u->resolved->sockaddr = url.addrs[0].sockaddr; u->resolved->socklen = url.addrs[0].socklen; u->resolved->name = url.addrs[0].name; u->resolved->naddrs = 1; } u->resolved->host = url.host; u->resolved->port = url.port; u->resolved->no_port = url.no_port; return NGX_OK; } #if (NGX_HTTP_CACHE) static ngx_int_t ngx_http_fastcgi_create_key(ngx_http_request_t *r) { ngx_str_t *key; ngx_http_fastcgi_loc_conf_t *flcf; key = ngx_array_push(&r->cache->keys); if (key == NULL) { return NGX_ERROR; } flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module); if (ngx_http_complex_value(r, &flcf->cache_key, key) != NGX_OK) { return NGX_ERROR; } return NGX_OK; } #endif static ngx_int_t ngx_http_fastcgi_create_request(ngx_http_request_t *r) { off_t file_pos; u_char ch, sep, *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, *hn, **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 (ngx_http_link_multi_headers(r) != NGX_OK) { return NGX_ERROR; } if (params->number || r->headers_in.multi) { 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; } for (n = 0; n < header_params; n++) { if (&header[i] == ignored[n]) { goto next_length; } } 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; } } key_len = sizeof("HTTP_") - 1 + header[i].key.len; val_len = header[i].value.len; for (hn = header[i].next; hn; hn = hn->next) { val_len += hn->value.len + 2; ignored[header_params++] = hn; } len += ((key_len > 127) ? 4 : 1) + key_len + ((val_len > 127) ? 4 : 1) + val_len; next_length: continue; } } 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_value; } } 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; for (hn = header[i].next; hn; hn = hn->next) { val_len += hn->value.len + 2; } 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, header[i].value.len); if (header[i].next) { if (header[i].key.len == sizeof("Cookie") - 1 && ngx_strncasecmp(header[i].key.data, (u_char *) "Cookie", sizeof("Cookie") - 1) == 0) { sep = ';'; } else { sep = ','; } for (hn = header[i].next; hn; hn = hn->next) { *b->last++ = sep; *b->last++ = ' '; b->last = ngx_copy(b->last, hn->value.data, hn->value.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_value: continue; } } if (padding) { ngx_memzero(b->last, padding); b->last += padding; } h = (ngx_http_fastcgi_header_t *) b->last; b->last += sizeof(ngx_http_fastcgi_header_t); h->version = 1; h->type = NGX_HTTP_FASTCGI_PARAMS; h->request_id_hi = 0; h->request_id_lo = 1; h->content_length_hi = 0; h->content_length_lo = 0; h->padding_length = 0; h->reserved = 0; if (r->request_body_no_buffering) { u->request_bufs = cl; u->output.output_filter = ngx_http_fastcgi_body_output_filter; u->output.filter_ctx = r; } else if (flcf->upstream.pass_request_body) { body = u->request_bufs; u->request_bufs = cl; #if (NGX_SUPPRESS_WARN) file_pos = 0; pos = NULL; #endif while (body) { if (ngx_buf_special(body->buf)) { body = body->next; continue; } if (body->buf->in_file) { file_pos = body->buf->file_pos; } else { pos = body->buf->pos; } next = 0; do { b = ngx_alloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } ngx_memcpy(b, body->buf, sizeof(ngx_buf_t)); if (body->buf->in_file) { b->file_pos = file_pos; file_pos += 32 * 1024; if (file_pos >= body->buf->file_last) { file_pos = body->buf->file_last; next = 1; } b->file_last = file_pos; len = (ngx_uint_t) (file_pos - b->file_pos); } else { b->pos = pos; b->start = pos; pos += 32 * 1024; if (pos >= body->buf->last) { pos = body->buf->last; next = 1; } b->last = pos; len = (ngx_uint_t) (pos - b->pos); } padding = 8 - len % 8; padding = (padding == 8) ? 0 : padding; h = (ngx_http_fastcgi_header_t *) cl->buf->last; cl->buf->last += sizeof(ngx_http_fastcgi_header_t); h->version = 1; h->type = NGX_HTTP_FASTCGI_STDIN; h->request_id_hi = 0; h->request_id_lo = 1; h->content_length_hi = (u_char) ((len >> 8) & 0xff); h->content_length_lo = (u_char) (len & 0xff); h->padding_length = (u_char) padding; h->reserved = 0; cl->next = ngx_alloc_chain_link(r->pool); if (cl->next == NULL) { return NGX_ERROR; } cl = cl->next; cl->buf = b; b = ngx_create_temp_buf(r->pool, sizeof(ngx_http_fastcgi_header_t) + padding); if (b == NULL) { return NGX_ERROR; } if (padding) { ngx_memzero(b->last, padding); b->last += padding; } cl->next = ngx_alloc_chain_link(r->pool); if (cl->next == NULL) { return NGX_ERROR; } cl = cl->next; cl->buf = b; } while (!next); body = body->next; } } else { u->request_bufs = cl; } if (!r->request_body_no_buffering) { h = (ngx_http_fastcgi_header_t *) cl->buf->last; cl->buf->last += sizeof(ngx_http_fastcgi_header_t); h->version = 1; h->type = NGX_HTTP_FASTCGI_STDIN; h->request_id_hi = 0; h->request_id_lo = 1; h->content_length_hi = 0; h->content_length_lo = 0; h->padding_length = 0; h->reserved = 0; } cl->next = NULL; return NGX_OK; } static ngx_int_t ngx_http_fastcgi_reinit_request(ngx_http_request_t *r) { ngx_http_fastcgi_ctx_t *f; f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module); if (f == NULL) { return NGX_OK; } f->state = ngx_http_fastcgi_st_version; f->fastcgi_stdout = 0; f->large_stderr = 0; if (f->split_parts) { f->split_parts->nelts = 0; } r->state = 0; return NGX_OK; } static ngx_int_t ngx_http_fastcgi_body_output_filter(void *data, ngx_chain_t *in) { ngx_http_request_t *r = data; off_t file_pos; u_char *pos, *start; size_t len, padding; ngx_buf_t *b; ngx_int_t rc; ngx_uint_t next, last; ngx_chain_t *cl, *tl, *out, **ll; ngx_http_fastcgi_ctx_t *f; ngx_http_fastcgi_header_t *h; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "fastcgi output filter"); f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module); if (in == NULL) { out = in; goto out; } out = NULL; ll = &out; if (!f->header_sent) { /* first buffer contains headers, pass it unmodified */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "fastcgi output header"); f->header_sent = 1; tl = ngx_alloc_chain_link(r->pool); if (tl == NULL) { return NGX_ERROR; } tl->buf = in->buf; *ll = tl; ll = &tl->next; in = in->next; if (in == NULL) { tl->next = NULL; goto out; } } cl = ngx_chain_get_free_buf(r->pool, &f->free); if (cl == NULL) { return NGX_ERROR; } b = cl->buf; b->tag = (ngx_buf_tag_t) &ngx_http_fastcgi_body_output_filter; b->temporary = 1; if (b->start == NULL) { /* reserve space for maximum possible padding, 7 bytes */ b->start = ngx_palloc(r->pool, sizeof(ngx_http_fastcgi_header_t) + 7); if (b->start == NULL) { return NGX_ERROR; } b->pos = b->start; b->last = b->start; b->end = b->start + sizeof(ngx_http_fastcgi_header_t) + 7; } *ll = cl; last = 0; padding = 0; #if (NGX_SUPPRESS_WARN) file_pos = 0; pos = NULL; #endif while (in) { ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0, "fastcgi output in l:%d f:%d %p, pos %p, size: %z " "file: %O, size: %O", in->buf->last_buf, in->buf->in_file, in->buf->start, in->buf->pos, in->buf->last - in->buf->pos, in->buf->file_pos, in->buf->file_last - in->buf->file_pos); if (in->buf->last_buf) { last = 1; } if (ngx_buf_special(in->buf)) { in = in->next; continue; } if (in->buf->in_file) { file_pos = in->buf->file_pos; } else { pos = in->buf->pos; } next = 0; do { tl = ngx_chain_get_free_buf(r->pool, &f->free); if (tl == NULL) { return NGX_ERROR; } b = tl->buf; start = b->start; ngx_memcpy(b, in->buf, sizeof(ngx_buf_t)); /* * restore b->start to preserve memory allocated in the buffer, * to reuse it later for headers and padding */ b->start = start; if (in->buf->in_file) { b->file_pos = file_pos; file_pos += 32 * 1024; if (file_pos >= in->buf->file_last) { file_pos = in->buf->file_last; next = 1; } b->file_last = file_pos; len = (ngx_uint_t) (file_pos - b->file_pos); } else { b->pos = pos; pos += 32 * 1024; if (pos >= in->buf->last) { pos = in->buf->last; next = 1; } b->last = pos; len = (ngx_uint_t) (pos - b->pos); } b->tag = (ngx_buf_tag_t) &ngx_http_fastcgi_body_output_filter; b->shadow = in->buf; b->last_shadow = next; b->last_buf = 0; b->last_in_chain = 0; padding = 8 - len % 8; padding = (padding == 8) ? 0 : padding; h = (ngx_http_fastcgi_header_t *) cl->buf->last; cl->buf->last += sizeof(ngx_http_fastcgi_header_t); h->version = 1; h->type = NGX_HTTP_FASTCGI_STDIN; h->request_id_hi = 0; h->request_id_lo = 1; h->content_length_hi = (u_char) ((len >> 8) & 0xff); h->content_length_lo = (u_char) (len & 0xff); h->padding_length = (u_char) padding; h->reserved = 0; cl->next = tl; cl = tl; tl = ngx_chain_get_free_buf(r->pool, &f->free); if (tl == NULL) { return NGX_ERROR; } b = tl->buf; b->tag = (ngx_buf_tag_t) &ngx_http_fastcgi_body_output_filter; b->temporary = 1; if (b->start == NULL) { /* reserve space for maximum possible padding, 7 bytes */ b->start = ngx_palloc(r->pool, sizeof(ngx_http_fastcgi_header_t) + 7); if (b->start == NULL) { return NGX_ERROR; } b->pos = b->start; b->last = b->start; b->end = b->start + sizeof(ngx_http_fastcgi_header_t) + 7; } if (padding) { ngx_memzero(b->last, padding); b->last += padding; } cl->next = tl; cl = tl; } while (!next); in = in->next; } if (last) { h = (ngx_http_fastcgi_header_t *) cl->buf->last; cl->buf->last += sizeof(ngx_http_fastcgi_header_t); h->version = 1; h->type = NGX_HTTP_FASTCGI_STDIN; h->request_id_hi = 0; h->request_id_lo = 1; h->content_length_hi = 0; h->content_length_lo = 0; h->padding_length = 0; h->reserved = 0; cl->buf->last_buf = 1; } else if (padding == 0) { /* TODO: do not allocate buffers instead */ cl->buf->temporary = 0; cl->buf->sync = 1; } cl->next = NULL; out: #if (NGX_DEBUG) for (cl = out; cl; cl = cl->next) { ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0, "fastcgi output out l:%d f:%d %p, pos %p, size: %z " "file: %O, size: %O", cl->buf->last_buf, cl->buf->in_file, cl->buf->start, cl->buf->pos, cl->buf->last - cl->buf->pos, cl->buf->file_pos, cl->buf->file_last - cl->buf->file_pos); } #endif rc = ngx_chain_writer(&r->upstream->writer, out); ngx_chain_update_chains(r->pool, &f->free, &f->busy, &out, (ngx_buf_tag_t) &ngx_http_fastcgi_body_output_filter); for (cl = f->free; cl; cl = cl->next) { /* mark original buffers as sent */ if (cl->buf->shadow) { if (cl->buf->last_shadow) { b = cl->buf->shadow; b->pos = b->last; } cl->buf->shadow = NULL; } } return rc; } static ngx_int_t ngx_http_fastcgi_process_header(ngx_http_request_t *r) { u_char *p, *msg, *start, *last, *part_start, *part_end; size_t size; ngx_str_t *status_line, *pattern; ngx_int_t rc, status; ngx_buf_t buf; ngx_uint_t i; ngx_table_elt_t *h; ngx_http_upstream_t *u; ngx_http_fastcgi_ctx_t *f; ngx_http_upstream_header_t *hh; ngx_http_fastcgi_loc_conf_t *flcf; ngx_http_fastcgi_split_part_t *part; ngx_http_upstream_main_conf_t *umcf; f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module); umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module); u = r->upstream; for ( ;; ) { if (f->state < ngx_http_fastcgi_st_data) { f->pos = u->buffer.pos; f->last = u->buffer.last; rc = ngx_http_fastcgi_process_record(r, f); u->buffer.pos = f->pos; u->buffer.last = f->last; if (rc == NGX_AGAIN) { return NGX_AGAIN; } if (rc == NGX_ERROR) { return NGX_HTTP_UPSTREAM_INVALID_HEADER; } if (f->type != NGX_HTTP_FASTCGI_STDOUT && f->type != NGX_HTTP_FASTCGI_STDERR) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent unexpected FastCGI record: %ui", f->type); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } if (f->type == NGX_HTTP_FASTCGI_STDOUT && f->length == 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream prematurely closed FastCGI stdout"); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } } if (f->state == ngx_http_fastcgi_st_padding) { if (u->buffer.pos + f->padding < u->buffer.last) { f->state = ngx_http_fastcgi_st_version; u->buffer.pos += f->padding; continue; } if (u->buffer.pos + f->padding == u->buffer.last) { f->state = ngx_http_fastcgi_st_version; u->buffer.pos = u->buffer.last; return NGX_AGAIN; } f->padding -= u->buffer.last - u->buffer.pos; u->buffer.pos = u->buffer.last; return NGX_AGAIN; } /* f->state == ngx_http_fastcgi_st_data */ if (f->type == NGX_HTTP_FASTCGI_STDERR) { if (f->length) { msg = u->buffer.pos; if (u->buffer.pos + f->length <= u->buffer.last) { u->buffer.pos += f->length; f->length = 0; f->state = ngx_http_fastcgi_st_padding; } else { f->length -= u->buffer.last - u->buffer.pos; u->buffer.pos = u->buffer.last; } for (p = u->buffer.pos - 1; msg < p; p--) { if (*p != LF && *p != CR && *p != '.' && *p != ' ') { break; } } p++; ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "FastCGI sent in stderr: \"%*s\"", p - msg, msg); flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module); if (flcf->catch_stderr) { pattern = flcf->catch_stderr->elts; for (i = 0; i < flcf->catch_stderr->nelts; i++) { if (ngx_strnstr(msg, (char *) pattern[i].data, p - msg) != NULL) { return NGX_HTTP_UPSTREAM_INVALID_HEADER; } } } if (u->buffer.pos == u->buffer.last) { if (!f->fastcgi_stdout) { /* * the special handling the large number * of the PHP warnings to not allocate memory */ #if (NGX_HTTP_CACHE) if (r->cache) { u->buffer.pos = u->buffer.start + r->cache->header_start; } else { u->buffer.pos = u->buffer.start; } #else u->buffer.pos = u->buffer.start; #endif u->buffer.last = u->buffer.pos; f->large_stderr = 1; } return NGX_AGAIN; } } else { f->state = ngx_http_fastcgi_st_padding; } continue; } /* f->type == NGX_HTTP_FASTCGI_STDOUT */ #if (NGX_HTTP_CACHE) if (f->large_stderr && r->cache) { ssize_t len; ngx_http_fastcgi_header_t *fh; start = u->buffer.start + r->cache->header_start; len = u->buffer.pos - start - 2 * sizeof(ngx_http_fastcgi_header_t); /* * A tail of large stderr output before HTTP header is placed * in a cache file without a FastCGI record header. * To workaround it we put a dummy FastCGI record header at the * start of the stderr output or update r->cache_header_start, * if there is no enough place for the record header. */ if (len >= 0) { fh = (ngx_http_fastcgi_header_t *) start; fh->version = 1; fh->type = NGX_HTTP_FASTCGI_STDERR; fh->request_id_hi = 0; fh->request_id_lo = 1; fh->content_length_hi = (u_char) ((len >> 8) & 0xff); fh->content_length_lo = (u_char) (len & 0xff); fh->padding_length = 0; fh->reserved = 0; } else { r->cache->header_start += u->buffer.pos - start - sizeof(ngx_http_fastcgi_header_t); } f->large_stderr = 0; } #endif f->fastcgi_stdout = 1; start = u->buffer.pos; if (u->buffer.pos + f->length < u->buffer.last) { /* * set u->buffer.last to the end of the FastCGI record data * for ngx_http_parse_header_line() */ last = u->buffer.last; u->buffer.last = u->buffer.pos + f->length; } else { last = NULL; } for ( ;; ) { part_start = u->buffer.pos; part_end = u->buffer.last; rc = ngx_http_parse_header_line(r, &u->buffer, 1); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http fastcgi parser: %i", rc); if (rc == NGX_AGAIN) { break; } if (rc == NGX_OK) { /* a header line has been parsed successfully */ h = ngx_list_push(&u->headers_in.headers); if (h == NULL) { return NGX_ERROR; } if (f->split_parts && f->split_parts->nelts) { part = f->split_parts->elts; size = u->buffer.pos - part_start; for (i = 0; i < f->split_parts->nelts; i++) { size += part[i].end - part[i].start; } p = ngx_pnalloc(r->pool, size); if (p == NULL) { h->hash = 0; return NGX_ERROR; } buf.pos = p; for (i = 0; i < f->split_parts->nelts; i++) { p = ngx_cpymem(p, part[i].start, part[i].end - part[i].start); } p = ngx_cpymem(p, part_start, u->buffer.pos - part_start); buf.last = p; f->split_parts->nelts = 0; rc = ngx_http_parse_header_line(r, &buf, 1); if (rc != NGX_OK) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "invalid header after joining " "FastCGI records"); h->hash = 0; return NGX_ERROR; } h->key.len = r->header_name_end - r->header_name_start; h->key.data = r->header_name_start; h->key.data[h->key.len] = '\0'; h->value.len = r->header_end - r->header_start; h->value.data = r->header_start; h->value.data[h->value.len] = '\0'; h->lowcase_key = ngx_pnalloc(r->pool, h->key.len); if (h->lowcase_key == NULL) { return NGX_ERROR; } } else { h->key.len = r->header_name_end - r->header_name_start; h->value.len = r->header_end - r->header_start; h->key.data = ngx_pnalloc(r->pool, h->key.len + 1 + h->value.len + 1 + h->key.len); if (h->key.data == NULL) { h->hash = 0; return NGX_ERROR; } h->value.data = h->key.data + h->key.len + 1; h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1; ngx_memcpy(h->key.data, r->header_name_start, h->key.len); h->key.data[h->key.len] = '\0'; ngx_memcpy(h->value.data, r->header_start, h->value.len); h->value.data[h->value.len] = '\0'; } h->hash = r->header_hash; if (h->key.len == r->lowcase_index) { ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len); } else { ngx_strlow(h->lowcase_key, h->key.data, h->key.len); } hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len); if (hh) { rc = hh->handler(r, h, hh->offset); if (rc != NGX_OK) { return rc; } } 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; } /* rc == NGX_HTTP_PARSE_INVALID_HEADER */ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid header: \"%*s\\x%02xd...\"", r->header_end - r->header_name_start, r->header_name_start, *r->header_end); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } 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_upstream_t *u; ngx_http_fastcgi_ctx_t *f; ngx_http_fastcgi_loc_conf_t *flcf; u = r->upstream; f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module); flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module); u->pipe->length = flcf->keep_conn ? (off_t) sizeof(ngx_http_fastcgi_header_t) : -1; if (u->headers_in.status_n == NGX_HTTP_NO_CONTENT || u->headers_in.status_n == NGX_HTTP_NOT_MODIFIED) { f->rest = 0; } else if (r->method == NGX_HTTP_HEAD) { f->rest = -2; } else { f->rest = u->headers_in.content_length_n; } return NGX_OK; } 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); if (p->upstream_done || f->closed) { r->upstream->keepalive = 0; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, p->log, 0, "http fastcgi data after close"); return NGX_OK; } b = NULL; prev = &buf->shadow; 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, p->log, 0, "http fastcgi closed stdout"); if (f->rest > 0) { ngx_log_error(NGX_LOG_ERR, p->log, 0, "upstream prematurely closed " "FastCGI stdout"); p->upstream_error = 1; p->upstream_eof = 0; f->closed = 1; break; } if (!flcf->keep_conn) { p->upstream_done = 1; } 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 (f->rest > 0) { ngx_log_error(NGX_LOG_ERR, p->log, 0, "upstream prematurely closed " "FastCGI request"); p->upstream_error = 1; p->upstream_eof = 0; f->closed = 1; break; } if (!flcf->keep_conn) { p->upstream_done = 1; break; } 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; } if (f->rest == -2) { f->rest = r->upstream->headers_in.content_length_n; } if (f->rest == 0) { ngx_log_error(NGX_LOG_WARN, p->log, 0, "upstream sent more data than specified in " "\"Content-Length\" header"); p->upstream_done = 1; break; } cl = ngx_chain_get_free_buf(p->pool, &p->free); if (cl == NULL) { return NGX_ERROR; } 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; } else { f->length -= f->last - f->pos; f->pos = f->last; b->last = f->last; } if (f->rest > 0) { if (b->last - b->pos > f->rest) { ngx_log_error(NGX_LOG_WARN, p->log, 0, "upstream sent more data than specified in " "\"Content-Length\" header"); b->last = b->pos + f->rest; p->upstream_done = 1; break; } f->rest -= b->last - b->pos; } } 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->rest > 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream prematurely closed " "FastCGI request"); u->error = 1; break; } if (f->pos + f->padding < f->last) { u->length = 0; break; } 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; } if (f->rest == 0) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "upstream sent more data than specified in " "\"Content-Length\" header"); u->length = 0; break; } cl = ngx_chain_get_free_buf(r->pool, &u->free_bufs); if (cl == NULL) { return NGX_ERROR; } *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; } else { f->length -= f->last - f->pos; f->pos = f->last; b->last = f->last; } if (f->rest > 0) { if (b->last - b->pos > f->rest) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "upstream sent more data than specified in " "\"Content-Length\" header"); b->last = b->pos + f->rest; u->length = 0; break; } f->rest -= b->last - b->pos; } } return NGX_OK; } static ngx_int_t ngx_http_fastcgi_process_record(ngx_http_request_t *r, ngx_http_fastcgi_ctx_t *f) { u_char ch, *p; ngx_http_fastcgi_state_e state; state = f->state; for (p = f->pos; p < f->last; p++) { ch = *p; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http fastcgi record byte: %02Xd", ch); switch (state) { case ngx_http_fastcgi_st_version: if (ch != 1) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent unsupported FastCGI " "protocol version: %d", ch); return NGX_ERROR; } state = ngx_http_fastcgi_st_type; break; case ngx_http_fastcgi_st_type: switch (ch) { case NGX_HTTP_FASTCGI_STDOUT: case NGX_HTTP_FASTCGI_STDERR: case NGX_HTTP_FASTCGI_END_REQUEST: f->type = (ngx_uint_t) ch; break; default: ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid FastCGI " "record type: %d", ch); return NGX_ERROR; } state = ngx_http_fastcgi_st_request_id_hi; break; /* we support the single request per connection */ case ngx_http_fastcgi_st_request_id_hi: if (ch != 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent unexpected FastCGI " "request id high byte: %d", ch); return NGX_ERROR; } state = ngx_http_fastcgi_st_request_id_lo; break; case ngx_http_fastcgi_st_request_id_lo: if (ch != 1) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent unexpected FastCGI " "request id low byte: %d", ch); return NGX_ERROR; } state = ngx_http_fastcgi_st_content_length_hi; break; case ngx_http_fastcgi_st_content_length_hi: f->length = ch << 8; state = ngx_http_fastcgi_st_content_length_lo; break; case ngx_http_fastcgi_st_content_length_lo: f->length |= (size_t) ch; state = ngx_http_fastcgi_st_padding_length; break; case ngx_http_fastcgi_st_padding_length: f->padding = (size_t) ch; state = ngx_http_fastcgi_st_reserved; break; case ngx_http_fastcgi_st_reserved: state = ngx_http_fastcgi_st_data; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http fastcgi record length: %z", f->length); f->pos = p + 1; f->state = state; return NGX_OK; /* suppress warning */ case ngx_http_fastcgi_st_data: case ngx_http_fastcgi_st_padding: break; } } f->pos = p; f->state = state; return NGX_AGAIN; } static void ngx_http_fastcgi_abort_request(ngx_http_request_t *r) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "abort http fastcgi request"); return; } static void ngx_http_fastcgi_finalize_request(ngx_http_request_t *r, ngx_int_t rc) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "finalize http fastcgi request"); return; } static ngx_int_t ngx_http_fastcgi_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var, *v; for (v = ngx_http_fastcgi_vars; v->name.len; v++) { var = ngx_http_add_variable(cf, &v->name, v->flags); if (var == NULL) { return NGX_ERROR; } var->get_handler = v->get_handler; var->data = v->data; } return NGX_OK; } static void * ngx_http_fastcgi_create_main_conf(ngx_conf_t *cf) { ngx_http_fastcgi_main_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_fastcgi_main_conf_t)); if (conf == NULL) { return NULL; } #if (NGX_HTTP_CACHE) if (ngx_array_init(&conf->caches, cf->pool, 4, sizeof(ngx_http_file_cache_t *)) != NGX_OK) { return NULL; } #endif return conf; } static void * ngx_http_fastcgi_create_loc_conf(ngx_conf_t *cf) { ngx_http_fastcgi_loc_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_fastcgi_loc_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->upstream.bufs.num = 0; * conf->upstream.ignore_headers = 0; * conf->upstream.next_upstream = 0; * conf->upstream.cache_zone = NULL; * conf->upstream.cache_use_stale = 0; * conf->upstream.cache_methods = 0; * conf->upstream.temp_path = NULL; * conf->upstream.hide_headers_hash = { NULL, 0 }; * conf->upstream.store_lengths = NULL; * conf->upstream.store_values = NULL; * * conf->index.len = { 0, NULL }; */ conf->upstream.store = NGX_CONF_UNSET; conf->upstream.store_access = NGX_CONF_UNSET_UINT; conf->upstream.next_upstream_tries = NGX_CONF_UNSET_UINT; conf->upstream.buffering = NGX_CONF_UNSET; conf->upstream.request_buffering = NGX_CONF_UNSET; conf->upstream.ignore_client_abort = NGX_CONF_UNSET; conf->upstream.force_ranges = NGX_CONF_UNSET; conf->upstream.local = NGX_CONF_UNSET_PTR; conf->upstream.socket_keepalive = NGX_CONF_UNSET; conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.next_upstream_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.send_lowat = NGX_CONF_UNSET_SIZE; conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE; conf->upstream.limit_rate = NGX_CONF_UNSET_SIZE; conf->upstream.busy_buffers_size_conf = NGX_CONF_UNSET_SIZE; conf->upstream.max_temp_file_size_conf = NGX_CONF_UNSET_SIZE; conf->upstream.temp_file_write_size_conf = NGX_CONF_UNSET_SIZE; conf->upstream.pass_request_headers = NGX_CONF_UNSET; conf->upstream.pass_request_body = NGX_CONF_UNSET; #if (NGX_HTTP_CACHE) conf->upstream.cache = NGX_CONF_UNSET; conf->upstream.cache_min_uses = NGX_CONF_UNSET_UINT; conf->upstream.cache_max_range_offset = NGX_CONF_UNSET; conf->upstream.cache_bypass = NGX_CONF_UNSET_PTR; conf->upstream.no_cache = NGX_CONF_UNSET_PTR; conf->upstream.cache_valid = NGX_CONF_UNSET_PTR; conf->upstream.cache_lock = NGX_CONF_UNSET; conf->upstream.cache_lock_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.cache_lock_age = NGX_CONF_UNSET_MSEC; conf->upstream.cache_revalidate = NGX_CONF_UNSET; conf->upstream.cache_background_update = NGX_CONF_UNSET; #endif conf->upstream.hide_headers = NGX_CONF_UNSET_PTR; conf->upstream.pass_headers = NGX_CONF_UNSET_PTR; conf->upstream.intercept_errors = NGX_CONF_UNSET; /* "fastcgi_cyclic_temp_file" is disabled */ conf->upstream.cyclic_temp_file = 0; conf->upstream.change_buffering = 1; conf->catch_stderr = NGX_CONF_UNSET_PTR; conf->keep_conn = NGX_CONF_UNSET; ngx_str_set(&conf->upstream.module, "fastcgi"); return conf; } static char * ngx_http_fastcgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_fastcgi_loc_conf_t *prev = parent; ngx_http_fastcgi_loc_conf_t *conf = child; size_t size; ngx_int_t rc; ngx_hash_init_t hash; ngx_http_core_loc_conf_t *clcf; #if (NGX_HTTP_CACHE) if (conf->upstream.store > 0) { conf->upstream.cache = 0; } if (conf->upstream.cache > 0) { conf->upstream.store = 0; } #endif if (conf->upstream.store == NGX_CONF_UNSET) { ngx_conf_merge_value(conf->upstream.store, prev->upstream.store, 0); conf->upstream.store_lengths = prev->upstream.store_lengths; conf->upstream.store_values = prev->upstream.store_values; } ngx_conf_merge_uint_value(conf->upstream.store_access, prev->upstream.store_access, 0600); ngx_conf_merge_uint_value(conf->upstream.next_upstream_tries, prev->upstream.next_upstream_tries, 0); ngx_conf_merge_value(conf->upstream.buffering, prev->upstream.buffering, 1); ngx_conf_merge_value(conf->upstream.request_buffering, prev->upstream.request_buffering, 1); ngx_conf_merge_value(conf->upstream.ignore_client_abort, prev->upstream.ignore_client_abort, 0); ngx_conf_merge_value(conf->upstream.force_ranges, prev->upstream.force_ranges, 0); ngx_conf_merge_ptr_value(conf->upstream.local, prev->upstream.local, NULL); ngx_conf_merge_value(conf->upstream.socket_keepalive, prev->upstream.socket_keepalive, 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.send_lowat, prev->upstream.send_lowat, 0); ngx_conf_merge_size_value(conf->upstream.buffer_size, prev->upstream.buffer_size, (size_t) ngx_pagesize); ngx_conf_merge_size_value(conf->upstream.limit_rate, prev->upstream.limit_rate, 0); ngx_conf_merge_bufs_value(conf->upstream.bufs, prev->upstream.bufs, 8, ngx_pagesize); if (conf->upstream.bufs.num < 2) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "there must be at least 2 \"fastcgi_buffers\""); return NGX_CONF_ERROR; } size = conf->upstream.buffer_size; if (size < conf->upstream.bufs.size) { size = conf->upstream.bufs.size; } ngx_conf_merge_size_value(conf->upstream.busy_buffers_size_conf, prev->upstream.busy_buffers_size_conf, NGX_CONF_UNSET_SIZE); if (conf->upstream.busy_buffers_size_conf == NGX_CONF_UNSET_SIZE) { conf->upstream.busy_buffers_size = 2 * size; } else { conf->upstream.busy_buffers_size = conf->upstream.busy_buffers_size_conf; } if (conf->upstream.busy_buffers_size < size) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"fastcgi_busy_buffers_size\" must be equal to or greater than " "the maximum of the value of \"fastcgi_buffer_size\" and " "one of the \"fastcgi_buffers\""); return NGX_CONF_ERROR; } if (conf->upstream.busy_buffers_size > (conf->upstream.bufs.num - 1) * conf->upstream.bufs.size) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"fastcgi_busy_buffers_size\" must be less than " "the size of all \"fastcgi_buffers\" minus one buffer"); return NGX_CONF_ERROR; } ngx_conf_merge_size_value(conf->upstream.temp_file_write_size_conf, prev->upstream.temp_file_write_size_conf, NGX_CONF_UNSET_SIZE); if (conf->upstream.temp_file_write_size_conf == NGX_CONF_UNSET_SIZE) { conf->upstream.temp_file_write_size = 2 * size; } else { conf->upstream.temp_file_write_size = conf->upstream.temp_file_write_size_conf; } if (conf->upstream.temp_file_write_size < size) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"fastcgi_temp_file_write_size\" must be equal to or greater " "than the maximum of the value of \"fastcgi_buffer_size\" and " "one of the \"fastcgi_buffers\""); return NGX_CONF_ERROR; } ngx_conf_merge_size_value(conf->upstream.max_temp_file_size_conf, prev->upstream.max_temp_file_size_conf, NGX_CONF_UNSET_SIZE); if (conf->upstream.max_temp_file_size_conf == NGX_CONF_UNSET_SIZE) { conf->upstream.max_temp_file_size = 1024 * 1024 * 1024; } else { conf->upstream.max_temp_file_size = conf->upstream.max_temp_file_size_conf; } if (conf->upstream.max_temp_file_size != 0 && conf->upstream.max_temp_file_size < size) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"fastcgi_max_temp_file_size\" must be equal to zero to disable " "temporary files usage or must be equal to or greater than " "the maximum of the value of \"fastcgi_buffer_size\" and " "one of the \"fastcgi_buffers\""); return NGX_CONF_ERROR; } ngx_conf_merge_bitmask_value(conf->upstream.ignore_headers, prev->upstream.ignore_headers, NGX_CONF_BITMASK_SET); ngx_conf_merge_bitmask_value(conf->upstream.next_upstream, prev->upstream.next_upstream, (NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_ERROR |NGX_HTTP_UPSTREAM_FT_TIMEOUT)); if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) { conf->upstream.next_upstream = NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_OFF; } if (ngx_conf_merge_path_value(cf, &conf->upstream.temp_path, prev->upstream.temp_path, &ngx_http_fastcgi_temp_path) != NGX_OK) { return NGX_CONF_ERROR; } #if (NGX_HTTP_CACHE) if (conf->upstream.cache == NGX_CONF_UNSET) { ngx_conf_merge_value(conf->upstream.cache, prev->upstream.cache, 0); conf->upstream.cache_zone = prev->upstream.cache_zone; conf->upstream.cache_value = prev->upstream.cache_value; } if (conf->upstream.cache_zone && conf->upstream.cache_zone->data == NULL) { ngx_shm_zone_t *shm_zone; shm_zone = conf->upstream.cache_zone; ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"fastcgi_cache\" zone \"%V\" is unknown", &shm_zone->shm.name); return NGX_CONF_ERROR; } ngx_conf_merge_uint_value(conf->upstream.cache_min_uses, prev->upstream.cache_min_uses, 1); ngx_conf_merge_off_value(conf->upstream.cache_max_range_offset, prev->upstream.cache_max_range_offset, NGX_MAX_OFF_T_VALUE); ngx_conf_merge_bitmask_value(conf->upstream.cache_use_stale, prev->upstream.cache_use_stale, (NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_OFF)); if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_OFF) { conf->upstream.cache_use_stale = NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_OFF; } if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_ERROR) { conf->upstream.cache_use_stale |= NGX_HTTP_UPSTREAM_FT_NOLIVE; } if (conf->upstream.cache_methods == 0) { conf->upstream.cache_methods = prev->upstream.cache_methods; } conf->upstream.cache_methods |= NGX_HTTP_GET|NGX_HTTP_HEAD; ngx_conf_merge_ptr_value(conf->upstream.cache_bypass, prev->upstream.cache_bypass, NULL); ngx_conf_merge_ptr_value(conf->upstream.no_cache, prev->upstream.no_cache, NULL); ngx_conf_merge_ptr_value(conf->upstream.cache_valid, prev->upstream.cache_valid, NULL); if (conf->cache_key.value.data == NULL) { conf->cache_key = prev->cache_key; } if (conf->upstream.cache && conf->cache_key.value.data == NULL) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "no \"fastcgi_cache_key\" for \"fastcgi_cache\""); } ngx_conf_merge_value(conf->upstream.cache_lock, prev->upstream.cache_lock, 0); ngx_conf_merge_msec_value(conf->upstream.cache_lock_timeout, prev->upstream.cache_lock_timeout, 5000); ngx_conf_merge_msec_value(conf->upstream.cache_lock_age, prev->upstream.cache_lock_age, 5000); ngx_conf_merge_value(conf->upstream.cache_revalidate, prev->upstream.cache_revalidate, 0); ngx_conf_merge_value(conf->upstream.cache_background_update, prev->upstream.cache_background_update, 0); #endif ngx_conf_merge_value(conf->upstream.pass_request_headers, prev->upstream.pass_request_headers, 1); ngx_conf_merge_value(conf->upstream.pass_request_body, prev->upstream.pass_request_body, 1); ngx_conf_merge_value(conf->upstream.intercept_errors, prev->upstream.intercept_errors, 0); ngx_conf_merge_ptr_value(conf->catch_stderr, prev->catch_stderr, NULL); ngx_conf_merge_value(conf->keep_conn, prev->keep_conn, 0); ngx_conf_merge_str_value(conf->index, prev->index, ""); hash.max_size = 512; hash.bucket_size = ngx_align(64, ngx_cacheline_size); hash.name = "fastcgi_hide_headers_hash"; if (ngx_http_upstream_hide_headers_hash(cf, &conf->upstream, &prev->upstream, ngx_http_fastcgi_hide_headers, &hash) != NGX_OK) { return NGX_CONF_ERROR; } clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); if (clcf->noname && conf->upstream.upstream == NULL && conf->fastcgi_lengths == NULL) { conf->upstream.upstream = prev->upstream.upstream; conf->fastcgi_lengths = prev->fastcgi_lengths; conf->fastcgi_values = prev->fastcgi_values; } if (clcf->lmt_excpt && clcf->handler == NULL && (conf->upstream.upstream || conf->fastcgi_lengths)) { clcf->handler = ngx_http_fastcgi_handler; } #if (NGX_PCRE) if (conf->split_regex == NULL) { conf->split_regex = prev->split_regex; conf->split_name = prev->split_name; } #endif if (conf->params_source == NULL) { conf->params = prev->params; #if (NGX_HTTP_CACHE) conf->params_cache = prev->params_cache; #endif conf->params_source = prev->params_source; } rc = ngx_http_fastcgi_init_params(cf, conf, &conf->params, NULL); if (rc != NGX_OK) { return NGX_CONF_ERROR; } #if (NGX_HTTP_CACHE) if (conf->upstream.cache) { rc = ngx_http_fastcgi_init_params(cf, conf, &conf->params_cache, ngx_http_fastcgi_cache_headers); if (rc != NGX_OK) { return NGX_CONF_ERROR; } } #endif /* * special handling to preserve conf->params in the "http" section * to inherit it to all servers */ if (prev->params.hash.buckets == NULL && conf->params_source == prev->params_source) { prev->params = conf->params; #if (NGX_HTTP_CACHE) prev->params_cache = conf->params_cache; #endif } return NGX_CONF_OK; } static ngx_int_t ngx_http_fastcgi_init_params(ngx_conf_t *cf, ngx_http_fastcgi_loc_conf_t *conf, ngx_http_fastcgi_params_t *params, ngx_keyval_t *default_params) { u_char *p; size_t size; uintptr_t *code; ngx_uint_t i, nsrc; ngx_array_t headers_names, params_merged; ngx_keyval_t *h; ngx_hash_key_t *hk; ngx_hash_init_t hash; ngx_http_upstream_param_t *src, *s; ngx_http_script_compile_t sc; ngx_http_script_copy_code_t *copy; if (params->hash.buckets) { return NGX_OK; } if (conf->params_source == NULL && default_params == NULL) { params->hash.buckets = (void *) 1; return NGX_OK; } params->lengths = ngx_array_create(cf->pool, 64, 1); if (params->lengths == NULL) { return NGX_ERROR; } params->values = ngx_array_create(cf->pool, 512, 1); if (params->values == NULL) { return NGX_ERROR; } if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t)) != NGX_OK) { return NGX_ERROR; } if (conf->params_source) { src = conf->params_source->elts; nsrc = conf->params_source->nelts; } else { src = NULL; nsrc = 0; } if (default_params) { if (ngx_array_init(¶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) (void *) ngx_http_script_copy_len_code; copy->len = src[i].key.len; copy = ngx_array_push_n(params->lengths, sizeof(ngx_http_script_copy_code_t)); if (copy == NULL) { return NGX_ERROR; } copy->code = (ngx_http_script_code_pt) (void *) ngx_http_script_copy_len_code; copy->len = src[i].skip_empty; size = (sizeof(ngx_http_script_copy_code_t) + src[i].key.len + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1); copy = ngx_array_push_n(params->values, size); if (copy == NULL) { return NGX_ERROR; } copy->code = ngx_http_script_copy_code; copy->len = src[i].key.len; p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t); ngx_memcpy(p, src[i].key.data, src[i].key.len); ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); sc.cf = cf; sc.source = &src[i].value; sc.flushes = ¶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.len && 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; } nginx-1.24.0/src/http/modules/perl/000755 001751 001751 00000000000 14415135700 020267 5ustar00mdouninmdounin000000 000000 nginx-1.24.0/src/http/modules/ngx_http_flv_module.c000644 001751 001751 00000014516 14415135676 023563 0ustar00mdouninmdounin000000 000000 /* * 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) { return NGX_DECLINED; } r->root_tested = !r->error_page; start = 0; len = of.size; i = 1; if (r->args.len) { if (ngx_http_arg(r, (u_char *) "start", 5, &value) == NGX_OK) { start = ngx_atoof(value.data, value.len); if (start == NGX_ERROR || start >= len) { start = 0; } if (start) { len = sizeof(ngx_flv_header) - 1 + len - start; i = 0; } } } log->action = "sending flv to client"; r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = len; r->headers_out.last_modified_time = of.mtime; if (ngx_http_set_etag(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_http_set_content_type(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (i == 0) { b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } b->pos = ngx_flv_header; b->last = ngx_flv_header + sizeof(ngx_flv_header) - 1; b->memory = 1; out[0].buf = b; out[0].next = &out[1]; } b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t)); if (b->file == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } r->allow_ranges = 1; rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { return rc; } b->file_pos = start; b->file_last = of.size; b->in_file = b->file_last ? 1 : 0; b->last_buf = (r == r->main) ? 1 : 0; b->last_in_chain = 1; b->sync = (b->last_buf || b->in_file) ? 0 : 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; } nginx-1.24.0/src/http/modules/ngx_http_geo_module.c000644 001751 001751 00000126046 14415135676 023550 0ustar00mdouninmdounin000000 000000 /* * 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 #if (NGX_HAVE_UNIX_DOMAIN) case AF_UNIX: vv = (ngx_http_variable_value_t *) ngx_radix32tree_find(ctx->u.trees.tree, INADDR_NONE); 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 #if (NGX_HAVE_UNIX_DOMAIN) case AF_UNIX: 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_table_elt_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 != NULL && ctx->proxies != NULL) { (void) ngx_http_get_forwarded_addr(r, addr, xfwd, NULL, ctx->proxies, ctx->proxy_recursive); } return NGX_OK; } static ngx_int_t ngx_http_geo_real_addr(ngx_http_request_t *r, ngx_http_geo_ctx_t *ctx, ngx_addr_t *addr) { ngx_http_variable_value_t *v; if (ctx->index == -1) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http geo started: %V", &r->connection->addr_text); addr->sockaddr = r->connection->sockaddr; addr->socklen = r->connection->socklen; /* addr->name = r->connection->addr_text; */ return NGX_OK; } v = ngx_http_get_flushed_variable(r, ctx->index); if (v == NULL || v->not_found) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http geo not found"); return NGX_ERROR; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http geo started: %v", v); if (ngx_parse_addr(r->pool, addr, v->data, v->len) == NGX_OK) { return NGX_OK; } return NGX_ERROR; } static char * ngx_http_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { char *rv; size_t len; ngx_str_t *value, name; ngx_uint_t i; ngx_conf_t save; ngx_pool_t *pool; ngx_array_t *a; ngx_http_variable_t *var; ngx_http_geo_ctx_t *geo; ngx_http_geo_conf_ctx_t ctx; #if (NGX_HAVE_INET6) static struct in6_addr zero; #endif value = cf->args->elts; geo = ngx_palloc(cf->pool, sizeof(ngx_http_geo_ctx_t)); if (geo == NULL) { return NGX_CONF_ERROR; } name = value[1]; if (name.data[0] != '$') { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid variable name \"%V\"", &name); return NGX_CONF_ERROR; } name.len--; name.data++; if (cf->args->nelts == 3) { geo->index = ngx_http_get_variable_index(cf, &name); if (geo->index == NGX_ERROR) { return NGX_CONF_ERROR; } name = value[2]; if (name.data[0] != '$') { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid variable name \"%V\"", &name); return NGX_CONF_ERROR; } name.len--; name.data++; } else { geo->index = -1; } var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE); if (var == NULL) { return NGX_CONF_ERROR; } pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log); if (pool == NULL) { return NGX_CONF_ERROR; } ngx_memzero(&ctx, sizeof(ngx_http_geo_conf_ctx_t)); ctx.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log); if (ctx.temp_pool == NULL) { ngx_destroy_pool(pool); return NGX_CONF_ERROR; } ngx_rbtree_init(&ctx.rbtree, &ctx.sentinel, ngx_str_rbtree_insert_value); ctx.pool = cf->pool; ctx.data_size = sizeof(ngx_http_geo_header_t) + sizeof(ngx_http_variable_value_t) + 0x10000 * sizeof(ngx_http_geo_range_t *); ctx.allow_binary_include = 1; save = *cf; cf->pool = pool; cf->ctx = &ctx; cf->handler = ngx_http_geo; cf->handler_conf = conf; rv = ngx_conf_parse(cf, NULL); *cf = save; if (rv != NGX_CONF_OK) { goto failed; } geo->proxies = ctx.proxies; geo->proxy_recursive = ctx.proxy_recursive; if (ctx.ranges) { if (ctx.high.low && !ctx.binary_include) { for (i = 0; i < 0x10000; i++) { a = (ngx_array_t *) ctx.high.low[i]; if (a == NULL) { continue; } if (a->nelts == 0) { ctx.high.low[i] = NULL; continue; } len = a->nelts * sizeof(ngx_http_geo_range_t); ctx.high.low[i] = ngx_palloc(cf->pool, len + sizeof(void *)); if (ctx.high.low[i] == NULL) { goto failed; } ngx_memcpy(ctx.high.low[i], a->elts, len); ctx.high.low[i][a->nelts].value = NULL; ctx.data_size += len + sizeof(void *); } if (ctx.allow_binary_include && !ctx.outside_entries && ctx.entries > 100000 && ctx.includes == 1) { ngx_http_geo_create_binary_base(&ctx); } } if (ctx.high.default_value == NULL) { ctx.high.default_value = &ngx_http_variable_null_value; } geo->u.high = ctx.high; var->get_handler = ngx_http_geo_range_variable; var->data = (uintptr_t) geo; } else { if (ctx.tree == NULL) { ctx.tree = ngx_radix_tree_create(cf->pool, -1); if (ctx.tree == NULL) { goto failed; } } geo->u.trees.tree = ctx.tree; #if (NGX_HAVE_INET6) if (ctx.tree6 == NULL) { ctx.tree6 = ngx_radix_tree_create(cf->pool, -1); if (ctx.tree6 == NULL) { goto failed; } } geo->u.trees.tree6 = ctx.tree6; #endif var->get_handler = ngx_http_geo_cidr_variable; var->data = (uintptr_t) geo; if (ngx_radix32tree_insert(ctx.tree, 0, 0, (uintptr_t) &ngx_http_variable_null_value) == NGX_ERROR) { goto failed; } /* NGX_BUSY is okay (default was set explicitly) */ #if (NGX_HAVE_INET6) if (ngx_radix128tree_insert(ctx.tree6, zero.s6_addr, zero.s6_addr, (uintptr_t) &ngx_http_variable_null_value) == NGX_ERROR) { goto failed; } #endif } ngx_destroy_pool(ctx.temp_pool); ngx_destroy_pool(pool); return NGX_CONF_OK; failed: ngx_destroy_pool(ctx.temp_pool); ngx_destroy_pool(pool); return NGX_CONF_ERROR; } static char * ngx_http_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) { char *rv; ngx_str_t *value; ngx_cidr_t cidr; ngx_http_geo_conf_ctx_t *ctx; ctx = cf->ctx; value = cf->args->elts; if (cf->args->nelts == 1) { if (ngx_strcmp(value[0].data, "ranges") == 0) { if (ctx->tree #if (NGX_HAVE_INET6) || ctx->tree6 #endif ) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the \"ranges\" directive must be " "the first directive inside \"geo\" block"); goto failed; } ctx->ranges = 1; rv = NGX_CONF_OK; goto done; } else if (ngx_strcmp(value[0].data, "proxy_recursive") == 0) { ctx->proxy_recursive = 1; rv = NGX_CONF_OK; goto done; } } if (cf->args->nelts != 2) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid number of the geo parameters"); goto failed; } if (ngx_strcmp(value[0].data, "include") == 0) { rv = ngx_http_geo_include(cf, ctx, &value[1]); goto done; } else if (ngx_strcmp(value[0].data, "proxy") == 0) { if (ngx_http_geo_cidr_value(cf, &value[1], &cidr) != NGX_OK) { goto failed; } rv = ngx_http_geo_add_proxy(cf, ctx, &cidr); goto done; } if (ctx->ranges) { rv = ngx_http_geo_range(cf, ctx, value); } else { rv = ngx_http_geo_cidr(cf, ctx, value); } done: ngx_reset_pool(cf->pool); return rv; failed: ngx_reset_pool(cf->pool); return NGX_CONF_ERROR; } static char * ngx_http_geo_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, ngx_str_t *value) { u_char *p, *last; in_addr_t start, end; ngx_str_t *net; ngx_uint_t del; if (ngx_strcmp(value[0].data, "default") == 0) { if (ctx->high.default_value) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "duplicate default geo range value: \"%V\", old value: \"%v\"", &value[1], ctx->high.default_value); } ctx->high.default_value = ngx_http_geo_value(cf, ctx, &value[1]); if (ctx->high.default_value == NULL) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } if (ctx->binary_include) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "binary geo range base \"%s\" cannot be mixed with usual entries", ctx->include_name.data); return NGX_CONF_ERROR; } if (ctx->high.low == NULL) { ctx->high.low = ngx_pcalloc(ctx->pool, 0x10000 * sizeof(ngx_http_geo_range_t *)); if (ctx->high.low == NULL) { return NGX_CONF_ERROR; } } ctx->entries++; ctx->outside_entries = 1; if (ngx_strcmp(value[0].data, "delete") == 0) { net = &value[1]; del = 1; } else { net = &value[0]; del = 0; } last = net->data + net->len; p = ngx_strlchr(net->data, last, '-'); if (p == NULL) { goto invalid; } start = ngx_inet_addr(net->data, p - net->data); if (start == INADDR_NONE) { goto invalid; } start = ntohl(start); p++; end = ngx_inet_addr(p, last - p); if (end == INADDR_NONE) { goto invalid; } end = ntohl(end); if (start > end) { goto invalid; } if (del) { if (ngx_http_geo_delete_range(cf, ctx, start, end)) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "no address range \"%V\" to delete", net); } return NGX_CONF_OK; } ctx->value = ngx_http_geo_value(cf, ctx, &value[1]); if (ctx->value == NULL) { return NGX_CONF_ERROR; } ctx->net = net; return ngx_http_geo_add_range(cf, ctx, start, end); invalid: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid range \"%V\"", net); return NGX_CONF_ERROR; } /* the add procedure is optimized to add a growing up sequence */ static char * ngx_http_geo_add_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end) { in_addr_t n; ngx_uint_t h, i, s, e; ngx_array_t *a; ngx_http_geo_range_t *range; for (n = start; n <= end; n = (n + 0x10000) & 0xffff0000) { h = n >> 16; if (n == start) { s = n & 0xffff; } else { s = 0; } if ((n | 0xffff) > end) { e = end & 0xffff; } else { e = 0xffff; } a = (ngx_array_t *) ctx->high.low[h]; if (a == NULL) { a = ngx_array_create(ctx->temp_pool, 64, sizeof(ngx_http_geo_range_t)); if (a == NULL) { return NGX_CONF_ERROR; } ctx->high.low[h] = (ngx_http_geo_range_t *) a; } i = a->nelts; range = a->elts; while (i) { i--; if (e < (ngx_uint_t) range[i].start) { continue; } if (s > (ngx_uint_t) range[i].end) { /* add after the range */ range = ngx_array_push(a); if (range == NULL) { return NGX_CONF_ERROR; } range = a->elts; ngx_memmove(&range[i + 2], &range[i + 1], (a->nelts - 2 - i) * sizeof(ngx_http_geo_range_t)); range[i + 1].start = (u_short) s; range[i + 1].end = (u_short) e; range[i + 1].value = ctx->value; goto next; } if (s == (ngx_uint_t) range[i].start && e == (ngx_uint_t) range[i].end) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "duplicate range \"%V\", value: \"%v\", old value: \"%v\"", ctx->net, ctx->value, range[i].value); range[i].value = ctx->value; goto next; } if (s > (ngx_uint_t) range[i].start && e < (ngx_uint_t) range[i].end) { /* split the range and insert the new one */ range = ngx_array_push(a); if (range == NULL) { return NGX_CONF_ERROR; } range = ngx_array_push(a); if (range == NULL) { return NGX_CONF_ERROR; } range = a->elts; ngx_memmove(&range[i + 3], &range[i + 1], (a->nelts - 3 - i) * sizeof(ngx_http_geo_range_t)); range[i + 2].start = (u_short) (e + 1); range[i + 2].end = range[i].end; range[i + 2].value = range[i].value; range[i + 1].start = (u_short) s; range[i + 1].end = (u_short) e; range[i + 1].value = ctx->value; range[i].end = (u_short) (s - 1); goto next; } if (s == (ngx_uint_t) range[i].start && e < (ngx_uint_t) range[i].end) { /* shift the range start and insert the new range */ range = ngx_array_push(a); if (range == NULL) { return NGX_CONF_ERROR; } range = a->elts; ngx_memmove(&range[i + 1], &range[i], (a->nelts - 1 - i) * sizeof(ngx_http_geo_range_t)); range[i + 1].start = (u_short) (e + 1); range[i].start = (u_short) s; range[i].end = (u_short) e; range[i].value = ctx->value; goto next; } if (s > (ngx_uint_t) range[i].start && e == (ngx_uint_t) range[i].end) { /* shift the range end and insert the new range */ range = ngx_array_push(a); if (range == NULL) { return NGX_CONF_ERROR; } range = a->elts; ngx_memmove(&range[i + 2], &range[i + 1], (a->nelts - 2 - i) * sizeof(ngx_http_geo_range_t)); range[i + 1].start = (u_short) s; range[i + 1].end = (u_short) e; range[i + 1].value = ctx->value; range[i].end = (u_short) (s - 1); goto next; } s = (ngx_uint_t) range[i].start; e = (ngx_uint_t) range[i].end; ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "range \"%V\" overlaps \"%d.%d.%d.%d-%d.%d.%d.%d\"", ctx->net, h >> 8, h & 0xff, s >> 8, s & 0xff, h >> 8, h & 0xff, e >> 8, e & 0xff); return NGX_CONF_ERROR; } /* add the first range */ range = ngx_array_push(a); if (range == NULL) { return NGX_CONF_ERROR; } range = a->elts; ngx_memmove(&range[1], &range[0], (a->nelts - 1) * sizeof(ngx_http_geo_range_t)); range[0].start = (u_short) s; range[0].end = (u_short) e; range[0].value = ctx->value; next: if (h == 0xffff) { break; } } return NGX_CONF_OK; } static ngx_uint_t ngx_http_geo_delete_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end) { in_addr_t n; ngx_uint_t h, i, s, e, warn; ngx_array_t *a; ngx_http_geo_range_t *range; warn = 0; for (n = start; n <= end; n = (n + 0x10000) & 0xffff0000) { h = n >> 16; if (n == start) { s = n & 0xffff; } else { s = 0; } if ((n | 0xffff) > end) { e = end & 0xffff; } else { e = 0xffff; } a = (ngx_array_t *) ctx->high.low[h]; if (a == NULL || a->nelts == 0) { warn = 1; goto next; } range = a->elts; for (i = 0; i < a->nelts; i++) { if (s == (ngx_uint_t) range[i].start && e == (ngx_uint_t) range[i].end) { ngx_memmove(&range[i], &range[i + 1], (a->nelts - 1 - i) * sizeof(ngx_http_geo_range_t)); a->nelts--; break; } if (i == a->nelts - 1) { warn = 1; } } next: if (h == 0xffff) { break; } } return warn; } static char * ngx_http_geo_cidr(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, ngx_str_t *value) { char *rv; ngx_int_t rc, del; ngx_str_t *net; ngx_cidr_t cidr; if (ctx->tree == NULL) { ctx->tree = ngx_radix_tree_create(ctx->pool, -1); if (ctx->tree == NULL) { return NGX_CONF_ERROR; } } #if (NGX_HAVE_INET6) if (ctx->tree6 == NULL) { ctx->tree6 = ngx_radix_tree_create(ctx->pool, -1); if (ctx->tree6 == NULL) { return NGX_CONF_ERROR; } } #endif if (ngx_strcmp(value[0].data, "default") == 0) { cidr.family = AF_INET; cidr.u.in.addr = 0; cidr.u.in.mask = 0; rv = ngx_http_geo_cidr_add(cf, ctx, &cidr, &value[1], &value[0]); if (rv != NGX_CONF_OK) { return rv; } #if (NGX_HAVE_INET6) cidr.family = AF_INET6; ngx_memzero(&cidr.u.in6, sizeof(ngx_in6_cidr_t)); rv = ngx_http_geo_cidr_add(cf, ctx, &cidr, &value[1], &value[0]); if (rv != NGX_CONF_OK) { return rv; } #endif return NGX_CONF_OK; } if (ngx_strcmp(value[0].data, "delete") == 0) { net = &value[1]; del = 1; } else { net = &value[0]; del = 0; } if (ngx_http_geo_cidr_value(cf, net, &cidr) != NGX_OK) { return NGX_CONF_ERROR; } if (cidr.family == AF_INET) { cidr.u.in.addr = ntohl(cidr.u.in.addr); cidr.u.in.mask = ntohl(cidr.u.in.mask); } if (del) { switch (cidr.family) { #if (NGX_HAVE_INET6) case AF_INET6: rc = ngx_radix128tree_delete(ctx->tree6, cidr.u.in6.addr.s6_addr, cidr.u.in6.mask.s6_addr); break; #endif default: /* AF_INET */ rc = ngx_radix32tree_delete(ctx->tree, cidr.u.in.addr, cidr.u.in.mask); break; } if (rc != NGX_OK) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "no network \"%V\" to delete", net); } return NGX_CONF_OK; } return ngx_http_geo_cidr_add(cf, ctx, &cidr, &value[1], net); } static char * ngx_http_geo_cidr_add(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, ngx_cidr_t *cidr, ngx_str_t *value, ngx_str_t *net) { ngx_int_t rc; ngx_http_variable_value_t *val, *old; val = ngx_http_geo_value(cf, ctx, value); if (val == NULL) { return NGX_CONF_ERROR; } switch (cidr->family) { #if (NGX_HAVE_INET6) case AF_INET6: rc = ngx_radix128tree_insert(ctx->tree6, cidr->u.in6.addr.s6_addr, cidr->u.in6.mask.s6_addr, (uintptr_t) val); if (rc == NGX_OK) { return NGX_CONF_OK; } if (rc == NGX_ERROR) { return NGX_CONF_ERROR; } /* rc == NGX_BUSY */ old = (ngx_http_variable_value_t *) ngx_radix128tree_find(ctx->tree6, cidr->u.in6.addr.s6_addr); ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "duplicate network \"%V\", value: \"%v\", old value: \"%v\"", net, val, old); rc = ngx_radix128tree_delete(ctx->tree6, cidr->u.in6.addr.s6_addr, cidr->u.in6.mask.s6_addr); if (rc == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree"); return NGX_CONF_ERROR; } rc = ngx_radix128tree_insert(ctx->tree6, cidr->u.in6.addr.s6_addr, cidr->u.in6.mask.s6_addr, (uintptr_t) val); break; #endif default: /* AF_INET */ rc = ngx_radix32tree_insert(ctx->tree, cidr->u.in.addr, cidr->u.in.mask, (uintptr_t) val); if (rc == NGX_OK) { return NGX_CONF_OK; } if (rc == NGX_ERROR) { return NGX_CONF_ERROR; } /* rc == NGX_BUSY */ old = (ngx_http_variable_value_t *) ngx_radix32tree_find(ctx->tree, cidr->u.in.addr); ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "duplicate network \"%V\", value: \"%v\", old value: \"%v\"", net, val, old); rc = ngx_radix32tree_delete(ctx->tree, cidr->u.in.addr, cidr->u.in.mask); if (rc == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree"); return NGX_CONF_ERROR; } rc = ngx_radix32tree_insert(ctx->tree, cidr->u.in.addr, cidr->u.in.mask, (uintptr_t) val); break; } if (rc == NGX_OK) { return NGX_CONF_OK; } return NGX_CONF_ERROR; } static ngx_http_variable_value_t * ngx_http_geo_value(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, ngx_str_t *value) { uint32_t hash; ngx_http_variable_value_t *val; ngx_http_geo_variable_value_node_t *gvvn; hash = ngx_crc32_long(value->data, value->len); gvvn = (ngx_http_geo_variable_value_node_t *) ngx_str_rbtree_lookup(&ctx->rbtree, value, hash); if (gvvn) { return gvvn->value; } val = ngx_palloc(ctx->pool, sizeof(ngx_http_variable_value_t)); if (val == NULL) { return NULL; } val->len = value->len; val->data = ngx_pstrdup(ctx->pool, value); if (val->data == NULL) { return NULL; } val->valid = 1; val->no_cacheable = 0; val->not_found = 0; gvvn = ngx_palloc(ctx->temp_pool, sizeof(ngx_http_geo_variable_value_node_t)); if (gvvn == NULL) { return NULL; } gvvn->sn.node.key = hash; gvvn->sn.str.len = val->len; gvvn->sn.str.data = val->data; gvvn->value = val; gvvn->offset = 0; ngx_rbtree_insert(&ctx->rbtree, &gvvn->sn.node); ctx->data_size += ngx_align(sizeof(ngx_http_variable_value_t) + value->len, sizeof(void *)); return val; } static char * ngx_http_geo_add_proxy(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, ngx_cidr_t *cidr) { ngx_cidr_t *c; if (ctx->proxies == NULL) { ctx->proxies = ngx_array_create(ctx->pool, 4, sizeof(ngx_cidr_t)); if (ctx->proxies == NULL) { return NGX_CONF_ERROR; } } c = ngx_array_push(ctx->proxies); if (c == NULL) { return NGX_CONF_ERROR; } *c = *cidr; return NGX_CONF_OK; } static ngx_int_t ngx_http_geo_cidr_value(ngx_conf_t *cf, ngx_str_t *net, ngx_cidr_t *cidr) { ngx_int_t rc; if (ngx_strcmp(net->data, "255.255.255.255") == 0) { cidr->family = AF_INET; cidr->u.in.addr = 0xffffffff; cidr->u.in.mask = 0xffffffff; return NGX_OK; } rc = ngx_ptocidr(net, cidr); if (rc == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid network \"%V\"", net); return NGX_ERROR; } if (rc == NGX_DONE) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "low address bits of %V are meaningless", net); } return NGX_OK; } static char * ngx_http_geo_include(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, ngx_str_t *name) { char *rv; ngx_str_t file; file.len = name->len + 4; file.data = ngx_pnalloc(ctx->temp_pool, name->len + 5); if (file.data == NULL) { return NGX_CONF_ERROR; } ngx_sprintf(file.data, "%V.bin%Z", name); if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK) { return NGX_CONF_ERROR; } if (ctx->ranges) { ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data); switch (ngx_http_geo_include_binary_base(cf, ctx, &file)) { case NGX_OK: return NGX_CONF_OK; case NGX_ERROR: return NGX_CONF_ERROR; default: break; } } file.len -= 4; file.data[file.len] = '\0'; ctx->include_name = file; if (ctx->outside_entries) { ctx->allow_binary_include = 0; } ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data); rv = ngx_conf_parse(cf, &file); ctx->includes++; ctx->outside_entries = 0; return rv; } static ngx_int_t ngx_http_geo_include_binary_base(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, ngx_str_t *name) { u_char *base, ch; time_t mtime; size_t size, len; ssize_t n; uint32_t crc32; ngx_err_t err; ngx_int_t rc; ngx_uint_t i; ngx_file_t file; ngx_file_info_t fi; ngx_http_geo_range_t *range, **ranges; ngx_http_geo_header_t *header; ngx_http_variable_value_t *vv; ngx_memzero(&file, sizeof(ngx_file_t)); file.name = *name; file.log = cf->log; file.fd = ngx_open_file(name->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); if (file.fd == NGX_INVALID_FILE) { err = ngx_errno; if (err != NGX_ENOENT) { ngx_conf_log_error(NGX_LOG_CRIT, cf, err, ngx_open_file_n " \"%s\" failed", name->data); } return NGX_DECLINED; } if (ctx->outside_entries) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "binary geo range base \"%s\" cannot be mixed with usual entries", name->data); rc = NGX_ERROR; goto done; } if (ctx->binary_include) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "second binary geo range base \"%s\" cannot be mixed with \"%s\"", name->data, ctx->include_name.data); rc = NGX_ERROR; goto done; } if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) { ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, ngx_fd_info_n " \"%s\" failed", name->data); goto failed; } size = (size_t) ngx_file_size(&fi); mtime = ngx_file_mtime(&fi); ch = name->data[name->len - 4]; name->data[name->len - 4] = '\0'; if (ngx_file_info(name->data, &fi) == NGX_FILE_ERROR) { ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, ngx_file_info_n " \"%s\" failed", name->data); goto failed; } name->data[name->len - 4] = ch; if (mtime < ngx_file_mtime(&fi)) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "stale binary geo range base \"%s\"", name->data); goto failed; } base = ngx_palloc(ctx->pool, size); if (base == NULL) { goto failed; } n = ngx_read_file(&file, base, size, 0); if (n == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, ngx_read_file_n " \"%s\" failed", name->data); goto failed; } if ((size_t) n != size) { ngx_conf_log_error(NGX_LOG_CRIT, cf, 0, ngx_read_file_n " \"%s\" returned only %z bytes instead of %z", name->data, n, size); goto failed; } header = (ngx_http_geo_header_t *) base; if (size < 16 || ngx_memcmp(&ngx_http_geo_header, header, 12) != 0) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "incompatible binary geo range base \"%s\"", name->data); goto failed; } ngx_crc32_init(crc32); vv = (ngx_http_variable_value_t *) (base + sizeof(ngx_http_geo_header_t)); while (vv->data) { len = ngx_align(sizeof(ngx_http_variable_value_t) + vv->len, sizeof(void *)); ngx_crc32_update(&crc32, (u_char *) vv, len); vv->data += (size_t) base; vv = (ngx_http_variable_value_t *) ((u_char *) vv + len); } ngx_crc32_update(&crc32, (u_char *) vv, sizeof(ngx_http_variable_value_t)); vv++; ranges = (ngx_http_geo_range_t **) vv; for (i = 0; i < 0x10000; i++) { ngx_crc32_update(&crc32, (u_char *) &ranges[i], sizeof(void *)); if (ranges[i]) { ranges[i] = (ngx_http_geo_range_t *) ((u_char *) ranges[i] + (size_t) base); } } range = (ngx_http_geo_range_t *) &ranges[0x10000]; while ((u_char *) range < base + size) { while (range->value) { ngx_crc32_update(&crc32, (u_char *) range, sizeof(ngx_http_geo_range_t)); range->value = (ngx_http_variable_value_t *) ((u_char *) range->value + (size_t) base); range++; } ngx_crc32_update(&crc32, (u_char *) range, sizeof(void *)); range = (ngx_http_geo_range_t *) ((u_char *) range + sizeof(void *)); } ngx_crc32_final(crc32); if (crc32 != header->crc32) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "CRC32 mismatch in binary geo range base \"%s\"", name->data); goto failed; } ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0, "using binary geo range base \"%s\"", name->data); ctx->include_name = *name; ctx->binary_include = 1; ctx->high.low = ranges; rc = NGX_OK; goto done; failed: rc = NGX_DECLINED; done: if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, ngx_close_file_n " \"%s\" failed", name->data); } return rc; } static void ngx_http_geo_create_binary_base(ngx_http_geo_conf_ctx_t *ctx) { u_char *p; uint32_t hash; ngx_str_t s; ngx_uint_t i; ngx_file_mapping_t fm; ngx_http_geo_range_t *r, *range, **ranges; ngx_http_geo_header_t *header; ngx_http_geo_variable_value_node_t *gvvn; fm.name = ngx_pnalloc(ctx->temp_pool, ctx->include_name.len + 5); if (fm.name == NULL) { return; } ngx_sprintf(fm.name, "%V.bin%Z", &ctx->include_name); fm.size = ctx->data_size; fm.log = ctx->pool->log; ngx_log_error(NGX_LOG_NOTICE, fm.log, 0, "creating binary geo range base \"%s\"", fm.name); if (ngx_create_file_mapping(&fm) != NGX_OK) { return; } p = ngx_cpymem(fm.addr, &ngx_http_geo_header, sizeof(ngx_http_geo_header_t)); p = ngx_http_geo_copy_values(fm.addr, p, ctx->rbtree.root, ctx->rbtree.sentinel); p += sizeof(ngx_http_variable_value_t); ranges = (ngx_http_geo_range_t **) p; p += 0x10000 * sizeof(ngx_http_geo_range_t *); for (i = 0; i < 0x10000; i++) { r = ctx->high.low[i]; if (r == NULL) { continue; } range = (ngx_http_geo_range_t *) p; ranges[i] = (ngx_http_geo_range_t *) (p - (u_char *) fm.addr); do { s.len = r->value->len; s.data = r->value->data; hash = ngx_crc32_long(s.data, s.len); gvvn = (ngx_http_geo_variable_value_node_t *) ngx_str_rbtree_lookup(&ctx->rbtree, &s, hash); range->value = (ngx_http_variable_value_t *) gvvn->offset; range->start = r->start; range->end = r->end; range++; } while ((++r)->value); range->value = NULL; p = (u_char *) range + sizeof(void *); } header = fm.addr; header->crc32 = ngx_crc32_long((u_char *) fm.addr + sizeof(ngx_http_geo_header_t), fm.size - sizeof(ngx_http_geo_header_t)); ngx_close_file_mapping(&fm); } static u_char * ngx_http_geo_copy_values(u_char *base, u_char *p, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) { ngx_http_variable_value_t *vv; ngx_http_geo_variable_value_node_t *gvvn; if (node == sentinel) { return p; } gvvn = (ngx_http_geo_variable_value_node_t *) node; gvvn->offset = p - base; vv = (ngx_http_variable_value_t *) p; *vv = *gvvn->value; p += sizeof(ngx_http_variable_value_t); vv->data = (u_char *) (p - base); p = ngx_cpymem(p, gvvn->sn.str.data, gvvn->sn.str.len); p = ngx_align_ptr(p, sizeof(void *)); p = ngx_http_geo_copy_values(base, p, node->left, sentinel); return ngx_http_geo_copy_values(base, p, node->right, sentinel); } nginx-1.24.0/src/http/modules/ngx_http_geoip_module.c000644 001751 001751 00000053533 14415135676 024101 0ustar00mdouninmdounin000000 000000 /* * 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_http_null_variable }; static u_long ngx_http_geoip_addr(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf) { ngx_addr_t addr; ngx_table_elt_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 != NULL && 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_table_elt_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 != NULL && 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); } } nginx-1.24.0/src/http/modules/ngx_http_grpc_module.c000644 001751 001751 00000431731 14415135676 023731 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Maxim Dounin * Copyright (C) Nginx, Inc. */ #include #include #include typedef struct { ngx_array_t *flushes; ngx_array_t *lengths; ngx_array_t *values; ngx_hash_t hash; } ngx_http_grpc_headers_t; typedef struct { ngx_http_upstream_conf_t upstream; ngx_http_grpc_headers_t headers; ngx_array_t *headers_source; ngx_str_t host; ngx_uint_t host_set; ngx_array_t *grpc_lengths; ngx_array_t *grpc_values; #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_array_t *ssl_conf_commands; #endif } ngx_http_grpc_loc_conf_t; typedef enum { ngx_http_grpc_st_start = 0, ngx_http_grpc_st_length_2, ngx_http_grpc_st_length_3, ngx_http_grpc_st_type, ngx_http_grpc_st_flags, ngx_http_grpc_st_stream_id, ngx_http_grpc_st_stream_id_2, ngx_http_grpc_st_stream_id_3, ngx_http_grpc_st_stream_id_4, ngx_http_grpc_st_payload, ngx_http_grpc_st_padding } ngx_http_grpc_state_e; typedef struct { size_t init_window; size_t send_window; size_t recv_window; ngx_uint_t last_stream_id; } ngx_http_grpc_conn_t; typedef struct { ngx_http_grpc_state_e state; ngx_uint_t frame_state; ngx_uint_t fragment_state; ngx_chain_t *in; ngx_chain_t *out; ngx_chain_t *free; ngx_chain_t *busy; ngx_http_grpc_conn_t *connection; ngx_uint_t id; ngx_uint_t pings; ngx_uint_t settings; off_t length; ssize_t send_window; size_t recv_window; size_t rest; ngx_uint_t stream_id; u_char type; u_char flags; u_char padding; ngx_uint_t error; ngx_uint_t window_update; ngx_uint_t setting_id; ngx_uint_t setting_value; u_char ping_data[8]; ngx_uint_t index; ngx_str_t name; ngx_str_t value; u_char *field_end; size_t field_length; size_t field_rest; u_char field_state; unsigned literal:1; unsigned field_huffman:1; unsigned header_sent:1; unsigned output_closed:1; unsigned output_blocked:1; unsigned parsing_headers:1; unsigned end_stream:1; unsigned done:1; unsigned status:1; unsigned rst:1; unsigned goaway:1; ngx_http_request_t *request; ngx_str_t host; } ngx_http_grpc_ctx_t; typedef struct { u_char length_0; u_char length_1; u_char length_2; u_char type; u_char flags; u_char stream_id_0; u_char stream_id_1; u_char stream_id_2; u_char stream_id_3; } ngx_http_grpc_frame_t; static ngx_int_t ngx_http_grpc_eval(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ngx_http_grpc_loc_conf_t *glcf); static ngx_int_t ngx_http_grpc_create_request(ngx_http_request_t *r); static ngx_int_t ngx_http_grpc_reinit_request(ngx_http_request_t *r); static ngx_int_t ngx_http_grpc_body_output_filter(void *data, ngx_chain_t *in); static ngx_int_t ngx_http_grpc_process_header(ngx_http_request_t *r); static ngx_int_t ngx_http_grpc_filter_init(void *data); static ngx_int_t ngx_http_grpc_filter(void *data, ssize_t bytes); static ngx_int_t ngx_http_grpc_parse_frame(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b); static ngx_int_t ngx_http_grpc_parse_header(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b); static ngx_int_t ngx_http_grpc_parse_fragment(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b); static ngx_int_t ngx_http_grpc_validate_header_name(ngx_http_request_t *r, ngx_str_t *s); static ngx_int_t ngx_http_grpc_validate_header_value(ngx_http_request_t *r, ngx_str_t *s); static ngx_int_t ngx_http_grpc_parse_rst_stream(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b); static ngx_int_t ngx_http_grpc_parse_goaway(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b); static ngx_int_t ngx_http_grpc_parse_window_update(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b); static ngx_int_t ngx_http_grpc_parse_settings(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b); static ngx_int_t ngx_http_grpc_parse_ping(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b); static ngx_int_t ngx_http_grpc_send_settings_ack(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx); static ngx_int_t ngx_http_grpc_send_ping_ack(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx); static ngx_int_t ngx_http_grpc_send_window_update(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx); static ngx_chain_t *ngx_http_grpc_get_buf(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx); static ngx_http_grpc_ctx_t *ngx_http_grpc_get_ctx(ngx_http_request_t *r); static ngx_int_t ngx_http_grpc_get_connection_data(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ngx_peer_connection_t *pc); static void ngx_http_grpc_cleanup(void *data); static void ngx_http_grpc_abort_request(ngx_http_request_t *r); static void ngx_http_grpc_finalize_request(ngx_http_request_t *r, ngx_int_t rc); static ngx_int_t ngx_http_grpc_internal_trailers_variable( ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_grpc_add_variables(ngx_conf_t *cf); static void *ngx_http_grpc_create_loc_conf(ngx_conf_t *cf); static char *ngx_http_grpc_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); static ngx_int_t ngx_http_grpc_init_headers(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *conf, ngx_http_grpc_headers_t *headers, ngx_keyval_t *default_headers); static char *ngx_http_grpc_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); #if (NGX_HTTP_SSL) static char *ngx_http_grpc_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_grpc_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data); static ngx_int_t ngx_http_grpc_merge_ssl(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *conf, ngx_http_grpc_loc_conf_t *prev); static ngx_int_t ngx_http_grpc_set_ssl(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *glcf); #endif static ngx_conf_bitmask_t ngx_http_grpc_next_upstream_masks[] = { { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR }, { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT }, { ngx_string("invalid_header"), NGX_HTTP_UPSTREAM_FT_INVALID_HEADER }, { ngx_string("non_idempotent"), NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT }, { ngx_string("http_500"), NGX_HTTP_UPSTREAM_FT_HTTP_500 }, { ngx_string("http_502"), NGX_HTTP_UPSTREAM_FT_HTTP_502 }, { ngx_string("http_503"), NGX_HTTP_UPSTREAM_FT_HTTP_503 }, { ngx_string("http_504"), NGX_HTTP_UPSTREAM_FT_HTTP_504 }, { ngx_string("http_403"), NGX_HTTP_UPSTREAM_FT_HTTP_403 }, { ngx_string("http_404"), NGX_HTTP_UPSTREAM_FT_HTTP_404 }, { ngx_string("http_429"), NGX_HTTP_UPSTREAM_FT_HTTP_429 }, { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF }, { ngx_null_string, 0 } }; #if (NGX_HTTP_SSL) static ngx_conf_bitmask_t ngx_http_grpc_ssl_protocols[] = { { ngx_string("SSLv2"), NGX_SSL_SSLv2 }, { ngx_string("SSLv3"), NGX_SSL_SSLv3 }, { ngx_string("TLSv1"), NGX_SSL_TLSv1 }, { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 }, { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 }, { ngx_string("TLSv1.3"), NGX_SSL_TLSv1_3 }, { ngx_null_string, 0 } }; static ngx_conf_post_t ngx_http_grpc_ssl_conf_command_post = { ngx_http_grpc_ssl_conf_command_check }; #endif static ngx_command_t ngx_http_grpc_commands[] = { { ngx_string("grpc_pass"), NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, ngx_http_grpc_pass, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("grpc_bind"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12, ngx_http_upstream_bind_set_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, upstream.local), NULL }, { ngx_string("grpc_socket_keepalive"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, upstream.socket_keepalive), NULL }, { ngx_string("grpc_connect_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, upstream.connect_timeout), NULL }, { ngx_string("grpc_send_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, upstream.send_timeout), NULL }, { ngx_string("grpc_intercept_errors"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, upstream.intercept_errors), NULL }, { ngx_string("grpc_buffer_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, upstream.buffer_size), NULL }, { ngx_string("grpc_read_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, upstream.read_timeout), NULL }, { ngx_string("grpc_next_upstream"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, upstream.next_upstream), &ngx_http_grpc_next_upstream_masks }, { ngx_string("grpc_next_upstream_tries"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, upstream.next_upstream_tries), NULL }, { ngx_string("grpc_next_upstream_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, upstream.next_upstream_timeout), NULL }, { ngx_string("grpc_set_header"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, ngx_conf_set_keyval_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, headers_source), NULL }, { ngx_string("grpc_pass_header"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_array_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, upstream.pass_headers), NULL }, { ngx_string("grpc_hide_header"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_array_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, upstream.hide_headers), NULL }, { ngx_string("grpc_ignore_headers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, upstream.ignore_headers), &ngx_http_upstream_ignore_headers_masks }, #if (NGX_HTTP_SSL) { ngx_string("grpc_ssl_session_reuse"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, upstream.ssl_session_reuse), NULL }, { ngx_string("grpc_ssl_protocols"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, ssl_protocols), &ngx_http_grpc_ssl_protocols }, { ngx_string("grpc_ssl_ciphers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, ssl_ciphers), NULL }, { ngx_string("grpc_ssl_name"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_set_complex_value_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, upstream.ssl_name), NULL }, { ngx_string("grpc_ssl_server_name"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, upstream.ssl_server_name), NULL }, { ngx_string("grpc_ssl_verify"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, upstream.ssl_verify), NULL }, { ngx_string("grpc_ssl_verify_depth"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, ssl_verify_depth), NULL }, { ngx_string("grpc_ssl_trusted_certificate"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, ssl_trusted_certificate), NULL }, { ngx_string("grpc_ssl_crl"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, ssl_crl), NULL }, { ngx_string("grpc_ssl_certificate"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_set_complex_value_zero_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, upstream.ssl_certificate), NULL }, { ngx_string("grpc_ssl_certificate_key"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_set_complex_value_zero_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, upstream.ssl_certificate_key), NULL }, { ngx_string("grpc_ssl_password_file"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_grpc_ssl_password_file, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("grpc_ssl_conf_command"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, ngx_conf_set_keyval_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_grpc_loc_conf_t, ssl_conf_commands), &ngx_http_grpc_ssl_conf_command_post }, #endif ngx_null_command }; static ngx_http_module_t ngx_http_grpc_module_ctx = { ngx_http_grpc_add_variables, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_grpc_create_loc_conf, /* create location configuration */ ngx_http_grpc_merge_loc_conf /* merge location configuration */ }; ngx_module_t ngx_http_grpc_module = { NGX_MODULE_V1, &ngx_http_grpc_module_ctx, /* module context */ ngx_http_grpc_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static u_char ngx_http_grpc_connection_start[] = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" /* connection preface */ "\x00\x00\x12\x04\x00\x00\x00\x00\x00" /* settings frame */ "\x00\x01\x00\x00\x00\x00" /* header table size */ "\x00\x02\x00\x00\x00\x00" /* disable push */ "\x00\x04\x7f\xff\xff\xff" /* initial window */ "\x00\x00\x04\x08\x00\x00\x00\x00\x00" /* window update frame */ "\x7f\xff\x00\x00"; static ngx_keyval_t ngx_http_grpc_headers[] = { { ngx_string("Content-Length"), ngx_string("$content_length") }, { ngx_string("TE"), ngx_string("$grpc_internal_trailers") }, { ngx_string("Host"), ngx_string("") }, { ngx_string("Connection"), ngx_string("") }, { ngx_string("Transfer-Encoding"), ngx_string("") }, { ngx_string("Keep-Alive"), ngx_string("") }, { ngx_string("Expect"), ngx_string("") }, { ngx_string("Upgrade"), ngx_string("") }, { ngx_null_string, ngx_null_string } }; static ngx_str_t ngx_http_grpc_hide_headers[] = { ngx_string("Date"), ngx_string("Server"), ngx_string("X-Accel-Expires"), ngx_string("X-Accel-Redirect"), ngx_string("X-Accel-Limit-Rate"), ngx_string("X-Accel-Buffering"), ngx_string("X-Accel-Charset"), ngx_null_string }; static ngx_http_variable_t ngx_http_grpc_vars[] = { { ngx_string("grpc_internal_trailers"), NULL, ngx_http_grpc_internal_trailers_variable, 0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 }, ngx_http_null_variable }; static ngx_int_t ngx_http_grpc_handler(ngx_http_request_t *r) { ngx_int_t rc; ngx_http_upstream_t *u; ngx_http_grpc_ctx_t *ctx; ngx_http_grpc_loc_conf_t *glcf; if (ngx_http_upstream_create(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_grpc_ctx_t)); if (ctx == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ctx->request = r; ngx_http_set_ctx(r, ctx, ngx_http_grpc_module); glcf = ngx_http_get_module_loc_conf(r, ngx_http_grpc_module); u = r->upstream; if (glcf->grpc_lengths == NULL) { ctx->host = glcf->host; #if (NGX_HTTP_SSL) u->ssl = glcf->ssl; if (u->ssl) { ngx_str_set(&u->schema, "grpcs://"); } else { ngx_str_set(&u->schema, "grpc://"); } #else ngx_str_set(&u->schema, "grpc://"); #endif } else { if (ngx_http_grpc_eval(r, ctx, glcf) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } } u->output.tag = (ngx_buf_tag_t) &ngx_http_grpc_module; u->conf = &glcf->upstream; u->create_request = ngx_http_grpc_create_request; u->reinit_request = ngx_http_grpc_reinit_request; u->process_header = ngx_http_grpc_process_header; u->abort_request = ngx_http_grpc_abort_request; u->finalize_request = ngx_http_grpc_finalize_request; u->input_filter_init = ngx_http_grpc_filter_init; u->input_filter = ngx_http_grpc_filter; u->input_filter_ctx = ctx; r->request_body_no_buffering = 1; rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init); if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { return rc; } return NGX_DONE; } static ngx_int_t ngx_http_grpc_eval(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ngx_http_grpc_loc_conf_t *glcf) { size_t add; ngx_url_t url; ngx_http_upstream_t *u; ngx_memzero(&url, sizeof(ngx_url_t)); if (ngx_http_script_run(r, &url.url, glcf->grpc_lengths->elts, 0, glcf->grpc_values->elts) == NULL) { return NGX_ERROR; } if (url.url.len > 7 && ngx_strncasecmp(url.url.data, (u_char *) "grpc://", 7) == 0) { add = 7; } else if (url.url.len > 8 && ngx_strncasecmp(url.url.data, (u_char *) "grpcs://", 8) == 0) { #if (NGX_HTTP_SSL) add = 8; r->upstream->ssl = 1; #else ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "grpcs protocol requires SSL support"); return NGX_ERROR; #endif } else { add = 0; } u = r->upstream; if (add) { u->schema.len = add; u->schema.data = url.url.data; url.url.data += add; url.url.len -= add; } else { ngx_str_set(&u->schema, "grpc://"); } url.no_resolve = 1; if (ngx_parse_url(r->pool, &url) != NGX_OK) { if (url.err) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "%s in upstream \"%V\"", url.err, &url.url); } return NGX_ERROR; } u->resolved = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t)); if (u->resolved == NULL) { return NGX_ERROR; } if (url.addrs) { u->resolved->sockaddr = url.addrs[0].sockaddr; u->resolved->socklen = url.addrs[0].socklen; u->resolved->name = url.addrs[0].name; u->resolved->naddrs = 1; } u->resolved->host = url.host; u->resolved->port = url.port; u->resolved->no_port = url.no_port; if (url.family != AF_UNIX) { if (url.no_port) { ctx->host = url.host; } else { ctx->host.len = url.host.len + 1 + url.port_text.len; ctx->host.data = url.host.data; } } else { ngx_str_set(&ctx->host, "localhost"); } return NGX_OK; } static ngx_int_t ngx_http_grpc_create_request(ngx_http_request_t *r) { u_char *p, *tmp, *key_tmp, *val_tmp, *headers_frame; size_t len, tmp_len, key_len, val_len, uri_len; uintptr_t escape; ngx_buf_t *b; ngx_uint_t i, next; ngx_chain_t *cl, *body; ngx_list_part_t *part; ngx_table_elt_t *header; ngx_http_grpc_ctx_t *ctx; ngx_http_upstream_t *u; ngx_http_grpc_frame_t *f; ngx_http_script_code_pt code; ngx_http_grpc_loc_conf_t *glcf; ngx_http_script_engine_t e, le; ngx_http_script_len_code_pt lcode; u = r->upstream; glcf = ngx_http_get_module_loc_conf(r, ngx_http_grpc_module); ctx = ngx_http_get_module_ctx(r, ngx_http_grpc_module); len = sizeof(ngx_http_grpc_connection_start) - 1 + sizeof(ngx_http_grpc_frame_t); /* headers frame */ /* :method header */ if (r->method == NGX_HTTP_GET || r->method == NGX_HTTP_POST) { len += 1; tmp_len = 0; } else { len += 1 + NGX_HTTP_V2_INT_OCTETS + r->method_name.len; tmp_len = r->method_name.len; } /* :scheme header */ len += 1; /* :path header */ if (r->valid_unparsed_uri) { escape = 0; uri_len = r->unparsed_uri.len; } else { escape = 2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len, NGX_ESCAPE_URI); uri_len = r->uri.len + escape + sizeof("?") - 1 + r->args.len; } len += 1 + NGX_HTTP_V2_INT_OCTETS + uri_len; if (tmp_len < uri_len) { tmp_len = uri_len; } /* :authority header */ if (!glcf->host_set) { len += 1 + NGX_HTTP_V2_INT_OCTETS + ctx->host.len; if (tmp_len < ctx->host.len) { tmp_len = ctx->host.len; } } /* other headers */ ngx_http_script_flush_no_cacheable_variables(r, glcf->headers.flushes); ngx_memzero(&le, sizeof(ngx_http_script_engine_t)); le.ip = glcf->headers.lengths->elts; le.request = r; le.flushed = 1; while (*(uintptr_t *) le.ip) { lcode = *(ngx_http_script_len_code_pt *) le.ip; key_len = lcode(&le); for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) { lcode = *(ngx_http_script_len_code_pt *) le.ip; } le.ip += sizeof(uintptr_t); if (val_len == 0) { continue; } len += 1 + NGX_HTTP_V2_INT_OCTETS + key_len + NGX_HTTP_V2_INT_OCTETS + val_len; if (tmp_len < key_len) { tmp_len = key_len; } if (tmp_len < val_len) { tmp_len = val_len; } } if (glcf->upstream.pass_request_headers) { part = &r->headers_in.headers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (ngx_hash_find(&glcf->headers.hash, header[i].hash, header[i].lowcase_key, header[i].key.len)) { continue; } len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len + NGX_HTTP_V2_INT_OCTETS + header[i].value.len; if (tmp_len < header[i].key.len) { tmp_len = header[i].key.len; } if (tmp_len < header[i].value.len) { tmp_len = header[i].value.len; } } } /* continuation frames */ len += sizeof(ngx_http_grpc_frame_t) * (len / NGX_HTTP_V2_DEFAULT_FRAME_SIZE); b = ngx_create_temp_buf(r->pool, len); if (b == NULL) { return NGX_ERROR; } cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_ERROR; } cl->buf = b; cl->next = NULL; tmp = ngx_palloc(r->pool, tmp_len * 3); if (tmp == NULL) { return NGX_ERROR; } key_tmp = tmp + tmp_len; val_tmp = tmp + 2 * tmp_len; /* connection preface */ b->last = ngx_copy(b->last, ngx_http_grpc_connection_start, sizeof(ngx_http_grpc_connection_start) - 1); /* headers frame */ headers_frame = b->last; f = (ngx_http_grpc_frame_t *) b->last; b->last += sizeof(ngx_http_grpc_frame_t); f->length_0 = 0; f->length_1 = 0; f->length_2 = 0; f->type = NGX_HTTP_V2_HEADERS_FRAME; f->flags = 0; f->stream_id_0 = 0; f->stream_id_1 = 0; f->stream_id_2 = 0; f->stream_id_3 = 1; if (r->method == NGX_HTTP_GET) { *b->last++ = ngx_http_v2_indexed(NGX_HTTP_V2_METHOD_GET_INDEX); ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc header: \":method: GET\""); } else if (r->method == NGX_HTTP_POST) { *b->last++ = ngx_http_v2_indexed(NGX_HTTP_V2_METHOD_POST_INDEX); ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc header: \":method: POST\""); } else { *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_METHOD_INDEX); b->last = ngx_http_v2_write_value(b->last, r->method_name.data, r->method_name.len, tmp); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc header: \":method: %V\"", &r->method_name); } #if (NGX_HTTP_SSL) if (u->ssl) { *b->last++ = ngx_http_v2_indexed(NGX_HTTP_V2_SCHEME_HTTPS_INDEX); ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc header: \":scheme: https\""); } else #endif { *b->last++ = ngx_http_v2_indexed(NGX_HTTP_V2_SCHEME_HTTP_INDEX); ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc header: \":scheme: http\""); } if (r->valid_unparsed_uri) { if (r->unparsed_uri.len == 1 && r->unparsed_uri.data[0] == '/') { *b->last++ = ngx_http_v2_indexed(NGX_HTTP_V2_PATH_ROOT_INDEX); } else { *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); b->last = ngx_http_v2_write_value(b->last, r->unparsed_uri.data, r->unparsed_uri.len, tmp); } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc header: \":path: %V\"", &r->unparsed_uri); } else if (escape || r->args.len > 0) { p = val_tmp; if (escape) { p = (u_char *) ngx_escape_uri(p, r->uri.data, r->uri.len, NGX_ESCAPE_URI); } else { p = ngx_copy(p, r->uri.data, r->uri.len); } if (r->args.len > 0) { *p++ = '?'; p = ngx_copy(p, r->args.data, r->args.len); } *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); b->last = ngx_http_v2_write_value(b->last, val_tmp, p - val_tmp, tmp); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc header: \":path: %*s\"", p - val_tmp, val_tmp); } else { *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); b->last = ngx_http_v2_write_value(b->last, r->uri.data, r->uri.len, tmp); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc header: \":path: %V\"", &r->uri); } if (!glcf->host_set) { *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_AUTHORITY_INDEX); b->last = ngx_http_v2_write_value(b->last, ctx->host.data, ctx->host.len, tmp); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc header: \":authority: %V\"", &ctx->host); } ngx_memzero(&e, sizeof(ngx_http_script_engine_t)); e.ip = glcf->headers.values->elts; e.request = r; e.flushed = 1; le.ip = glcf->headers.lengths->elts; while (*(uintptr_t *) le.ip) { lcode = *(ngx_http_script_len_code_pt *) le.ip; key_len = lcode(&le); for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) { lcode = *(ngx_http_script_len_code_pt *) le.ip; } le.ip += sizeof(uintptr_t); if (val_len == 0) { e.skip = 1; while (*(uintptr_t *) e.ip) { code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); } e.ip += sizeof(uintptr_t); e.skip = 0; continue; } *b->last++ = 0; e.pos = key_tmp; code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); b->last = ngx_http_v2_write_name(b->last, key_tmp, key_len, tmp); e.pos = val_tmp; while (*(uintptr_t *) e.ip) { code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); } e.ip += sizeof(uintptr_t); b->last = ngx_http_v2_write_value(b->last, val_tmp, val_len, tmp); #if (NGX_DEBUG) if (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP) { ngx_strlow(key_tmp, key_tmp, key_len); ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc header: \"%*s: %*s\"", key_len, key_tmp, val_len, val_tmp); } #endif } if (glcf->upstream.pass_request_headers) { part = &r->headers_in.headers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (ngx_hash_find(&glcf->headers.hash, header[i].hash, header[i].lowcase_key, header[i].key.len)) { continue; } *b->last++ = 0; b->last = ngx_http_v2_write_name(b->last, header[i].key.data, header[i].key.len, tmp); b->last = ngx_http_v2_write_value(b->last, header[i].value.data, header[i].value.len, tmp); #if (NGX_DEBUG) if (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP) { ngx_strlow(tmp, header[i].key.data, header[i].key.len); ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc header: \"%*s: %V\"", header[i].key.len, tmp, &header[i].value); } #endif } } /* update headers frame length */ len = b->last - headers_frame - sizeof(ngx_http_grpc_frame_t); if (len > NGX_HTTP_V2_DEFAULT_FRAME_SIZE) { len = NGX_HTTP_V2_DEFAULT_FRAME_SIZE; next = 1; } else { next = 0; } f = (ngx_http_grpc_frame_t *) headers_frame; f->length_0 = (u_char) ((len >> 16) & 0xff); f->length_1 = (u_char) ((len >> 8) & 0xff); f->length_2 = (u_char) (len & 0xff); /* create additional continuation frames */ p = headers_frame; while (next) { p += sizeof(ngx_http_grpc_frame_t) + NGX_HTTP_V2_DEFAULT_FRAME_SIZE; len = b->last - p; ngx_memmove(p + sizeof(ngx_http_grpc_frame_t), p, len); b->last += sizeof(ngx_http_grpc_frame_t); if (len > NGX_HTTP_V2_DEFAULT_FRAME_SIZE) { len = NGX_HTTP_V2_DEFAULT_FRAME_SIZE; next = 1; } else { next = 0; } f = (ngx_http_grpc_frame_t *) p; f->length_0 = (u_char) ((len >> 16) & 0xff); f->length_1 = (u_char) ((len >> 8) & 0xff); f->length_2 = (u_char) (len & 0xff); f->type = NGX_HTTP_V2_CONTINUATION_FRAME; f->flags = 0; f->stream_id_0 = 0; f->stream_id_1 = 0; f->stream_id_2 = 0; f->stream_id_3 = 1; } f->flags |= NGX_HTTP_V2_END_HEADERS_FLAG; ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc header: %*xs%s, len: %uz", (size_t) ngx_min(b->last - b->pos, 256), b->pos, b->last - b->pos > 256 ? "..." : "", b->last - b->pos); if (r->request_body_no_buffering) { u->request_bufs = cl; } else { body = u->request_bufs; u->request_bufs = cl; if (body == NULL) { f = (ngx_http_grpc_frame_t *) headers_frame; f->flags |= NGX_HTTP_V2_END_STREAM_FLAG; } while (body) { b = ngx_alloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } ngx_memcpy(b, body->buf, sizeof(ngx_buf_t)); cl->next = ngx_alloc_chain_link(r->pool); if (cl->next == NULL) { return NGX_ERROR; } cl = cl->next; cl->buf = b; body = body->next; } b->last_buf = 1; } u->output.output_filter = ngx_http_grpc_body_output_filter; u->output.filter_ctx = r; b->flush = 1; cl->next = NULL; return NGX_OK; } static ngx_int_t ngx_http_grpc_reinit_request(ngx_http_request_t *r) { ngx_http_grpc_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_grpc_module); if (ctx == NULL) { return NGX_OK; } ctx->state = 0; ctx->header_sent = 0; ctx->output_closed = 0; ctx->output_blocked = 0; ctx->parsing_headers = 0; ctx->end_stream = 0; ctx->done = 0; ctx->status = 0; ctx->rst = 0; ctx->goaway = 0; ctx->connection = NULL; return NGX_OK; } static ngx_int_t ngx_http_grpc_body_output_filter(void *data, ngx_chain_t *in) { ngx_http_request_t *r = data; off_t file_pos; u_char *p, *pos, *start; size_t len, limit; ngx_buf_t *b; ngx_int_t rc; ngx_uint_t next, last; ngx_chain_t *cl, *out, **ll; ngx_http_upstream_t *u; ngx_http_grpc_ctx_t *ctx; ngx_http_grpc_frame_t *f; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc output filter"); ctx = ngx_http_grpc_get_ctx(r); if (ctx == NULL) { return NGX_ERROR; } if (in) { if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) { return NGX_ERROR; } } out = NULL; ll = &out; if (!ctx->header_sent) { /* first buffer contains headers */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc output header"); ctx->header_sent = 1; if (ctx->id != 1) { /* * keepalive connection: skip connection preface, * update stream identifiers */ b = ctx->in->buf; b->pos += sizeof(ngx_http_grpc_connection_start) - 1; p = b->pos; while (p < b->last) { f = (ngx_http_grpc_frame_t *) p; p += sizeof(ngx_http_grpc_frame_t); f->stream_id_0 = (u_char) ((ctx->id >> 24) & 0xff); f->stream_id_1 = (u_char) ((ctx->id >> 16) & 0xff); f->stream_id_2 = (u_char) ((ctx->id >> 8) & 0xff); f->stream_id_3 = (u_char) (ctx->id & 0xff); p += (f->length_0 << 16) + (f->length_1 << 8) + f->length_2; } } if (ctx->in->buf->last_buf) { ctx->output_closed = 1; } *ll = ctx->in; ll = &ctx->in->next; ctx->in = ctx->in->next; } if (ctx->out) { /* queued control frames */ *ll = ctx->out; for (cl = ctx->out, ll = &cl->next; cl; cl = cl->next) { ll = &cl->next; } ctx->out = NULL; } f = NULL; last = 0; limit = ngx_max(0, ctx->send_window); if (limit > ctx->connection->send_window) { limit = ctx->connection->send_window; } ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc output limit: %uz w:%z:%uz", limit, ctx->send_window, ctx->connection->send_window); #if (NGX_SUPPRESS_WARN) file_pos = 0; pos = NULL; cl = NULL; #endif in = ctx->in; while (in && limit > 0) { ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0, "grpc output in l:%d f:%d %p, pos %p, size: %z " "file: %O, size: %O", in->buf->last_buf, in->buf->in_file, in->buf->start, in->buf->pos, in->buf->last - in->buf->pos, in->buf->file_pos, in->buf->file_last - in->buf->file_pos); if (ngx_buf_special(in->buf)) { goto next; } if (in->buf->in_file) { file_pos = in->buf->file_pos; } else { pos = in->buf->pos; } next = 0; do { cl = ngx_http_grpc_get_buf(r, ctx); if (cl == NULL) { return NGX_ERROR; } b = cl->buf; f = (ngx_http_grpc_frame_t *) b->last; b->last += sizeof(ngx_http_grpc_frame_t); *ll = cl; ll = &cl->next; cl = ngx_chain_get_free_buf(r->pool, &ctx->free); if (cl == NULL) { return NGX_ERROR; } b = cl->buf; start = b->start; ngx_memcpy(b, in->buf, sizeof(ngx_buf_t)); /* * restore b->start to preserve memory allocated in the buffer, * to reuse it later for headers and control frames */ b->start = start; if (in->buf->in_file) { b->file_pos = file_pos; file_pos += ngx_min(NGX_HTTP_V2_DEFAULT_FRAME_SIZE, limit); if (file_pos >= in->buf->file_last) { file_pos = in->buf->file_last; next = 1; } b->file_last = file_pos; len = (ngx_uint_t) (file_pos - b->file_pos); } else { b->pos = pos; pos += ngx_min(NGX_HTTP_V2_DEFAULT_FRAME_SIZE, limit); if (pos >= in->buf->last) { pos = in->buf->last; next = 1; } b->last = pos; len = (ngx_uint_t) (pos - b->pos); } b->tag = (ngx_buf_tag_t) &ngx_http_grpc_body_output_filter; b->shadow = in->buf; b->last_shadow = next; b->last_buf = 0; b->last_in_chain = 0; *ll = cl; ll = &cl->next; f->length_0 = (u_char) ((len >> 16) & 0xff); f->length_1 = (u_char) ((len >> 8) & 0xff); f->length_2 = (u_char) (len & 0xff); f->type = NGX_HTTP_V2_DATA_FRAME; f->flags = 0; f->stream_id_0 = (u_char) ((ctx->id >> 24) & 0xff); f->stream_id_1 = (u_char) ((ctx->id >> 16) & 0xff); f->stream_id_2 = (u_char) ((ctx->id >> 8) & 0xff); f->stream_id_3 = (u_char) (ctx->id & 0xff); limit -= len; ctx->send_window -= len; ctx->connection->send_window -= len; } while (!next && limit > 0); if (!next) { /* * if the buffer wasn't fully sent due to flow control limits, * preserve position for future use */ if (in->buf->in_file) { in->buf->file_pos = file_pos; } else { in->buf->pos = pos; } break; } next: if (in->buf->last_buf) { last = 1; } in = in->next; } ctx->in = in; if (last) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc output last"); ctx->output_closed = 1; if (f) { f->flags |= NGX_HTTP_V2_END_STREAM_FLAG; } else { cl = ngx_http_grpc_get_buf(r, ctx); if (cl == NULL) { return NGX_ERROR; } b = cl->buf; f = (ngx_http_grpc_frame_t *) b->last; b->last += sizeof(ngx_http_grpc_frame_t); f->length_0 = 0; f->length_1 = 0; f->length_2 = 0; f->type = NGX_HTTP_V2_DATA_FRAME; f->flags = NGX_HTTP_V2_END_STREAM_FLAG; f->stream_id_0 = (u_char) ((ctx->id >> 24) & 0xff); f->stream_id_1 = (u_char) ((ctx->id >> 16) & 0xff); f->stream_id_2 = (u_char) ((ctx->id >> 8) & 0xff); f->stream_id_3 = (u_char) (ctx->id & 0xff); *ll = cl; ll = &cl->next; } cl->buf->last_buf = 1; } *ll = NULL; #if (NGX_DEBUG) for (cl = out; cl; cl = cl->next) { ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0, "grpc output out l:%d f:%d %p, pos %p, size: %z " "file: %O, size: %O", cl->buf->last_buf, cl->buf->in_file, cl->buf->start, cl->buf->pos, cl->buf->last - cl->buf->pos, cl->buf->file_pos, cl->buf->file_last - cl->buf->file_pos); } ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc output limit: %uz w:%z:%uz", limit, ctx->send_window, ctx->connection->send_window); #endif rc = ngx_chain_writer(&r->upstream->writer, out); ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, (ngx_buf_tag_t) &ngx_http_grpc_body_output_filter); for (cl = ctx->free; cl; cl = cl->next) { /* mark original buffers as sent */ if (cl->buf->shadow) { if (cl->buf->last_shadow) { b = cl->buf->shadow; b->pos = b->last; } cl->buf->shadow = NULL; } } if (rc == NGX_OK && ctx->in) { rc = NGX_AGAIN; } if (rc == NGX_AGAIN) { ctx->output_blocked = 1; } else { ctx->output_blocked = 0; } if (ctx->done) { /* * We have already got the response and were sending some additional * control frames. Even if there is still something unsent, stop * here anyway. */ u = r->upstream; u->length = 0; if (ctx->in == NULL && ctx->out == NULL && ctx->output_closed && !ctx->output_blocked && !ctx->goaway && ctx->state == ngx_http_grpc_st_start) { u->keepalive = 1; } ngx_post_event(u->peer.connection->read, &ngx_posted_events); } return rc; } static ngx_int_t ngx_http_grpc_process_header(ngx_http_request_t *r) { ngx_str_t *status_line; ngx_int_t rc, status; ngx_buf_t *b; ngx_table_elt_t *h; ngx_http_upstream_t *u; ngx_http_grpc_ctx_t *ctx; ngx_http_upstream_header_t *hh; ngx_http_upstream_main_conf_t *umcf; u = r->upstream; b = &u->buffer; ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc response: %*xs%s, len: %uz", (size_t) ngx_min(b->last - b->pos, 256), b->pos, b->last - b->pos > 256 ? "..." : "", b->last - b->pos); ctx = ngx_http_grpc_get_ctx(r); if (ctx == NULL) { return NGX_ERROR; } umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module); for ( ;; ) { if (ctx->state < ngx_http_grpc_st_payload) { rc = ngx_http_grpc_parse_frame(r, ctx, b); if (rc == NGX_AGAIN) { /* * there can be a lot of window update frames, * so we reset buffer if it is empty and we haven't * started parsing headers yet */ if (!ctx->parsing_headers) { b->pos = b->start; b->last = b->pos; } return NGX_AGAIN; } if (rc == NGX_ERROR) { return NGX_HTTP_UPSTREAM_INVALID_HEADER; } /* * RFC 7540 says that implementations MUST discard frames * that have unknown or unsupported types. However, extension * frames that appear in the middle of a header block are * not permitted. Also, for obvious reasons CONTINUATION frames * cannot appear before headers, and DATA frames are not expected * to appear before all headers are parsed. */ if (ctx->type == NGX_HTTP_V2_DATA_FRAME || (ctx->type == NGX_HTTP_V2_CONTINUATION_FRAME && !ctx->parsing_headers) || (ctx->type != NGX_HTTP_V2_CONTINUATION_FRAME && ctx->parsing_headers)) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent unexpected http2 frame: %d", ctx->type); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } if (ctx->stream_id && ctx->stream_id != ctx->id) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent frame for unknown stream %ui", ctx->stream_id); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } } /* frame payload */ if (ctx->type == NGX_HTTP_V2_RST_STREAM_FRAME) { rc = ngx_http_grpc_parse_rst_stream(r, ctx, b); if (rc == NGX_AGAIN) { return NGX_AGAIN; } if (rc == NGX_ERROR) { return NGX_HTTP_UPSTREAM_INVALID_HEADER; } ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream rejected request with error %ui", ctx->error); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } if (ctx->type == NGX_HTTP_V2_GOAWAY_FRAME) { rc = ngx_http_grpc_parse_goaway(r, ctx, b); if (rc == NGX_AGAIN) { return NGX_AGAIN; } if (rc == NGX_ERROR) { return NGX_HTTP_UPSTREAM_INVALID_HEADER; } /* * If stream_id is lower than one we use, our * request won't be processed and needs to be retried. * If stream_id is greater or equal to the one we use, * we can continue normally (except we can't use this * connection for additional requests). If there is * a real error, the connection will be closed. */ if (ctx->stream_id < ctx->id) { /* TODO: we can retry non-idempotent requests */ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent goaway with error %ui", ctx->error); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } ctx->goaway = 1; continue; } if (ctx->type == NGX_HTTP_V2_WINDOW_UPDATE_FRAME) { rc = ngx_http_grpc_parse_window_update(r, ctx, b); if (rc == NGX_AGAIN) { return NGX_AGAIN; } if (rc == NGX_ERROR) { return NGX_HTTP_UPSTREAM_INVALID_HEADER; } if (ctx->in) { ngx_post_event(u->peer.connection->write, &ngx_posted_events); } continue; } if (ctx->type == NGX_HTTP_V2_SETTINGS_FRAME) { rc = ngx_http_grpc_parse_settings(r, ctx, b); if (rc == NGX_AGAIN) { return NGX_AGAIN; } if (rc == NGX_ERROR) { return NGX_HTTP_UPSTREAM_INVALID_HEADER; } if (ctx->in) { ngx_post_event(u->peer.connection->write, &ngx_posted_events); } continue; } if (ctx->type == NGX_HTTP_V2_PING_FRAME) { rc = ngx_http_grpc_parse_ping(r, ctx, b); if (rc == NGX_AGAIN) { return NGX_AGAIN; } if (rc == NGX_ERROR) { return NGX_HTTP_UPSTREAM_INVALID_HEADER; } ngx_post_event(u->peer.connection->write, &ngx_posted_events); continue; } if (ctx->type == NGX_HTTP_V2_PUSH_PROMISE_FRAME) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent unexpected push promise frame"); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } if (ctx->type != NGX_HTTP_V2_HEADERS_FRAME && ctx->type != NGX_HTTP_V2_CONTINUATION_FRAME) { /* priority, unknown frames */ if (b->last - b->pos < (ssize_t) ctx->rest) { ctx->rest -= b->last - b->pos; b->pos = b->last; return NGX_AGAIN; } b->pos += ctx->rest; ctx->rest = 0; ctx->state = ngx_http_grpc_st_start; continue; } /* headers */ for ( ;; ) { rc = ngx_http_grpc_parse_header(r, ctx, b); if (rc == NGX_AGAIN) { break; } if (rc == NGX_OK) { /* a header line has been parsed successfully */ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc header: \"%V: %V\"", &ctx->name, &ctx->value); if (ctx->name.len && ctx->name.data[0] == ':') { if (ctx->name.len != sizeof(":status") - 1 || ngx_strncmp(ctx->name.data, ":status", sizeof(":status") - 1) != 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid header \"%V: %V\"", &ctx->name, &ctx->value); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } if (ctx->status) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent duplicate :status header"); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } status_line = &ctx->value; if (status_line->len != 3) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid :status \"%V\"", status_line); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } status = ngx_atoi(status_line->data, 3); if (status == NGX_ERROR) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid :status \"%V\"", status_line); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } if (status < NGX_HTTP_OK) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent unexpected :status \"%V\"", status_line); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } u->headers_in.status_n = status; if (u->state && u->state->status == 0) { u->state->status = status; } ctx->status = 1; continue; } else if (!ctx->status) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent no :status header"); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } h = ngx_list_push(&u->headers_in.headers); if (h == NULL) { return NGX_ERROR; } h->key = ctx->name; h->value = ctx->value; h->lowcase_key = h->key.data; h->hash = ngx_hash_key(h->key.data, h->key.len); hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len); if (hh) { rc = hh->handler(r, h, hh->offset); if (rc != NGX_OK) { return rc; } } continue; } if (rc == NGX_HTTP_PARSE_HEADER_DONE) { /* a whole header has been parsed successfully */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc header done"); if (ctx->end_stream) { u->headers_in.content_length_n = 0; if (ctx->in == NULL && ctx->out == NULL && ctx->output_closed && !ctx->output_blocked && !ctx->goaway && b->last == b->pos) { u->keepalive = 1; } } return NGX_OK; } /* there was error while a header line parsing */ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid header"); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } /* rc == NGX_AGAIN */ if (ctx->rest == 0) { ctx->state = ngx_http_grpc_st_start; continue; } return NGX_AGAIN; } } static ngx_int_t ngx_http_grpc_filter_init(void *data) { ngx_http_grpc_ctx_t *ctx = data; ngx_http_request_t *r; ngx_http_upstream_t *u; r = ctx->request; u = r->upstream; if (u->headers_in.status_n == NGX_HTTP_NO_CONTENT || u->headers_in.status_n == NGX_HTTP_NOT_MODIFIED || r->method == NGX_HTTP_HEAD) { ctx->length = 0; } else { ctx->length = u->headers_in.content_length_n; } if (ctx->end_stream) { if (ctx->length > 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream prematurely closed stream"); return NGX_ERROR; } u->length = 0; ctx->done = 1; } else { u->length = 1; } return NGX_OK; } static ngx_int_t ngx_http_grpc_filter(void *data, ssize_t bytes) { ngx_http_grpc_ctx_t *ctx = data; ngx_int_t rc; ngx_buf_t *b, *buf; ngx_chain_t *cl, **ll; ngx_table_elt_t *h; ngx_http_request_t *r; ngx_http_upstream_t *u; r = ctx->request; u = r->upstream; b = &u->buffer; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc filter bytes:%z", bytes); b->pos = b->last; b->last += bytes; for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) { ll = &cl->next; } for ( ;; ) { if (ctx->state < ngx_http_grpc_st_payload) { rc = ngx_http_grpc_parse_frame(r, ctx, b); if (rc == NGX_AGAIN) { if (ctx->done) { if (ctx->length > 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream prematurely closed stream"); return NGX_ERROR; } /* * We have finished parsing the response and the * remaining control frames. If there are unsent * control frames, post a write event to send them. */ if (ctx->out) { ngx_post_event(u->peer.connection->write, &ngx_posted_events); return NGX_AGAIN; } u->length = 0; if (ctx->in == NULL && ctx->output_closed && !ctx->output_blocked && !ctx->goaway && ctx->state == ngx_http_grpc_st_start) { u->keepalive = 1; } break; } return NGX_AGAIN; } if (rc == NGX_ERROR) { return NGX_ERROR; } if ((ctx->type == NGX_HTTP_V2_CONTINUATION_FRAME && !ctx->parsing_headers) || (ctx->type != NGX_HTTP_V2_CONTINUATION_FRAME && ctx->parsing_headers)) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent unexpected http2 frame: %d", ctx->type); return NGX_ERROR; } if (ctx->type == NGX_HTTP_V2_DATA_FRAME) { if (ctx->stream_id != ctx->id) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent data frame " "for unknown stream %ui", ctx->stream_id); return NGX_ERROR; } if (ctx->rest > ctx->recv_window) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream violated stream flow control, " "received %uz data frame with window %uz", ctx->rest, ctx->recv_window); return NGX_ERROR; } if (ctx->rest > ctx->connection->recv_window) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream violated connection flow control, " "received %uz data frame with window %uz", ctx->rest, ctx->connection->recv_window); return NGX_ERROR; } ctx->recv_window -= ctx->rest; ctx->connection->recv_window -= ctx->rest; if (ctx->connection->recv_window < NGX_HTTP_V2_MAX_WINDOW / 4 || ctx->recv_window < NGX_HTTP_V2_MAX_WINDOW / 4) { if (ngx_http_grpc_send_window_update(r, ctx) != NGX_OK) { return NGX_ERROR; } ngx_post_event(u->peer.connection->write, &ngx_posted_events); } } if (ctx->stream_id && ctx->stream_id != ctx->id) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent frame for unknown stream %ui", ctx->stream_id); return NGX_ERROR; } if (ctx->stream_id && ctx->done && ctx->type != NGX_HTTP_V2_RST_STREAM_FRAME && ctx->type != NGX_HTTP_V2_WINDOW_UPDATE_FRAME) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent frame for closed stream %ui", ctx->stream_id); return NGX_ERROR; } ctx->padding = 0; } if (ctx->state == ngx_http_grpc_st_padding) { if (b->last - b->pos < (ssize_t) ctx->rest) { ctx->rest -= b->last - b->pos; b->pos = b->last; return NGX_AGAIN; } b->pos += ctx->rest; ctx->rest = 0; ctx->state = ngx_http_grpc_st_start; if (ctx->flags & NGX_HTTP_V2_END_STREAM_FLAG) { ctx->done = 1; } continue; } /* frame payload */ if (ctx->type == NGX_HTTP_V2_RST_STREAM_FRAME) { rc = ngx_http_grpc_parse_rst_stream(r, ctx, b); if (rc == NGX_AGAIN) { return NGX_AGAIN; } if (rc == NGX_ERROR) { return NGX_ERROR; } if (ctx->error || !ctx->done) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream rejected request with error %ui", ctx->error); return NGX_ERROR; } if (ctx->rst) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent frame for closed stream %ui", ctx->stream_id); return NGX_ERROR; } ctx->rst = 1; continue; } if (ctx->type == NGX_HTTP_V2_GOAWAY_FRAME) { rc = ngx_http_grpc_parse_goaway(r, ctx, b); if (rc == NGX_AGAIN) { return NGX_AGAIN; } if (rc == NGX_ERROR) { return NGX_ERROR; } /* * If stream_id is lower than one we use, our * request won't be processed and needs to be retried. * If stream_id is greater or equal to the one we use, * we can continue normally (except we can't use this * connection for additional requests). If there is * a real error, the connection will be closed. */ if (ctx->stream_id < ctx->id) { /* TODO: we can retry non-idempotent requests */ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent goaway with error %ui", ctx->error); return NGX_ERROR; } ctx->goaway = 1; continue; } if (ctx->type == NGX_HTTP_V2_WINDOW_UPDATE_FRAME) { rc = ngx_http_grpc_parse_window_update(r, ctx, b); if (rc == NGX_AGAIN) { return NGX_AGAIN; } if (rc == NGX_ERROR) { return NGX_ERROR; } if (ctx->in) { ngx_post_event(u->peer.connection->write, &ngx_posted_events); } continue; } if (ctx->type == NGX_HTTP_V2_SETTINGS_FRAME) { rc = ngx_http_grpc_parse_settings(r, ctx, b); if (rc == NGX_AGAIN) { return NGX_AGAIN; } if (rc == NGX_ERROR) { return NGX_ERROR; } if (ctx->in) { ngx_post_event(u->peer.connection->write, &ngx_posted_events); } continue; } if (ctx->type == NGX_HTTP_V2_PING_FRAME) { rc = ngx_http_grpc_parse_ping(r, ctx, b); if (rc == NGX_AGAIN) { return NGX_AGAIN; } if (rc == NGX_ERROR) { return NGX_ERROR; } ngx_post_event(u->peer.connection->write, &ngx_posted_events); continue; } if (ctx->type == NGX_HTTP_V2_PUSH_PROMISE_FRAME) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent unexpected push promise frame"); return NGX_ERROR; } if (ctx->type == NGX_HTTP_V2_HEADERS_FRAME || ctx->type == NGX_HTTP_V2_CONTINUATION_FRAME) { for ( ;; ) { rc = ngx_http_grpc_parse_header(r, ctx, b); if (rc == NGX_AGAIN) { break; } if (rc == NGX_OK) { /* a header line has been parsed successfully */ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc trailer: \"%V: %V\"", &ctx->name, &ctx->value); if (ctx->name.len && ctx->name.data[0] == ':') { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid " "trailer \"%V: %V\"", &ctx->name, &ctx->value); return NGX_ERROR; } h = ngx_list_push(&u->headers_in.trailers); if (h == NULL) { return NGX_ERROR; } h->key = ctx->name; h->value = ctx->value; h->lowcase_key = h->key.data; h->hash = ngx_hash_key(h->key.data, h->key.len); continue; } if (rc == NGX_HTTP_PARSE_HEADER_DONE) { /* a whole header has been parsed successfully */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc trailer done"); if (ctx->end_stream) { ctx->done = 1; break; } ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent trailer without " "end stream flag"); return NGX_ERROR; } /* there was error while a header line parsing */ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid trailer"); return NGX_ERROR; } if (rc == NGX_HTTP_PARSE_HEADER_DONE) { continue; } /* rc == NGX_AGAIN */ if (ctx->rest == 0) { ctx->state = ngx_http_grpc_st_start; continue; } return NGX_AGAIN; } if (ctx->type != NGX_HTTP_V2_DATA_FRAME) { /* priority, unknown frames */ if (b->last - b->pos < (ssize_t) ctx->rest) { ctx->rest -= b->last - b->pos; b->pos = b->last; return NGX_AGAIN; } b->pos += ctx->rest; ctx->rest = 0; ctx->state = ngx_http_grpc_st_start; continue; } /* * data frame: * * +---------------+ * |Pad Length? (8)| * +---------------+-----------------------------------------------+ * | Data (*) ... * +---------------------------------------------------------------+ * | Padding (*) ... * +---------------------------------------------------------------+ */ if (ctx->flags & NGX_HTTP_V2_PADDED_FLAG) { if (ctx->rest == 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent too short http2 frame"); return NGX_ERROR; } if (b->pos == b->last) { return NGX_AGAIN; } ctx->flags &= ~NGX_HTTP_V2_PADDED_FLAG; ctx->padding = *b->pos++; ctx->rest -= 1; if (ctx->padding > ctx->rest) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent http2 frame with too long " "padding: %d in frame %uz", ctx->padding, ctx->rest); return NGX_ERROR; } continue; } if (ctx->rest == ctx->padding) { goto done; } if (b->pos == b->last) { return NGX_AGAIN; } cl = ngx_chain_get_free_buf(r->pool, &u->free_bufs); if (cl == NULL) { return NGX_ERROR; } *ll = cl; ll = &cl->next; buf = cl->buf; buf->flush = 1; buf->memory = 1; buf->pos = b->pos; buf->tag = u->output.tag; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc output buf %p", buf->pos); if (b->last - b->pos < (ssize_t) ctx->rest - ctx->padding) { ctx->rest -= b->last - b->pos; b->pos = b->last; buf->last = b->pos; if (ctx->length != -1) { if (buf->last - buf->pos > ctx->length) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent response body larger " "than indicated content length"); return NGX_ERROR; } ctx->length -= buf->last - buf->pos; } return NGX_AGAIN; } b->pos += ctx->rest - ctx->padding; buf->last = b->pos; ctx->rest = ctx->padding; if (ctx->length != -1) { if (buf->last - buf->pos > ctx->length) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent response body larger " "than indicated content length"); return NGX_ERROR; } ctx->length -= buf->last - buf->pos; } done: if (ctx->padding) { ctx->state = ngx_http_grpc_st_padding; continue; } ctx->state = ngx_http_grpc_st_start; if (ctx->flags & NGX_HTTP_V2_END_STREAM_FLAG) { ctx->done = 1; } } return NGX_OK; } static ngx_int_t ngx_http_grpc_parse_frame(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b) { u_char ch, *p; ngx_http_grpc_state_e state; state = ctx->state; for (p = b->pos; p < b->last; p++) { ch = *p; #if 0 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc frame byte: %02Xd, s:%d", ch, state); #endif switch (state) { case ngx_http_grpc_st_start: ctx->rest = ch << 16; state = ngx_http_grpc_st_length_2; break; case ngx_http_grpc_st_length_2: ctx->rest |= ch << 8; state = ngx_http_grpc_st_length_3; break; case ngx_http_grpc_st_length_3: ctx->rest |= ch; if (ctx->rest > NGX_HTTP_V2_DEFAULT_FRAME_SIZE) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent too large http2 frame: %uz", ctx->rest); return NGX_ERROR; } state = ngx_http_grpc_st_type; break; case ngx_http_grpc_st_type: ctx->type = ch; state = ngx_http_grpc_st_flags; break; case ngx_http_grpc_st_flags: ctx->flags = ch; state = ngx_http_grpc_st_stream_id; break; case ngx_http_grpc_st_stream_id: ctx->stream_id = (ch & 0x7f) << 24; state = ngx_http_grpc_st_stream_id_2; break; case ngx_http_grpc_st_stream_id_2: ctx->stream_id |= ch << 16; state = ngx_http_grpc_st_stream_id_3; break; case ngx_http_grpc_st_stream_id_3: ctx->stream_id |= ch << 8; state = ngx_http_grpc_st_stream_id_4; break; case ngx_http_grpc_st_stream_id_4: ctx->stream_id |= ch; ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc frame: %d, len: %uz, f:%d, i:%ui", ctx->type, ctx->rest, ctx->flags, ctx->stream_id); b->pos = p + 1; ctx->state = ngx_http_grpc_st_payload; ctx->frame_state = 0; return NGX_OK; /* suppress warning */ case ngx_http_grpc_st_payload: case ngx_http_grpc_st_padding: break; } } b->pos = p; ctx->state = state; return NGX_AGAIN; } static ngx_int_t ngx_http_grpc_parse_header(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b) { u_char ch, *p, *last; size_t min; ngx_int_t rc; enum { sw_start = 0, sw_padding_length, sw_dependency, sw_dependency_2, sw_dependency_3, sw_dependency_4, sw_weight, sw_fragment, sw_padding } state; state = ctx->frame_state; if (state == sw_start) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc parse header: start"); if (ctx->type == NGX_HTTP_V2_HEADERS_FRAME) { ctx->parsing_headers = 1; ctx->fragment_state = 0; min = (ctx->flags & NGX_HTTP_V2_PADDED_FLAG ? 1 : 0) + (ctx->flags & NGX_HTTP_V2_PRIORITY_FLAG ? 5 : 0); if (ctx->rest < min) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent headers frame " "with invalid length: %uz", ctx->rest); return NGX_ERROR; } if (ctx->flags & NGX_HTTP_V2_END_STREAM_FLAG) { ctx->end_stream = 1; } if (ctx->flags & NGX_HTTP_V2_PADDED_FLAG) { state = sw_padding_length; } else if (ctx->flags & NGX_HTTP_V2_PRIORITY_FLAG) { state = sw_dependency; } else { state = sw_fragment; } } else if (ctx->type == NGX_HTTP_V2_CONTINUATION_FRAME) { state = sw_fragment; } ctx->padding = 0; ctx->frame_state = state; } if (state < sw_fragment) { if (b->last - b->pos < (ssize_t) ctx->rest) { last = b->last; } else { last = b->pos + ctx->rest; } for (p = b->pos; p < last; p++) { ch = *p; #if 0 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc header byte: %02Xd s:%d", ch, state); #endif /* * headers frame: * * +---------------+ * |Pad Length? (8)| * +-+-------------+----------------------------------------------+ * |E| Stream Dependency? (31) | * +-+-------------+----------------------------------------------+ * | Weight? (8) | * +-+-------------+----------------------------------------------+ * | Header Block Fragment (*) ... * +--------------------------------------------------------------+ * | Padding (*) ... * +--------------------------------------------------------------+ */ switch (state) { case sw_padding_length: ctx->padding = ch; if (ctx->flags & NGX_HTTP_V2_PRIORITY_FLAG) { state = sw_dependency; break; } goto fragment; case sw_dependency: state = sw_dependency_2; break; case sw_dependency_2: state = sw_dependency_3; break; case sw_dependency_3: state = sw_dependency_4; break; case sw_dependency_4: state = sw_weight; break; case sw_weight: goto fragment; /* suppress warning */ case sw_start: case sw_fragment: case sw_padding: break; } } ctx->rest -= p - b->pos; b->pos = p; ctx->frame_state = state; return NGX_AGAIN; fragment: p++; ctx->rest -= p - b->pos; b->pos = p; if (ctx->padding > ctx->rest) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent http2 frame with too long " "padding: %d in frame %uz", ctx->padding, ctx->rest); return NGX_ERROR; } state = sw_fragment; ctx->frame_state = state; } if (state == sw_fragment) { rc = ngx_http_grpc_parse_fragment(r, ctx, b); if (rc == NGX_AGAIN) { return NGX_AGAIN; } if (rc == NGX_ERROR) { return NGX_ERROR; } if (rc == NGX_OK) { return NGX_OK; } /* rc == NGX_DONE */ state = sw_padding; ctx->frame_state = state; } if (state == sw_padding) { if (b->last - b->pos < (ssize_t) ctx->rest) { ctx->rest -= b->last - b->pos; b->pos = b->last; return NGX_AGAIN; } b->pos += ctx->rest; ctx->rest = 0; ctx->state = ngx_http_grpc_st_start; if (ctx->flags & NGX_HTTP_V2_END_HEADERS_FLAG) { if (ctx->fragment_state) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent truncated http2 header"); return NGX_ERROR; } ctx->parsing_headers = 0; return NGX_HTTP_PARSE_HEADER_DONE; } return NGX_AGAIN; } /* unreachable */ return NGX_ERROR; } static ngx_int_t ngx_http_grpc_parse_fragment(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b) { u_char ch, *p, *last; size_t size; ngx_uint_t index, size_update; enum { sw_start = 0, sw_index, sw_name_length, sw_name_length_2, sw_name_length_3, sw_name_length_4, sw_name, sw_name_bytes, sw_value_length, sw_value_length_2, sw_value_length_3, sw_value_length_4, sw_value, sw_value_bytes } state; /* header block fragment */ #if 0 ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc header fragment %p:%p rest:%uz", b->pos, b->last, ctx->rest); #endif if (b->last - b->pos < (ssize_t) ctx->rest - ctx->padding) { last = b->last; } else { last = b->pos + ctx->rest - ctx->padding; } state = ctx->fragment_state; for (p = b->pos; p < last; p++) { ch = *p; #if 0 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc header byte: %02Xd s:%d", ch, state); #endif switch (state) { case sw_start: ctx->index = 0; if ((ch & 0x80) == 0x80) { /* * indexed header: * * 0 1 2 3 4 5 6 7 * +---+---+---+---+---+---+---+---+ * | 1 | Index (7+) | * +---+---------------------------+ */ index = ch & ~0x80; if (index == 0 || index > 61) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid http2 " "table index: %ui", index); return NGX_ERROR; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc indexed header: %ui", index); ctx->index = index; ctx->literal = 0; goto done; } else if ((ch & 0xc0) == 0x40) { /* * literal header with incremental indexing: * * 0 1 2 3 4 5 6 7 * +---+---+---+---+---+---+---+---+ * | 0 | 1 | Index (6+) | * +---+---+-----------------------+ * | H | Value Length (7+) | * +---+---------------------------+ * | Value String (Length octets) | * +-------------------------------+ * * 0 1 2 3 4 5 6 7 * +---+---+---+---+---+---+---+---+ * | 0 | 1 | 0 | * +---+---+-----------------------+ * | H | Name Length (7+) | * +---+---------------------------+ * | Name String (Length octets) | * +---+---------------------------+ * | H | Value Length (7+) | * +---+---------------------------+ * | Value String (Length octets) | * +-------------------------------+ */ index = ch & ~0xc0; if (index > 61) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid http2 " "table index: %ui", index); return NGX_ERROR; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc literal header: %ui", index); if (index == 0) { state = sw_name_length; break; } ctx->index = index; ctx->literal = 1; state = sw_value_length; break; } else if ((ch & 0xe0) == 0x20) { /* * dynamic table size update: * * 0 1 2 3 4 5 6 7 * +---+---+---+---+---+---+---+---+ * | 0 | 0 | 1 | Max size (5+) | * +---+---------------------------+ */ size_update = ch & ~0xe0; if (size_update > 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid http2 " "dynamic table size update: %ui", size_update); return NGX_ERROR; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc table size update: %ui", size_update); break; } else if ((ch & 0xf0) == 0x10) { /* * literal header field never indexed: * * 0 1 2 3 4 5 6 7 * +---+---+---+---+---+---+---+---+ * | 0 | 0 | 0 | 1 | Index (4+) | * +---+---+-----------------------+ * | H | Value Length (7+) | * +---+---------------------------+ * | Value String (Length octets) | * +-------------------------------+ * * 0 1 2 3 4 5 6 7 * +---+---+---+---+---+---+---+---+ * | 0 | 0 | 0 | 1 | 0 | * +---+---+-----------------------+ * | H | Name Length (7+) | * +---+---------------------------+ * | Name String (Length octets) | * +---+---------------------------+ * | H | Value Length (7+) | * +---+---------------------------+ * | Value String (Length octets) | * +-------------------------------+ */ index = ch & ~0xf0; if (index == 0x0f) { ctx->index = index; ctx->literal = 1; state = sw_index; break; } if (index == 0) { state = sw_name_length; break; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc literal header never indexed: %ui", index); ctx->index = index; ctx->literal = 1; state = sw_value_length; break; } else if ((ch & 0xf0) == 0x00) { /* * literal header field without indexing: * * 0 1 2 3 4 5 6 7 * +---+---+---+---+---+---+---+---+ * | 0 | 0 | 0 | 0 | Index (4+) | * +---+---+-----------------------+ * | H | Value Length (7+) | * +---+---------------------------+ * | Value String (Length octets) | * +-------------------------------+ * * 0 1 2 3 4 5 6 7 * +---+---+---+---+---+---+---+---+ * | 0 | 0 | 0 | 0 | 0 | * +---+---+-----------------------+ * | H | Name Length (7+) | * +---+---------------------------+ * | Name String (Length octets) | * +---+---------------------------+ * | H | Value Length (7+) | * +---+---------------------------+ * | Value String (Length octets) | * +-------------------------------+ */ index = ch & ~0xf0; if (index == 0x0f) { ctx->index = index; ctx->literal = 1; state = sw_index; break; } if (index == 0) { state = sw_name_length; break; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc literal header without indexing: %ui", index); ctx->index = index; ctx->literal = 1; state = sw_value_length; break; } /* not reached */ return NGX_ERROR; case sw_index: ctx->index = ctx->index + (ch & ~0x80); if (ch & 0x80) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent http2 table index " "with continuation flag"); return NGX_ERROR; } if (ctx->index > 61) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid http2 " "table index: %ui", ctx->index); return NGX_ERROR; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc header index: %ui", ctx->index); state = sw_value_length; break; case sw_name_length: ctx->field_huffman = ch & 0x80 ? 1 : 0; ctx->field_length = ch & ~0x80; if (ctx->field_length == 0x7f) { state = sw_name_length_2; break; } if (ctx->field_length == 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent zero http2 " "header name length"); return NGX_ERROR; } state = sw_name; break; case sw_name_length_2: ctx->field_length += ch & ~0x80; if (ch & 0x80) { state = sw_name_length_3; break; } state = sw_name; break; case sw_name_length_3: ctx->field_length += (ch & ~0x80) << 7; if (ch & 0x80) { state = sw_name_length_4; break; } state = sw_name; break; case sw_name_length_4: ctx->field_length += (ch & ~0x80) << 14; if (ch & 0x80) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent too large http2 " "header name length"); return NGX_ERROR; } state = sw_name; break; case sw_name: ctx->name.len = ctx->field_huffman ? ctx->field_length * 8 / 5 : ctx->field_length; ctx->name.data = ngx_pnalloc(r->pool, ctx->name.len + 1); if (ctx->name.data == NULL) { return NGX_ERROR; } ctx->field_end = ctx->name.data; ctx->field_rest = ctx->field_length; ctx->field_state = 0; state = sw_name_bytes; /* fall through */ case sw_name_bytes: ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc name: len:%uz h:%d last:%uz, rest:%uz", ctx->field_length, ctx->field_huffman, last - p, ctx->rest - (p - b->pos)); size = ngx_min(last - p, (ssize_t) ctx->field_rest); ctx->field_rest -= size; if (ctx->field_huffman) { if (ngx_http_huff_decode(&ctx->field_state, p, size, &ctx->field_end, ctx->field_rest == 0, r->connection->log) != NGX_OK) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid encoded header"); return NGX_ERROR; } ctx->name.len = ctx->field_end - ctx->name.data; ctx->name.data[ctx->name.len] = '\0'; } else { ctx->field_end = ngx_cpymem(ctx->field_end, p, size); ctx->name.data[ctx->name.len] = '\0'; } p += size - 1; if (ctx->field_rest == 0) { state = sw_value_length; } break; case sw_value_length: ctx->field_huffman = ch & 0x80 ? 1 : 0; ctx->field_length = ch & ~0x80; if (ctx->field_length == 0x7f) { state = sw_value_length_2; break; } if (ctx->field_length == 0) { ngx_str_set(&ctx->value, ""); goto done; } state = sw_value; break; case sw_value_length_2: ctx->field_length += ch & ~0x80; if (ch & 0x80) { state = sw_value_length_3; break; } state = sw_value; break; case sw_value_length_3: ctx->field_length += (ch & ~0x80) << 7; if (ch & 0x80) { state = sw_value_length_4; break; } state = sw_value; break; case sw_value_length_4: ctx->field_length += (ch & ~0x80) << 14; if (ch & 0x80) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent too large http2 " "header value length"); return NGX_ERROR; } state = sw_value; break; case sw_value: ctx->value.len = ctx->field_huffman ? ctx->field_length * 8 / 5 : ctx->field_length; ctx->value.data = ngx_pnalloc(r->pool, ctx->value.len + 1); if (ctx->value.data == NULL) { return NGX_ERROR; } ctx->field_end = ctx->value.data; ctx->field_rest = ctx->field_length; ctx->field_state = 0; state = sw_value_bytes; /* fall through */ case sw_value_bytes: ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc value: len:%uz h:%d last:%uz, rest:%uz", ctx->field_length, ctx->field_huffman, last - p, ctx->rest - (p - b->pos)); size = ngx_min(last - p, (ssize_t) ctx->field_rest); ctx->field_rest -= size; if (ctx->field_huffman) { if (ngx_http_huff_decode(&ctx->field_state, p, size, &ctx->field_end, ctx->field_rest == 0, r->connection->log) != NGX_OK) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid encoded header"); return NGX_ERROR; } ctx->value.len = ctx->field_end - ctx->value.data; ctx->value.data[ctx->value.len] = '\0'; } else { ctx->field_end = ngx_cpymem(ctx->field_end, p, size); ctx->value.data[ctx->value.len] = '\0'; } p += size - 1; if (ctx->field_rest == 0) { goto done; } break; } continue; done: p++; ctx->rest -= p - b->pos; ctx->fragment_state = sw_start; b->pos = p; if (ctx->index) { ctx->name = *ngx_http_v2_get_static_name(ctx->index); } if (ctx->index && !ctx->literal) { ctx->value = *ngx_http_v2_get_static_value(ctx->index); } if (!ctx->index) { if (ngx_http_grpc_validate_header_name(r, &ctx->name) != NGX_OK) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid header: \"%V: %V\"", &ctx->name, &ctx->value); return NGX_ERROR; } } if (!ctx->index || ctx->literal) { if (ngx_http_grpc_validate_header_value(r, &ctx->value) != NGX_OK) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid header: \"%V: %V\"", &ctx->name, &ctx->value); return NGX_ERROR; } } return NGX_OK; } ctx->rest -= p - b->pos; ctx->fragment_state = state; b->pos = p; if (ctx->rest > ctx->padding) { return NGX_AGAIN; } return NGX_DONE; } static ngx_int_t ngx_http_grpc_validate_header_name(ngx_http_request_t *r, ngx_str_t *s) { u_char ch; ngx_uint_t i; for (i = 0; i < s->len; i++) { ch = s->data[i]; if (ch == ':' && i > 0) { return NGX_ERROR; } if (ch >= 'A' && ch <= 'Z') { return NGX_ERROR; } if (ch <= 0x20 || ch == 0x7f) { return NGX_ERROR; } } return NGX_OK; } static ngx_int_t ngx_http_grpc_validate_header_value(ngx_http_request_t *r, ngx_str_t *s) { u_char ch; ngx_uint_t i; for (i = 0; i < s->len; i++) { ch = s->data[i]; if (ch == '\0' || ch == CR || ch == LF) { return NGX_ERROR; } } return NGX_OK; } static ngx_int_t ngx_http_grpc_parse_rst_stream(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b) { u_char ch, *p, *last; enum { sw_start = 0, sw_error_2, sw_error_3, sw_error_4 } state; if (b->last - b->pos < (ssize_t) ctx->rest) { last = b->last; } else { last = b->pos + ctx->rest; } state = ctx->frame_state; if (state == sw_start) { if (ctx->rest != 4) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent rst stream frame " "with invalid length: %uz", ctx->rest); return NGX_ERROR; } } for (p = b->pos; p < last; p++) { ch = *p; #if 0 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc rst byte: %02Xd s:%d", ch, state); #endif switch (state) { case sw_start: ctx->error = (ngx_uint_t) ch << 24; state = sw_error_2; break; case sw_error_2: ctx->error |= ch << 16; state = sw_error_3; break; case sw_error_3: ctx->error |= ch << 8; state = sw_error_4; break; case sw_error_4: ctx->error |= ch; state = sw_start; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc error: %ui", ctx->error); break; } } ctx->rest -= p - b->pos; ctx->frame_state = state; b->pos = p; if (ctx->rest > 0) { return NGX_AGAIN; } ctx->state = ngx_http_grpc_st_start; return NGX_OK; } static ngx_int_t ngx_http_grpc_parse_goaway(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b) { u_char ch, *p, *last; enum { sw_start = 0, sw_last_stream_id_2, sw_last_stream_id_3, sw_last_stream_id_4, sw_error, sw_error_2, sw_error_3, sw_error_4, sw_debug } state; if (b->last - b->pos < (ssize_t) ctx->rest) { last = b->last; } else { last = b->pos + ctx->rest; } state = ctx->frame_state; if (state == sw_start) { if (ctx->stream_id) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent goaway frame " "with non-zero stream id: %ui", ctx->stream_id); return NGX_ERROR; } if (ctx->rest < 8) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent goaway frame " "with invalid length: %uz", ctx->rest); return NGX_ERROR; } } for (p = b->pos; p < last; p++) { ch = *p; #if 0 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc goaway byte: %02Xd s:%d", ch, state); #endif switch (state) { case sw_start: ctx->stream_id = (ch & 0x7f) << 24; state = sw_last_stream_id_2; break; case sw_last_stream_id_2: ctx->stream_id |= ch << 16; state = sw_last_stream_id_3; break; case sw_last_stream_id_3: ctx->stream_id |= ch << 8; state = sw_last_stream_id_4; break; case sw_last_stream_id_4: ctx->stream_id |= ch; state = sw_error; break; case sw_error: ctx->error = (ngx_uint_t) ch << 24; state = sw_error_2; break; case sw_error_2: ctx->error |= ch << 16; state = sw_error_3; break; case sw_error_3: ctx->error |= ch << 8; state = sw_error_4; break; case sw_error_4: ctx->error |= ch; state = sw_debug; break; case sw_debug: break; } } ctx->rest -= p - b->pos; ctx->frame_state = state; b->pos = p; if (ctx->rest > 0) { return NGX_AGAIN; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc goaway: %ui, stream %ui", ctx->error, ctx->stream_id); ctx->state = ngx_http_grpc_st_start; return NGX_OK; } static ngx_int_t ngx_http_grpc_parse_window_update(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b) { u_char ch, *p, *last; enum { sw_start = 0, sw_size_2, sw_size_3, sw_size_4 } state; if (b->last - b->pos < (ssize_t) ctx->rest) { last = b->last; } else { last = b->pos + ctx->rest; } state = ctx->frame_state; if (state == sw_start) { if (ctx->rest != 4) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent window update frame " "with invalid length: %uz", ctx->rest); return NGX_ERROR; } } for (p = b->pos; p < last; p++) { ch = *p; #if 0 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc window update byte: %02Xd s:%d", ch, state); #endif switch (state) { case sw_start: ctx->window_update = (ch & 0x7f) << 24; state = sw_size_2; break; case sw_size_2: ctx->window_update |= ch << 16; state = sw_size_3; break; case sw_size_3: ctx->window_update |= ch << 8; state = sw_size_4; break; case sw_size_4: ctx->window_update |= ch; state = sw_start; break; } } ctx->rest -= p - b->pos; ctx->frame_state = state; b->pos = p; if (ctx->rest > 0) { return NGX_AGAIN; } ctx->state = ngx_http_grpc_st_start; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc window update: %ui", ctx->window_update); if (ctx->stream_id) { if (ctx->window_update > (size_t) NGX_HTTP_V2_MAX_WINDOW - ctx->send_window) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent too large window update"); return NGX_ERROR; } ctx->send_window += ctx->window_update; } else { if (ctx->window_update > NGX_HTTP_V2_MAX_WINDOW - ctx->connection->send_window) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent too large window update"); return NGX_ERROR; } ctx->connection->send_window += ctx->window_update; } return NGX_OK; } static ngx_int_t ngx_http_grpc_parse_settings(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b) { u_char ch, *p, *last; ssize_t window_update; enum { sw_start = 0, sw_id, sw_id_2, sw_value, sw_value_2, sw_value_3, sw_value_4 } state; if (b->last - b->pos < (ssize_t) ctx->rest) { last = b->last; } else { last = b->pos + ctx->rest; } state = ctx->frame_state; if (state == sw_start) { if (ctx->stream_id) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent settings frame " "with non-zero stream id: %ui", ctx->stream_id); return NGX_ERROR; } if (ctx->flags & NGX_HTTP_V2_ACK_FLAG) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc settings ack"); if (ctx->rest != 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent settings frame " "with ack flag and non-zero length: %uz", ctx->rest); return NGX_ERROR; } ctx->state = ngx_http_grpc_st_start; return NGX_OK; } if (ctx->rest % 6 != 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent settings frame " "with invalid length: %uz", ctx->rest); return NGX_ERROR; } if (ctx->free == NULL && ctx->settings++ > 1000) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent too many settings frames"); return NGX_ERROR; } } for (p = b->pos; p < last; p++) { ch = *p; #if 0 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc settings byte: %02Xd s:%d", ch, state); #endif switch (state) { case sw_start: case sw_id: ctx->setting_id = ch << 8; state = sw_id_2; break; case sw_id_2: ctx->setting_id |= ch; state = sw_value; break; case sw_value: ctx->setting_value = (ngx_uint_t) ch << 24; state = sw_value_2; break; case sw_value_2: ctx->setting_value |= ch << 16; state = sw_value_3; break; case sw_value_3: ctx->setting_value |= ch << 8; state = sw_value_4; break; case sw_value_4: ctx->setting_value |= ch; state = sw_id; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc setting: %ui %ui", ctx->setting_id, ctx->setting_value); /* * The following settings are defined by the protocol: * * SETTINGS_HEADER_TABLE_SIZE, SETTINGS_ENABLE_PUSH, * SETTINGS_MAX_CONCURRENT_STREAMS, SETTINGS_INITIAL_WINDOW_SIZE, * SETTINGS_MAX_FRAME_SIZE, SETTINGS_MAX_HEADER_LIST_SIZE * * Only SETTINGS_INITIAL_WINDOW_SIZE seems to be needed in * a simple client. */ if (ctx->setting_id == 0x04) { /* SETTINGS_INITIAL_WINDOW_SIZE */ if (ctx->setting_value > NGX_HTTP_V2_MAX_WINDOW) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent settings frame " "with too large initial window size: %ui", ctx->setting_value); return NGX_ERROR; } window_update = ctx->setting_value - ctx->connection->init_window; ctx->connection->init_window = ctx->setting_value; if (ctx->send_window > 0 && window_update > (ssize_t) NGX_HTTP_V2_MAX_WINDOW - ctx->send_window) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent settings frame " "with too large initial window size: %ui", ctx->setting_value); return NGX_ERROR; } ctx->send_window += window_update; } break; } } ctx->rest -= p - b->pos; ctx->frame_state = state; b->pos = p; if (ctx->rest > 0) { return NGX_AGAIN; } ctx->state = ngx_http_grpc_st_start; return ngx_http_grpc_send_settings_ack(r, ctx); } static ngx_int_t ngx_http_grpc_parse_ping(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b) { u_char ch, *p, *last; enum { sw_start = 0, sw_data_2, sw_data_3, sw_data_4, sw_data_5, sw_data_6, sw_data_7, sw_data_8 } state; if (b->last - b->pos < (ssize_t) ctx->rest) { last = b->last; } else { last = b->pos + ctx->rest; } state = ctx->frame_state; if (state == sw_start) { if (ctx->stream_id) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent ping frame " "with non-zero stream id: %ui", ctx->stream_id); return NGX_ERROR; } if (ctx->rest != 8) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent ping frame " "with invalid length: %uz", ctx->rest); return NGX_ERROR; } if (ctx->flags & NGX_HTTP_V2_ACK_FLAG) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent ping frame with ack flag"); return NGX_ERROR; } if (ctx->free == NULL && ctx->pings++ > 1000) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent too many ping frames"); return NGX_ERROR; } } for (p = b->pos; p < last; p++) { ch = *p; #if 0 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc ping byte: %02Xd s:%d", ch, state); #endif if (state < sw_data_8) { ctx->ping_data[state] = ch; state++; } else { ctx->ping_data[7] = ch; state = sw_start; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc ping"); } } ctx->rest -= p - b->pos; ctx->frame_state = state; b->pos = p; if (ctx->rest > 0) { return NGX_AGAIN; } ctx->state = ngx_http_grpc_st_start; return ngx_http_grpc_send_ping_ack(r, ctx); } static ngx_int_t ngx_http_grpc_send_settings_ack(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx) { ngx_chain_t *cl, **ll; ngx_http_grpc_frame_t *f; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc send settings ack"); for (cl = ctx->out, ll = &ctx->out; cl; cl = cl->next) { ll = &cl->next; } cl = ngx_http_grpc_get_buf(r, ctx); if (cl == NULL) { return NGX_ERROR; } f = (ngx_http_grpc_frame_t *) cl->buf->last; cl->buf->last += sizeof(ngx_http_grpc_frame_t); f->length_0 = 0; f->length_1 = 0; f->length_2 = 0; f->type = NGX_HTTP_V2_SETTINGS_FRAME; f->flags = NGX_HTTP_V2_ACK_FLAG; f->stream_id_0 = 0; f->stream_id_1 = 0; f->stream_id_2 = 0; f->stream_id_3 = 0; *ll = cl; return NGX_OK; } static ngx_int_t ngx_http_grpc_send_ping_ack(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx) { ngx_chain_t *cl, **ll; ngx_http_grpc_frame_t *f; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc send ping ack"); for (cl = ctx->out, ll = &ctx->out; cl; cl = cl->next) { ll = &cl->next; } cl = ngx_http_grpc_get_buf(r, ctx); if (cl == NULL) { return NGX_ERROR; } f = (ngx_http_grpc_frame_t *) cl->buf->last; cl->buf->last += sizeof(ngx_http_grpc_frame_t); f->length_0 = 0; f->length_1 = 0; f->length_2 = 8; f->type = NGX_HTTP_V2_PING_FRAME; f->flags = NGX_HTTP_V2_ACK_FLAG; f->stream_id_0 = 0; f->stream_id_1 = 0; f->stream_id_2 = 0; f->stream_id_3 = 0; cl->buf->last = ngx_copy(cl->buf->last, ctx->ping_data, 8); *ll = cl; return NGX_OK; } static ngx_int_t ngx_http_grpc_send_window_update(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx) { size_t n; ngx_chain_t *cl, **ll; ngx_http_grpc_frame_t *f; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc send window update: %uz %uz", ctx->connection->recv_window, ctx->recv_window); for (cl = ctx->out, ll = &ctx->out; cl; cl = cl->next) { ll = &cl->next; } cl = ngx_http_grpc_get_buf(r, ctx); if (cl == NULL) { return NGX_ERROR; } f = (ngx_http_grpc_frame_t *) cl->buf->last; cl->buf->last += sizeof(ngx_http_grpc_frame_t); f->length_0 = 0; f->length_1 = 0; f->length_2 = 4; f->type = NGX_HTTP_V2_WINDOW_UPDATE_FRAME; f->flags = 0; f->stream_id_0 = 0; f->stream_id_1 = 0; f->stream_id_2 = 0; f->stream_id_3 = 0; n = NGX_HTTP_V2_MAX_WINDOW - ctx->connection->recv_window; ctx->connection->recv_window = NGX_HTTP_V2_MAX_WINDOW; *cl->buf->last++ = (u_char) ((n >> 24) & 0xff); *cl->buf->last++ = (u_char) ((n >> 16) & 0xff); *cl->buf->last++ = (u_char) ((n >> 8) & 0xff); *cl->buf->last++ = (u_char) (n & 0xff); f = (ngx_http_grpc_frame_t *) cl->buf->last; cl->buf->last += sizeof(ngx_http_grpc_frame_t); f->length_0 = 0; f->length_1 = 0; f->length_2 = 4; f->type = NGX_HTTP_V2_WINDOW_UPDATE_FRAME; f->flags = 0; f->stream_id_0 = (u_char) ((ctx->id >> 24) & 0xff); f->stream_id_1 = (u_char) ((ctx->id >> 16) & 0xff); f->stream_id_2 = (u_char) ((ctx->id >> 8) & 0xff); f->stream_id_3 = (u_char) (ctx->id & 0xff); n = NGX_HTTP_V2_MAX_WINDOW - ctx->recv_window; ctx->recv_window = NGX_HTTP_V2_MAX_WINDOW; *cl->buf->last++ = (u_char) ((n >> 24) & 0xff); *cl->buf->last++ = (u_char) ((n >> 16) & 0xff); *cl->buf->last++ = (u_char) ((n >> 8) & 0xff); *cl->buf->last++ = (u_char) (n & 0xff); *ll = cl; return NGX_OK; } static ngx_chain_t * ngx_http_grpc_get_buf(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx) { u_char *start; ngx_buf_t *b; ngx_chain_t *cl; cl = ngx_chain_get_free_buf(r->pool, &ctx->free); if (cl == NULL) { return NULL; } b = cl->buf; start = b->start; if (start == NULL) { /* * each buffer is large enough to hold two window update * frames in a row */ start = ngx_palloc(r->pool, 2 * sizeof(ngx_http_grpc_frame_t) + 8); if (start == NULL) { return NULL; } } ngx_memzero(b, sizeof(ngx_buf_t)); b->start = start; b->pos = start; b->last = start; b->end = start + 2 * sizeof(ngx_http_grpc_frame_t) + 8; b->tag = (ngx_buf_tag_t) &ngx_http_grpc_body_output_filter; b->temporary = 1; b->flush = 1; return cl; } static ngx_http_grpc_ctx_t * ngx_http_grpc_get_ctx(ngx_http_request_t *r) { ngx_http_grpc_ctx_t *ctx; ngx_http_upstream_t *u; ctx = ngx_http_get_module_ctx(r, ngx_http_grpc_module); if (ctx->connection == NULL) { u = r->upstream; if (ngx_http_grpc_get_connection_data(r, ctx, &u->peer) != NGX_OK) { return NULL; } } return ctx; } static ngx_int_t ngx_http_grpc_get_connection_data(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ngx_peer_connection_t *pc) { ngx_connection_t *c; ngx_pool_cleanup_t *cln; c = pc->connection; if (pc->cached) { /* * for cached connections, connection data can be found * in the cleanup handler */ for (cln = c->pool->cleanup; cln; cln = cln->next) { if (cln->handler == ngx_http_grpc_cleanup) { ctx->connection = cln->data; break; } } if (ctx->connection == NULL) { ngx_log_error(NGX_LOG_ERR, c->log, 0, "no connection data found for " "keepalive http2 connection"); return NGX_ERROR; } ctx->send_window = ctx->connection->init_window; ctx->recv_window = NGX_HTTP_V2_MAX_WINDOW; ctx->connection->last_stream_id += 2; ctx->id = ctx->connection->last_stream_id; return NGX_OK; } cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_http_grpc_conn_t)); if (cln == NULL) { return NGX_ERROR; } cln->handler = ngx_http_grpc_cleanup; ctx->connection = cln->data; ctx->connection->init_window = NGX_HTTP_V2_DEFAULT_WINDOW; ctx->connection->send_window = NGX_HTTP_V2_DEFAULT_WINDOW; ctx->connection->recv_window = NGX_HTTP_V2_MAX_WINDOW; ctx->send_window = NGX_HTTP_V2_DEFAULT_WINDOW; ctx->recv_window = NGX_HTTP_V2_MAX_WINDOW; ctx->id = 1; ctx->connection->last_stream_id = 1; return NGX_OK; } static void ngx_http_grpc_cleanup(void *data) { #if 0 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "grpc cleanup"); #endif return; } static void ngx_http_grpc_abort_request(ngx_http_request_t *r) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "abort grpc request"); return; } static void ngx_http_grpc_finalize_request(ngx_http_request_t *r, ngx_int_t rc) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "finalize grpc request"); return; } static ngx_int_t ngx_http_grpc_internal_trailers_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_table_elt_t *te; te = r->headers_in.te; if (te == NULL) { v->not_found = 1; return NGX_OK; } if (ngx_strlcasestrn(te->value.data, te->value.data + te->value.len, (u_char *) "trailers", 8 - 1) == NULL) { v->not_found = 1; return NGX_OK; } v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = (u_char *) "trailers"; v->len = sizeof("trailers") - 1; return NGX_OK; } static ngx_int_t ngx_http_grpc_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var, *v; for (v = ngx_http_grpc_vars; v->name.len; v++) { var = ngx_http_add_variable(cf, &v->name, v->flags); if (var == NULL) { return NGX_ERROR; } var->get_handler = v->get_handler; var->data = v->data; } return NGX_OK; } static void * ngx_http_grpc_create_loc_conf(ngx_conf_t *cf) { ngx_http_grpc_loc_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_grpc_loc_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->upstream.ignore_headers = 0; * conf->upstream.next_upstream = 0; * conf->upstream.hide_headers_hash = { NULL, 0 }; * * conf->headers.lengths = NULL; * conf->headers.values = NULL; * conf->headers.hash = { NULL, 0 }; * conf->host = { 0, NULL }; * conf->host_set = 0; * conf->ssl = 0; * conf->ssl_protocols = 0; * conf->ssl_ciphers = { 0, NULL }; * conf->ssl_trusted_certificate = { 0, NULL }; * conf->ssl_crl = { 0, NULL }; */ conf->upstream.local = NGX_CONF_UNSET_PTR; conf->upstream.socket_keepalive = NGX_CONF_UNSET; conf->upstream.next_upstream_tries = NGX_CONF_UNSET_UINT; conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.next_upstream_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE; conf->upstream.hide_headers = NGX_CONF_UNSET_PTR; conf->upstream.pass_headers = NGX_CONF_UNSET_PTR; conf->upstream.intercept_errors = NGX_CONF_UNSET; #if (NGX_HTTP_SSL) conf->upstream.ssl_session_reuse = NGX_CONF_UNSET; conf->upstream.ssl_name = NGX_CONF_UNSET_PTR; conf->upstream.ssl_server_name = NGX_CONF_UNSET; conf->upstream.ssl_verify = NGX_CONF_UNSET; conf->ssl_verify_depth = NGX_CONF_UNSET_UINT; conf->upstream.ssl_certificate = NGX_CONF_UNSET_PTR; conf->upstream.ssl_certificate_key = NGX_CONF_UNSET_PTR; conf->upstream.ssl_passwords = NGX_CONF_UNSET_PTR; conf->ssl_conf_commands = NGX_CONF_UNSET_PTR; #endif /* the hardcoded values */ conf->upstream.cyclic_temp_file = 0; conf->upstream.buffering = 0; conf->upstream.ignore_client_abort = 0; conf->upstream.send_lowat = 0; conf->upstream.bufs.num = 0; conf->upstream.busy_buffers_size = 0; conf->upstream.max_temp_file_size = 0; conf->upstream.temp_file_write_size = 0; conf->upstream.pass_request_headers = 1; conf->upstream.pass_request_body = 1; conf->upstream.force_ranges = 0; conf->upstream.pass_trailers = 1; conf->upstream.preserve_output = 1; conf->headers_source = NGX_CONF_UNSET_PTR; ngx_str_set(&conf->upstream.module, "grpc"); return conf; } static char * ngx_http_grpc_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_grpc_loc_conf_t *prev = parent; ngx_http_grpc_loc_conf_t *conf = child; ngx_int_t rc; ngx_hash_init_t hash; ngx_http_core_loc_conf_t *clcf; ngx_conf_merge_ptr_value(conf->upstream.local, prev->upstream.local, NULL); ngx_conf_merge_value(conf->upstream.socket_keepalive, prev->upstream.socket_keepalive, 0); ngx_conf_merge_uint_value(conf->upstream.next_upstream_tries, prev->upstream.next_upstream_tries, 0); ngx_conf_merge_msec_value(conf->upstream.connect_timeout, prev->upstream.connect_timeout, 60000); ngx_conf_merge_msec_value(conf->upstream.send_timeout, prev->upstream.send_timeout, 60000); ngx_conf_merge_msec_value(conf->upstream.read_timeout, prev->upstream.read_timeout, 60000); ngx_conf_merge_msec_value(conf->upstream.next_upstream_timeout, prev->upstream.next_upstream_timeout, 0); ngx_conf_merge_size_value(conf->upstream.buffer_size, prev->upstream.buffer_size, (size_t) ngx_pagesize); ngx_conf_merge_bitmask_value(conf->upstream.ignore_headers, prev->upstream.ignore_headers, NGX_CONF_BITMASK_SET); ngx_conf_merge_bitmask_value(conf->upstream.next_upstream, prev->upstream.next_upstream, (NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_ERROR |NGX_HTTP_UPSTREAM_FT_TIMEOUT)); if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) { conf->upstream.next_upstream = NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_OFF; } ngx_conf_merge_value(conf->upstream.intercept_errors, prev->upstream.intercept_errors, 0); #if (NGX_HTTP_SSL) if (ngx_http_grpc_merge_ssl(cf, conf, prev) != NGX_OK) { return NGX_CONF_ERROR; } 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_SSL_TLSv1_3)); ngx_conf_merge_str_value(conf->ssl_ciphers, prev->ssl_ciphers, "DEFAULT"); ngx_conf_merge_ptr_value(conf->upstream.ssl_name, prev->upstream.ssl_name, NULL); ngx_conf_merge_value(conf->upstream.ssl_server_name, prev->upstream.ssl_server_name, 0); ngx_conf_merge_value(conf->upstream.ssl_verify, 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_ptr_value(conf->upstream.ssl_certificate, prev->upstream.ssl_certificate, NULL); ngx_conf_merge_ptr_value(conf->upstream.ssl_certificate_key, prev->upstream.ssl_certificate_key, NULL); ngx_conf_merge_ptr_value(conf->upstream.ssl_passwords, prev->upstream.ssl_passwords, NULL); ngx_conf_merge_ptr_value(conf->ssl_conf_commands, prev->ssl_conf_commands, NULL); if (conf->ssl && ngx_http_grpc_set_ssl(cf, conf) != NGX_OK) { return NGX_CONF_ERROR; } #endif hash.max_size = 512; hash.bucket_size = ngx_align(64, ngx_cacheline_size); hash.name = "grpc_headers_hash"; if (ngx_http_upstream_hide_headers_hash(cf, &conf->upstream, &prev->upstream, ngx_http_grpc_hide_headers, &hash) != NGX_OK) { return NGX_CONF_ERROR; } clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); if (clcf->noname && conf->upstream.upstream == NULL && conf->grpc_lengths == NULL) { conf->upstream.upstream = prev->upstream.upstream; conf->host = prev->host; conf->grpc_lengths = prev->grpc_lengths; conf->grpc_values = prev->grpc_values; #if (NGX_HTTP_SSL) conf->ssl = prev->ssl; #endif } if (clcf->lmt_excpt && clcf->handler == NULL && (conf->upstream.upstream || conf->grpc_lengths)) { clcf->handler = ngx_http_grpc_handler; } ngx_conf_merge_ptr_value(conf->headers_source, prev->headers_source, NULL); if (conf->headers_source == prev->headers_source) { conf->headers = prev->headers; conf->host_set = prev->host_set; } rc = ngx_http_grpc_init_headers(cf, conf, &conf->headers, ngx_http_grpc_headers); if (rc != NGX_OK) { return NGX_CONF_ERROR; } /* * special handling to preserve conf->headers in the "http" section * to inherit it to all servers */ if (prev->headers.hash.buckets == NULL && conf->headers_source == prev->headers_source) { prev->headers = conf->headers; prev->host_set = conf->host_set; } return NGX_CONF_OK; } static ngx_int_t ngx_http_grpc_init_headers(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *conf, ngx_http_grpc_headers_t *headers, ngx_keyval_t *default_headers) { u_char *p; size_t size; uintptr_t *code; ngx_uint_t i; ngx_array_t headers_names, headers_merged; ngx_keyval_t *src, *s, *h; ngx_hash_key_t *hk; ngx_hash_init_t hash; ngx_http_script_compile_t sc; ngx_http_script_copy_code_t *copy; if (headers->hash.buckets) { return NGX_OK; } if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t)) != NGX_OK) { return NGX_ERROR; } if (ngx_array_init(&headers_merged, cf->temp_pool, 4, sizeof(ngx_keyval_t)) != NGX_OK) { return NGX_ERROR; } headers->lengths = ngx_array_create(cf->pool, 64, 1); if (headers->lengths == NULL) { return NGX_ERROR; } headers->values = ngx_array_create(cf->pool, 512, 1); if (headers->values == NULL) { return NGX_ERROR; } if (conf->headers_source) { src = conf->headers_source->elts; for (i = 0; i < conf->headers_source->nelts; i++) { if (src[i].key.len == 4 && ngx_strncasecmp(src[i].key.data, (u_char *) "Host", 4) == 0) { conf->host_set = 1; } s = ngx_array_push(&headers_merged); if (s == NULL) { return NGX_ERROR; } *s = src[i]; } } h = default_headers; while (h->key.len) { src = headers_merged.elts; for (i = 0; i < headers_merged.nelts; i++) { if (ngx_strcasecmp(h->key.data, src[i].key.data) == 0) { goto next; } } s = ngx_array_push(&headers_merged); if (s == NULL) { return NGX_ERROR; } *s = *h; next: h++; } src = headers_merged.elts; for (i = 0; i < headers_merged.nelts; i++) { hk = ngx_array_push(&headers_names); if (hk == NULL) { return NGX_ERROR; } hk->key = src[i].key; hk->key_hash = ngx_hash_key_lc(src[i].key.data, src[i].key.len); hk->value = (void *) 1; if (src[i].value.len == 0) { continue; } copy = ngx_array_push_n(headers->lengths, sizeof(ngx_http_script_copy_code_t)); if (copy == NULL) { return NGX_ERROR; } copy->code = (ngx_http_script_code_pt) (void *) ngx_http_script_copy_len_code; copy->len = src[i].key.len; size = (sizeof(ngx_http_script_copy_code_t) + src[i].key.len + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1); copy = ngx_array_push_n(headers->values, size); if (copy == NULL) { return NGX_ERROR; } copy->code = ngx_http_script_copy_code; copy->len = src[i].key.len; p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t); ngx_memcpy(p, src[i].key.data, src[i].key.len); ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); sc.cf = cf; sc.source = &src[i].value; sc.flushes = &headers->flushes; sc.lengths = &headers->lengths; sc.values = &headers->values; if (ngx_http_script_compile(&sc) != NGX_OK) { return NGX_ERROR; } code = ngx_array_push_n(headers->lengths, sizeof(uintptr_t)); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) NULL; code = ngx_array_push_n(headers->values, sizeof(uintptr_t)); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) NULL; } code = ngx_array_push_n(headers->lengths, sizeof(uintptr_t)); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) NULL; hash.hash = &headers->hash; hash.key = ngx_hash_key_lc; hash.max_size = 512; hash.bucket_size = 64; hash.name = "grpc_headers_hash"; hash.pool = cf->pool; hash.temp_pool = NULL; return ngx_hash_init(&hash, headers_names.elts, headers_names.nelts); } static char * ngx_http_grpc_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_grpc_loc_conf_t *glcf = conf; size_t add; ngx_str_t *value, *url; ngx_url_t u; ngx_uint_t n; ngx_http_core_loc_conf_t *clcf; ngx_http_script_compile_t sc; if (glcf->upstream.upstream || glcf->grpc_lengths) { return "is duplicate"; } clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); clcf->handler = ngx_http_grpc_handler; if (clcf->name.len && 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 = &glcf->grpc_lengths; sc.values = &glcf->grpc_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) glcf->ssl = 1; #endif return NGX_CONF_OK; } if (ngx_strncasecmp(url->data, (u_char *) "grpc://", 7) == 0) { add = 7; } else if (ngx_strncasecmp(url->data, (u_char *) "grpcs://", 8) == 0) { #if (NGX_HTTP_SSL) glcf->ssl = 1; add = 8; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "grpcs protocol requires SSL support"); return NGX_CONF_ERROR; #endif } else { add = 0; } ngx_memzero(&u, sizeof(ngx_url_t)); u.url.len = url->len - add; u.url.data = url->data + add; u.no_resolve = 1; glcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0); if (glcf->upstream.upstream == NULL) { return NGX_CONF_ERROR; } if (u.family != AF_UNIX) { if (u.no_port) { glcf->host = u.host; } else { glcf->host.len = u.host.len + 1 + u.port_text.len; glcf->host.data = u.host.data; } } else { ngx_str_set(&glcf->host, "localhost"); } return NGX_CONF_OK; } #if (NGX_HTTP_SSL) static char * ngx_http_grpc_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_grpc_loc_conf_t *glcf = conf; ngx_str_t *value; if (glcf->upstream.ssl_passwords != NGX_CONF_UNSET_PTR) { return "is duplicate"; } value = cf->args->elts; glcf->upstream.ssl_passwords = ngx_ssl_read_password_file(cf, &value[1]); if (glcf->upstream.ssl_passwords == NULL) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } static char * ngx_http_grpc_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data) { #ifndef SSL_CONF_FLAG_FILE return "is not supported on this platform"; #else return NGX_CONF_OK; #endif } static ngx_int_t ngx_http_grpc_merge_ssl(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *conf, ngx_http_grpc_loc_conf_t *prev) { ngx_uint_t preserve; if (conf->ssl_protocols == 0 && conf->ssl_ciphers.data == NULL && conf->upstream.ssl_certificate == NGX_CONF_UNSET_PTR && conf->upstream.ssl_certificate_key == NGX_CONF_UNSET_PTR && conf->upstream.ssl_passwords == NGX_CONF_UNSET_PTR && conf->upstream.ssl_verify == NGX_CONF_UNSET && conf->ssl_verify_depth == NGX_CONF_UNSET_UINT && conf->ssl_trusted_certificate.data == NULL && conf->ssl_crl.data == NULL && conf->upstream.ssl_session_reuse == NGX_CONF_UNSET && conf->ssl_conf_commands == NGX_CONF_UNSET_PTR) { if (prev->upstream.ssl) { conf->upstream.ssl = prev->upstream.ssl; return NGX_OK; } preserve = 1; } else { preserve = 0; } conf->upstream.ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t)); if (conf->upstream.ssl == NULL) { return NGX_ERROR; } conf->upstream.ssl->log = cf->log; /* * special handling to preserve conf->upstream.ssl * in the "http" section to inherit it to all servers */ if (preserve) { prev->upstream.ssl = conf->upstream.ssl; } return NGX_OK; } static ngx_int_t ngx_http_grpc_set_ssl(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *glcf) { ngx_pool_cleanup_t *cln; if (glcf->upstream.ssl->ctx) { return NGX_OK; } if (ngx_ssl_create(glcf->upstream.ssl, glcf->ssl_protocols, NULL) != NGX_OK) { return NGX_ERROR; } cln = ngx_pool_cleanup_add(cf->pool, 0); if (cln == NULL) { ngx_ssl_cleanup_ctx(glcf->upstream.ssl); return NGX_ERROR; } cln->handler = ngx_ssl_cleanup_ctx; cln->data = glcf->upstream.ssl; if (ngx_ssl_ciphers(cf, glcf->upstream.ssl, &glcf->ssl_ciphers, 0) != NGX_OK) { return NGX_ERROR; } if (glcf->upstream.ssl_certificate && glcf->upstream.ssl_certificate->value.len) { if (glcf->upstream.ssl_certificate_key == NULL) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no \"grpc_ssl_certificate_key\" is defined " "for certificate \"%V\"", &glcf->upstream.ssl_certificate->value); return NGX_ERROR; } if (glcf->upstream.ssl_certificate->lengths || glcf->upstream.ssl_certificate_key->lengths) { glcf->upstream.ssl_passwords = ngx_ssl_preserve_passwords(cf, glcf->upstream.ssl_passwords); if (glcf->upstream.ssl_passwords == NULL) { return NGX_ERROR; } } else { if (ngx_ssl_certificate(cf, glcf->upstream.ssl, &glcf->upstream.ssl_certificate->value, &glcf->upstream.ssl_certificate_key->value, glcf->upstream.ssl_passwords) != NGX_OK) { return NGX_ERROR; } } } if (glcf->upstream.ssl_verify) { if (glcf->ssl_trusted_certificate.len == 0) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no grpc_ssl_trusted_certificate for grpc_ssl_verify"); return NGX_ERROR; } if (ngx_ssl_trusted_certificate(cf, glcf->upstream.ssl, &glcf->ssl_trusted_certificate, glcf->ssl_verify_depth) != NGX_OK) { return NGX_ERROR; } if (ngx_ssl_crl(cf, glcf->upstream.ssl, &glcf->ssl_crl) != NGX_OK) { return NGX_ERROR; } } if (ngx_ssl_client_session_cache(cf, glcf->upstream.ssl, glcf->upstream.ssl_session_reuse) != NGX_OK) { return NGX_ERROR; } #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation if (SSL_CTX_set_alpn_protos(glcf->upstream.ssl->ctx, (u_char *) "\x02h2", 3) != 0) { ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, "SSL_CTX_set_alpn_protos() failed"); return NGX_ERROR; } #endif if (ngx_ssl_conf_commands(cf, glcf->upstream.ssl, glcf->ssl_conf_commands) != NGX_OK) { return NGX_ERROR; } return NGX_OK; } #endif nginx-1.24.0/src/http/modules/ngx_http_gunzip_filter_module.c000644 001751 001751 00000041216 14415135676 025652 0ustar00mdouninmdounin000000 000000 /* * 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; } nginx-1.24.0/src/http/modules/ngx_http_gzip_filter_module.c000644 001751 001751 00000072761 14415135676 025320 0ustar00mdouninmdounin000000 000000 /* * 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 buffering:1; unsigned zlib_ng:1; unsigned state_allocated:1; size_t zin; size_t zout; z_stream zstream; ngx_http_request_t *request; } ngx_http_gzip_ctx_t; 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_add_data(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx); static ngx_int_t ngx_http_gzip_filter_get_buf(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx); static ngx_int_t ngx_http_gzip_filter_deflate(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx); static ngx_int_t ngx_http_gzip_filter_deflate_end(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx); static void *ngx_http_gzip_filter_alloc(void *opaque, u_int items, u_int size); static void ngx_http_gzip_filter_free(void *opaque, void *address); static void ngx_http_gzip_filter_free_copy_buf(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx); static ngx_int_t ngx_http_gzip_add_variables(ngx_conf_t *cf); static ngx_int_t ngx_http_gzip_ratio_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_gzip_filter_init(ngx_conf_t *cf); static void *ngx_http_gzip_create_conf(ngx_conf_t *cf); static char *ngx_http_gzip_merge_conf(ngx_conf_t *cf, void *parent, void *child); static char *ngx_http_gzip_window(ngx_conf_t *cf, void *post, void *data); static char *ngx_http_gzip_hash(ngx_conf_t *cf, void *post, void *data); static ngx_conf_num_bounds_t ngx_http_gzip_comp_level_bounds = { ngx_conf_check_num_bounds, 1, 9 }; static ngx_conf_post_handler_pt ngx_http_gzip_window_p = ngx_http_gzip_window; static ngx_conf_post_handler_pt ngx_http_gzip_hash_p = ngx_http_gzip_hash; static ngx_command_t ngx_http_gzip_filter_commands[] = { { ngx_string("gzip"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF |NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_gzip_conf_t, enable), NULL }, { ngx_string("gzip_buffers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, ngx_conf_set_bufs_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_gzip_conf_t, bufs), NULL }, { ngx_string("gzip_types"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_types_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_gzip_conf_t, types_keys), &ngx_http_html_default_types[0] }, { ngx_string("gzip_comp_level"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_gzip_conf_t, level), &ngx_http_gzip_comp_level_bounds }, { ngx_string("gzip_window"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_gzip_conf_t, wbits), &ngx_http_gzip_window_p }, { ngx_string("gzip_hash"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_gzip_conf_t, memlevel), &ngx_http_gzip_hash_p }, { ngx_string("postpone_gzipping"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_gzip_conf_t, postpone_gzipping), NULL }, { ngx_string("gzip_no_buffer"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_gzip_conf_t, no_buffer), NULL }, { ngx_string("gzip_min_length"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_gzip_conf_t, min_length), NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_gzip_filter_module_ctx = { ngx_http_gzip_add_variables, /* preconfiguration */ ngx_http_gzip_filter_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_gzip_create_conf, /* create location configuration */ ngx_http_gzip_merge_conf /* merge location configuration */ }; ngx_module_t ngx_http_gzip_filter_module = { NGX_MODULE_V1, &ngx_http_gzip_filter_module_ctx, /* module context */ ngx_http_gzip_filter_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_str_t ngx_http_gzip_ratio = ngx_string("gzip_ratio"); static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter; static ngx_uint_t ngx_http_gzip_assume_zlib_ng; 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; h->next = NULL; 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; } 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 * * A zlib variant from Intel (https://github.com/jtkukunas/zlib) * uses additional 16-byte padding in one of window-sized buffers. */ if (!ngx_http_gzip_assume_zlib_ng) { ctx->allocated = 8192 + 16 + (1 << (wbits + 2)) + (1 << (memlevel + 9)); } else { /* * Another zlib variant, https://github.com/zlib-ng/zlib-ng. * It used to force window bits to 13 for fast compression level, * uses (64 + sizeof(void*)) additional space on all allocations * for alignment, 16-byte padding in one of window-sized buffers, * and 128K hash. */ if (conf->level == 1) { wbits = ngx_max(wbits, 13); } ctx->allocated = 8192 + 16 + (1 << (wbits + 2)) + 131072 + (1 << (memlevel + 8)) + 4 * (64 + sizeof(void*)); ctx->zlib_ng = 1; } } static ngx_int_t ngx_http_gzip_filter_buffer(ngx_http_gzip_ctx_t *ctx, ngx_chain_t *in) { size_t size, buffered; ngx_buf_t *b, *buf; ngx_chain_t *cl, **ll; ngx_http_request_t *r; ngx_http_gzip_conf_t *conf; r = ctx->request; r->connection->buffered |= NGX_HTTP_GZIP_BUFFERED; buffered = 0; ll = &ctx->in; for (cl = ctx->in; cl; cl = cl->next) { buffered += cl->buf->last - cl->buf->pos; ll = &cl->next; } conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module); while (in) { cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_ERROR; } b = in->buf; size = b->last - b->pos; buffered += size; if (b->flush || b->last_buf || buffered > conf->postpone_gzipping) { ctx->buffering = 0; } if (ctx->buffering && size) { buf = ngx_create_temp_buf(r->pool, size); if (buf == NULL) { return NGX_ERROR; } buf->last = ngx_cpymem(buf->pos, b->pos, size); b->pos = b->last; buf->last_buf = b->last_buf; buf->tag = (ngx_buf_tag_t) &ngx_http_gzip_filter_module; cl->buf = buf; } else { cl->buf = b; } *ll = cl; ll = &cl->next; in = in->next; } *ll = NULL; return ctx->buffering ? NGX_OK : NGX_DONE; } static ngx_int_t ngx_http_gzip_filter_deflate_start(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) { int rc; ngx_http_gzip_conf_t *conf; conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module); ctx->preallocated = ngx_palloc(r->pool, ctx->allocated); if (ctx->preallocated == NULL) { return NGX_ERROR; } ctx->free_mem = ctx->preallocated; ctx->zstream.zalloc = ngx_http_gzip_filter_alloc; ctx->zstream.zfree = ngx_http_gzip_filter_free; ctx->zstream.opaque = ctx; rc = deflateInit2(&ctx->zstream, (int) conf->level, Z_DEFLATED, ctx->wbits + 16, 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->flush = Z_NO_FLUSH; return NGX_OK; } static ngx_int_t ngx_http_gzip_filter_add_data(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) { ngx_chain_t *cl; if (ctx->zstream.avail_in || ctx->flush != Z_NO_FLUSH || ctx->redo) { return NGX_OK; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "gzip in: %p", ctx->in); if (ctx->in == NULL) { return NGX_DECLINED; } if (ctx->copy_buf) { /* * to avoid CPU cache trashing we do not free() just quit buf, * but postpone free()ing after zlib compressing and data output */ ctx->copy_buf->next = ctx->copied; ctx->copied = ctx->copy_buf; ctx->copy_buf = NULL; } cl = ctx->in; ctx->in_buf = cl->buf; ctx->in = cl->next; if (ctx->in_buf->tag == (ngx_buf_tag_t) &ngx_http_gzip_filter_module) { ctx->copy_buf = cl; } else { ngx_free_chain(r->pool, cl); } ctx->zstream.next_in = ctx->in_buf->pos; ctx->zstream.avail_in = ctx->in_buf->last - ctx->in_buf->pos; ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "gzip in_buf:%p ni:%p ai:%ud", ctx->in_buf, ctx->zstream.next_in, ctx->zstream.avail_in); if (ctx->in_buf->last_buf) { ctx->flush = Z_FINISH; } else if (ctx->in_buf->flush) { ctx->flush = Z_SYNC_FLUSH; } else if (ctx->zstream.avail_in == 0) { /* ctx->flush == Z_NO_FLUSH */ return NGX_AGAIN; } return NGX_OK; } static ngx_int_t ngx_http_gzip_filter_get_buf(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) { ngx_chain_t *cl; ngx_http_gzip_conf_t *conf; if (ctx->zstream.avail_out) { return NGX_OK; } conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module); if (ctx->free) { cl = ctx->free; ctx->out_buf = cl->buf; ctx->free = cl->next; ngx_free_chain(r->pool, cl); } else if (ctx->bufs < conf->bufs.num) { ctx->out_buf = ngx_create_temp_buf(r->pool, conf->bufs.size); if (ctx->out_buf == NULL) { return NGX_ERROR; } ctx->out_buf->tag = (ngx_buf_tag_t) &ngx_http_gzip_filter_module; ctx->out_buf->recycled = 1; ctx->bufs++; } else { ctx->nomem = 1; return NGX_DECLINED; } ctx->zstream.next_out = ctx->out_buf->pos; ctx->zstream.avail_out = conf->bufs.size; return NGX_OK; } static ngx_int_t ngx_http_gzip_filter_deflate(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) { int rc; ngx_buf_t *b; ngx_chain_t *cl; ngx_http_gzip_conf_t *conf; ngx_log_debug6(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "deflate in: ni:%p no:%p ai:%ud ao:%ud fl:%d redo:%d", ctx->zstream.next_in, ctx->zstream.next_out, ctx->zstream.avail_in, ctx->zstream.avail_out, ctx->flush, ctx->redo); rc = deflate(&ctx->zstream, ctx->flush); if (rc != Z_OK && rc != Z_STREAM_END && rc != Z_BUF_ERROR) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "deflate() failed: %d, %d", ctx->flush, rc); return NGX_ERROR; } ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "deflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d", ctx->zstream.next_in, ctx->zstream.next_out, ctx->zstream.avail_in, ctx->zstream.avail_out, rc); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "gzip in_buf:%p pos:%p", ctx->in_buf, ctx->in_buf->pos); if (ctx->zstream.next_in) { ctx->in_buf->pos = ctx->zstream.next_in; if (ctx->zstream.avail_in == 0) { ctx->zstream.next_in = NULL; } } ctx->out_buf->last = ctx->zstream.next_out; if (ctx->zstream.avail_out == 0 && rc != Z_STREAM_END) { /* 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; ctx->zin = ctx->zstream.total_in; ctx->zout = ctx->zstream.total_out; 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; } b = ctx->out_buf; if (ngx_buf_size(b) == 0) { b->temporary = 0; } b->last_buf = 1; cl->buf = b; cl->next = NULL; *ctx->last_out = cl; ctx->last_out = &cl->next; ctx->zstream.avail_in = 0; ctx->zstream.avail_out = 0; ctx->done = 1; r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED; return NGX_OK; } static void * ngx_http_gzip_filter_alloc(void *opaque, u_int items, u_int size) { ngx_http_gzip_ctx_t *ctx = opaque; void *p; ngx_uint_t alloc; alloc = items * size; if (items == 1 && alloc % 512 != 0 && alloc < 8192 && !ctx->state_allocated) { /* * The zlib deflate_state allocation, it takes about 6K, * we allocate 8K. Other allocations are divisible by 512. */ ctx->state_allocated = 1; alloc = 8192; } if (alloc <= ctx->allocated) { p = ctx->free_mem; ctx->free_mem += alloc; ctx->allocated -= alloc; ngx_log_debug4(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0, "gzip alloc: n:%ud s:%ud a:%ui p:%p", items, size, alloc, p); return p; } if (ctx->zlib_ng) { ngx_log_error(NGX_LOG_ALERT, ctx->request->connection->log, 0, "gzip filter failed to use preallocated memory: " "%ud of %ui", items * size, ctx->allocated); } else { ngx_http_gzip_assume_zlib_ng = 1; } p = ngx_palloc(ctx->request->pool, items * size); return p; } static void ngx_http_gzip_filter_free(void *opaque, void *address) { #if 0 ngx_http_gzip_ctx_t *ctx = opaque; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0, "gzip free: %p", address); #endif } static void ngx_http_gzip_filter_free_copy_buf(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) { ngx_chain_t *cl; for (cl = ctx->copied; cl; cl = cl->next) { ngx_pfree(r->pool, cl->buf->start); } ctx->copied = NULL; } static ngx_int_t ngx_http_gzip_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var; var = ngx_http_add_variable(cf, &ngx_http_gzip_ratio, NGX_HTTP_VAR_NOHASH); if (var == NULL) { return NGX_ERROR; } var->get_handler = ngx_http_gzip_ratio_variable; return NGX_OK; } static ngx_int_t ngx_http_gzip_ratio_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_uint_t zint, zfrac; ngx_http_gzip_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_gzip_filter_module); if (ctx == NULL || ctx->zout == 0) { v->not_found = 1; return NGX_OK; } v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = ngx_pnalloc(r->pool, NGX_INT32_LEN + 3); if (v->data == NULL) { return NGX_ERROR; } zint = (ngx_uint_t) (ctx->zin / ctx->zout); zfrac = (ngx_uint_t) ((ctx->zin * 100 / ctx->zout) % 100); if ((ctx->zin * 1000 / ctx->zout) % 10 > 4) { /* the rounding, e.g., 2.125 to 2.13 */ zfrac++; if (zfrac > 99) { zint++; zfrac = 0; } } v->len = ngx_sprintf(v->data, "%ui.%02ui", zint, zfrac) - v->data; return NGX_OK; } static void * ngx_http_gzip_create_conf(ngx_conf_t *cf) { ngx_http_gzip_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_gzip_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->bufs.num = 0; * conf->types = { NULL }; * conf->types_keys = NULL; */ conf->enable = NGX_CONF_UNSET; conf->no_buffer = NGX_CONF_UNSET; conf->postpone_gzipping = NGX_CONF_UNSET_SIZE; conf->level = NGX_CONF_UNSET; conf->wbits = NGX_CONF_UNSET_SIZE; conf->memlevel = NGX_CONF_UNSET_SIZE; conf->min_length = NGX_CONF_UNSET; return conf; } static char * ngx_http_gzip_merge_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_gzip_conf_t *prev = parent; ngx_http_gzip_conf_t *conf = child; ngx_conf_merge_value(conf->enable, prev->enable, 0); ngx_conf_merge_value(conf->no_buffer, prev->no_buffer, 0); ngx_conf_merge_bufs_value(conf->bufs, prev->bufs, (128 * 1024) / ngx_pagesize, ngx_pagesize); ngx_conf_merge_size_value(conf->postpone_gzipping, prev->postpone_gzipping, 0); ngx_conf_merge_value(conf->level, prev->level, 1); ngx_conf_merge_size_value(conf->wbits, prev->wbits, MAX_WBITS); ngx_conf_merge_size_value(conf->memlevel, prev->memlevel, MAX_MEM_LEVEL - 1); ngx_conf_merge_value(conf->min_length, prev->min_length, 20); if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types, &prev->types_keys, &prev->types, ngx_http_html_default_types) != NGX_OK) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } static ngx_int_t ngx_http_gzip_filter_init(ngx_conf_t *cf) { ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_gzip_header_filter; ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_gzip_body_filter; return NGX_OK; } static char * ngx_http_gzip_window(ngx_conf_t *cf, void *post, void *data) { size_t *np = data; size_t wbits, wsize; wbits = 15; for (wsize = 32 * 1024; wsize > 256; wsize >>= 1) { if (wsize == *np) { *np = wbits; return NGX_CONF_OK; } wbits--; } return "must be 512, 1k, 2k, 4k, 8k, 16k, or 32k"; } static char * ngx_http_gzip_hash(ngx_conf_t *cf, void *post, void *data) { size_t *np = data; size_t memlevel, hsize; memlevel = 9; for (hsize = 128 * 1024; hsize > 256; hsize >>= 1) { if (hsize == *np) { *np = memlevel; return NGX_CONF_OK; } memlevel--; } return "must be 512, 1k, 2k, 4k, 8k, 16k, 32k, 64k, or 128k"; } nginx-1.24.0/src/http/modules/ngx_http_gzip_static_module.c000644 001751 001751 00000020565 14415135676 025315 0ustar00mdouninmdounin000000 000000 /* * 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 }; static ngx_http_module_t ngx_http_gzip_static_module_ctx = { NULL, /* preconfiguration */ ngx_http_gzip_static_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_gzip_static_create_conf, /* create location configuration */ ngx_http_gzip_static_merge_conf /* merge location configuration */ }; ngx_module_t ngx_http_gzip_static_module = { NGX_MODULE_V1, &ngx_http_gzip_static_module_ctx, /* module context */ ngx_http_gzip_static_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_int_t ngx_http_gzip_static_handler(ngx_http_request_t *r) { u_char *p; size_t root; ngx_str_t path; ngx_int_t rc; ngx_uint_t level; ngx_log_t *log; ngx_buf_t *b; ngx_chain_t out; ngx_table_elt_t *h; ngx_open_file_info_t of; ngx_http_core_loc_conf_t *clcf; ngx_http_gzip_static_conf_t *gzcf; if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) { return NGX_DECLINED; } if (r->uri.data[r->uri.len - 1] == '/') { return NGX_DECLINED; } gzcf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_static_module); if (gzcf->enable == NGX_HTTP_GZIP_STATIC_OFF) { return NGX_DECLINED; } if (gzcf->enable == NGX_HTTP_GZIP_STATIC_ON) { rc = ngx_http_gzip_ok(r); } else { /* always */ rc = NGX_OK; } clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (!clcf->gzip_vary && rc != NGX_OK) { return NGX_DECLINED; } log = r->connection->log; p = ngx_http_map_uri_to_path(r, &path, &root, sizeof(".gz") - 1); if (p == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } *p++ = '.'; *p++ = 'g'; *p++ = 'z'; *p = '\0'; path.len = p - path.data; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http filename: \"%s\"", path.data); ngx_memzero(&of, sizeof(ngx_open_file_info_t)); of.read_ahead = clcf->read_ahead; of.directio = clcf->directio; of.valid = clcf->open_file_cache_valid; of.min_uses = clcf->open_file_cache_min_uses; of.errors = clcf->open_file_cache_errors; of.events = clcf->open_file_cache_events; if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool) != NGX_OK) { switch (of.err) { case 0: return NGX_HTTP_INTERNAL_SERVER_ERROR; case NGX_ENOENT: case NGX_ENOTDIR: case NGX_ENAMETOOLONG: return NGX_DECLINED; case NGX_EACCES: #if (NGX_HAVE_OPENAT) case NGX_EMLINK: case NGX_ELOOP: #endif level = NGX_LOG_ERR; break; default: level = NGX_LOG_CRIT; break; } ngx_log_error(level, log, of.err, "%s \"%s\" failed", of.failed, path.data); return NGX_DECLINED; } if (gzcf->enable == NGX_HTTP_GZIP_STATIC_ON) { r->gzip_vary = 1; if (rc != NGX_OK) { return NGX_DECLINED; } } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http static fd: %d", of.fd); if (of.is_dir) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http dir"); return NGX_DECLINED; } #if !(NGX_WIN32) /* the not regular files are probably Unix specific */ if (!of.is_file) { ngx_log_error(NGX_LOG_CRIT, log, 0, "\"%s\" is not a regular file", path.data); return NGX_HTTP_NOT_FOUND; } #endif r->root_tested = !r->error_page; rc = ngx_http_discard_request_body(r); if (rc != NGX_OK) { return rc; } log->action = "sending response to client"; r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = of.size; r->headers_out.last_modified_time = of.mtime; if (ngx_http_set_etag(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_http_set_content_type(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } h = ngx_list_push(&r->headers_out.headers); if (h == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } h->hash = 1; h->next = NULL; ngx_str_set(&h->key, "Content-Encoding"); ngx_str_set(&h->value, "gzip"); r->headers_out.content_encoding = h; r->allow_ranges = 1; /* we need to allocate all before the header would be sent */ b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t)); if (b->file == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { return rc; } b->file_pos = 0; b->file_last = of.size; b->in_file = b->file_last ? 1 : 0; b->last_buf = (r == r->main) ? 1 : 0; b->last_in_chain = 1; b->sync = (b->last_buf || b->in_file) ? 0 : 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; } nginx-1.24.0/src/http/modules/ngx_http_headers_filter_module.c000644 001751 001751 00000051524 14415135676 025754 0ustar00mdouninmdounin000000 000000 /* * 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_array_t *trailers; } ngx_http_headers_conf_t; static ngx_int_t ngx_http_set_expires(ngx_http_request_t *r, ngx_http_headers_conf_t *conf); static ngx_int_t ngx_http_parse_expires(ngx_str_t *value, ngx_http_expires_t *expires, time_t *expires_time, char **err); static ngx_int_t ngx_http_add_multi_header_lines(ngx_http_request_t *r, ngx_http_header_val_t *hv, ngx_str_t *value); static ngx_int_t ngx_http_add_header(ngx_http_request_t *r, ngx_http_header_val_t *hv, ngx_str_t *value); static ngx_int_t ngx_http_set_last_modified(ngx_http_request_t *r, ngx_http_header_val_t *hv, ngx_str_t *value); static ngx_int_t ngx_http_set_response_header(ngx_http_request_t *r, ngx_http_header_val_t *hv, ngx_str_t *value); static void *ngx_http_headers_create_conf(ngx_conf_t *cf); static char *ngx_http_headers_merge_conf(ngx_conf_t *cf, void *parent, void *child); static ngx_int_t ngx_http_headers_filter_init(ngx_conf_t *cf); static char *ngx_http_headers_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_headers_add(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_http_set_header_t ngx_http_set_headers[] = { { ngx_string("Cache-Control"), offsetof(ngx_http_headers_out_t, cache_control), ngx_http_add_multi_header_lines }, { ngx_string("Link"), offsetof(ngx_http_headers_out_t, link), ngx_http_add_multi_header_lines }, { ngx_string("Last-Modified"), offsetof(ngx_http_headers_out_t, last_modified), ngx_http_set_last_modified }, { ngx_string("ETag"), offsetof(ngx_http_headers_out_t, etag), ngx_http_set_response_header }, { ngx_null_string, 0, NULL } }; static ngx_command_t ngx_http_headers_filter_commands[] = { { ngx_string("expires"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF |NGX_CONF_TAKE12, ngx_http_headers_expires, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("add_header"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF |NGX_CONF_TAKE23, ngx_http_headers_add, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_headers_conf_t, headers), NULL }, { ngx_string("add_trailer"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF |NGX_CONF_TAKE23, ngx_http_headers_add, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_headers_conf_t, trailers), NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_headers_filter_module_ctx = { NULL, /* preconfiguration */ ngx_http_headers_filter_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_headers_create_conf, /* create location configuration */ ngx_http_headers_merge_conf /* merge location configuration */ }; ngx_module_t ngx_http_headers_filter_module = { NGX_MODULE_V1, &ngx_http_headers_filter_module_ctx, /* module context */ ngx_http_headers_filter_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter; static ngx_int_t ngx_http_headers_filter(ngx_http_request_t *r) { ngx_str_t value; ngx_uint_t i, safe_status; ngx_http_header_val_t *h; ngx_http_headers_conf_t *conf; if (r != r->main) { return ngx_http_next_header_filter(r); } conf = ngx_http_get_module_loc_conf(r, ngx_http_headers_filter_module); if (conf->expires == NGX_HTTP_EXPIRES_OFF && conf->headers == NULL && conf->trailers == NULL) { return ngx_http_next_header_filter(r); } switch (r->headers_out.status) { case NGX_HTTP_OK: case NGX_HTTP_CREATED: case NGX_HTTP_NO_CONTENT: case NGX_HTTP_PARTIAL_CONTENT: case NGX_HTTP_MOVED_PERMANENTLY: case NGX_HTTP_MOVED_TEMPORARILY: case NGX_HTTP_SEE_OTHER: case NGX_HTTP_NOT_MODIFIED: case NGX_HTTP_TEMPORARY_REDIRECT: case NGX_HTTP_PERMANENT_REDIRECT: safe_status = 1; break; default: safe_status = 0; break; } if (conf->expires != NGX_HTTP_EXPIRES_OFF && safe_status) { if (ngx_http_set_expires(r, conf) != NGX_OK) { return NGX_ERROR; } } if (conf->headers) { h = conf->headers->elts; for (i = 0; i < conf->headers->nelts; i++) { if (!safe_status && !h[i].always) { continue; } if (ngx_http_complex_value(r, &h[i].value, &value) != NGX_OK) { return NGX_ERROR; } if (h[i].handler(r, &h[i], &value) != NGX_OK) { return NGX_ERROR; } } } if (conf->trailers) { h = conf->trailers->elts; for (i = 0; i < conf->trailers->nelts; i++) { if (!safe_status && !h[i].always) { continue; } r->expect_trailers = 1; break; } } return ngx_http_next_header_filter(r); } static ngx_int_t ngx_http_trailers_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_str_t value; ngx_uint_t i, safe_status; ngx_chain_t *cl; ngx_table_elt_t *t; ngx_http_header_val_t *h; ngx_http_headers_conf_t *conf; conf = ngx_http_get_module_loc_conf(r, ngx_http_headers_filter_module); if (in == NULL || conf->trailers == NULL || !r->expect_trailers || r->header_only) { return ngx_http_next_body_filter(r, in); } for (cl = in; cl; cl = cl->next) { if (cl->buf->last_buf) { break; } } if (cl == NULL) { return ngx_http_next_body_filter(r, in); } switch (r->headers_out.status) { case NGX_HTTP_OK: case NGX_HTTP_CREATED: case NGX_HTTP_NO_CONTENT: case NGX_HTTP_PARTIAL_CONTENT: case NGX_HTTP_MOVED_PERMANENTLY: case NGX_HTTP_MOVED_TEMPORARILY: case NGX_HTTP_SEE_OTHER: case NGX_HTTP_NOT_MODIFIED: case NGX_HTTP_TEMPORARY_REDIRECT: case NGX_HTTP_PERMANENT_REDIRECT: safe_status = 1; break; default: safe_status = 0; break; } h = conf->trailers->elts; for (i = 0; i < conf->trailers->nelts; i++) { if (!safe_status && !h[i].always) { continue; } if (ngx_http_complex_value(r, &h[i].value, &value) != NGX_OK) { return NGX_ERROR; } if (value.len) { t = ngx_list_push(&r->headers_out.trailers); if (t == NULL) { return NGX_ERROR; } t->key = h[i].key; t->value = value; t->hash = 1; } } return ngx_http_next_body_filter(r, in); } static ngx_int_t ngx_http_set_expires(ngx_http_request_t *r, ngx_http_headers_conf_t *conf) { char *err; size_t len; time_t now, expires_time, max_age; ngx_str_t value; ngx_int_t rc; ngx_table_elt_t *e, *cc; 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->next = NULL; e->hash = 1; ngx_str_set(&e->key, "Expires"); } len = sizeof("Mon, 28 Sep 1970 06:00:00 GMT"); e->value.len = len - 1; cc = r->headers_out.cache_control; if (cc == NULL) { cc = ngx_list_push(&r->headers_out.headers); if (cc == NULL) { e->hash = 0; return NGX_ERROR; } r->headers_out.cache_control = cc; cc->next = NULL; cc->hash = 1; ngx_str_set(&cc->key, "Cache-Control"); } else { for (cc = cc->next; cc; cc = cc->next) { cc->hash = 0; } cc = r->headers_out.cache_control; cc->next = NULL; } 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) { e->hash = 0; cc->hash = 0; 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) { cc->hash = 0; return NGX_ERROR; } cc->value.len = ngx_sprintf(cc->value.data, "max-age=%T", max_age) - cc->value.data; return NGX_OK; } static ngx_int_t ngx_http_parse_expires(ngx_str_t *value, ngx_http_expires_t *expires, time_t *expires_time, char **err) { ngx_uint_t minus; if (*expires != NGX_HTTP_EXPIRES_MODIFIED) { if (value->len == 5 && ngx_strncmp(value->data, "epoch", 5) == 0) { *expires = NGX_HTTP_EXPIRES_EPOCH; return NGX_OK; } if (value->len == 3 && ngx_strncmp(value->data, "max", 3) == 0) { *expires = NGX_HTTP_EXPIRES_MAX; return NGX_OK; } if (value->len == 3 && ngx_strncmp(value->data, "off", 3) == 0) { *expires = NGX_HTTP_EXPIRES_OFF; return NGX_OK; } } if (value->len && value->data[0] == '@') { value->data++; value->len--; minus = 0; if (*expires == NGX_HTTP_EXPIRES_MODIFIED) { *err = "daily time cannot be used with \"modified\" parameter"; return NGX_ERROR; } *expires = NGX_HTTP_EXPIRES_DAILY; } else if (value->len && value->data[0] == '+') { value->data++; value->len--; minus = 0; } else if (value->len && value->data[0] == '-') { value->data++; value->len--; minus = 1; } else { minus = 0; } *expires_time = ngx_parse_time(value, 1); if (*expires_time == (time_t) NGX_ERROR) { *err = "invalid value"; return NGX_ERROR; } if (*expires == NGX_HTTP_EXPIRES_DAILY && *expires_time > 24 * 60 * 60) { *err = "daily time value must be less than 24 hours"; return NGX_ERROR; } if (minus) { *expires_time = - *expires_time; } return NGX_OK; } static ngx_int_t ngx_http_add_header(ngx_http_request_t *r, ngx_http_header_val_t *hv, ngx_str_t *value) { ngx_table_elt_t *h; if (value->len) { h = ngx_list_push(&r->headers_out.headers); if (h == NULL) { return NGX_ERROR; } h->hash = 1; h->key = hv->key; h->value = *value; } return NGX_OK; } static ngx_int_t ngx_http_add_multi_header_lines(ngx_http_request_t *r, ngx_http_header_val_t *hv, ngx_str_t *value) { ngx_table_elt_t *h, **ph; if (value->len == 0) { return NGX_OK; } h = ngx_list_push(&r->headers_out.headers); if (h == NULL) { return NGX_ERROR; } h->hash = 1; h->key = hv->key; h->value = *value; ph = (ngx_table_elt_t **) ((char *) &r->headers_out + hv->offset); while (*ph) { ph = &(*ph)->next; } *ph = h; h->next = NULL; 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->next = NULL; } h->hash = 1; h->key = hv->key; h->value = *value; return NGX_OK; } static void * ngx_http_headers_create_conf(ngx_conf_t *cf) { ngx_http_headers_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_headers_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->headers = NULL; * conf->trailers = NULL; * conf->expires_time = 0; * conf->expires_value = NULL; */ conf->expires = NGX_HTTP_EXPIRES_UNSET; return conf; } static char * ngx_http_headers_merge_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_headers_conf_t *prev = parent; ngx_http_headers_conf_t *conf = child; if (conf->expires == NGX_HTTP_EXPIRES_UNSET) { conf->expires = prev->expires; conf->expires_time = prev->expires_time; conf->expires_value = prev->expires_value; if (conf->expires == NGX_HTTP_EXPIRES_UNSET) { conf->expires = NGX_HTTP_EXPIRES_OFF; } } if (conf->headers == NULL) { conf->headers = prev->headers; } if (conf->trailers == NULL) { conf->trailers = prev->trailers; } return NGX_CONF_OK; } static ngx_int_t ngx_http_headers_filter_init(ngx_conf_t *cf) { ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_headers_filter; ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_trailers_filter; return NGX_OK; } static char * ngx_http_headers_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_headers_conf_t *hcf = conf; char *err; ngx_str_t *value; ngx_int_t rc; ngx_uint_t n; ngx_http_complex_value_t cv; ngx_http_compile_complex_value_t ccv; if (hcf->expires != NGX_HTTP_EXPIRES_UNSET) { return "is duplicate"; } value = cf->args->elts; if (cf->args->nelts == 2) { hcf->expires = NGX_HTTP_EXPIRES_ACCESS; n = 1; } else { /* cf->args->nelts == 3 */ if (ngx_strcmp(value[1].data, "modified") != 0) { return "invalid value"; } hcf->expires = NGX_HTTP_EXPIRES_MODIFIED; n = 2; } ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[n]; ccv.complex_value = &cv; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } if (cv.lengths != NULL) { hcf->expires_value = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); if (hcf->expires_value == NULL) { return NGX_CONF_ERROR; } *hcf->expires_value = cv; return NGX_CONF_OK; } rc = ngx_http_parse_expires(&value[n], &hcf->expires, &hcf->expires_time, &err); if (rc != NGX_OK) { return err; } return NGX_CONF_OK; } static char * ngx_http_headers_add(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_headers_conf_t *hcf = conf; ngx_str_t *value; ngx_uint_t i; ngx_array_t **headers; ngx_http_header_val_t *hv; ngx_http_set_header_t *set; ngx_http_compile_complex_value_t ccv; value = cf->args->elts; headers = (ngx_array_t **) ((char *) hcf + cmd->offset); if (*headers == NULL) { *headers = ngx_array_create(cf->pool, 1, sizeof(ngx_http_header_val_t)); if (*headers == NULL) { return NGX_CONF_ERROR; } } hv = ngx_array_push(*headers); if (hv == NULL) { return NGX_CONF_ERROR; } hv->key = value[1]; hv->handler = NULL; hv->offset = 0; hv->always = 0; if (headers == &hcf->headers) { hv->handler = ngx_http_add_header; set = ngx_http_set_headers; for (i = 0; set[i].name.len; i++) { if (ngx_strcasecmp(value[1].data, set[i].name.data) != 0) { continue; } hv->offset = set[i].offset; hv->handler = set[i].handler; break; } } if (value[2].len == 0) { ngx_memzero(&hv->value, sizeof(ngx_http_complex_value_t)); } else { ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[2]; ccv.complex_value = &hv->value; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } } if (cf->args->nelts == 3) { return NGX_CONF_OK; } if (ngx_strcmp(value[3].data, "always") != 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[3]); return NGX_CONF_ERROR; } hv->always = 1; return NGX_CONF_OK; } nginx-1.24.0/src/http/modules/ngx_http_image_filter_module.c000644 001751 001751 00000123111 14415135676 025413 0ustar00mdouninmdounin000000 000000 /* * 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_WEBP 4 #define NGX_HTTP_IMAGE_BUFFERED 0x08 typedef struct { ngx_uint_t filter; ngx_uint_t width; ngx_uint_t height; ngx_uint_t angle; ngx_uint_t jpeg_quality; ngx_uint_t webp_quality; ngx_uint_t sharpen; ngx_flag_t transparency; ngx_flag_t interlace; ngx_http_complex_value_t *wcv; ngx_http_complex_value_t *hcv; ngx_http_complex_value_t *acv; ngx_http_complex_value_t *jqcv; ngx_http_complex_value_t *wqcv; ngx_http_complex_value_t *shcv; size_t buffer_size; } ngx_http_image_filter_conf_t; typedef struct { u_char *image; u_char *last; size_t length; ngx_uint_t width; ngx_uint_t height; ngx_uint_t max_width; ngx_uint_t max_height; ngx_uint_t angle; ngx_uint_t phase; ngx_uint_t type; ngx_uint_t force; } ngx_http_image_filter_ctx_t; static ngx_int_t ngx_http_image_send(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx, ngx_chain_t *in); static ngx_uint_t ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in); static ngx_int_t ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in); static ngx_buf_t *ngx_http_image_process(ngx_http_request_t *r); static ngx_buf_t *ngx_http_image_json(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx); static ngx_buf_t *ngx_http_image_asis(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx); static void ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b); static ngx_int_t ngx_http_image_size(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx); static ngx_buf_t *ngx_http_image_resize(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx); static gdImagePtr ngx_http_image_source(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx); static gdImagePtr ngx_http_image_new(ngx_http_request_t *r, int w, int h, int colors); static u_char *ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type, gdImagePtr img, int *size); static void ngx_http_image_cleanup(void *data); static ngx_uint_t ngx_http_image_filter_get_value(ngx_http_request_t *r, ngx_http_complex_value_t *cv, ngx_uint_t v); static ngx_uint_t ngx_http_image_filter_value(ngx_str_t *value); static void *ngx_http_image_filter_create_conf(ngx_conf_t *cf); static char *ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child); static char *ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_image_filter_webp_quality(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_int_t ngx_http_image_filter_init(ngx_conf_t *cf); static ngx_command_t ngx_http_image_filter_commands[] = { { ngx_string("image_filter"), NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123, ngx_http_image_filter, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("image_filter_jpeg_quality"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_image_filter_jpeg_quality, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("image_filter_webp_quality"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_image_filter_webp_quality, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("image_filter_sharpen"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_image_filter_sharpen, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("image_filter_transparency"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_image_filter_conf_t, transparency), NULL }, { ngx_string("image_filter_interlace"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_image_filter_conf_t, interlace), NULL }, { ngx_string("image_filter_buffer"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_image_filter_conf_t, buffer_size), NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_image_filter_module_ctx = { NULL, /* preconfiguration */ ngx_http_image_filter_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_image_filter_create_conf, /* create location configuration */ ngx_http_image_filter_merge_conf /* merge location configuration */ }; ngx_module_t ngx_http_image_filter_module = { NGX_MODULE_V1, &ngx_http_image_filter_module_ctx, /* module context */ ngx_http_image_filter_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter; static ngx_str_t ngx_http_image_types[] = { ngx_string("image/jpeg"), ngx_string("image/gif"), ngx_string("image/png"), ngx_string("image/webp") }; static ngx_int_t ngx_http_image_header_filter(ngx_http_request_t *r) { off_t len; ngx_http_image_filter_ctx_t *ctx; ngx_http_image_filter_conf_t *conf; if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) { return ngx_http_next_header_filter(r); } ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module); if (ctx) { ngx_http_set_ctx(r, NULL, ngx_http_image_filter_module); return ngx_http_next_header_filter(r); } conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); if (conf->filter == NGX_HTTP_IMAGE_OFF) { return ngx_http_next_header_filter(r); } if (r->headers_out.content_type.len >= sizeof("multipart/x-mixed-replace") - 1 && ngx_strncasecmp(r->headers_out.content_type.data, (u_char *) "multipart/x-mixed-replace", sizeof("multipart/x-mixed-replace") - 1) == 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "image filter: multipart/x-mixed-replace response"); return NGX_ERROR; } ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_image_filter_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_image_filter_module); len = r->headers_out.content_length_n; if (len != -1 && len > (off_t) conf->buffer_size) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "image filter: too big response: %O", len); return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE; } if (len == -1) { ctx->length = conf->buffer_size; } else { ctx->length = (size_t) len; } if (r->headers_out.refresh) { r->headers_out.refresh->hash = 0; } r->main_filter_need_in_memory = 1; r->allow_ranges = 0; return NGX_OK; } static ngx_int_t ngx_http_image_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_int_t rc; ngx_str_t *ct; ngx_chain_t out; ngx_http_image_filter_ctx_t *ctx; ngx_http_image_filter_conf_t *conf; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image filter"); if (in == NULL) { return ngx_http_next_body_filter(r, in); } ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module); if (ctx == NULL) { return ngx_http_next_body_filter(r, in); } switch (ctx->phase) { case NGX_HTTP_IMAGE_START: ctx->type = ngx_http_image_test(r, in); conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); if (ctx->type == NGX_HTTP_IMAGE_NONE) { if (conf->filter == NGX_HTTP_IMAGE_SIZE) { out.buf = ngx_http_image_json(r, NULL); if (out.buf) { out.next = NULL; ctx->phase = NGX_HTTP_IMAGE_DONE; return ngx_http_image_send(r, ctx, &out); } } return ngx_http_filter_finalize_request(r, &ngx_http_image_filter_module, NGX_HTTP_UNSUPPORTED_MEDIA_TYPE); } /* override content type */ ct = &ngx_http_image_types[ctx->type - 1]; r->headers_out.content_type_len = ct->len; r->headers_out.content_type = *ct; r->headers_out.content_type_lowcase = NULL; if (conf->filter == NGX_HTTP_IMAGE_TEST) { ctx->phase = NGX_HTTP_IMAGE_PASS; return ngx_http_image_send(r, ctx, in); } ctx->phase = NGX_HTTP_IMAGE_READ; /* fall through */ case NGX_HTTP_IMAGE_READ: rc = ngx_http_image_read(r, in); if (rc == NGX_AGAIN) { return NGX_OK; } if (rc == NGX_ERROR) { return ngx_http_filter_finalize_request(r, &ngx_http_image_filter_module, NGX_HTTP_UNSUPPORTED_MEDIA_TYPE); } /* fall through */ case NGX_HTTP_IMAGE_PROCESS: out.buf = ngx_http_image_process(r); if (out.buf == NULL) { return ngx_http_filter_finalize_request(r, &ngx_http_image_filter_module, NGX_HTTP_UNSUPPORTED_MEDIA_TYPE); } out.next = NULL; ctx->phase = NGX_HTTP_IMAGE_PASS; return ngx_http_image_send(r, ctx, &out); case NGX_HTTP_IMAGE_PASS: return ngx_http_next_body_filter(r, in); default: /* NGX_HTTP_IMAGE_DONE */ rc = ngx_http_next_body_filter(r, NULL); /* NGX_ERROR resets any pending data */ return (rc == NGX_OK) ? NGX_ERROR : rc; } } static ngx_int_t ngx_http_image_send(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx, ngx_chain_t *in) { ngx_int_t rc; rc = ngx_http_next_header_filter(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { return NGX_ERROR; } rc = ngx_http_next_body_filter(r, in); if (ctx->phase == NGX_HTTP_IMAGE_DONE) { /* NGX_ERROR resets any pending data */ return (rc == NGX_OK) ? NGX_ERROR : rc; } return rc; } static ngx_uint_t ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in) { u_char *p; p = in->buf->pos; if (in->buf->last - p < 16) { return NGX_HTTP_IMAGE_NONE; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image filter: \"%c%c\"", p[0], p[1]); if (p[0] == 0xff && p[1] == 0xd8) { /* JPEG */ return NGX_HTTP_IMAGE_JPEG; } else if (p[0] == 'G' && p[1] == 'I' && p[2] == 'F' && p[3] == '8' && p[5] == 'a') { if (p[4] == '9' || p[4] == '7') { /* GIF */ return NGX_HTTP_IMAGE_GIF; } } else if (p[0] == 0x89 && p[1] == 'P' && p[2] == 'N' && p[3] == 'G' && p[4] == 0x0d && p[5] == 0x0a && p[6] == 0x1a && p[7] == 0x0a) { /* PNG */ return NGX_HTTP_IMAGE_PNG; } else if (p[0] == 'R' && p[1] == 'I' && p[2] == 'F' && p[3] == 'F' && p[8] == 'W' && p[9] == 'E' && p[10] == 'B' && p[11] == 'P') { /* WebP */ return NGX_HTTP_IMAGE_WEBP; } return NGX_HTTP_IMAGE_NONE; } static ngx_int_t ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in) { u_char *p; size_t size, rest; ngx_buf_t *b; ngx_chain_t *cl; ngx_http_image_filter_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module); if (ctx->image == NULL) { ctx->image = ngx_palloc(r->pool, ctx->length); if (ctx->image == NULL) { return NGX_ERROR; } ctx->last = ctx->image; } p = ctx->last; for (cl = in; cl; cl = cl->next) { b = cl->buf; size = b->last - b->pos; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image buf: %uz", size); rest = ctx->image + ctx->length - p; if (size > rest) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "image filter: too big response"); return NGX_ERROR; } p = ngx_cpymem(p, b->pos, size); b->pos += size; if (b->last_buf) { ctx->last = p; return NGX_OK; } } ctx->last = p; r->connection->buffered |= NGX_HTTP_IMAGE_BUFFERED; return NGX_AGAIN; } static ngx_buf_t * ngx_http_image_process(ngx_http_request_t *r) { ngx_int_t rc; ngx_http_image_filter_ctx_t *ctx; ngx_http_image_filter_conf_t *conf; r->connection->buffered &= ~NGX_HTTP_IMAGE_BUFFERED; ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module); rc = ngx_http_image_size(r, ctx); conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); if (conf->filter == NGX_HTTP_IMAGE_SIZE) { return ngx_http_image_json(r, rc == NGX_OK ? ctx : NULL); } ctx->angle = ngx_http_image_filter_get_value(r, conf->acv, conf->angle); if (conf->filter == NGX_HTTP_IMAGE_ROTATE) { if (ctx->angle != 90 && ctx->angle != 180 && ctx->angle != 270) { return NULL; } return ngx_http_image_resize(r, ctx); } ctx->max_width = ngx_http_image_filter_get_value(r, conf->wcv, conf->width); if (ctx->max_width == 0) { return NULL; } ctx->max_height = ngx_http_image_filter_get_value(r, conf->hcv, conf->height); if (ctx->max_height == 0) { return NULL; } if (rc == NGX_OK && ctx->width <= ctx->max_width && ctx->height <= ctx->max_height && ctx->angle == 0 && !ctx->force) { return ngx_http_image_asis(r, ctx); } return ngx_http_image_resize(r, ctx); } static ngx_buf_t * ngx_http_image_json(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) { size_t len; ngx_buf_t *b; b = ngx_calloc_buf(r->pool); if (b == NULL) { return NULL; } b->memory = 1; b->last_buf = 1; ngx_http_clean_header(r); r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_type_len = sizeof("application/json") - 1; ngx_str_set(&r->headers_out.content_type, "application/json"); r->headers_out.content_type_lowcase = NULL; if (ctx == NULL) { b->pos = (u_char *) "{}" CRLF; b->last = b->pos + sizeof("{}" CRLF) - 1; ngx_http_image_length(r, b); return b; } len = sizeof("{ \"img\" : " "{ \"width\": , \"height\": , \"type\": \"jpeg\" } }" CRLF) - 1 + 2 * NGX_SIZE_T_LEN; b->pos = ngx_pnalloc(r->pool, len); if (b->pos == NULL) { return NULL; } b->last = ngx_sprintf(b->pos, "{ \"img\" : " "{ \"width\": %uz," " \"height\": %uz," " \"type\": \"%s\" } }" CRLF, ctx->width, ctx->height, ngx_http_image_types[ctx->type - 1].data + 6); ngx_http_image_length(r, b); return b; } static ngx_buf_t * ngx_http_image_asis(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) { ngx_buf_t *b; b = ngx_calloc_buf(r->pool); if (b == NULL) { return NULL; } b->pos = ctx->image; b->last = ctx->last; b->memory = 1; b->last_buf = 1; ngx_http_image_length(r, b); return b; } static void ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b) { r->headers_out.content_length_n = b->last - b->pos; if (r->headers_out.content_length) { r->headers_out.content_length->hash = 0; } r->headers_out.content_length = NULL; } static ngx_int_t ngx_http_image_size(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) { u_char *p, *last; size_t len, app; ngx_uint_t width, height; p = ctx->image; switch (ctx->type) { case NGX_HTTP_IMAGE_JPEG: p += 2; last = ctx->image + ctx->length - 10; width = 0; height = 0; app = 0; while (p < last) { if (p[0] == 0xff && p[1] != 0xff) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "JPEG: %02xd %02xd", p[0], p[1]); p++; if ((*p == 0xc0 || *p == 0xc1 || *p == 0xc2 || *p == 0xc3 || *p == 0xc9 || *p == 0xca || *p == 0xcb) && (width == 0 || height == 0)) { width = p[6] * 256 + p[7]; height = p[4] * 256 + p[5]; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "JPEG: %02xd %02xd", p[1], p[2]); len = p[1] * 256 + p[2]; if (*p >= 0xe1 && *p <= 0xef) { /* application data, e.g., EXIF, Adobe XMP, etc. */ app += len; } p += len; continue; } p++; } if (width == 0 || height == 0) { return NGX_DECLINED; } if (ctx->length / 20 < app) { /* force conversion if application data consume more than 5% */ ctx->force = 1; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "app data size: %uz", app); } break; case NGX_HTTP_IMAGE_GIF: if (ctx->length < 10) { return NGX_DECLINED; } width = p[7] * 256 + p[6]; height = p[9] * 256 + p[8]; break; case NGX_HTTP_IMAGE_PNG: if (ctx->length < 24) { return NGX_DECLINED; } width = p[18] * 256 + p[19]; height = p[22] * 256 + p[23]; break; case NGX_HTTP_IMAGE_WEBP: if (ctx->length < 30) { return NGX_DECLINED; } if (p[12] != 'V' || p[13] != 'P' || p[14] != '8') { return NGX_DECLINED; } switch (p[15]) { case ' ': if (p[20] & 1) { /* not a key frame */ return NGX_DECLINED; } if (p[23] != 0x9d || p[24] != 0x01 || p[25] != 0x2a) { /* invalid start code */ return NGX_DECLINED; } width = (p[26] | p[27] << 8) & 0x3fff; height = (p[28] | p[29] << 8) & 0x3fff; break; case 'L': if (p[20] != 0x2f) { /* invalid signature */ return NGX_DECLINED; } width = ((p[21] | p[22] << 8) & 0x3fff) + 1; height = ((p[22] >> 6 | p[23] << 2 | p[24] << 10) & 0x3fff) + 1; break; case 'X': width = (p[24] | p[25] << 8 | p[26] << 16) + 1; height = (p[27] | p[28] << 8 | p[29] << 16) + 1; break; default: return NGX_DECLINED; } break; default: return NGX_DECLINED; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image size: %d x %d", (int) width, (int) height); ctx->width = width; ctx->height = height; return NGX_OK; } static ngx_buf_t * ngx_http_image_resize(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) { int sx, sy, dx, dy, ox, oy, ax, ay, size, colors, palette, transparent, sharpen, red, green, blue, t; u_char *out; ngx_buf_t *b; ngx_uint_t resize; gdImagePtr src, dst; ngx_pool_cleanup_t *cln; ngx_http_image_filter_conf_t *conf; src = ngx_http_image_source(r, ctx); if (src == NULL) { return NULL; } sx = gdImageSX(src); sy = gdImageSY(src); conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); if (!ctx->force && ctx->angle == 0 && (ngx_uint_t) sx <= ctx->max_width && (ngx_uint_t) sy <= ctx->max_height) { gdImageDestroy(src); return ngx_http_image_asis(r, ctx); } colors = gdImageColorsTotal(src); if (colors && conf->transparency) { transparent = gdImageGetTransparent(src); if (transparent != -1) { palette = colors; red = gdImageRed(src, transparent); green = gdImageGreen(src, transparent); blue = gdImageBlue(src, transparent); goto transparent; } } palette = 0; transparent = -1; red = 0; green = 0; blue = 0; transparent: gdImageColorTransparent(src, -1); dx = sx; dy = sy; if (conf->filter == NGX_HTTP_IMAGE_RESIZE) { if ((ngx_uint_t) dx > ctx->max_width) { dy = dy * ctx->max_width / dx; dy = dy ? dy : 1; dx = ctx->max_width; } if ((ngx_uint_t) dy > ctx->max_height) { dx = dx * ctx->max_height / dy; dx = dx ? dx : 1; dy = ctx->max_height; } resize = 1; } else if (conf->filter == NGX_HTTP_IMAGE_ROTATE) { resize = 0; } else { /* NGX_HTTP_IMAGE_CROP */ resize = 0; if ((double) dx / dy < (double) ctx->max_width / ctx->max_height) { if ((ngx_uint_t) dx > ctx->max_width) { dy = dy * ctx->max_width / dx; dy = dy ? dy : 1; dx = ctx->max_width; resize = 1; } } else { if ((ngx_uint_t) dy > ctx->max_height) { dx = dx * ctx->max_height / dy; dx = dx ? dx : 1; dy = ctx->max_height; resize = 1; } } } if (resize) { dst = ngx_http_image_new(r, dx, dy, palette); if (dst == NULL) { gdImageDestroy(src); return NULL; } if (colors == 0) { gdImageSaveAlpha(dst, 1); gdImageAlphaBlending(dst, 0); } gdImageCopyResampled(dst, src, 0, 0, 0, 0, dx, dy, sx, sy); if (colors) { gdImageTrueColorToPalette(dst, 1, 256); } gdImageDestroy(src); } else { dst = src; } if (ctx->angle) { src = dst; ax = (dx % 2 == 0) ? 1 : 0; ay = (dy % 2 == 0) ? 1 : 0; switch (ctx->angle) { case 90: case 270: dst = ngx_http_image_new(r, dy, dx, palette); if (dst == NULL) { gdImageDestroy(src); return NULL; } if (ctx->angle == 90) { ox = dy / 2 + ay; oy = dx / 2 - ax; } else { ox = dy / 2 - ay; oy = dx / 2 + ax; } gdImageCopyRotated(dst, src, ox, oy, 0, 0, dx + ax, dy + ay, ctx->angle); gdImageDestroy(src); t = dx; dx = dy; dy = t; break; case 180: dst = ngx_http_image_new(r, dx, dy, palette); if (dst == NULL) { gdImageDestroy(src); return NULL; } gdImageCopyRotated(dst, src, dx / 2 - ax, dy / 2 - ay, 0, 0, dx + ax, dy + ay, ctx->angle); gdImageDestroy(src); break; } } if (conf->filter == NGX_HTTP_IMAGE_CROP) { src = dst; if ((ngx_uint_t) dx > ctx->max_width) { ox = dx - ctx->max_width; } else { ox = 0; } if ((ngx_uint_t) dy > ctx->max_height) { oy = dy - ctx->max_height; } else { oy = 0; } if (ox || oy) { dst = ngx_http_image_new(r, dx - ox, dy - oy, colors); if (dst == NULL) { gdImageDestroy(src); return NULL; } ox /= 2; oy /= 2; ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image crop: %d x %d @ %d x %d", dx, dy, ox, oy); if (colors == 0) { gdImageSaveAlpha(dst, 1); gdImageAlphaBlending(dst, 0); } gdImageCopy(dst, src, 0, 0, ox, oy, dx - ox, dy - oy); if (colors) { gdImageTrueColorToPalette(dst, 1, 256); } gdImageDestroy(src); } } if (transparent != -1 && colors) { gdImageColorTransparent(dst, gdImageColorExact(dst, red, green, blue)); } sharpen = ngx_http_image_filter_get_value(r, conf->shcv, conf->sharpen); if (sharpen > 0) { gdImageSharpen(dst, sharpen); } gdImageInterlace(dst, (int) conf->interlace); out = ngx_http_image_out(r, ctx->type, dst, &size); ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image: %d x %d %d", sx, sy, colors); gdImageDestroy(dst); ngx_pfree(r->pool, ctx->image); if (out == NULL) { return NULL; } cln = ngx_pool_cleanup_add(r->pool, 0); if (cln == NULL) { gdFree(out); return NULL; } b = ngx_calloc_buf(r->pool); if (b == NULL) { gdFree(out); return NULL; } cln->handler = ngx_http_image_cleanup; cln->data = out; b->pos = out; b->last = out + size; b->memory = 1; b->last_buf = 1; ngx_http_image_length(r, b); ngx_http_weak_etag(r); return b; } static gdImagePtr ngx_http_image_source(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) { char *failed; gdImagePtr img; img = NULL; switch (ctx->type) { case NGX_HTTP_IMAGE_JPEG: img = gdImageCreateFromJpegPtr(ctx->length, ctx->image); failed = "gdImageCreateFromJpegPtr() failed"; break; case NGX_HTTP_IMAGE_GIF: img = gdImageCreateFromGifPtr(ctx->length, ctx->image); failed = "gdImageCreateFromGifPtr() failed"; break; case NGX_HTTP_IMAGE_PNG: img = gdImageCreateFromPngPtr(ctx->length, ctx->image); failed = "gdImageCreateFromPngPtr() failed"; break; case NGX_HTTP_IMAGE_WEBP: #if (NGX_HAVE_GD_WEBP) img = gdImageCreateFromWebpPtr(ctx->length, ctx->image); failed = "gdImageCreateFromWebpPtr() failed"; #else failed = "nginx was built without GD WebP support"; #endif break; default: failed = "unknown image type"; break; } if (img == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed); } return img; } static gdImagePtr ngx_http_image_new(ngx_http_request_t *r, int w, int h, int colors) { gdImagePtr img; if (colors == 0) { img = gdImageCreateTrueColor(w, h); if (img == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "gdImageCreateTrueColor() failed"); return NULL; } } else { img = gdImageCreate(w, h); if (img == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "gdImageCreate() failed"); return NULL; } } return img; } static u_char * ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type, gdImagePtr img, int *size) { char *failed; u_char *out; ngx_int_t q; ngx_http_image_filter_conf_t *conf; out = NULL; switch (type) { case NGX_HTTP_IMAGE_JPEG: conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); q = ngx_http_image_filter_get_value(r, conf->jqcv, conf->jpeg_quality); if (q <= 0) { return NULL; } out = gdImageJpegPtr(img, size, q); failed = "gdImageJpegPtr() failed"; break; case NGX_HTTP_IMAGE_GIF: out = gdImageGifPtr(img, size); failed = "gdImageGifPtr() failed"; break; case NGX_HTTP_IMAGE_PNG: out = gdImagePngPtr(img, size); failed = "gdImagePngPtr() failed"; break; case NGX_HTTP_IMAGE_WEBP: #if (NGX_HAVE_GD_WEBP) conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); q = ngx_http_image_filter_get_value(r, conf->wqcv, conf->webp_quality); if (q <= 0) { return NULL; } out = gdImageWebpPtrEx(img, size, q); failed = "gdImageWebpPtrEx() failed"; #else failed = "nginx was built without GD WebP support"; #endif break; default: failed = "unknown image type"; break; } if (out == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed); } return out; } static void ngx_http_image_cleanup(void *data) { gdFree(data); } static ngx_uint_t ngx_http_image_filter_get_value(ngx_http_request_t *r, ngx_http_complex_value_t *cv, ngx_uint_t v) { ngx_str_t val; if (cv == NULL) { return v; } if (ngx_http_complex_value(r, cv, &val) != NGX_OK) { return 0; } return ngx_http_image_filter_value(&val); } static ngx_uint_t ngx_http_image_filter_value(ngx_str_t *value) { ngx_int_t n; if (value->len == 1 && value->data[0] == '-') { return (ngx_uint_t) -1; } n = ngx_atoi(value->data, value->len); if (n > 0) { return (ngx_uint_t) n; } return 0; } static void * ngx_http_image_filter_create_conf(ngx_conf_t *cf) { ngx_http_image_filter_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_image_filter_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->width = 0; * conf->height = 0; * conf->angle = 0; * conf->wcv = NULL; * conf->hcv = NULL; * conf->acv = NULL; * conf->jqcv = NULL; * conf->wqcv = NULL; * conf->shcv = NULL; */ conf->filter = NGX_CONF_UNSET_UINT; conf->jpeg_quality = NGX_CONF_UNSET_UINT; conf->webp_quality = NGX_CONF_UNSET_UINT; conf->sharpen = NGX_CONF_UNSET_UINT; conf->transparency = NGX_CONF_UNSET; conf->interlace = NGX_CONF_UNSET; conf->buffer_size = NGX_CONF_UNSET_SIZE; return conf; } static char * ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_image_filter_conf_t *prev = parent; ngx_http_image_filter_conf_t *conf = child; if (conf->filter == NGX_CONF_UNSET_UINT) { if (prev->filter == NGX_CONF_UNSET_UINT) { conf->filter = NGX_HTTP_IMAGE_OFF; } else { conf->filter = prev->filter; conf->width = prev->width; conf->height = prev->height; conf->angle = prev->angle; conf->wcv = prev->wcv; conf->hcv = prev->hcv; conf->acv = prev->acv; } } if (conf->jpeg_quality == NGX_CONF_UNSET_UINT) { /* 75 is libjpeg default quality */ ngx_conf_merge_uint_value(conf->jpeg_quality, prev->jpeg_quality, 75); if (conf->jqcv == NULL) { conf->jqcv = prev->jqcv; } } if (conf->webp_quality == NGX_CONF_UNSET_UINT) { /* 80 is libwebp default quality */ ngx_conf_merge_uint_value(conf->webp_quality, prev->webp_quality, 80); if (conf->wqcv == NULL) { conf->wqcv = prev->wqcv; } } if (conf->sharpen == NGX_CONF_UNSET_UINT) { ngx_conf_merge_uint_value(conf->sharpen, prev->sharpen, 0); if (conf->shcv == NULL) { conf->shcv = prev->shcv; } } ngx_conf_merge_value(conf->transparency, prev->transparency, 1); ngx_conf_merge_value(conf->interlace, prev->interlace, 0); ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, 1 * 1024 * 1024); return NGX_CONF_OK; } static char * ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_image_filter_conf_t *imcf = conf; ngx_str_t *value; ngx_int_t n; ngx_uint_t i; ngx_http_complex_value_t cv; ngx_http_compile_complex_value_t ccv; value = cf->args->elts; i = 1; if (cf->args->nelts == 2) { if (ngx_strcmp(value[i].data, "off") == 0) { imcf->filter = NGX_HTTP_IMAGE_OFF; } else if (ngx_strcmp(value[i].data, "test") == 0) { imcf->filter = NGX_HTTP_IMAGE_TEST; } else if (ngx_strcmp(value[i].data, "size") == 0) { imcf->filter = NGX_HTTP_IMAGE_SIZE; } else { goto failed; } return NGX_CONF_OK; } else if (cf->args->nelts == 3) { if (ngx_strcmp(value[i].data, "rotate") == 0) { if (imcf->filter != NGX_HTTP_IMAGE_RESIZE && imcf->filter != NGX_HTTP_IMAGE_CROP) { imcf->filter = NGX_HTTP_IMAGE_ROTATE; } ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[++i]; ccv.complex_value = &cv; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } if (cv.lengths == NULL) { n = ngx_http_image_filter_value(&value[i]); if (n != 90 && n != 180 && n != 270) { goto failed; } imcf->angle = (ngx_uint_t) n; } else { imcf->acv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); if (imcf->acv == NULL) { return NGX_CONF_ERROR; } *imcf->acv = cv; } return NGX_CONF_OK; } else { goto failed; } } if (ngx_strcmp(value[i].data, "resize") == 0) { imcf->filter = NGX_HTTP_IMAGE_RESIZE; } else if (ngx_strcmp(value[i].data, "crop") == 0) { imcf->filter = NGX_HTTP_IMAGE_CROP; } else { goto failed; } ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[++i]; ccv.complex_value = &cv; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } if (cv.lengths == NULL) { n = ngx_http_image_filter_value(&value[i]); if (n == 0) { goto failed; } imcf->width = (ngx_uint_t) n; } else { imcf->wcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); if (imcf->wcv == NULL) { return NGX_CONF_ERROR; } *imcf->wcv = cv; } ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[++i]; ccv.complex_value = &cv; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } if (cv.lengths == NULL) { n = ngx_http_image_filter_value(&value[i]); if (n == 0) { goto failed; } imcf->height = (ngx_uint_t) n; } else { imcf->hcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); if (imcf->hcv == NULL) { return NGX_CONF_ERROR; } *imcf->hcv = cv; } return NGX_CONF_OK; failed: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[i]); return NGX_CONF_ERROR; } static char * ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_image_filter_conf_t *imcf = conf; ngx_str_t *value; ngx_int_t n; ngx_http_complex_value_t cv; ngx_http_compile_complex_value_t ccv; value = cf->args->elts; ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = &cv; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } if (cv.lengths == NULL) { n = ngx_http_image_filter_value(&value[1]); if (n <= 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid value \"%V\"", &value[1]); return NGX_CONF_ERROR; } imcf->jpeg_quality = (ngx_uint_t) n; } else { imcf->jqcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); if (imcf->jqcv == NULL) { return NGX_CONF_ERROR; } *imcf->jqcv = cv; } return NGX_CONF_OK; } static char * ngx_http_image_filter_webp_quality(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_image_filter_conf_t *imcf = conf; ngx_str_t *value; ngx_int_t n; ngx_http_complex_value_t cv; ngx_http_compile_complex_value_t ccv; value = cf->args->elts; ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = &cv; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } if (cv.lengths == NULL) { n = ngx_http_image_filter_value(&value[1]); if (n <= 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid value \"%V\"", &value[1]); return NGX_CONF_ERROR; } imcf->webp_quality = (ngx_uint_t) n; } else { imcf->wqcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); if (imcf->wqcv == NULL) { return NGX_CONF_ERROR; } *imcf->wqcv = cv; } return NGX_CONF_OK; } static char * ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_image_filter_conf_t *imcf = conf; ngx_str_t *value; ngx_int_t n; ngx_http_complex_value_t cv; ngx_http_compile_complex_value_t ccv; value = cf->args->elts; ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = &cv; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } if (cv.lengths == NULL) { n = ngx_http_image_filter_value(&value[1]); if (n < 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid value \"%V\"", &value[1]); return NGX_CONF_ERROR; } imcf->sharpen = (ngx_uint_t) n; } else { imcf->shcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); if (imcf->shcv == NULL) { return NGX_CONF_ERROR; } *imcf->shcv = cv; } return NGX_CONF_OK; } static ngx_int_t ngx_http_image_filter_init(ngx_conf_t *cf) { ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_image_header_filter; ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_image_body_filter; return NGX_OK; } nginx-1.24.0/src/http/modules/ngx_http_index_module.c000644 001751 001751 00000035274 14415135676 024107 0ustar00mdouninmdounin000000 000000 /* * 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_HTTP_INTERNAL_SERVER_ERROR; } allocated = path.data + path.len - name; } if (index[i].values == NULL) { /* index[i].name.len includes the terminating '\0' */ ngx_memcpy(name, index[i].name.data, index[i].name.len); path.len = (name + index[i].name.len - 1) - path.data; } else { e.ip = index[i].values->elts; e.pos = name; while (*(uintptr_t *) e.ip) { code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); } if (*name == '/') { uri.len = len - 1; uri.data = name; return ngx_http_internal_redirect(r, &uri, &r->args); } path.len = e.pos - path.data; *e.pos = '\0'; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "open index \"%V\"", &path); ngx_memzero(&of, sizeof(ngx_open_file_info_t)); of.read_ahead = clcf->read_ahead; of.directio = clcf->directio; of.valid = clcf->open_file_cache_valid; of.min_uses = clcf->open_file_cache_min_uses; of.test_only = 1; of.errors = clcf->open_file_cache_errors; of.events = clcf->open_file_cache_events; if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool) != NGX_OK) { if (of.err == 0) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, of.err, "%s \"%s\" failed", of.failed, path.data); #if (NGX_HAVE_OPENAT) if (of.err == NGX_EMLINK || of.err == NGX_ELOOP) { return NGX_HTTP_FORBIDDEN; } #endif if (of.err == NGX_ENOTDIR || of.err == NGX_ENAMETOOLONG || of.err == NGX_EACCES) { return ngx_http_index_error(r, clcf, path.data, of.err); } if (!dir_tested) { rc = ngx_http_index_test_dir(r, clcf, path.data, name - 1); if (rc != NGX_OK) { return rc; } dir_tested = 1; } if (of.err == NGX_ENOENT) { continue; } ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err, "%s \"%s\" failed", of.failed, path.data); return NGX_HTTP_INTERNAL_SERVER_ERROR; } uri.len = r->uri.len + len - 1; if (!clcf->alias) { uri.data = path.data + root; } else { uri.data = ngx_pnalloc(r->pool, uri.len); if (uri.data == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } p = ngx_copy(uri.data, r->uri.data, r->uri.len); ngx_memcpy(p, name, len - 1); } return ngx_http_internal_redirect(r, &uri, &r->args); } return NGX_DECLINED; } static ngx_int_t ngx_http_index_test_dir(ngx_http_request_t *r, ngx_http_core_loc_conf_t *clcf, u_char *path, u_char *last) { u_char c; ngx_str_t dir; ngx_open_file_info_t of; c = *last; if (c != '/' || path == last) { /* "alias" without trailing slash */ c = *(++last); } *last = '\0'; dir.len = last - path; dir.data = path; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http index check dir: \"%V\"", &dir); ngx_memzero(&of, sizeof(ngx_open_file_info_t)); of.test_dir = 1; of.test_only = 1; of.valid = clcf->open_file_cache_valid; of.errors = clcf->open_file_cache_errors; if (ngx_http_set_disable_symlinks(r, clcf, &dir, &of) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_open_cached_file(clcf->open_file_cache, &dir, &of, r->pool) != NGX_OK) { if (of.err) { #if (NGX_HAVE_OPENAT) if (of.err == NGX_EMLINK || of.err == NGX_ELOOP) { return NGX_HTTP_FORBIDDEN; } #endif if (of.err == NGX_ENOENT) { *last = c; return ngx_http_index_error(r, clcf, dir.data, NGX_ENOENT); } if (of.err == NGX_EACCES) { *last = c; /* * ngx_http_index_test_dir() is called after the first index * file testing has returned an error distinct from NGX_EACCES. * This means that directory searching is allowed. */ return NGX_OK; } ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err, "%s \"%s\" failed", of.failed, dir.data); } return NGX_HTTP_INTERNAL_SERVER_ERROR; } *last = c; if (of.is_dir) { return NGX_OK; } ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "\"%s\" is not a directory", dir.data); return NGX_HTTP_INTERNAL_SERVER_ERROR; } static ngx_int_t ngx_http_index_error(ngx_http_request_t *r, ngx_http_core_loc_conf_t *clcf, u_char *file, ngx_err_t err) { if (err == NGX_EACCES) { ngx_log_error(NGX_LOG_ERR, r->connection->log, err, "\"%s\" is forbidden", file); return NGX_HTTP_FORBIDDEN; } if (clcf->log_not_found) { ngx_log_error(NGX_LOG_ERR, r->connection->log, err, "\"%s\" is not found", file); } return NGX_HTTP_NOT_FOUND; } static void * ngx_http_index_create_loc_conf(ngx_conf_t *cf) { ngx_http_index_loc_conf_t *conf; conf = ngx_palloc(cf->pool, sizeof(ngx_http_index_loc_conf_t)); if (conf == NULL) { return NULL; } conf->indices = NULL; conf->max_index_len = 0; return conf; } static char * ngx_http_index_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_index_loc_conf_t *prev = parent; ngx_http_index_loc_conf_t *conf = child; ngx_http_index_t *index; if (conf->indices == NULL) { conf->indices = prev->indices; conf->max_index_len = prev->max_index_len; } if (conf->indices == NULL) { conf->indices = ngx_array_create(cf->pool, 1, sizeof(ngx_http_index_t)); if (conf->indices == NULL) { return NGX_CONF_ERROR; } index = ngx_array_push(conf->indices); if (index == NULL) { return NGX_CONF_ERROR; } index->name.len = sizeof(NGX_HTTP_DEFAULT_INDEX); index->name.data = (u_char *) NGX_HTTP_DEFAULT_INDEX; index->lengths = NULL; index->values = NULL; conf->max_index_len = sizeof(NGX_HTTP_DEFAULT_INDEX); return NGX_CONF_OK; } return NGX_CONF_OK; } static ngx_int_t ngx_http_index_init(ngx_conf_t *cf) { ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } *h = ngx_http_index_handler; return NGX_OK; } /* TODO: warn about duplicate indices */ static char * ngx_http_index_set_index(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_index_loc_conf_t *ilcf = conf; ngx_str_t *value; ngx_uint_t i, n; ngx_http_index_t *index; ngx_http_script_compile_t sc; if (ilcf->indices == NULL) { ilcf->indices = ngx_array_create(cf->pool, 2, sizeof(ngx_http_index_t)); if (ilcf->indices == NULL) { return NGX_CONF_ERROR; } } value = cf->args->elts; for (i = 1; i < cf->args->nelts; i++) { if (value[i].data[0] == '/' && i != cf->args->nelts - 1) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "only the last index in \"index\" directive " "should be absolute"); } if (value[i].len == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "index \"%V\" in \"index\" directive is invalid", &value[1]); return NGX_CONF_ERROR; } index = ngx_array_push(ilcf->indices); if (index == NULL) { return NGX_CONF_ERROR; } index->name.len = value[i].len; index->name.data = value[i].data; index->lengths = NULL; index->values = NULL; n = ngx_http_script_variables_count(&value[i]); if (n == 0) { if (ilcf->max_index_len < index->name.len) { ilcf->max_index_len = index->name.len; } if (index->name.data[0] == '/') { continue; } /* include the terminating '\0' to the length to use ngx_memcpy() */ index->name.len++; continue; } ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); sc.cf = cf; sc.source = &value[i]; sc.lengths = &index->lengths; sc.values = &index->values; sc.variables = n; sc.complete_lengths = 1; sc.complete_values = 1; if (ngx_http_script_compile(&sc) != NGX_OK) { return NGX_CONF_ERROR; } } return NGX_CONF_OK; } nginx-1.24.0/src/http/modules/ngx_http_limit_conn_module.c000644 001751 001751 00000050151 14415135676 025122 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ #include #include #include #define NGX_HTTP_LIMIT_CONN_PASSED 1 #define NGX_HTTP_LIMIT_CONN_REJECTED 2 #define NGX_HTTP_LIMIT_CONN_REJECTED_DRY_RUN 3 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_rbtree_node_t sentinel; } ngx_http_limit_conn_shctx_t; typedef struct { ngx_http_limit_conn_shctx_t *sh; ngx_slab_pool_t *shpool; 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_flag_t dry_run; } 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 ngx_int_t ngx_http_limit_conn_status_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); 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_add_variables(ngx_conf_t *cf); 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_string("limit_conn_dry_run"), 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_limit_conn_conf_t, dry_run), NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_limit_conn_module_ctx = { ngx_http_limit_conn_add_variables, /* 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_http_variable_t ngx_http_limit_conn_vars[] = { { ngx_string("limit_conn_status"), NULL, ngx_http_limit_conn_status_variable, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, ngx_http_null_variable }; static ngx_str_t ngx_http_limit_conn_status[] = { ngx_string("PASSED"), ngx_string("REJECTED"), ngx_string("REJECTED_DRY_RUN") }; 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_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_status) { 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_status = NGX_HTTP_LIMIT_CONN_PASSED; hash = ngx_crc32_short(key.data, key.len); ngx_shmtx_lock(&ctx->shpool->mutex); node = ngx_http_limit_conn_lookup(&ctx->sh->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(ctx->shpool, n); if (node == NULL) { ngx_shmtx_unlock(&ctx->shpool->mutex); ngx_http_limit_conn_cleanup_all(r->pool); if (lccf->dry_run) { r->main->limit_conn_status = NGX_HTTP_LIMIT_CONN_REJECTED_DRY_RUN; return NGX_DECLINED; } r->main->limit_conn_status = NGX_HTTP_LIMIT_CONN_REJECTED; 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->sh->rbtree, node); } else { lc = (ngx_http_limit_conn_node_t *) &node->color; if ((ngx_uint_t) lc->conn >= limits[i].conn) { ngx_shmtx_unlock(&ctx->shpool->mutex); ngx_log_error(lccf->log_level, r->connection->log, 0, "limiting connections%s by zone \"%V\"", lccf->dry_run ? ", dry run," : "", &limits[i].shm_zone->shm.name); ngx_http_limit_conn_cleanup_all(r->pool); if (lccf->dry_run) { r->main->limit_conn_status = NGX_HTTP_LIMIT_CONN_REJECTED_DRY_RUN; return NGX_DECLINED; } r->main->limit_conn_status = NGX_HTTP_LIMIT_CONN_REJECTED; 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(&ctx->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_rbtree_node_t *node; ngx_http_limit_conn_ctx_t *ctx; ngx_http_limit_conn_node_t *lc; ctx = lccln->shm_zone->data; node = lccln->node; lc = (ngx_http_limit_conn_node_t *) &node->color; ngx_shmtx_lock(&ctx->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->sh->rbtree, node); ngx_slab_free_locked(ctx->shpool, node); } ngx_shmtx_unlock(&ctx->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_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->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_conn_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_conn_rbtree_insert_value); len = sizeof(" in limit_conn_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_conn_zone \"%V\"%Z", &shm_zone->shm.name); return NGX_OK; } static ngx_int_t ngx_http_limit_conn_status_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { if (r->main->limit_conn_status == 0) { v->not_found = 1; return NGX_OK; } v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->len = ngx_http_limit_conn_status[r->main->limit_conn_status - 1].len; v->data = ngx_http_limit_conn_status[r->main->limit_conn_status - 1].data; 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; conf->dry_run = NGX_CONF_UNSET; 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); ngx_conf_merge_value(conf->dry_run, prev->dry_run, 0); 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_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var, *v; for (v = ngx_http_limit_conn_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_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; } nginx-1.24.0/src/http/modules/ngx_http_limit_req_module.c000644 001751 001751 00000070220 14415135676 024753 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ #include #include #include #define NGX_HTTP_LIMIT_REQ_PASSED 1 #define NGX_HTTP_LIMIT_REQ_DELAYED 2 #define NGX_HTTP_LIMIT_REQ_REJECTED 3 #define NGX_HTTP_LIMIT_REQ_DELAYED_DRY_RUN 4 #define NGX_HTTP_LIMIT_REQ_REJECTED_DRY_RUN 5 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 delay; } 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_flag_t dry_run; } 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_unlock(ngx_http_limit_req_limit_t *limits, ngx_uint_t n); static void ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, ngx_uint_t n); static ngx_int_t ngx_http_limit_req_status_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); 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_add_variables(ngx_conf_t *cf); 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_string("limit_req_dry_run"), 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_limit_req_conf_t, dry_run), NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_limit_req_module_ctx = { ngx_http_limit_req_add_variables, /* 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_http_variable_t ngx_http_limit_req_vars[] = { { ngx_string("limit_req_status"), NULL, ngx_http_limit_req_status_variable, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, ngx_http_null_variable }; static ngx_str_t ngx_http_limit_req_status[] = { ngx_string("PASSED"), ngx_string("DELAYED"), ngx_string("REJECTED"), ngx_string("DELAYED_DRY_RUN"), ngx_string("REJECTED_DRY_RUN") }; 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_status) { 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) { ngx_http_limit_req_unlock(limits, n); 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; } if (rc == NGX_BUSY || rc == NGX_ERROR) { if (rc == NGX_BUSY) { ngx_log_error(lrcf->limit_log_level, r->connection->log, 0, "limiting requests%s, excess: %ui.%03ui by zone \"%V\"", lrcf->dry_run ? ", dry run" : "", excess / 1000, excess % 1000, &limit->shm_zone->shm.name); } ngx_http_limit_req_unlock(limits, n); if (lrcf->dry_run) { r->main->limit_req_status = NGX_HTTP_LIMIT_REQ_REJECTED_DRY_RUN; return NGX_DECLINED; } r->main->limit_req_status = NGX_HTTP_LIMIT_REQ_REJECTED; 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) { r->main->limit_req_status = NGX_HTTP_LIMIT_REQ_PASSED; return NGX_DECLINED; } ngx_log_error(lrcf->delay_log_level, r->connection->log, 0, "delaying request%s, excess: %ui.%03ui, by zone \"%V\"", lrcf->dry_run ? ", dry run" : "", excess / 1000, excess % 1000, &limit->shm_zone->shm.name); if (lrcf->dry_run) { r->main->limit_req_status = NGX_HTTP_LIMIT_REQ_DELAYED_DRY_RUN; return NGX_DECLINED; } r->main->limit_req_status = NGX_HTTP_LIMIT_REQ_DELAYED; if (r->connection->read->ready) { ngx_post_event(r->connection->read, &ngx_posted_events); } else { if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } } r->read_event_handler = ngx_http_test_reading; r->write_event_handler = ngx_http_limit_req_delay; r->connection->write->delayed = 1; ngx_add_timer(r->connection->write, delay); return NGX_AGAIN; } static void ngx_http_limit_req_delay(ngx_http_request_t *r) { ngx_event_t *wev; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "limit_req delay"); wev = r->connection->write; if (wev->delayed) { if (ngx_handle_write_event(wev, 0) != NGX_OK) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); } return; } if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } r->read_event_handler = ngx_http_block_reading; r->write_event_handler = ngx_http_core_run_phases; ngx_http_core_run_phases(r); } static void ngx_http_limit_req_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) { ngx_rbtree_node_t **p; ngx_http_limit_req_node_t *lrn, *lrnt; for ( ;; ) { if (node->key < temp->key) { p = &temp->left; } else if (node->key > temp->key) { p = &temp->right; } else { /* node->key == temp->key */ lrn = (ngx_http_limit_req_node_t *) &node->color; lrnt = (ngx_http_limit_req_node_t *) &temp->color; p = (ngx_memn2cmp(lrn->data, lrnt->data, lrn->len, lrnt->len) < 0) ? &temp->left : &temp->right; } if (*p == sentinel) { break; } temp = *p; } *p = node; node->parent = temp; node->left = sentinel; node->right = sentinel; ngx_rbt_red(node); } static ngx_int_t ngx_http_limit_req_lookup(ngx_http_limit_req_limit_t *limit, ngx_uint_t hash, ngx_str_t *key, ngx_uint_t *ep, ngx_uint_t account) { size_t size; ngx_int_t rc, excess; ngx_msec_t now; ngx_msec_int_t ms; ngx_rbtree_node_t *node, *sentinel; ngx_http_limit_req_ctx_t *ctx; ngx_http_limit_req_node_t *lr; now = ngx_current_msec; ctx = limit->shm_zone->data; node = ctx->sh->rbtree.root; sentinel = ctx->sh->rbtree.sentinel; while (node != sentinel) { if (hash < node->key) { node = node->left; continue; } if (hash > node->key) { node = node->right; continue; } /* hash == node->key */ lr = (ngx_http_limit_req_node_t *) &node->color; rc = ngx_memn2cmp(key->data, lr->data, key->len, (size_t) lr->len); if (rc == 0) { ngx_queue_remove(&lr->queue); ngx_queue_insert_head(&ctx->sh->queue, &lr->queue); ms = (ngx_msec_int_t) (now - lr->last); if (ms < -60000) { ms = 1; } else if (ms < 0) { ms = 0; } excess = lr->excess - ctx->rate * 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; if (ms) { lr->last = now; } return NGX_OK; } lr->count++; ctx->node = lr; return NGX_AGAIN; } node = (rc < 0) ? node->left : node->right; } *ep = 0; size = offsetof(ngx_rbtree_node_t, color) + offsetof(ngx_http_limit_req_node_t, data) + key->len; ngx_http_limit_req_expire(ctx, 1); node = ngx_slab_alloc_locked(ctx->shpool, size); if (node == NULL) { ngx_http_limit_req_expire(ctx, 0); node = ngx_slab_alloc_locked(ctx->shpool, size); if (node == NULL) { ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "could not allocate node%s", ctx->shpool->log_ctx); return NGX_ERROR; } } node->key = hash; lr = (ngx_http_limit_req_node_t *) &node->color; lr->len = (u_short) key->len; lr->excess = 0; ngx_memcpy(lr->data, key->data, key->len); ngx_rbtree_insert(&ctx->sh->rbtree, node); ngx_queue_insert_head(&ctx->sh->queue, &lr->queue); if (account) { lr->last = now; lr->count = 0; return NGX_OK; } lr->last = 0; lr->count = 1; ctx->node = lr; return NGX_AGAIN; } static ngx_msec_t ngx_http_limit_req_account(ngx_http_limit_req_limit_t *limits, ngx_uint_t n, ngx_uint_t *ep, ngx_http_limit_req_limit_t **limit) { ngx_int_t excess; ngx_msec_t now, delay, max_delay; ngx_msec_int_t ms; ngx_http_limit_req_ctx_t *ctx; ngx_http_limit_req_node_t *lr; excess = *ep; if ((ngx_uint_t) excess <= (*limit)->delay) { max_delay = 0; } else { ctx = (*limit)->shm_zone->data; max_delay = (excess - (*limit)->delay) * 1000 / ctx->rate; } while (n--) { ctx = limits[n].shm_zone->data; lr = ctx->node; if (lr == NULL) { continue; } ngx_shmtx_lock(&ctx->shpool->mutex); now = ngx_current_msec; ms = (ngx_msec_int_t) (now - lr->last); if (ms < -60000) { ms = 1; } else if (ms < 0) { ms = 0; } excess = lr->excess - ctx->rate * ms / 1000 + 1000; if (excess < 0) { excess = 0; } if (ms) { lr->last = now; } lr->excess = excess; lr->count--; ngx_shmtx_unlock(&ctx->shpool->mutex); ctx->node = NULL; if ((ngx_uint_t) excess <= limits[n].delay) { continue; } delay = (excess - limits[n].delay) * 1000 / ctx->rate; if (delay > max_delay) { max_delay = delay; *ep = excess; *limit = &limits[n]; } } return max_delay; } static void ngx_http_limit_req_unlock(ngx_http_limit_req_limit_t *limits, ngx_uint_t n) { ngx_http_limit_req_ctx_t *ctx; while (n--) { ctx = limits[n].shm_zone->data; if (ctx->node == NULL) { continue; } ngx_shmtx_lock(&ctx->shpool->mutex); ctx->node->count--; ngx_shmtx_unlock(&ctx->shpool->mutex); ctx->node = NULL; } } static void ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, ngx_uint_t n) { ngx_int_t excess; ngx_msec_t now; ngx_queue_t *q; ngx_msec_int_t ms; ngx_rbtree_node_t *node; ngx_http_limit_req_node_t *lr; now = ngx_current_msec; /* * n == 1 deletes one or two zero rate entries * n == 0 deletes oldest entry by force * and one or two zero rate entries */ while (n < 3) { if (ngx_queue_empty(&ctx->sh->queue)) { return; } q = ngx_queue_last(&ctx->sh->queue); lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue); if (lr->count) { /* * There is not much sense in looking further, * because we bump nodes on the lookup stage. */ return; } if (n++ != 0) { ms = (ngx_msec_int_t) (now - lr->last); ms = ngx_abs(ms); if (ms < 60000) { return; } excess = lr->excess - ctx->rate * ms / 1000; if (excess > 0) { return; } } ngx_queue_remove(q); node = (ngx_rbtree_node_t *) ((u_char *) lr - offsetof(ngx_rbtree_node_t, color)); ngx_rbtree_delete(&ctx->sh->rbtree, node); ngx_slab_free_locked(ctx->shpool, node); } } static ngx_int_t ngx_http_limit_req_init_zone(ngx_shm_zone_t *shm_zone, void *data) { ngx_http_limit_req_ctx_t *octx = data; size_t len; ngx_http_limit_req_ctx_t *ctx; ctx = shm_zone->data; if (octx) { if (ctx->key.value.len != octx->key.value.len || ngx_strncmp(ctx->key.value.data, octx->key.value.data, ctx->key.value.len) != 0) { ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0, "limit_req \"%V\" uses the \"%V\" key " "while previously it used the \"%V\" key", &shm_zone->shm.name, &ctx->key.value, &octx->key.value); return NGX_ERROR; } ctx->sh = octx->sh; ctx->shpool = octx->shpool; return NGX_OK; } ctx->shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; if (shm_zone->shm.exists) { ctx->sh = ctx->shpool->data; return NGX_OK; } ctx->sh = ngx_slab_alloc(ctx->shpool, sizeof(ngx_http_limit_req_shctx_t)); if (ctx->sh == NULL) { return NGX_ERROR; } ctx->shpool->data = ctx->sh; ngx_rbtree_init(&ctx->sh->rbtree, &ctx->sh->sentinel, ngx_http_limit_req_rbtree_insert_value); ngx_queue_init(&ctx->sh->queue); len = sizeof(" in limit_req zone \"\"") + shm_zone->shm.name.len; ctx->shpool->log_ctx = ngx_slab_alloc(ctx->shpool, len); if (ctx->shpool->log_ctx == NULL) { return NGX_ERROR; } ngx_sprintf(ctx->shpool->log_ctx, " in limit_req zone \"%V\"%Z", &shm_zone->shm.name); ctx->shpool->log_nomem = 0; return NGX_OK; } static ngx_int_t ngx_http_limit_req_status_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { if (r->main->limit_req_status == 0) { v->not_found = 1; return NGX_OK; } v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->len = ngx_http_limit_req_status[r->main->limit_req_status - 1].len; v->data = ngx_http_limit_req_status[r->main->limit_req_status - 1].data; 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; conf->dry_run = NGX_CONF_UNSET; 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); ngx_conf_merge_value(conf->dry_run, prev->dry_run, 0); 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, delay; ngx_str_t *value, s; ngx_uint_t i; ngx_shm_zone_t *shm_zone; ngx_http_limit_req_limit_t *limit, *limits; value = cf->args->elts; shm_zone = NULL; burst = 0; delay = 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 value \"%V\"", &value[i]); return NGX_CONF_ERROR; } continue; } if (ngx_strncmp(value[i].data, "delay=", 6) == 0) { delay = ngx_atoi(value[i].data + 6, value[i].len - 6); if (delay <= 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid delay value \"%V\"", &value[i]); return NGX_CONF_ERROR; } continue; } if (ngx_strcmp(value[i].data, "nodelay") == 0) { delay = NGX_MAX_INT_T_VALUE / 1000; 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->delay = delay * 1000; return NGX_CONF_OK; } static ngx_int_t ngx_http_limit_req_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var, *v; for (v = ngx_http_limit_req_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_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; } nginx-1.24.0/src/http/modules/ngx_http_log_module.c000644 001751 001751 00000141155 14415135676 023555 0ustar00mdouninmdounin000000 000000 /* * 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; #define NGX_HTTP_LOG_ESCAPE_DEFAULT 0 #define NGX_HTTP_LOG_ESCAPE_JSON 1 #define NGX_HTTP_LOG_ESCAPE_NONE 2 static void ngx_http_log_write(ngx_http_request_t *r, ngx_http_log_t *log, u_char *buf, size_t len); static ssize_t ngx_http_log_script_write(ngx_http_request_t *r, ngx_http_log_script_t *script, u_char **name, u_char *buf, size_t len); #if (NGX_ZLIB) static ssize_t ngx_http_log_gzip(ngx_fd_t fd, u_char *buf, size_t len, ngx_int_t level, ngx_log_t *log); static void *ngx_http_log_gzip_alloc(void *opaque, u_int items, u_int size); static void ngx_http_log_gzip_free(void *opaque, void *address); #endif static void ngx_http_log_flush(ngx_open_file_t *file, ngx_log_t *log); static void ngx_http_log_flush_handler(ngx_event_t *ev); static u_char *ngx_http_log_pipe(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static u_char *ngx_http_log_time(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static u_char *ngx_http_log_iso8601(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static u_char *ngx_http_log_msec(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static u_char *ngx_http_log_request_time(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static u_char *ngx_http_log_status(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static u_char *ngx_http_log_bytes_sent(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static u_char *ngx_http_log_body_bytes_sent(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static u_char *ngx_http_log_request_length(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static ngx_int_t ngx_http_log_variable_compile(ngx_conf_t *cf, ngx_http_log_op_t *op, ngx_str_t *value, ngx_uint_t escape); static size_t ngx_http_log_variable_getlen(ngx_http_request_t *r, uintptr_t data); static u_char *ngx_http_log_variable(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static uintptr_t ngx_http_log_escape(u_char *dst, u_char *src, size_t size); static size_t ngx_http_log_json_variable_getlen(ngx_http_request_t *r, uintptr_t data); static u_char *ngx_http_log_json_variable(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static size_t ngx_http_log_unescaped_variable_getlen(ngx_http_request_t *r, uintptr_t data); static u_char *ngx_http_log_unescaped_variable(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static void *ngx_http_log_create_main_conf(ngx_conf_t *cf); static void *ngx_http_log_create_loc_conf(ngx_conf_t *cf); static char *ngx_http_log_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); static char *ngx_http_log_set_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_log_set_format(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_log_compile_format(ngx_conf_t *cf, ngx_array_t *flushes, ngx_array_t *ops, ngx_array_t *args, ngx_uint_t s); static char *ngx_http_log_open_file_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_int_t ngx_http_log_init(ngx_conf_t *cf); static ngx_command_t ngx_http_log_commands[] = { { ngx_string("log_format"), NGX_HTTP_MAIN_CONF|NGX_CONF_2MORE, ngx_http_log_set_format, NGX_HTTP_MAIN_CONF_OFFSET, 0, NULL }, { ngx_string("access_log"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF |NGX_HTTP_LMT_CONF|NGX_CONF_1MORE, ngx_http_log_set_log, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("open_log_file_cache"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234, ngx_http_log_open_file_cache, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_log_module_ctx = { NULL, /* preconfiguration */ ngx_http_log_init, /* postconfiguration */ ngx_http_log_create_main_conf, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_log_create_loc_conf, /* create location configuration */ ngx_http_log_merge_loc_conf /* merge location configuration */ }; ngx_module_t ngx_http_log_module = { NGX_MODULE_V1, &ngx_http_log_module_ctx, /* module context */ ngx_http_log_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_str_t ngx_http_access_log = ngx_string(NGX_HTTP_LOG_PATH); static ngx_str_t ngx_http_combined_fmt = ngx_string("$remote_addr - $remote_user [$time_local] " "\"$request\" $status $body_bytes_sent " "\"$http_referer\" \"$http_user_agent\""); static ngx_http_log_var_t ngx_http_log_vars[] = { { ngx_string("pipe"), 1, ngx_http_log_pipe }, { ngx_string("time_local"), sizeof("28/Sep/1970:12:00:00 +0600") - 1, ngx_http_log_time }, { ngx_string("time_iso8601"), sizeof("1970-09-28T12:00:00+06:00") - 1, ngx_http_log_iso8601 }, { ngx_string("msec"), NGX_TIME_T_LEN + 4, ngx_http_log_msec }, { ngx_string("request_time"), NGX_TIME_T_LEN + 4, ngx_http_log_request_time }, { ngx_string("status"), NGX_INT_T_LEN, ngx_http_log_status }, { ngx_string("bytes_sent"), NGX_OFF_T_LEN, ngx_http_log_bytes_sent }, { ngx_string("body_bytes_sent"), NGX_OFF_T_LEN, ngx_http_log_body_bytes_sent }, { ngx_string("request_length"), NGX_SIZE_T_LEN, ngx_http_log_request_length }, { ngx_null_string, 0, NULL } }; static ngx_int_t ngx_http_log_handler(ngx_http_request_t *r) { u_char *line, *p; size_t len, size; ssize_t n; ngx_str_t val; ngx_uint_t i, l; ngx_http_log_t *log; ngx_http_log_op_t *op; ngx_http_log_buf_t *buffer; ngx_http_log_loc_conf_t *lcf; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http log handler"); lcf = ngx_http_get_module_loc_conf(r, ngx_http_log_module); if (lcf->off) { return NGX_OK; } log = lcf->logs->elts; for (l = 0; l < lcf->logs->nelts; l++) { if (log[l].filter) { if (ngx_http_complex_value(r, log[l].filter, &val) != NGX_OK) { return NGX_ERROR; } if (val.len == 0 || (val.len == 1 && val.data[0] == '0')) { continue; } } if (ngx_time() == log[l].disk_full_time) { /* * on FreeBSD writing to a full filesystem with enabled softupdates * may block process for much longer time than writing to non-full * filesystem, so we skip writing to a log for one second */ continue; } ngx_http_script_flush_no_cacheable_variables(r, log[l].format->flushes); len = 0; op = log[l].format->ops->elts; for (i = 0; i < log[l].format->ops->nelts; i++) { if (op[i].len == 0) { len += op[i].getlen(r, op[i].data); } else { len += op[i].len; } } if (log[l].syslog_peer) { /* length of syslog's PRI and HEADER message parts */ len += sizeof("<255>Jan 01 00:00:00 ") - 1 + ngx_cycle->hostname.len + 1 + log[l].syslog_peer->tag.len + 2; goto alloc_line; } len += NGX_LINEFEED_SIZE; buffer = log[l].file ? log[l].file->data : NULL; if (buffer) { if (len > (size_t) (buffer->last - buffer->pos)) { ngx_http_log_write(r, &log[l], buffer->start, buffer->pos - buffer->start); buffer->pos = buffer->start; } if (len <= (size_t) (buffer->last - buffer->pos)) { p = buffer->pos; if (buffer->event && p == buffer->start) { ngx_add_timer(buffer->event, buffer->flush); } for (i = 0; i < log[l].format->ops->nelts; i++) { p = op[i].run(r, p, &op[i]); } ngx_linefeed(p); buffer->pos = p; continue; } if (buffer->event && buffer->event->timer_set) { ngx_del_timer(buffer->event); } } alloc_line: line = ngx_pnalloc(r->pool, len); if (line == NULL) { return NGX_ERROR; } p = line; if (log[l].syslog_peer) { p = ngx_syslog_add_header(log[l].syslog_peer, line); } for (i = 0; i < log[l].format->ops->nelts; i++) { p = op[i].run(r, p, &op[i]); } if (log[l].syslog_peer) { size = p - line; n = ngx_syslog_send(log[l].syslog_peer, line, size); if (n < 0) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "send() to syslog failed"); } else if ((size_t) n != size) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "send() to syslog has written only %z of %uz", n, size); } continue; } ngx_linefeed(p); ngx_http_log_write(r, &log[l], line, p - line); } return NGX_OK; } static void ngx_http_log_write(ngx_http_request_t *r, ngx_http_log_t *log, u_char *buf, size_t len) { u_char *name; time_t now; ssize_t n; ngx_err_t err; #if (NGX_ZLIB) ngx_http_log_buf_t *buffer; #endif if (log->script == NULL) { name = log->file->name.data; #if (NGX_ZLIB) buffer = log->file->data; if (buffer && buffer->gzip) { n = ngx_http_log_gzip(log->file->fd, buf, len, buffer->gzip, r->connection->log); } else { n = ngx_write_fd(log->file->fd, buf, len); } #else n = ngx_write_fd(log->file->fd, buf, len); #endif } else { name = NULL; n = ngx_http_log_script_write(r, log->script, &name, buf, len); } if (n == (ssize_t) len) { return; } now = ngx_time(); if (n == -1) { err = ngx_errno; if (err == NGX_ENOSPC) { log->disk_full_time = now; } if (now - log->error_log_time > 59) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, err, ngx_write_fd_n " to \"%s\" failed", name); log->error_log_time = now; } return; } if (now - log->error_log_time > 59) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, ngx_write_fd_n " to \"%s\" was incomplete: %z of %uz", name, n, len); log->error_log_time = now; } } static ssize_t ngx_http_log_script_write(ngx_http_request_t *r, ngx_http_log_script_t *script, u_char **name, u_char *buf, size_t len) { size_t root; ssize_t n; ngx_str_t log, path; ngx_open_file_info_t of; ngx_http_log_loc_conf_t *llcf; ngx_http_core_loc_conf_t *clcf; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (!r->root_tested) { /* test root directory existence */ if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) { /* simulate successful logging */ return len; } path.data[root] = '\0'; ngx_memzero(&of, sizeof(ngx_open_file_info_t)); of.valid = clcf->open_file_cache_valid; of.min_uses = clcf->open_file_cache_min_uses; of.test_dir = 1; of.test_only = 1; of.errors = clcf->open_file_cache_errors; of.events = clcf->open_file_cache_events; if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) { /* simulate successful logging */ return len; } if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool) != NGX_OK) { if (of.err == 0) { /* simulate successful logging */ return len; } ngx_log_error(NGX_LOG_ERR, r->connection->log, of.err, "testing \"%s\" existence failed", path.data); /* simulate successful logging */ return len; } if (!of.is_dir) { ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_ENOTDIR, "testing \"%s\" existence failed", path.data); /* simulate successful logging */ return len; } } if (ngx_http_script_run(r, &log, script->lengths->elts, 1, script->values->elts) == NULL) { /* simulate successful logging */ return len; } log.data[log.len - 1] = '\0'; *name = log.data; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http log \"%s\"", log.data); llcf = ngx_http_get_module_loc_conf(r, ngx_http_log_module); ngx_memzero(&of, sizeof(ngx_open_file_info_t)); of.log = 1; of.valid = llcf->open_file_cache_valid; of.min_uses = llcf->open_file_cache_min_uses; of.directio = NGX_OPEN_FILE_DIRECTIO_OFF; if (ngx_http_set_disable_symlinks(r, clcf, &log, &of) != NGX_OK) { /* simulate successful logging */ return len; } if (ngx_open_cached_file(llcf->open_file_cache, &log, &of, r->pool) != NGX_OK) { if (of.err == 0) { /* simulate successful logging */ return len; } ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno, "%s \"%s\" failed", of.failed, log.data); /* simulate successful logging */ return len; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http log #%d", of.fd); n = ngx_write_fd(of.fd, buf, len); return n; } #if (NGX_ZLIB) static ssize_t ngx_http_log_gzip(ngx_fd_t fd, u_char *buf, size_t len, ngx_int_t level, ngx_log_t *log) { int rc, wbits, memlevel; u_char *out; size_t size; ssize_t n; z_stream zstream; ngx_err_t err; ngx_pool_t *pool; wbits = MAX_WBITS; memlevel = MAX_MEM_LEVEL - 1; while ((ssize_t) len < ((1 << (wbits - 1)) - 262)) { wbits--; memlevel--; } /* * This is a formula from deflateBound() for conservative upper bound of * compressed data plus 18 bytes of gzip wrapper. */ size = len + ((len + 7) >> 3) + ((len + 63) >> 6) + 5 + 18; ngx_memzero(&zstream, sizeof(z_stream)); pool = ngx_create_pool(256, log); if (pool == NULL) { /* simulate successful logging */ return len; } pool->log = log; zstream.zalloc = ngx_http_log_gzip_alloc; zstream.zfree = ngx_http_log_gzip_free; zstream.opaque = pool; out = ngx_pnalloc(pool, size); if (out == NULL) { goto done; } zstream.next_in = buf; zstream.avail_in = len; zstream.next_out = out; zstream.avail_out = size; rc = deflateInit2(&zstream, (int) level, Z_DEFLATED, wbits + 16, memlevel, Z_DEFAULT_STRATEGY); if (rc != Z_OK) { ngx_log_error(NGX_LOG_ALERT, log, 0, "deflateInit2() failed: %d", rc); goto done; } ngx_log_debug4(NGX_LOG_DEBUG_HTTP, log, 0, "deflate in: ni:%p no:%p ai:%ud ao:%ud", zstream.next_in, zstream.next_out, zstream.avail_in, zstream.avail_out); rc = deflate(&zstream, Z_FINISH); if (rc != Z_STREAM_END) { ngx_log_error(NGX_LOG_ALERT, log, 0, "deflate(Z_FINISH) failed: %d", rc); goto done; } ngx_log_debug5(NGX_LOG_DEBUG_HTTP, log, 0, "deflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d", zstream.next_in, zstream.next_out, zstream.avail_in, zstream.avail_out, rc); size -= zstream.avail_out; rc = deflateEnd(&zstream); if (rc != Z_OK) { ngx_log_error(NGX_LOG_ALERT, log, 0, "deflateEnd() failed: %d", rc); goto done; } n = ngx_write_fd(fd, out, size); if (n != (ssize_t) size) { err = (n == -1) ? ngx_errno : 0; ngx_destroy_pool(pool); ngx_set_errno(err); return -1; } done: ngx_destroy_pool(pool); /* simulate successful logging */ return len; } static void * ngx_http_log_gzip_alloc(void *opaque, u_int items, u_int size) { ngx_pool_t *pool = opaque; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pool->log, 0, "gzip alloc: n:%ud s:%ud", items, size); return ngx_palloc(pool, items * size); } static void ngx_http_log_gzip_free(void *opaque, void *address) { #if 0 ngx_pool_t *pool = opaque; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pool->log, 0, "gzip free: %p", address); #endif } #endif static void ngx_http_log_flush(ngx_open_file_t *file, ngx_log_t *log) { size_t len; ssize_t n; ngx_http_log_buf_t *buffer; buffer = file->data; len = buffer->pos - buffer->start; if (len == 0) { return; } #if (NGX_ZLIB) if (buffer->gzip) { n = ngx_http_log_gzip(file->fd, buffer->start, len, buffer->gzip, log); } else { n = ngx_write_fd(file->fd, buffer->start, len); } #else n = ngx_write_fd(file->fd, buffer->start, len); #endif if (n == -1) { ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, ngx_write_fd_n " to \"%s\" failed", file->name.data); } else if ((size_t) n != len) { ngx_log_error(NGX_LOG_ALERT, log, 0, ngx_write_fd_n " to \"%s\" was incomplete: %z of %uz", file->name.data, n, len); } buffer->pos = buffer->start; if (buffer->event && buffer->event->timer_set) { ngx_del_timer(buffer->event); } } static void ngx_http_log_flush_handler(ngx_event_t *ev) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "http log buffer flush handler"); ngx_http_log_flush(ev->data, ev->log); } static u_char * ngx_http_log_copy_short(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) { size_t len; uintptr_t data; len = op->len; data = op->data; while (len--) { *buf++ = (u_char) (data & 0xff); data >>= 8; } return buf; } static u_char * ngx_http_log_copy_long(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) { return ngx_cpymem(buf, (u_char *) op->data, op->len); } static u_char * ngx_http_log_pipe(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) { if (r->pipeline) { *buf = 'p'; } else { *buf = '.'; } return buf + 1; } static u_char * ngx_http_log_time(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) { return ngx_cpymem(buf, ngx_cached_http_log_time.data, ngx_cached_http_log_time.len); } static u_char * ngx_http_log_iso8601(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) { return ngx_cpymem(buf, ngx_cached_http_log_iso8601.data, ngx_cached_http_log_iso8601.len); } static u_char * ngx_http_log_msec(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) { ngx_time_t *tp; tp = ngx_timeofday(); return ngx_sprintf(buf, "%T.%03M", tp->sec, tp->msec); } static u_char * ngx_http_log_request_time(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) { ngx_time_t *tp; ngx_msec_int_t ms; tp = ngx_timeofday(); ms = (ngx_msec_int_t) ((tp->sec - r->start_sec) * 1000 + (tp->msec - r->start_msec)); ms = ngx_max(ms, 0); return ngx_sprintf(buf, "%T.%03M", (time_t) ms / 1000, ms % 1000); } static u_char * ngx_http_log_status(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) { ngx_uint_t status; if (r->err_status) { status = r->err_status; } else if (r->headers_out.status) { status = r->headers_out.status; } else if (r->http_version == NGX_HTTP_VERSION_9) { status = 9; } else { status = 0; } return ngx_sprintf(buf, "%03ui", status); } static u_char * ngx_http_log_bytes_sent(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) { return ngx_sprintf(buf, "%O", r->connection->sent); } /* * although there is a real $body_bytes_sent variable, * this log operation code function is more optimized for logging */ static u_char * ngx_http_log_body_bytes_sent(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) { off_t length; length = r->connection->sent - r->header_size; if (length > 0) { return ngx_sprintf(buf, "%O", length); } *buf = '0'; return buf + 1; } static u_char * ngx_http_log_request_length(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) { return ngx_sprintf(buf, "%O", r->request_length); } static ngx_int_t ngx_http_log_variable_compile(ngx_conf_t *cf, ngx_http_log_op_t *op, ngx_str_t *value, ngx_uint_t escape) { ngx_int_t index; index = ngx_http_get_variable_index(cf, value); if (index == NGX_ERROR) { return NGX_ERROR; } op->len = 0; switch (escape) { case NGX_HTTP_LOG_ESCAPE_JSON: op->getlen = ngx_http_log_json_variable_getlen; op->run = ngx_http_log_json_variable; break; case NGX_HTTP_LOG_ESCAPE_NONE: op->getlen = ngx_http_log_unescaped_variable_getlen; op->run = ngx_http_log_unescaped_variable; break; default: /* NGX_HTTP_LOG_ESCAPE_DEFAULT */ op->getlen = ngx_http_log_variable_getlen; op->run = ngx_http_log_variable; } op->data = index; return NGX_OK; } static size_t ngx_http_log_variable_getlen(ngx_http_request_t *r, uintptr_t data) { uintptr_t len; ngx_http_variable_value_t *value; value = ngx_http_get_indexed_variable(r, data); if (value == NULL || value->not_found) { return 1; } len = ngx_http_log_escape(NULL, value->data, value->len); value->escape = len ? 1 : 0; return value->len + len * 3; } static u_char * ngx_http_log_variable(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) { ngx_http_variable_value_t *value; value = ngx_http_get_indexed_variable(r, op->data); if (value == NULL || value->not_found) { *buf = '-'; return buf + 1; } if (value->escape == 0) { return ngx_cpymem(buf, value->data, value->len); } else { return (u_char *) ngx_http_log_escape(buf, value->data, value->len); } } static uintptr_t ngx_http_log_escape(u_char *dst, u_char *src, size_t size) { ngx_uint_t n; static u_char hex[] = "0123456789ABCDEF"; static uint32_t escape[] = { 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ 0x00000004, /* 0000 0000 0000 0000 0000 0000 0000 0100 */ /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ 0x10000000, /* 0001 0000 0000 0000 0000 0000 0000 0000 */ /* ~}| {zyx wvut srqp onml kjih gfed cba` */ 0x80000000, /* 1000 0000 0000 0000 0000 0000 0000 0000 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ }; if (dst == NULL) { /* find the number of the characters to be escaped */ n = 0; while (size) { if (escape[*src >> 5] & (1U << (*src & 0x1f))) { n++; } src++; size--; } return (uintptr_t) n; } while (size) { if (escape[*src >> 5] & (1U << (*src & 0x1f))) { *dst++ = '\\'; *dst++ = 'x'; *dst++ = hex[*src >> 4]; *dst++ = hex[*src & 0xf]; src++; } else { *dst++ = *src++; } size--; } return (uintptr_t) dst; } static size_t ngx_http_log_json_variable_getlen(ngx_http_request_t *r, uintptr_t data) { uintptr_t len; ngx_http_variable_value_t *value; value = ngx_http_get_indexed_variable(r, data); if (value == NULL || value->not_found) { return 0; } len = ngx_escape_json(NULL, value->data, value->len); value->escape = len ? 1 : 0; return value->len + len; } static u_char * ngx_http_log_json_variable(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) { ngx_http_variable_value_t *value; value = ngx_http_get_indexed_variable(r, op->data); if (value == NULL || value->not_found) { return buf; } if (value->escape == 0) { return ngx_cpymem(buf, value->data, value->len); } else { return (u_char *) ngx_escape_json(buf, value->data, value->len); } } static size_t ngx_http_log_unescaped_variable_getlen(ngx_http_request_t *r, uintptr_t data) { ngx_http_variable_value_t *value; value = ngx_http_get_indexed_variable(r, data); if (value == NULL || value->not_found) { return 0; } value->escape = 0; return value->len; } static u_char * ngx_http_log_unescaped_variable(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) { ngx_http_variable_value_t *value; value = ngx_http_get_indexed_variable(r, op->data); if (value == NULL || value->not_found) { return buf; } return ngx_cpymem(buf, value->data, value->len); } static void * ngx_http_log_create_main_conf(ngx_conf_t *cf) { ngx_http_log_main_conf_t *conf; ngx_http_log_fmt_t *fmt; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_log_main_conf_t)); if (conf == NULL) { return NULL; } if (ngx_array_init(&conf->formats, cf->pool, 4, sizeof(ngx_http_log_fmt_t)) != NGX_OK) { return NULL; } fmt = ngx_array_push(&conf->formats); if (fmt == NULL) { return NULL; } ngx_str_set(&fmt->name, "combined"); fmt->flushes = NULL; fmt->ops = ngx_array_create(cf->pool, 16, sizeof(ngx_http_log_op_t)); if (fmt->ops == NULL) { return NULL; } return conf; } static void * ngx_http_log_create_loc_conf(ngx_conf_t *cf) { ngx_http_log_loc_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_log_loc_conf_t)); if (conf == NULL) { return NULL; } conf->open_file_cache = NGX_CONF_UNSET_PTR; return conf; } static char * ngx_http_log_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_log_loc_conf_t *prev = parent; ngx_http_log_loc_conf_t *conf = child; ngx_http_log_t *log; ngx_http_log_fmt_t *fmt; ngx_http_log_main_conf_t *lmcf; if (conf->open_file_cache == NGX_CONF_UNSET_PTR) { conf->open_file_cache = prev->open_file_cache; conf->open_file_cache_valid = prev->open_file_cache_valid; conf->open_file_cache_min_uses = prev->open_file_cache_min_uses; if (conf->open_file_cache == NGX_CONF_UNSET_PTR) { conf->open_file_cache = NULL; } } if (conf->logs || conf->off) { return NGX_CONF_OK; } conf->logs = prev->logs; conf->off = prev->off; if (conf->logs || conf->off) { return NGX_CONF_OK; } conf->logs = ngx_array_create(cf->pool, 2, sizeof(ngx_http_log_t)); if (conf->logs == NULL) { return NGX_CONF_ERROR; } log = ngx_array_push(conf->logs); if (log == NULL) { return NGX_CONF_ERROR; } ngx_memzero(log, sizeof(ngx_http_log_t)); log->file = ngx_conf_open_file(cf->cycle, &ngx_http_access_log); if (log->file == NULL) { return NGX_CONF_ERROR; } lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_log_module); fmt = lmcf->formats.elts; /* the default "combined" format */ log->format = &fmt[0]; lmcf->combined_used = 1; return NGX_CONF_OK; } static char * ngx_http_log_set_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_log_loc_conf_t *llcf = conf; ssize_t size; ngx_int_t gzip; ngx_uint_t i, n; ngx_msec_t flush; ngx_str_t *value, name, s; ngx_http_log_t *log; ngx_syslog_peer_t *peer; ngx_http_log_buf_t *buffer; ngx_http_log_fmt_t *fmt; ngx_http_log_main_conf_t *lmcf; ngx_http_script_compile_t sc; ngx_http_compile_complex_value_t ccv; value = cf->args->elts; if (ngx_strcmp(value[1].data, "off") == 0) { llcf->off = 1; if (cf->args->nelts == 2) { return NGX_CONF_OK; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[2]); return NGX_CONF_ERROR; } if (llcf->logs == NULL) { llcf->logs = ngx_array_create(cf->pool, 2, sizeof(ngx_http_log_t)); if (llcf->logs == NULL) { return NGX_CONF_ERROR; } } lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_log_module); log = ngx_array_push(llcf->logs); if (log == NULL) { return NGX_CONF_ERROR; } ngx_memzero(log, sizeof(ngx_http_log_t)); if (ngx_strncmp(value[1].data, "syslog:", 7) == 0) { peer = ngx_pcalloc(cf->pool, sizeof(ngx_syslog_peer_t)); if (peer == NULL) { return NGX_CONF_ERROR; } if (ngx_syslog_process_conf(cf, peer) != NGX_CONF_OK) { return NGX_CONF_ERROR; } log->syslog_peer = peer; goto process_formats; } n = ngx_http_script_variables_count(&value[1]); if (n == 0) { log->file = ngx_conf_open_file(cf->cycle, &value[1]); if (log->file == NULL) { return NGX_CONF_ERROR; } } else { if (ngx_conf_full_name(cf->cycle, &value[1], 0) != NGX_OK) { return NGX_CONF_ERROR; } log->script = ngx_pcalloc(cf->pool, sizeof(ngx_http_log_script_t)); if (log->script == NULL) { return NGX_CONF_ERROR; } ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); sc.cf = cf; sc.source = &value[1]; sc.lengths = &log->script->lengths; sc.values = &log->script->values; sc.variables = n; sc.complete_lengths = 1; sc.complete_values = 1; if (ngx_http_script_compile(&sc) != NGX_OK) { return NGX_CONF_ERROR; } } process_formats: if (cf->args->nelts >= 3) { name = value[2]; if (ngx_strcmp(name.data, "combined") == 0) { lmcf->combined_used = 1; } } else { ngx_str_set(&name, "combined"); lmcf->combined_used = 1; } fmt = lmcf->formats.elts; for (i = 0; i < lmcf->formats.nelts; i++) { if (fmt[i].name.len == name.len && ngx_strcasecmp(fmt[i].name.data, name.data) == 0) { log->format = &fmt[i]; break; } } if (log->format == NULL) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unknown log format \"%V\"", &name); return NGX_CONF_ERROR; } size = 0; flush = 0; gzip = 0; for (i = 3; i < cf->args->nelts; i++) { if (ngx_strncmp(value[i].data, "buffer=", 7) == 0) { s.len = value[i].len - 7; s.data = value[i].data + 7; size = ngx_parse_size(&s); if (size == NGX_ERROR || size == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid buffer size \"%V\"", &s); return NGX_CONF_ERROR; } continue; } if (ngx_strncmp(value[i].data, "flush=", 6) == 0) { s.len = value[i].len - 6; s.data = value[i].data + 6; flush = ngx_parse_time(&s, 0); if (flush == (ngx_msec_t) NGX_ERROR || flush == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid flush time \"%V\"", &s); return NGX_CONF_ERROR; } continue; } if (ngx_strncmp(value[i].data, "gzip", 4) == 0 && (value[i].len == 4 || value[i].data[4] == '=')) { #if (NGX_ZLIB) if (size == 0) { size = 64 * 1024; } if (value[i].len == 4) { gzip = Z_BEST_SPEED; continue; } s.len = value[i].len - 5; s.data = value[i].data + 5; gzip = ngx_atoi(s.data, s.len); if (gzip < 1 || gzip > 9) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid compression level \"%V\"", &s); return NGX_CONF_ERROR; } continue; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "nginx was built without zlib support"); return NGX_CONF_ERROR; #endif } if (ngx_strncmp(value[i].data, "if=", 3) == 0) { s.len = value[i].len - 3; s.data = value[i].data + 3; ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &s; ccv.complex_value = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); if (ccv.complex_value == NULL) { return NGX_CONF_ERROR; } if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } log->filter = ccv.complex_value; continue; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[i]); return NGX_CONF_ERROR; } if (flush && size == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "no buffer is defined for access_log \"%V\"", &value[1]); return NGX_CONF_ERROR; } if (size) { if (log->script) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "buffered logs cannot have variables in name"); return NGX_CONF_ERROR; } if (log->syslog_peer) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "logs to syslog cannot be buffered"); return NGX_CONF_ERROR; } if (log->file->data) { buffer = log->file->data; if (buffer->last - buffer->start != size || buffer->flush != flush || buffer->gzip != gzip) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "access_log \"%V\" already defined " "with conflicting parameters", &value[1]); return NGX_CONF_ERROR; } return NGX_CONF_OK; } buffer = ngx_pcalloc(cf->pool, sizeof(ngx_http_log_buf_t)); if (buffer == NULL) { return NGX_CONF_ERROR; } buffer->start = ngx_pnalloc(cf->pool, size); if (buffer->start == NULL) { return NGX_CONF_ERROR; } buffer->pos = buffer->start; buffer->last = buffer->start + size; if (flush) { buffer->event = ngx_pcalloc(cf->pool, sizeof(ngx_event_t)); if (buffer->event == NULL) { return NGX_CONF_ERROR; } buffer->event->data = log->file; buffer->event->handler = ngx_http_log_flush_handler; buffer->event->log = &cf->cycle->new_log; buffer->event->cancelable = 1; buffer->flush = flush; } buffer->gzip = gzip; log->file->flush = ngx_http_log_flush; log->file->data = buffer; } return NGX_CONF_OK; } static char * ngx_http_log_set_format(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_log_main_conf_t *lmcf = conf; ngx_str_t *value; ngx_uint_t i; ngx_http_log_fmt_t *fmt; value = cf->args->elts; fmt = lmcf->formats.elts; for (i = 0; i < lmcf->formats.nelts; i++) { if (fmt[i].name.len == value[1].len && ngx_strcmp(fmt[i].name.data, value[1].data) == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "duplicate \"log_format\" name \"%V\"", &value[1]); return NGX_CONF_ERROR; } } fmt = ngx_array_push(&lmcf->formats); if (fmt == NULL) { return NGX_CONF_ERROR; } fmt->name = value[1]; fmt->flushes = ngx_array_create(cf->pool, 4, sizeof(ngx_int_t)); if (fmt->flushes == NULL) { return NGX_CONF_ERROR; } fmt->ops = ngx_array_create(cf->pool, 16, sizeof(ngx_http_log_op_t)); if (fmt->ops == NULL) { return NGX_CONF_ERROR; } return ngx_http_log_compile_format(cf, fmt->flushes, fmt->ops, cf->args, 2); } static char * ngx_http_log_compile_format(ngx_conf_t *cf, ngx_array_t *flushes, ngx_array_t *ops, ngx_array_t *args, ngx_uint_t s) { u_char *data, *p, ch; size_t i, len; ngx_str_t *value, var; ngx_int_t *flush; ngx_uint_t bracket, escape; ngx_http_log_op_t *op; ngx_http_log_var_t *v; escape = NGX_HTTP_LOG_ESCAPE_DEFAULT; value = args->elts; if (s < args->nelts && ngx_strncmp(value[s].data, "escape=", 7) == 0) { data = value[s].data + 7; if (ngx_strcmp(data, "json") == 0) { escape = NGX_HTTP_LOG_ESCAPE_JSON; } else if (ngx_strcmp(data, "none") == 0) { escape = NGX_HTTP_LOG_ESCAPE_NONE; } else if (ngx_strcmp(data, "default") != 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unknown log format escaping \"%s\"", data); return NGX_CONF_ERROR; } s++; } for ( /* void */ ; s < args->nelts; s++) { i = 0; while (i < value[s].len) { op = ngx_array_push(ops); if (op == NULL) { return NGX_CONF_ERROR; } data = &value[s].data[i]; if (value[s].data[i] == '$') { if (++i == value[s].len) { goto invalid; } if (value[s].data[i] == '{') { bracket = 1; if (++i == value[s].len) { goto invalid; } var.data = &value[s].data[i]; } else { bracket = 0; var.data = &value[s].data[i]; } for (var.len = 0; i < value[s].len; i++, var.len++) { ch = value[s].data[i]; if (ch == '}' && bracket) { i++; bracket = 0; break; } if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_') { continue; } break; } if (bracket) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the closing bracket in \"%V\" " "variable is missing", &var); return NGX_CONF_ERROR; } if (var.len == 0) { goto invalid; } for (v = ngx_http_log_vars; v->name.len; v++) { if (v->name.len == var.len && ngx_strncmp(v->name.data, var.data, var.len) == 0) { op->len = v->len; op->getlen = NULL; op->run = v->run; op->data = 0; goto found; } } if (ngx_http_log_variable_compile(cf, op, &var, escape) != NGX_OK) { return NGX_CONF_ERROR; } if (flushes) { flush = ngx_array_push(flushes); if (flush == NULL) { return NGX_CONF_ERROR; } *flush = op->data; /* variable index */ } found: continue; } i++; while (i < value[s].len && value[s].data[i] != '$') { i++; } len = &value[s].data[i] - data; if (len) { op->len = len; op->getlen = NULL; if (len <= sizeof(uintptr_t)) { op->run = ngx_http_log_copy_short; op->data = 0; while (len--) { op->data <<= 8; op->data |= data[len]; } } else { op->run = ngx_http_log_copy_long; p = ngx_pnalloc(cf->pool, len); if (p == NULL) { return NGX_CONF_ERROR; } ngx_memcpy(p, data, len); op->data = (uintptr_t) p; } } } } return NGX_CONF_OK; invalid: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%s\"", data); return NGX_CONF_ERROR; } static char * ngx_http_log_open_file_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_log_loc_conf_t *llcf = conf; time_t inactive, valid; ngx_str_t *value, s; ngx_int_t max, min_uses; ngx_uint_t i; if (llcf->open_file_cache != NGX_CONF_UNSET_PTR) { return "is duplicate"; } value = cf->args->elts; max = 0; inactive = 10; valid = 60; min_uses = 1; for (i = 1; i < cf->args->nelts; i++) { if (ngx_strncmp(value[i].data, "max=", 4) == 0) { max = ngx_atoi(value[i].data + 4, value[i].len - 4); if (max == NGX_ERROR) { goto failed; } continue; } if (ngx_strncmp(value[i].data, "inactive=", 9) == 0) { s.len = value[i].len - 9; s.data = value[i].data + 9; inactive = ngx_parse_time(&s, 1); if (inactive == (time_t) NGX_ERROR) { goto failed; } continue; } if (ngx_strncmp(value[i].data, "min_uses=", 9) == 0) { min_uses = ngx_atoi(value[i].data + 9, value[i].len - 9); if (min_uses == NGX_ERROR) { goto failed; } continue; } if (ngx_strncmp(value[i].data, "valid=", 6) == 0) { s.len = value[i].len - 6; s.data = value[i].data + 6; valid = ngx_parse_time(&s, 1); if (valid == (time_t) NGX_ERROR) { goto failed; } continue; } if (ngx_strcmp(value[i].data, "off") == 0) { llcf->open_file_cache = NULL; continue; } failed: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid \"open_log_file_cache\" parameter \"%V\"", &value[i]); return NGX_CONF_ERROR; } if (llcf->open_file_cache == NULL) { return NGX_CONF_OK; } if (max == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"open_log_file_cache\" must have \"max\" parameter"); return NGX_CONF_ERROR; } llcf->open_file_cache = ngx_open_file_cache_init(cf->pool, max, inactive); if (llcf->open_file_cache) { llcf->open_file_cache_valid = valid; llcf->open_file_cache_min_uses = min_uses; return NGX_CONF_OK; } return NGX_CONF_ERROR; } static ngx_int_t ngx_http_log_init(ngx_conf_t *cf) { ngx_str_t *value; ngx_array_t a; ngx_http_handler_pt *h; ngx_http_log_fmt_t *fmt; ngx_http_log_main_conf_t *lmcf; ngx_http_core_main_conf_t *cmcf; lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_log_module); if (lmcf->combined_used) { if (ngx_array_init(&a, cf->pool, 1, sizeof(ngx_str_t)) != NGX_OK) { return NGX_ERROR; } value = ngx_array_push(&a); if (value == NULL) { return NGX_ERROR; } *value = ngx_http_combined_fmt; fmt = lmcf->formats.elts; if (ngx_http_log_compile_format(cf, NULL, fmt->ops, &a, 0) != NGX_CONF_OK) { return NGX_ERROR; } } cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h = ngx_array_push(&cmcf->phases[NGX_HTTP_LOG_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } *h = ngx_http_log_handler; return NGX_OK; } nginx-1.24.0/src/http/modules/ngx_http_map_module.c000644 001751 001751 00000035244 14415135676 023552 0ustar00mdouninmdounin000000 000000 /* * 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; #if (NGX_PCRE) ngx_array_t regexes; #endif ngx_http_variable_value_t *default_value; ngx_conf_t *cf; unsigned hostnames:1; unsigned no_cacheable:1; } ngx_http_map_conf_ctx_t; typedef struct { ngx_http_map_t map; ngx_http_complex_value_t value; ngx_http_variable_value_t *default_value; ngx_uint_t hostnames; /* unsigned hostnames:1 */ } ngx_http_map_ctx_t; static int ngx_libc_cdecl ngx_http_map_cmp_dns_wildcards(const void *one, const void *two); static void *ngx_http_map_create_conf(ngx_conf_t *cf); static char *ngx_http_map_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf); static ngx_command_t ngx_http_map_commands[] = { { ngx_string("map"), NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE2, ngx_http_map_block, NGX_HTTP_MAIN_CONF_OFFSET, 0, NULL }, { ngx_string("map_hash_max_size"), NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_MAIN_CONF_OFFSET, offsetof(ngx_http_map_conf_t, hash_max_size), NULL }, { ngx_string("map_hash_bucket_size"), NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_MAIN_CONF_OFFSET, offsetof(ngx_http_map_conf_t, hash_bucket_size), NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_map_module_ctx = { NULL, /* preconfiguration */ NULL, /* postconfiguration */ ngx_http_map_create_conf, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; ngx_module_t ngx_http_map_module = { NGX_MODULE_V1, &ngx_http_map_module_ctx, /* module context */ ngx_http_map_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_int_t ngx_http_map_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_http_map_ctx_t *map = (ngx_http_map_ctx_t *) data; ngx_str_t val, str; ngx_http_complex_value_t *cv; ngx_http_variable_value_t *value; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http map started"); if (ngx_http_complex_value(r, &map->value, &val) != NGX_OK) { return NGX_ERROR; } if (map->hostnames && val.len > 0 && val.data[val.len - 1] == '.') { val.len--; } value = ngx_http_map_find(r, &map->map, &val); if (value == NULL) { value = map->default_value; } if (!value->valid) { cv = (ngx_http_complex_value_t *) value->data; if (ngx_http_complex_value(r, cv, &str) != NGX_OK) { return NGX_ERROR; } v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->len = str.len; v->data = str.data; } else { *v = *value; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http map: \"%V\" \"%v\"", &val, v); return NGX_OK; } static void * ngx_http_map_create_conf(ngx_conf_t *cf) { ngx_http_map_conf_t *mcf; mcf = ngx_palloc(cf->pool, sizeof(ngx_http_map_conf_t)); if (mcf == NULL) { return NULL; } mcf->hash_max_size = NGX_CONF_UNSET_UINT; mcf->hash_bucket_size = NGX_CONF_UNSET_UINT; return mcf; } static char * ngx_http_map_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_map_conf_t *mcf = conf; char *rv; ngx_str_t *value, name; ngx_conf_t save; ngx_pool_t *pool; ngx_hash_init_t hash; ngx_http_map_ctx_t *map; ngx_http_variable_t *var; ngx_http_map_conf_ctx_t ctx; ngx_http_compile_complex_value_t ccv; if (mcf->hash_max_size == NGX_CONF_UNSET_UINT) { mcf->hash_max_size = 2048; } if (mcf->hash_bucket_size == NGX_CONF_UNSET_UINT) { mcf->hash_bucket_size = ngx_cacheline_size; } else { mcf->hash_bucket_size = ngx_align(mcf->hash_bucket_size, ngx_cacheline_size); } map = ngx_pcalloc(cf->pool, sizeof(ngx_http_map_ctx_t)); if (map == NULL) { return NGX_CONF_ERROR; } value = cf->args->elts; ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = &map->value; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } name = value[2]; if (name.data[0] != '$') { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid variable name \"%V\"", &name); return NGX_CONF_ERROR; } name.len--; name.data++; var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE); if (var == NULL) { return NGX_CONF_ERROR; } var->get_handler = ngx_http_map_variable; var->data = (uintptr_t) map; pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log); if (pool == NULL) { return NGX_CONF_ERROR; } ctx.keys.pool = cf->pool; ctx.keys.temp_pool = pool; if (ngx_hash_keys_array_init(&ctx.keys, NGX_HASH_LARGE) != NGX_OK) { ngx_destroy_pool(pool); return NGX_CONF_ERROR; } ctx.values_hash = ngx_pcalloc(pool, sizeof(ngx_array_t) * ctx.keys.hsize); if (ctx.values_hash == NULL) { ngx_destroy_pool(pool); return NGX_CONF_ERROR; } #if (NGX_PCRE) if (ngx_array_init(&ctx.regexes, cf->pool, 2, sizeof(ngx_http_map_regex_t)) != NGX_OK) { ngx_destroy_pool(pool); return NGX_CONF_ERROR; } #endif ctx.default_value = NULL; ctx.cf = &save; ctx.hostnames = 0; ctx.no_cacheable = 0; save = *cf; cf->pool = pool; cf->ctx = &ctx; cf->handler = ngx_http_map; cf->handler_conf = conf; rv = ngx_conf_parse(cf, NULL); *cf = save; if (rv != NGX_CONF_OK) { ngx_destroy_pool(pool); return rv; } if (ctx.no_cacheable) { var->flags |= NGX_HTTP_VAR_NOCACHEABLE; } map->default_value = ctx.default_value ? ctx.default_value: &ngx_http_variable_null_value; map->hostnames = ctx.hostnames; hash.key = ngx_hash_key_lc; hash.max_size = mcf->hash_max_size; hash.bucket_size = mcf->hash_bucket_size; hash.name = "map_hash"; hash.pool = cf->pool; if (ctx.keys.keys.nelts) { hash.hash = &map->map.hash.hash; hash.temp_pool = NULL; if (ngx_hash_init(&hash, ctx.keys.keys.elts, ctx.keys.keys.nelts) != NGX_OK) { ngx_destroy_pool(pool); return NGX_CONF_ERROR; } } if (ctx.keys.dns_wc_head.nelts) { ngx_qsort(ctx.keys.dns_wc_head.elts, (size_t) ctx.keys.dns_wc_head.nelts, sizeof(ngx_hash_key_t), ngx_http_map_cmp_dns_wildcards); hash.hash = NULL; hash.temp_pool = pool; if (ngx_hash_wildcard_init(&hash, ctx.keys.dns_wc_head.elts, ctx.keys.dns_wc_head.nelts) != NGX_OK) { ngx_destroy_pool(pool); return NGX_CONF_ERROR; } map->map.hash.wc_head = (ngx_hash_wildcard_t *) hash.hash; } if (ctx.keys.dns_wc_tail.nelts) { ngx_qsort(ctx.keys.dns_wc_tail.elts, (size_t) ctx.keys.dns_wc_tail.nelts, sizeof(ngx_hash_key_t), ngx_http_map_cmp_dns_wildcards); hash.hash = NULL; hash.temp_pool = pool; if (ngx_hash_wildcard_init(&hash, ctx.keys.dns_wc_tail.elts, ctx.keys.dns_wc_tail.nelts) != NGX_OK) { ngx_destroy_pool(pool); return NGX_CONF_ERROR; } map->map.hash.wc_tail = (ngx_hash_wildcard_t *) hash.hash; } #if (NGX_PCRE) if (ctx.regexes.nelts) { map->map.regex = ctx.regexes.elts; map->map.nregex = ctx.regexes.nelts; } #endif ngx_destroy_pool(pool); return rv; } static int ngx_libc_cdecl ngx_http_map_cmp_dns_wildcards(const void *one, const void *two) { ngx_hash_key_t *first, *second; first = (ngx_hash_key_t *) one; second = (ngx_hash_key_t *) two; return ngx_dns_strcmp(first->key.data, second->key.data); } static char * ngx_http_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) { u_char *data; size_t len; ngx_int_t rv; ngx_str_t *value, v; ngx_uint_t i, key; ngx_http_map_conf_ctx_t *ctx; ngx_http_complex_value_t cv, *cvp; ngx_http_variable_value_t *var, **vp; ngx_http_compile_complex_value_t ccv; ctx = cf->ctx; value = cf->args->elts; if (cf->args->nelts == 1 && ngx_strcmp(value[0].data, "hostnames") == 0) { ctx->hostnames = 1; return NGX_CONF_OK; } if (cf->args->nelts == 1 && ngx_strcmp(value[0].data, "volatile") == 0) { ctx->no_cacheable = 1; return NGX_CONF_OK; } if (cf->args->nelts != 2) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid number of the map parameters"); return NGX_CONF_ERROR; } if (ngx_strcmp(value[0].data, "include") == 0) { return ngx_conf_include(cf, dummy, conf); } key = 0; for (i = 0; i < value[1].len; i++) { key = ngx_hash(key, value[1].data[i]); } key %= ctx->keys.hsize; vp = ctx->values_hash[key].elts; if (vp) { for (i = 0; i < ctx->values_hash[key].nelts; i++) { if (vp[i]->valid) { data = vp[i]->data; len = vp[i]->len; } else { cvp = (ngx_http_complex_value_t *) vp[i]->data; data = cvp->value.data; len = cvp->value.len; } if (value[1].len != len) { continue; } if (ngx_strncmp(value[1].data, data, len) == 0) { var = vp[i]; goto found; } } } else { if (ngx_array_init(&ctx->values_hash[key], cf->pool, 4, sizeof(ngx_http_variable_value_t *)) != NGX_OK) { return NGX_CONF_ERROR; } } var = ngx_palloc(ctx->keys.pool, sizeof(ngx_http_variable_value_t)); if (var == NULL) { return NGX_CONF_ERROR; } v.len = value[1].len; v.data = ngx_pstrdup(ctx->keys.pool, &value[1]); if (v.data == NULL) { return NGX_CONF_ERROR; } ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = ctx->cf; ccv.value = &v; ccv.complex_value = &cv; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } if (cv.lengths != NULL) { cvp = ngx_palloc(ctx->keys.pool, sizeof(ngx_http_complex_value_t)); if (cvp == NULL) { return NGX_CONF_ERROR; } *cvp = cv; var->len = 0; var->data = (u_char *) cvp; var->valid = 0; } else { var->len = v.len; var->data = v.data; var->valid = 1; } var->no_cacheable = 0; var->not_found = 0; vp = ngx_array_push(&ctx->values_hash[key]); if (vp == NULL) { return NGX_CONF_ERROR; } *vp = var; found: if (ngx_strcmp(value[0].data, "default") == 0) { if (ctx->default_value) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "duplicate default map parameter"); return NGX_CONF_ERROR; } ctx->default_value = var; return NGX_CONF_OK; } #if (NGX_PCRE) if (value[0].len && value[0].data[0] == '~') { ngx_regex_compile_t rc; ngx_http_map_regex_t *regex; u_char errstr[NGX_MAX_CONF_ERRSTR]; regex = ngx_array_push(&ctx->regexes); if (regex == NULL) { return NGX_CONF_ERROR; } value[0].len--; value[0].data++; ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); if (value[0].data[0] == '*') { value[0].len--; value[0].data++; rc.options = NGX_REGEX_CASELESS; } rc.pattern = value[0]; rc.err.len = NGX_MAX_CONF_ERRSTR; rc.err.data = errstr; regex->regex = ngx_http_regex_compile(ctx->cf, &rc); if (regex->regex == NULL) { return NGX_CONF_ERROR; } regex->value = var; return NGX_CONF_OK; } #endif if (value[0].len && value[0].data[0] == '\\') { value[0].len--; value[0].data++; } rv = ngx_hash_add_key(&ctx->keys, &value[0], var, (ctx->hostnames) ? NGX_HASH_WILDCARD_KEY : 0); if (rv == NGX_OK) { return NGX_CONF_OK; } if (rv == NGX_DECLINED) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid hostname or wildcard \"%V\"", &value[0]); } if (rv == NGX_BUSY) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "conflicting parameter \"%V\"", &value[0]); } return NGX_CONF_ERROR; } nginx-1.24.0/src/http/modules/ngx_http_memcached_module.c000644 001751 001751 00000051361 14415135676 024701 0ustar00mdouninmdounin000000 000000 /* * 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_TAKE12, ngx_http_upstream_bind_set_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_memcached_loc_conf_t, upstream.local), NULL }, { ngx_string("memcached_socket_keepalive"), 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_memcached_loc_conf_t, upstream.socket_keepalive), 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; h->next = NULL; 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 (bytes > u->length || 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 (bytes > u->length || ngx_strncmp(last, ngx_http_memcached_end, b->last - last) != 0) { ngx_log_error(NGX_LOG_ERR, ctx->request->connection->log, 0, "memcached sent invalid trailer"); b->last = last; cl->buf->last = last; u->length = 0; ctx->rest = 0; return NGX_OK; } ctx->rest -= b->last - last; b->last = last; cl->buf->last = last; u->length = ctx->rest; if (u->length == 0) { u->keepalive = 1; } return NGX_OK; } static void ngx_http_memcached_abort_request(ngx_http_request_t *r) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "abort http memcached request"); return; } static void ngx_http_memcached_finalize_request(ngx_http_request_t *r, ngx_int_t rc) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "finalize http memcached request"); return; } static void * ngx_http_memcached_create_loc_conf(ngx_conf_t *cf) { ngx_http_memcached_loc_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_memcached_loc_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->upstream.bufs.num = 0; * conf->upstream.next_upstream = 0; * conf->upstream.temp_path = NULL; */ conf->upstream.local = NGX_CONF_UNSET_PTR; conf->upstream.socket_keepalive = NGX_CONF_UNSET; 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_value(conf->upstream.socket_keepalive, prev->upstream.socket_keepalive, 0); 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.len && 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; } nginx-1.24.0/src/http/modules/ngx_http_mirror_module.c000644 001751 001751 00000015212 14415135676 024300 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Roman Arutyunyan * Copyright (C) Nginx, Inc. */ #include #include #include typedef struct { ngx_array_t *mirror; ngx_flag_t request_body; } ngx_http_mirror_loc_conf_t; typedef struct { ngx_int_t status; } ngx_http_mirror_ctx_t; static ngx_int_t ngx_http_mirror_handler(ngx_http_request_t *r); static void ngx_http_mirror_body_handler(ngx_http_request_t *r); static ngx_int_t ngx_http_mirror_handler_internal(ngx_http_request_t *r); static void *ngx_http_mirror_create_loc_conf(ngx_conf_t *cf); static char *ngx_http_mirror_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); static char *ngx_http_mirror(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_int_t ngx_http_mirror_init(ngx_conf_t *cf); static ngx_command_t ngx_http_mirror_commands[] = { { ngx_string("mirror"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_mirror, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("mirror_request_body"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_mirror_loc_conf_t, request_body), NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_mirror_module_ctx = { NULL, /* preconfiguration */ ngx_http_mirror_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_mirror_create_loc_conf, /* create location configuration */ ngx_http_mirror_merge_loc_conf /* merge location configuration */ }; ngx_module_t ngx_http_mirror_module = { NGX_MODULE_V1, &ngx_http_mirror_module_ctx, /* module context */ ngx_http_mirror_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_int_t ngx_http_mirror_handler(ngx_http_request_t *r) { ngx_int_t rc; ngx_http_mirror_ctx_t *ctx; ngx_http_mirror_loc_conf_t *mlcf; if (r != r->main) { return NGX_DECLINED; } mlcf = ngx_http_get_module_loc_conf(r, ngx_http_mirror_module); if (mlcf->mirror == NULL) { return NGX_DECLINED; } ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "mirror handler"); if (mlcf->request_body) { ctx = ngx_http_get_module_ctx(r, ngx_http_mirror_module); if (ctx) { return ctx->status; } ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_mirror_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ctx->status = NGX_DONE; ngx_http_set_ctx(r, ctx, ngx_http_mirror_module); rc = ngx_http_read_client_request_body(r, ngx_http_mirror_body_handler); if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { return rc; } ngx_http_finalize_request(r, NGX_DONE); return NGX_DONE; } return ngx_http_mirror_handler_internal(r); } static void ngx_http_mirror_body_handler(ngx_http_request_t *r) { ngx_http_mirror_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_mirror_module); ctx->status = ngx_http_mirror_handler_internal(r); r->preserve_body = 1; r->write_event_handler = ngx_http_core_run_phases; ngx_http_core_run_phases(r); } static ngx_int_t ngx_http_mirror_handler_internal(ngx_http_request_t *r) { ngx_str_t *name; ngx_uint_t i; ngx_http_request_t *sr; ngx_http_mirror_loc_conf_t *mlcf; mlcf = ngx_http_get_module_loc_conf(r, ngx_http_mirror_module); name = mlcf->mirror->elts; for (i = 0; i < mlcf->mirror->nelts; i++) { if (ngx_http_subrequest(r, &name[i], &r->args, &sr, NULL, NGX_HTTP_SUBREQUEST_BACKGROUND) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } sr->header_only = 1; sr->method = r->method; sr->method_name = r->method_name; } return NGX_DECLINED; } static void * ngx_http_mirror_create_loc_conf(ngx_conf_t *cf) { ngx_http_mirror_loc_conf_t *mlcf; mlcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_mirror_loc_conf_t)); if (mlcf == NULL) { return NULL; } mlcf->mirror = NGX_CONF_UNSET_PTR; mlcf->request_body = NGX_CONF_UNSET; return mlcf; } static char * ngx_http_mirror_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_mirror_loc_conf_t *prev = parent; ngx_http_mirror_loc_conf_t *conf = child; ngx_conf_merge_ptr_value(conf->mirror, prev->mirror, NULL); ngx_conf_merge_value(conf->request_body, prev->request_body, 1); return NGX_CONF_OK; } static char * ngx_http_mirror(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_mirror_loc_conf_t *mlcf = conf; ngx_str_t *value, *s; value = cf->args->elts; if (ngx_strcmp(value[1].data, "off") == 0) { if (mlcf->mirror != NGX_CONF_UNSET_PTR) { return "is duplicate"; } mlcf->mirror = NULL; return NGX_CONF_OK; } if (mlcf->mirror == NULL) { return "is duplicate"; } if (mlcf->mirror == NGX_CONF_UNSET_PTR) { mlcf->mirror = ngx_array_create(cf->pool, 4, sizeof(ngx_str_t)); if (mlcf->mirror == NULL) { return NGX_CONF_ERROR; } } s = ngx_array_push(mlcf->mirror); if (s == NULL) { return NGX_CONF_ERROR; } *s = value[1]; return NGX_CONF_OK; } static ngx_int_t ngx_http_mirror_init(ngx_conf_t *cf) { ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h = ngx_array_push(&cmcf->phases[NGX_HTTP_PRECONTENT_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } *h = ngx_http_mirror_handler; return NGX_OK; } nginx-1.24.0/src/http/modules/ngx_http_mp4_module.c000644 001751 001751 00000347265 14415135676 023506 0ustar00mdouninmdounin000000 000000 /* * 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_EDTS_ATOM 2 #define NGX_HTTP_MP4_ELST_ATOM 3 #define NGX_HTTP_MP4_MDIA_ATOM 4 #define NGX_HTTP_MP4_MDHD_ATOM 5 #define NGX_HTTP_MP4_HDLR_ATOM 6 #define NGX_HTTP_MP4_MINF_ATOM 7 #define NGX_HTTP_MP4_VMHD_ATOM 8 #define NGX_HTTP_MP4_SMHD_ATOM 9 #define NGX_HTTP_MP4_DINF_ATOM 10 #define NGX_HTTP_MP4_STBL_ATOM 11 #define NGX_HTTP_MP4_STSD_ATOM 12 #define NGX_HTTP_MP4_STTS_ATOM 13 #define NGX_HTTP_MP4_STTS_DATA 14 #define NGX_HTTP_MP4_STSS_ATOM 15 #define NGX_HTTP_MP4_STSS_DATA 16 #define NGX_HTTP_MP4_CTTS_ATOM 17 #define NGX_HTTP_MP4_CTTS_DATA 18 #define NGX_HTTP_MP4_STSC_ATOM 19 #define NGX_HTTP_MP4_STSC_START 20 #define NGX_HTTP_MP4_STSC_DATA 21 #define NGX_HTTP_MP4_STSC_END 22 #define NGX_HTTP_MP4_STSZ_ATOM 23 #define NGX_HTTP_MP4_STSZ_DATA 24 #define NGX_HTTP_MP4_STCO_ATOM 25 #define NGX_HTTP_MP4_STCO_DATA 26 #define NGX_HTTP_MP4_CO64_ATOM 27 #define NGX_HTTP_MP4_CO64_DATA 28 #define NGX_HTTP_MP4_LAST_ATOM NGX_HTTP_MP4_CO64_DATA typedef struct { size_t buffer_size; size_t max_buffer_size; ngx_flag_t start_key_frame; } 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 { u_char size[4]; u_char name[4]; } ngx_mp4_edts_atom_t; typedef struct { u_char size[4]; u_char name[4]; u_char version[1]; u_char flags[3]; u_char entries[4]; u_char duration[8]; u_char media_time[8]; u_char media_rate[2]; u_char reserved[2]; } ngx_mp4_elst_atom_t; typedef struct { uint32_t timescale; uint32_t time_to_sample_entries; 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; uint64_t duration; uint64_t prefix; uint64_t movie_duration; 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 edts_atom_buf; ngx_buf_t elst_atom_buf; ngx_buf_t mdia_atom_buf; ngx_buf_t mdhd_atom_buf; ngx_buf_t hdlr_atom_buf; 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_edts_atom_t edts_atom; ngx_mp4_elst_atom_t elst_atom; ngx_mp4_stsc_entry_t stsc_start_chunk_entry; ngx_mp4_stsc_entry_t stsc_end_chunk_entry; } ngx_http_mp4_trak_t; typedef struct { ngx_file_t file; u_char *buffer; u_char *buffer_start; u_char *buffer_pos; u_char *buffer_end; size_t buffer_size; off_t offset; off_t end; off_t content_length; ngx_uint_t start; ngx_uint_t length; uint32_t timescale; ngx_http_request_t *request; ngx_array_t trak; ngx_http_mp4_trak_t traks[2]; size_t ftyp_size; size_t moov_size; ngx_chain_t *out; ngx_chain_t ftyp_atom; ngx_chain_t moov_atom; ngx_chain_t mvhd_atom; ngx_chain_t mdat_atom; ngx_chain_t mdat_data; ngx_buf_t ftyp_atom_buf; ngx_buf_t moov_atom_buf; ngx_buf_t mvhd_atom_buf; ngx_buf_t mdat_atom_buf; ngx_buf_t mdat_data_buf; u_char moov_atom_header[8]; u_char mdat_atom_header[16]; } ngx_http_mp4_file_t; typedef struct { char *name; ngx_int_t (*handler)(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); } ngx_http_mp4_atom_handler_t; #define ngx_mp4_atom_header(mp4) (mp4->buffer_pos - 8) #define ngx_mp4_atom_data(mp4) mp4->buffer_pos #define ngx_mp4_atom_data_size(t) (uint64_t) (sizeof(t) - 8) #define ngx_mp4_atom_next(mp4, n) \ \ if (n > (size_t) (mp4->buffer_end - mp4->buffer_pos)) { \ mp4->buffer_pos = mp4->buffer_end; \ \ } else { \ mp4->buffer_pos += (size_t) n; \ } \ \ mp4->offset += n #define ngx_mp4_set_atom_name(p, n1, n2, n3, n4) \ ((u_char *) (p))[4] = n1; \ ((u_char *) (p))[5] = n2; \ ((u_char *) (p))[6] = n3; \ ((u_char *) (p))[7] = n4 #define ngx_mp4_get_16value(p) \ ( ((uint16_t) ((u_char *) (p))[0] << 8) \ + ( ((u_char *) (p))[1]) ) #define ngx_mp4_set_16value(p, n) \ ((u_char *) (p))[0] = (u_char) ((n) >> 8); \ ((u_char *) (p))[1] = (u_char) (n) #define ngx_mp4_get_32value(p) \ ( ((uint32_t) ((u_char *) (p))[0] << 24) \ + ( ((u_char *) (p))[1] << 16) \ + ( ((u_char *) (p))[2] << 8) \ + ( ((u_char *) (p))[3]) ) #define ngx_mp4_set_32value(p, n) \ ((u_char *) (p))[0] = (u_char) ((n) >> 24); \ ((u_char *) (p))[1] = (u_char) ((n) >> 16); \ ((u_char *) (p))[2] = (u_char) ((n) >> 8); \ ((u_char *) (p))[3] = (u_char) (n) #define ngx_mp4_get_64value(p) \ ( ((uint64_t) ((u_char *) (p))[0] << 56) \ + ((uint64_t) ((u_char *) (p))[1] << 48) \ + ((uint64_t) ((u_char *) (p))[2] << 40) \ + ((uint64_t) ((u_char *) (p))[3] << 32) \ + ((uint64_t) ((u_char *) (p))[4] << 24) \ + ( ((u_char *) (p))[5] << 16) \ + ( ((u_char *) (p))[6] << 8) \ + ( ((u_char *) (p))[7]) ) #define ngx_mp4_set_64value(p, n) \ ((u_char *) (p))[0] = (u_char) ((uint64_t) (n) >> 56); \ ((u_char *) (p))[1] = (u_char) ((uint64_t) (n) >> 48); \ ((u_char *) (p))[2] = (u_char) ((uint64_t) (n) >> 40); \ ((u_char *) (p))[3] = (u_char) ((uint64_t) (n) >> 32); \ ((u_char *) (p))[4] = (u_char) ( (n) >> 24); \ ((u_char *) (p))[5] = (u_char) ( (n) >> 16); \ ((u_char *) (p))[6] = (u_char) ( (n) >> 8); \ ((u_char *) (p))[7] = (u_char) (n) #define ngx_mp4_last_trak(mp4) \ &((ngx_http_mp4_trak_t *) mp4->trak.elts)[mp4->trak.nelts - 1] static ngx_int_t ngx_http_mp4_handler(ngx_http_request_t *r); static ngx_int_t ngx_http_mp4_atofp(u_char *line, size_t n, size_t point); static ngx_int_t ngx_http_mp4_process(ngx_http_mp4_file_t *mp4); static ngx_int_t ngx_http_mp4_read_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_atom_handler_t *atom, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_read(ngx_http_mp4_file_t *mp4, size_t size); static ngx_int_t ngx_http_mp4_read_ftyp_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_read_moov_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_read_mdat_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static size_t ngx_http_mp4_update_mdat_atom(ngx_http_mp4_file_t *mp4, off_t start_offset, off_t end_offset); static ngx_int_t ngx_http_mp4_read_mvhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_read_trak_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static void ngx_http_mp4_update_trak_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak); static ngx_int_t ngx_http_mp4_read_cmov_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_read_tkhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_read_mdia_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static void ngx_http_mp4_update_mdia_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak); static ngx_int_t ngx_http_mp4_read_mdhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static void ngx_http_mp4_update_mdhd_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak); static ngx_int_t ngx_http_mp4_read_hdlr_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_read_minf_atom(ngx_http_mp4_file_t *mp4, 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_edts_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak); static void ngx_http_mp4_update_stbl_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak); static ngx_int_t ngx_http_mp4_read_stsd_atom(ngx_http_mp4_file_t *mp4, 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 uint32_t ngx_http_mp4_seek_key_frame(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak, uint32_t start_sample); static ngx_int_t ngx_http_mp4_read_stss_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_update_stss_atom(ngx_http_mp4_file_t *mp4, 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_string("mp4_start_key_frame"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_mp4_conf_t, start_key_frame), NULL }, ngx_null_command }; 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) { return NGX_DECLINED; } r->root_tested = !r->error_page; r->allow_ranges = 1; start = -1; length = 0; r->headers_out.content_length_n = of.size; mp4 = NULL; b = NULL; if (r->args.len) { if (ngx_http_arg(r, (u_char *) "start", 5, &value) == NGX_OK) { /* * A Flash player may send start value with a lot of digits * after dot so a custom function is used instead of ngx_atofp(). */ start = ngx_http_mp4_atofp(value.data, value.len, 3); } if (ngx_http_arg(r, (u_char *) "end", 3, &value) == NGX_OK) { end = ngx_http_mp4_atofp(value.data, value.len, 3); if (end > 0) { if (start < 0) { start = 0; } if (end > start) { length = end - start; } } } } if (start >= 0) { r->single_range = 1; mp4 = ngx_pcalloc(r->pool, sizeof(ngx_http_mp4_file_t)); if (mp4 == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } mp4->file.fd = of.fd; mp4->file.name = path; mp4->file.log = r->connection->log; mp4->end = of.size; mp4->start = (ngx_uint_t) start; mp4->length = length; mp4->request = r; switch (ngx_http_mp4_process(mp4)) { case NGX_DECLINED: if (mp4->buffer) { ngx_pfree(r->pool, mp4->buffer); } ngx_pfree(r->pool, mp4); mp4 = NULL; break; case NGX_OK: r->headers_out.content_length_n = mp4->content_length; break; default: /* NGX_ERROR */ if (mp4->buffer) { ngx_pfree(r->pool, mp4->buffer); } ngx_pfree(r->pool, mp4); return NGX_HTTP_INTERNAL_SERVER_ERROR; } } log->action = "sending mp4 to client"; if (clcf->directio <= of.size) { /* * DIRECTIO is set on transfer only * to allow kernel to cache "moov" atom */ if (ngx_directio_on(of.fd) == NGX_FILE_ERROR) { ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, ngx_directio_on_n " \"%s\" failed", path.data); } of.is_directio = 1; if (mp4) { mp4->file.directio = 1; } } r->headers_out.status = NGX_HTTP_OK; r->headers_out.last_modified_time = of.mtime; if (ngx_http_set_etag(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_http_set_content_type(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (mp4 == NULL) { b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t)); if (b->file == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } } rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { return rc; } if (mp4) { return ngx_http_output_filter(r, mp4->out); } b->file_pos = 0; b->file_last = of.size; b->in_file = b->file_last ? 1 : 0; b->last_buf = (r == r->main) ? 1 : 0; b->last_in_chain = 1; b->sync = (b->last_buf || b->in_file) ? 0 : 1; b->file->fd = of.fd; b->file->name = path; b->file->log = log; b->file->directio = of.is_directio; out.buf = b; out.next = NULL; return ngx_http_output_filter(r, &out); } static ngx_int_t ngx_http_mp4_atofp(u_char *line, size_t n, size_t point) { ngx_int_t value, cutoff, cutlim; ngx_uint_t dot; /* same as ngx_atofp(), but allows additional digits */ if (n == 0) { return NGX_ERROR; } cutoff = NGX_MAX_INT_T_VALUE / 10; cutlim = NGX_MAX_INT_T_VALUE % 10; dot = 0; for (value = 0; n--; line++) { if (*line == '.') { if (dot) { return NGX_ERROR; } dot = 1; continue; } if (*line < '0' || *line > '9') { return NGX_ERROR; } if (point == 0) { continue; } if (value >= cutoff && (value > cutoff || *line - '0' > cutlim)) { return NGX_ERROR; } value = value * 10 + (*line - '0'); point -= dot; } while (point--) { if (value > cutoff) { return NGX_ERROR; } value = value * 10; } return value; } static ngx_int_t ngx_http_mp4_process(ngx_http_mp4_file_t *mp4) { off_t start_offset, end_offset, adjustment; ngx_int_t rc; ngx_uint_t i, j; ngx_chain_t **prev; ngx_http_mp4_trak_t *trak; ngx_http_mp4_conf_t *conf; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 start:%ui, length:%ui", mp4->start, mp4->length); conf = ngx_http_get_module_loc_conf(mp4->request, ngx_http_mp4_module); mp4->buffer_size = conf->buffer_size; rc = ngx_http_mp4_read_atom(mp4, ngx_http_mp4_atoms, mp4->end); if (rc != NGX_OK) { return rc; } if (mp4->trak.nelts == 0) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "no mp4 trak atoms were found in \"%s\"", mp4->file.name.data); return NGX_ERROR; } if (mp4->mdat_atom.buf == NULL) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "no mp4 mdat atom was found in \"%s\"", mp4->file.name.data); return NGX_ERROR; } prev = &mp4->out; if (mp4->ftyp_atom.buf) { *prev = &mp4->ftyp_atom; prev = &mp4->ftyp_atom.next; } *prev = &mp4->moov_atom; prev = &mp4->moov_atom.next; if (mp4->mvhd_atom.buf) { mp4->moov_size += mp4->mvhd_atom_buf.last - mp4->mvhd_atom_buf.pos; *prev = &mp4->mvhd_atom; prev = &mp4->mvhd_atom.next; } start_offset = mp4->end; end_offset = 0; trak = mp4->trak.elts; for (i = 0; i < mp4->trak.nelts; i++) { if (ngx_http_mp4_update_stts_atom(mp4, &trak[i]) != NGX_OK) { return NGX_ERROR; } if (ngx_http_mp4_update_stss_atom(mp4, &trak[i]) != NGX_OK) { return NGX_ERROR; } ngx_http_mp4_update_ctts_atom(mp4, &trak[i]); if (ngx_http_mp4_update_stsc_atom(mp4, &trak[i]) != NGX_OK) { return NGX_ERROR; } if (ngx_http_mp4_update_stsz_atom(mp4, &trak[i]) != NGX_OK) { return NGX_ERROR; } if (trak[i].out[NGX_HTTP_MP4_CO64_DATA].buf) { if (ngx_http_mp4_update_co64_atom(mp4, &trak[i]) != NGX_OK) { return NGX_ERROR; } } else { if (ngx_http_mp4_update_stco_atom(mp4, &trak[i]) != NGX_OK) { return NGX_ERROR; } } ngx_http_mp4_update_stbl_atom(mp4, &trak[i]); ngx_http_mp4_update_minf_atom(mp4, &trak[i]); ngx_http_mp4_update_mdhd_atom(mp4, &trak[i]); trak[i].size += trak[i].hdlr_size; ngx_http_mp4_update_mdia_atom(mp4, &trak[i]); trak[i].size += trak[i].tkhd_size; ngx_http_mp4_update_edts_atom(mp4, &trak[i]); ngx_http_mp4_update_trak_atom(mp4, &trak[i]); mp4->moov_size += trak[i].size; if (start_offset > trak[i].start_offset) { start_offset = trak[i].start_offset; } if (end_offset < trak[i].end_offset) { end_offset = trak[i].end_offset; } *prev = &trak[i].out[NGX_HTTP_MP4_TRAK_ATOM]; prev = &trak[i].out[NGX_HTTP_MP4_TRAK_ATOM].next; for (j = 0; j < NGX_HTTP_MP4_LAST_ATOM + 1; j++) { if (trak[i].out[j].buf) { *prev = &trak[i].out[j]; prev = &trak[i].out[j].next; } } } if (end_offset < start_offset) { end_offset = start_offset; } mp4->moov_size += 8; ngx_mp4_set_32value(mp4->moov_atom_header, mp4->moov_size); ngx_mp4_set_atom_name(mp4->moov_atom_header, 'm', 'o', 'o', 'v'); mp4->content_length += mp4->moov_size; *prev = &mp4->mdat_atom; if (start_offset > mp4->mdat_data.buf->file_last) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "start time is out mp4 mdat atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; } adjustment = mp4->ftyp_size + mp4->moov_size + ngx_http_mp4_update_mdat_atom(mp4, start_offset, end_offset) - start_offset; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 adjustment:%O", adjustment); for (i = 0; i < mp4->trak.nelts; i++) { if (trak[i].out[NGX_HTTP_MP4_CO64_DATA].buf) { ngx_http_mp4_adjust_co64_atom(mp4, &trak[i], adjustment); } else { ngx_http_mp4_adjust_stco_atom(mp4, &trak[i], (int32_t) adjustment); } } return NGX_OK; } typedef struct { u_char size[4]; u_char name[4]; } ngx_mp4_atom_header_t; typedef struct { u_char size[4]; u_char name[4]; u_char size64[8]; } ngx_mp4_atom_header64_t; static ngx_int_t ngx_http_mp4_read_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_atom_handler_t *atom, uint64_t atom_data_size) { off_t end; size_t atom_header_size; u_char *atom_header, *atom_name; uint64_t atom_size; ngx_int_t rc; ngx_uint_t n; end = mp4->offset + atom_data_size; while (mp4->offset < end) { if (ngx_http_mp4_read(mp4, sizeof(uint32_t)) != NGX_OK) { return NGX_ERROR; } atom_header = mp4->buffer_pos; atom_size = ngx_mp4_get_32value(atom_header); atom_header_size = sizeof(ngx_mp4_atom_header_t); if (atom_size == 0) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 atom end"); return NGX_OK; } if (atom_size < sizeof(ngx_mp4_atom_header_t)) { if (atom_size == 1) { if (ngx_http_mp4_read(mp4, sizeof(ngx_mp4_atom_header64_t)) != NGX_OK) { return NGX_ERROR; } /* 64-bit atom size */ atom_header = mp4->buffer_pos; atom_size = ngx_mp4_get_64value(atom_header + 8); atom_header_size = sizeof(ngx_mp4_atom_header64_t); if (atom_size < sizeof(ngx_mp4_atom_header64_t)) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 atom is too small:%uL", mp4->file.name.data, atom_size); return NGX_ERROR; } } else { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 atom is too small:%uL", mp4->file.name.data, atom_size); return NGX_ERROR; } } if (ngx_http_mp4_read(mp4, sizeof(ngx_mp4_atom_header_t)) != NGX_OK) { return NGX_ERROR; } atom_header = mp4->buffer_pos; atom_name = atom_header + sizeof(uint32_t); ngx_log_debug4(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 atom: %*s @%O:%uL", (size_t) 4, atom_name, mp4->offset, atom_size); if (atom_size > (uint64_t) (NGX_MAX_OFF_T_VALUE - mp4->offset) || mp4->offset + (off_t) atom_size > end) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 atom too large:%uL", mp4->file.name.data, atom_size); return NGX_ERROR; } for (n = 0; atom[n].name; n++) { if (ngx_strncmp(atom_name, atom[n].name, 4) == 0) { ngx_mp4_atom_next(mp4, atom_header_size); rc = atom[n].handler(mp4, atom_size - atom_header_size); if (rc != NGX_OK) { return rc; } goto next; } } ngx_mp4_atom_next(mp4, atom_size); next: continue; } return NGX_OK; } static ngx_int_t ngx_http_mp4_read(ngx_http_mp4_file_t *mp4, size_t size) { ssize_t n; if (mp4->buffer_pos + size <= mp4->buffer_end) { return NGX_OK; } if (mp4->offset + (off_t) mp4->buffer_size > mp4->end) { mp4->buffer_size = (size_t) (mp4->end - mp4->offset); } if (mp4->buffer_size < size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 file truncated", mp4->file.name.data); return NGX_ERROR; } if (mp4->buffer == NULL) { mp4->buffer = ngx_palloc(mp4->request->pool, mp4->buffer_size); if (mp4->buffer == NULL) { return NGX_ERROR; } mp4->buffer_start = mp4->buffer; } n = ngx_read_file(&mp4->file, mp4->buffer_start, mp4->buffer_size, mp4->offset); if (n == NGX_ERROR) { return NGX_ERROR; } if ((size_t) n != mp4->buffer_size) { ngx_log_error(NGX_LOG_CRIT, mp4->file.log, 0, ngx_read_file_n " read only %z of %z from \"%s\"", n, mp4->buffer_size, mp4->file.name.data); return NGX_ERROR; } mp4->buffer_pos = mp4->buffer_start; mp4->buffer_end = mp4->buffer_start + mp4->buffer_size; return NGX_OK; } static ngx_int_t ngx_http_mp4_read_ftyp_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { u_char *ftyp_atom; size_t atom_size; ngx_buf_t *atom; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 ftyp atom"); if (atom_data_size > 1024 || ngx_mp4_atom_data(mp4) + (size_t) atom_data_size > mp4->buffer_end) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 ftyp atom is too large:%uL", mp4->file.name.data, atom_data_size); return NGX_ERROR; } if (mp4->ftyp_atom.buf) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "duplicate mp4 ftyp atom in \"%s\"", mp4->file.name.data); 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; } if (mp4->moov_atom.buf) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "duplicate mp4 moov atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; } 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"); if (mp4->mdat_atom.buf) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "duplicate mp4 mdat atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; } data = &mp4->mdat_data_buf; data->file = &mp4->file; data->in_file = 1; data->last_buf = (mp4->request == mp4->request->main) ? 1 : 0; data->last_in_chain = 1; data->file_last = mp4->offset + atom_data_size; mp4->mdat_atom.buf = &mp4->mdat_atom_buf; mp4->mdat_atom.next = &mp4->mdat_data; mp4->mdat_data.buf = data; if (mp4->trak.nelts) { /* skip atoms after mdat atom */ mp4->offset = mp4->end; } else { ngx_mp4_atom_next(mp4, atom_data_size); } return NGX_OK; } static size_t ngx_http_mp4_update_mdat_atom(ngx_http_mp4_file_t *mp4, off_t start_offset, off_t end_offset) { off_t atom_data_size; u_char *atom_header; uint32_t atom_header_size; uint64_t atom_size; ngx_buf_t *atom; atom_data_size = end_offset - start_offset; mp4->mdat_data.buf->file_pos = start_offset; mp4->mdat_data.buf->file_last = end_offset; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mdat new offset @%O:%O", start_offset, atom_data_size); atom_header = mp4->mdat_atom_header; if ((uint64_t) atom_data_size > (uint64_t) 0xffffffff - sizeof(ngx_mp4_atom_header_t)) { atom_size = 1; atom_header_size = sizeof(ngx_mp4_atom_header64_t); ngx_mp4_set_64value(atom_header + sizeof(ngx_mp4_atom_header_t), sizeof(ngx_mp4_atom_header64_t) + atom_data_size); } else { atom_size = sizeof(ngx_mp4_atom_header_t) + atom_data_size; atom_header_size = sizeof(ngx_mp4_atom_header_t); } mp4->content_length += atom_header_size + atom_data_size; ngx_mp4_set_32value(atom_header, atom_size); ngx_mp4_set_atom_name(atom_header, 'm', 'd', 'a', 't'); atom = &mp4->mdat_atom_buf; atom->temporary = 1; atom->pos = atom_header; atom->last = atom_header + atom_header_size; return atom_header_size; } typedef struct { u_char size[4]; u_char name[4]; u_char version[1]; u_char flags[3]; u_char creation_time[4]; u_char modification_time[4]; u_char timescale[4]; u_char duration[4]; u_char rate[4]; u_char volume[2]; u_char reserved[10]; u_char matrix[36]; u_char preview_time[4]; u_char preview_duration[4]; u_char poster_time[4]; u_char selection_time[4]; u_char selection_duration[4]; u_char current_time[4]; u_char next_track_id[4]; } ngx_mp4_mvhd_atom_t; typedef struct { u_char size[4]; u_char name[4]; u_char version[1]; u_char flags[3]; u_char creation_time[8]; u_char modification_time[8]; u_char timescale[4]; u_char duration[8]; u_char rate[4]; u_char volume[2]; u_char reserved[10]; u_char matrix[36]; u_char preview_time[4]; u_char preview_duration[4]; u_char poster_time[4]; u_char selection_time[4]; u_char selection_duration[4]; u_char current_time[4]; u_char next_track_id[4]; } ngx_mp4_mvhd64_atom_t; static ngx_int_t ngx_http_mp4_read_mvhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { u_char *atom_header; size_t atom_size; uint32_t timescale; uint64_t duration, start_time, length_time; ngx_buf_t *atom; ngx_mp4_mvhd_atom_t *mvhd_atom; ngx_mp4_mvhd64_atom_t *mvhd64_atom; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 mvhd atom"); if (mp4->mvhd_atom.buf) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "duplicate mp4 mvhd atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; } 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); if (trak->out[NGX_HTTP_MP4_TKHD_ATOM].buf) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "duplicate mp4 tkhd atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; } trak->tkhd_size = atom_size; trak->movie_duration = duration; 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); if (trak->out[NGX_HTTP_MP4_MDIA_ATOM].buf) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "duplicate mp4 mdia atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; } 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); if (trak->out[NGX_HTTP_MP4_MDHD_ATOM].buf) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "duplicate mp4 mdhd atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; } trak->mdhd_size = atom_size; trak->timescale = timescale; trak->duration = duration; ngx_mp4_set_32value(mdhd_atom->size, atom_size); 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 void ngx_http_mp4_update_mdhd_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak) { ngx_buf_t *atom; ngx_mp4_mdhd_atom_t *mdhd_atom; ngx_mp4_mdhd64_atom_t *mdhd64_atom; atom = trak->out[NGX_HTTP_MP4_MDHD_ATOM].buf; if (atom == NULL) { return; } mdhd_atom = (ngx_mp4_mdhd_atom_t *) atom->pos; mdhd64_atom = (ngx_mp4_mdhd64_atom_t *) atom->pos; if (mdhd_atom->version[0] == 0) { ngx_mp4_set_32value(mdhd_atom->duration, trak->duration); } else { ngx_mp4_set_64value(mdhd64_atom->duration, trak->duration); } trak->size += trak->mdhd_size; } static ngx_int_t ngx_http_mp4_read_hdlr_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { 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); if (trak->out[NGX_HTTP_MP4_HDLR_ATOM].buf) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "duplicate mp4 hdlr atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; } 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); if (trak->out[NGX_HTTP_MP4_MINF_ATOM].buf) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "duplicate mp4 minf atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; } 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); if (trak->out[NGX_HTTP_MP4_VMHD_ATOM].buf || trak->out[NGX_HTTP_MP4_SMHD_ATOM].buf) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "duplicate mp4 vmhd/smhd atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; } 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); if (trak->out[NGX_HTTP_MP4_VMHD_ATOM].buf || trak->out[NGX_HTTP_MP4_SMHD_ATOM].buf) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "duplicate mp4 vmhd/smhd atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; } 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); if (trak->out[NGX_HTTP_MP4_DINF_ATOM].buf) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "duplicate mp4 dinf atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; } 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); if (trak->out[NGX_HTTP_MP4_STBL_ATOM].buf) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "duplicate mp4 stbl atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; } 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_edts_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak) { ngx_buf_t *atom; ngx_mp4_elst_atom_t *elst_atom; ngx_mp4_edts_atom_t *edts_atom; if (trak->prefix == 0) { return; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 edts atom update prefix:%uL", trak->prefix); edts_atom = &trak->edts_atom; ngx_mp4_set_32value(edts_atom->size, sizeof(ngx_mp4_edts_atom_t) + sizeof(ngx_mp4_elst_atom_t)); ngx_mp4_set_atom_name(edts_atom, 'e', 'd', 't', 's'); atom = &trak->edts_atom_buf; atom->temporary = 1; atom->pos = (u_char *) edts_atom; atom->last = (u_char *) edts_atom + sizeof(ngx_mp4_edts_atom_t); trak->out[NGX_HTTP_MP4_EDTS_ATOM].buf = atom; elst_atom = &trak->elst_atom; ngx_mp4_set_32value(elst_atom->size, sizeof(ngx_mp4_elst_atom_t)); ngx_mp4_set_atom_name(elst_atom, 'e', 'l', 's', 't'); elst_atom->version[0] = 1; elst_atom->flags[0] = 0; elst_atom->flags[1] = 0; elst_atom->flags[2] = 0; ngx_mp4_set_32value(elst_atom->entries, 1); ngx_mp4_set_64value(elst_atom->duration, trak->movie_duration); ngx_mp4_set_64value(elst_atom->media_time, trak->prefix); ngx_mp4_set_16value(elst_atom->media_rate, 1); ngx_mp4_set_16value(elst_atom->reserved, 0); atom = &trak->elst_atom_buf; atom->temporary = 1; atom->pos = (u_char *) elst_atom; atom->last = (u_char *) elst_atom + sizeof(ngx_mp4_elst_atom_t); trak->out[NGX_HTTP_MP4_ELST_ATOM].buf = atom; trak->size += sizeof(ngx_mp4_edts_atom_t) + sizeof(ngx_mp4_elst_atom_t); } static void ngx_http_mp4_update_stbl_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak) { 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); if (trak->out[NGX_HTTP_MP4_STSD_ATOM].buf) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "duplicate mp4 stsd atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; } 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); if (trak->out[NGX_HTTP_MP4_STTS_ATOM].buf) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "duplicate mp4 stts atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; } 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, key_prefix; 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 + trak->prefix; 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 -= (uint64_t) 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) { key_prefix = ngx_http_mp4_seek_key_frame(mp4, trak, start_sample); start_sample -= key_prefix; while (rest < key_prefix) { trak->prefix += rest * duration; key_prefix -= rest; entry--; entries++; count = ngx_mp4_get_32value(entry->count); duration = ngx_mp4_get_32value(entry->duration); rest = count; } trak->prefix += key_prefix * duration; trak->duration += trak->prefix; rest -= key_prefix; ngx_mp4_set_32value(entry->count, count - rest); data->pos = (u_char *) entry; trak->time_to_sample_entries = entries; 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; } static uint32_t ngx_http_mp4_seek_key_frame(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak, uint32_t start_sample) { uint32_t key_prefix, sample, *entry, *end; ngx_buf_t *data; ngx_http_mp4_conf_t *conf; conf = ngx_http_get_module_loc_conf(mp4->request, ngx_http_mp4_module); if (!conf->start_key_frame) { return 0; } data = trak->out[NGX_HTTP_MP4_STSS_DATA].buf; if (data == NULL) { return 0; } entry = (uint32_t *) data->pos; end = (uint32_t *) data->last; /* sync samples starts from 1 */ start_sample++; key_prefix = 0; while (entry < end) { sample = ngx_mp4_get_32value(entry); if (sample > start_sample) { break; } key_prefix = start_sample - sample; entry++; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 key frame prefix:%uD", key_prefix); return key_prefix; } typedef struct { u_char size[4]; u_char name[4]; 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); if (trak->out[NGX_HTTP_MP4_STSS_ATOM].buf) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "duplicate mp4 stss atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; } 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); if (trak->out[NGX_HTTP_MP4_CTTS_ATOM].buf) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "duplicate mp4 ctts atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; } 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); if (trak->out[NGX_HTTP_MP4_STSC_ATOM].buf) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "duplicate mp4 stsc atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; } 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); if (trak->out[NGX_HTTP_MP4_STSZ_ATOM].buf) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "duplicate mp4 stsz atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; } 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 (trak->start_chunk_samples_size > (uint64_t) mp4->end) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "too large mp4 start samples size in \"%s\"", mp4->file.name.data); return NGX_ERROR; } 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); if (trak->end_chunk_samples_size > (uint64_t) mp4->end) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "too large mp4 end samples size in \"%s\"", mp4->file.name.data); return NGX_ERROR; } } 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); if (trak->out[NGX_HTTP_MP4_STCO_ATOM].buf || trak->out[NGX_HTTP_MP4_CO64_ATOM].buf) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "duplicate mp4 stco/co64 atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; } 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; uint64_t chunk_offset, samples_size; 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); chunk_offset = ngx_mp4_get_32value(data->pos); samples_size = trak->start_chunk_samples_size; if (chunk_offset > (uint64_t) mp4->end - samples_size || chunk_offset + samples_size > NGX_MAX_UINT32_VALUE) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "too large chunk offset in \"%s\"", mp4->file.name.data); return NGX_ERROR; } trak->start_offset = chunk_offset + 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) { chunk_offset = ngx_mp4_get_32value(data->last - sizeof(uint32_t)); samples_size = trak->end_chunk_samples_size; if (chunk_offset > (uint64_t) mp4->end - samples_size || chunk_offset + samples_size > NGX_MAX_UINT32_VALUE) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "too large chunk offset in \"%s\"", mp4->file.name.data); return NGX_ERROR; } trak->end_offset = chunk_offset + 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); if (trak->out[NGX_HTTP_MP4_STCO_ATOM].buf || trak->out[NGX_HTTP_MP4_CO64_ATOM].buf) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "duplicate mp4 stco/co64 atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; } 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, chunk_offset, samples_size; 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); chunk_offset = ngx_mp4_get_64value(data->pos); samples_size = trak->start_chunk_samples_size; if (chunk_offset > (uint64_t) mp4->end - samples_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "too large chunk offset in \"%s\"", mp4->file.name.data); return NGX_ERROR; } trak->start_offset = chunk_offset + 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) { chunk_offset = ngx_mp4_get_64value(data->last - sizeof(uint64_t)); samples_size = trak->end_chunk_samples_size; if (chunk_offset > (uint64_t) mp4->end - samples_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "too large chunk offset in \"%s\"", mp4->file.name.data); return NGX_ERROR; } trak->end_offset = chunk_offset + 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; conf->start_key_frame = NGX_CONF_UNSET; 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); ngx_conf_merge_value(conf->start_key_frame, prev->start_key_frame, 0); return NGX_CONF_OK; } nginx-1.24.0/src/http/modules/ngx_http_not_modified_filter_module.c000644 001751 001751 00000015500 14415135676 026773 0ustar00mdouninmdounin000000 000000 /* * 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; } nginx-1.24.0/src/http/modules/ngx_http_proxy_module.c000644 001751 001751 00000430467 14415135676 024164 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ #include #include #include #define NGX_HTTP_PROXY_COOKIE_SECURE 0x0001 #define NGX_HTTP_PROXY_COOKIE_SECURE_ON 0x0002 #define NGX_HTTP_PROXY_COOKIE_SECURE_OFF 0x0004 #define NGX_HTTP_PROXY_COOKIE_HTTPONLY 0x0008 #define NGX_HTTP_PROXY_COOKIE_HTTPONLY_ON 0x0010 #define NGX_HTTP_PROXY_COOKIE_HTTPONLY_OFF 0x0020 #define NGX_HTTP_PROXY_COOKIE_SAMESITE 0x0040 #define NGX_HTTP_PROXY_COOKIE_SAMESITE_STRICT 0x0080 #define NGX_HTTP_PROXY_COOKIE_SAMESITE_LAX 0x0100 #define NGX_HTTP_PROXY_COOKIE_SAMESITE_NONE 0x0200 #define NGX_HTTP_PROXY_COOKIE_SAMESITE_OFF 0x0400 typedef struct { ngx_array_t caches; /* ngx_http_file_cache_t * */ } ngx_http_proxy_main_conf_t; 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_str_t *value, 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 { union { ngx_http_complex_value_t complex; #if (NGX_PCRE) ngx_http_regex_t *regex; #endif } cookie; ngx_array_t flags_values; ngx_uint_t regex; } ngx_http_proxy_cookie_flags_t; typedef struct { ngx_str_t key_start; ngx_str_t schema; 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_array_t *cookie_flags; ngx_http_complex_value_t *method; ngx_str_t location; ngx_str_t url; #if (NGX_HTTP_CACHE) ngx_http_complex_value_t cache_key; #endif ngx_http_proxy_vars_t vars; ngx_flag_t redirect; ngx_uint_t http_version; ngx_uint_t headers_hash_max_size; ngx_uint_t headers_hash_bucket_size; #if (NGX_HTTP_SSL) ngx_uint_t ssl; ngx_uint_t ssl_protocols; ngx_str_t ssl_ciphers; ngx_uint_t ssl_verify_depth; ngx_str_t ssl_trusted_certificate; ngx_str_t ssl_crl; ngx_array_t *ssl_conf_commands; #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_parse_cookie(ngx_str_t *value, ngx_array_t *attrs); static ngx_int_t ngx_http_proxy_rewrite_cookie_value(ngx_http_request_t *r, ngx_str_t *value, ngx_array_t *rewrites); static ngx_int_t ngx_http_proxy_rewrite_cookie_flags(ngx_http_request_t *r, ngx_array_t *attrs, ngx_array_t *flags); static ngx_int_t ngx_http_proxy_edit_cookie_flags(ngx_http_request_t *r, ngx_array_t *attrs, ngx_uint_t flags); static ngx_int_t ngx_http_proxy_rewrite(ngx_http_request_t *r, ngx_str_t *value, size_t prefix, size_t len, ngx_str_t *replacement); static ngx_int_t ngx_http_proxy_add_variables(ngx_conf_t *cf); static void *ngx_http_proxy_create_main_conf(ngx_conf_t *cf); 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_cookie_flags(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_proxy_store(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); #if (NGX_HTTP_CACHE) 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); #if (NGX_HTTP_SSL) static char *ngx_http_proxy_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data); #endif static ngx_int_t ngx_http_proxy_rewrite_regex(ngx_conf_t *cf, ngx_http_proxy_rewrite_t *pr, ngx_str_t *regex, ngx_uint_t caseless); #if (NGX_HTTP_SSL) static ngx_int_t ngx_http_proxy_merge_ssl(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *conf, ngx_http_proxy_loc_conf_t *prev); static ngx_int_t ngx_http_proxy_set_ssl(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *plcf); #endif static void ngx_http_proxy_set_vars(ngx_url_t *u, ngx_http_proxy_vars_t *v); static ngx_conf_post_t ngx_http_proxy_lowat_post = { ngx_http_proxy_lowat_check }; static ngx_conf_bitmask_t ngx_http_proxy_next_upstream_masks[] = { { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR }, { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT }, { ngx_string("invalid_header"), NGX_HTTP_UPSTREAM_FT_INVALID_HEADER }, { ngx_string("non_idempotent"), NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT }, { ngx_string("http_500"), NGX_HTTP_UPSTREAM_FT_HTTP_500 }, { ngx_string("http_502"), NGX_HTTP_UPSTREAM_FT_HTTP_502 }, { ngx_string("http_503"), NGX_HTTP_UPSTREAM_FT_HTTP_503 }, { ngx_string("http_504"), NGX_HTTP_UPSTREAM_FT_HTTP_504 }, { ngx_string("http_403"), NGX_HTTP_UPSTREAM_FT_HTTP_403 }, { ngx_string("http_404"), NGX_HTTP_UPSTREAM_FT_HTTP_404 }, { ngx_string("http_429"), NGX_HTTP_UPSTREAM_FT_HTTP_429 }, { ngx_string("updating"), NGX_HTTP_UPSTREAM_FT_UPDATING }, { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF }, { ngx_null_string, 0 } }; #if (NGX_HTTP_SSL) static ngx_conf_bitmask_t ngx_http_proxy_ssl_protocols[] = { { ngx_string("SSLv2"), NGX_SSL_SSLv2 }, { ngx_string("SSLv3"), NGX_SSL_SSLv3 }, { ngx_string("TLSv1"), NGX_SSL_TLSv1 }, { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 }, { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 }, { ngx_string("TLSv1.3"), NGX_SSL_TLSv1_3 }, { ngx_null_string, 0 } }; static ngx_conf_post_t ngx_http_proxy_ssl_conf_command_post = { ngx_http_proxy_ssl_conf_command_check }; #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_cookie_flags"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234, ngx_http_proxy_cookie_flags, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("proxy_store"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_proxy_store, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("proxy_store_access"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123, ngx_conf_set_access_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.store_access), NULL }, { ngx_string("proxy_buffering"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.buffering), NULL }, { ngx_string("proxy_request_buffering"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.request_buffering), NULL }, { ngx_string("proxy_ignore_client_abort"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.ignore_client_abort), NULL }, { ngx_string("proxy_bind"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12, ngx_http_upstream_bind_set_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.local), NULL }, { ngx_string("proxy_socket_keepalive"), 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.socket_keepalive), NULL }, { ngx_string("proxy_connect_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.connect_timeout), NULL }, { ngx_string("proxy_send_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.send_timeout), NULL }, { ngx_string("proxy_send_lowat"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.send_lowat), &ngx_http_proxy_lowat_post }, { ngx_string("proxy_intercept_errors"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.intercept_errors), NULL }, { ngx_string("proxy_set_header"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, ngx_conf_set_keyval_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, headers_source), NULL }, { ngx_string("proxy_headers_hash_max_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, headers_hash_max_size), NULL }, { ngx_string("proxy_headers_hash_bucket_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, headers_hash_bucket_size), NULL }, { ngx_string("proxy_set_body"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, body_source), NULL }, { ngx_string("proxy_method"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_set_complex_value_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, method), NULL }, { ngx_string("proxy_pass_request_headers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.pass_request_headers), NULL }, { ngx_string("proxy_pass_request_body"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.pass_request_body), NULL }, { ngx_string("proxy_buffer_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.buffer_size), NULL }, { ngx_string("proxy_read_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.read_timeout), NULL }, { ngx_string("proxy_buffers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, ngx_conf_set_bufs_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.bufs), NULL }, { ngx_string("proxy_busy_buffers_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.busy_buffers_size_conf), NULL }, { ngx_string("proxy_force_ranges"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.force_ranges), NULL }, { ngx_string("proxy_limit_rate"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.limit_rate), NULL }, #if (NGX_HTTP_CACHE) { ngx_string("proxy_cache"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_proxy_cache, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("proxy_cache_key"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_proxy_cache_key, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("proxy_cache_path"), NGX_HTTP_MAIN_CONF|NGX_CONF_2MORE, ngx_http_file_cache_set_slot, NGX_HTTP_MAIN_CONF_OFFSET, offsetof(ngx_http_proxy_main_conf_t, caches), &ngx_http_proxy_module }, { ngx_string("proxy_cache_bypass"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_set_predicate_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_bypass), NULL }, { ngx_string("proxy_no_cache"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_set_predicate_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.no_cache), NULL }, { ngx_string("proxy_cache_valid"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_file_cache_valid_set_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_valid), NULL }, { ngx_string("proxy_cache_min_uses"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_min_uses), NULL }, { ngx_string("proxy_cache_max_range_offset"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_off_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_max_range_offset), NULL }, { ngx_string("proxy_cache_use_stale"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_use_stale), &ngx_http_proxy_next_upstream_masks }, { ngx_string("proxy_cache_methods"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_methods), &ngx_http_upstream_cache_method_mask }, { ngx_string("proxy_cache_lock"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_lock), NULL }, { ngx_string("proxy_cache_lock_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_lock_timeout), NULL }, { ngx_string("proxy_cache_lock_age"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_lock_age), NULL }, { ngx_string("proxy_cache_revalidate"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_revalidate), NULL }, { ngx_string("proxy_cache_convert_head"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_convert_head), NULL }, { ngx_string("proxy_cache_background_update"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_background_update), NULL }, #endif { ngx_string("proxy_temp_path"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234, ngx_conf_set_path_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.temp_path), NULL }, { ngx_string("proxy_max_temp_file_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.max_temp_file_size_conf), NULL }, { ngx_string("proxy_temp_file_write_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.temp_file_write_size_conf), NULL }, { ngx_string("proxy_next_upstream"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.next_upstream), &ngx_http_proxy_next_upstream_masks }, { ngx_string("proxy_next_upstream_tries"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.next_upstream_tries), NULL }, { ngx_string("proxy_next_upstream_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.next_upstream_timeout), NULL }, { ngx_string("proxy_pass_header"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_array_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.pass_headers), NULL }, { ngx_string("proxy_hide_header"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_array_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.hide_headers), NULL }, { ngx_string("proxy_ignore_headers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.ignore_headers), &ngx_http_upstream_ignore_headers_masks }, { ngx_string("proxy_http_version"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_enum_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, http_version), &ngx_http_proxy_http_version }, #if (NGX_HTTP_SSL) { ngx_string("proxy_ssl_session_reuse"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_session_reuse), NULL }, { ngx_string("proxy_ssl_protocols"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, ssl_protocols), &ngx_http_proxy_ssl_protocols }, { ngx_string("proxy_ssl_ciphers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, ssl_ciphers), NULL }, { ngx_string("proxy_ssl_name"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_set_complex_value_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_name), NULL }, { ngx_string("proxy_ssl_server_name"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_server_name), NULL }, { ngx_string("proxy_ssl_verify"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_verify), NULL }, { ngx_string("proxy_ssl_verify_depth"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, ssl_verify_depth), NULL }, { ngx_string("proxy_ssl_trusted_certificate"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, ssl_trusted_certificate), NULL }, { ngx_string("proxy_ssl_crl"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, ssl_crl), NULL }, { ngx_string("proxy_ssl_certificate"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_set_complex_value_zero_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_certificate), NULL }, { ngx_string("proxy_ssl_certificate_key"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_set_complex_value_zero_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.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 }, { ngx_string("proxy_ssl_conf_command"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, ngx_conf_set_keyval_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, ssl_conf_commands), &ngx_http_proxy_ssl_conf_command_post }, #endif ngx_null_command }; static ngx_http_module_t ngx_http_proxy_module_ctx = { ngx_http_proxy_add_variables, /* preconfiguration */ NULL, /* postconfiguration */ ngx_http_proxy_create_main_conf, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_proxy_create_loc_conf, /* create location configuration */ ngx_http_proxy_merge_loc_conf /* merge location configuration */ }; ngx_module_t ngx_http_proxy_module = { NGX_MODULE_V1, &ngx_http_proxy_module_ctx, /* module context */ ngx_http_proxy_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static char ngx_http_proxy_version[] = " HTTP/1.0" CRLF; static char ngx_http_proxy_version_11[] = " HTTP/1.1" CRLF; static ngx_keyval_t ngx_http_proxy_headers[] = { { ngx_string("Host"), ngx_string("$proxy_host") }, { ngx_string("Connection"), ngx_string("close") }, { ngx_string("Content-Length"), ngx_string("$proxy_internal_body_length") }, { ngx_string("Transfer-Encoding"), ngx_string("$proxy_internal_chunked") }, { ngx_string("TE"), ngx_string("") }, { ngx_string("Keep-Alive"), ngx_string("") }, { ngx_string("Expect"), ngx_string("") }, { ngx_string("Upgrade"), ngx_string("") }, { ngx_null_string, ngx_null_string } }; static ngx_str_t ngx_http_proxy_hide_headers[] = { ngx_string("Date"), ngx_string("Server"), ngx_string("X-Pad"), ngx_string("X-Accel-Expires"), ngx_string("X-Accel-Redirect"), ngx_string("X-Accel-Limit-Rate"), ngx_string("X-Accel-Buffering"), ngx_string("X-Accel-Charset"), ngx_null_string }; #if (NGX_HTTP_CACHE) static ngx_keyval_t ngx_http_proxy_cache_headers[] = { { ngx_string("Host"), ngx_string("$proxy_host") }, { ngx_string("Connection"), ngx_string("close") }, { ngx_string("Content-Length"), ngx_string("$proxy_internal_body_length") }, { ngx_string("Transfer-Encoding"), ngx_string("$proxy_internal_chunked") }, { ngx_string("TE"), ngx_string("") }, { ngx_string("Keep-Alive"), ngx_string("") }, { ngx_string("Expect"), ngx_string("") }, { ngx_string("Upgrade"), ngx_string("") }, { ngx_string("If-Modified-Since"), ngx_string("$upstream_cache_last_modified") }, { ngx_string("If-Unmodified-Since"), ngx_string("") }, { ngx_string("If-None-Match"), ngx_string("$upstream_cache_etag") }, { ngx_string("If-Match"), ngx_string("") }, { ngx_string("Range"), ngx_string("") }, { ngx_string("If-Range"), ngx_string("") }, { ngx_null_string, ngx_null_string } }; #endif static ngx_http_variable_t ngx_http_proxy_vars[] = { { ngx_string("proxy_host"), NULL, ngx_http_proxy_host_variable, 0, NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 }, { ngx_string("proxy_port"), NULL, ngx_http_proxy_port_variable, 0, NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 }, { ngx_string("proxy_add_x_forwarded_for"), NULL, ngx_http_proxy_add_x_forwarded_for_variable, 0, NGX_HTTP_VAR_NOHASH, 0 }, #if 0 { ngx_string("proxy_add_via"), NULL, NULL, 0, NGX_HTTP_VAR_NOHASH, 0 }, #endif { ngx_string("proxy_internal_body_length"), NULL, ngx_http_proxy_internal_body_length_variable, 0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 }, { ngx_string("proxy_internal_chunked"), NULL, ngx_http_proxy_internal_chunked_variable, 0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 }, ngx_http_null_variable }; static ngx_path_init_t ngx_http_proxy_temp_path = { ngx_string(NGX_HTTP_PROXY_TEMP_PATH), { 1, 2, 0 } }; static ngx_conf_bitmask_t ngx_http_proxy_cookie_flags_masks[] = { { ngx_string("secure"), NGX_HTTP_PROXY_COOKIE_SECURE|NGX_HTTP_PROXY_COOKIE_SECURE_ON }, { ngx_string("nosecure"), NGX_HTTP_PROXY_COOKIE_SECURE|NGX_HTTP_PROXY_COOKIE_SECURE_OFF }, { ngx_string("httponly"), NGX_HTTP_PROXY_COOKIE_HTTPONLY|NGX_HTTP_PROXY_COOKIE_HTTPONLY_ON }, { ngx_string("nohttponly"), NGX_HTTP_PROXY_COOKIE_HTTPONLY|NGX_HTTP_PROXY_COOKIE_HTTPONLY_OFF }, { ngx_string("samesite=strict"), NGX_HTTP_PROXY_COOKIE_SAMESITE|NGX_HTTP_PROXY_COOKIE_SAMESITE_STRICT }, { ngx_string("samesite=lax"), NGX_HTTP_PROXY_COOKIE_SAMESITE|NGX_HTTP_PROXY_COOKIE_SAMESITE_LAX }, { ngx_string("samesite=none"), NGX_HTTP_PROXY_COOKIE_SAMESITE|NGX_HTTP_PROXY_COOKIE_SAMESITE_NONE }, { ngx_string("nosamesite"), NGX_HTTP_PROXY_COOKIE_SAMESITE|NGX_HTTP_PROXY_COOKIE_SAMESITE_OFF }, { ngx_null_string, 0 } }; static ngx_int_t ngx_http_proxy_handler(ngx_http_request_t *r) { 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->ssl; #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 || plcf->cookie_flags) { u->rewrite_cookie = ngx_http_proxy_rewrite_cookie; } u->buffering = plcf->upstream.buffering; u->pipe = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t)); if (u->pipe == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } u->pipe->input_filter = ngx_http_proxy_copy_filter; u->pipe->input_ctx = r; u->input_filter_init = ngx_http_proxy_input_filter_init; u->input_filter = ngx_http_proxy_non_buffered_copy_filter; u->input_filter_ctx = r; u->accel = 1; if (!plcf->upstream.request_buffering && plcf->body_values == NULL && plcf->upstream.pass_request_body && (!r->headers_in.chunked || plcf->http_version == NGX_HTTP_VERSION_11)) { r->request_body_no_buffering = 1; } rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init); if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { return rc; } return NGX_DONE; } static ngx_int_t ngx_http_proxy_eval(ngx_http_request_t *r, ngx_http_proxy_ctx_t *ctx, ngx_http_proxy_loc_conf_t *plcf) { u_char *p; size_t add; u_short port; ngx_str_t proxy; ngx_url_t url; ngx_http_upstream_t *u; if (ngx_http_script_run(r, &proxy, plcf->proxy_lengths->elts, 0, plcf->proxy_values->elts) == NULL) { return NGX_ERROR; } if (proxy.len > 7 && ngx_strncasecmp(proxy.data, (u_char *) "http://", 7) == 0) { add = 7; port = 80; #if (NGX_HTTP_SSL) } else if (proxy.len > 8 && ngx_strncasecmp(proxy.data, (u_char *) "https://", 8) == 0) { add = 8; port = 443; r->upstream->ssl = 1; #endif } else { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid URL prefix in \"%V\"", &proxy); return NGX_ERROR; } u = r->upstream; u->schema.len = add; u->schema.data = proxy.data; ngx_memzero(&url, sizeof(ngx_url_t)); url.url.len = proxy.len - add; url.url.data = proxy.data + add; url.default_port = port; url.uri_part = 1; url.no_resolve = 1; if (ngx_parse_url(r->pool, &url) != NGX_OK) { if (url.err) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "%s in upstream \"%V\"", url.err, &url.url); } return NGX_ERROR; } if (url.uri.len) { if (url.uri.data[0] == '?') { p = ngx_pnalloc(r->pool, url.uri.len + 1); if (p == NULL) { return NGX_ERROR; } *p++ = '/'; ngx_memcpy(p, url.uri.data, url.uri.len); url.uri.len++; url.uri.data = p - 1; } } ctx->vars.key_start = u->schema; ngx_http_proxy_set_vars(&url, &ctx->vars); u->resolved = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t)); if (u->resolved == NULL) { return NGX_ERROR; } if (url.addrs) { u->resolved->sockaddr = url.addrs[0].sockaddr; u->resolved->socklen = url.addrs[0].socklen; u->resolved->name = url.addrs[0].name; u->resolved->naddrs = 1; } u->resolved->host = url.host; u->resolved->port = (in_port_t) (url.no_port ? port : url.port); u->resolved->no_port = url.no_port; return NGX_OK; } #if (NGX_HTTP_CACHE) static ngx_int_t ngx_http_proxy_create_key(ngx_http_request_t *r) { size_t len, loc_len; u_char *p; uintptr_t escape; ngx_str_t *key; ngx_http_upstream_t *u; ngx_http_proxy_ctx_t *ctx; ngx_http_proxy_loc_conf_t *plcf; u = r->upstream; plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); key = ngx_array_push(&r->cache->keys); if (key == NULL) { return NGX_ERROR; } if (plcf->cache_key.value.data) { if (ngx_http_complex_value(r, &plcf->cache_key, key) != NGX_OK) { return NGX_ERROR; } return NGX_OK; } *key = ctx->vars.key_start; key = ngx_array_push(&r->cache->keys); if (key == NULL) { return NGX_ERROR; } if (plcf->proxy_lengths && ctx->vars.uri.len) { *key = ctx->vars.uri; u->uri = ctx->vars.uri; return NGX_OK; } else if (ctx->vars.uri.len == 0 && r->valid_unparsed_uri) { *key = r->unparsed_uri; u->uri = r->unparsed_uri; return NGX_OK; } loc_len = (r->valid_location && ctx->vars.uri.len) ? plcf->location.len : 0; if (r->quoted_uri || r->internal) { escape = 2 * ngx_escape_uri(NULL, r->uri.data + loc_len, r->uri.len - loc_len, NGX_ESCAPE_URI); } else { escape = 0; } len = ctx->vars.uri.len + r->uri.len - loc_len + escape + sizeof("?") - 1 + r->args.len; p = ngx_pnalloc(r->pool, len); if (p == NULL) { return NGX_ERROR; } key->data = p; if (r->valid_location) { p = ngx_copy(p, ctx->vars.uri.data, ctx->vars.uri.len); } if (escape) { ngx_escape_uri(p, r->uri.data + loc_len, r->uri.len - loc_len, NGX_ESCAPE_URI); p += r->uri.len - loc_len + escape; } else { p = ngx_copy(p, r->uri.data + loc_len, r->uri.len - loc_len); } if (r->args.len > 0) { *p++ = '?'; p = ngx_copy(p, r->args.data, r->args.len); } key->len = p - key->data; u->uri = *key; return NGX_OK; } #endif static ngx_int_t ngx_http_proxy_create_request(ngx_http_request_t *r) { size_t len, uri_len, loc_len, body_len, key_len, val_len; uintptr_t escape; ngx_buf_t *b; ngx_str_t method; ngx_uint_t i, unparsed_uri; ngx_chain_t *cl, *body; ngx_list_part_t *part; ngx_table_elt_t *header; ngx_http_upstream_t *u; ngx_http_proxy_ctx_t *ctx; ngx_http_script_code_pt code; ngx_http_proxy_headers_t *headers; ngx_http_script_engine_t e, le; ngx_http_proxy_loc_conf_t *plcf; ngx_http_script_len_code_pt lcode; u = r->upstream; plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); #if (NGX_HTTP_CACHE) headers = u->cacheable ? &plcf->headers_cache : &plcf->headers; #else headers = &plcf->headers; #endif if (u->method.len) { /* HEAD was changed to GET to cache response */ method = u->method; } else if (plcf->method) { if (ngx_http_complex_value(r, plcf->method, &method) != NGX_OK) { return NGX_ERROR; } } else { method = r->method_name; } ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); if (method.len == 4 && ngx_strncasecmp(method.data, (u_char *) "HEAD", 4) == 0) { ctx->head = 1; } len = method.len + 1 + sizeof(ngx_http_proxy_version) - 1 + sizeof(CRLF) - 1; escape = 0; loc_len = 0; unparsed_uri = 0; if (plcf->proxy_lengths && ctx->vars.uri.len) { uri_len = ctx->vars.uri.len; } else if (ctx->vars.uri.len == 0 && r->valid_unparsed_uri) { unparsed_uri = 1; uri_len = r->unparsed_uri.len; } else { loc_len = (r->valid_location && ctx->vars.uri.len) ? plcf->location.len : 0; if (r->quoted_uri || r->internal) { escape = 2 * ngx_escape_uri(NULL, r->uri.data + loc_len, r->uri.len - loc_len, NGX_ESCAPE_URI); } uri_len = ctx->vars.uri.len + r->uri.len - loc_len + escape + sizeof("?") - 1 + r->args.len; } if (uri_len == 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "zero length URI to proxy"); return NGX_ERROR; } len += uri_len; ngx_memzero(&le, sizeof(ngx_http_script_engine_t)); ngx_http_script_flush_no_cacheable_variables(r, plcf->body_flushes); ngx_http_script_flush_no_cacheable_variables(r, headers->flushes); if (plcf->body_lengths) { le.ip = plcf->body_lengths->elts; le.request = r; le.flushed = 1; body_len = 0; while (*(uintptr_t *) le.ip) { lcode = *(ngx_http_script_len_code_pt *) le.ip; body_len += lcode(&le); } ctx->internal_body_length = body_len; len += body_len; } else if (r->headers_in.chunked && r->reading_body) { ctx->internal_body_length = -1; ctx->internal_chunked = 1; } else { ctx->internal_body_length = r->headers_in.content_length_n; } le.ip = headers->lengths->elts; le.request = r; le.flushed = 1; while (*(uintptr_t *) le.ip) { lcode = *(ngx_http_script_len_code_pt *) le.ip; key_len = lcode(&le); for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) { lcode = *(ngx_http_script_len_code_pt *) le.ip; } le.ip += sizeof(uintptr_t); if (val_len == 0) { continue; } len += key_len + sizeof(": ") - 1 + val_len + sizeof(CRLF) - 1; } if (plcf->upstream.pass_request_headers) { part = &r->headers_in.headers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (ngx_hash_find(&headers->hash, header[i].hash, header[i].lowcase_key, header[i].key.len)) { continue; } len += header[i].key.len + sizeof(": ") - 1 + header[i].value.len + sizeof(CRLF) - 1; } } b = ngx_create_temp_buf(r->pool, len); if (b == NULL) { return NGX_ERROR; } cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_ERROR; } cl->buf = b; /* the request line */ b->last = ngx_copy(b->last, method.data, method.len); *b->last++ = ' '; u->uri.data = b->last; if (plcf->proxy_lengths && ctx->vars.uri.len) { b->last = ngx_copy(b->last, ctx->vars.uri.data, ctx->vars.uri.len); } else if (unparsed_uri) { b->last = ngx_copy(b->last, r->unparsed_uri.data, r->unparsed_uri.len); } else { if (r->valid_location) { b->last = ngx_copy(b->last, ctx->vars.uri.data, ctx->vars.uri.len); } if (escape) { ngx_escape_uri(b->last, r->uri.data + loc_len, r->uri.len - loc_len, NGX_ESCAPE_URI); b->last += r->uri.len - loc_len + escape; } else { b->last = ngx_copy(b->last, r->uri.data + loc_len, r->uri.len - loc_len); } if (r->args.len > 0) { *b->last++ = '?'; b->last = ngx_copy(b->last, r->args.data, r->args.len); } } u->uri.len = b->last - u->uri.data; if (plcf->http_version == NGX_HTTP_VERSION_11) { b->last = ngx_cpymem(b->last, ngx_http_proxy_version_11, sizeof(ngx_http_proxy_version_11) - 1); } else { b->last = ngx_cpymem(b->last, ngx_http_proxy_version, sizeof(ngx_http_proxy_version) - 1); } ngx_memzero(&e, sizeof(ngx_http_script_engine_t)); e.ip = headers->values->elts; e.pos = b->last; e.request = r; e.flushed = 1; le.ip = headers->lengths->elts; while (*(uintptr_t *) le.ip) { lcode = *(ngx_http_script_len_code_pt *) le.ip; (void) lcode(&le); for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) { lcode = *(ngx_http_script_len_code_pt *) le.ip; } le.ip += sizeof(uintptr_t); if (val_len == 0) { e.skip = 1; while (*(uintptr_t *) e.ip) { code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); } e.ip += sizeof(uintptr_t); e.skip = 0; continue; } code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); *e.pos++ = ':'; *e.pos++ = ' '; while (*(uintptr_t *) e.ip) { code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); } e.ip += sizeof(uintptr_t); *e.pos++ = CR; *e.pos++ = LF; } b->last = e.pos; if (plcf->upstream.pass_request_headers) { part = &r->headers_in.headers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (ngx_hash_find(&headers->hash, header[i].hash, header[i].lowcase_key, header[i].key.len)) { continue; } b->last = ngx_copy(b->last, header[i].key.data, header[i].key.len); *b->last++ = ':'; *b->last++ = ' '; b->last = ngx_copy(b->last, header[i].value.data, header[i].value.len); *b->last++ = CR; *b->last++ = LF; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http proxy header: \"%V: %V\"", &header[i].key, &header[i].value); } } /* add "\r\n" at the header end */ *b->last++ = CR; *b->last++ = LF; if (plcf->body_values) { e.ip = plcf->body_values->elts; e.pos = b->last; e.skip = 0; while (*(uintptr_t *) e.ip) { code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); } b->last = e.pos; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http proxy header:%N\"%*s\"", (size_t) (b->last - b->pos), b->pos); if (r->request_body_no_buffering) { u->request_bufs = cl; if (ctx->internal_chunked) { u->output.output_filter = ngx_http_proxy_body_output_filter; u->output.filter_ctx = r; } } else if (plcf->body_values == NULL && plcf->upstream.pass_request_body) { body = u->request_bufs; u->request_bufs = cl; while (body) { b = ngx_alloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } ngx_memcpy(b, body->buf, sizeof(ngx_buf_t)); cl->next = ngx_alloc_chain_link(r->pool); if (cl->next == NULL) { return NGX_ERROR; } cl = cl->next; cl->buf = b; body = body->next; } } else { u->request_bufs = cl; } b->flush = 1; cl->next = NULL; return NGX_OK; } static ngx_int_t ngx_http_proxy_reinit_request(ngx_http_request_t *r) { ngx_http_proxy_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); if (ctx == NULL) { return NGX_OK; } ctx->status.code = 0; ctx->status.count = 0; ctx->status.start = NULL; ctx->status.end = NULL; ctx->chunked.state = 0; r->upstream->process_header = ngx_http_proxy_process_status_line; r->upstream->pipe->input_filter = ngx_http_proxy_copy_filter; r->upstream->input_filter = ngx_http_proxy_non_buffered_copy_filter; r->state = 0; return NGX_OK; } static ngx_int_t ngx_http_proxy_body_output_filter(void *data, ngx_chain_t *in) { ngx_http_request_t *r = data; off_t size; u_char *chunk; ngx_int_t rc; ngx_buf_t *b; ngx_chain_t *out, *cl, *tl, **ll, **fl; ngx_http_proxy_ctx_t *ctx; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "proxy output filter"); ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); if (in == NULL) { out = in; goto out; } out = NULL; ll = &out; if (!ctx->header_sent) { /* first buffer contains headers, pass it unmodified */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "proxy output header"); ctx->header_sent = 1; tl = ngx_alloc_chain_link(r->pool); if (tl == NULL) { return NGX_ERROR; } tl->buf = in->buf; *ll = tl; ll = &tl->next; in = in->next; if (in == NULL) { tl->next = NULL; goto out; } } size = 0; cl = in; fl = ll; for ( ;; ) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "proxy output chunk: %O", ngx_buf_size(cl->buf)); size += ngx_buf_size(cl->buf); if (cl->buf->flush || cl->buf->sync || ngx_buf_in_memory(cl->buf) || cl->buf->in_file) { tl = ngx_alloc_chain_link(r->pool); if (tl == NULL) { return NGX_ERROR; } tl->buf = cl->buf; *ll = tl; ll = &tl->next; } if (cl->next == NULL) { break; } cl = cl->next; } if (size) { tl = ngx_chain_get_free_buf(r->pool, &ctx->free); if (tl == NULL) { return NGX_ERROR; } b = tl->buf; chunk = b->start; if (chunk == NULL) { /* the "0000000000000000" is 64-bit hexadecimal string */ chunk = ngx_palloc(r->pool, sizeof("0000000000000000" CRLF) - 1); if (chunk == NULL) { return NGX_ERROR; } b->start = chunk; b->end = chunk + sizeof("0000000000000000" CRLF) - 1; } b->tag = (ngx_buf_tag_t) &ngx_http_proxy_body_output_filter; b->memory = 0; b->temporary = 1; b->pos = chunk; b->last = ngx_sprintf(chunk, "%xO" CRLF, size); tl->next = *fl; *fl = tl; } if (cl->buf->last_buf) { tl = ngx_chain_get_free_buf(r->pool, &ctx->free); if (tl == NULL) { return NGX_ERROR; } b = tl->buf; b->tag = (ngx_buf_tag_t) &ngx_http_proxy_body_output_filter; b->temporary = 0; b->memory = 1; b->last_buf = 1; b->pos = (u_char *) CRLF "0" CRLF CRLF; b->last = b->pos + 7; cl->buf->last_buf = 0; *ll = tl; if (size == 0) { b->pos += 2; } } else if (size > 0) { tl = ngx_chain_get_free_buf(r->pool, &ctx->free); if (tl == NULL) { return NGX_ERROR; } b = tl->buf; b->tag = (ngx_buf_tag_t) &ngx_http_proxy_body_output_filter; b->temporary = 0; b->memory = 1; b->pos = (u_char *) CRLF; b->last = b->pos + 2; *ll = tl; } else { *ll = NULL; } out: rc = ngx_chain_writer(&r->upstream->writer, out); ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, (ngx_buf_tag_t) &ngx_http_proxy_body_output_filter); return rc; } static ngx_int_t ngx_http_proxy_process_status_line(ngx_http_request_t *r) { size_t len; ngx_int_t rc; ngx_http_upstream_t *u; ngx_http_proxy_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); if (ctx == NULL) { return NGX_ERROR; } u = r->upstream; rc = ngx_http_parse_status_line(r, &u->buffer, &ctx->status); if (rc == NGX_AGAIN) { return rc; } if (rc == NGX_ERROR) { #if (NGX_HTTP_CACHE) if (r->cache) { r->http_version = NGX_HTTP_VERSION_9; return NGX_OK; } #endif ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent no valid HTTP/1.0 header"); #if 0 if (u->accel) { return NGX_HTTP_UPSTREAM_INVALID_HEADER; } #endif r->http_version = NGX_HTTP_VERSION_9; u->state->status = NGX_HTTP_OK; u->headers_in.connection_close = 1; return NGX_OK; } if (u->state && u->state->status == 0) { u->state->status = ctx->status.code; } u->headers_in.status_n = ctx->status.code; len = ctx->status.end - ctx->status.start; u->headers_in.status_line.len = len; u->headers_in.status_line.data = ngx_pnalloc(r->pool, len); if (u->headers_in.status_line.data == NULL) { return NGX_ERROR; } ngx_memcpy(u->headers_in.status_line.data, ctx->status.start, len); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http proxy status %ui \"%V\"", u->headers_in.status_n, &u->headers_in.status_line); if (ctx->status.http_version < NGX_HTTP_VERSION_11) { u->headers_in.connection_close = 1; } u->process_header = ngx_http_proxy_process_header; return ngx_http_proxy_process_header(r); } static ngx_int_t ngx_http_proxy_process_header(ngx_http_request_t *r) { ngx_int_t rc; ngx_table_elt_t *h; ngx_http_upstream_t *u; ngx_http_proxy_ctx_t *ctx; ngx_http_upstream_header_t *hh; ngx_http_upstream_main_conf_t *umcf; umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module); for ( ;; ) { rc = ngx_http_parse_header_line(r, &r->upstream->buffer, 1); if (rc == NGX_OK) { /* a header line has been parsed successfully */ h = ngx_list_push(&r->upstream->headers_in.headers); if (h == NULL) { return NGX_ERROR; } h->hash = r->header_hash; h->key.len = r->header_name_end - r->header_name_start; h->value.len = r->header_end - r->header_start; h->key.data = ngx_pnalloc(r->pool, h->key.len + 1 + h->value.len + 1 + h->key.len); if (h->key.data == NULL) { h->hash = 0; return NGX_ERROR; } h->value.data = h->key.data + h->key.len + 1; h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1; ngx_memcpy(h->key.data, r->header_name_start, h->key.len); h->key.data[h->key.len] = '\0'; ngx_memcpy(h->value.data, r->header_start, h->value.len); h->value.data[h->value.len] = '\0'; if (h->key.len == r->lowcase_index) { ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len); } else { ngx_strlow(h->lowcase_key, h->key.data, h->key.len); } hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len); if (hh) { rc = hh->handler(r, h, hh->offset); if (rc != NGX_OK) { return rc; } } 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"; h->next = NULL; } 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"; h->next = NULL; } /* 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; } /* rc == NGX_HTTP_PARSE_INVALID_HEADER */ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid header: \"%*s\\x%02xd...\"", r->header_end - r->header_name_start, r->header_name_start, *r->header_end); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } } static ngx_int_t ngx_http_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; } if (p->upstream_done) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, p->log, 0, "http proxy data after close"); return NGX_OK; } if (p->length == 0) { ngx_log_error(NGX_LOG_WARN, p->log, 0, "upstream sent more data than specified in " "\"Content-Length\" header"); r = p->input_ctx; r->upstream->keepalive = 0; p->upstream_done = 1; return NGX_OK; } cl = ngx_chain_get_free_buf(p->pool, &p->free); if (cl == NULL) { return NGX_ERROR; } 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; } if (b->last - b->pos > p->length) { ngx_log_error(NGX_LOG_WARN, p->log, 0, "upstream sent more data than specified in " "\"Content-Length\" header"); b->last = b->pos + p->length; p->upstream_done = 1; return NGX_OK; } p->length -= b->last - b->pos; if (p->length == 0) { r = p->input_ctx; r->upstream->keepalive = !r->upstream->headers_in.connection_close; } 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; } if (p->upstream_done) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, p->log, 0, "http proxy data after close"); return NGX_OK; } if (p->length == 0) { ngx_log_error(NGX_LOG_WARN, p->log, 0, "upstream sent data after final chunk"); r->upstream->keepalive = 0; p->upstream_done = 1; return NGX_OK; } b = NULL; prev = &buf->shadow; 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->length = 0; r->upstream->keepalive = !r->upstream->headers_in.connection_close; if (buf->pos != buf->last) { ngx_log_error(NGX_LOG_WARN, p->log, 0, "upstream sent data after final chunk"); r->upstream->keepalive = 0; } break; } 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, p->log, 0, "upstream sent invalid chunked response"); return NGX_ERROR; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, p->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; if (u->length == 0) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "upstream sent more data than specified in " "\"Content-Length\" header"); u->keepalive = 0; return NGX_OK; } for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) { ll = &cl->next; } 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; } if (bytes > u->length) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "upstream sent more data than specified in " "\"Content-Length\" header"); cl->buf->last = cl->buf->pos + u->length; u->length = 0; return NGX_OK; } u->length -= bytes; if (u->length == 0) { 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; if (buf->pos != buf->last) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "upstream sent data after final chunk"); u->keepalive = 0; } break; } if (rc == NGX_AGAIN) { break; } /* invalid response */ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid chunked response"); return NGX_ERROR; } return NGX_OK; } static void ngx_http_proxy_abort_request(ngx_http_request_t *r) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "abort http proxy request"); return; } static void ngx_http_proxy_finalize_request(ngx_http_request_t *r, ngx_int_t rc) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "finalize http proxy request"); return; } static ngx_int_t ngx_http_proxy_host_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_http_proxy_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); if (ctx == NULL) { v->not_found = 1; return NGX_OK; } v->len = ctx->vars.host_header.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = ctx->vars.host_header.data; return NGX_OK; } static ngx_int_t ngx_http_proxy_port_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_http_proxy_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); if (ctx == NULL) { v->not_found = 1; return NGX_OK; } v->len = ctx->vars.port.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = ctx->vars.port.data; return NGX_OK; } static ngx_int_t ngx_http_proxy_add_x_forwarded_for_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { size_t len; u_char *p; ngx_table_elt_t *h, *xfwd; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; xfwd = r->headers_in.x_forwarded_for; len = 0; for (h = xfwd; h; h = h->next) { len += h->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 (h = xfwd; h; h = h->next) { p = ngx_copy(p, h->value.data, h->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->value, 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) { u_char *p; size_t len; ngx_int_t rc, rv; ngx_str_t *key, *value; ngx_uint_t i; ngx_array_t attrs; ngx_keyval_t *attr; ngx_http_proxy_loc_conf_t *plcf; if (ngx_array_init(&attrs, r->pool, 2, sizeof(ngx_keyval_t)) != NGX_OK) { return NGX_ERROR; } if (ngx_http_proxy_parse_cookie(&h->value, &attrs) != NGX_OK) { return NGX_ERROR; } attr = attrs.elts; if (attr[0].value.data == NULL) { return NGX_DECLINED; } rv = NGX_DECLINED; plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); for (i = 1; i < attrs.nelts; i++) { key = &attr[i].key; value = &attr[i].value; if (plcf->cookie_domains && key->len == 6 && ngx_strncasecmp(key->data, (u_char *) "domain", 6) == 0 && value->data) { rc = ngx_http_proxy_rewrite_cookie_value(r, value, plcf->cookie_domains); if (rc == NGX_ERROR) { return NGX_ERROR; } if (rc != NGX_DECLINED) { rv = rc; } } if (plcf->cookie_paths && key->len == 4 && ngx_strncasecmp(key->data, (u_char *) "path", 4) == 0 && value->data) { rc = ngx_http_proxy_rewrite_cookie_value(r, value, plcf->cookie_paths); if (rc == NGX_ERROR) { return NGX_ERROR; } if (rc != NGX_DECLINED) { rv = rc; } } } if (plcf->cookie_flags) { rc = ngx_http_proxy_rewrite_cookie_flags(r, &attrs, plcf->cookie_flags); if (rc == NGX_ERROR) { return NGX_ERROR; } if (rc != NGX_DECLINED) { rv = rc; } attr = attrs.elts; } if (rv != NGX_OK) { return rv; } len = 0; for (i = 0; i < attrs.nelts; i++) { if (attr[i].key.data == NULL) { continue; } if (i > 0) { len += 2; } len += attr[i].key.len; if (attr[i].value.data) { len += 1 + attr[i].value.len; } } p = ngx_pnalloc(r->pool, len + 1); if (p == NULL) { return NGX_ERROR; } h->value.data = p; h->value.len = len; for (i = 0; i < attrs.nelts; i++) { if (attr[i].key.data == NULL) { continue; } if (i > 0) { *p++ = ';'; *p++ = ' '; } p = ngx_cpymem(p, attr[i].key.data, attr[i].key.len); if (attr[i].value.data) { *p++ = '='; p = ngx_cpymem(p, attr[i].value.data, attr[i].value.len); } } *p = '\0'; return NGX_OK; } static ngx_int_t ngx_http_proxy_parse_cookie(ngx_str_t *value, ngx_array_t *attrs) { u_char *start, *end, *p, *last; ngx_str_t name, val; ngx_keyval_t *attr; start = value->data; end = value->data + value->len; for ( ;; ) { last = (u_char *) ngx_strchr(start, ';'); if (last == NULL) { last = end; } while (start < last && *start == ' ') { start++; } for (p = start; p < last && *p != '='; p++) { /* void */ } name.data = start; name.len = p - start; while (name.len && name.data[name.len - 1] == ' ') { name.len--; } if (p < last) { p++; while (p < last && *p == ' ') { p++; } val.data = p; val.len = last - val.data; while (val.len && val.data[val.len - 1] == ' ') { val.len--; } } else { ngx_str_null(&val); } attr = ngx_array_push(attrs); if (attr == NULL) { return NGX_ERROR; } attr->key = name; attr->value = val; if (last == end) { break; } start = last + 1; } return NGX_OK; } static ngx_int_t ngx_http_proxy_rewrite_cookie_value(ngx_http_request_t *r, ngx_str_t *value, ngx_array_t *rewrites) { ngx_int_t rc; ngx_uint_t i; ngx_http_proxy_rewrite_t *pr; pr = rewrites->elts; for (i = 0; i < rewrites->nelts; i++) { rc = pr[i].handler(r, value, 0, value->len, &pr[i]); if (rc != NGX_DECLINED) { return rc; } } return NGX_DECLINED; } static ngx_int_t ngx_http_proxy_rewrite_cookie_flags(ngx_http_request_t *r, ngx_array_t *attrs, ngx_array_t *flags) { ngx_str_t pattern, value; #if (NGX_PCRE) ngx_int_t rc; #endif ngx_uint_t i, m, f, nelts; ngx_keyval_t *attr; ngx_conf_bitmask_t *mask; ngx_http_complex_value_t *flags_values; ngx_http_proxy_cookie_flags_t *pcf; attr = attrs->elts; pcf = flags->elts; for (i = 0; i < flags->nelts; i++) { #if (NGX_PCRE) if (pcf[i].regex) { rc = ngx_http_regex_exec(r, pcf[i].cookie.regex, &attr[0].key); if (rc == NGX_ERROR) { return NGX_ERROR; } if (rc == NGX_OK) { break; } /* NGX_DECLINED */ continue; } #endif if (ngx_http_complex_value(r, &pcf[i].cookie.complex, &pattern) != NGX_OK) { return NGX_ERROR; } if (pattern.len == attr[0].key.len && ngx_strncasecmp(attr[0].key.data, pattern.data, pattern.len) == 0) { break; } } if (i == flags->nelts) { return NGX_DECLINED; } nelts = pcf[i].flags_values.nelts; flags_values = pcf[i].flags_values.elts; mask = ngx_http_proxy_cookie_flags_masks; f = 0; for (i = 0; i < nelts; i++) { if (ngx_http_complex_value(r, &flags_values[i], &value) != NGX_OK) { return NGX_ERROR; } if (value.len == 0) { continue; } for (m = 0; mask[m].name.len != 0; m++) { if (mask[m].name.len != value.len || ngx_strncasecmp(mask[m].name.data, value.data, value.len) != 0) { continue; } f |= mask[m].mask; break; } if (mask[m].name.len == 0) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "invalid proxy_cookie_flags flag \"%V\"", &value); } } if (f == 0) { return NGX_DECLINED; } return ngx_http_proxy_edit_cookie_flags(r, attrs, f); } static ngx_int_t ngx_http_proxy_edit_cookie_flags(ngx_http_request_t *r, ngx_array_t *attrs, ngx_uint_t flags) { ngx_str_t *key, *value; ngx_uint_t i; ngx_keyval_t *attr; attr = attrs->elts; for (i = 1; i < attrs->nelts; i++) { key = &attr[i].key; if (key->len == 6 && ngx_strncasecmp(key->data, (u_char *) "secure", 6) == 0) { if (flags & NGX_HTTP_PROXY_COOKIE_SECURE_ON) { flags &= ~NGX_HTTP_PROXY_COOKIE_SECURE_ON; } else if (flags & NGX_HTTP_PROXY_COOKIE_SECURE_OFF) { key->data = NULL; } continue; } if (key->len == 8 && ngx_strncasecmp(key->data, (u_char *) "httponly", 8) == 0) { if (flags & NGX_HTTP_PROXY_COOKIE_HTTPONLY_ON) { flags &= ~NGX_HTTP_PROXY_COOKIE_HTTPONLY_ON; } else if (flags & NGX_HTTP_PROXY_COOKIE_HTTPONLY_OFF) { key->data = NULL; } continue; } if (key->len == 8 && ngx_strncasecmp(key->data, (u_char *) "samesite", 8) == 0) { value = &attr[i].value; if (flags & NGX_HTTP_PROXY_COOKIE_SAMESITE_STRICT) { flags &= ~NGX_HTTP_PROXY_COOKIE_SAMESITE_STRICT; if (value->len != 6 || ngx_strncasecmp(value->data, (u_char *) "strict", 6) != 0) { ngx_str_set(key, "SameSite"); ngx_str_set(value, "Strict"); } } else if (flags & NGX_HTTP_PROXY_COOKIE_SAMESITE_LAX) { flags &= ~NGX_HTTP_PROXY_COOKIE_SAMESITE_LAX; if (value->len != 3 || ngx_strncasecmp(value->data, (u_char *) "lax", 3) != 0) { ngx_str_set(key, "SameSite"); ngx_str_set(value, "Lax"); } } else if (flags & NGX_HTTP_PROXY_COOKIE_SAMESITE_NONE) { flags &= ~NGX_HTTP_PROXY_COOKIE_SAMESITE_NONE; if (value->len != 4 || ngx_strncasecmp(value->data, (u_char *) "none", 4) != 0) { ngx_str_set(key, "SameSite"); ngx_str_set(value, "None"); } } else if (flags & NGX_HTTP_PROXY_COOKIE_SAMESITE_OFF) { key->data = NULL; } continue; } } if (flags & NGX_HTTP_PROXY_COOKIE_SECURE_ON) { attr = ngx_array_push(attrs); if (attr == NULL) { return NGX_ERROR; } ngx_str_set(&attr->key, "Secure"); ngx_str_null(&attr->value); } if (flags & NGX_HTTP_PROXY_COOKIE_HTTPONLY_ON) { attr = ngx_array_push(attrs); if (attr == NULL) { return NGX_ERROR; } ngx_str_set(&attr->key, "HttpOnly"); ngx_str_null(&attr->value); } if (flags & (NGX_HTTP_PROXY_COOKIE_SAMESITE_STRICT |NGX_HTTP_PROXY_COOKIE_SAMESITE_LAX |NGX_HTTP_PROXY_COOKIE_SAMESITE_NONE)) { attr = ngx_array_push(attrs); if (attr == NULL) { return NGX_ERROR; } ngx_str_set(&attr->key, "SameSite"); if (flags & NGX_HTTP_PROXY_COOKIE_SAMESITE_STRICT) { ngx_str_set(&attr->value, "Strict"); } else if (flags & NGX_HTTP_PROXY_COOKIE_SAMESITE_LAX) { ngx_str_set(&attr->value, "Lax"); } else { ngx_str_set(&attr->value, "None"); } } return NGX_OK; } static ngx_int_t ngx_http_proxy_rewrite_complex_handler(ngx_http_request_t *r, ngx_str_t *value, size_t prefix, size_t len, ngx_http_proxy_rewrite_t *pr) { ngx_str_t pattern, replacement; if (ngx_http_complex_value(r, &pr->pattern.complex, &pattern) != NGX_OK) { return NGX_ERROR; } if (pattern.len > len || ngx_rstrncmp(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, value, prefix, pattern.len, &replacement); } #if (NGX_PCRE) static ngx_int_t ngx_http_proxy_rewrite_regex_handler(ngx_http_request_t *r, ngx_str_t *value, size_t prefix, size_t len, ngx_http_proxy_rewrite_t *pr) { ngx_str_t pattern, replacement; pattern.len = len; pattern.data = 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; } return ngx_http_proxy_rewrite(r, value, prefix, len, &replacement); } #endif static ngx_int_t ngx_http_proxy_rewrite_domain_handler(ngx_http_request_t *r, ngx_str_t *value, 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 = value->data + prefix; if (len && 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, value, prefix, len, &replacement); } static ngx_int_t ngx_http_proxy_rewrite(ngx_http_request_t *r, ngx_str_t *value, size_t prefix, size_t len, ngx_str_t *replacement) { u_char *p, *data; size_t new_len; if (len == value->len) { *value = *replacement; return NGX_OK; } new_len = replacement->len + 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, value->data, prefix); p = ngx_copy(p, replacement->data, replacement->len); ngx_memcpy(p, value->data + prefix + len, value->len - len - prefix + 1); value->data = data; } else { p = ngx_copy(value->data + prefix, replacement->data, replacement->len); ngx_memmove(p, value->data + prefix + len, value->len - len - prefix + 1); } value->len = new_len; return NGX_OK; } static ngx_int_t ngx_http_proxy_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var, *v; for (v = ngx_http_proxy_vars; v->name.len; v++) { var = ngx_http_add_variable(cf, &v->name, v->flags); if (var == NULL) { return NGX_ERROR; } var->get_handler = v->get_handler; var->data = v->data; } return NGX_OK; } static void * ngx_http_proxy_create_main_conf(ngx_conf_t *cf) { ngx_http_proxy_main_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_proxy_main_conf_t)); if (conf == NULL) { return NULL; } #if (NGX_HTTP_CACHE) if (ngx_array_init(&conf->caches, cf->pool, 4, sizeof(ngx_http_file_cache_t *)) != NGX_OK) { return NULL; } #endif return conf; } static void * ngx_http_proxy_create_loc_conf(ngx_conf_t *cf) { ngx_http_proxy_loc_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_proxy_loc_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->upstream.bufs.num = 0; * conf->upstream.ignore_headers = 0; * conf->upstream.next_upstream = 0; * conf->upstream.cache_zone = NULL; * conf->upstream.cache_use_stale = 0; * conf->upstream.cache_methods = 0; * conf->upstream.temp_path = NULL; * conf->upstream.hide_headers_hash = { NULL, 0 }; * conf->upstream.store_lengths = NULL; * conf->upstream.store_values = NULL; * * conf->location = NULL; * conf->url = { 0, 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->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.socket_keepalive = NGX_CONF_UNSET; conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.next_upstream_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.send_lowat = NGX_CONF_UNSET_SIZE; conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE; conf->upstream.limit_rate = NGX_CONF_UNSET_SIZE; conf->upstream.busy_buffers_size_conf = NGX_CONF_UNSET_SIZE; conf->upstream.max_temp_file_size_conf = NGX_CONF_UNSET_SIZE; conf->upstream.temp_file_write_size_conf = NGX_CONF_UNSET_SIZE; conf->upstream.pass_request_headers = NGX_CONF_UNSET; conf->upstream.pass_request_body = NGX_CONF_UNSET; #if (NGX_HTTP_CACHE) conf->upstream.cache = NGX_CONF_UNSET; conf->upstream.cache_min_uses = NGX_CONF_UNSET_UINT; conf->upstream.cache_max_range_offset = NGX_CONF_UNSET; conf->upstream.cache_bypass = NGX_CONF_UNSET_PTR; conf->upstream.no_cache = NGX_CONF_UNSET_PTR; conf->upstream.cache_valid = NGX_CONF_UNSET_PTR; conf->upstream.cache_lock = NGX_CONF_UNSET; conf->upstream.cache_lock_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.cache_lock_age = NGX_CONF_UNSET_MSEC; conf->upstream.cache_revalidate = NGX_CONF_UNSET; conf->upstream.cache_convert_head = NGX_CONF_UNSET; conf->upstream.cache_background_update = NGX_CONF_UNSET; #endif conf->upstream.hide_headers = NGX_CONF_UNSET_PTR; conf->upstream.pass_headers = NGX_CONF_UNSET_PTR; conf->upstream.intercept_errors = NGX_CONF_UNSET; #if (NGX_HTTP_SSL) conf->upstream.ssl_session_reuse = NGX_CONF_UNSET; conf->upstream.ssl_name = NGX_CONF_UNSET_PTR; conf->upstream.ssl_server_name = NGX_CONF_UNSET; conf->upstream.ssl_verify = NGX_CONF_UNSET; conf->upstream.ssl_certificate = NGX_CONF_UNSET_PTR; conf->upstream.ssl_certificate_key = NGX_CONF_UNSET_PTR; conf->upstream.ssl_passwords = NGX_CONF_UNSET_PTR; conf->ssl_verify_depth = NGX_CONF_UNSET_UINT; conf->ssl_conf_commands = NGX_CONF_UNSET_PTR; #endif /* "proxy_cyclic_temp_file" is disabled */ conf->upstream.cyclic_temp_file = 0; conf->upstream.change_buffering = 1; conf->headers_source = NGX_CONF_UNSET_PTR; conf->method = NGX_CONF_UNSET_PTR; conf->redirect = NGX_CONF_UNSET; conf->cookie_domains = NGX_CONF_UNSET_PTR; conf->cookie_paths = NGX_CONF_UNSET_PTR; conf->cookie_flags = NGX_CONF_UNSET_PTR; conf->http_version = NGX_CONF_UNSET_UINT; 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_value(conf->upstream.socket_keepalive, prev->upstream.socket_keepalive, 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.send_lowat, prev->upstream.send_lowat, 0); ngx_conf_merge_size_value(conf->upstream.buffer_size, prev->upstream.buffer_size, (size_t) ngx_pagesize); ngx_conf_merge_size_value(conf->upstream.limit_rate, prev->upstream.limit_rate, 0); ngx_conf_merge_bufs_value(conf->upstream.bufs, prev->upstream.bufs, 8, ngx_pagesize); if (conf->upstream.bufs.num < 2) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "there must be at least 2 \"proxy_buffers\""); return NGX_CONF_ERROR; } size = conf->upstream.buffer_size; if (size < conf->upstream.bufs.size) { size = conf->upstream.bufs.size; } ngx_conf_merge_size_value(conf->upstream.busy_buffers_size_conf, prev->upstream.busy_buffers_size_conf, NGX_CONF_UNSET_SIZE); if (conf->upstream.busy_buffers_size_conf == NGX_CONF_UNSET_SIZE) { conf->upstream.busy_buffers_size = 2 * size; } else { conf->upstream.busy_buffers_size = conf->upstream.busy_buffers_size_conf; } if (conf->upstream.busy_buffers_size < size) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"proxy_busy_buffers_size\" must be equal to or greater than " "the maximum of the value of \"proxy_buffer_size\" and " "one of the \"proxy_buffers\""); return NGX_CONF_ERROR; } if (conf->upstream.busy_buffers_size > (conf->upstream.bufs.num - 1) * conf->upstream.bufs.size) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"proxy_busy_buffers_size\" must be less than " "the size of all \"proxy_buffers\" minus one buffer"); return NGX_CONF_ERROR; } ngx_conf_merge_size_value(conf->upstream.temp_file_write_size_conf, prev->upstream.temp_file_write_size_conf, NGX_CONF_UNSET_SIZE); if (conf->upstream.temp_file_write_size_conf == NGX_CONF_UNSET_SIZE) { conf->upstream.temp_file_write_size = 2 * size; } else { conf->upstream.temp_file_write_size = conf->upstream.temp_file_write_size_conf; } if (conf->upstream.temp_file_write_size < size) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"proxy_temp_file_write_size\" must be equal to or greater " "than the maximum of the value of \"proxy_buffer_size\" and " "one of the \"proxy_buffers\""); return NGX_CONF_ERROR; } ngx_conf_merge_size_value(conf->upstream.max_temp_file_size_conf, prev->upstream.max_temp_file_size_conf, NGX_CONF_UNSET_SIZE); if (conf->upstream.max_temp_file_size_conf == NGX_CONF_UNSET_SIZE) { conf->upstream.max_temp_file_size = 1024 * 1024 * 1024; } else { conf->upstream.max_temp_file_size = conf->upstream.max_temp_file_size_conf; } if (conf->upstream.max_temp_file_size != 0 && conf->upstream.max_temp_file_size < size) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"proxy_max_temp_file_size\" must be equal to zero to disable " "temporary files usage or must be equal to or greater than " "the maximum of the value of \"proxy_buffer_size\" and " "one of the \"proxy_buffers\""); return NGX_CONF_ERROR; } ngx_conf_merge_bitmask_value(conf->upstream.ignore_headers, prev->upstream.ignore_headers, NGX_CONF_BITMASK_SET); ngx_conf_merge_bitmask_value(conf->upstream.next_upstream, prev->upstream.next_upstream, (NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_ERROR |NGX_HTTP_UPSTREAM_FT_TIMEOUT)); if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) { conf->upstream.next_upstream = NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_OFF; } if (ngx_conf_merge_path_value(cf, &conf->upstream.temp_path, prev->upstream.temp_path, &ngx_http_proxy_temp_path) != NGX_OK) { return NGX_CONF_ERROR; } #if (NGX_HTTP_CACHE) if (conf->upstream.cache == NGX_CONF_UNSET) { ngx_conf_merge_value(conf->upstream.cache, prev->upstream.cache, 0); conf->upstream.cache_zone = prev->upstream.cache_zone; conf->upstream.cache_value = prev->upstream.cache_value; } if (conf->upstream.cache_zone && conf->upstream.cache_zone->data == NULL) { ngx_shm_zone_t *shm_zone; shm_zone = conf->upstream.cache_zone; ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"proxy_cache\" zone \"%V\" is unknown", &shm_zone->shm.name); return NGX_CONF_ERROR; } ngx_conf_merge_uint_value(conf->upstream.cache_min_uses, prev->upstream.cache_min_uses, 1); ngx_conf_merge_off_value(conf->upstream.cache_max_range_offset, prev->upstream.cache_max_range_offset, NGX_MAX_OFF_T_VALUE); ngx_conf_merge_bitmask_value(conf->upstream.cache_use_stale, prev->upstream.cache_use_stale, (NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_OFF)); if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_OFF) { conf->upstream.cache_use_stale = NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_OFF; } if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_ERROR) { conf->upstream.cache_use_stale |= NGX_HTTP_UPSTREAM_FT_NOLIVE; } if (conf->upstream.cache_methods == 0) { conf->upstream.cache_methods = prev->upstream.cache_methods; } conf->upstream.cache_methods |= NGX_HTTP_GET|NGX_HTTP_HEAD; ngx_conf_merge_ptr_value(conf->upstream.cache_bypass, prev->upstream.cache_bypass, NULL); ngx_conf_merge_ptr_value(conf->upstream.no_cache, prev->upstream.no_cache, NULL); ngx_conf_merge_ptr_value(conf->upstream.cache_valid, prev->upstream.cache_valid, NULL); if (conf->cache_key.value.data == NULL) { conf->cache_key = prev->cache_key; } ngx_conf_merge_value(conf->upstream.cache_lock, prev->upstream.cache_lock, 0); ngx_conf_merge_msec_value(conf->upstream.cache_lock_timeout, prev->upstream.cache_lock_timeout, 5000); ngx_conf_merge_msec_value(conf->upstream.cache_lock_age, prev->upstream.cache_lock_age, 5000); ngx_conf_merge_value(conf->upstream.cache_revalidate, prev->upstream.cache_revalidate, 0); ngx_conf_merge_value(conf->upstream.cache_convert_head, prev->upstream.cache_convert_head, 1); ngx_conf_merge_value(conf->upstream.cache_background_update, prev->upstream.cache_background_update, 0); #endif 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) if (ngx_http_proxy_merge_ssl(cf, conf, prev) != NGX_OK) { return NGX_CONF_ERROR; } 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_SSL_TLSv1_3)); ngx_conf_merge_str_value(conf->ssl_ciphers, prev->ssl_ciphers, "DEFAULT"); ngx_conf_merge_ptr_value(conf->upstream.ssl_name, prev->upstream.ssl_name, NULL); ngx_conf_merge_value(conf->upstream.ssl_server_name, prev->upstream.ssl_server_name, 0); ngx_conf_merge_value(conf->upstream.ssl_verify, 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_ptr_value(conf->upstream.ssl_certificate, prev->upstream.ssl_certificate, NULL); ngx_conf_merge_ptr_value(conf->upstream.ssl_certificate_key, prev->upstream.ssl_certificate_key, NULL); ngx_conf_merge_ptr_value(conf->upstream.ssl_passwords, prev->upstream.ssl_passwords, NULL); ngx_conf_merge_ptr_value(conf->ssl_conf_commands, prev->ssl_conf_commands, NULL); if (conf->ssl && ngx_http_proxy_set_ssl(cf, conf) != NGX_OK) { return NGX_CONF_ERROR; } #endif ngx_conf_merge_ptr_value(conf->method, prev->method, NULL); 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_ptr_value(conf->cookie_flags, prev->cookie_flags, 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->ssl = prev->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; } } ngx_conf_merge_ptr_value(conf->headers_source, prev->headers_source, NULL); if (conf->headers_source == prev->headers_source) { conf->headers = prev->headers; #if (NGX_HTTP_CACHE) conf->headers_cache = prev->headers_cache; #endif } rc = ngx_http_proxy_init_headers(cf, conf, &conf->headers, ngx_http_proxy_headers); if (rc != NGX_OK) { return NGX_CONF_ERROR; } #if (NGX_HTTP_CACHE) if (conf->upstream.cache) { rc = ngx_http_proxy_init_headers(cf, conf, &conf->headers_cache, ngx_http_proxy_cache_headers); if (rc != NGX_OK) { return NGX_CONF_ERROR; } } #endif /* * special handling to preserve conf->headers in the "http" section * to inherit it to all servers */ if (prev->headers.hash.buckets == NULL && conf->headers_source == prev->headers_source) { prev->headers = conf->headers; #if (NGX_HTTP_CACHE) prev->headers_cache = conf->headers_cache; #endif } return NGX_CONF_OK; } static ngx_int_t ngx_http_proxy_init_headers(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *conf, ngx_http_proxy_headers_t *headers, ngx_keyval_t *default_headers) { u_char *p; size_t size; uintptr_t *code; ngx_uint_t i; ngx_array_t headers_names, headers_merged; ngx_keyval_t *src, *s, *h; ngx_hash_key_t *hk; ngx_hash_init_t hash; ngx_http_script_compile_t sc; ngx_http_script_copy_code_t *copy; if (headers->hash.buckets) { return NGX_OK; } if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t)) != NGX_OK) { return NGX_ERROR; } if (ngx_array_init(&headers_merged, cf->temp_pool, 4, sizeof(ngx_keyval_t)) != NGX_OK) { return NGX_ERROR; } headers->lengths = ngx_array_create(cf->pool, 64, 1); if (headers->lengths == NULL) { return NGX_ERROR; } headers->values = ngx_array_create(cf->pool, 512, 1); if (headers->values == NULL) { return NGX_ERROR; } if (conf->headers_source) { src = conf->headers_source->elts; for (i = 0; i < conf->headers_source->nelts; i++) { s = ngx_array_push(&headers_merged); if (s == NULL) { return NGX_ERROR; } *s = src[i]; } } h = default_headers; while (h->key.len) { src = headers_merged.elts; for (i = 0; i < headers_merged.nelts; i++) { if (ngx_strcasecmp(h->key.data, src[i].key.data) == 0) { goto next; } } s = ngx_array_push(&headers_merged); if (s == NULL) { return NGX_ERROR; } *s = *h; next: h++; } src = headers_merged.elts; for (i = 0; i < headers_merged.nelts; i++) { hk = ngx_array_push(&headers_names); if (hk == NULL) { return NGX_ERROR; } hk->key = src[i].key; hk->key_hash = ngx_hash_key_lc(src[i].key.data, src[i].key.len); hk->value = (void *) 1; if (src[i].value.len == 0) { continue; } copy = ngx_array_push_n(headers->lengths, sizeof(ngx_http_script_copy_code_t)); if (copy == NULL) { return NGX_ERROR; } copy->code = (ngx_http_script_code_pt) (void *) ngx_http_script_copy_len_code; copy->len = src[i].key.len; size = (sizeof(ngx_http_script_copy_code_t) + src[i].key.len + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1); copy = ngx_array_push_n(headers->values, size); if (copy == NULL) { return NGX_ERROR; } copy->code = ngx_http_script_copy_code; copy->len = src[i].key.len; p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t); ngx_memcpy(p, src[i].key.data, src[i].key.len); ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); sc.cf = cf; sc.source = &src[i].value; sc.flushes = &headers->flushes; sc.lengths = &headers->lengths; sc.values = &headers->values; if (ngx_http_script_compile(&sc) != NGX_OK) { return NGX_ERROR; } code = ngx_array_push_n(headers->lengths, sizeof(uintptr_t)); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) NULL; code = ngx_array_push_n(headers->values, sizeof(uintptr_t)); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) NULL; } code = ngx_array_push_n(headers->lengths, sizeof(uintptr_t)); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) NULL; hash.hash = &headers->hash; hash.key = ngx_hash_key_lc; hash.max_size = conf->headers_hash_max_size; hash.bucket_size = conf->headers_hash_bucket_size; hash.name = "proxy_headers_hash"; hash.pool = cf->pool; hash.temp_pool = NULL; return ngx_hash_init(&hash, headers_names.elts, headers_names.nelts); } static char * ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_proxy_loc_conf_t *plcf = conf; size_t add; u_short port; ngx_str_t *value, *url; ngx_url_t u; ngx_uint_t n; ngx_http_core_loc_conf_t *clcf; ngx_http_script_compile_t sc; if (plcf->upstream.upstream || plcf->proxy_lengths) { return "is duplicate"; } clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); clcf->handler = ngx_http_proxy_handler; if (clcf->name.len && 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 "is duplicate"; } plcf->redirect = 1; value = cf->args->elts; if (cf->args->nelts == 2) { if (ngx_strcmp(value[1].data, "off") == 0) { if (plcf->redirects) { return "is duplicate"; } plcf->redirect = 0; 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 (cf->args->nelts == 2 && ngx_strcmp(value[1].data, "default") == 0) { if (plcf->proxy_lengths) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"proxy_redirect default\" cannot be used " "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 "is duplicate"; } value = cf->args->elts; if (cf->args->nelts == 2) { if (ngx_strcmp(value[1].data, "off") == 0) { if (plcf->cookie_domains != NGX_CONF_UNSET_PTR) { return "is duplicate"; } plcf->cookie_domains = NULL; return NGX_CONF_OK; } 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 "is duplicate"; } value = cf->args->elts; if (cf->args->nelts == 2) { if (ngx_strcmp(value[1].data, "off") == 0) { if (plcf->cookie_paths != NGX_CONF_UNSET_PTR) { return "is duplicate"; } plcf->cookie_paths = NULL; return NGX_CONF_OK; } 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 char * ngx_http_proxy_cookie_flags(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_proxy_loc_conf_t *plcf = conf; ngx_str_t *value; ngx_uint_t i; ngx_http_complex_value_t *cv; ngx_http_proxy_cookie_flags_t *pcf; ngx_http_compile_complex_value_t ccv; #if (NGX_PCRE) ngx_regex_compile_t rc; u_char errstr[NGX_MAX_CONF_ERRSTR]; #endif if (plcf->cookie_flags == NULL) { return "is duplicate"; } value = cf->args->elts; if (cf->args->nelts == 2) { if (ngx_strcmp(value[1].data, "off") == 0) { if (plcf->cookie_flags != NGX_CONF_UNSET_PTR) { return "is duplicate"; } plcf->cookie_flags = NULL; return NGX_CONF_OK; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[1]); return NGX_CONF_ERROR; } if (plcf->cookie_flags == NGX_CONF_UNSET_PTR) { plcf->cookie_flags = ngx_array_create(cf->pool, 1, sizeof(ngx_http_proxy_cookie_flags_t)); if (plcf->cookie_flags == NULL) { return NGX_CONF_ERROR; } } pcf = ngx_array_push(plcf->cookie_flags); if (pcf == NULL) { return NGX_CONF_ERROR; } pcf->regex = 0; if (value[1].data[0] == '~') { value[1].len--; value[1].data++; #if (NGX_PCRE) ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); rc.pattern = value[1]; rc.err.len = NGX_MAX_CONF_ERRSTR; rc.err.data = errstr; rc.options = NGX_REGEX_CASELESS; pcf->cookie.regex = ngx_http_regex_compile(cf, &rc); if (pcf->cookie.regex == NULL) { return NGX_CONF_ERROR; } pcf->regex = 1; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "using regex \"%V\" requires PCRE library", &value[1]); return NGX_CONF_ERROR; #endif } else { ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = &pcf->cookie.complex; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } } if (ngx_array_init(&pcf->flags_values, cf->pool, cf->args->nelts - 2, sizeof(ngx_http_complex_value_t)) != NGX_OK) { return NGX_CONF_ERROR; } for (i = 2; i < cf->args->nelts; i++) { cv = ngx_array_push(&pcf->flags_values); if (cv == NULL) { return NGX_CONF_ERROR; } ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[i]; ccv.complex_value = cv; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } } return NGX_CONF_OK; } static ngx_int_t ngx_http_proxy_rewrite_regex(ngx_conf_t *cf, ngx_http_proxy_rewrite_t *pr, ngx_str_t *regex, ngx_uint_t caseless) { #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->upstream.ssl_passwords != NGX_CONF_UNSET_PTR) { return "is duplicate"; } value = cf->args->elts; plcf->upstream.ssl_passwords = ngx_ssl_read_password_file(cf, &value[1]); if (plcf->upstream.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 char * ngx_http_proxy_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data) { #ifndef SSL_CONF_FLAG_FILE return "is not supported on this platform"; #else return NGX_CONF_OK; #endif } static ngx_int_t ngx_http_proxy_merge_ssl(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *conf, ngx_http_proxy_loc_conf_t *prev) { ngx_uint_t preserve; if (conf->ssl_protocols == 0 && conf->ssl_ciphers.data == NULL && conf->upstream.ssl_certificate == NGX_CONF_UNSET_PTR && conf->upstream.ssl_certificate_key == NGX_CONF_UNSET_PTR && conf->upstream.ssl_passwords == NGX_CONF_UNSET_PTR && conf->upstream.ssl_verify == NGX_CONF_UNSET && conf->ssl_verify_depth == NGX_CONF_UNSET_UINT && conf->ssl_trusted_certificate.data == NULL && conf->ssl_crl.data == NULL && conf->upstream.ssl_session_reuse == NGX_CONF_UNSET && conf->ssl_conf_commands == NGX_CONF_UNSET_PTR) { if (prev->upstream.ssl) { conf->upstream.ssl = prev->upstream.ssl; return NGX_OK; } preserve = 1; } else { preserve = 0; } conf->upstream.ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t)); if (conf->upstream.ssl == NULL) { return NGX_ERROR; } conf->upstream.ssl->log = cf->log; /* * special handling to preserve conf->upstream.ssl * in the "http" section to inherit it to all servers */ if (preserve) { prev->upstream.ssl = conf->upstream.ssl; } return NGX_OK; } 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; if (plcf->upstream.ssl->ctx) { return NGX_OK; } 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) { ngx_ssl_cleanup_ctx(plcf->upstream.ssl); return NGX_ERROR; } cln->handler = ngx_ssl_cleanup_ctx; cln->data = plcf->upstream.ssl; if (ngx_ssl_ciphers(cf, plcf->upstream.ssl, &plcf->ssl_ciphers, 0) != NGX_OK) { return NGX_ERROR; } if (plcf->upstream.ssl_certificate && plcf->upstream.ssl_certificate->value.len) { if (plcf->upstream.ssl_certificate_key == NULL) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no \"proxy_ssl_certificate_key\" is defined " "for certificate \"%V\"", &plcf->upstream.ssl_certificate->value); return NGX_ERROR; } if (plcf->upstream.ssl_certificate->lengths || plcf->upstream.ssl_certificate_key->lengths) { plcf->upstream.ssl_passwords = ngx_ssl_preserve_passwords(cf, plcf->upstream.ssl_passwords); if (plcf->upstream.ssl_passwords == NULL) { return NGX_ERROR; } } else { if (ngx_ssl_certificate(cf, plcf->upstream.ssl, &plcf->upstream.ssl_certificate->value, &plcf->upstream.ssl_certificate_key->value, plcf->upstream.ssl_passwords) != NGX_OK) { return NGX_ERROR; } } } if (plcf->upstream.ssl_verify) { if (plcf->ssl_trusted_certificate.len == 0) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no proxy_ssl_trusted_certificate for proxy_ssl_verify"); return NGX_ERROR; } if (ngx_ssl_trusted_certificate(cf, plcf->upstream.ssl, &plcf->ssl_trusted_certificate, plcf->ssl_verify_depth) != NGX_OK) { return NGX_ERROR; } if (ngx_ssl_crl(cf, plcf->upstream.ssl, &plcf->ssl_crl) != NGX_OK) { return NGX_ERROR; } } if (ngx_ssl_client_session_cache(cf, plcf->upstream.ssl, plcf->upstream.ssl_session_reuse) != NGX_OK) { return NGX_ERROR; } if (ngx_ssl_conf_commands(cf, plcf->upstream.ssl, plcf->ssl_conf_commands) != 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; } nginx-1.24.0/src/http/modules/ngx_http_random_index_module.c000644 001751 001751 00000021415 14415135676 025437 0ustar00mdouninmdounin000000 000000 /* * 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 = 0; #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; } nginx-1.24.0/src/http/modules/ngx_http_range_filter_module.c000644 001751 001751 00000065006 14415135676 025435 0ustar00mdouninmdounin000000 000000 /* * 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; 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; r->headers_out.accept_ranges->next = NULL; ngx_str_set(&r->headers_out.accept_ranges->key, "Accept-Ranges"); ngx_str_set(&r->headers_out.accept_ranges->value, "bytes"); return ngx_http_next_header_filter(r); } static ngx_int_t ngx_http_range_parse(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx, ngx_uint_t ranges) { u_char *p; off_t start, end, size, content_length, cutoff, cutlim; ngx_uint_t suffix; ngx_http_range_t *range; ngx_http_range_filter_ctx_t *mctx; if (r != r->main) { mctx = ngx_http_get_module_ctx(r->main, ngx_http_range_body_filter_module); if (mctx) { ctx->ranges = mctx->ranges; return NGX_OK; } } if (ngx_array_init(&ctx->ranges, r->pool, 1, sizeof(ngx_http_range_t)) != NGX_OK) { return NGX_ERROR; } p = r->headers_in.range->value.data + 6; size = 0; content_length = r->headers_out.content_length_n; cutoff = NGX_MAX_OFF_T_VALUE / 10; cutlim = NGX_MAX_OFF_T_VALUE % 10; for ( ;; ) { start = 0; end = 0; suffix = 0; while (*p == ' ') { p++; } if (*p != '-') { if (*p < '0' || *p > '9') { return NGX_HTTP_RANGE_NOT_SATISFIABLE; } while (*p >= '0' && *p <= '9') { if (start >= cutoff && (start > cutoff || *p - '0' > cutlim)) { return NGX_HTTP_RANGE_NOT_SATISFIABLE; } start = start * 10 + (*p++ - '0'); } while (*p == ' ') { p++; } if (*p++ != '-') { return NGX_HTTP_RANGE_NOT_SATISFIABLE; } while (*p == ' ') { p++; } if (*p == ',' || *p == '\0') { end = content_length; goto found; } } else { suffix = 1; p++; } if (*p < '0' || *p > '9') { return NGX_HTTP_RANGE_NOT_SATISFIABLE; } while (*p >= '0' && *p <= '9') { if (end >= cutoff && (end > cutoff || *p - '0' > cutlim)) { return NGX_HTTP_RANGE_NOT_SATISFIABLE; } end = end * 10 + (*p++ - '0'); } while (*p == ' ') { p++; } if (*p != ',' && *p != '\0') { return NGX_HTTP_RANGE_NOT_SATISFIABLE; } if (suffix) { start = (end < content_length) ? content_length - end : 0; end = content_length - 1; } if (end >= content_length) { end = content_length; } else { end++; } found: if (start < end) { range = ngx_array_push(&ctx->ranges); if (range == NULL) { return NGX_ERROR; } range->start = start; range->end = end; if (size > NGX_MAX_OFF_T_VALUE - (end - start)) { return NGX_HTTP_RANGE_NOT_SATISFIABLE; } size += end - start; if (ranges-- == 0) { return NGX_DECLINED; } } else if (start == 0) { return NGX_DECLINED; } if (*p++ != ',') { break; } } if (ctx->ranges.nelts == 0) { return NGX_HTTP_RANGE_NOT_SATISFIABLE; } if (size > content_length) { return NGX_DECLINED; } return NGX_OK; } static ngx_int_t ngx_http_range_singlepart_header(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx) { ngx_table_elt_t *content_range; ngx_http_range_t *range; if (r != r->main) { return ngx_http_next_header_filter(r); } content_range = ngx_list_push(&r->headers_out.headers); if (content_range == NULL) { return NGX_ERROR; } if (r->headers_out.content_range) { r->headers_out.content_range->hash = 0; } r->headers_out.content_range = content_range; content_range->hash = 1; content_range->next = NULL; ngx_str_set(&content_range->key, "Content-Range"); content_range->value.data = ngx_pnalloc(r->pool, sizeof("bytes -/") - 1 + 3 * NGX_OFF_T_LEN); if (content_range->value.data == NULL) { content_range->hash = 0; r->headers_out.content_range = NULL; return NGX_ERROR; } /* "Content-Range: bytes SSSS-EEEE/TTTT" header */ range = ctx->ranges.elts; content_range->value.len = ngx_sprintf(content_range->value.data, "bytes %O-%O/%O", range->start, range->end - 1, r->headers_out.content_length_n) - content_range->value.data; r->headers_out.content_length_n = range->end - range->start; r->headers_out.content_offset = range->start; if (r->headers_out.content_length) { r->headers_out.content_length->hash = 0; r->headers_out.content_length = NULL; } return ngx_http_next_header_filter(r); } static ngx_int_t ngx_http_range_multipart_header(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx) { off_t len; size_t size; ngx_uint_t i; ngx_http_range_t *range; ngx_atomic_uint_t boundary; size = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN + sizeof(CRLF "Content-Type: ") - 1 + r->headers_out.content_type.len + sizeof(CRLF "Content-Range: bytes ") - 1; if (r->headers_out.content_type_len == r->headers_out.content_type.len && r->headers_out.charset.len) { size += sizeof("; charset=") - 1 + r->headers_out.charset.len; } ctx->boundary_header.data = ngx_pnalloc(r->pool, size); if (ctx->boundary_header.data == NULL) { return NGX_ERROR; } boundary = ngx_next_temp_number(0); /* * The boundary header of the range: * CRLF * "--0123456789" CRLF * "Content-Type: image/jpeg" CRLF * "Content-Range: bytes " */ if (r->headers_out.content_type_len == r->headers_out.content_type.len && r->headers_out.charset.len) { ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data, CRLF "--%0muA" CRLF "Content-Type: %V; charset=%V" CRLF "Content-Range: bytes ", boundary, &r->headers_out.content_type, &r->headers_out.charset) - ctx->boundary_header.data; } else if (r->headers_out.content_type.len) { ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data, CRLF "--%0muA" CRLF "Content-Type: %V" CRLF "Content-Range: bytes ", boundary, &r->headers_out.content_type) - ctx->boundary_header.data; } else { ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data, CRLF "--%0muA" CRLF "Content-Range: bytes ", boundary) - ctx->boundary_header.data; } r->headers_out.content_type.data = ngx_pnalloc(r->pool, sizeof("Content-Type: multipart/byteranges; boundary=") - 1 + NGX_ATOMIC_T_LEN); if (r->headers_out.content_type.data == NULL) { return NGX_ERROR; } r->headers_out.content_type_lowcase = NULL; /* "Content-Type: multipart/byteranges; boundary=0123456789" */ r->headers_out.content_type.len = ngx_sprintf(r->headers_out.content_type.data, "multipart/byteranges; boundary=%0muA", boundary) - r->headers_out.content_type.data; r->headers_out.content_type_len = r->headers_out.content_type.len; r->headers_out.charset.len = 0; /* the size of the last boundary CRLF "--0123456789--" CRLF */ len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN + sizeof("--" CRLF) - 1; range = ctx->ranges.elts; for (i = 0; i < ctx->ranges.nelts; i++) { /* the size of the range: "SSSS-EEEE/TTTT" CRLF CRLF */ range[i].content_range.data = ngx_pnalloc(r->pool, 3 * NGX_OFF_T_LEN + 2 + 4); if (range[i].content_range.data == NULL) { return NGX_ERROR; } range[i].content_range.len = ngx_sprintf(range[i].content_range.data, "%O-%O/%O" CRLF CRLF, range[i].start, range[i].end - 1, r->headers_out.content_length_n) - range[i].content_range.data; len += ctx->boundary_header.len + range[i].content_range.len + (range[i].end - range[i].start); } r->headers_out.content_length_n = len; if (r->headers_out.content_length) { r->headers_out.content_length->hash = 0; r->headers_out.content_length = NULL; } if (r->headers_out.content_range) { r->headers_out.content_range->hash = 0; r->headers_out.content_range = 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; } if (r->headers_out.content_range) { r->headers_out.content_range->hash = 0; } r->headers_out.content_range = content_range; content_range->hash = 1; content_range->next = NULL; ngx_str_set(&content_range->key, "Content-Range"); content_range->value.data = ngx_pnalloc(r->pool, sizeof("bytes */") - 1 + NGX_OFF_T_LEN); if (content_range->value.data == NULL) { content_range->hash = 0; r->headers_out.content_range = NULL; return NGX_ERROR; } content_range->value.len = ngx_sprintf(content_range->value.data, "bytes */%O", r->headers_out.content_length_n) - content_range->value.data; ngx_http_clear_content_length(r); return NGX_HTTP_RANGE_NOT_SATISFIABLE; } static ngx_int_t ngx_http_range_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_http_range_filter_ctx_t *ctx; if (in == NULL) { return ngx_http_next_body_filter(r, in); } ctx = ngx_http_get_module_ctx(r, ngx_http_range_body_filter_module); if (ctx == NULL) { return ngx_http_next_body_filter(r, in); } if (ctx->ranges.nelts == 1) { return ngx_http_range_singlepart_body(r, ctx, in); } /* * multipart ranges are supported only if whole body is in a single buffer */ if (ngx_buf_special(in->buf)) { return ngx_http_next_body_filter(r, in); } if (ngx_http_range_test_overlapped(r, ctx, in) != NGX_OK) { return NGX_ERROR; } return ngx_http_range_multipart_body(r, ctx, in); } static ngx_int_t ngx_http_range_test_overlapped(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in) { off_t start, last; ngx_buf_t *buf; ngx_uint_t i; ngx_http_range_t *range; if (ctx->offset) { goto overlapped; } buf = in->buf; if (!buf->last_buf) { start = ctx->offset; last = ctx->offset + ngx_buf_size(buf); range = ctx->ranges.elts; for (i = 0; i < ctx->ranges.nelts; i++) { if (start > range[i].start || last < range[i].end) { goto overlapped; } } } ctx->offset = ngx_buf_size(buf); return NGX_OK; overlapped: ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "range in overlapped buffers"); return NGX_ERROR; } static ngx_int_t ngx_http_range_singlepart_body(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in) { off_t start, last; ngx_int_t rc; ngx_buf_t *buf; ngx_chain_t *out, *cl, *tl, **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)) { if (range->end <= start) { continue; } tl = ngx_alloc_chain_link(r->pool); if (tl == NULL) { return NGX_ERROR; } tl->buf = buf; tl->next = NULL; *ll = tl; ll = &tl->next; continue; } if (range->end <= start || range->start >= last) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http range body skip"); if (buf->in_file) { buf->file_pos = buf->file_last; } buf->pos = buf->last; buf->sync = 1; continue; } if (range->start > start) { if (buf->in_file) { buf->file_pos += range->start - start; } if (ngx_buf_in_memory(buf)) { buf->pos += (size_t) (range->start - start); } } if (range->end <= last) { if (buf->in_file) { buf->file_last -= last - range->end; } if (ngx_buf_in_memory(buf)) { buf->last -= (size_t) (last - range->end); } buf->last_buf = (r == r->main) ? 1 : 0; buf->last_in_chain = 1; tl = ngx_alloc_chain_link(r->pool); if (tl == NULL) { return NGX_ERROR; } tl->buf = buf; tl->next = NULL; *ll = tl; ll = &tl->next; continue; } tl = ngx_alloc_chain_link(r->pool); if (tl == NULL) { return NGX_ERROR; } tl->buf = buf; tl->next = NULL; *ll = tl; ll = &tl->next; } rc = ngx_http_next_body_filter(r, out); while (out) { cl = out; out = out->next; ngx_free_chain(r->pool, cl); } return rc; } 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; } nginx-1.24.0/src/http/modules/ngx_http_realip_module.c000644 001751 001751 00000035671 14415135676 024255 0ustar00mdouninmdounin000000 000000 /* * 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_http_realip_ctx_t *ngx_http_realip_get_module_ctx( ngx_http_request_t *r); static ngx_int_t ngx_http_realip_remote_addr_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_realip_remote_port_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_command_t ngx_http_realip_commands[] = { { ngx_string("set_real_ip_from"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_realip_from, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("real_ip_header"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_realip, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("real_ip_recursive"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_realip_loc_conf_t, recursive), NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_realip_module_ctx = { ngx_http_realip_add_variables, /* preconfiguration */ ngx_http_realip_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_realip_create_loc_conf, /* create location configuration */ ngx_http_realip_merge_loc_conf /* merge location configuration */ }; ngx_module_t ngx_http_realip_module = { NGX_MODULE_V1, &ngx_http_realip_module_ctx, /* module context */ ngx_http_realip_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_variable_t ngx_http_realip_vars[] = { { ngx_string("realip_remote_addr"), NULL, ngx_http_realip_remote_addr_variable, 0, 0, 0 }, { ngx_string("realip_remote_port"), NULL, ngx_http_realip_remote_port_variable, 0, 0, 0 }, ngx_http_null_variable }; static ngx_int_t ngx_http_realip_handler(ngx_http_request_t *r) { u_char *p; size_t len; ngx_str_t *value; ngx_uint_t i, hash; ngx_addr_t addr; ngx_list_part_t *part; ngx_table_elt_t *header, *xfwd; ngx_connection_t *c; ngx_http_realip_ctx_t *ctx; ngx_http_realip_loc_conf_t *rlcf; rlcf = ngx_http_get_module_loc_conf(r, ngx_http_realip_module); if (rlcf->from == NULL) { return NGX_DECLINED; } ctx = ngx_http_realip_get_module_ctx(r); if (ctx) { return NGX_DECLINED; } switch (rlcf->type) { case NGX_HTTP_REALIP_XREALIP: if (r->headers_in.x_real_ip == NULL) { return NGX_DECLINED; } value = &r->headers_in.x_real_ip->value; xfwd = NULL; break; case NGX_HTTP_REALIP_XFWD: xfwd = r->headers_in.x_forwarded_for; if (xfwd == NULL) { return NGX_DECLINED; } value = NULL; break; case NGX_HTTP_REALIP_PROXY: if (r->connection->proxy_protocol == NULL) { return NGX_DECLINED; } value = &r->connection->proxy_protocol->src_addr; xfwd = NULL; break; default: /* NGX_HTTP_REALIP_HEADER */ part = &r->headers_in.headers.part; header = part->elts; hash = rlcf->hash; len = rlcf->header.len; p = rlcf->header.data; for (i = 0; /* void */ ; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (hash == header[i].hash && len == header[i].key.len && ngx_strncmp(p, header[i].lowcase_key, len) == 0) { value = &header[i].value; xfwd = NULL; goto found; } } return NGX_DECLINED; } found: c = r->connection; addr.sockaddr = c->sockaddr; addr.socklen = c->socklen; /* addr.name = c->addr_text; */ if (ngx_http_get_forwarded_addr(r, &addr, xfwd, value, rlcf->from, rlcf->recursive) != NGX_DECLINED) { if (rlcf->type == NGX_HTTP_REALIP_PROXY) { ngx_inet_set_port(addr.sockaddr, c->proxy_protocol->src_port); } return ngx_http_realip_set_addr(r, &addr); } return NGX_DECLINED; } static ngx_int_t ngx_http_realip_set_addr(ngx_http_request_t *r, ngx_addr_t *addr) { size_t len; u_char *p; u_char text[NGX_SOCKADDR_STRLEN]; ngx_connection_t *c; ngx_pool_cleanup_t *cln; ngx_http_realip_ctx_t *ctx; cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_http_realip_ctx_t)); if (cln == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ctx = cln->data; c = r->connection; len = ngx_sock_ntop(addr->sockaddr, addr->socklen, text, NGX_SOCKADDR_STRLEN, 0); if (len == 0) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } p = ngx_pnalloc(c->pool, len); if (p == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_memcpy(p, text, len); cln->handler = ngx_http_realip_cleanup; ngx_http_set_ctx(r, ctx, ngx_http_realip_module); ctx->connection = c; ctx->sockaddr = c->sockaddr; ctx->socklen = c->socklen; ctx->addr_text = c->addr_text; c->sockaddr = addr->sockaddr; c->socklen = addr->socklen; c->addr_text.len = len; c->addr_text.data = p; return NGX_DECLINED; } static void ngx_http_realip_cleanup(void *data) { ngx_http_realip_ctx_t *ctx = data; ngx_connection_t *c; c = ctx->connection; c->sockaddr = ctx->sockaddr; c->socklen = ctx->socklen; c->addr_text = ctx->addr_text; } static char * ngx_http_realip_from(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_realip_loc_conf_t *rlcf = conf; ngx_int_t rc; ngx_str_t *value; ngx_url_t u; ngx_cidr_t c, *cidr; ngx_uint_t i; struct sockaddr_in *sin; #if (NGX_HAVE_INET6) struct sockaddr_in6 *sin6; #endif value = cf->args->elts; if (rlcf->from == NULL) { rlcf->from = ngx_array_create(cf->pool, 2, sizeof(ngx_cidr_t)); if (rlcf->from == NULL) { return NGX_CONF_ERROR; } } #if (NGX_HAVE_UNIX_DOMAIN) if (ngx_strcmp(value[1].data, "unix:") == 0) { cidr = ngx_array_push(rlcf->from); if (cidr == NULL) { return NGX_CONF_ERROR; } cidr->family = AF_UNIX; return NGX_CONF_OK; } #endif rc = ngx_ptocidr(&value[1], &c); if (rc != NGX_ERROR) { if (rc == NGX_DONE) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "low address bits of %V are meaningless", &value[1]); } cidr = ngx_array_push(rlcf->from); if (cidr == NULL) { return NGX_CONF_ERROR; } *cidr = c; return NGX_CONF_OK; } ngx_memzero(&u, sizeof(ngx_url_t)); u.host = value[1]; if (ngx_inet_resolve_host(cf->pool, &u) != NGX_OK) { if (u.err) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%s in set_real_ip_from \"%V\"", u.err, &u.host); } return NGX_CONF_ERROR; } cidr = ngx_array_push_n(rlcf->from, u.naddrs); if (cidr == NULL) { return NGX_CONF_ERROR; } ngx_memzero(cidr, u.naddrs * sizeof(ngx_cidr_t)); for (i = 0; i < u.naddrs; i++) { cidr[i].family = u.addrs[i].sockaddr->sa_family; switch (cidr[i].family) { #if (NGX_HAVE_INET6) case AF_INET6: sin6 = (struct sockaddr_in6 *) u.addrs[i].sockaddr; cidr[i].u.in6.addr = sin6->sin6_addr; ngx_memset(cidr[i].u.in6.mask.s6_addr, 0xff, 16); break; #endif default: /* AF_INET */ sin = (struct sockaddr_in *) u.addrs[i].sockaddr; cidr[i].u.in.addr = sin->sin_addr.s_addr; cidr[i].u.in.mask = 0xffffffff; break; } } return NGX_CONF_OK; } static char * ngx_http_realip(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_realip_loc_conf_t *rlcf = conf; ngx_str_t *value; if (rlcf->type != NGX_CONF_UNSET_UINT) { return "is duplicate"; } value = cf->args->elts; if (ngx_strcmp(value[1].data, "X-Real-IP") == 0) { rlcf->type = NGX_HTTP_REALIP_XREALIP; return NGX_CONF_OK; } if (ngx_strcmp(value[1].data, "X-Forwarded-For") == 0) { rlcf->type = NGX_HTTP_REALIP_XFWD; return NGX_CONF_OK; } if (ngx_strcmp(value[1].data, "proxy_protocol") == 0) { rlcf->type = NGX_HTTP_REALIP_PROXY; return NGX_CONF_OK; } rlcf->type = NGX_HTTP_REALIP_HEADER; rlcf->hash = ngx_hash_strlow(value[1].data, value[1].data, value[1].len); rlcf->header = value[1]; return NGX_CONF_OK; } static void * ngx_http_realip_create_loc_conf(ngx_conf_t *cf) { ngx_http_realip_loc_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_realip_loc_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->from = NULL; * conf->hash = 0; * conf->header = { 0, NULL }; */ conf->type = NGX_CONF_UNSET_UINT; conf->recursive = NGX_CONF_UNSET; return conf; } static char * ngx_http_realip_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_realip_loc_conf_t *prev = parent; ngx_http_realip_loc_conf_t *conf = child; if (conf->from == NULL) { conf->from = prev->from; } ngx_conf_merge_uint_value(conf->type, prev->type, NGX_HTTP_REALIP_XREALIP); ngx_conf_merge_value(conf->recursive, prev->recursive, 0); if (conf->header.len == 0) { conf->hash = prev->hash; conf->header = prev->header; } return NGX_CONF_OK; } static ngx_int_t ngx_http_realip_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var, *v; for (v = ngx_http_realip_vars; v->name.len; v++) { var = ngx_http_add_variable(cf, &v->name, v->flags); if (var == NULL) { return NGX_ERROR; } var->get_handler = v->get_handler; var->data = v->data; } return NGX_OK; } static ngx_int_t ngx_http_realip_init(ngx_conf_t *cf) { ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h = ngx_array_push(&cmcf->phases[NGX_HTTP_POST_READ_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } *h = ngx_http_realip_handler; h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } *h = ngx_http_realip_handler; return NGX_OK; } static ngx_http_realip_ctx_t * ngx_http_realip_get_module_ctx(ngx_http_request_t *r) { ngx_pool_cleanup_t *cln; ngx_http_realip_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_realip_module); if (ctx == NULL && (r->internal || r->filter_finalize)) { /* * if module context was reset, the original address * can still be found in the cleanup handler */ for (cln = r->pool->cleanup; cln; cln = cln->next) { if (cln->handler == ngx_http_realip_cleanup) { ctx = cln->data; break; } } } return ctx; } static ngx_int_t ngx_http_realip_remote_addr_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_str_t *addr_text; ngx_http_realip_ctx_t *ctx; ctx = ngx_http_realip_get_module_ctx(r); addr_text = ctx ? &ctx->addr_text : &r->connection->addr_text; v->len = addr_text->len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = addr_text->data; return NGX_OK; } static ngx_int_t ngx_http_realip_remote_port_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_uint_t port; struct sockaddr *sa; ngx_http_realip_ctx_t *ctx; ctx = ngx_http_realip_get_module_ctx(r); sa = ctx ? ctx->sockaddr : r->connection->sockaddr; v->len = 0; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = ngx_pnalloc(r->pool, sizeof("65535") - 1); if (v->data == NULL) { return NGX_ERROR; } port = ngx_inet_get_port(sa); if (port > 0 && port < 65536) { v->len = ngx_sprintf(v->data, "%ui", port) - v->data; } return NGX_OK; } nginx-1.24.0/src/http/modules/ngx_http_referer_module.c000644 001751 001751 00000041751 14415135676 024427 0ustar00mdouninmdounin000000 000000 /* * 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 ngx_int_t ngx_http_referer_add_variables(ngx_conf_t *cf); static void * ngx_http_referer_create_conf(ngx_conf_t *cf); static char * ngx_http_referer_merge_conf(ngx_conf_t *cf, void *parent, void *child); static char *ngx_http_valid_referers(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_int_t ngx_http_add_referer(ngx_conf_t *cf, ngx_hash_keys_arrays_t *keys, ngx_str_t *value, ngx_str_t *uri); static ngx_int_t ngx_http_add_regex_referer(ngx_conf_t *cf, ngx_http_referer_conf_t *rlcf, ngx_str_t *name); #if (NGX_PCRE) static ngx_int_t ngx_http_add_regex_server_name(ngx_conf_t *cf, ngx_http_referer_conf_t *rlcf, ngx_http_regex_t *regex); #endif static int ngx_libc_cdecl ngx_http_cmp_referer_wildcards(const void *one, const void *two); static ngx_command_t ngx_http_referer_commands[] = { { ngx_string("valid_referers"), NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_valid_referers, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("referer_hash_max_size"), NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_referer_conf_t, referer_hash_max_size), NULL }, { ngx_string("referer_hash_bucket_size"), NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_referer_conf_t, referer_hash_bucket_size), NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_referer_module_ctx = { ngx_http_referer_add_variables, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_referer_create_conf, /* create location configuration */ ngx_http_referer_merge_conf /* merge location configuration */ }; ngx_module_t ngx_http_referer_module = { NGX_MODULE_V1, &ngx_http_referer_module_ctx, /* module context */ ngx_http_referer_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_str_t ngx_http_invalid_referer_name = ngx_string("invalid_referer"); static ngx_int_t ngx_http_referer_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p, *ref, *last; size_t len; ngx_str_t *uri; ngx_uint_t i, key; ngx_http_referer_conf_t *rlcf; u_char buf[256]; #if (NGX_PCRE) ngx_int_t rc; ngx_str_t referer; #endif rlcf = ngx_http_get_module_loc_conf(r, ngx_http_referer_module); if (rlcf->hash.hash.buckets == NULL && rlcf->hash.wc_head == NULL && rlcf->hash.wc_tail == NULL #if (NGX_PCRE) && rlcf->regex == NULL && rlcf->server_name_regex == NULL #endif ) { goto valid; } if (r->headers_in.referer == NULL) { if (rlcf->no_referer) { goto valid; } goto invalid; } len = r->headers_in.referer->value.len; ref = r->headers_in.referer->value.data; if (len >= sizeof("http://i.ru") - 1) { last = ref + len; if (ngx_strncasecmp(ref, (u_char *) "http://", 7) == 0) { ref += 7; len -= 7; goto valid_scheme; } else if (ngx_strncasecmp(ref, (u_char *) "https://", 8) == 0) { ref += 8; len -= 8; goto valid_scheme; } } if (rlcf->blocked_referer) { goto valid; } goto invalid; valid_scheme: i = 0; key = 0; for (p = ref; p < last; p++) { if (*p == '/' || *p == ':') { break; } if (i == 256) { goto invalid; } buf[i] = ngx_tolower(*p); key = ngx_hash(key, buf[i++]); } uri = ngx_hash_find_combined(&rlcf->hash, key, buf, p - ref); if (uri) { goto uri; } #if (NGX_PCRE) if (rlcf->server_name_regex) { referer.len = p - ref; referer.data = buf; rc = ngx_regex_exec_array(rlcf->server_name_regex, &referer, r->connection->log); if (rc == NGX_OK) { goto valid; } if (rc == NGX_ERROR) { return rc; } /* NGX_DECLINED */ } if (rlcf->regex) { referer.len = len; referer.data = ref; rc = ngx_regex_exec_array(rlcf->regex, &referer, r->connection->log); if (rc == NGX_OK) { goto valid; } if (rc == NGX_ERROR) { return rc; } /* NGX_DECLINED */ } #endif invalid: *v = ngx_http_variable_true_value; return NGX_OK; uri: for ( /* void */ ; p < last; p++) { if (*p == '/') { break; } } len = last - p; if (uri == NGX_HTTP_REFERER_NO_URI_PART) { goto valid; } if (len < uri->len || ngx_strncmp(uri->data, p, uri->len) != 0) { goto invalid; } valid: *v = ngx_http_variable_null_value; return NGX_OK; } static ngx_int_t ngx_http_referer_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var; var = ngx_http_add_variable(cf, &ngx_http_invalid_referer_name, NGX_HTTP_VAR_CHANGEABLE); if (var == NULL) { return NGX_ERROR; } var->get_handler = ngx_http_referer_variable; return NGX_OK; } static void * ngx_http_referer_create_conf(ngx_conf_t *cf) { ngx_http_referer_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_referer_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->hash = { NULL }; * conf->server_names = 0; * conf->keys = NULL; */ #if (NGX_PCRE) conf->regex = NGX_CONF_UNSET_PTR; conf->server_name_regex = NGX_CONF_UNSET_PTR; #endif conf->no_referer = NGX_CONF_UNSET; conf->blocked_referer = NGX_CONF_UNSET; conf->referer_hash_max_size = NGX_CONF_UNSET_UINT; conf->referer_hash_bucket_size = NGX_CONF_UNSET_UINT; return conf; } static char * ngx_http_referer_merge_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_referer_conf_t *prev = parent; ngx_http_referer_conf_t *conf = child; ngx_uint_t n; ngx_hash_init_t hash; ngx_http_server_name_t *sn; ngx_http_core_srv_conf_t *cscf; if (conf->keys == NULL) { conf->hash = prev->hash; #if (NGX_PCRE) ngx_conf_merge_ptr_value(conf->regex, prev->regex, NULL); ngx_conf_merge_ptr_value(conf->server_name_regex, prev->server_name_regex, NULL); #endif ngx_conf_merge_value(conf->no_referer, prev->no_referer, 0); ngx_conf_merge_value(conf->blocked_referer, prev->blocked_referer, 0); ngx_conf_merge_uint_value(conf->referer_hash_max_size, prev->referer_hash_max_size, 2048); ngx_conf_merge_uint_value(conf->referer_hash_bucket_size, prev->referer_hash_bucket_size, 64); return NGX_CONF_OK; } if (conf->server_names == 1) { cscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_core_module); sn = cscf->server_names.elts; for (n = 0; n < cscf->server_names.nelts; n++) { #if (NGX_PCRE) if (sn[n].regex) { if (ngx_http_add_regex_server_name(cf, conf, sn[n].regex) != NGX_OK) { return NGX_CONF_ERROR; } continue; } #endif if (ngx_http_add_referer(cf, conf->keys, &sn[n].name, NULL) != NGX_OK) { return NGX_CONF_ERROR; } } } if ((conf->no_referer == 1 || conf->blocked_referer == 1) && conf->keys->keys.nelts == 0 && conf->keys->dns_wc_head.nelts == 0 && conf->keys->dns_wc_tail.nelts == 0) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "the \"none\" or \"blocked\" referers are specified " "in the \"valid_referers\" directive " "without any valid referer"); return NGX_CONF_ERROR; } ngx_conf_merge_uint_value(conf->referer_hash_max_size, prev->referer_hash_max_size, 2048); ngx_conf_merge_uint_value(conf->referer_hash_bucket_size, prev->referer_hash_bucket_size, 64); conf->referer_hash_bucket_size = ngx_align(conf->referer_hash_bucket_size, ngx_cacheline_size); hash.key = ngx_hash_key_lc; hash.max_size = conf->referer_hash_max_size; hash.bucket_size = conf->referer_hash_bucket_size; hash.name = "referer_hash"; hash.pool = cf->pool; if (conf->keys->keys.nelts) { hash.hash = &conf->hash.hash; hash.temp_pool = NULL; if (ngx_hash_init(&hash, conf->keys->keys.elts, conf->keys->keys.nelts) != NGX_OK) { return NGX_CONF_ERROR; } } if (conf->keys->dns_wc_head.nelts) { ngx_qsort(conf->keys->dns_wc_head.elts, (size_t) conf->keys->dns_wc_head.nelts, sizeof(ngx_hash_key_t), ngx_http_cmp_referer_wildcards); hash.hash = NULL; hash.temp_pool = cf->temp_pool; if (ngx_hash_wildcard_init(&hash, conf->keys->dns_wc_head.elts, conf->keys->dns_wc_head.nelts) != NGX_OK) { return NGX_CONF_ERROR; } conf->hash.wc_head = (ngx_hash_wildcard_t *) hash.hash; } if (conf->keys->dns_wc_tail.nelts) { ngx_qsort(conf->keys->dns_wc_tail.elts, (size_t) conf->keys->dns_wc_tail.nelts, sizeof(ngx_hash_key_t), ngx_http_cmp_referer_wildcards); hash.hash = NULL; hash.temp_pool = cf->temp_pool; if (ngx_hash_wildcard_init(&hash, conf->keys->dns_wc_tail.elts, conf->keys->dns_wc_tail.nelts) != NGX_OK) { return NGX_CONF_ERROR; } conf->hash.wc_tail = (ngx_hash_wildcard_t *) hash.hash; } #if (NGX_PCRE) ngx_conf_merge_ptr_value(conf->regex, prev->regex, NULL); ngx_conf_merge_ptr_value(conf->server_name_regex, prev->server_name_regex, NULL); #endif if (conf->no_referer == NGX_CONF_UNSET) { conf->no_referer = 0; } if (conf->blocked_referer == NGX_CONF_UNSET) { conf->blocked_referer = 0; } conf->keys = NULL; return NGX_CONF_OK; } static char * ngx_http_valid_referers(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_referer_conf_t *rlcf = conf; u_char *p; ngx_str_t *value, uri; ngx_uint_t i; if (rlcf->keys == NULL) { rlcf->keys = ngx_pcalloc(cf->temp_pool, sizeof(ngx_hash_keys_arrays_t)); if (rlcf->keys == NULL) { return NGX_CONF_ERROR; } rlcf->keys->pool = cf->pool; rlcf->keys->temp_pool = cf->pool; if (ngx_hash_keys_array_init(rlcf->keys, NGX_HASH_SMALL) != NGX_OK) { return NGX_CONF_ERROR; } } value = cf->args->elts; for (i = 1; i < cf->args->nelts; i++) { if (value[i].len == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid referer \"%V\"", &value[i]); return NGX_CONF_ERROR; } if (ngx_strcmp(value[i].data, "none") == 0) { rlcf->no_referer = 1; continue; } if (ngx_strcmp(value[i].data, "blocked") == 0) { rlcf->blocked_referer = 1; continue; } if (ngx_strcmp(value[i].data, "server_names") == 0) { rlcf->server_names = 1; continue; } if (value[i].data[0] == '~') { if (ngx_http_add_regex_referer(cf, rlcf, &value[i]) != NGX_OK) { return NGX_CONF_ERROR; } continue; } ngx_str_null(&uri); p = (u_char *) ngx_strchr(value[i].data, '/'); if (p) { uri.len = (value[i].data + value[i].len) - p; uri.data = p; value[i].len = p - value[i].data; } if (ngx_http_add_referer(cf, rlcf->keys, &value[i], &uri) != NGX_OK) { return NGX_CONF_ERROR; } } return NGX_CONF_OK; } static ngx_int_t ngx_http_add_referer(ngx_conf_t *cf, ngx_hash_keys_arrays_t *keys, ngx_str_t *value, ngx_str_t *uri) { ngx_int_t rc; ngx_str_t *u; if (uri == NULL || uri->len == 0) { u = NGX_HTTP_REFERER_NO_URI_PART; } else { u = ngx_palloc(cf->pool, sizeof(ngx_str_t)); if (u == NULL) { return NGX_ERROR; } *u = *uri; } rc = ngx_hash_add_key(keys, value, u, NGX_HASH_WILDCARD_KEY); if (rc == NGX_OK) { return NGX_OK; } if (rc == NGX_DECLINED) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid hostname or wildcard \"%V\"", value); } if (rc == NGX_BUSY) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "conflicting parameter \"%V\"", value); } return NGX_ERROR; } static ngx_int_t ngx_http_add_regex_referer(ngx_conf_t *cf, ngx_http_referer_conf_t *rlcf, ngx_str_t *name) { #if (NGX_PCRE) ngx_regex_elt_t *re; ngx_regex_compile_t rc; u_char errstr[NGX_MAX_CONF_ERRSTR]; if (name->len == 1) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "empty regex in \"%V\"", name); return NGX_ERROR; } if (rlcf->regex == NGX_CONF_UNSET_PTR) { rlcf->regex = ngx_array_create(cf->pool, 2, sizeof(ngx_regex_elt_t)); if (rlcf->regex == NULL) { return NGX_ERROR; } } re = ngx_array_push(rlcf->regex); if (re == NULL) { return NGX_ERROR; } name->len--; name->data++; ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); rc.pattern = *name; rc.pool = cf->pool; rc.options = NGX_REGEX_CASELESS; rc.err.len = NGX_MAX_CONF_ERRSTR; rc.err.data = errstr; if (ngx_regex_compile(&rc) != NGX_OK) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err); return NGX_ERROR; } re->regex = rc.regex; re->name = name->data; return NGX_OK; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the using of the regex \"%V\" requires PCRE library", name); return NGX_ERROR; #endif } #if (NGX_PCRE) static ngx_int_t ngx_http_add_regex_server_name(ngx_conf_t *cf, ngx_http_referer_conf_t *rlcf, ngx_http_regex_t *regex) { ngx_regex_elt_t *re; if (rlcf->server_name_regex == NGX_CONF_UNSET_PTR) { rlcf->server_name_regex = ngx_array_create(cf->pool, 2, sizeof(ngx_regex_elt_t)); if (rlcf->server_name_regex == NULL) { return NGX_ERROR; } } re = ngx_array_push(rlcf->server_name_regex); if (re == NULL) { return NGX_ERROR; } re->regex = regex->regex; re->name = regex->name.data; return NGX_OK; } #endif static int ngx_libc_cdecl ngx_http_cmp_referer_wildcards(const void *one, const void *two) { ngx_hash_key_t *first, *second; first = (ngx_hash_key_t *) one; second = (ngx_hash_key_t *) two; return ngx_dns_strcmp(first->key.data, second->key.data); } nginx-1.24.0/src/http/modules/ngx_http_rewrite_module.c000644 001751 001751 00000067322 14415135676 024460 0ustar00mdouninmdounin000000 000000 /* * 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); } return e->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; if (value[2].len == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "empty replacement"); return NGX_CONF_ERROR; } 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|NGX_HTTP_VAR_WEAK); if (v == NULL) { return NGX_CONF_ERROR; } index = ngx_http_get_variable_index(cf, &value[1]); if (index == NGX_ERROR) { return NGX_CONF_ERROR; } if (v->get_handler == NULL) { v->get_handler = ngx_http_rewrite_var; v->data = index; } if (ngx_http_rewrite_value(cf, lcf, &value[2]) != NGX_CONF_OK) { return NGX_CONF_ERROR; } if (v->set_handler) { vhcode = ngx_http_script_start_code(cf->pool, &lcf->codes, sizeof(ngx_http_script_var_handler_code_t)); if (vhcode == NULL) { return NGX_CONF_ERROR; } vhcode->code = ngx_http_script_var_set_handler_code; vhcode->handler = v->set_handler; vhcode->data = v->data; return NGX_CONF_OK; } vcode = ngx_http_script_start_code(cf->pool, &lcf->codes, sizeof(ngx_http_script_var_code_t)); if (vcode == NULL) { return NGX_CONF_ERROR; } vcode->code = ngx_http_script_set_var_code; vcode->index = (uintptr_t) index; return NGX_CONF_OK; } static char * ngx_http_rewrite_value(ngx_conf_t *cf, ngx_http_rewrite_loc_conf_t *lcf, ngx_str_t *value) { ngx_int_t n; ngx_http_script_compile_t sc; ngx_http_script_value_code_t *val; ngx_http_script_complex_value_code_t *complex; n = ngx_http_script_variables_count(value); if (n == 0) { val = ngx_http_script_start_code(cf->pool, &lcf->codes, sizeof(ngx_http_script_value_code_t)); if (val == NULL) { return NGX_CONF_ERROR; } n = ngx_atoi(value->data, value->len); if (n == NGX_ERROR) { n = 0; } val->code = ngx_http_script_value_code; val->value = (uintptr_t) n; val->text_len = (uintptr_t) value->len; val->text_data = (uintptr_t) value->data; return NGX_CONF_OK; } complex = ngx_http_script_start_code(cf->pool, &lcf->codes, sizeof(ngx_http_script_complex_value_code_t)); if (complex == NULL) { return NGX_CONF_ERROR; } complex->code = ngx_http_script_complex_value_code; complex->lengths = NULL; ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); sc.cf = cf; sc.source = value; sc.lengths = &complex->lengths; sc.values = &lcf->codes; sc.variables = n; sc.complete_lengths = 1; if (ngx_http_script_compile(&sc) != NGX_OK) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } nginx-1.24.0/src/http/modules/ngx_http_scgi_module.c000644 001751 001751 00000172312 14415135676 023720 0ustar00mdouninmdounin000000 000000 /* * 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 ngx_int_t ngx_http_scgi_input_filter_init(void *data); static void ngx_http_scgi_abort_request(ngx_http_request_t *r); static void ngx_http_scgi_finalize_request(ngx_http_request_t *r, ngx_int_t rc); static void *ngx_http_scgi_create_main_conf(ngx_conf_t *cf); static void *ngx_http_scgi_create_loc_conf(ngx_conf_t *cf); static char *ngx_http_scgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); static ngx_int_t ngx_http_scgi_init_params(ngx_conf_t *cf, ngx_http_scgi_loc_conf_t *conf, ngx_http_scgi_params_t *params, ngx_keyval_t *default_params); static char *ngx_http_scgi_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_scgi_store(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); #if (NGX_HTTP_CACHE) static ngx_int_t ngx_http_scgi_create_key(ngx_http_request_t *r); static char *ngx_http_scgi_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_scgi_cache_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); #endif static ngx_conf_bitmask_t ngx_http_scgi_next_upstream_masks[] = { { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR }, { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT }, { ngx_string("invalid_header"), NGX_HTTP_UPSTREAM_FT_INVALID_HEADER }, { ngx_string("non_idempotent"), NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT }, { ngx_string("http_500"), NGX_HTTP_UPSTREAM_FT_HTTP_500 }, { ngx_string("http_503"), NGX_HTTP_UPSTREAM_FT_HTTP_503 }, { ngx_string("http_403"), NGX_HTTP_UPSTREAM_FT_HTTP_403 }, { ngx_string("http_404"), NGX_HTTP_UPSTREAM_FT_HTTP_404 }, { ngx_string("http_429"), NGX_HTTP_UPSTREAM_FT_HTTP_429 }, { ngx_string("updating"), NGX_HTTP_UPSTREAM_FT_UPDATING }, { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF }, { ngx_null_string, 0 } }; ngx_module_t ngx_http_scgi_module; static ngx_command_t ngx_http_scgi_commands[] = { { ngx_string("scgi_pass"), NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, ngx_http_scgi_pass, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("scgi_store"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_scgi_store, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("scgi_store_access"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123, ngx_conf_set_access_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.store_access), NULL }, { ngx_string("scgi_buffering"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.buffering), NULL }, { ngx_string("scgi_request_buffering"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.request_buffering), NULL }, { ngx_string("scgi_ignore_client_abort"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.ignore_client_abort), NULL }, { ngx_string("scgi_bind"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12, ngx_http_upstream_bind_set_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.local), NULL }, { ngx_string("scgi_socket_keepalive"), 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.socket_keepalive), NULL }, { ngx_string("scgi_connect_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.connect_timeout), NULL }, { ngx_string("scgi_send_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.send_timeout), NULL }, { ngx_string("scgi_buffer_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.buffer_size), NULL }, { ngx_string("scgi_pass_request_headers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.pass_request_headers), NULL }, { ngx_string("scgi_pass_request_body"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.pass_request_body), NULL }, { ngx_string("scgi_intercept_errors"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.intercept_errors), NULL }, { ngx_string("scgi_read_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.read_timeout), NULL }, { ngx_string("scgi_buffers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, ngx_conf_set_bufs_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.bufs), NULL }, { ngx_string("scgi_busy_buffers_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.busy_buffers_size_conf), NULL }, { ngx_string("scgi_force_ranges"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.force_ranges), NULL }, { ngx_string("scgi_limit_rate"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.limit_rate), NULL }, #if (NGX_HTTP_CACHE) { ngx_string("scgi_cache"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_scgi_cache, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("scgi_cache_key"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_scgi_cache_key, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("scgi_cache_path"), NGX_HTTP_MAIN_CONF|NGX_CONF_2MORE, ngx_http_file_cache_set_slot, NGX_HTTP_MAIN_CONF_OFFSET, offsetof(ngx_http_scgi_main_conf_t, caches), &ngx_http_scgi_module }, { ngx_string("scgi_cache_bypass"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_set_predicate_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_bypass), NULL }, { ngx_string("scgi_no_cache"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_set_predicate_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.no_cache), NULL }, { ngx_string("scgi_cache_valid"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_file_cache_valid_set_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_valid), NULL }, { ngx_string("scgi_cache_min_uses"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_min_uses), NULL }, { ngx_string("scgi_cache_max_range_offset"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_off_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_max_range_offset), NULL }, { ngx_string("scgi_cache_use_stale"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_use_stale), &ngx_http_scgi_next_upstream_masks }, { ngx_string("scgi_cache_methods"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_methods), &ngx_http_upstream_cache_method_mask }, { ngx_string("scgi_cache_lock"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_lock), NULL }, { ngx_string("scgi_cache_lock_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_lock_timeout), NULL }, { ngx_string("scgi_cache_lock_age"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_lock_age), NULL }, { ngx_string("scgi_cache_revalidate"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_revalidate), NULL }, { ngx_string("scgi_cache_background_update"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_background_update), NULL }, #endif { ngx_string("scgi_temp_path"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234, ngx_conf_set_path_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.temp_path), NULL }, { ngx_string("scgi_max_temp_file_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.max_temp_file_size_conf), NULL }, { ngx_string("scgi_temp_file_write_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.temp_file_write_size_conf), NULL }, { ngx_string("scgi_next_upstream"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.next_upstream), &ngx_http_scgi_next_upstream_masks }, { ngx_string("scgi_next_upstream_tries"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.next_upstream_tries), NULL }, { ngx_string("scgi_next_upstream_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.next_upstream_timeout), NULL }, { ngx_string("scgi_param"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE23, ngx_http_upstream_param_set_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, params_source), NULL }, { ngx_string("scgi_pass_header"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_array_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.pass_headers), NULL }, { ngx_string("scgi_hide_header"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_array_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.hide_headers), NULL }, { ngx_string("scgi_ignore_headers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.ignore_headers), &ngx_http_upstream_ignore_headers_masks }, ngx_null_command }; static ngx_http_module_t ngx_http_scgi_module_ctx = { NULL, /* preconfiguration */ NULL, /* postconfiguration */ ngx_http_scgi_create_main_conf, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_scgi_create_loc_conf, /* create location configuration */ ngx_http_scgi_merge_loc_conf /* merge location configuration */ }; ngx_module_t ngx_http_scgi_module = { NGX_MODULE_V1, &ngx_http_scgi_module_ctx, /* module context */ ngx_http_scgi_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_str_t ngx_http_scgi_hide_headers[] = { ngx_string("Status"), ngx_string("X-Accel-Expires"), ngx_string("X-Accel-Redirect"), ngx_string("X-Accel-Limit-Rate"), ngx_string("X-Accel-Buffering"), ngx_string("X-Accel-Charset"), ngx_null_string }; #if (NGX_HTTP_CACHE) static ngx_keyval_t ngx_http_scgi_cache_headers[] = { { ngx_string("HTTP_IF_MODIFIED_SINCE"), ngx_string("$upstream_cache_last_modified") }, { ngx_string("HTTP_IF_UNMODIFIED_SINCE"), ngx_string("") }, { ngx_string("HTTP_IF_NONE_MATCH"), ngx_string("$upstream_cache_etag") }, { ngx_string("HTTP_IF_MATCH"), ngx_string("") }, { ngx_string("HTTP_RANGE"), ngx_string("") }, { ngx_string("HTTP_IF_RANGE"), ngx_string("") }, { ngx_null_string, ngx_null_string } }; #endif static ngx_path_init_t ngx_http_scgi_temp_path = { ngx_string(NGX_HTTP_SCGI_TEMP_PATH), { 1, 2, 0 } }; static ngx_int_t ngx_http_scgi_handler(ngx_http_request_t *r) { ngx_int_t rc; ngx_http_status_t *status; ngx_http_upstream_t *u; ngx_http_scgi_loc_conf_t *scf; #if (NGX_HTTP_CACHE) ngx_http_scgi_main_conf_t *smcf; #endif if (ngx_http_upstream_create(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } status = ngx_pcalloc(r->pool, sizeof(ngx_http_status_t)); if (status == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_http_set_ctx(r, status, ngx_http_scgi_module); scf = ngx_http_get_module_loc_conf(r, ngx_http_scgi_module); if (scf->scgi_lengths) { if (ngx_http_scgi_eval(r, scf) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } } u = r->upstream; ngx_str_set(&u->schema, "scgi://"); u->output.tag = (ngx_buf_tag_t) &ngx_http_scgi_module; u->conf = &scf->upstream; #if (NGX_HTTP_CACHE) smcf = ngx_http_get_module_main_conf(r, ngx_http_scgi_module); u->caches = &smcf->caches; u->create_key = ngx_http_scgi_create_key; #endif u->create_request = ngx_http_scgi_create_request; u->reinit_request = ngx_http_scgi_reinit_request; u->process_header = ngx_http_scgi_process_status_line; u->abort_request = ngx_http_scgi_abort_request; u->finalize_request = ngx_http_scgi_finalize_request; r->state = 0; u->buffering = scf->upstream.buffering; u->pipe = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t)); if (u->pipe == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } u->pipe->input_filter = ngx_event_pipe_copy_input_filter; u->pipe->input_ctx = r; u->input_filter_init = ngx_http_scgi_input_filter_init; u->input_filter = ngx_http_upstream_non_buffered_filter; u->input_filter_ctx = r; if (!scf->upstream.request_buffering && scf->upstream.pass_request_body && !r->headers_in.chunked) { r->request_body_no_buffering = 1; } rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init); if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { return rc; } return NGX_DONE; } static ngx_int_t ngx_http_scgi_eval(ngx_http_request_t *r, ngx_http_scgi_loc_conf_t * scf) { ngx_url_t url; ngx_http_upstream_t *u; ngx_memzero(&url, sizeof(ngx_url_t)); if (ngx_http_script_run(r, &url.url, scf->scgi_lengths->elts, 0, scf->scgi_values->elts) == NULL) { return NGX_ERROR; } url.no_resolve = 1; if (ngx_parse_url(r->pool, &url) != NGX_OK) { if (url.err) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "%s in upstream \"%V\"", url.err, &url.url); } return NGX_ERROR; } u = r->upstream; u->resolved = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t)); if (u->resolved == NULL) { return NGX_ERROR; } if (url.addrs) { u->resolved->sockaddr = url.addrs[0].sockaddr; u->resolved->socklen = url.addrs[0].socklen; u->resolved->name = url.addrs[0].name; u->resolved->naddrs = 1; } u->resolved->host = url.host; u->resolved->port = url.port; u->resolved->no_port = url.no_port; return NGX_OK; } #if (NGX_HTTP_CACHE) static ngx_int_t ngx_http_scgi_create_key(ngx_http_request_t *r) { ngx_str_t *key; ngx_http_scgi_loc_conf_t *scf; key = ngx_array_push(&r->cache->keys); if (key == NULL) { return NGX_ERROR; } scf = ngx_http_get_module_loc_conf(r, ngx_http_scgi_module); if (ngx_http_complex_value(r, &scf->cache_key, key) != NGX_OK) { return NGX_ERROR; } return NGX_OK; } #endif static ngx_int_t ngx_http_scgi_create_request(ngx_http_request_t *r) { off_t content_length_n; u_char ch, sep, *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, *hn, **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 (ngx_http_link_multi_headers(r) != NGX_OK) { return NGX_ERROR; } if (params->number || r->headers_in.multi) { 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; } for (n = 0; n < header_params; n++) { if (&header[i] == ignored[n]) { goto next_length; } } 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; for (hn = header[i].next; hn; hn = hn->next) { len += hn->value.len + 2; ignored[header_params++] = hn; } next_length: continue; } } /* 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_value; } } 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); if (header[i].next) { if (header[i].key.len == sizeof("Cookie") - 1 && ngx_strncasecmp(header[i].key.data, (u_char *) "Cookie", sizeof("Cookie") - 1) == 0) { sep = ';'; } else { sep = ','; } for (hn = header[i].next; hn; hn = hn->next) { *b->last++ = sep; *b->last++ = ' '; b->last = ngx_copy(b->last, hn->value.data, hn->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_value: continue; } } *b->last++ = (u_char) ','; if (r->request_body_no_buffering) { r->upstream->request_bufs = cl; } else if (scf->upstream.pass_request_body) { body = r->upstream->request_bufs; r->upstream->request_bufs = cl; while (body) { b = ngx_alloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } ngx_memcpy(b, body->buf, sizeof(ngx_buf_t)); cl->next = ngx_alloc_chain_link(r->pool); if (cl->next == NULL) { return NGX_ERROR; } cl = cl->next; cl->buf = b; body = body->next; } } else { r->upstream->request_bufs = cl; } cl->next = NULL; return NGX_OK; } static ngx_int_t ngx_http_scgi_reinit_request(ngx_http_request_t *r) { ngx_http_status_t *status; status = ngx_http_get_module_ctx(r, ngx_http_scgi_module); if (status == NULL) { return NGX_OK; } status->code = 0; status->count = 0; status->start = NULL; status->end = NULL; r->upstream->process_header = ngx_http_scgi_process_status_line; r->state = 0; return NGX_OK; } static ngx_int_t ngx_http_scgi_process_status_line(ngx_http_request_t *r) { size_t len; ngx_int_t rc; ngx_http_status_t *status; ngx_http_upstream_t *u; status = ngx_http_get_module_ctx(r, ngx_http_scgi_module); if (status == NULL) { return NGX_ERROR; } u = r->upstream; rc = ngx_http_parse_status_line(r, &u->buffer, status); if (rc == NGX_AGAIN) { return rc; } if (rc == NGX_ERROR) { u->process_header = ngx_http_scgi_process_header; return ngx_http_scgi_process_header(r); } if (u->state && u->state->status == 0) { u->state->status = status->code; } u->headers_in.status_n = status->code; len = status->end - status->start; u->headers_in.status_line.len = len; u->headers_in.status_line.data = ngx_pnalloc(r->pool, len); if (u->headers_in.status_line.data == NULL) { return NGX_ERROR; } ngx_memcpy(u->headers_in.status_line.data, status->start, len); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http scgi status %ui \"%V\"", u->headers_in.status_n, &u->headers_in.status_line); u->process_header = ngx_http_scgi_process_header; return ngx_http_scgi_process_header(r); } static ngx_int_t ngx_http_scgi_process_header(ngx_http_request_t *r) { ngx_str_t *status_line; ngx_int_t rc, status; ngx_table_elt_t *h; ngx_http_upstream_t *u; ngx_http_upstream_header_t *hh; ngx_http_upstream_main_conf_t *umcf; umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module); for ( ;; ) { rc = ngx_http_parse_header_line(r, &r->upstream->buffer, 1); if (rc == NGX_OK) { /* a header line has been parsed successfully */ h = ngx_list_push(&r->upstream->headers_in.headers); if (h == NULL) { return NGX_ERROR; } h->hash = r->header_hash; h->key.len = r->header_name_end - r->header_name_start; h->value.len = r->header_end - r->header_start; h->key.data = ngx_pnalloc(r->pool, h->key.len + 1 + h->value.len + 1 + h->key.len); if (h->key.data == NULL) { h->hash = 0; return NGX_ERROR; } h->value.data = h->key.data + h->key.len + 1; h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1; ngx_memcpy(h->key.data, r->header_name_start, h->key.len); h->key.data[h->key.len] = '\0'; ngx_memcpy(h->value.data, r->header_start, h->value.len); h->value.data[h->value.len] = '\0'; if (h->key.len == r->lowcase_index) { ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len); } else { ngx_strlow(h->lowcase_key, h->key.data, h->key.len); } hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len); if (hh) { rc = hh->handler(r, h, hh->offset); if (rc != NGX_OK) { return rc; } } 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; } /* rc == NGX_HTTP_PARSE_INVALID_HEADER */ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid header: \"%*s\\x%02xd...\"", r->header_end - r->header_name_start, r->header_name_start, *r->header_end); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } } static ngx_int_t ngx_http_scgi_input_filter_init(void *data) { ngx_http_request_t *r = data; ngx_http_upstream_t *u; u = r->upstream; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http scgi filter init s:%ui l:%O", u->headers_in.status_n, u->headers_in.content_length_n); if (u->headers_in.status_n == NGX_HTTP_NO_CONTENT || u->headers_in.status_n == NGX_HTTP_NOT_MODIFIED) { u->pipe->length = 0; u->length = 0; } else if (r->method == NGX_HTTP_HEAD) { u->pipe->length = -1; u->length = -1; } else { u->pipe->length = u->headers_in.content_length_n; u->length = u->headers_in.content_length_n; } return NGX_OK; } static void ngx_http_scgi_abort_request(ngx_http_request_t *r) { 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.socket_keepalive = NGX_CONF_UNSET; conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.next_upstream_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.send_lowat = NGX_CONF_UNSET_SIZE; conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE; conf->upstream.limit_rate = NGX_CONF_UNSET_SIZE; conf->upstream.busy_buffers_size_conf = NGX_CONF_UNSET_SIZE; conf->upstream.max_temp_file_size_conf = NGX_CONF_UNSET_SIZE; conf->upstream.temp_file_write_size_conf = NGX_CONF_UNSET_SIZE; conf->upstream.pass_request_headers = NGX_CONF_UNSET; conf->upstream.pass_request_body = NGX_CONF_UNSET; #if (NGX_HTTP_CACHE) conf->upstream.cache = NGX_CONF_UNSET; conf->upstream.cache_min_uses = NGX_CONF_UNSET_UINT; conf->upstream.cache_max_range_offset = NGX_CONF_UNSET; conf->upstream.cache_bypass = NGX_CONF_UNSET_PTR; conf->upstream.no_cache = NGX_CONF_UNSET_PTR; conf->upstream.cache_valid = NGX_CONF_UNSET_PTR; conf->upstream.cache_lock = NGX_CONF_UNSET; conf->upstream.cache_lock_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.cache_lock_age = NGX_CONF_UNSET_MSEC; conf->upstream.cache_revalidate = NGX_CONF_UNSET; conf->upstream.cache_background_update = NGX_CONF_UNSET; #endif conf->upstream.hide_headers = NGX_CONF_UNSET_PTR; conf->upstream.pass_headers = NGX_CONF_UNSET_PTR; conf->upstream.intercept_errors = NGX_CONF_UNSET; /* "scgi_cyclic_temp_file" is disabled */ conf->upstream.cyclic_temp_file = 0; conf->upstream.change_buffering = 1; ngx_str_set(&conf->upstream.module, "scgi"); return conf; } static char * ngx_http_scgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_scgi_loc_conf_t *prev = parent; ngx_http_scgi_loc_conf_t *conf = child; size_t size; ngx_int_t rc; ngx_hash_init_t hash; ngx_http_core_loc_conf_t *clcf; #if (NGX_HTTP_CACHE) if (conf->upstream.store > 0) { conf->upstream.cache = 0; } if (conf->upstream.cache > 0) { conf->upstream.store = 0; } #endif if (conf->upstream.store == NGX_CONF_UNSET) { ngx_conf_merge_value(conf->upstream.store, prev->upstream.store, 0); conf->upstream.store_lengths = prev->upstream.store_lengths; conf->upstream.store_values = prev->upstream.store_values; } ngx_conf_merge_uint_value(conf->upstream.store_access, prev->upstream.store_access, 0600); ngx_conf_merge_uint_value(conf->upstream.next_upstream_tries, prev->upstream.next_upstream_tries, 0); ngx_conf_merge_value(conf->upstream.buffering, prev->upstream.buffering, 1); ngx_conf_merge_value(conf->upstream.request_buffering, prev->upstream.request_buffering, 1); ngx_conf_merge_value(conf->upstream.ignore_client_abort, prev->upstream.ignore_client_abort, 0); ngx_conf_merge_value(conf->upstream.force_ranges, prev->upstream.force_ranges, 0); ngx_conf_merge_ptr_value(conf->upstream.local, prev->upstream.local, NULL); ngx_conf_merge_value(conf->upstream.socket_keepalive, prev->upstream.socket_keepalive, 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.send_lowat, prev->upstream.send_lowat, 0); ngx_conf_merge_size_value(conf->upstream.buffer_size, prev->upstream.buffer_size, (size_t) ngx_pagesize); ngx_conf_merge_size_value(conf->upstream.limit_rate, prev->upstream.limit_rate, 0); ngx_conf_merge_bufs_value(conf->upstream.bufs, prev->upstream.bufs, 8, ngx_pagesize); if (conf->upstream.bufs.num < 2) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "there must be at least 2 \"scgi_buffers\""); return NGX_CONF_ERROR; } size = conf->upstream.buffer_size; if (size < conf->upstream.bufs.size) { size = conf->upstream.bufs.size; } ngx_conf_merge_size_value(conf->upstream.busy_buffers_size_conf, prev->upstream.busy_buffers_size_conf, NGX_CONF_UNSET_SIZE); if (conf->upstream.busy_buffers_size_conf == NGX_CONF_UNSET_SIZE) { conf->upstream.busy_buffers_size = 2 * size; } else { conf->upstream.busy_buffers_size = conf->upstream.busy_buffers_size_conf; } if (conf->upstream.busy_buffers_size < size) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"scgi_busy_buffers_size\" must be equal to or greater " "than the maximum of the value of \"scgi_buffer_size\" and " "one of the \"scgi_buffers\""); return NGX_CONF_ERROR; } if (conf->upstream.busy_buffers_size > (conf->upstream.bufs.num - 1) * conf->upstream.bufs.size) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"scgi_busy_buffers_size\" must be less than " "the size of all \"scgi_buffers\" minus one buffer"); return NGX_CONF_ERROR; } ngx_conf_merge_size_value(conf->upstream.temp_file_write_size_conf, prev->upstream.temp_file_write_size_conf, NGX_CONF_UNSET_SIZE); if (conf->upstream.temp_file_write_size_conf == NGX_CONF_UNSET_SIZE) { conf->upstream.temp_file_write_size = 2 * size; } else { conf->upstream.temp_file_write_size = conf->upstream.temp_file_write_size_conf; } if (conf->upstream.temp_file_write_size < size) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"scgi_temp_file_write_size\" must be equal to or greater than " "the maximum of the value of \"scgi_buffer_size\" and " "one of the \"scgi_buffers\""); return NGX_CONF_ERROR; } ngx_conf_merge_size_value(conf->upstream.max_temp_file_size_conf, prev->upstream.max_temp_file_size_conf, NGX_CONF_UNSET_SIZE); if (conf->upstream.max_temp_file_size_conf == NGX_CONF_UNSET_SIZE) { conf->upstream.max_temp_file_size = 1024 * 1024 * 1024; } else { conf->upstream.max_temp_file_size = conf->upstream.max_temp_file_size_conf; } if (conf->upstream.max_temp_file_size != 0 && conf->upstream.max_temp_file_size < size) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"scgi_max_temp_file_size\" must be equal to zero to disable " "temporary files usage or must be equal to or greater than " "the maximum of the value of \"scgi_buffer_size\" and " "one of the \"scgi_buffers\""); return NGX_CONF_ERROR; } ngx_conf_merge_bitmask_value(conf->upstream.ignore_headers, prev->upstream.ignore_headers, NGX_CONF_BITMASK_SET); ngx_conf_merge_bitmask_value(conf->upstream.next_upstream, prev->upstream.next_upstream, (NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_ERROR |NGX_HTTP_UPSTREAM_FT_TIMEOUT)); if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) { conf->upstream.next_upstream = NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_OFF; } if (ngx_conf_merge_path_value(cf, &conf->upstream.temp_path, prev->upstream.temp_path, &ngx_http_scgi_temp_path) != NGX_OK) { return NGX_CONF_ERROR; } #if (NGX_HTTP_CACHE) if (conf->upstream.cache == NGX_CONF_UNSET) { ngx_conf_merge_value(conf->upstream.cache, prev->upstream.cache, 0); conf->upstream.cache_zone = prev->upstream.cache_zone; conf->upstream.cache_value = prev->upstream.cache_value; } if (conf->upstream.cache_zone && conf->upstream.cache_zone->data == NULL) { ngx_shm_zone_t *shm_zone; shm_zone = conf->upstream.cache_zone; ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"scgi_cache\" zone \"%V\" is unknown", &shm_zone->shm.name); return NGX_CONF_ERROR; } ngx_conf_merge_uint_value(conf->upstream.cache_min_uses, prev->upstream.cache_min_uses, 1); ngx_conf_merge_off_value(conf->upstream.cache_max_range_offset, prev->upstream.cache_max_range_offset, NGX_MAX_OFF_T_VALUE); ngx_conf_merge_bitmask_value(conf->upstream.cache_use_stale, prev->upstream.cache_use_stale, (NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_OFF)); if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_OFF) { conf->upstream.cache_use_stale = NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_OFF; } if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_ERROR) { conf->upstream.cache_use_stale |= NGX_HTTP_UPSTREAM_FT_NOLIVE; } if (conf->upstream.cache_methods == 0) { conf->upstream.cache_methods = prev->upstream.cache_methods; } conf->upstream.cache_methods |= NGX_HTTP_GET|NGX_HTTP_HEAD; ngx_conf_merge_ptr_value(conf->upstream.cache_bypass, prev->upstream.cache_bypass, NULL); ngx_conf_merge_ptr_value(conf->upstream.no_cache, prev->upstream.no_cache, NULL); ngx_conf_merge_ptr_value(conf->upstream.cache_valid, prev->upstream.cache_valid, NULL); if (conf->cache_key.value.data == NULL) { conf->cache_key = prev->cache_key; } if (conf->upstream.cache && conf->cache_key.value.data == NULL) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "no \"scgi_cache_key\" for \"scgi_cache\""); } ngx_conf_merge_value(conf->upstream.cache_lock, prev->upstream.cache_lock, 0); ngx_conf_merge_msec_value(conf->upstream.cache_lock_timeout, prev->upstream.cache_lock_timeout, 5000); ngx_conf_merge_msec_value(conf->upstream.cache_lock_age, prev->upstream.cache_lock_age, 5000); ngx_conf_merge_value(conf->upstream.cache_revalidate, prev->upstream.cache_revalidate, 0); ngx_conf_merge_value(conf->upstream.cache_background_update, prev->upstream.cache_background_update, 0); #endif ngx_conf_merge_value(conf->upstream.pass_request_headers, prev->upstream.pass_request_headers, 1); ngx_conf_merge_value(conf->upstream.pass_request_body, prev->upstream.pass_request_body, 1); ngx_conf_merge_value(conf->upstream.intercept_errors, prev->upstream.intercept_errors, 0); hash.max_size = 512; hash.bucket_size = ngx_align(64, ngx_cacheline_size); hash.name = "scgi_hide_headers_hash"; if (ngx_http_upstream_hide_headers_hash(cf, &conf->upstream, &prev->upstream, ngx_http_scgi_hide_headers, &hash) != NGX_OK) { return NGX_CONF_ERROR; } clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); if (clcf->noname && conf->upstream.upstream == NULL && conf->scgi_lengths == NULL) { conf->upstream.upstream = prev->upstream.upstream; conf->scgi_lengths = prev->scgi_lengths; conf->scgi_values = prev->scgi_values; } if (clcf->lmt_excpt && clcf->handler == NULL && (conf->upstream.upstream || conf->scgi_lengths)) { clcf->handler = ngx_http_scgi_handler; } if (conf->params_source == NULL) { conf->params = prev->params; #if (NGX_HTTP_CACHE) conf->params_cache = prev->params_cache; #endif conf->params_source = prev->params_source; } rc = ngx_http_scgi_init_params(cf, conf, &conf->params, NULL); if (rc != NGX_OK) { return NGX_CONF_ERROR; } #if (NGX_HTTP_CACHE) if (conf->upstream.cache) { rc = ngx_http_scgi_init_params(cf, conf, &conf->params_cache, ngx_http_scgi_cache_headers); if (rc != NGX_OK) { return NGX_CONF_ERROR; } } #endif /* * special handling to preserve conf->params in the "http" section * to inherit it to all servers */ if (prev->params.hash.buckets == NULL && conf->params_source == prev->params_source) { prev->params = conf->params; #if (NGX_HTTP_CACHE) prev->params_cache = conf->params_cache; #endif } return NGX_CONF_OK; } static ngx_int_t ngx_http_scgi_init_params(ngx_conf_t *cf, ngx_http_scgi_loc_conf_t *conf, ngx_http_scgi_params_t *params, ngx_keyval_t *default_params) { u_char *p; size_t size; uintptr_t *code; ngx_uint_t i, nsrc; ngx_array_t headers_names, params_merged; ngx_keyval_t *h; ngx_hash_key_t *hk; ngx_hash_init_t hash; ngx_http_upstream_param_t *src, *s; ngx_http_script_compile_t sc; ngx_http_script_copy_code_t *copy; if (params->hash.buckets) { return NGX_OK; } if (conf->params_source == NULL && default_params == NULL) { params->hash.buckets = (void *) 1; return NGX_OK; } params->lengths = ngx_array_create(cf->pool, 64, 1); if (params->lengths == NULL) { return NGX_ERROR; } params->values = ngx_array_create(cf->pool, 512, 1); if (params->values == NULL) { return NGX_ERROR; } if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t)) != NGX_OK) { return NGX_ERROR; } if (conf->params_source) { src = conf->params_source->elts; nsrc = conf->params_source->nelts; } else { src = NULL; nsrc = 0; } if (default_params) { if (ngx_array_init(¶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) (void *) ngx_http_script_copy_len_code; copy->len = src[i].key.len + 1; copy = ngx_array_push_n(params->lengths, sizeof(ngx_http_script_copy_code_t)); if (copy == NULL) { return NGX_ERROR; } copy->code = (ngx_http_script_code_pt) (void *) ngx_http_script_copy_len_code; copy->len = src[i].skip_empty; size = (sizeof(ngx_http_script_copy_code_t) + src[i].key.len + 1 + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1); copy = ngx_array_push_n(params->values, size); if (copy == NULL) { return NGX_ERROR; } copy->code = ngx_http_script_copy_code; copy->len = src[i].key.len + 1; p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t); (void) ngx_cpystrn(p, src[i].key.data, src[i].key.len + 1); ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); sc.cf = cf; sc.source = &src[i].value; sc.flushes = ¶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.len && 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 nginx-1.24.0/src/http/modules/ngx_http_secure_link_module.c000644 001751 001751 00000021765 14415135676 025303 0ustar00mdouninmdounin000000 000000 /* * 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[18], md5_buf[16]; conf = ngx_http_get_module_loc_conf(r, ngx_http_secure_link_module); if (conf->secret.data) { return ngx_http_secure_link_old_variable(r, conf, v, data); } if (conf->variable == NULL || conf->md5 == NULL) { goto not_found; } if (ngx_http_complex_value(r, conf->variable, &val) != NGX_OK) { return NGX_ERROR; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "secure link: \"%V\"", &val); last = val.data + val.len; p = ngx_strlchr(val.data, last, ','); expires = 0; if (p) { val.len = p++ - val.data; expires = ngx_atotm(p, last - p); if (expires <= 0) { goto not_found; } ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_secure_link_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_secure_link_module); ctx->expires.len = last - p; ctx->expires.data = p; } if (val.len > 24) { goto not_found; } hash.data = hash_buf; if (ngx_decode_base64url(&hash, &val) != NGX_OK) { goto not_found; } if (hash.len != 16) { goto not_found; } if (ngx_http_complex_value(r, conf->md5, &val) != NGX_OK) { return NGX_ERROR; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "secure link md5: \"%V\"", &val); ngx_md5_init(&md5); ngx_md5_update(&md5, val.data, val.len); ngx_md5_final(md5_buf, &md5); if (ngx_memcmp(hash_buf, md5_buf, 16) != 0) { goto not_found; } v->data = (u_char *) ((expires && expires < ngx_time()) ? "0" : "1"); v->len = 1; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; return NGX_OK; not_found: v->not_found = 1; return NGX_OK; } static ngx_int_t ngx_http_secure_link_old_variable(ngx_http_request_t *r, ngx_http_secure_link_conf_t *conf, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p, *start, *end, *last; size_t len; ngx_int_t n; ngx_uint_t i; ngx_md5_t md5; u_char hash[16]; p = &r->unparsed_uri.data[1]; last = r->unparsed_uri.data + r->unparsed_uri.len; while (p < last) { if (*p++ == '/') { start = p; goto md5_start; } } goto not_found; md5_start: while (p < last) { if (*p++ == '/') { end = p - 1; goto url_start; } } goto not_found; url_start: len = last - p; if (end - start != 32 || len == 0) { goto not_found; } ngx_md5_init(&md5); ngx_md5_update(&md5, p, len); ngx_md5_update(&md5, conf->secret.data, conf->secret.len); ngx_md5_final(hash, &md5); for (i = 0; i < 16; i++) { n = ngx_hextoi(&start[2 * i], 2); if (n == NGX_ERROR || n != hash[i]) { goto not_found; } } v->len = len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; not_found: v->not_found = 1; return NGX_OK; } static ngx_int_t ngx_http_secure_link_expires_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_http_secure_link_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_secure_link_module); if (ctx) { v->len = ctx->expires.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = ctx->expires.data; } else { v->not_found = 1; } return NGX_OK; } static void * ngx_http_secure_link_create_conf(ngx_conf_t *cf) { ngx_http_secure_link_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_secure_link_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->secret = { 0, NULL }; */ conf->variable = NGX_CONF_UNSET_PTR; conf->md5 = NGX_CONF_UNSET_PTR; 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) { ngx_conf_init_ptr_value(conf->variable, NULL); ngx_conf_init_ptr_value(conf->md5, NULL); if (conf->variable || conf->md5) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"secure_link_secret\" cannot be mixed with " "\"secure_link\" and \"secure_link_md5\""); return NGX_CONF_ERROR; } return NGX_CONF_OK; } ngx_conf_merge_ptr_value(conf->variable, prev->variable, NULL); ngx_conf_merge_ptr_value(conf->md5, prev->md5, NULL); if (conf->variable == NULL && conf->md5 == NULL) { conf->secret = prev->secret; } 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; } nginx-1.24.0/src/http/modules/ngx_http_slice_filter_module.c000644 001751 001751 00000033315 14415135676 025436 0ustar00mdouninmdounin000000 000000 /* * 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; unsigned last:1; unsigned active:1; ngx_http_request_t *sr; } ngx_http_slice_ctx_t; typedef struct { off_t start; off_t end; off_t complete_length; } ngx_http_slice_content_range_t; static ngx_int_t ngx_http_slice_header_filter(ngx_http_request_t *r); static ngx_int_t ngx_http_slice_body_filter(ngx_http_request_t *r, ngx_chain_t *in); static ngx_int_t ngx_http_slice_parse_content_range(ngx_http_request_t *r, ngx_http_slice_content_range_t *cr); static ngx_int_t ngx_http_slice_range_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static off_t ngx_http_slice_get_start(ngx_http_request_t *r); static void *ngx_http_slice_create_loc_conf(ngx_conf_t *cf); static char *ngx_http_slice_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); static ngx_int_t ngx_http_slice_add_variables(ngx_conf_t *cf); static ngx_int_t ngx_http_slice_init(ngx_conf_t *cf); static ngx_command_t ngx_http_slice_filter_commands[] = { { ngx_string("slice"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_slice_loc_conf_t, size), NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_slice_filter_module_ctx = { ngx_http_slice_add_variables, /* preconfiguration */ ngx_http_slice_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_slice_create_loc_conf, /* create location configuration */ ngx_http_slice_merge_loc_conf /* merge location configuration */ }; ngx_module_t ngx_http_slice_filter_module = { NGX_MODULE_V1, &ngx_http_slice_filter_module_ctx, /* module context */ ngx_http_slice_filter_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_str_t ngx_http_slice_range_name = ngx_string("slice_range"); static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter; static ngx_int_t ngx_http_slice_header_filter(ngx_http_request_t *r) { off_t end; ngx_int_t rc; ngx_table_elt_t *h; ngx_http_slice_ctx_t *ctx; ngx_http_slice_loc_conf_t *slcf; ngx_http_slice_content_range_t cr; ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module); if (ctx == NULL) { return ngx_http_next_header_filter(r); } if (r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT) { if (r == r->main) { ngx_http_set_ctx(r, NULL, ngx_http_slice_filter_module); return ngx_http_next_header_filter(r); } ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "unexpected status code %ui in slice response", r->headers_out.status); return NGX_ERROR; } h = r->headers_out.etag; if (ctx->etag.len) { if (h == NULL || h->value.len != ctx->etag.len || ngx_strncmp(h->value.data, ctx->etag.data, ctx->etag.len) != 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "etag mismatch in slice response"); return NGX_ERROR; } } if (h) { ctx->etag = h->value; } if (ngx_http_slice_parse_content_range(r, &cr) != NGX_OK) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid range in slice response"); return NGX_ERROR; } if (cr.complete_length == -1) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no complete length in slice response"); return NGX_ERROR; } ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http slice response range: %O-%O/%O", cr.start, cr.end, cr.complete_length); slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module); end = ngx_min(cr.start + (off_t) slcf->size, cr.complete_length); if (cr.start != ctx->start || cr.end != end) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "unexpected range in slice response: %O-%O", cr.start, cr.end); return NGX_ERROR; } ctx->start = end; ctx->active = 1; r->headers_out.status = NGX_HTTP_OK; r->headers_out.status_line.len = 0; r->headers_out.content_length_n = cr.complete_length; r->headers_out.content_offset = cr.start; r->headers_out.content_range->hash = 0; r->headers_out.content_range = NULL; if (r->headers_out.accept_ranges) { r->headers_out.accept_ranges->hash = 0; r->headers_out.accept_ranges = NULL; } r->allow_ranges = 1; r->subrequest_ranges = 1; r->single_range = 1; rc = ngx_http_next_header_filter(r); if (r != r->main) { return rc; } r->preserve_body = 1; if (r->headers_out.status == NGX_HTTP_PARTIAL_CONTENT) { if (ctx->start + (off_t) slcf->size <= r->headers_out.content_offset) { ctx->start = slcf->size * (r->headers_out.content_offset / slcf->size); } ctx->end = r->headers_out.content_offset + r->headers_out.content_length_n; } else { ctx->end = cr.complete_length; } return rc; } static ngx_int_t ngx_http_slice_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_int_t rc; ngx_chain_t *cl; ngx_http_slice_ctx_t *ctx; ngx_http_slice_loc_conf_t *slcf; ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module); if (ctx == NULL || r != r->main) { return ngx_http_next_body_filter(r, in); } for (cl = in; cl; cl = cl->next) { if (cl->buf->last_buf) { cl->buf->last_buf = 0; cl->buf->last_in_chain = 1; cl->buf->sync = 1; ctx->last = 1; } } rc = ngx_http_next_body_filter(r, in); if (rc == NGX_ERROR || !ctx->last) { return rc; } if (ctx->sr && !ctx->sr->done) { return rc; } if (!ctx->active) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "missing slice response"); return NGX_ERROR; } if (ctx->start >= ctx->end) { ngx_http_set_ctx(r, NULL, ngx_http_slice_filter_module); ngx_http_send_special(r, NGX_HTTP_LAST); return rc; } if (r->buffered) { return rc; } if (ngx_http_subrequest(r, &r->uri, &r->args, &ctx->sr, NULL, NGX_HTTP_SUBREQUEST_CLONE) != NGX_OK) { return NGX_ERROR; } ngx_http_set_ctx(ctx->sr, ctx, ngx_http_slice_filter_module); slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module); ctx->range.len = ngx_sprintf(ctx->range.data, "bytes=%O-%O", ctx->start, ctx->start + (off_t) slcf->size - 1) - ctx->range.data; ctx->active = 0; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http slice subrequest: \"%V\"", &ctx->range); return rc; } static ngx_int_t ngx_http_slice_parse_content_range(ngx_http_request_t *r, ngx_http_slice_content_range_t *cr) { off_t start, end, complete_length, cutoff, cutlim; u_char *p; ngx_table_elt_t *h; h = r->headers_out.content_range; if (h == NULL || h->value.len < 7 || ngx_strncmp(h->value.data, "bytes ", 6) != 0) { return NGX_ERROR; } p = h->value.data + 6; cutoff = NGX_MAX_OFF_T_VALUE / 10; cutlim = NGX_MAX_OFF_T_VALUE % 10; start = 0; end = 0; complete_length = 0; while (*p == ' ') { p++; } if (*p < '0' || *p > '9') { return NGX_ERROR; } while (*p >= '0' && *p <= '9') { if (start >= cutoff && (start > cutoff || *p - '0' > cutlim)) { return NGX_ERROR; } start = start * 10 + (*p++ - '0'); } while (*p == ' ') { p++; } if (*p++ != '-') { return NGX_ERROR; } while (*p == ' ') { p++; } if (*p < '0' || *p > '9') { return NGX_ERROR; } while (*p >= '0' && *p <= '9') { if (end >= cutoff && (end > cutoff || *p - '0' > cutlim)) { return NGX_ERROR; } end = end * 10 + (*p++ - '0'); } end++; while (*p == ' ') { p++; } if (*p++ != '/') { return NGX_ERROR; } while (*p == ' ') { p++; } if (*p != '*') { if (*p < '0' || *p > '9') { return NGX_ERROR; } while (*p >= '0' && *p <= '9') { if (complete_length >= cutoff && (complete_length > cutoff || *p - '0' > cutlim)) { return NGX_ERROR; } complete_length = complete_length * 10 + (*p++ - '0'); } } else { complete_length = -1; p++; } while (*p == ' ') { p++; } if (*p != '\0') { return NGX_ERROR; } cr->start = start; cr->end = end; cr->complete_length = complete_length; return NGX_OK; } static ngx_int_t ngx_http_slice_range_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; ngx_http_slice_ctx_t *ctx; ngx_http_slice_loc_conf_t *slcf; ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module); if (ctx == NULL) { if (r != r->main || r->headers_out.status) { v->not_found = 1; return NGX_OK; } slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module); if (slcf->size == 0) { v->not_found = 1; return NGX_OK; } ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_slice_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_slice_filter_module); p = ngx_pnalloc(r->pool, sizeof("bytes=-") - 1 + 2 * NGX_OFF_T_LEN); if (p == NULL) { return NGX_ERROR; } ctx->start = slcf->size * (ngx_http_slice_get_start(r) / slcf->size); ctx->range.data = p; ctx->range.len = ngx_sprintf(p, "bytes=%O-%O", ctx->start, ctx->start + (off_t) slcf->size - 1) - p; } v->data = ctx->range.data; v->valid = 1; v->not_found = 0; v->no_cacheable = 1; v->len = ctx->range.len; return NGX_OK; } static off_t ngx_http_slice_get_start(ngx_http_request_t *r) { off_t start, cutoff, cutlim; u_char *p; ngx_table_elt_t *h; if (r->headers_in.if_range) { return 0; } h = r->headers_in.range; if (h == NULL || h->value.len < 7 || ngx_strncasecmp(h->value.data, (u_char *) "bytes=", 6) != 0) { return 0; } p = h->value.data + 6; if (ngx_strchr(p, ',')) { return 0; } while (*p == ' ') { p++; } if (*p == '-') { return 0; } cutoff = NGX_MAX_OFF_T_VALUE / 10; cutlim = NGX_MAX_OFF_T_VALUE % 10; start = 0; while (*p >= '0' && *p <= '9') { if (start >= cutoff && (start > cutoff || *p - '0' > cutlim)) { return 0; } start = start * 10 + (*p++ - '0'); } return start; } static void * ngx_http_slice_create_loc_conf(ngx_conf_t *cf) { ngx_http_slice_loc_conf_t *slcf; slcf = ngx_palloc(cf->pool, sizeof(ngx_http_slice_loc_conf_t)); if (slcf == NULL) { return NULL; } slcf->size = NGX_CONF_UNSET_SIZE; return slcf; } static char * ngx_http_slice_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_slice_loc_conf_t *prev = parent; ngx_http_slice_loc_conf_t *conf = child; ngx_conf_merge_size_value(conf->size, prev->size, 0); return NGX_CONF_OK; } static ngx_int_t ngx_http_slice_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var; var = ngx_http_add_variable(cf, &ngx_http_slice_range_name, 0); if (var == NULL) { return NGX_ERROR; } var->get_handler = ngx_http_slice_range_variable; return NGX_OK; } static ngx_int_t ngx_http_slice_init(ngx_conf_t *cf) { ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_slice_header_filter; ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_slice_body_filter; return NGX_OK; } nginx-1.24.0/src/http/modules/ngx_http_split_clients_module.c000644 001751 001751 00000014727 14415135676 025654 0ustar00mdouninmdounin000000 000000 /* * 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; } nginx-1.24.0/src/http/modules/ngx_http_ssi_filter_module.c000644 001751 001751 00000234776 14415135676 025153 0ustar00mdouninmdounin000000 000000 /* * 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[] = "