nginx-1.4.6/000755 001751 001751 00000000000 12305336451 014035 5ustar00mdouninmdounin000000 000000 nginx-1.4.6/auto/000755 001751 001751 00000000000 12305336446 015011 5ustar00mdouninmdounin000000 000000 nginx-1.4.6/conf/000755 001751 001751 00000000000 12305336445 014765 5ustar00mdouninmdounin000000 000000 nginx-1.4.6/contrib/000755 001751 001751 00000000000 12305336445 015500 5ustar00mdouninmdounin000000 000000 nginx-1.4.6/src/000755 001751 001751 00000000000 12305336446 014630 5ustar00mdouninmdounin000000 000000 nginx-1.4.6/configure000755 001751 001751 00000004501 12305336445 015747 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 $NGX_OBJS echo > $NGX_AUTO_HEADERS_H echo > $NGX_AUTOCONF_ERR echo "#define NGX_CONFIGURE \"$NGX_CONFIGURE\"" > $NGX_AUTO_CONFIG_H if [ $NGX_DEBUG = YES ]; then have=NGX_DEBUG . auto/have fi if test -z "$NGX_PLATFORM"; then echo "checking for OS" NGX_SYSTEM=`uname -s 2>/dev/null` NGX_RELEASE=`uname -r 2>/dev/null` NGX_MACHINE=`uname -m 2>/dev/null` echo " + $NGX_SYSTEM $NGX_RELEASE $NGX_MACHINE" NGX_PLATFORM="$NGX_SYSTEM:$NGX_RELEASE:$NGX_MACHINE"; case "$NGX_SYSTEM" in MINGW32_*) NGX_PLATFORM=win32 ;; esac else echo "building for $NGX_PLATFORM" NGX_SYSTEM=$NGX_PLATFORM fi . auto/cc/conf if [ "$NGX_PLATFORM" != win32 ]; then . auto/headers fi . auto/os/conf if [ "$NGX_PLATFORM" != win32 ]; then . auto/unix fi . auto/modules . auto/lib/conf case ".$NGX_PREFIX" in .) NGX_PREFIX=${NGX_PREFIX:-/usr/local/nginx} have=NGX_PREFIX value="\"$NGX_PREFIX/\"" . auto/define ;; .!) NGX_PREFIX= ;; *) have=NGX_PREFIX value="\"$NGX_PREFIX/\"" . auto/define ;; esac if [ ".$NGX_CONF_PREFIX" != "." ]; then have=NGX_CONF_PREFIX value="\"$NGX_CONF_PREFIX/\"" . auto/define fi have=NGX_SBIN_PATH value="\"$NGX_SBIN_PATH\"" . auto/define have=NGX_CONF_PATH value="\"$NGX_CONF_PATH\"" . auto/define have=NGX_PID_PATH value="\"$NGX_PID_PATH\"" . auto/define have=NGX_LOCK_PATH value="\"$NGX_LOCK_PATH\"" . auto/define have=NGX_ERROR_LOG_PATH value="\"$NGX_ERROR_LOG_PATH\"" . auto/define have=NGX_HTTP_LOG_PATH value="\"$NGX_HTTP_LOG_PATH\"" . auto/define have=NGX_HTTP_CLIENT_TEMP_PATH value="\"$NGX_HTTP_CLIENT_TEMP_PATH\"" . auto/define have=NGX_HTTP_PROXY_TEMP_PATH value="\"$NGX_HTTP_PROXY_TEMP_PATH\"" . auto/define have=NGX_HTTP_FASTCGI_TEMP_PATH value="\"$NGX_HTTP_FASTCGI_TEMP_PATH\"" . auto/define have=NGX_HTTP_UWSGI_TEMP_PATH value="\"$NGX_HTTP_UWSGI_TEMP_PATH\"" . auto/define have=NGX_HTTP_SCGI_TEMP_PATH value="\"$NGX_HTTP_SCGI_TEMP_PATH\"" . auto/define . auto/make . auto/lib/make . auto/install # STUB . auto/stubs have=NGX_USER value="\"$NGX_USER\"" . auto/define have=NGX_GROUP value="\"$NGX_GROUP\"" . auto/define . auto/summary nginx-1.4.6/LICENSE000644 001751 001751 00000002565 12305336445 015055 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) 2002-2014 Igor Sysoev * Copyright (C) 2011-2014 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.4.6/README000644 001751 001751 00000000061 12305336445 014715 0ustar00mdouninmdounin000000 000000 Documentation is available at http://nginx.org nginx-1.4.6/html/000755 001751 001751 00000000000 12305336446 015005 5ustar00mdouninmdounin000000 000000 nginx-1.4.6/man/000755 001751 001751 00000000000 12305336446 014614 5ustar00mdouninmdounin000000 000000 nginx-1.4.6/CHANGES.ru000644 001751 001751 00001234435 12305336450 015470 0ustar00mdouninmdounin000000 000000 Изменения в nginx 1.4.6 04.03.2014 *) Исправление: директива client_max_body_size могла не работать при чтении тела запроса с использованием chunked transfer encoding; ошибка появилась в 1.3.9. Спасибо Lucas Molas. *) Исправление: при проксировании WebSocket-соединений в рабочем процессе мог произойти segmentation fault. Изменения в nginx 1.4.5 11.02.2014 *) Исправление: переменная $ssl_session_id содержала всю сессию в сериализованном виде вместо её идентификатора. Спасибо Ivan Ristić. *) Исправление: клиентские соединения могли сразу закрываться, если использовался отложенный accept; ошибка появилась в 1.3.15. *) Исправление: при проксировании в логах могли появляться сообщения "zero size buf in output"; ошибка появилась в 1.3.9. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовался модуль ngx_http_spdy_module. *) Исправление: при использовании методов обработки соединений select, poll и /dev/poll проксируемые WebSocket-соединения могли зависать сразу после открытия. *) Исправление: при чтении тела запроса с использованием chunked transfer encoding по SSL-соединению мог произойти таймаут. *) Исправление: утечки памяти в nginx/Windows. Изменения в nginx 1.4.4 19.11.2013 *) Безопасность: символ, следующий за незакодированным пробелом в строке запроса, обрабатывался неправильно (CVE-2013-4547); ошибка появилась в 0.8.41. Спасибо Ivan Fratric из Google Security Team. Изменения в nginx 1.4.3 08.10.2013 *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовался модуль ngx_http_spdy_module и директива client_body_in_file_only. *) Исправление: на старте или во время переконфигурации мог произойти segmentation fault, если использовалась директива try_files с пустым параметром. *) Исправление: переменная $request_time не работала в nginx/Windows. *) Исправление: в модуле ngx_http_auth_basic_module при использовании метода шифрования паролей "$apr1$". Спасибо Markus Linnala. *) Исправление: в модуле ngx_http_autoindex_module. *) Исправление: в почтовом прокси-сервере. Изменения в nginx 1.4.2 17.07.2013 *) Исправление: метод $r->header_in() встроенного перла не возвращал значения строк "Cookie" и "X-Forwarded-For" из заголовка запроса; ошибка появилась в 1.3.14. *) Исправление: nginx не собирался с модулем ngx_mail_ssl_module, но без модуля ngx_http_ssl_module; ошибка появилась в 1.3.14. *) Исправление: в директиве proxy_set_body. Спасибо Lanshun Zhou. *) Исправление: параметр fail_timeout директивы server в блоке upstream мог не работать, если использовался параметр max_fails; ошибка появилась в 1.3.0. *) Исправление: в рабочем процессе мог произойти segmentation fault, если использовалась директива ssl_stapling. Спасибо Piotr Sikora. *) Исправление: nginx/Windows мог перестать принимать соединения, если использовалось несколько рабочих процессов. Изменения в nginx 1.4.1 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 cтарее 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. Спасибо Максиму Дунину. *) Исправление: при проксировании SMPT 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_capablities и 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 LOIGN 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_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_494_page[] = "" CRLF "400 Request Header Or Cookie Too Large" CRLF "" CRLF "

400 Bad Request

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

400 Bad Request

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

400 Bad Request

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

400 Bad Request

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

500 Internal Server Error

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

501 Not Implemented

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

502 Bad Gateway

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

503 Service Temporarily Unavailable

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

504 Gateway Time-out

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

507 Insufficient Storage

" CRLF ; static ngx_str_t ngx_http_error_pages[] = { ngx_null_string, /* 201, 204 */ #define NGX_HTTP_LAST_2XX 202 #define NGX_HTTP_OFF_3XX (NGX_HTTP_LAST_2XX - 201) /* ngx_null_string, */ /* 300 */ ngx_string(ngx_http_error_301_page), ngx_string(ngx_http_error_302_page), ngx_string(ngx_http_error_303_page), ngx_null_string, /* 304 */ ngx_null_string, /* 305 */ ngx_null_string, /* 306 */ ngx_string(ngx_http_error_307_page), #define NGX_HTTP_LAST_3XX 308 #define NGX_HTTP_OFF_4XX (NGX_HTTP_LAST_3XX - 301 + NGX_HTTP_OFF_3XX) ngx_string(ngx_http_error_400_page), ngx_string(ngx_http_error_401_page), ngx_string(ngx_http_error_402_page), ngx_string(ngx_http_error_403_page), ngx_string(ngx_http_error_404_page), ngx_string(ngx_http_error_405_page), ngx_string(ngx_http_error_406_page), ngx_null_string, /* 407 */ ngx_string(ngx_http_error_408_page), ngx_string(ngx_http_error_409_page), ngx_string(ngx_http_error_410_page), ngx_string(ngx_http_error_411_page), ngx_string(ngx_http_error_412_page), ngx_string(ngx_http_error_413_page), ngx_string(ngx_http_error_414_page), ngx_string(ngx_http_error_415_page), ngx_string(ngx_http_error_416_page), #define NGX_HTTP_LAST_4XX 417 #define NGX_HTTP_OFF_5XX (NGX_HTTP_LAST_4XX - 400 + NGX_HTTP_OFF_4XX) ngx_string(ngx_http_error_494_page), /* 494, request header too large */ ngx_string(ngx_http_error_495_page), /* 495, https certificate error */ ngx_string(ngx_http_error_496_page), /* 496, https no certificate */ ngx_string(ngx_http_error_497_page), /* 497, http to https */ ngx_string(ngx_http_error_404_page), /* 498, canceled */ ngx_null_string, /* 499, client has closed connection */ ngx_string(ngx_http_error_500_page), ngx_string(ngx_http_error_501_page), ngx_string(ngx_http_error_502_page), ngx_string(ngx_http_error_503_page), ngx_string(ngx_http_error_504_page), ngx_null_string, /* 505 */ ngx_null_string, /* 506 */ ngx_string(ngx_http_error_507_page) #define NGX_HTTP_LAST_5XX 508 }; static ngx_str_t ngx_http_get_name = { 3, (u_char *) "GET " }; 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: %d, \"%V?%V\"", error, &r->uri, &r->args); r->err_status = error; if (r->keepalive) { switch (error) { case NGX_HTTP_BAD_REQUEST: case NGX_HTTP_REQUEST_ENTITY_TOO_LARGE: case NGX_HTTP_REQUEST_URI_TOO_LARGE: case NGX_HTTP_TO_HTTPS: case NGX_HTTPS_CERT_ERROR: case NGX_HTTPS_NO_CERT: case NGX_HTTP_INTERNAL_SERVER_ERROR: case NGX_HTTP_NOT_IMPLEMENTED: r->keepalive = 0; } } if (r->lingering_close) { switch (error) { case NGX_HTTP_BAD_REQUEST: case NGX_HTTP_TO_HTTPS: case NGX_HTTPS_CERT_ERROR: case NGX_HTTPS_NO_CERT: r->lingering_close = 0; } } r->headers_out.content_type.len = 0; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (!r->error_page && clcf->error_pages && r->uri_changes != 0) { if (clcf->recursive_error_pages == 0) { r->error_page = 1; } err_page = clcf->error_pages->elts; for (i = 0; i < clcf->error_pages->nelts; i++) { if (err_page[i].status == error) { return ngx_http_send_error_page(r, &err_page[i]); } } } r->expect_tested = 1; if (ngx_http_discard_request_body(r) != NGX_OK) { r->keepalive = 0; } if (clcf->msie_refresh && r->headers_in.msie && (error == NGX_HTTP_MOVED_PERMANENTLY || error == NGX_HTTP_MOVED_TEMPORARILY)) { return ngx_http_send_refresh(r); } if (error == NGX_HTTP_CREATED) { /* 201 */ err = 0; } else if (error == NGX_HTTP_NO_CONTENT) { /* 204 */ err = 0; } else if (error >= NGX_HTTP_MOVED_PERMANENTLY && error < NGX_HTTP_LAST_3XX) { /* 3XX */ err = error - NGX_HTTP_MOVED_PERMANENTLY + NGX_HTTP_OFF_3XX; } else if (error >= NGX_HTTP_BAD_REQUEST && error < NGX_HTTP_LAST_4XX) { /* 4XX */ err = error - NGX_HTTP_BAD_REQUEST + NGX_HTTP_OFF_4XX; } else if (error >= NGX_HTTP_NGINX_CODES && error < NGX_HTTP_LAST_5XX) { /* 49X, 5XX */ err = error - NGX_HTTP_NGINX_CODES + NGX_HTTP_OFF_5XX; switch (error) { case NGX_HTTP_TO_HTTPS: case NGX_HTTPS_CERT_ERROR: case NGX_HTTPS_NO_CERT: case NGX_HTTP_REQUEST_HEADER_TOO_LARGE: r->err_status = NGX_HTTP_BAD_REQUEST; break; } } else { /* unknown code, zero body */ err = 0; } return ngx_http_send_special_response(r, clcf, err); } ngx_int_t ngx_http_filter_finalize_request(ngx_http_request_t *r, ngx_module_t *m, ngx_int_t error) { void *ctx; ngx_int_t rc; ngx_http_clean_header(r); ctx = NULL; if (m) { ctx = r->ctx[m->ctx_index]; } /* clear the modules contexts */ ngx_memzero(r->ctx, sizeof(void *) * ngx_http_max_module); if (m) { r->ctx[m->ctx_index] = ctx; } r->filter_finalize = 1; rc = ngx_http_special_response_handler(r, error); /* NGX_ERROR resets any pending data */ switch (rc) { case NGX_OK: case NGX_DONE: return NGX_ERROR; default: return rc; } } void ngx_http_clean_header(ngx_http_request_t *r) { ngx_memzero(&r->headers_out.status, sizeof(ngx_http_headers_out_t) - offsetof(ngx_http_headers_out_t, status)); r->headers_out.headers.part.nelts = 0; r->headers_out.headers.part.next = NULL; r->headers_out.headers.last = &r->headers_out.headers.part; r->headers_out.content_length_n = -1; r->headers_out.last_modified_time = -1; } static ngx_int_t ngx_http_send_error_page(ngx_http_request_t *r, ngx_http_err_page_t *err_page) { ngx_int_t overwrite; ngx_str_t uri, args; ngx_table_elt_t *location; ngx_http_core_loc_conf_t *clcf; overwrite = err_page->overwrite; if (overwrite && overwrite != NGX_HTTP_OK) { r->expect_tested = 1; } if (overwrite >= 0) { r->err_status = overwrite; } if (ngx_http_complex_value(r, &err_page->value, &uri) != NGX_OK) { return NGX_ERROR; } if (uri.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_get_name; } return ngx_http_internal_redirect(r, &uri, &args); } if (uri.data[0] == '@') { return ngx_http_named_location(r, &uri); } location = ngx_list_push(&r->headers_out.headers); if (location == NULL) { return NGX_ERROR; } if (overwrite != NGX_HTTP_MOVED_PERMANENTLY && overwrite != NGX_HTTP_MOVED_TEMPORARILY && overwrite != NGX_HTTP_SEE_OTHER && overwrite != NGX_HTTP_TEMPORARY_REDIRECT) { r->err_status = NGX_HTTP_MOVED_TEMPORARILY; } location->hash = 1; ngx_str_set(&location->key, "Location"); location->value = uri; ngx_http_clear_location(r); r->headers_out.location = location; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (clcf->msie_refresh && r->headers_in.msie) { return ngx_http_send_refresh(r); } return ngx_http_send_special_response(r, clcf, r->err_status - NGX_HTTP_MOVED_PERMANENTLY + NGX_HTTP_OFF_3XX); } static ngx_int_t ngx_http_send_special_response(ngx_http_request_t *r, ngx_http_core_loc_conf_t *clcf, ngx_uint_t err) { u_char *tail; size_t len; ngx_int_t rc; ngx_buf_t *b; ngx_uint_t msie_padding; ngx_chain_t out[3]; if (clcf->server_tokens) { len = sizeof(ngx_http_error_full_tail) - 1; tail = ngx_http_error_full_tail; } else { len = sizeof(ngx_http_error_tail) - 1; tail = ngx_http_error_tail; } msie_padding = 0; if (ngx_http_error_pages[err].len) { r->headers_out.content_length_n = ngx_http_error_pages[err].len + len; if (clcf->msie_padding && (r->headers_in.msie || r->headers_in.chrome) && r->http_version >= NGX_HTTP_VERSION_10 && err >= NGX_HTTP_OFF_4XX) { r->headers_out.content_length_n += sizeof(ngx_http_msie_padding) - 1; msie_padding = 1; } r->headers_out.content_type_len = sizeof("text/html") - 1; ngx_str_set(&r->headers_out.content_type, "text/html"); r->headers_out.content_type_lowcase = NULL; } else { r->headers_out.content_length_n = 0; } if (r->headers_out.content_length) { r->headers_out.content_length->hash = 0; r->headers_out.content_length = NULL; } ngx_http_clear_accept_ranges(r); ngx_http_clear_last_modified(r); ngx_http_clear_etag(r); rc = ngx_http_send_header(r); if (rc == NGX_ERROR || r->header_only) { return rc; } if (ngx_http_error_pages[err].len == 0) { return ngx_http_send_special(r, NGX_HTTP_LAST); } b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } b->memory = 1; b->pos = ngx_http_error_pages[err].data; b->last = ngx_http_error_pages[err].data + ngx_http_error_pages[err].len; out[0].buf = b; out[0].next = &out[1]; b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } b->memory = 1; b->pos = tail; b->last = tail + len; out[1].buf = b; out[1].next = NULL; if (msie_padding) { b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } b->memory = 1; b->pos = ngx_http_msie_padding; b->last = ngx_http_msie_padding + sizeof(ngx_http_msie_padding) - 1; out[1].next = &out[2]; out[2].buf = b; out[2].next = NULL; } if (r == r->main) { b->last_buf = 1; } b->last_in_chain = 1; return ngx_http_output_filter(r, &out[0]); } static ngx_int_t ngx_http_send_refresh(ngx_http_request_t *r) { u_char *p, *location; size_t len, size; uintptr_t escape; ngx_int_t rc; ngx_buf_t *b; ngx_chain_t out; len = r->headers_out.location->value.len; location = r->headers_out.location->value.data; escape = 2 * ngx_escape_uri(NULL, location, len, NGX_ESCAPE_REFRESH); size = sizeof(ngx_http_msie_refresh_head) - 1 + escape + len + sizeof(ngx_http_msie_refresh_tail) - 1; r->err_status = NGX_HTTP_OK; r->headers_out.content_type_len = sizeof("text/html") - 1; ngx_str_set(&r->headers_out.content_type, "text/html"); r->headers_out.content_type_lowcase = NULL; r->headers_out.location->hash = 0; r->headers_out.location = NULL; r->headers_out.content_length_n = size; if (r->headers_out.content_length) { r->headers_out.content_length->hash = 0; r->headers_out.content_length = NULL; } ngx_http_clear_accept_ranges(r); ngx_http_clear_last_modified(r); ngx_http_clear_etag(r); rc = ngx_http_send_header(r); if (rc == NGX_ERROR || r->header_only) { return rc; } b = ngx_create_temp_buf(r->pool, size); if (b == NULL) { return NGX_ERROR; } p = ngx_cpymem(b->pos, ngx_http_msie_refresh_head, sizeof(ngx_http_msie_refresh_head) - 1); if (escape == 0) { p = ngx_cpymem(p, location, len); } else { p = (u_char *) ngx_escape_uri(p, location, len, NGX_ESCAPE_REFRESH); } b->last = ngx_cpymem(p, ngx_http_msie_refresh_tail, sizeof(ngx_http_msie_refresh_tail) - 1); b->last_buf = 1; b->last_in_chain = 1; out.buf = b; out.next = NULL; return ngx_http_output_filter(r, &out); } nginx-1.4.6/src/http/ngx_http_upstream.c000644 001751 001751 00000400112 12305336446 021524 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_send(ngx_http_request_t *r, ngx_http_upstream_t *u); static ngx_int_t ngx_http_upstream_cache_status(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); #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); static void ngx_http_upstream_send_request_handler(ngx_http_request_t *r, ngx_http_upstream_t *u); static void ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u); static ngx_int_t ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u); static ngx_int_t ngx_http_upstream_intercept_errors(ngx_http_request_t *r, ngx_http_upstream_t *u); static ngx_int_t ngx_http_upstream_test_connect(ngx_connection_t *c); static ngx_int_t ngx_http_upstream_process_headers(ngx_http_request_t *r, ngx_http_upstream_t *u); static void ngx_http_upstream_process_body_in_memory(ngx_http_request_t *r, ngx_http_upstream_t *u); static void ngx_http_upstream_send_response(ngx_http_request_t *r, ngx_http_upstream_t *u); static void ngx_http_upstream_upgrade(ngx_http_request_t *r, ngx_http_upstream_t *u); static void ngx_http_upstream_upgraded_read_downstream(ngx_http_request_t *r); static void ngx_http_upstream_upgraded_write_downstream(ngx_http_request_t *r); static void ngx_http_upstream_upgraded_read_upstream(ngx_http_request_t *r, ngx_http_upstream_t *u); static void ngx_http_upstream_upgraded_write_upstream(ngx_http_request_t *r, ngx_http_upstream_t *u); static void ngx_http_upstream_process_upgraded(ngx_http_request_t *r, ngx_uint_t from_upstream, ngx_uint_t do_write); static void ngx_http_upstream_process_non_buffered_downstream(ngx_http_request_t *r); static void ngx_http_upstream_process_non_buffered_upstream(ngx_http_request_t *r, ngx_http_upstream_t *u); static void ngx_http_upstream_process_non_buffered_request(ngx_http_request_t *r, ngx_uint_t do_write); static ngx_int_t ngx_http_upstream_non_buffered_filter_init(void *data); static ngx_int_t ngx_http_upstream_non_buffered_filter(void *data, ssize_t bytes); 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); static void ngx_http_upstream_store(ngx_http_request_t *r, ngx_http_upstream_t *u); static void ngx_http_upstream_dummy_handler(ngx_http_request_t *r, ngx_http_upstream_t *u); static void ngx_http_upstream_next(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_uint_t ft_type); static void ngx_http_upstream_cleanup(void *data); static void ngx_http_upstream_finalize_request(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_int_t rc); static ngx_int_t ngx_http_upstream_process_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_upstream_process_content_length(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_upstream_process_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_copy_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_upstream_copy_multi_header_lines(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_upstream_copy_content_type(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_upstream_copy_last_modified(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_upstream_rewrite_location(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_upstream_rewrite_refresh(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_upstream_rewrite_set_cookie(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_upstream_copy_allow_ranges(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); #if (NGX_HTTP_GZIP) static ngx_int_t ngx_http_upstream_copy_content_encoding(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); #endif static ngx_int_t ngx_http_upstream_add_variables(ngx_conf_t *cf); static ngx_int_t ngx_http_upstream_addr_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_upstream_status_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_upstream_response_time_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_upstream_response_length_variable( ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static char *ngx_http_upstream(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy); static char *ngx_http_upstream_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_addr_t *ngx_http_upstream_get_local(ngx_http_request_t *r, ngx_http_upstream_local_t *local); static void *ngx_http_upstream_create_main_conf(ngx_conf_t *cf); static char *ngx_http_upstream_init_main_conf(ngx_conf_t *cf, void *conf); #if (NGX_HTTP_SSL) static void ngx_http_upstream_ssl_init_connection(ngx_http_request_t *, ngx_http_upstream_t *u, ngx_connection_t *c); static void ngx_http_upstream_ssl_handshake(ngx_connection_t *c); #endif ngx_http_upstream_header_t ngx_http_upstream_headers_in[] = { { ngx_string("Status"), ngx_http_upstream_process_header_line, offsetof(ngx_http_upstream_headers_in_t, status), ngx_http_upstream_copy_header_line, 0, 0 }, { ngx_string("Content-Type"), ngx_http_upstream_process_header_line, offsetof(ngx_http_upstream_headers_in_t, content_type), ngx_http_upstream_copy_content_type, 0, 1 }, { ngx_string("Content-Length"), ngx_http_upstream_process_content_length, offsetof(ngx_http_upstream_headers_in_t, content_length), 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_header_line, offsetof(ngx_http_upstream_headers_in_t, last_modified), ngx_http_upstream_copy_last_modified, 0, 0 }, { ngx_string("ETag"), ngx_http_upstream_process_header_line, offsetof(ngx_http_upstream_headers_in_t, etag), ngx_http_upstream_copy_header_line, offsetof(ngx_http_headers_out_t, etag), 0 }, { ngx_string("Server"), ngx_http_upstream_process_header_line, offsetof(ngx_http_upstream_headers_in_t, server), ngx_http_upstream_copy_header_line, offsetof(ngx_http_headers_out_t, server), 0 }, { ngx_string("WWW-Authenticate"), ngx_http_upstream_process_header_line, offsetof(ngx_http_upstream_headers_in_t, www_authenticate), ngx_http_upstream_copy_header_line, 0, 0 }, { ngx_string("Location"), ngx_http_upstream_process_header_line, offsetof(ngx_http_upstream_headers_in_t, location), ngx_http_upstream_rewrite_location, 0, 0 }, { ngx_string("Refresh"), ngx_http_upstream_ignore_header_line, 0, ngx_http_upstream_rewrite_refresh, 0, 0 }, { ngx_string("Set-Cookie"), ngx_http_upstream_process_set_cookie, 0, ngx_http_upstream_rewrite_set_cookie, 0, 1 }, { ngx_string("Content-Disposition"), ngx_http_upstream_ignore_header_line, 0, ngx_http_upstream_copy_header_line, 0, 1 }, { ngx_string("Cache-Control"), ngx_http_upstream_process_cache_control, 0, ngx_http_upstream_copy_multi_header_lines, offsetof(ngx_http_headers_out_t, cache_control), 1 }, { ngx_string("Expires"), ngx_http_upstream_process_expires, 0, ngx_http_upstream_copy_header_line, offsetof(ngx_http_headers_out_t, expires), 1 }, { ngx_string("Accept-Ranges"), ngx_http_upstream_process_header_line, offsetof(ngx_http_upstream_headers_in_t, accept_ranges), ngx_http_upstream_copy_allow_ranges, offsetof(ngx_http_headers_out_t, accept_ranges), 1 }, { ngx_string("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("X-Powered-By"), ngx_http_upstream_ignore_header_line, 0, ngx_http_upstream_copy_header_line, 0, 0 }, { ngx_string("X-Accel-Expires"), ngx_http_upstream_process_accel_expires, 0, ngx_http_upstream_copy_header_line, 0, 0 }, { ngx_string("X-Accel-Redirect"), ngx_http_upstream_process_header_line, offsetof(ngx_http_upstream_headers_in_t, x_accel_redirect), ngx_http_upstream_copy_header_line, 0, 0 }, { ngx_string("X-Accel-Limit-Rate"), ngx_http_upstream_process_limit_rate, 0, ngx_http_upstream_copy_header_line, 0, 0 }, { ngx_string("X-Accel-Buffering"), ngx_http_upstream_process_buffering, 0, ngx_http_upstream_copy_header_line, 0, 0 }, { ngx_string("X-Accel-Charset"), ngx_http_upstream_process_charset, 0, ngx_http_upstream_copy_header_line, 0, 0 }, { ngx_string("Transfer-Encoding"), ngx_http_upstream_process_transfer_encoding, 0, ngx_http_upstream_ignore_header_line, 0, 0 }, #if (NGX_HTTP_GZIP) { ngx_string("Content-Encoding"), ngx_http_upstream_process_header_line, offsetof(ngx_http_upstream_headers_in_t, content_encoding), ngx_http_upstream_copy_content_encoding, 0, 0 }, #endif { ngx_null_string, NULL, 0, NULL, 0, 0 } }; static ngx_command_t ngx_http_upstream_commands[] = { { ngx_string("upstream"), NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1, ngx_http_upstream, 0, 0, NULL }, { ngx_string("server"), NGX_HTTP_UPS_CONF|NGX_CONF_1MORE, ngx_http_upstream_server, NGX_HTTP_SRV_CONF_OFFSET, 0, NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_upstream_module_ctx = { ngx_http_upstream_add_variables, /* preconfiguration */ NULL, /* postconfiguration */ ngx_http_upstream_create_main_conf, /* create main configuration */ ngx_http_upstream_init_main_conf, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; ngx_module_t ngx_http_upstream_module = { NGX_MODULE_V1, &ngx_http_upstream_module_ctx, /* module context */ ngx_http_upstream_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_variable_t ngx_http_upstream_vars[] = { { ngx_string("upstream_addr"), NULL, ngx_http_upstream_addr_variable, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("upstream_status"), NULL, ngx_http_upstream_status_variable, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("upstream_response_time"), NULL, ngx_http_upstream_response_time_variable, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("upstream_response_length"), NULL, ngx_http_upstream_response_length_variable, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, #if (NGX_HTTP_CACHE) { ngx_string("upstream_cache_status"), NULL, ngx_http_upstream_cache_status, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, #endif { ngx_null_string, NULL, NULL, 0, 0, 0 } }; static ngx_http_upstream_next_t ngx_http_upstream_next_errors[] = { { 500, NGX_HTTP_UPSTREAM_FT_HTTP_500 }, { 502, NGX_HTTP_UPSTREAM_FT_HTTP_502 }, { 503, NGX_HTTP_UPSTREAM_FT_HTTP_503 }, { 504, NGX_HTTP_UPSTREAM_FT_HTTP_504 }, { 404, NGX_HTTP_UPSTREAM_FT_HTTP_404 }, { 0, 0 } }; ngx_conf_bitmask_t ngx_http_upstream_cache_method_mask[] = { { ngx_string("GET"), NGX_HTTP_GET}, { ngx_string("HEAD"), NGX_HTTP_HEAD }, { ngx_string("POST"), NGX_HTTP_POST }, { ngx_null_string, 0 } }; ngx_conf_bitmask_t ngx_http_upstream_ignore_headers_masks[] = { { ngx_string("X-Accel-Redirect"), NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT }, { ngx_string("X-Accel-Expires"), NGX_HTTP_UPSTREAM_IGN_XA_EXPIRES }, { ngx_string("X-Accel-Limit-Rate"), NGX_HTTP_UPSTREAM_IGN_XA_LIMIT_RATE }, { ngx_string("X-Accel-Buffering"), NGX_HTTP_UPSTREAM_IGN_XA_BUFFERING }, { ngx_string("X-Accel-Charset"), NGX_HTTP_UPSTREAM_IGN_XA_CHARSET }, { ngx_string("Expires"), NGX_HTTP_UPSTREAM_IGN_EXPIRES }, { ngx_string("Cache-Control"), NGX_HTTP_UPSTREAM_IGN_CACHE_CONTROL }, { ngx_string("Set-Cookie"), NGX_HTTP_UPSTREAM_IGN_SET_COOKIE }, { ngx_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_THREADS) u->peer.lock = &r->connection->lock; #endif #if (NGX_HTTP_CACHE) r->cache = NULL; #endif u->headers_in.content_length_n = -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_SPDY) if (r->spdy_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_DONE) { return; } if (rc != NGX_DECLINED) { ngx_http_finalize_request(r, rc); return; } } #endif u->store = (u->conf->store || u->conf->store_lengths); if (!u->store && !r->post_action && !u->conf->ignore_client_abort) { r->read_event_handler = ngx_http_upstream_rd_check_broken_connection; r->write_event_handler = ngx_http_upstream_wr_check_broken_connection; } if (r->request_body) { u->request_bufs = r->request_body->bufs; } if (u->create_request(r) != NGX_OK) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } u->peer.local = ngx_http_upstream_get_local(r, u->conf->local); clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); u->output.alignment = clcf->directio_alignment; u->output.pool = r->pool; u->output.bufs.num = 1; u->output.bufs.size = clcf->client_body_buffer_size; 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 (u->resolved->sockaddr) { 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; } 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_memcmp(uscf->host.data, host->data, host->len) == 0) { goto found; } } if (u->resolved->port == 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no port in upstream \"%V\"", host); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } temp.name = *host; ctx = ngx_resolve_start(clcf->resolver, &temp); if (ctx == NULL) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (ctx == NGX_NO_RESOLVER) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no resolver defined to resolve %V", host); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY); return; } ctx->name = *host; ctx->type = NGX_RESOLVE_A; ctx->handler = ngx_http_upstream_resolve_handler; ctx->data = r; ctx->timeout = clcf->resolver_timeout; u->resolved->ctx = ctx; if (ngx_resolve_name(ctx) != NGX_OK) { u->resolved->ctx = NULL; ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } return; } found: if (uscf == NULL) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "no upstream configuration"); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (uscf->peer.init(r, uscf) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } 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; c = r->cache; if (c == NULL) { if (!(r->method & u->conf->cache_methods)) { return NGX_DECLINED; } if (r->method & NGX_HTTP_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 increased at least to %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; 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 = r->cache; c->min_uses = u->conf->cache_min_uses; c->body_start = u->conf->buffer_size; c->file_cache = u->conf->cache->data; c->lock = u->conf->cache_lock; c->lock_timeout = u->conf->cache_lock_timeout; u->cache_status = NGX_HTTP_CACHE_MISS; } rc = ngx_http_file_cache_open(r); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http upstream cache: %i", rc); switch (rc) { case NGX_HTTP_CACHE_UPDATING: if (u->conf->cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING) { u->cache_status = rc; rc = NGX_OK; } else { rc = NGX_HTTP_CACHE_STALE; } break; case NGX_OK: u->cache_status = NGX_HTTP_CACHE_HIT; } switch (rc) { case NGX_OK: rc = ngx_http_upstream_cache_send(r, u); if (rc != NGX_HTTP_UPSTREAM_INVALID_HEADER) { return rc; } break; case NGX_HTTP_CACHE_STALE: c->valid_sec = 0; u->buffer.start = NULL; u->cache_status = NGX_HTTP_CACHE_EXPIRED; break; case NGX_DECLINED: if ((size_t) (u->buffer.end - u->buffer.start) < u->conf->buffer_size) { u->buffer.start = NULL; } else { u->buffer.pos = u->buffer.start + c->header_start; u->buffer.last = u->buffer.pos; } break; case NGX_HTTP_CACHE_SCARCE: u->cacheable = 0; break; case NGX_AGAIN: return NGX_BUSY; case NGX_ERROR: return NGX_ERROR; default: /* cached NGX_HTTP_BAD_GATEWAY, NGX_HTTP_GATEWAY_TIME_OUT, etc. */ u->cache_status = NGX_HTTP_CACHE_HIT; return rc; } r->cached = 0; return NGX_DECLINED; } static ngx_int_t ngx_http_upstream_cache_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; if (ngx_list_init(&u->headers_in.headers, r->pool, 8, sizeof(ngx_table_elt_t)) != NGX_OK) { return NGX_ERROR; } rc = u->process_header(r); if (rc == NGX_OK) { if (ngx_http_upstream_process_headers(r, u) != NGX_OK) { return NGX_DONE; } return ngx_http_cache_send(r); } if (rc == NGX_ERROR) { return NGX_ERROR; } /* rc == NGX_HTTP_UPSTREAM_INVALID_HEADER */ /* TODO: delete file */ return rc; } #endif static void ngx_http_upstream_resolve_handler(ngx_resolver_ctx_t *ctx) { ngx_connection_t *c; ngx_http_request_t *r; ngx_http_upstream_t *u; ngx_http_upstream_resolved_t *ur; r = ctx->data; c = r->connection; u = r->upstream; ur = u->resolved; 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) { in_addr_t addr; ngx_uint_t i; for (i = 0; i < ctx->naddrs; i++) { addr = ntohl(ur->addrs[i]); ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "name was resolved to %ud.%ud.%ud.%ud", (addr >> 24) & 0xff, (addr >> 16) & 0xff, (addr >> 8) & 0xff, addr & 0xff); } } #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; ngx_http_upstream_connect(r, u); failed: ngx_http_run_posted_requests(c); } static void ngx_http_upstream_handler(ngx_event_t *ev) { ngx_connection_t *c; ngx_http_request_t *r; ngx_http_log_ctx_t *ctx; ngx_http_upstream_t *u; c = ev->data; r = c->data; u = r->upstream; c = r->connection; ctx = c->log->data; ctx->current_request = r; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream request: \"%V?%V\"", &r->uri, &r->args); if (ev->write) { u->write_event_handler(r, u); } else { u->read_event_handler(r, u); } ngx_http_run_posted_requests(c); } static void ngx_http_upstream_rd_check_broken_connection(ngx_http_request_t *r) { ngx_http_upstream_check_broken_connection(r, r->connection->read); } static void ngx_http_upstream_wr_check_broken_connection(ngx_http_request_t *r) { ngx_http_upstream_check_broken_connection(r, r->connection->write); } static void ngx_http_upstream_check_broken_connection(ngx_http_request_t *r, ngx_event_t *ev) { int n; char buf[1]; ngx_err_t err; ngx_int_t event; ngx_connection_t *c; ngx_http_upstream_t *u; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ev->log, 0, "http upstream check client, write event:%d, \"%V\"", ev->write, &r->uri); c = r->connection; u = r->upstream; if (c->error) { if ((ngx_event_flags & NGX_USE_LEVEL_EVENT) && ev->active) { event = ev->write ? NGX_WRITE_EVENT : NGX_READ_EVENT; if (ngx_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_SPDY) if (r->spdy_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 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_time_t *tp; ngx_connection_t *c; r->connection->log->action = "connecting to upstream"; if (u->state && u->state->response_sec) { tp = ngx_timeofday(); u->state->response_sec = tp->sec - u->state->response_sec; u->state->response_msec = tp->msec - u->state->response_msec; } 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)); tp = ngx_timeofday(); u->state->response_sec = tp->sec; u->state->response_msec = tp->msec; 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 */ c = u->peer.connection; c->data = r; c->write->handler = ngx_http_upstream_handler; c->read->handler = ngx_http_upstream_handler; u->write_event_handler = ngx_http_upstream_send_request_handler; u->read_event_handler = ngx_http_upstream_process_header; c->sendfile &= r->connection->sendfile; u->output.sendfile = c->sendfile; if (c->pool == NULL) { /* we need separate pool here to be able to cache SSL connections */ c->pool = ngx_create_pool(128, r->connection->log); if (c->pool == NULL) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } } c->log = r->connection->log; c->pool->log = c->log; c->read->log = c->log; c->write->log = c->log; /* init or reinit the ngx_output_chain() and ngx_chain_writer() contexts */ u->writer.out = NULL; u->writer.last = &u->writer.out; u->writer.connection = c; u->writer.limit = 0; if (u->request_sent) { if (ngx_http_upstream_reinit(r, u) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } } if (r->request_body && r->request_body->buf && r->request_body->temp_file && r == r->main) { /* * the r->request_body->buf can be reused for one request only, * the subrequests should allocate their own temporary bufs */ u->output.free = ngx_alloc_chain_link(r->pool); if (u->output.free == NULL) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } u->output.free->buf = r->request_body->buf; u->output.free->next = NULL; u->output.allocated = 1; r->request_body->buf->pos = r->request_body->buf->start; r->request_body->buf->last = r->request_body->buf->start; r->request_body->buf->tag = u->output.tag; } u->request_sent = 0; 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); } #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; if (ngx_ssl_create_connection(u->conf->ssl, c, NGX_SSL_BUFFER|NGX_SSL_CLIENT) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } c->sendfile = 0; u->output.sendfile = 0; if (u->conf->ssl_session_reuse) { if (u->peer.set_session(&u->peer, u->peer.data) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } } r->connection->log->action = "SSL handshaking to upstream"; rc = ngx_ssl_handshake(c); if (rc == NGX_AGAIN) { c->ssl->handler = ngx_http_upstream_ssl_handshake; return; } ngx_http_upstream_ssl_handshake(c); } static void ngx_http_upstream_ssl_handshake(ngx_connection_t *c) { ngx_http_request_t *r; ngx_http_upstream_t *u; r = c->data; u = r->upstream; if (c->ssl->handshaked) { if (u->conf->ssl_session_reuse) { u->peer.save_session(&u->peer, u->peer.data); } c->write->handler = ngx_http_upstream_handler; c->read->handler = ngx_http_upstream_handler; ngx_http_upstream_send_request(r, u); return; } ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); } #endif static ngx_int_t ngx_http_upstream_reinit(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_chain_t *cl; if (u->reinit_request(r) != NGX_OK) { return NGX_ERROR; } u->keepalive = 0; u->upgrade = 0; ngx_memzero(&u->headers_in, sizeof(ngx_http_upstream_headers_in_t)); u->headers_in.content_length_n = -1; if (ngx_list_init(&u->headers_in.headers, r->pool, 8, sizeof(ngx_table_elt_t)) != NGX_OK) { return NGX_ERROR; } /* reinit the request chain */ for (cl = u->request_bufs; cl; cl = cl->next) { cl->buf->pos = cl->buf->start; cl->buf->file_pos = 0; } /* 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_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->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_output_chain(&u->output, u->request_sent ? NULL : u->request_bufs); u->request_sent = 1; if (rc == NGX_ERROR) { ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); return; } if (c->write->timer_set) { ngx_del_timer(c->write); } if (rc == NGX_AGAIN) { ngx_add_timer(c->write, u->conf->send_timeout); if (ngx_handle_write_event(c->write, u->conf->send_lowat) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } return; } /* rc == NGX_OK */ if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) { if (ngx_tcp_push(c->fd) == NGX_ERROR) { ngx_log_error(NGX_LOG_CRIT, c->log, ngx_socket_errno, ngx_tcp_push_n " failed"); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } c->tcp_nopush = NGX_TCP_NOPUSH_UNSET; } ngx_add_timer(c->read, u->conf->read_timeout); #if 1 if (c->read->ready) { /* post aio operation */ /* * TODO comment * although we can post aio operation just in the end * of ngx_http_upstream_connect() CHECK IT !!! * it's better to do here because we postpone header buffer allocation */ ngx_http_upstream_process_header(r, u); return; } #endif 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; } } static void ngx_http_upstream_send_request_handler(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_connection_t *c; c = u->peer.connection; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http upstream send request handler"); if (c->write->timedout) { ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT); return; } #if (NGX_HTTP_SSL) if (u->ssl && c->ssl == NULL) { ngx_http_upstream_ssl_init_connection(r, u, c); return; } #endif if (u->header_sent) { u->write_event_handler = ngx_http_upstream_dummy_handler; (void) ngx_handle_write_event(c->write, 0); return; } ngx_http_upstream_send_request(r, u); } static void ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u) { ssize_t n; ngx_int_t rc; ngx_connection_t *c; c = u->peer.connection; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream process header"); c->log->action = "reading response header from upstream"; if (c->read->timedout) { ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT); return; } if (!u->request_sent && ngx_http_upstream_test_connect(c) != NGX_OK) { ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); return; } if (u->buffer.start == NULL) { u->buffer.start = ngx_palloc(r->pool, u->conf->buffer_size); if (u->buffer.start == NULL) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } u->buffer.pos = u->buffer.start; u->buffer.last = u->buffer.start; u->buffer.end = u->buffer.start + u->conf->buffer_size; u->buffer.temporary = 1; u->buffer.tag = u->output.tag; if (ngx_list_init(&u->headers_in.headers, r->pool, 8, sizeof(ngx_table_elt_t)) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } #if (NGX_HTTP_CACHE) if (r->cache) { u->buffer.pos += r->cache->header_start; u->buffer.last = u->buffer.pos; } #endif } for ( ;; ) { n = c->recv(c, u->buffer.last, u->buffer.end - u->buffer.last); if (n == NGX_AGAIN) { #if 0 ngx_add_timer(rev, u->read_timeout); #endif if (ngx_handle_read_event(c->read, 0) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } return; } if (n == 0) { ngx_log_error(NGX_LOG_ERR, c->log, 0, "upstream prematurely closed connection"); } if (n == NGX_ERROR || n == 0) { ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); return; } u->buffer.last += n; #if 0 u->valid_header_in = 0; u->peer.cached = 0; #endif rc = u->process_header(r); if (rc == NGX_AGAIN) { if (u->buffer.last == u->buffer.end) { ngx_log_error(NGX_LOG_ERR, c->log, 0, "upstream sent too big header"); ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_INVALID_HEADER); return; } continue; } break; } if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) { ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_INVALID_HEADER); return; } if (rc == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } /* rc == NGX_OK */ if (u->headers_in.status_n > NGX_HTTP_SPECIAL_RESPONSE) { if (r->subrequest_in_memory) { u->buffer.last = u->buffer.pos; } if (ngx_http_upstream_test_next(r, u) == NGX_OK) { return; } if (ngx_http_upstream_intercept_errors(r, u) == NGX_OK) { return; } } if (ngx_http_upstream_process_headers(r, u) != NGX_OK) { return; } if (!r->subrequest_in_memory) { ngx_http_upstream_send_response(r, u); return; } /* subrequest content in memory */ if (u->input_filter == NULL) { u->input_filter_init = ngx_http_upstream_non_buffered_filter_init; u->input_filter = ngx_http_upstream_non_buffered_filter; u->input_filter_ctx = r; } if (u->input_filter_init(u->input_filter_ctx) == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } n = u->buffer.last - u->buffer.pos; if (n) { u->buffer.last -= n; u->state->response_length += n; if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } if (u->length == 0) { ngx_http_upstream_finalize_request(r, u, 0); return; } } u->read_event_handler = ngx_http_upstream_process_body_in_memory; ngx_http_upstream_process_body_in_memory(r, u); } static ngx_int_t ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_uint_t status; ngx_http_upstream_next_t *un; status = u->headers_in.status_n; for (un = ngx_http_upstream_next_errors; un->status; un++) { if (status != un->status) { continue; } if (u->peer.tries > 1 && (u->conf->next_upstream & un->mask)) { ngx_http_upstream_next(r, u, un->mask); return NGX_OK; } #if (NGX_HTTP_CACHE) if (u->cache_status == NGX_HTTP_CACHE_EXPIRED && (u->conf->cache_use_stale & un->mask)) { ngx_int_t rc; rc = u->reinit_request(r); if (rc == NGX_OK) { u->cache_status = NGX_HTTP_CACHE_STALE; rc = ngx_http_upstream_cache_send(r, u); } ngx_http_upstream_finalize_request(r, u, rc); return NGX_OK; } #endif } return NGX_DECLINED; } static ngx_int_t ngx_http_upstream_intercept_errors(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_int_t status; ngx_uint_t i; ngx_table_elt_t *h; ngx_http_err_page_t *err_page; ngx_http_core_loc_conf_t *clcf; status = u->headers_in.status_n; if (status == NGX_HTTP_NOT_FOUND && u->conf->intercept_404) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_NOT_FOUND); return NGX_OK; } if (!u->conf->intercept_errors) { return NGX_DECLINED; } clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (clcf->error_pages == NULL) { return NGX_DECLINED; } err_page = clcf->error_pages->elts; for (i = 0; i < clcf->error_pages->nelts; i++) { if (err_page[i].status == status) { if (status == NGX_HTTP_UNAUTHORIZED && u->headers_in.www_authenticate) { h = ngx_list_push(&r->headers_out.headers); if (h == NULL) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return NGX_OK; } *h = *u->headers_in.www_authenticate; r->headers_out.www_authenticate = h; } #if (NGX_HTTP_CACHE) if (r->cache) { time_t valid; valid = ngx_http_file_cache_valid(u->conf->cache_valid, status); if (valid) { r->cache->valid_sec = ngx_time() + valid; r->cache->error = status; } ngx_http_file_cache_free(r->cache, u->pipe->temp_file); } #endif ngx_http_upstream_finalize_request(r, u, status); return NGX_OK; } } return NGX_DECLINED; } static ngx_int_t ngx_http_upstream_test_connect(ngx_connection_t *c) { int err; socklen_t len; #if (NGX_HAVE_KQUEUE) if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { if (c->write->pending_eof || c->read->pending_eof) { if (c->write->pending_eof) { err = c->write->kq_errno; } else { err = c->read->kq_errno; } c->log->action = "connecting to upstream"; (void) ngx_connection_error(c, err, "kevent() reported that connect() failed"); return NGX_ERROR; } } else #endif { err = 0; len = sizeof(int); /* * BSDs and Linux return 0 and set a pending error in err * Solaris returns -1 and sets errno */ if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len) == -1) { err = ngx_errno; } if (err) { c->log->action = "connecting to upstream"; (void) ngx_connection_error(c, err, "connect() failed"); return NGX_ERROR; } } return NGX_OK; } static ngx_int_t ngx_http_upstream_process_headers(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_str_t *uri, args; ngx_uint_t i, flags; ngx_list_part_t *part; ngx_table_elt_t *h; ngx_http_upstream_header_t *hh; ngx_http_upstream_main_conf_t *umcf; umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module); if (u->headers_in.x_accel_redirect && !(u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT)) { ngx_http_upstream_finalize_request(r, u, NGX_DECLINED); part = &u->headers_in.headers.part; h = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; h = part->elts; i = 0; } hh = ngx_hash_find(&umcf->headers_in_hash, h[i].hash, h[i].lowcase_key, h[i].key.len); if (hh && hh->redirect) { if (hh->copy_handler(r, &h[i], hh->conf) != NGX_OK) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return NGX_DONE; } } } uri = &u->headers_in.x_accel_redirect->value; 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; } ngx_http_internal_redirect(r, uri, &args); ngx_http_finalize_request(r, NGX_DONE); return NGX_DONE; } part = &u->headers_in.headers.part; h = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; h = part->elts; i = 0; } if (ngx_hash_find(&u->conf->hide_headers_hash, h[i].hash, h[i].lowcase_key, h[i].key.len)) { continue; } hh = ngx_hash_find(&umcf->headers_in_hash, h[i].hash, h[i].lowcase_key, h[i].key.len); if (hh) { if (hh->copy_handler(r, &h[i], hh->conf) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return NGX_DONE; } continue; } if (ngx_http_upstream_copy_header_line(r, &h[i], 0) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return NGX_DONE; } } if (r->headers_out.server && r->headers_out.server->value.data == NULL) { r->headers_out.server->hash = 0; } if (r->headers_out.date && r->headers_out.date->value.data == NULL) { r->headers_out.date->hash = 0; } r->headers_out.status = u->headers_in.status_n; r->headers_out.status_line = u->headers_in.status_line; r->headers_out.content_length_n = u->headers_in.content_length_n; u->length = u->headers_in.content_length_n; return NGX_OK; } static void ngx_http_upstream_process_body_in_memory(ngx_http_request_t *r, ngx_http_upstream_t *u) { size_t size; ssize_t n; ngx_buf_t *b; ngx_event_t *rev; ngx_connection_t *c; c = u->peer.connection; rev = c->read; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream process body on memory"); if (rev->timedout) { ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out"); ngx_http_upstream_finalize_request(r, u, NGX_ETIMEDOUT); return; } b = &u->buffer; for ( ;; ) { size = b->end - b->last; if (size == 0) { ngx_log_error(NGX_LOG_ALERT, c->log, 0, "upstream buffer is too small to read response"); ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } n = c->recv(c, b->last, size); if (n == NGX_AGAIN) { break; } if (n == 0 || n == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, n); return; } u->state->response_length += n; if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } if (!rev->ready) { break; } } if (u->length == 0) { ngx_http_upstream_finalize_request(r, u, 0); return; } if (ngx_handle_read_event(rev, 0) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } if (rev->active) { ngx_add_timer(rev, u->conf->read_timeout); } else if (rev->timer_set) { ngx_del_timer(rev); } } static void ngx_http_upstream_send_response(ngx_http_request_t *r, ngx_http_upstream_t *u) { int tcp_nodelay; ssize_t n; ngx_int_t rc; ngx_event_pipe_t *p; ngx_connection_t *c; ngx_http_core_loc_conf_t *clcf; rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->post_action) { ngx_http_upstream_finalize_request(r, u, rc); return; } if (u->upgrade) { ngx_http_upstream_upgrade(r, u); return; } c = r->connection; if (r->header_only) { if (u->cacheable || u->store) { if (ngx_shutdown_socket(c->fd, NGX_WRITE_SHUTDOWN) == -1) { ngx_connection_error(c, ngx_socket_errno, ngx_shutdown_socket_n " failed"); } r->read_event_handler = ngx_http_request_empty_handler; r->write_event_handler = ngx_http_request_empty_handler; c->error = 1; } else { ngx_http_upstream_finalize_request(r, u, rc); return; } } u->header_sent = 1; if (r->request_body && r->request_body->temp_file) { ngx_pool_run_cleanup_file(r->pool, r->request_body->temp_file->file.fd); r->request_body->temp_file->file.fd = NGX_INVALID_FILE; } clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (!u->buffering) { if (u->input_filter == NULL) { u->input_filter_init = ngx_http_upstream_non_buffered_filter_init; u->input_filter = ngx_http_upstream_non_buffered_filter; u->input_filter_ctx = r; } u->read_event_handler = ngx_http_upstream_process_non_buffered_upstream; r->write_event_handler = ngx_http_upstream_process_non_buffered_downstream; r->limit_rate = 0; if (u->input_filter_init(u->input_filter_ctx) == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, 0); return; } if (clcf->tcp_nodelay && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay"); tcp_nodelay = 1; if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, (const void *) &tcp_nodelay, sizeof(int)) == -1) { ngx_connection_error(c, ngx_socket_errno, "setsockopt(TCP_NODELAY) failed"); ngx_http_upstream_finalize_request(r, u, 0); return; } c->tcp_nodelay = NGX_TCP_NODELAY_SET; } n = u->buffer.last - u->buffer.pos; if (n) { u->buffer.last = u->buffer.pos; u->state->response_length += n; if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, 0); 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, 0); return; } if (u->peer.connection->read->ready || u->length == 0) { ngx_http_upstream_process_non_buffered_upstream(r, u); } } return; } /* TODO: preallocate event_pipe bufs, look "Content-Length" */ #if (NGX_HTTP_CACHE) if (r->cache && r->cache->file.fd != NGX_INVALID_FILE) { ngx_pool_run_cleanup_file(r->pool, r->cache->file.fd); r->cache->file.fd = NGX_INVALID_FILE; } switch (ngx_http_test_predicates(r, u->conf->no_cache)) { case NGX_ERROR: ngx_http_upstream_finalize_request(r, u, 0); return; case NGX_DECLINED: u->cacheable = 0; break; default: /* NGX_OK */ if (u->cache_status == NGX_HTTP_CACHE_BYPASS) { r->cache->min_uses = u->conf->cache_min_uses; r->cache->body_start = u->conf->buffer_size; r->cache->file_cache = u->conf->cache->data; if (ngx_http_file_cache_create(r) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, 0); 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->last_modified = r->headers_out.last_modified_time; r->cache->date = now; r->cache->body_start = (u_short) (u->buffer.pos - u->buffer.start); ngx_http_file_cache_set_header(r, u->buffer.start); } else { u->cacheable = 0; r->headers_out.last_modified_time = -1; } } 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); } #endif p = u->pipe; p->output_filter = (ngx_event_pipe_output_filter_pt) ngx_http_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->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, 0); 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; } 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; p->preread_bufs = ngx_alloc_chain_link(r->pool); if (p->preread_bufs == NULL) { ngx_http_upstream_finalize_request(r, u, 0); 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, 0); 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_AIO_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, 0); return; } u->read_event_handler = ngx_http_upstream_process_upstream; r->write_event_handler = ngx_http_upstream_process_downstream; ngx_http_upstream_process_upstream(r, u); } static void ngx_http_upstream_upgrade(ngx_http_request_t *r, ngx_http_upstream_t *u) { int tcp_nodelay; ngx_connection_t *c; ngx_http_core_loc_conf_t *clcf; c = r->connection; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); /* TODO: prevent upgrade if not requested or not possible */ r->keepalive = 0; c->log->action = "proxying upgraded connection"; u->read_event_handler = ngx_http_upstream_upgraded_read_upstream; u->write_event_handler = ngx_http_upstream_upgraded_write_upstream; r->read_event_handler = ngx_http_upstream_upgraded_read_downstream; r->write_event_handler = ngx_http_upstream_upgraded_write_downstream; if (clcf->tcp_nodelay) { tcp_nodelay = 1; if (c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay"); if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, (const void *) &tcp_nodelay, sizeof(int)) == -1) { ngx_connection_error(c, ngx_socket_errno, "setsockopt(TCP_NODELAY) failed"); ngx_http_upstream_finalize_request(r, u, 0); return; } c->tcp_nodelay = NGX_TCP_NODELAY_SET; } if (u->peer.connection->tcp_nodelay == NGX_TCP_NODELAY_UNSET) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, u->peer.connection->log, 0, "tcp_nodelay"); if (setsockopt(u->peer.connection->fd, IPPROTO_TCP, TCP_NODELAY, (const void *) &tcp_nodelay, sizeof(int)) == -1) { ngx_connection_error(u->peer.connection, ngx_socket_errno, "setsockopt(TCP_NODELAY) failed"); ngx_http_upstream_finalize_request(r, u, 0); return; } u->peer.connection->tcp_nodelay = NGX_TCP_NODELAY_SET; } } if (ngx_http_send_special(r, NGX_HTTP_FLUSH) == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, 0); return; } if (u->peer.connection->read->ready || u->buffer.pos != u->buffer.last) { ngx_post_event(c->read, &ngx_posted_events); ngx_http_upstream_process_upgraded(r, 1, 1); return; } ngx_http_upstream_process_upgraded(r, 0, 1); } static void ngx_http_upstream_upgraded_read_downstream(ngx_http_request_t *r) { ngx_http_upstream_process_upgraded(r, 0, 0); } static void ngx_http_upstream_upgraded_write_downstream(ngx_http_request_t *r) { ngx_http_upstream_process_upgraded(r, 1, 1); } static void ngx_http_upstream_upgraded_read_upstream(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_http_upstream_process_upgraded(r, 1, 0); } static void ngx_http_upstream_upgraded_write_upstream(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_http_upstream_process_upgraded(r, 0, 1); } static void ngx_http_upstream_process_upgraded(ngx_http_request_t *r, ngx_uint_t from_upstream, ngx_uint_t do_write) { size_t size; ssize_t n; ngx_buf_t *b; ngx_connection_t *c, *downstream, *upstream, *dst, *src; ngx_http_upstream_t *u; ngx_http_core_loc_conf_t *clcf; c = r->connection; u = r->upstream; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream process upgraded, fu:%ui", from_upstream); downstream = c; upstream = u->peer.connection; if (downstream->write->timedout) { c->timedout = 1; ngx_connection_error(c, NGX_ETIMEDOUT, "client timed out"); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_REQUEST_TIME_OUT); return; } if (upstream->read->timedout || upstream->write->timedout) { ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out"); ngx_http_upstream_finalize_request(r, u, 0); 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, 0); 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, 0); return; } if (n > 0) { b->pos += n; if (b->pos == b->last) { b->pos = b->start; b->last = b->start; } } } } size = b->end - b->last; if (size && src->read->ready) { n = src->recv(src, b->last, size); if (n == NGX_AGAIN || n == 0) { break; } if (n > 0) { do_write = 1; b->last += n; continue; } if (n == NGX_ERROR) { src->read->eof = 1; } } break; } if ((upstream->read->eof && u->buffer.pos == u->buffer.last) || (downstream->read->eof && u->from_client.pos == u->from_client.last) || (downstream->read->eof && upstream->read->eof)) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream upgraded done"); ngx_http_upstream_finalize_request(r, u, 0); return; } clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (ngx_handle_write_event(upstream->write, u->conf->send_lowat) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, 0); return; } if (upstream->write->active && !upstream->write->ready) { ngx_add_timer(upstream->write, u->conf->send_timeout); } else if (upstream->write->timer_set) { ngx_del_timer(upstream->write); } if (ngx_handle_read_event(upstream->read, 0) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, 0); 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, 0); return; } if (ngx_handle_read_event(downstream->read, 0) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, 0); 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, 0); return; } ngx_http_upstream_process_non_buffered_request(r, 0); } static void ngx_http_upstream_process_non_buffered_request(ngx_http_request_t *r, ngx_uint_t do_write) { size_t size; ssize_t n; ngx_buf_t *b; ngx_int_t rc; ngx_connection_t *downstream, *upstream; ngx_http_upstream_t *u; ngx_http_core_loc_conf_t *clcf; u = r->upstream; downstream = r->connection; upstream = u->peer.connection; b = &u->buffer; do_write = do_write || u->length == 0; for ( ;; ) { if (do_write) { if (u->out_bufs || u->busy_bufs) { rc = ngx_http_output_filter(r, u->out_bufs); if (rc == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, 0); 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 || upstream->read->error) { ngx_http_upstream_finalize_request(r, u, 0); return; } b->pos = b->start; b->last = b->start; } } size = b->end - b->last; if (size && upstream->read->ready) { n = upstream->recv(upstream, b->last, size); if (n == NGX_AGAIN) { break; } if (n > 0) { u->state->response_length += n; if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, 0); 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, 0); return; } } if (downstream->write->active && !downstream->write->ready) { ngx_add_timer(downstream->write, clcf->send_timeout); } else if (downstream->write->timer_set) { ngx_del_timer(downstream->write); } if (ngx_handle_read_event(upstream->read, 0) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, 0); return; } if (upstream->read->active && !upstream->read->ready) { ngx_add_timer(upstream->read, u->conf->read_timeout); } else if (upstream->read->timer_set) { ngx_del_timer(upstream->read); } } static ngx_int_t ngx_http_upstream_non_buffered_filter_init(void *data) { return NGX_OK; } static ngx_int_t ngx_http_upstream_non_buffered_filter(void *data, ssize_t bytes) { ngx_http_request_t *r = data; ngx_buf_t *b; ngx_chain_t *cl, **ll; ngx_http_upstream_t *u; u = r->upstream; for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) { ll = &cl->next; } cl = ngx_chain_get_free_buf(r->pool, &u->free_bufs); if (cl == NULL) { return NGX_ERROR; } *ll = cl; cl->buf->flush = 1; cl->buf->memory = 1; b = &u->buffer; cl->buf->pos = b->last; b->last += bytes; cl->buf->last = b->last; cl->buf->tag = u->output.tag; if (u->length == -1) { return NGX_OK; } u->length -= bytes; return NGX_OK; } 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 (wev->timedout) { if (wev->delayed) { wev->timedout = 0; wev->delayed = 0; if (!wev->ready) { ngx_add_timer(wev, p->send_timeout); if (ngx_handle_write_event(wev, p->send_lowat) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, 0); } return; } if (ngx_event_pipe(p, wev->write) == NGX_ABORT) { ngx_http_upstream_finalize_request(r, u, 0); return; } } else { p->downstream_error = 1; c->timedout = 1; ngx_connection_error(c, NGX_ETIMEDOUT, "client timed out"); } } else { if (wev->delayed) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http downstream delayed"); if (ngx_handle_write_event(wev, p->send_lowat) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, 0); } return; } if (ngx_event_pipe(p, 1) == NGX_ABORT) { ngx_http_upstream_finalize_request(r, u, 0); return; } } ngx_http_upstream_process_request(r); } static void ngx_http_upstream_process_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 upstream"); c->log->action = "reading upstream"; if (c->read->timedout) { u->pipe->upstream_error = 1; ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out"); } else { if (ngx_event_pipe(u->pipe, 0) == NGX_ABORT) { ngx_http_upstream_finalize_request(r, u, 0); return; } } ngx_http_upstream_process_request(r); } static void ngx_http_upstream_process_request(ngx_http_request_t *r) { ngx_temp_file_t *tf; ngx_event_pipe_t *p; ngx_http_upstream_t *u; u = r->upstream; p = u->pipe; if (u->peer.connection) { if (u->store) { if (p->upstream_eof || p->upstream_done) { tf = u->pipe->temp_file; if (u->headers_in.status_n == NGX_HTTP_OK && (u->headers_in.content_length_n == -1 || (u->headers_in.content_length_n == tf->offset))) { ngx_http_upstream_store(r, u); u->store = 0; } } } #if (NGX_HTTP_CACHE) if (u->cacheable) { if (p->upstream_done) { ngx_http_file_cache_update(r, u->pipe->temp_file); } else if (p->upstream_eof) { tf = u->pipe->temp_file; if (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, u->pipe->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 0 ngx_http_busy_unlock(u->conf->busy_lock, &u->busy_lock); #endif ngx_http_upstream_finalize_request(r, u, 0); 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, 0); } } } 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_http_parse_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) { ngx_http_map_uri_to_path(r, &path, &root, 0); } 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); } 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_uint_t status, state; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http next upstream, %xi", ft_type); #if 0 ngx_http_busy_unlock(u->conf->busy_lock, &u->busy_lock); #endif if (u->peer.sockaddr) { if (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) { status = 0; /* TODO: inform balancer instead */ u->peer.tries++; } else { switch(ft_type) { case NGX_HTTP_UPSTREAM_FT_TIMEOUT: status = NGX_HTTP_GATEWAY_TIME_OUT; break; case NGX_HTTP_UPSTREAM_FT_HTTP_500: status = NGX_HTTP_INTERNAL_SERVER_ERROR; break; case NGX_HTTP_UPSTREAM_FT_HTTP_404: status = NGX_HTTP_NOT_FOUND; break; /* * NGX_HTTP_UPSTREAM_FT_BUSY_LOCK and NGX_HTTP_UPSTREAM_FT_MAX_WAITING * never reach here */ default: status = NGX_HTTP_BAD_GATEWAY; } } if (r->connection->error) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_CLIENT_CLOSED_REQUEST); return; } if (status) { u->state->status = status; if (u->peer.tries == 0 || !(u->conf->next_upstream & ft_type)) { #if (NGX_HTTP_CACHE) if (u->cache_status == NGX_HTTP_CACHE_EXPIRED && (u->conf->cache_use_stale & ft_type)) { ngx_int_t rc; rc = u->reinit_request(r); if (rc == NGX_OK) { u->cache_status = NGX_HTTP_CACHE_STALE; rc = ngx_http_upstream_cache_send(r, u); } ngx_http_upstream_finalize_request(r, u, rc); return; } #endif ngx_http_upstream_finalize_request(r, u, status); return; } } if (u->peer.connection) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "close http upstream connection: %d", u->peer.connection->fd); #if (NGX_HTTP_SSL) if (u->peer.connection->ssl) { u->peer.connection->ssl->no_wait_shutdown = 1; u->peer.connection->ssl->no_send_shutdown = 1; (void) ngx_ssl_shutdown(u->peer.connection); } #endif if (u->peer.connection->pool) { ngx_destroy_pool(u->peer.connection->pool); } ngx_close_connection(u->peer.connection); u->peer.connection = NULL; } #if 0 if (u->conf->busy_lock && !u->busy_locked) { ngx_http_upstream_busy_lock(p); return; } #endif 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_time_t *tp; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "finalize http upstream request: %i", rc); if (u->cleanup) { *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_sec) { tp = ngx_timeofday(); u->state->response_sec = tp->sec - u->state->response_sec; u->state->response_msec = tp->msec - u->state->response_msec; if (u->pipe && u->pipe->read_length) { u->state->response_length = u->pipe->read_length; } } u->finalize_request(r, rc); if (u->peer.free && u->peer.sockaddr) { u->peer.free(&u->peer, u->peer.data, 0); u->peer.sockaddr = NULL; } if (u->peer.connection) { #if (NGX_HTTP_SSL) /* TODO: do not shutdown persistent connection */ if (u->peer.connection->ssl) { /* * We send the "close notify" shutdown alert to the upstream only * and do not wait its "close notify" shutdown alert. * It is acceptable according to the TLS standard. */ u->peer.connection->ssl->no_wait_shutdown = 1; (void) ngx_ssl_shutdown(u->peer.connection); } #endif ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "close http upstream connection: %d", u->peer.connection->fd); if (u->peer.connection->pool) { ngx_destroy_pool(u->peer.connection->pool); } ngx_close_connection(u->peer.connection); } u->peer.connection = NULL; if (u->pipe && u->pipe->temp_file) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http upstream temp fd: %d", u->pipe->temp_file->file.fd); } if (u->store && u->pipe && u->pipe->temp_file && u->pipe->temp_file->file.fd != NGX_INVALID_FILE) { if (ngx_delete_file(u->pipe->temp_file->file.name.data) == NGX_FILE_ERROR) { ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno, ngx_delete_file_n " \"%s\" failed", u->pipe->temp_file->file.name.data); } } #if (NGX_HTTP_CACHE) if (r->cache) { if (u->cacheable) { if (rc == NGX_HTTP_BAD_GATEWAY || rc == NGX_HTTP_GATEWAY_TIME_OUT) { time_t valid; valid = ngx_http_file_cache_valid(u->conf->cache_valid, rc); if (valid) { r->cache->valid_sec = ngx_time() + valid; r->cache->error = rc; } } } ngx_http_file_cache_free(r->cache, u->pipe->temp_file); } #endif if (u->header_sent && rc != NGX_HTTP_REQUEST_TIME_OUT && (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE)) { rc = 0; } if (rc == NGX_DECLINED) { return; } r->connection->log->action = "sending to client"; if (rc == 0 && !r->header_only #if (NGX_HTTP_CACHE) && !r->cached #endif ) { rc = ngx_http_send_special(r, NGX_HTTP_LAST); } ngx_http_finalize_request(r, rc); } static ngx_int_t ngx_http_upstream_process_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { ngx_table_elt_t **ph; ph = (ngx_table_elt_t **) ((char *) &r->upstream->headers_in + offset); if (*ph == NULL) { *ph = h; } return NGX_OK; } static ngx_int_t ngx_http_upstream_ignore_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { return NGX_OK; } static ngx_int_t ngx_http_upstream_process_content_length(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { ngx_http_upstream_t *u; u = r->upstream; u->headers_in.content_length = h; u->headers_in.content_length_n = ngx_atoof(h->value.data, h->value.len); return NGX_OK; } static ngx_int_t ngx_http_upstream_process_set_cookie(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { #if (NGX_HTTP_CACHE) ngx_http_upstream_t *u; u = r->upstream; if (!(u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_SET_COOKIE)) { u->cacheable = 0; } #endif return NGX_OK; } static ngx_int_t ngx_http_upstream_process_cache_control(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { ngx_array_t *pa; ngx_table_elt_t **ph; ngx_http_upstream_t *u; u = r->upstream; pa = &u->headers_in.cache_control; if (pa->elts == NULL) { if (ngx_array_init(pa, r->pool, 2, sizeof(ngx_table_elt_t *)) != NGX_OK) { return NGX_ERROR; } } ph = ngx_array_push(pa); if (ph == NULL) { return NGX_ERROR; } *ph = h; #if (NGX_HTTP_CACHE) { u_char *p, *last; ngx_int_t n; if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_CACHE_CONTROL) { return NGX_OK; } if (r->cache == NULL) { return NGX_OK; } if (r->cache->valid_sec != 0) { return NGX_OK; } p = h->value.data; last = p + h->value.len; if (ngx_strlcasestrn(p, last, (u_char *) "no-cache", 8 - 1) != NULL || ngx_strlcasestrn(p, last, (u_char *) "no-store", 8 - 1) != NULL || ngx_strlcasestrn(p, last, (u_char *) "private", 7 - 1) != NULL) { u->cacheable = 0; return NGX_OK; } p = ngx_strlcasestrn(p, last, (u_char *) "max-age=", 8 - 1); if (p == NULL) { return NGX_OK; } n = 0; for (p += 8; p < last; p++) { if (*p == ',' || *p == ';' || *p == ' ') { break; } if (*p >= '0' && *p <= '9') { n = n * 10 + *p - '0'; continue; } u->cacheable = 0; return NGX_OK; } if (n == 0) { u->cacheable = 0; return NGX_OK; } r->cache->valid_sec = ngx_time() + n; } #endif return NGX_OK; } static ngx_int_t ngx_http_upstream_process_expires(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { ngx_http_upstream_t *u; u = r->upstream; u->headers_in.expires = h; #if (NGX_HTTP_CACHE) { time_t expires; if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_EXPIRES) { return NGX_OK; } if (r->cache == NULL) { return NGX_OK; } if (r->cache->valid_sec != 0) { return NGX_OK; } expires = ngx_http_parse_time(h->value.data, h->value.len); if (expires == NGX_ERROR || expires < ngx_time()) { u->cacheable = 0; return NGX_OK; } r->cache->valid_sec = expires; } #endif return NGX_OK; } static ngx_int_t ngx_http_upstream_process_accel_expires(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { ngx_http_upstream_t *u; u = r->upstream; u->headers_in.x_accel_expires = h; #if (NGX_HTTP_CACHE) { u_char *p; size_t len; ngx_int_t n; if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_EXPIRES) { return NGX_OK; } if (r->cache == NULL) { return NGX_OK; } len = h->value.len; p = h->value.data; if (p[0] != '@') { n = ngx_atoi(p, len); switch (n) { case 0: u->cacheable = 0; /* fall through */ case NGX_ERROR: return NGX_OK; default: r->cache->valid_sec = ngx_time() + n; return NGX_OK; } } p++; len--; n = ngx_atoi(p, len); if (n != NGX_ERROR) { r->cache->valid_sec = n; } } #endif return NGX_OK; } static ngx_int_t ngx_http_upstream_process_limit_rate(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { ngx_int_t n; ngx_http_upstream_t *u; u = r->upstream; u->headers_in.x_accel_limit_rate = h; if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_LIMIT_RATE) { return NGX_OK; } n = ngx_atoi(h->value.data, h->value.len); if (n != NGX_ERROR) { r->limit_rate = (size_t) n; } return NGX_OK; } static ngx_int_t ngx_http_upstream_process_buffering(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { u_char c0, c1, c2; ngx_http_upstream_t *u; u = r->upstream; if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_BUFFERING) { return NGX_OK; } if (u->conf->change_buffering) { if (h->value.len == 2) { c0 = ngx_tolower(h->value.data[0]); c1 = ngx_tolower(h->value.data[1]); if (c0 == 'n' && c1 == 'o') { u->buffering = 0; } } else if (h->value.len == 3) { c0 = ngx_tolower(h->value.data[0]); c1 = ngx_tolower(h->value.data[1]); c2 = ngx_tolower(h->value.data[2]); if (c0 == 'y' && c1 == 'e' && c2 == 's') { u->buffering = 1; } } } return NGX_OK; } static ngx_int_t ngx_http_upstream_process_charset(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { if (r->upstream->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_CHARSET) { return NGX_OK; } r->headers_out.override_charset = &h->value; return NGX_OK; } static ngx_int_t ngx_http_upstream_process_connection(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { r->upstream->headers_in.connection = h; if (ngx_strlcasestrn(h->value.data, h->value.data + h->value.len, (u_char *) "close", 5 - 1) != NULL) { r->upstream->headers_in.connection_close = 1; } return NGX_OK; } static ngx_int_t ngx_http_upstream_process_transfer_encoding(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { r->upstream->headers_in.transfer_encoding = h; if (ngx_strlcasestrn(h->value.data, h->value.data + h->value.len, (u_char *) "chunked", 7 - 1) != NULL) { r->upstream->headers_in.chunked = 1; } return NGX_OK; } static ngx_int_t ngx_http_upstream_copy_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { ngx_table_elt_t *ho, **ph; ho = ngx_list_push(&r->headers_out.headers); if (ho == NULL) { return NGX_ERROR; } *ho = *h; if (offset) { ph = (ngx_table_elt_t **) ((char *) &r->headers_out + offset); *ph = ho; } return NGX_OK; } static ngx_int_t ngx_http_upstream_copy_multi_header_lines(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { ngx_array_t *pa; ngx_table_elt_t *ho, **ph; pa = (ngx_array_t *) ((char *) &r->headers_out + offset); if (pa->elts == NULL) { if (ngx_array_init(pa, r->pool, 2, sizeof(ngx_table_elt_t *)) != NGX_OK) { return NGX_ERROR; } } ph = ngx_array_push(pa); if (ph == NULL) { return NGX_ERROR; } ho = ngx_list_push(&r->headers_out.headers); if (ho == NULL) { return NGX_ERROR; } *ho = *h; *ph = ho; return NGX_OK; } static ngx_int_t ngx_http_upstream_copy_content_type(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { u_char *p, *last; r->headers_out.content_type_len = h->value.len; r->headers_out.content_type = h->value; r->headers_out.content_type_lowcase = NULL; for (p = h->value.data; *p; p++) { if (*p != ';') { continue; } last = p; while (*++p == ' ') { /* void */ } if (*p == '\0') { return NGX_OK; } if (ngx_strncasecmp(p, (u_char *) "charset=", 8) != 0) { continue; } p += 8; r->headers_out.content_type_len = last - h->value.data; if (*p == '"') { p++; } last = h->value.data + h->value.len; if (*(last - 1) == '"') { last--; } r->headers_out.charset.len = last - p; r->headers_out.charset.data = p; return NGX_OK; } return NGX_OK; } static ngx_int_t ngx_http_upstream_copy_last_modified(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { ngx_table_elt_t *ho; ho = ngx_list_push(&r->headers_out.headers); if (ho == NULL) { return NGX_ERROR; } *ho = *h; r->headers_out.last_modified = ho; #if (NGX_HTTP_CACHE) if (r->upstream->cacheable) { r->headers_out.last_modified_time = ngx_http_parse_time(h->value.data, h->value.len); } #endif return NGX_OK; } static ngx_int_t ngx_http_upstream_rewrite_location(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { ngx_int_t rc; ngx_table_elt_t *ho; ho = ngx_list_push(&r->headers_out.headers); if (ho == NULL) { return NGX_ERROR; } *ho = *h; if (r->upstream->rewrite_redirect) { rc = r->upstream->rewrite_redirect(r, ho, 0); if (rc == NGX_DECLINED) { return NGX_OK; } if (rc == NGX_OK) { r->headers_out.location = ho; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "rewritten location: \"%V\"", &ho->value); } return rc; } if (ho->value.data[0] != '/') { r->headers_out.location = ho; } /* * we do not set r->headers_out.location here to avoid the handling * the local redirects without a host name by ngx_http_header_filter() */ return NGX_OK; } static ngx_int_t ngx_http_upstream_rewrite_refresh(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { u_char *p; ngx_int_t rc; ngx_table_elt_t *ho; ho = ngx_list_push(&r->headers_out.headers); if (ho == NULL) { return NGX_ERROR; } *ho = *h; if (r->upstream->rewrite_redirect) { p = ngx_strcasestrn(ho->value.data, "url=", 4 - 1); if (p) { rc = r->upstream->rewrite_redirect(r, ho, p + 4 - ho->value.data); } else { return NGX_OK; } if (rc == NGX_DECLINED) { return NGX_OK; } if (rc == NGX_OK) { r->headers_out.refresh = ho; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "rewritten refresh: \"%V\"", &ho->value); } return rc; } r->headers_out.refresh = ho; return NGX_OK; } static ngx_int_t ngx_http_upstream_rewrite_set_cookie(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { ngx_int_t rc; ngx_table_elt_t *ho; ho = ngx_list_push(&r->headers_out.headers); if (ho == NULL) { return NGX_ERROR; } *ho = *h; if (r->upstream->rewrite_cookie) { rc = r->upstream->rewrite_cookie(r, ho); if (rc == NGX_DECLINED) { return NGX_OK; } #if (NGX_DEBUG) if (rc == NGX_OK) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "rewritten cookie: \"%V\"", &ho->value); } #endif return rc; } return NGX_OK; } static ngx_int_t ngx_http_upstream_copy_allow_ranges(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { ngx_table_elt_t *ho; #if (NGX_HTTP_CACHE) if (r->cached) { r->allow_ranges = 1; return NGX_OK; } #endif ho = ngx_list_push(&r->headers_out.headers); if (ho == NULL) { return NGX_ERROR; } *ho = *h; r->headers_out.accept_ranges = ho; return NGX_OK; } #if (NGX_HTTP_GZIP) static ngx_int_t ngx_http_upstream_copy_content_encoding(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { ngx_table_elt_t *ho; ho = ngx_list_push(&r->headers_out.headers); if (ho == NULL) { return NGX_ERROR; } *ho = *h; r->headers_out.content_encoding = ho; return NGX_OK; } #endif static ngx_int_t ngx_http_upstream_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var, *v; for (v = ngx_http_upstream_vars; v->name.len; v++) { var = ngx_http_add_variable(cf, &v->name, v->flags); if (var == NULL) { return NGX_ERROR; } var->get_handler = v->get_handler; var->data = v->data; } return NGX_OK; } static ngx_int_t ngx_http_upstream_addr_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; size_t len; ngx_uint_t i; ngx_http_upstream_state_t *state; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; if (r->upstream_states == NULL || r->upstream_states->nelts == 0) { v->not_found = 1; return NGX_OK; } len = 0; state = r->upstream_states->elts; for (i = 0; i < r->upstream_states->nelts; i++) { if (state[i].peer) { len += state[i].peer->len + 2; } else { len += 3; } } p = ngx_pnalloc(r->pool, len); if (p == NULL) { return NGX_ERROR; } v->data = p; i = 0; for ( ;; ) { if (state[i].peer) { p = ngx_cpymem(p, state[i].peer->data, state[i].peer->len); } if (++i == r->upstream_states->nelts) { break; } if (state[i].peer) { *p++ = ','; *p++ = ' '; } else { *p++ = ' '; *p++ = ':'; *p++ = ' '; if (++i == r->upstream_states->nelts) { break; } continue; } } v->len = p - v->data; return NGX_OK; } static ngx_int_t ngx_http_upstream_status_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; size_t len; ngx_uint_t i; ngx_http_upstream_state_t *state; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; if (r->upstream_states == NULL || r->upstream_states->nelts == 0) { v->not_found = 1; return NGX_OK; } len = r->upstream_states->nelts * (3 + 2); p = ngx_pnalloc(r->pool, len); if (p == NULL) { return NGX_ERROR; } v->data = p; i = 0; state = r->upstream_states->elts; for ( ;; ) { if (state[i].status) { p = ngx_sprintf(p, "%ui", state[i].status); } else { *p++ = '-'; } if (++i == r->upstream_states->nelts) { break; } if (state[i].peer) { *p++ = ','; *p++ = ' '; } else { *p++ = ' '; *p++ = ':'; *p++ = ' '; if (++i == r->upstream_states->nelts) { break; } continue; } } v->len = p - v->data; return NGX_OK; } static ngx_int_t ngx_http_upstream_response_time_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; size_t len; ngx_uint_t i; ngx_msec_int_t ms; ngx_http_upstream_state_t *state; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; if (r->upstream_states == NULL || r->upstream_states->nelts == 0) { v->not_found = 1; return NGX_OK; } len = r->upstream_states->nelts * (NGX_TIME_T_LEN + 4 + 2); p = ngx_pnalloc(r->pool, len); if (p == NULL) { return NGX_ERROR; } v->data = p; i = 0; state = r->upstream_states->elts; for ( ;; ) { if (state[i].status) { ms = (ngx_msec_int_t) (state[i].response_sec * 1000 + state[i].response_msec); ms = ngx_max(ms, 0); p = ngx_sprintf(p, "%d.%03d", ms / 1000, ms % 1000); } else { *p++ = '-'; } if (++i == r->upstream_states->nelts) { break; } if (state[i].peer) { *p++ = ','; *p++ = ' '; } else { *p++ = ' '; *p++ = ':'; *p++ = ' '; if (++i == r->upstream_states->nelts) { break; } continue; } } v->len = p - v->data; return NGX_OK; } static ngx_int_t ngx_http_upstream_response_length_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; size_t len; ngx_uint_t i; ngx_http_upstream_state_t *state; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; if (r->upstream_states == NULL || r->upstream_states->nelts == 0) { v->not_found = 1; return NGX_OK; } len = r->upstream_states->nelts * (NGX_OFF_T_LEN + 2); p = ngx_pnalloc(r->pool, len); if (p == NULL) { return NGX_ERROR; } v->data = p; i = 0; state = r->upstream_states->elts; for ( ;; ) { p = ngx_sprintf(p, "%O", state[i].response_length); if (++i == r->upstream_states->nelts) { break; } if (state[i].peer) { *p++ = ','; *p++ = ' '; } else { *p++ = ' '; *p++ = ':'; *p++ = ' '; if (++i == r->upstream_states->nelts) { break; } continue; } } v->len = p - v->data; return NGX_OK; } ngx_int_t ngx_http_upstream_header_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { if (r->upstream == NULL) { v->not_found = 1; return NGX_OK; } return ngx_http_variable_unknown_header(v, (ngx_str_t *) data, &r->upstream->headers_in.headers.part, sizeof("upstream_http_") - 1); } #if (NGX_HTTP_CACHE) ngx_int_t ngx_http_upstream_cache_status(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_uint_t n; if (r->upstream == NULL || r->upstream->cache_status == 0) { v->not_found = 1; return NGX_OK; } n = r->upstream->cache_status - 1; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->len = ngx_http_cache_status[n].len; v->data = ngx_http_cache_status[n].data; return NGX_OK; } #endif static char * ngx_http_upstream(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy) { char *rv; void *mconf; ngx_str_t *value; ngx_url_t u; ngx_uint_t m; ngx_conf_t pcf; ngx_http_module_t *module; ngx_http_conf_ctx_t *ctx, *http_ctx; ngx_http_upstream_srv_conf_t *uscf; ngx_memzero(&u, sizeof(ngx_url_t)); value = cf->args->elts; u.host = value[1]; u.no_resolve = 1; u.no_port = 1; uscf = ngx_http_upstream_add(cf, &u, NGX_HTTP_UPSTREAM_CREATE |NGX_HTTP_UPSTREAM_WEIGHT |NGX_HTTP_UPSTREAM_MAX_FAILS |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT |NGX_HTTP_UPSTREAM_DOWN |NGX_HTTP_UPSTREAM_BACKUP); if (uscf == NULL) { return NGX_CONF_ERROR; } ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t)); if (ctx == NULL) { return NGX_CONF_ERROR; } http_ctx = cf->ctx; ctx->main_conf = http_ctx->main_conf; /* the upstream{}'s srv_conf */ ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); if (ctx->srv_conf == NULL) { return NGX_CONF_ERROR; } ctx->srv_conf[ngx_http_upstream_module.ctx_index] = uscf; uscf->srv_conf = ctx->srv_conf; /* the upstream{}'s loc_conf */ ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); if (ctx->loc_conf == NULL) { return NGX_CONF_ERROR; } for (m = 0; ngx_modules[m]; m++) { if (ngx_modules[m]->type != NGX_HTTP_MODULE) { continue; } module = ngx_modules[m]->ctx; if (module->create_srv_conf) { mconf = module->create_srv_conf(cf); if (mconf == NULL) { return NGX_CONF_ERROR; } ctx->srv_conf[ngx_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[ngx_modules[m]->ctx_index] = mconf; } } /* 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 == NULL) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "no servers are inside upstream"); return NGX_CONF_ERROR; } return rv; } static char * ngx_http_upstream_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_upstream_srv_conf_t *uscf = conf; time_t fail_timeout; ngx_str_t *value, s; ngx_url_t u; ngx_int_t weight, max_fails; ngx_uint_t i; ngx_http_upstream_server_t *us; if (uscf->servers == NULL) { uscf->servers = ngx_array_create(cf->pool, 4, sizeof(ngx_http_upstream_server_t)); if (uscf->servers == NULL) { return NGX_CONF_ERROR; } } 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; 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; } weight = 1; max_fails = 1; fail_timeout = 10; for (i = 2; i < cf->args->nelts; i++) { if (ngx_strncmp(value[i].data, "weight=", 7) == 0) { if (!(uscf->flags & NGX_HTTP_UPSTREAM_WEIGHT)) { goto invalid; } weight = ngx_atoi(&value[i].data[7], value[i].len - 7); if (weight == NGX_ERROR || weight == 0) { goto invalid; } continue; } if (ngx_strncmp(value[i].data, "max_fails=", 10) == 0) { if (!(uscf->flags & NGX_HTTP_UPSTREAM_MAX_FAILS)) { goto invalid; } 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 invalid; } 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_strncmp(value[i].data, "backup", 6) == 0) { if (!(uscf->flags & NGX_HTTP_UPSTREAM_BACKUP)) { goto invalid; } us->backup = 1; continue; } if (ngx_strncmp(value[i].data, "down", 4) == 0) { if (!(uscf->flags & NGX_HTTP_UPSTREAM_DOWN)) { goto invalid; } us->down = 1; continue; } goto invalid; } us->addrs = u.addrs; us->naddrs = u.naddrs; us->weight = weight; us->max_fails = max_fails; us->fail_timeout = fail_timeout; return NGX_CONF_OK; invalid: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[i]); return NGX_CONF_ERROR; } ngx_http_upstream_srv_conf_t * ngx_http_upstream_add(ngx_conf_t *cf, ngx_url_t *u, ngx_uint_t flags) { ngx_uint_t i; ngx_http_upstream_server_t *us; ngx_http_upstream_srv_conf_t *uscf, **uscfp; ngx_http_upstream_main_conf_t *umcf; if (!(flags & NGX_HTTP_UPSTREAM_CREATE)) { if (ngx_parse_url(cf->pool, u) != NGX_OK) { if (u->err) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%s in upstream \"%V\"", u->err, &u->url); } return NULL; } } umcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_upstream_module); uscfp = umcf->upstreams.elts; for (i = 0; i < umcf->upstreams.nelts; i++) { if (uscfp[i]->host.len != u->host.len || ngx_strncasecmp(uscfp[i]->host.data, u->host.data, u->host.len) != 0) { continue; } if ((flags & NGX_HTTP_UPSTREAM_CREATE) && (uscfp[i]->flags & NGX_HTTP_UPSTREAM_CREATE)) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "duplicate upstream \"%V\"", &u->host); return NULL; } if ((uscfp[i]->flags & NGX_HTTP_UPSTREAM_CREATE) && !u->no_port) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "upstream \"%V\" may not have port %d", &u->host, u->port); return NULL; } if ((flags & NGX_HTTP_UPSTREAM_CREATE) && !uscfp[i]->no_port) { ngx_log_error(NGX_LOG_WARN, cf->log, 0, "upstream \"%V\" may not have port %d in %s:%ui", &u->host, uscfp[i]->port, uscfp[i]->file_name, uscfp[i]->line); return NULL; } if (uscfp[i]->port && u->port && uscfp[i]->port != u->port) { continue; } if (uscfp[i]->default_port && u->default_port && uscfp[i]->default_port != u->default_port) { continue; } if (flags & NGX_HTTP_UPSTREAM_CREATE) { uscfp[i]->flags = flags; } return uscfp[i]; } uscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_srv_conf_t)); if (uscf == NULL) { return NULL; } uscf->flags = flags; uscf->host = u->host; uscf->file_name = cf->conf_file->file.name.data; uscf->line = cf->conf_file->line; uscf->port = u->port; uscf->default_port = u->default_port; uscf->no_port = u->no_port; if (u->naddrs == 1) { uscf->servers = ngx_array_create(cf->pool, 1, sizeof(ngx_http_upstream_server_t)); if (uscf->servers == NULL) { return NULL; } us = ngx_array_push(uscf->servers); if (us == NULL) { return NULL; } ngx_memzero(us, sizeof(ngx_http_upstream_server_t)); us->addrs = u->addrs; us->naddrs = 1; } uscfp = ngx_array_push(&umcf->upstreams); if (uscfp == NULL) { return NULL; } *uscfp = uscf; return uscf; } char * ngx_http_upstream_bind_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { char *p = conf; ngx_int_t rc; ngx_str_t *value; ngx_http_complex_value_t cv; ngx_http_upstream_local_t **plocal, *local; ngx_http_compile_complex_value_t ccv; plocal = (ngx_http_upstream_local_t **) (p + cmd->offset); if (*plocal != NGX_CONF_UNSET_PTR) { return "is duplicate"; } value = cf->args->elts; if (ngx_strcmp(value[1].data, "off") == 0) { *plocal = NULL; return NGX_CONF_OK; } ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = &cv; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } local = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_local_t)); if (local == NULL) { return NGX_CONF_ERROR; } *plocal = local; if (cv.lengths) { local->value = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); if (local->value == NULL) { return NGX_CONF_ERROR; } *local->value = cv; return NGX_CONF_OK; } local->addr = ngx_palloc(cf->pool, sizeof(ngx_addr_t)); if (local->addr == NULL) { return NGX_CONF_ERROR; } rc = ngx_parse_addr(cf->pool, local->addr, value[1].data, value[1].len); switch (rc) { case NGX_OK: local->addr->name = value[1]; return NGX_CONF_OK; case NGX_DECLINED: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid address \"%V\"", &value[1]); /* fall through */ default: return NGX_CONF_ERROR; } } static ngx_addr_t * ngx_http_upstream_get_local(ngx_http_request_t *r, ngx_http_upstream_local_t *local) { ngx_int_t rc; ngx_str_t val; ngx_addr_t *addr; if (local == NULL) { return NULL; } if (local->value == NULL) { return local->addr; } if (ngx_http_complex_value(r, local->value, &val) != NGX_OK) { return NULL; } if (val.len == 0) { return NULL; } addr = ngx_palloc(r->pool, sizeof(ngx_addr_t)); if (addr == NULL) { return NULL; } rc = ngx_parse_addr(r->pool, addr, val.data, val.len); switch (rc) { case NGX_OK: addr->name = val; return addr; case NGX_DECLINED: ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid local address \"%V\"", &val); /* fall through */ default: return NULL; } } char * ngx_http_upstream_param_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { char *p = conf; ngx_str_t *value; ngx_array_t **a; ngx_http_upstream_param_t *param; a = (ngx_array_t **) (p + cmd->offset); if (*a == NULL) { *a = ngx_array_create(cf->pool, 4, sizeof(ngx_http_upstream_param_t)); if (*a == NULL) { return NGX_CONF_ERROR; } } param = ngx_array_push(*a); if (param == NULL) { return NGX_CONF_ERROR; } value = cf->args->elts; param->key = value[1]; param->value = value[2]; param->skip_empty = 0; if (cf->args->nelts == 4) { if (ngx_strcmp(value[3].data, "if_not_empty") != 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[3]); return NGX_CONF_ERROR; } param->skip_empty = 1; } return NGX_CONF_OK; } ngx_int_t ngx_http_upstream_hide_headers_hash(ngx_conf_t *cf, ngx_http_upstream_conf_t *conf, ngx_http_upstream_conf_t *prev, ngx_str_t *default_hide_headers, ngx_hash_init_t *hash) { ngx_str_t *h; ngx_uint_t i, j; ngx_array_t hide_headers; ngx_hash_key_t *hk; if (conf->hide_headers == NGX_CONF_UNSET_PTR && conf->pass_headers == NGX_CONF_UNSET_PTR) { conf->hide_headers = prev->hide_headers; conf->pass_headers = prev->pass_headers; conf->hide_headers_hash = prev->hide_headers_hash; if (conf->hide_headers_hash.buckets #if (NGX_HTTP_CACHE) && ((conf->cache == NULL) == (prev->cache == NULL)) #endif ) { return NGX_OK; } } else { if (conf->hide_headers == NGX_CONF_UNSET_PTR) { conf->hide_headers = prev->hide_headers; } if (conf->pass_headers == NGX_CONF_UNSET_PTR) { conf->pass_headers = prev->pass_headers; } } if (ngx_array_init(&hide_headers, cf->temp_pool, 4, sizeof(ngx_hash_key_t)) != NGX_OK) { return NGX_ERROR; } for (h = default_hide_headers; h->len; h++) { hk = ngx_array_push(&hide_headers); if (hk == NULL) { return NGX_ERROR; } hk->key = *h; hk->key_hash = ngx_hash_key_lc(h->data, h->len); hk->value = (void *) 1; } if (conf->hide_headers != NGX_CONF_UNSET_PTR) { h = conf->hide_headers->elts; for (i = 0; i < conf->hide_headers->nelts; i++) { hk = hide_headers.elts; for (j = 0; j < hide_headers.nelts; j++) { if (ngx_strcasecmp(h[i].data, hk[j].key.data) == 0) { goto exist; } } hk = ngx_array_push(&hide_headers); if (hk == NULL) { return NGX_ERROR; } hk->key = h[i]; hk->key_hash = ngx_hash_key_lc(h[i].data, h[i].len); hk->value = (void *) 1; exist: continue; } } if (conf->pass_headers != NGX_CONF_UNSET_PTR) { h = conf->pass_headers->elts; hk = hide_headers.elts; for (i = 0; i < conf->pass_headers->nelts; i++) { for (j = 0; j < hide_headers.nelts; j++) { if (hk[j].key.data == NULL) { continue; } if (ngx_strcasecmp(h[i].data, hk[j].key.data) == 0) { hk[j].key.data = NULL; break; } } } } hash->hash = &conf->hide_headers_hash; hash->key = ngx_hash_key_lc; hash->pool = cf->pool; hash->temp_pool = NULL; return ngx_hash_init(hash, hide_headers.elts, hide_headers.nelts); } static void * ngx_http_upstream_create_main_conf(ngx_conf_t *cf) { ngx_http_upstream_main_conf_t *umcf; umcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_main_conf_t)); if (umcf == NULL) { return NULL; } if (ngx_array_init(&umcf->upstreams, cf->pool, 4, sizeof(ngx_http_upstream_srv_conf_t *)) != NGX_OK) { return NULL; } return umcf; } static char * ngx_http_upstream_init_main_conf(ngx_conf_t *cf, void *conf) { ngx_http_upstream_main_conf_t *umcf = conf; ngx_uint_t i; ngx_array_t headers_in; ngx_hash_key_t *hk; ngx_hash_init_t hash; ngx_http_upstream_init_pt init; ngx_http_upstream_header_t *header; ngx_http_upstream_srv_conf_t **uscfp; uscfp = umcf->upstreams.elts; for (i = 0; i < umcf->upstreams.nelts; i++) { init = uscfp[i]->peer.init_upstream ? uscfp[i]->peer.init_upstream: ngx_http_upstream_init_round_robin; if (init(cf, uscfp[i]) != NGX_OK) { return NGX_CONF_ERROR; } } /* upstream_headers_in_hash */ if (ngx_array_init(&headers_in, cf->temp_pool, 32, sizeof(ngx_hash_key_t)) != NGX_OK) { return NGX_CONF_ERROR; } for (header = ngx_http_upstream_headers_in; header->name.len; header++) { hk = ngx_array_push(&headers_in); if (hk == NULL) { return NGX_CONF_ERROR; } hk->key = header->name; hk->key_hash = ngx_hash_key_lc(header->name.data, header->name.len); hk->value = header; } hash.hash = &umcf->headers_in_hash; hash.key = ngx_hash_key_lc; hash.max_size = 512; hash.bucket_size = ngx_align(64, ngx_cacheline_size); hash.name = "upstream_headers_in_hash"; hash.pool = cf->pool; hash.temp_pool = NULL; if (ngx_hash_init(&hash, headers_in.elts, headers_in.nelts) != NGX_OK) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } nginx-1.4.6/src/http/ngx_http_upstream.h000644 001751 001751 00000031300 12305336446 021530 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_404 0x00000100 #define NGX_HTTP_UPSTREAM_FT_UPDATING 0x00000200 #define NGX_HTTP_UPSTREAM_FT_BUSY_LOCK 0x00000400 #define NGX_HTTP_UPSTREAM_FT_MAX_WAITING 0x00000800 #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_404) #define NGX_HTTP_UPSTREAM_INVALID_HEADER 40 #define NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT 0x00000002 #define NGX_HTTP_UPSTREAM_IGN_XA_EXPIRES 0x00000004 #define NGX_HTTP_UPSTREAM_IGN_EXPIRES 0x00000008 #define NGX_HTTP_UPSTREAM_IGN_CACHE_CONTROL 0x00000010 #define NGX_HTTP_UPSTREAM_IGN_SET_COOKIE 0x00000020 #define NGX_HTTP_UPSTREAM_IGN_XA_LIMIT_RATE 0x00000040 #define NGX_HTTP_UPSTREAM_IGN_XA_BUFFERING 0x00000080 #define NGX_HTTP_UPSTREAM_IGN_XA_CHARSET 0x00000100 typedef struct { ngx_msec_t bl_time; ngx_uint_t bl_state; ngx_uint_t status; time_t response_sec; ngx_uint_t response_msec; off_t response_length; ngx_str_t *peer; } ngx_http_upstream_state_t; typedef struct { ngx_hash_t headers_in_hash; ngx_array_t upstreams; /* ngx_http_upstream_srv_conf_t */ } ngx_http_upstream_main_conf_t; typedef struct ngx_http_upstream_srv_conf_s ngx_http_upstream_srv_conf_t; typedef ngx_int_t (*ngx_http_upstream_init_pt)(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us); typedef ngx_int_t (*ngx_http_upstream_init_peer_pt)(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us); typedef struct { ngx_http_upstream_init_pt init_upstream; ngx_http_upstream_init_peer_pt init; void *data; } ngx_http_upstream_peer_t; typedef struct { ngx_addr_t *addrs; ngx_uint_t naddrs; ngx_uint_t weight; ngx_uint_t max_fails; time_t fail_timeout; unsigned down:1; unsigned backup:1; } ngx_http_upstream_server_t; #define NGX_HTTP_UPSTREAM_CREATE 0x0001 #define NGX_HTTP_UPSTREAM_WEIGHT 0x0002 #define NGX_HTTP_UPSTREAM_MAX_FAILS 0x0004 #define NGX_HTTP_UPSTREAM_FAIL_TIMEOUT 0x0008 #define NGX_HTTP_UPSTREAM_DOWN 0x0010 #define NGX_HTTP_UPSTREAM_BACKUP 0x0020 struct ngx_http_upstream_srv_conf_s { ngx_http_upstream_peer_t peer; void **srv_conf; ngx_array_t *servers; /* ngx_http_upstream_server_t */ ngx_uint_t flags; ngx_str_t host; u_char *file_name; ngx_uint_t line; in_port_t port; in_port_t default_port; ngx_uint_t no_port; /* unsigned no_port:1 */ }; typedef struct { ngx_addr_t *addr; ngx_http_complex_value_t *value; } ngx_http_upstream_local_t; typedef struct { ngx_http_upstream_srv_conf_t *upstream; ngx_msec_t connect_timeout; ngx_msec_t send_timeout; ngx_msec_t read_timeout; ngx_msec_t timeout; size_t send_lowat; size_t buffer_size; 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_flag_t 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_path_t *temp_path; ngx_hash_t hide_headers_hash; ngx_array_t *hide_headers; ngx_array_t *pass_headers; ngx_http_upstream_local_t *local; #if (NGX_HTTP_CACHE) ngx_shm_zone_t *cache; ngx_uint_t cache_min_uses; ngx_uint_t cache_use_stale; ngx_uint_t cache_methods; ngx_flag_t cache_lock; ngx_msec_t cache_lock_timeout; ngx_array_t *cache_valid; ngx_array_t *cache_bypass; ngx_array_t *no_cache; #endif ngx_array_t *store_lengths; ngx_array_t *store_values; signed store:2; unsigned intercept_404:1; unsigned change_buffering:1; #if (NGX_HTTP_SSL) ngx_ssl_t *ssl; ngx_flag_t ssl_session_reuse; #endif ngx_str_t module; } ngx_http_upstream_conf_t; typedef struct { ngx_str_t name; ngx_http_header_handler_pt handler; ngx_uint_t offset; ngx_http_header_handler_pt copy_handler; ngx_uint_t conf; ngx_uint_t redirect; /* unsigned redirect:1; */ } ngx_http_upstream_header_t; typedef struct { ngx_list_t headers; ngx_uint_t status_n; ngx_str_t status_line; ngx_table_elt_t *status; ngx_table_elt_t *date; ngx_table_elt_t *server; ngx_table_elt_t *connection; ngx_table_elt_t *expires; ngx_table_elt_t *etag; ngx_table_elt_t *x_accel_expires; ngx_table_elt_t *x_accel_redirect; ngx_table_elt_t *x_accel_limit_rate; ngx_table_elt_t *content_type; ngx_table_elt_t *content_length; ngx_table_elt_t *last_modified; ngx_table_elt_t *location; ngx_table_elt_t *accept_ranges; ngx_table_elt_t *www_authenticate; ngx_table_elt_t *transfer_encoding; #if (NGX_HTTP_GZIP) ngx_table_elt_t *content_encoding; #endif off_t content_length_n; ngx_array_t cache_control; unsigned connection_close:1; unsigned chunked:1; } ngx_http_upstream_headers_in_t; typedef struct { ngx_str_t host; in_port_t port; ngx_uint_t no_port; /* unsigned no_port:1 */ ngx_uint_t naddrs; in_addr_t *addrs; struct sockaddr *sockaddr; socklen_t socklen; ngx_resolver_ctx_t *ctx; } ngx_http_upstream_resolved_t; typedef void (*ngx_http_upstream_handler_pt)(ngx_http_request_t *r, ngx_http_upstream_t *u); struct ngx_http_upstream_s { ngx_http_upstream_handler_pt read_event_handler; ngx_http_upstream_handler_pt write_event_handler; ngx_peer_connection_t peer; ngx_event_pipe_t *pipe; ngx_chain_t *request_bufs; ngx_output_chain_ctx_t output; ngx_chain_writer_ctx_t writer; ngx_http_upstream_conf_t *conf; ngx_http_upstream_headers_in_t headers_in; ngx_http_upstream_resolved_t *resolved; ngx_buf_t from_client; ngx_buf_t buffer; off_t length; ngx_chain_t *out_bufs; ngx_chain_t *busy_bufs; ngx_chain_t *free_bufs; ngx_int_t (*input_filter_init)(void *data); ngx_int_t (*input_filter)(void *data, ssize_t bytes); void *input_filter_ctx; #if (NGX_HTTP_CACHE) ngx_int_t (*create_key)(ngx_http_request_t *r); #endif ngx_int_t (*create_request)(ngx_http_request_t *r); ngx_int_t (*reinit_request)(ngx_http_request_t *r); ngx_int_t (*process_header)(ngx_http_request_t *r); void (*abort_request)(ngx_http_request_t *r); void (*finalize_request)(ngx_http_request_t *r, ngx_int_t rc); ngx_int_t (*rewrite_redirect)(ngx_http_request_t *r, ngx_table_elt_t *h, size_t prefix); ngx_int_t (*rewrite_cookie)(ngx_http_request_t *r, ngx_table_elt_t *h); ngx_msec_t timeout; ngx_http_upstream_state_t *state; ngx_str_t method; ngx_str_t schema; ngx_str_t uri; ngx_http_cleanup_pt *cleanup; unsigned store:1; unsigned cacheable:1; unsigned accel:1; unsigned ssl:1; #if (NGX_HTTP_CACHE) unsigned cache_status:3; #endif unsigned buffering:1; unsigned keepalive:1; unsigned upgrade:1; unsigned request_sent:1; unsigned 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_header_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); ngx_int_t ngx_http_upstream_create(ngx_http_request_t *r); void ngx_http_upstream_init(ngx_http_request_t *r); ngx_http_upstream_srv_conf_t *ngx_http_upstream_add(ngx_conf_t *cf, ngx_url_t *u, ngx_uint_t flags); char *ngx_http_upstream_bind_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); char *ngx_http_upstream_param_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); ngx_int_t ngx_http_upstream_hide_headers_hash(ngx_conf_t *cf, ngx_http_upstream_conf_t *conf, ngx_http_upstream_conf_t *prev, ngx_str_t *default_hide_headers, ngx_hash_init_t *hash); #define ngx_http_conf_upstream_srv_conf(uscf, module) \ uscf->srv_conf[module.ctx_index] extern ngx_module_t ngx_http_upstream_module; extern ngx_conf_bitmask_t ngx_http_upstream_cache_method_mask[]; extern ngx_conf_bitmask_t ngx_http_upstream_ignore_headers_masks[]; #endif /* _NGX_HTTP_UPSTREAM_H_INCLUDED_ */ nginx-1.4.6/src/http/ngx_http_upstream_round_robin.c000644 001751 001751 00000042254 12305336446 024135 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ #include #include #include static ngx_http_upstream_rr_peer_t *ngx_http_upstream_get_peer( ngx_http_upstream_rr_peer_data_t *rrp); #if (NGX_HTTP_SSL) static ngx_int_t ngx_http_upstream_empty_set_session(ngx_peer_connection_t *pc, void *data); static void ngx_http_upstream_empty_save_session(ngx_peer_connection_t *pc, void *data); #endif ngx_int_t ngx_http_upstream_init_round_robin(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us) { ngx_url_t u; ngx_uint_t i, j, n, w; ngx_http_upstream_server_t *server; ngx_http_upstream_rr_peers_t *peers, *backup; us->peer.init = ngx_http_upstream_init_round_robin_peer; if (us->servers) { server = us->servers->elts; n = 0; w = 0; for (i = 0; i < us->servers->nelts; i++) { if (server[i].backup) { continue; } n += server[i].naddrs; w += server[i].naddrs * server[i].weight; } if (n == 0) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no servers in upstream \"%V\" in %s:%ui", &us->host, us->file_name, us->line); return NGX_ERROR; } peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t) + sizeof(ngx_http_upstream_rr_peer_t) * (n - 1)); if (peers == NULL) { return NGX_ERROR; } peers->single = (n == 1); peers->number = n; peers->weighted = (w != n); peers->total_weight = w; peers->name = &us->host; n = 0; for (i = 0; i < us->servers->nelts; i++) { for (j = 0; j < server[i].naddrs; j++) { if (server[i].backup) { continue; } peers->peer[n].sockaddr = server[i].addrs[j].sockaddr; peers->peer[n].socklen = server[i].addrs[j].socklen; peers->peer[n].name = server[i].addrs[j].name; peers->peer[n].max_fails = server[i].max_fails; peers->peer[n].fail_timeout = server[i].fail_timeout; peers->peer[n].down = server[i].down; peers->peer[n].weight = server[i].weight; peers->peer[n].effective_weight = server[i].weight; peers->peer[n].current_weight = 0; n++; } } us->peer.data = peers; /* backup servers */ n = 0; w = 0; for (i = 0; i < us->servers->nelts; i++) { if (!server[i].backup) { continue; } n += server[i].naddrs; w += server[i].naddrs * server[i].weight; } if (n == 0) { return NGX_OK; } backup = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t) + sizeof(ngx_http_upstream_rr_peer_t) * (n - 1)); if (backup == NULL) { return NGX_ERROR; } peers->single = 0; backup->single = 0; backup->number = n; backup->weighted = (w != n); backup->total_weight = w; backup->name = &us->host; n = 0; for (i = 0; i < us->servers->nelts; i++) { for (j = 0; j < server[i].naddrs; j++) { if (!server[i].backup) { continue; } backup->peer[n].sockaddr = server[i].addrs[j].sockaddr; backup->peer[n].socklen = server[i].addrs[j].socklen; backup->peer[n].name = server[i].addrs[j].name; backup->peer[n].weight = server[i].weight; backup->peer[n].effective_weight = server[i].weight; backup->peer[n].current_weight = 0; backup->peer[n].max_fails = server[i].max_fails; backup->peer[n].fail_timeout = server[i].fail_timeout; backup->peer[n].down = server[i].down; 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) + sizeof(ngx_http_upstream_rr_peer_t) * (n - 1)); if (peers == NULL) { return NGX_ERROR; } peers->single = (n == 1); peers->number = n; peers->weighted = 0; peers->total_weight = n; peers->name = &us->host; for (i = 0; i < u.naddrs; i++) { peers->peer[i].sockaddr = u.addrs[i].sockaddr; peers->peer[i].socklen = u.addrs[i].socklen; peers->peer[i].name = u.addrs[i].name; peers->peer[i].weight = 1; peers->peer[i].effective_weight = 1; peers->peer[i].current_weight = 0; peers->peer[i].max_fails = 1; peers->peer[i].fail_timeout = 10; } 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 = 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 = rrp->peers->number; #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; ngx_uint_t i, n; struct sockaddr_in *sin; 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) + sizeof(ngx_http_upstream_rr_peer_t) * (ur->naddrs - 1)); if (peers == NULL) { return NGX_ERROR; } peers->single = (ur->naddrs == 1); peers->number = ur->naddrs; peers->name = &ur->host; if (ur->sockaddr) { peers->peer[0].sockaddr = ur->sockaddr; peers->peer[0].socklen = ur->socklen; peers->peer[0].name = ur->host; peers->peer[0].weight = 1; peers->peer[0].effective_weight = 1; peers->peer[0].current_weight = 0; peers->peer[0].max_fails = 1; peers->peer[0].fail_timeout = 10; } else { for (i = 0; i < ur->naddrs; i++) { len = NGX_INET_ADDRSTRLEN + sizeof(":65536") - 1; p = ngx_pnalloc(r->pool, len); if (p == NULL) { return NGX_ERROR; } len = ngx_inet_ntop(AF_INET, &ur->addrs[i], p, NGX_INET_ADDRSTRLEN); len = ngx_sprintf(&p[len], ":%d", ur->port) - p; sin = ngx_pcalloc(r->pool, sizeof(struct sockaddr_in)); if (sin == NULL) { return NGX_ERROR; } sin->sin_family = AF_INET; sin->sin_port = htons(ur->port); sin->sin_addr.s_addr = ur->addrs[i]; peers->peer[i].sockaddr = (struct sockaddr *) sin; peers->peer[i].socklen = sizeof(struct sockaddr_in); peers->peer[i].name.len = len; peers->peer[i].name.data = p; peers->peer[i].weight = 1; peers->peer[i].effective_weight = 1; peers->peer[i].current_weight = 0; peers->peer[i].max_fails = 1; peers->peer[i].fail_timeout = 10; } } rrp->peers = peers; rrp->current = 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 = rrp->peers->number; #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); /* ngx_lock_mutex(rrp->peers->mutex); */ pc->cached = 0; pc->connection = NULL; if (rrp->peers->single) { peer = &rrp->peers->peer[0]; if (peer->down) { goto failed; } } 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: %ui %i", rrp->current, peer->current_weight); } pc->sockaddr = peer->sockaddr; pc->socklen = peer->socklen; pc->name = &peer->name; /* ngx_unlock_mutex(rrp->peers->mutex); */ if (pc->tries == 1 && rrp->peers->next) { pc->tries += rrp->peers->next->number; } return NGX_OK; failed: peers = rrp->peers; if (peers->next) { /* ngx_unlock_mutex(peers->mutex); */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0, "backup servers"); rrp->peers = peers->next; pc->tries = rrp->peers->number; n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1)) / (8 * sizeof(uintptr_t)); for (i = 0; i < n; i++) { rrp->tried[i] = 0; } rc = ngx_http_upstream_get_round_robin_peer(pc, rrp); if (rc != NGX_BUSY) { return rc; } /* ngx_lock_mutex(peers->mutex); */ } /* all peers failed, mark them as live for quick recovery */ for (i = 0; i < peers->number; i++) { peers->peer[i].fails = 0; } /* ngx_unlock_mutex(peers->mutex); */ 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; ngx_http_upstream_rr_peer_t *peer, *best; now = ngx_time(); best = NULL; total = 0; for (i = 0; i < rrp->peers->number; i++) { n = i / (8 * sizeof(uintptr_t)); m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t)); if (rrp->tried[n] & m) { continue; } peer = &rrp->peers->peer[i]; if (peer->down) { continue; } if (peer->max_fails && peer->fails >= peer->max_fails && now - peer->checked <= peer->fail_timeout) { continue; } peer->current_weight += peer->effective_weight; total += peer->effective_weight; if (peer->effective_weight < peer->weight) { peer->effective_weight++; } if (best == NULL || peer->current_weight > best->current_weight) { best = peer; } } if (best == NULL) { return NULL; } i = best - &rrp->peers->peer[0]; rrp->current = i; n = i / (8 * sizeof(uintptr_t)); m = (uintptr_t) 1 << i % (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 */ if (rrp->peers->single) { pc->tries = 0; return; } peer = &rrp->peers->peer[rrp->current]; if (state & NGX_PEER_FAILED) { now = ngx_time(); /* ngx_lock_mutex(rrp->peers->mutex); */ peer->fails++; peer->accessed = now; peer->checked = now; if (peer->max_fails) { peer->effective_weight -= peer->weight / peer->max_fails; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0, "free rr peer failed: %ui %i", rrp->current, peer->effective_weight); if (peer->effective_weight < 0) { peer->effective_weight = 0; } /* ngx_unlock_mutex(rrp->peers->mutex); */ } else { /* mark peer live if check passed */ if (peer->accessed < peer->checked) { peer->fails = 0; } } if (pc->tries) { pc->tries--; } /* ngx_unlock_mutex(rrp->peers->mutex); */ } #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; peer = &rrp->peers->peer[rrp->current]; /* TODO: threads only mutex */ /* ngx_lock_mutex(rrp->peers->mutex); */ ssl_session = peer->ssl_session; rc = ngx_ssl_set_session(pc->connection, ssl_session); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0, "set session: %p:%d", ssl_session, ssl_session ? ssl_session->references : 0); /* ngx_unlock_mutex(rrp->peers->mutex); */ 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; ssl_session = ngx_ssl_get_session(pc->connection); if (ssl_session == NULL) { return; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0, "save session: %p:%d", ssl_session, ssl_session->references); peer = &rrp->peers->peer[rrp->current]; /* TODO: threads only mutex */ /* ngx_lock_mutex(rrp->peers->mutex); */ old_ssl_session = peer->ssl_session; peer->ssl_session = ssl_session; /* ngx_unlock_mutex(rrp->peers->mutex); */ if (old_ssl_session) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0, "old session: %p:%d", old_ssl_session, old_ssl_session->references); /* 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.4.6/src/http/ngx_http_upstream_round_robin.h000644 001751 001751 00000004725 12305336446 024143 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 { struct sockaddr *sockaddr; socklen_t socklen; ngx_str_t name; ngx_int_t current_weight; ngx_int_t effective_weight; ngx_int_t weight; ngx_uint_t fails; time_t accessed; time_t checked; ngx_uint_t max_fails; time_t fail_timeout; ngx_uint_t down; /* unsigned down:1; */ #if (NGX_HTTP_SSL) ngx_ssl_session_t *ssl_session; /* local to a process */ #endif } ngx_http_upstream_rr_peer_t; 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; /* ngx_mutex_t *mutex; */ ngx_uint_t total_weight; unsigned single:1; unsigned weighted:1; ngx_str_t *name; ngx_http_upstream_rr_peers_t *next; ngx_http_upstream_rr_peer_t peer[1]; }; typedef struct { ngx_http_upstream_rr_peers_t *peers; ngx_uint_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.4.6/src/http/ngx_http_variables.c000644 001751 001751 00000167734 12305336446 021660 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ #include #include #include #include static ngx_int_t ngx_http_variable_request(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static void ngx_http_variable_request_set(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_request_get_size(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static void ngx_http_variable_request_set_size(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_header(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_cookies(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_headers(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_headers_internal(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data, u_char sep); static ngx_int_t ngx_http_variable_unknown_header_in(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_unknown_header_out(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_request_line(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_cookie(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_argument(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); #if (NGX_HAVE_TCP_INFO) static ngx_int_t ngx_http_variable_tcpinfo(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); #endif static ngx_int_t ngx_http_variable_content_length(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_host(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_binary_remote_addr(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_remote_addr(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_remote_port(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_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 ngx_int_t ngx_http_variable_is_args(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_document_root(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_realpath_root(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_request_filename(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_server_name(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_request_method(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_remote_user(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_bytes_sent(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_body_bytes_sent(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_pipe(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_request_completion(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_request_body(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_request_body_file(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_request_length(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_request_time(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_status(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_sent_content_type(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_sent_content_length(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_sent_location(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_sent_last_modified(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_sent_connection(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_sent_keep_alive(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_sent_transfer_encoding(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_connection(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_connection_requests(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_nginx_version(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_hostname(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_pid(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_msec(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_time_iso8601(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_time_local(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); /* * TODO: * Apache CGI: AUTH_TYPE, PATH_INFO (null), PATH_TRANSLATED * REMOTE_HOST (null), REMOTE_IDENT (null), * SERVER_SOFTWARE * * Apache SSI: DOCUMENT_NAME, LAST_MODIFIED, USER_NAME (file owner) */ /* * the $http_host, $http_user_agent, $http_referer, and $http_via * variables may be handled by generic * ngx_http_variable_unknown_header_in(), but for performance reasons * they are handled using dedicated entries */ static ngx_http_variable_t ngx_http_core_variables[] = { { ngx_string("http_host"), NULL, ngx_http_variable_header, offsetof(ngx_http_request_t, headers_in.host), 0, 0 }, { ngx_string("http_user_agent"), NULL, ngx_http_variable_header, offsetof(ngx_http_request_t, headers_in.user_agent), 0, 0 }, { ngx_string("http_referer"), NULL, ngx_http_variable_header, offsetof(ngx_http_request_t, headers_in.referer), 0, 0 }, #if (NGX_HTTP_GZIP) { ngx_string("http_via"), NULL, ngx_http_variable_header, offsetof(ngx_http_request_t, headers_in.via), 0, 0 }, #endif #if (NGX_HTTP_X_FORWARDED_FOR) { ngx_string("http_x_forwarded_for"), NULL, ngx_http_variable_headers, offsetof(ngx_http_request_t, headers_in.x_forwarded_for), 0, 0 }, #endif { ngx_string("http_cookie"), NULL, ngx_http_variable_cookies, offsetof(ngx_http_request_t, headers_in.cookies), 0, 0 }, { ngx_string("content_length"), NULL, ngx_http_variable_content_length, 0, 0, 0 }, { ngx_string("content_type"), NULL, ngx_http_variable_header, offsetof(ngx_http_request_t, headers_in.content_type), 0, 0 }, { ngx_string("host"), NULL, ngx_http_variable_host, 0, 0, 0 }, { ngx_string("binary_remote_addr"), NULL, ngx_http_variable_binary_remote_addr, 0, 0, 0 }, { ngx_string("remote_addr"), NULL, ngx_http_variable_remote_addr, 0, 0, 0 }, { ngx_string("remote_port"), NULL, ngx_http_variable_remote_port, 0, 0, 0 }, { ngx_string("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_request_set, ngx_http_variable_request, offsetof(ngx_http_request_t, args), NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("is_args"), NULL, ngx_http_variable_is_args, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("request_filename"), NULL, ngx_http_variable_request_filename, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("server_name"), NULL, ngx_http_variable_server_name, 0, 0, 0 }, { ngx_string("request_method"), NULL, ngx_http_variable_request_method, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("remote_user"), NULL, ngx_http_variable_remote_user, 0, 0, 0 }, { ngx_string("bytes_sent"), NULL, ngx_http_variable_bytes_sent, 0, 0, 0 }, { ngx_string("body_bytes_sent"), NULL, ngx_http_variable_body_bytes_sent, 0, 0, 0 }, { ngx_string("pipe"), NULL, ngx_http_variable_pipe, 0, 0, 0 }, { ngx_string("request_completion"), NULL, ngx_http_variable_request_completion, 0, 0, 0 }, { ngx_string("request_body"), NULL, ngx_http_variable_request_body, 0, 0, 0 }, { ngx_string("request_body_file"), NULL, ngx_http_variable_request_body_file, 0, 0, 0 }, { ngx_string("request_length"), NULL, ngx_http_variable_request_length, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("request_time"), NULL, ngx_http_variable_request_time, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("status"), NULL, ngx_http_variable_status, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("sent_http_content_type"), NULL, ngx_http_variable_sent_content_type, 0, 0, 0 }, { ngx_string("sent_http_content_length"), NULL, ngx_http_variable_sent_content_length, 0, 0, 0 }, { ngx_string("sent_http_location"), NULL, ngx_http_variable_sent_location, 0, 0, 0 }, { ngx_string("sent_http_last_modified"), NULL, ngx_http_variable_sent_last_modified, 0, 0, 0 }, { ngx_string("sent_http_connection"), NULL, ngx_http_variable_sent_connection, 0, 0, 0 }, { ngx_string("sent_http_keep_alive"), NULL, ngx_http_variable_sent_keep_alive, 0, 0, 0 }, { ngx_string("sent_http_transfer_encoding"), NULL, ngx_http_variable_sent_transfer_encoding, 0, 0, 0 }, { ngx_string("sent_http_cache_control"), NULL, ngx_http_variable_headers, offsetof(ngx_http_request_t, headers_out.cache_control), 0, 0 }, { ngx_string("limit_rate"), ngx_http_variable_request_set_size, ngx_http_variable_request_get_size, offsetof(ngx_http_request_t, limit_rate), NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("connection"), NULL, ngx_http_variable_connection, 0, 0, 0 }, { ngx_string("connection_requests"), NULL, ngx_http_variable_connection_requests, 0, 0, 0 }, { ngx_string("nginx_version"), NULL, ngx_http_variable_nginx_version, 0, 0, 0 }, { ngx_string("hostname"), NULL, ngx_http_variable_hostname, 0, 0, 0 }, { ngx_string("pid"), NULL, ngx_http_variable_pid, 0, 0, 0 }, { ngx_string("msec"), NULL, ngx_http_variable_msec, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("time_iso8601"), NULL, ngx_http_variable_time_iso8601, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("time_local"), NULL, ngx_http_variable_time_local, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, #if (NGX_HAVE_TCP_INFO) { ngx_string("tcpinfo_rtt"), NULL, ngx_http_variable_tcpinfo, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("tcpinfo_rttvar"), NULL, ngx_http_variable_tcpinfo, 1, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("tcpinfo_snd_cwnd"), NULL, ngx_http_variable_tcpinfo, 2, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_string("tcpinfo_rcv_space"), NULL, ngx_http_variable_tcpinfo, 3, NGX_HTTP_VAR_NOCACHEABLE, 0 }, #endif { ngx_null_string, NULL, NULL, 0, 0, 0 } }; ngx_http_variable_value_t ngx_http_variable_null_value = ngx_http_variable(""); ngx_http_variable_value_t ngx_http_variable_true_value = ngx_http_variable("1"); ngx_http_variable_t * ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags) { ngx_int_t rc; ngx_uint_t i; ngx_hash_key_t *key; ngx_http_variable_t *v; ngx_http_core_main_conf_t *cmcf; if (name->len == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid variable name \"$\""); return NULL; } cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); key = cmcf->variables_keys->keys.elts; for (i = 0; i < cmcf->variables_keys->keys.nelts; i++) { if (name->len != key[i].key.len || ngx_strncasecmp(name->data, key[i].key.data, name->len) != 0) { continue; } v = key[i].value; if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the duplicate \"%V\" variable", name); return NULL; } return v; } v = ngx_palloc(cf->pool, sizeof(ngx_http_variable_t)); if (v == NULL) { return NULL; } v->name.len = name->len; v->name.data = ngx_pnalloc(cf->pool, name->len); if (v->name.data == NULL) { return NULL; } ngx_strlow(v->name.data, name->data, name->len); v->set_handler = NULL; v->get_handler = NULL; v->data = 0; v->flags = flags; v->index = 0; rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v, 0); if (rc == NGX_ERROR) { return NULL; } if (rc == NGX_BUSY) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "conflicting variable name \"%V\"", name); return NULL; } return v; } ngx_int_t ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name) { ngx_uint_t i; ngx_http_variable_t *v; ngx_http_core_main_conf_t *cmcf; if (name->len == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid variable name \"$\""); return NGX_ERROR; } cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); v = cmcf->variables.elts; if (v == NULL) { if (ngx_array_init(&cmcf->variables, cf->pool, 4, sizeof(ngx_http_variable_t)) != NGX_OK) { return NGX_ERROR; } } else { for (i = 0; i < cmcf->variables.nelts; i++) { if (name->len != v[i].name.len || ngx_strncasecmp(name->data, v[i].name.data, name->len) != 0) { continue; } return i; } } v = ngx_array_push(&cmcf->variables); if (v == NULL) { return NGX_ERROR; } v->name.len = name->len; v->name.data = ngx_pnalloc(cf->pool, name->len); if (v->name.data == NULL) { return NGX_ERROR; } ngx_strlow(v->name.data, name->data, name->len); v->set_handler = NULL; v->get_handler = NULL; v->data = 0; v->flags = 0; v->index = cmcf->variables.nelts - 1; return v->index; } ngx_http_variable_value_t * ngx_http_get_indexed_variable(ngx_http_request_t *r, ngx_uint_t index) { ngx_http_variable_t *v; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); if (cmcf->variables.nelts <= index) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "unknown variable index: %d", index); return NULL; } if (r->variables[index].not_found || r->variables[index].valid) { return &r->variables[index]; } v = cmcf->variables.elts; if (v[index].get_handler(r, &r->variables[index], v[index].data) == NGX_OK) { if (v[index].flags & NGX_HTTP_VAR_NOCACHEABLE) { r->variables[index].no_cacheable = 1; } return &r->variables[index]; } r->variables[index].valid = 0; r->variables[index].not_found = 1; return NULL; } ngx_http_variable_value_t * ngx_http_get_flushed_variable(ngx_http_request_t *r, ngx_uint_t index) { ngx_http_variable_value_t *v; v = &r->variables[index]; if (v->valid || v->not_found) { if (!v->no_cacheable) { return v; } v->valid = 0; v->not_found = 0; } return ngx_http_get_indexed_variable(r, index); } ngx_http_variable_value_t * ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key) { ngx_http_variable_t *v; ngx_http_variable_value_t *vv; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); v = ngx_hash_find(&cmcf->variables_hash, key, name->data, name->len); if (v) { if (v->flags & NGX_HTTP_VAR_INDEXED) { return ngx_http_get_flushed_variable(r, v->index); } else { vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t)); if (vv && v->get_handler(r, vv, v->data) == NGX_OK) { return vv; } return NULL; } } vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t)); if (vv == NULL) { return NULL; } if (ngx_strncmp(name->data, "http_", 5) == 0) { if (ngx_http_variable_unknown_header_in(r, vv, (uintptr_t) name) == NGX_OK) { return vv; } return NULL; } if (ngx_strncmp(name->data, "sent_http_", 10) == 0) { if (ngx_http_variable_unknown_header_out(r, vv, (uintptr_t) name) == NGX_OK) { return vv; } return NULL; } if (ngx_strncmp(name->data, "upstream_http_", 14) == 0) { if (ngx_http_upstream_header_variable(r, vv, (uintptr_t) name) == NGX_OK) { return vv; } return NULL; } if (ngx_strncmp(name->data, "cookie_", 7) == 0) { if (ngx_http_variable_cookie(r, vv, (uintptr_t) name) == NGX_OK) { return vv; } return NULL; } if (ngx_strncmp(name->data, "arg_", 4) == 0) { if (ngx_http_variable_argument(r, vv, (uintptr_t) name) == NGX_OK) { return vv; } return NULL; } vv->not_found = 1; return vv; } static ngx_int_t ngx_http_variable_request(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_str_t *s; s = (ngx_str_t *) ((char *) r + data); if (s->data) { v->len = s->len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = s->data; } else { v->not_found = 1; } return NGX_OK; } 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; } static ngx_int_t ngx_http_variable_request_get_size(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { size_t *sp; sp = (size_t *) ((char *) r + data); v->data = ngx_pnalloc(r->pool, NGX_SIZE_T_LEN); if (v->data == NULL) { return NGX_ERROR; } v->len = ngx_sprintf(v->data, "%uz", *sp) - v->data; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; return NGX_OK; } static void ngx_http_variable_request_set_size(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ssize_t s, *sp; ngx_str_t val; val.len = v->len; val.data = v->data; s = ngx_parse_size(&val); if (s == NGX_ERROR) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid size \"%V\"", &val); return; } sp = (ssize_t *) ((char *) r + data); *sp = s; return; } static ngx_int_t ngx_http_variable_header(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_table_elt_t *h; h = *(ngx_table_elt_t **) ((char *) r + data); if (h) { v->len = h->value.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = h->value.data; } else { v->not_found = 1; } return NGX_OK; } static ngx_int_t ngx_http_variable_cookies(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { return ngx_http_variable_headers_internal(r, v, data, ';'); } static ngx_int_t ngx_http_variable_headers(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { return ngx_http_variable_headers_internal(r, v, data, ','); } static ngx_int_t ngx_http_variable_headers_internal(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data, u_char sep) { size_t len; u_char *p, *end; ngx_uint_t i, n; ngx_array_t *a; ngx_table_elt_t **h; a = (ngx_array_t *) ((char *) r + data); n = a->nelts; h = a->elts; len = 0; for (i = 0; i < n; i++) { if (h[i]->hash == 0) { continue; } len += h[i]->value.len + 2; } if (len == 0) { v->not_found = 1; return NGX_OK; } len -= 2; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; if (n == 1) { v->len = (*h)->value.len; v->data = (*h)->value.data; return NGX_OK; } p = ngx_pnalloc(r->pool, len); if (p == NULL) { return NGX_ERROR; } v->len = len; v->data = p; end = p + len; for (i = 0; /* void */ ; i++) { if (h[i]->hash == 0) { continue; } p = ngx_copy(p, h[i]->value.data, h[i]->value.len); if (p == end) { break; } *p++ = sep; *p++ = ' '; } return NGX_OK; } static ngx_int_t ngx_http_variable_unknown_header_in(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { return ngx_http_variable_unknown_header(v, (ngx_str_t *) data, &r->headers_in.headers.part, sizeof("http_") - 1); } static ngx_int_t ngx_http_variable_unknown_header_out(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { return ngx_http_variable_unknown_header(v, (ngx_str_t *) data, &r->headers_out.headers.part, sizeof("sent_http_") - 1); } ngx_int_t ngx_http_variable_unknown_header(ngx_http_variable_value_t *v, ngx_str_t *var, ngx_list_part_t *part, size_t prefix) { u_char ch; ngx_uint_t i, n; ngx_table_elt_t *header; header = part->elts; for (i = 0; /* void */ ; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (header[i].hash == 0) { continue; } for (n = 0; n + prefix < var->len && n < header[i].key.len; n++) { ch = header[i].key.data[n]; if (ch >= 'A' && ch <= 'Z') { ch |= 0x20; } else if (ch == '-') { ch = '_'; } if (var->data[n + prefix] != ch) { break; } } if (n + prefix == var->len && n == header[i].key.len) { v->len = header[i].value.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = header[i].value.data; return NGX_OK; } } v->not_found = 1; return NGX_OK; } static ngx_int_t ngx_http_variable_request_line(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p, *s; s = r->request_line.data; if (s == NULL) { s = r->request_start; if (s == NULL) { v->not_found = 1; return NGX_OK; } for (p = s; p < r->header_in->last; p++) { if (*p == CR || *p == LF) { break; } } r->request_line.len = p - s; r->request_line.data = s; } v->len = r->request_line.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = s; return NGX_OK; } static ngx_int_t ngx_http_variable_cookie(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_str_t *name = (ngx_str_t *) data; ngx_str_t cookie, s; s.len = name->len - (sizeof("cookie_") - 1); s.data = name->data + sizeof("cookie_") - 1; if (ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &s, &cookie) == NGX_DECLINED) { v->not_found = 1; return NGX_OK; } v->len = cookie.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = cookie.data; return NGX_OK; } static ngx_int_t ngx_http_variable_argument(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_str_t *name = (ngx_str_t *) data; u_char *arg; size_t len; ngx_str_t value; len = name->len - (sizeof("arg_") - 1); arg = name->data + sizeof("arg_") - 1; if (ngx_http_arg(r, arg, len, &value) != NGX_OK) { v->not_found = 1; return NGX_OK; } v->data = value.data; v->len = value.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; return NGX_OK; } #if (NGX_HAVE_TCP_INFO) static ngx_int_t ngx_http_variable_tcpinfo(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { struct tcp_info ti; socklen_t len; uint32_t value; len = sizeof(struct tcp_info); if (getsockopt(r->connection->fd, IPPROTO_TCP, TCP_INFO, &ti, &len) == -1) { v->not_found = 1; return NGX_OK; } v->data = ngx_pnalloc(r->pool, NGX_INT32_LEN); if (v->data == NULL) { return NGX_ERROR; } switch (data) { case 0: value = ti.tcpi_rtt; break; case 1: value = ti.tcpi_rttvar; break; case 2: value = ti.tcpi_snd_cwnd; break; case 3: value = ti.tcpi_rcv_space; break; /* suppress warning */ default: value = 0; break; } v->len = ngx_sprintf(v->data, "%uD", value) - v->data; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; return NGX_OK; } #endif static ngx_int_t ngx_http_variable_content_length(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; if (r->headers_in.content_length) { v->len = r->headers_in.content_length->value.len; v->data = r->headers_in.content_length->value.data; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; } else if (r->headers_in.content_length_n >= 0) { p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN); if (p == NULL) { return NGX_ERROR; } v->len = ngx_sprintf(p, "%O", r->headers_in.content_length_n) - p; v->data = p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; } else { v->not_found = 1; } return NGX_OK; } static ngx_int_t ngx_http_variable_host(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_http_core_srv_conf_t *cscf; if (r->headers_in.server.len) { v->len = r->headers_in.server.len; v->data = r->headers_in.server.data; } else { cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); v->len = cscf->server_name.len; v->data = cscf->server_name.data; } v->valid = 1; v->no_cacheable = 0; v->not_found = 0; return NGX_OK; } static ngx_int_t ngx_http_variable_binary_remote_addr(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { struct sockaddr_in *sin; #if (NGX_HAVE_INET6) struct sockaddr_in6 *sin6; #endif switch (r->connection->sockaddr->sa_family) { #if (NGX_HAVE_INET6) case AF_INET6: sin6 = (struct sockaddr_in6 *) r->connection->sockaddr; v->len = sizeof(struct in6_addr); v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = sin6->sin6_addr.s6_addr; break; #endif default: /* AF_INET */ sin = (struct sockaddr_in *) r->connection->sockaddr; v->len = sizeof(in_addr_t); v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = (u_char *) &sin->sin_addr; break; } return NGX_OK; } static ngx_int_t ngx_http_variable_remote_addr(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { v->len = r->connection->addr_text.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = r->connection->addr_text.data; return NGX_OK; } static ngx_int_t ngx_http_variable_remote_port(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_uint_t port; struct sockaddr_in *sin; #if (NGX_HAVE_INET6) struct sockaddr_in6 *sin6; #endif v->len = 0; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = ngx_pnalloc(r->pool, sizeof("65535") - 1); if (v->data == NULL) { return NGX_ERROR; } switch (r->connection->sockaddr->sa_family) { #if (NGX_HAVE_INET6) case AF_INET6: sin6 = (struct sockaddr_in6 *) r->connection->sockaddr; port = ntohs(sin6->sin6_port); break; #endif default: /* AF_INET */ sin = (struct sockaddr_in *) r->connection->sockaddr; port = ntohs(sin->sin_port); break; } if (port > 0 && port < 65536) { v->len = ngx_sprintf(v->data, "%ui", port) - v->data; } return NGX_OK; } static ngx_int_t ngx_http_variable_server_addr(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_str_t s; u_char addr[NGX_SOCKADDR_STRLEN]; s.len = NGX_SOCKADDR_STRLEN; s.data = addr; if (ngx_connection_local_sockaddr(r->connection, &s, 0) != NGX_OK) { return NGX_ERROR; } s.data = ngx_pnalloc(r->pool, s.len); if (s.data == NULL) { return NGX_ERROR; } ngx_memcpy(s.data, addr, s.len); v->len = s.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = s.data; return NGX_OK; } static ngx_int_t ngx_http_variable_server_port(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_uint_t port; struct sockaddr_in *sin; #if (NGX_HAVE_INET6) struct sockaddr_in6 *sin6; #endif v->len = 0; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; if (ngx_connection_local_sockaddr(r->connection, NULL, 0) != NGX_OK) { return NGX_ERROR; } v->data = ngx_pnalloc(r->pool, sizeof("65535") - 1); if (v->data == NULL) { return NGX_ERROR; } switch (r->connection->local_sockaddr->sa_family) { #if (NGX_HAVE_INET6) case AF_INET6: sin6 = (struct sockaddr_in6 *) r->connection->local_sockaddr; port = ntohs(sin6->sin6_port); break; #endif default: /* AF_INET */ sin = (struct sockaddr_in *) r->connection->local_sockaddr; port = ntohs(sin->sin_port); break; } if (port > 0 && port < 65536) { v->len = ngx_sprintf(v->data, "%ui", port) - v->data; } return NGX_OK; } static ngx_int_t ngx_http_variable_scheme(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { #if (NGX_HTTP_SSL) if (r->connection->ssl) { v->len = sizeof("https") - 1; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = (u_char *) "https"; return NGX_OK; } #endif v->len = sizeof("http") - 1; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = (u_char *) "http"; return NGX_OK; } static ngx_int_t ngx_http_variable_https(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { #if (NGX_HTTP_SSL) if (r->connection->ssl) { v->len = sizeof("on") - 1; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = (u_char *) "on"; return NGX_OK; } #endif *v = ngx_http_variable_null_value; return NGX_OK; } static ngx_int_t ngx_http_variable_is_args(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { v->valid = 1; v->no_cacheable = 0; v->not_found = 0; if (r->args.len == 0) { v->len = 0; v->data = NULL; return NGX_OK; } v->len = 1; v->data = (u_char *) "?"; return NGX_OK; } static ngx_int_t ngx_http_variable_document_root(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_str_t path; ngx_http_core_loc_conf_t *clcf; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (clcf->root_lengths == NULL) { v->len = clcf->root.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = clcf->root.data; } else { if (ngx_http_script_run(r, &path, clcf->root_lengths->elts, 0, clcf->root_values->elts) == NULL) { return NGX_ERROR; } if (ngx_conf_full_name((ngx_cycle_t *) ngx_cycle, &path, 0) != 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_conf_full_name((ngx_cycle_t *) ngx_cycle, &path, 0) != NGX_OK) { return NGX_ERROR; } } #if (NGX_HAVE_MAX_PATH) real = buffer; #else real = NULL; #endif real = ngx_realpath(path.data, real); if (real == NULL) { ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno, ngx_realpath_n " \"%s\" failed", path.data); return NGX_ERROR; } len = ngx_strlen(real); v->data = ngx_pnalloc(r->pool, len); if (v->data == NULL) { #if !(NGX_HAVE_MAX_PATH) ngx_free(real); #endif return NGX_ERROR; } v->len = len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; ngx_memcpy(v->data, real, len); #if !(NGX_HAVE_MAX_PATH) ngx_free(real); #endif return NGX_OK; } static ngx_int_t ngx_http_variable_request_filename(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { size_t root; ngx_str_t path; if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) { return NGX_ERROR; } /* ngx_http_map_uri_to_path() allocates memory for terminating '\0' */ v->len = path.len - 1; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = path.data; return NGX_OK; } static ngx_int_t ngx_http_variable_server_name(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_http_core_srv_conf_t *cscf; cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); v->len = cscf->server_name.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = cscf->server_name.data; return NGX_OK; } static ngx_int_t ngx_http_variable_request_method(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { if (r->main->method_name.data) { v->len = r->main->method_name.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = r->main->method_name.data; } else { v->not_found = 1; } return NGX_OK; } static ngx_int_t ngx_http_variable_remote_user(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_int_t rc; rc = ngx_http_auth_basic_user(r); if (rc == NGX_DECLINED) { v->not_found = 1; return NGX_OK; } if (rc == NGX_ERROR) { return NGX_ERROR; } v->len = r->headers_in.user.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = r->headers_in.user.data; return NGX_OK; } static ngx_int_t ngx_http_variable_bytes_sent(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN); if (p == NULL) { return NGX_ERROR; } v->len = ngx_sprintf(p, "%O", r->connection->sent) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } static ngx_int_t ngx_http_variable_body_bytes_sent(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { off_t sent; u_char *p; sent = r->connection->sent - r->header_size; if (sent < 0) { sent = 0; } p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN); if (p == NULL) { return NGX_ERROR; } v->len = ngx_sprintf(p, "%O", sent) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } static ngx_int_t ngx_http_variable_pipe(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { v->data = (u_char *) (r->pipeline ? "p" : "."); v->len = 1; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; return NGX_OK; } static ngx_int_t ngx_http_variable_status(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_uint_t status; v->data = ngx_pnalloc(r->pool, NGX_INT_T_LEN); if (v->data == NULL) { return NGX_ERROR; } if (r->err_status) { status = r->err_status; } else if (r->headers_out.status) { status = r->headers_out.status; } else if (r->http_version == NGX_HTTP_VERSION_9) { status = 9; } else { status = 0; } v->len = ngx_sprintf(v->data, "%03ui", status) - v->data; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; return NGX_OK; } static ngx_int_t ngx_http_variable_sent_content_type(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { if (r->headers_out.content_type.len) { v->len = r->headers_out.content_type.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = r->headers_out.content_type.data; } else { v->not_found = 1; } return NGX_OK; } static ngx_int_t ngx_http_variable_sent_content_length(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; if (r->headers_out.content_length) { v->len = r->headers_out.content_length->value.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = r->headers_out.content_length->value.data; return NGX_OK; } if (r->headers_out.content_length_n >= 0) { p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN); if (p == NULL) { return NGX_ERROR; } v->len = ngx_sprintf(p, "%O", r->headers_out.content_length_n) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } v->not_found = 1; return NGX_OK; } static ngx_int_t ngx_http_variable_sent_location(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_str_t name; if (r->headers_out.location) { v->len = r->headers_out.location->value.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = r->headers_out.location->value.data; return NGX_OK; } ngx_str_set(&name, "sent_http_location"); return ngx_http_variable_unknown_header(v, &name, &r->headers_out.headers.part, sizeof("sent_http_") - 1); } static ngx_int_t ngx_http_variable_sent_last_modified(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; if (r->headers_out.last_modified) { v->len = r->headers_out.last_modified->value.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = r->headers_out.last_modified->value.data; return NGX_OK; } if (r->headers_out.last_modified_time >= 0) { p = ngx_pnalloc(r->pool, sizeof("Last-Modified: Mon, 28 Sep 1970 06:00:00 GMT") - 1); if (p == NULL) { return NGX_ERROR; } v->len = ngx_http_time(p, r->headers_out.last_modified_time) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } v->not_found = 1; return NGX_OK; } static ngx_int_t ngx_http_variable_sent_connection(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { size_t len; char *p; if (r->headers_out.status == NGX_HTTP_SWITCHING_PROTOCOLS) { len = sizeof("upgrade") - 1; p = "upgrade"; } else if (r->keepalive) { len = sizeof("keep-alive") - 1; p = "keep-alive"; } else { len = sizeof("close") - 1; p = "close"; } v->len = len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = (u_char *) p; return NGX_OK; } static ngx_int_t ngx_http_variable_sent_keep_alive(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; ngx_http_core_loc_conf_t *clcf; if (r->keepalive) { clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (clcf->keepalive_header) { p = ngx_pnalloc(r->pool, sizeof("timeout=") - 1 + NGX_TIME_T_LEN); if (p == NULL) { return NGX_ERROR; } v->len = ngx_sprintf(p, "timeout=%T", clcf->keepalive_header) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } } v->not_found = 1; return NGX_OK; } static ngx_int_t ngx_http_variable_sent_transfer_encoding(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { if (r->chunked) { v->len = sizeof("chunked") - 1; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = (u_char *) "chunked"; } else { v->not_found = 1; } return NGX_OK; } static ngx_int_t ngx_http_variable_request_completion(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { if (r->request_complete) { v->len = 2; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = (u_char *) "OK"; return NGX_OK; } v->len = 0; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = (u_char *) ""; return NGX_OK; } static ngx_int_t ngx_http_variable_request_body(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; size_t len; ngx_buf_t *buf; ngx_chain_t *cl; if (r->request_body == NULL || r->request_body->bufs == NULL || r->request_body->temp_file) { v->not_found = 1; return NGX_OK; } cl = r->request_body->bufs; buf = cl->buf; if (cl->next == NULL) { v->len = buf->last - buf->pos; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = buf->pos; return NGX_OK; } len = buf->last - buf->pos; cl = cl->next; for ( /* void */ ; cl; cl = cl->next) { buf = cl->buf; len += buf->last - buf->pos; } p = ngx_pnalloc(r->pool, len); if (p == NULL) { return NGX_ERROR; } v->data = p; cl = r->request_body->bufs; for ( /* void */ ; cl; cl = cl->next) { buf = cl->buf; p = ngx_cpymem(p, buf->pos, buf->last - buf->pos); } v->len = len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; return NGX_OK; } static ngx_int_t ngx_http_variable_request_body_file(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { if (r->request_body == NULL || r->request_body->temp_file == NULL) { v->not_found = 1; return NGX_OK; } v->len = r->request_body->temp_file->file.name.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = r->request_body->temp_file->file.name.data; return NGX_OK; } static ngx_int_t ngx_http_variable_request_length(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN); if (p == NULL) { return NGX_ERROR; } v->len = ngx_sprintf(p, "%O", r->request_length) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } static ngx_int_t ngx_http_variable_request_time(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; ngx_time_t *tp; ngx_msec_int_t ms; p = ngx_pnalloc(r->pool, NGX_TIME_T_LEN + 4); if (p == NULL) { return NGX_ERROR; } tp = ngx_timeofday(); ms = (ngx_msec_int_t) ((tp->sec - r->start_sec) * 1000 + (tp->msec - r->start_msec)); ms = ngx_max(ms, 0); v->len = ngx_sprintf(p, "%T.%03M", (time_t) ms / 1000, ms % 1000) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } static ngx_int_t ngx_http_variable_connection(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; p = ngx_pnalloc(r->pool, NGX_ATOMIC_T_LEN); if (p == NULL) { return NGX_ERROR; } v->len = ngx_sprintf(p, "%uA", r->connection->number) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } static ngx_int_t ngx_http_variable_connection_requests(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; p = ngx_pnalloc(r->pool, NGX_INT_T_LEN); if (p == NULL) { return NGX_ERROR; } v->len = ngx_sprintf(p, "%ui", r->connection->requests) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } static ngx_int_t ngx_http_variable_nginx_version(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { v->len = sizeof(NGINX_VERSION) - 1; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = (u_char *) NGINX_VERSION; return NGX_OK; } static ngx_int_t ngx_http_variable_hostname(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { v->len = ngx_cycle->hostname.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = ngx_cycle->hostname.data; return NGX_OK; } static ngx_int_t ngx_http_variable_pid(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; p = ngx_pnalloc(r->pool, NGX_INT64_LEN); if (p == NULL) { return NGX_ERROR; } v->len = ngx_sprintf(p, "%P", ngx_pid) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } static ngx_int_t ngx_http_variable_msec(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; ngx_time_t *tp; p = ngx_pnalloc(r->pool, NGX_TIME_T_LEN + 4); if (p == NULL) { return NGX_ERROR; } tp = ngx_timeofday(); v->len = ngx_sprintf(p, "%T.%03M", tp->sec, tp->msec) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } static ngx_int_t ngx_http_variable_time_iso8601(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; p = ngx_pnalloc(r->pool, ngx_cached_http_log_iso8601.len); if (p == NULL) { return NGX_ERROR; } ngx_memcpy(p, ngx_cached_http_log_iso8601.data, ngx_cached_http_log_iso8601.len); v->len = ngx_cached_http_log_iso8601.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } static ngx_int_t ngx_http_variable_time_local(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; p = ngx_pnalloc(r->pool, ngx_cached_http_log_time.len); if (p == NULL) { return NGX_ERROR; } ngx_memcpy(p, ngx_cached_http_log_time.data, ngx_cached_http_log_time.len); v->len = ngx_cached_http_log_time.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; } void * ngx_http_map_find(ngx_http_request_t *r, ngx_http_map_t *map, ngx_str_t *match) { void *value; u_char *low; size_t len; ngx_uint_t key; len = match->len; if (len) { low = ngx_pnalloc(r->pool, len); if (low == NULL) { return NULL; } } else { low = NULL; } key = ngx_hash_strlow(low, match->data, len); value = ngx_hash_find_combined(&map->hash, key, low, len); if (value) { return value; } #if (NGX_PCRE) if (len && map->nregex) { ngx_int_t n; ngx_uint_t i; ngx_http_map_regex_t *reg; reg = map->regex; for (i = 0; i < map->nregex; i++) { n = ngx_http_regex_exec(r, reg[i].regex, match); if (n == NGX_OK) { return reg[i].value; } if (n == NGX_DECLINED) { continue; } /* NGX_ERROR */ return NULL; } } #endif return NULL; } #if (NGX_PCRE) static ngx_int_t ngx_http_variable_not_found(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { v->not_found = 1; return NGX_OK; } ngx_http_regex_t * ngx_http_regex_compile(ngx_conf_t *cf, ngx_regex_compile_t *rc) { u_char *p; size_t size; ngx_str_t name; ngx_uint_t i, n; ngx_http_variable_t *v; ngx_http_regex_t *re; ngx_http_regex_variable_t *rv; ngx_http_core_main_conf_t *cmcf; rc->pool = cf->pool; if (ngx_regex_compile(rc) != NGX_OK) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc->err); return NULL; } re = ngx_pcalloc(cf->pool, sizeof(ngx_http_regex_t)); if (re == NULL) { return NULL; } re->regex = rc->regex; re->ncaptures = rc->captures; 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; re->name = rc->pattern; size = rc->name_size; p = rc->names; for (i = 0; i < n; i++) { rv[i].capture = 2 * ((p[0] << 8) + p[1]); name.data = &p[2]; name.len = ngx_strlen(name.data); v = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE); if (v == NULL) { return NULL; } rv[i].index = ngx_http_get_variable_index(cf, &name); if (rv[i].index == NGX_ERROR) { return NULL; } v->get_handler = ngx_http_variable_not_found; p += size; } return re; } ngx_int_t ngx_http_regex_exec(ngx_http_request_t *r, ngx_http_regex_t *re, ngx_str_t *s) { ngx_int_t rc, index; ngx_uint_t i, n, len; ngx_http_variable_value_t *vv; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); if (re->ncaptures) { len = cmcf->ncaptures; if (r->captures == NULL) { r->captures = ngx_palloc(r->pool, len * sizeof(int)); if (r->captures == NULL) { return NGX_ERROR; } } } else { len = 0; } rc = ngx_regex_exec(re->regex, s, r->captures, len); if (rc == NGX_REGEX_NO_MATCHED) { return NGX_DECLINED; } if (rc < 0) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, ngx_regex_exec_n " failed: %i on \"%V\" using \"%V\"", rc, s, &re->name); return NGX_ERROR; } for (i = 0; i < re->nvariables; i++) { n = re->variables[i].capture; index = re->variables[i].index; vv = &r->variables[index]; vv->len = r->captures[n + 1] - r->captures[n]; vv->valid = 1; vv->no_cacheable = 0; vv->not_found = 0; vv->data = &s->data[r->captures[n]]; #if (NGX_DEBUG) { ngx_http_variable_t *v; v = cmcf->variables.elts; ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http regex set $%V to \"%*s\"", &v[index].name, vv->len, vv->data); } #endif } r->ncaptures = rc * 2; r->captures_data = s->data; return NGX_OK; } #endif ngx_int_t ngx_http_variables_add_core_vars(ngx_conf_t *cf) { ngx_int_t rc; ngx_http_variable_t *cv, *v; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); cmcf->variables_keys = ngx_pcalloc(cf->temp_pool, sizeof(ngx_hash_keys_arrays_t)); if (cmcf->variables_keys == NULL) { return NGX_ERROR; } cmcf->variables_keys->pool = cf->pool; cmcf->variables_keys->temp_pool = cf->pool; if (ngx_hash_keys_array_init(cmcf->variables_keys, NGX_HASH_SMALL) != NGX_OK) { return NGX_ERROR; } for (cv = ngx_http_core_variables; cv->name.len; cv++) { v = ngx_palloc(cf->pool, sizeof(ngx_http_variable_t)); if (v == NULL) { return NGX_ERROR; } *v = *cv; rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v, NGX_HASH_READONLY_KEY); if (rc == NGX_OK) { continue; } if (rc == NGX_BUSY) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "conflicting variable name \"%V\"", &v->name); } return NGX_ERROR; } return NGX_OK; } ngx_int_t ngx_http_variables_init_vars(ngx_conf_t *cf) { ngx_uint_t i, n; ngx_hash_key_t *key; ngx_hash_init_t hash; ngx_http_variable_t *v, *av; ngx_http_core_main_conf_t *cmcf; /* set the handlers for the indexed http variables */ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); v = cmcf->variables.elts; key = cmcf->variables_keys->keys.elts; for (i = 0; i < cmcf->variables.nelts; i++) { for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) { av = key[n].value; if (av->get_handler && 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; goto next; } } if (ngx_strncmp(v[i].name.data, "http_", 5) == 0) { v[i].get_handler = ngx_http_variable_unknown_header_in; v[i].data = (uintptr_t) &v[i].name; continue; } if (ngx_strncmp(v[i].name.data, "sent_http_", 10) == 0) { v[i].get_handler = ngx_http_variable_unknown_header_out; v[i].data = (uintptr_t) &v[i].name; continue; } if (ngx_strncmp(v[i].name.data, "upstream_http_", 14) == 0) { v[i].get_handler = ngx_http_upstream_header_variable; v[i].data = (uintptr_t) &v[i].name; v[i].flags = NGX_HTTP_VAR_NOCACHEABLE; continue; } if (ngx_strncmp(v[i].name.data, "cookie_", 7) == 0) { v[i].get_handler = ngx_http_variable_cookie; v[i].data = (uintptr_t) &v[i].name; continue; } if (ngx_strncmp(v[i].name.data, "arg_", 4) == 0) { v[i].get_handler = ngx_http_variable_argument; v[i].data = (uintptr_t) &v[i].name; v[i].flags = NGX_HTTP_VAR_NOCACHEABLE; continue; } ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "unknown \"%V\" variable", &v[i].name); return NGX_ERROR; next: continue; } for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) { av = key[n].value; if (av->flags & NGX_HTTP_VAR_NOHASH) { key[n].key.data = NULL; } } hash.hash = &cmcf->variables_hash; hash.key = ngx_hash_key; hash.max_size = cmcf->variables_hash_max_size; hash.bucket_size = cmcf->variables_hash_bucket_size; hash.name = "variables_hash"; hash.pool = cf->pool; hash.temp_pool = NULL; if (ngx_hash_init(&hash, cmcf->variables_keys->keys.elts, cmcf->variables_keys->keys.nelts) != NGX_OK) { return NGX_ERROR; } cmcf->variables_keys = NULL; return NGX_OK; } nginx-1.4.6/src/http/ngx_http_variables.h000644 001751 001751 00000005740 12305336446 021651 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 struct ngx_http_variable_s { ngx_str_t name; /* must be first to build the hash */ ngx_http_set_variable_pt set_handler; ngx_http_get_variable_pt get_handler; uintptr_t data; ngx_uint_t flags; ngx_uint_t index; }; ngx_http_variable_t *ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags); ngx_int_t ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name); ngx_http_variable_value_t *ngx_http_get_indexed_variable(ngx_http_request_t *r, ngx_uint_t index); ngx_http_variable_value_t *ngx_http_get_flushed_variable(ngx_http_request_t *r, ngx_uint_t index); ngx_http_variable_value_t *ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key); ngx_int_t ngx_http_variable_unknown_header(ngx_http_variable_value_t *v, ngx_str_t *var, ngx_list_part_t *part, size_t prefix); #if (NGX_PCRE) typedef struct { ngx_uint_t capture; ngx_int_t index; } ngx_http_regex_variable_t; typedef struct { ngx_regex_t *regex; ngx_uint_t ncaptures; ngx_http_regex_variable_t *variables; ngx_uint_t nvariables; ngx_str_t name; } ngx_http_regex_t; typedef struct { ngx_http_regex_t *regex; void *value; } ngx_http_map_regex_t; ngx_http_regex_t *ngx_http_regex_compile(ngx_conf_t *cf, ngx_regex_compile_t *rc); ngx_int_t ngx_http_regex_exec(ngx_http_request_t *r, ngx_http_regex_t *re, ngx_str_t *s); #endif typedef struct { ngx_hash_combined_t hash; #if (NGX_PCRE) ngx_http_map_regex_t *regex; ngx_uint_t nregex; #endif } ngx_http_map_t; void *ngx_http_map_find(ngx_http_request_t *r, ngx_http_map_t *map, ngx_str_t *match); ngx_int_t ngx_http_variables_add_core_vars(ngx_conf_t *cf); ngx_int_t ngx_http_variables_init_vars(ngx_conf_t *cf); extern ngx_http_variable_value_t ngx_http_variable_null_value; extern ngx_http_variable_value_t ngx_http_variable_true_value; #endif /* _NGX_HTTP_VARIABLES_H_INCLUDED_ */ nginx-1.4.6/src/http/ngx_http_write_filter_module.c000644 001751 001751 00000020727 12305336446 023742 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; 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; 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: %z", cl->buf->temporary, cl->buf->in_file, cl->buf->start, cl->buf->pos, cl->buf->last - cl->buf->pos, cl->buf->file_pos, cl->buf->file_last - cl->buf->file_pos); #if 1 if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) { ngx_log_error(NGX_LOG_ALERT, c->log, 0, "zero size buf in writer " "t:%d r:%d f:%d %p %p-%p %p %O-%O", cl->buf->temporary, cl->buf->recycled, cl->buf->in_file, cl->buf->start, cl->buf->pos, cl->buf->last, cl->buf->file, cl->buf->file_pos, cl->buf->file_last); ngx_debug_point(); return NGX_ERROR; } #endif size += ngx_buf_size(cl->buf); if (cl->buf->flush || cl->buf->recycled) { flush = 1; } if (cl->buf->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: %z", cl->buf->temporary, cl->buf->in_file, cl->buf->start, cl->buf->pos, cl->buf->last - cl->buf->pos, cl->buf->file_pos, cl->buf->file_last - cl->buf->file_pos); #if 1 if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) { ngx_log_error(NGX_LOG_ALERT, c->log, 0, "zero size buf in writer " "t:%d r:%d f:%d %p %p-%p %p %O-%O", cl->buf->temporary, cl->buf->recycled, cl->buf->in_file, cl->buf->start, cl->buf->pos, cl->buf->last, cl->buf->file, cl->buf->file_pos, cl->buf->file_last); ngx_debug_point(); return NGX_ERROR; } #endif size += ngx_buf_size(cl->buf); if (cl->buf->flush || cl->buf->recycled) { flush = 1; } if (cl->buf->last_buf) { last = 1; } } *ll = NULL; ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, "http write filter: l:%d f:%d 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)) { if (last || flush) { 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) { limit = (off_t) r->limit_rate * (ngx_time() - r->start_sec + 1) - (c->sent - clcf->limit_rate_after); if (limit <= 0) { c->write->delayed = 1; ngx_add_timer(c->write, (ngx_msec_t) (- limit * 1000 / r->limit_rate + 1)); 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 (clcf->limit_rate_after) { sent -= clcf->limit_rate_after; if (sent < 0) { sent = 0; } nsent -= clcf->limit_rate_after; if (nsent < 0) { nsent = 0; } } delay = (ngx_msec_t) ((nsent - sent) * 1000 / r->limit_rate); if (delay > 0) { limit = 0; c->write->delayed = 1; ngx_add_timer(c->write, delay); } } if (limit && c->write->ready && c->sent - sent >= limit - (off_t) (2 * ngx_pagesize)) { c->write->delayed = 1; ngx_add_timer(c->write, 1); } for (cl = r->out; cl && cl != chain; /* void */) { ln = cl; cl = cl->next; ngx_free_chain(r->pool, ln); } r->out = chain; if (chain) { c->buffered |= NGX_HTTP_WRITE_BUFFERED; return NGX_AGAIN; } c->buffered &= ~NGX_HTTP_WRITE_BUFFERED; if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) { return NGX_AGAIN; } return NGX_OK; } static ngx_int_t ngx_http_write_filter_init(ngx_conf_t *cf) { ngx_http_top_body_filter = ngx_http_write_filter; return NGX_OK; } nginx-1.4.6/src/http/modules/ngx_http_addition_filter_module.c000644 001751 001751 00000015420 12305336445 026044 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_clear_etag(r); return ngx_http_next_header_filter(r); } static ngx_int_t ngx_http_addition_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_int_t rc; ngx_uint_t last; ngx_chain_t *cl; ngx_http_request_t *sr; ngx_http_addition_ctx_t *ctx; ngx_http_addition_conf_t *conf; if (in == NULL || r->header_only) { return ngx_http_next_body_filter(r, in); } ctx = ngx_http_get_module_ctx(r, ngx_http_addition_filter_module); if (ctx == NULL) { return ngx_http_next_body_filter(r, in); } conf = ngx_http_get_module_loc_conf(r, ngx_http_addition_filter_module); if (!ctx->before_body_sent) { ctx->before_body_sent = 1; if (conf->before_body.len) { if (ngx_http_subrequest(r, &conf->before_body, NULL, &sr, NULL, 0) != NGX_OK) { return NGX_ERROR; } } } if (conf->after_body.len == 0) { ngx_http_set_ctx(r, NULL, ngx_http_addition_filter_module); return ngx_http_next_body_filter(r, in); } last = 0; for (cl = in; cl; cl = cl->next) { if (cl->buf->last_buf) { cl->buf->last_buf = 0; cl->buf->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.4.6/src/http/modules/ngx_http_access_module.c000644 001751 001751 00000023465 12305336445 024155 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 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 } 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 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); } #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 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 ngx_memzero(&cidr, sizeof(ngx_cidr_t)); value = cf->args->elts; all = (value[1].len == 3 && ngx_strcmp(value[1].data, "all") == 0); if (!all) { 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]); } } switch (cidr.family) { #if (NGX_HAVE_INET6) case AF_INET6: case 0: /* 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; if (!all) { break; } /* "all" passes through */ #endif default: /* AF_INET */ 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; } 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 (NGX_HAVE_INET6) if (conf->rules == NULL && conf->rules6 == NULL) { conf->rules = prev->rules; conf->rules6 = prev->rules6; } #else if (conf->rules == NULL) { conf->rules = prev->rules; } #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.4.6/src/http/modules/ngx_http_charset_filter_module.c000644 001751 001751 00000120030 12305336445 025674 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); 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/x-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); } mcf = ngx_http_get_module_main_conf(r, ngx_http_charset_filter_module); charsets = mcf->charsets.elts; if (source_charset != charset && (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); if (source_charset != charset) { return ngx_http_charset_ctx(r, charsets, charset, source_charset); } return ngx_http_next_header_filter(r); 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->ignore_content_encoding && r->headers_out.content_encoding && r->headers_out.content_encoding->value.len) { return NGX_DECLINED; } 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.4.6/src/http/modules/ngx_http_auth_basic_module.c000644 001751 001751 00000030540 12305336445 025006 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ #include #include #include #include #define NGX_HTTP_AUTH_BUF_SIZE 2048 typedef struct { ngx_str_t passwd; } ngx_http_auth_basic_ctx_t; typedef struct { ngx_http_complex_value_t *realm; ngx_http_complex_value_t user_file; } ngx_http_auth_basic_loc_conf_t; static ngx_int_t ngx_http_auth_basic_handler(ngx_http_request_t *r); static ngx_int_t ngx_http_auth_basic_crypt_handler(ngx_http_request_t *r, ngx_http_auth_basic_ctx_t *ctx, ngx_str_t *passwd, ngx_str_t *realm); static ngx_int_t ngx_http_auth_basic_set_realm(ngx_http_request_t *r, ngx_str_t *realm); static void ngx_http_auth_basic_close(ngx_file_t *file); static void *ngx_http_auth_basic_create_loc_conf(ngx_conf_t *cf); static char *ngx_http_auth_basic_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); static ngx_int_t ngx_http_auth_basic_init(ngx_conf_t *cf); static char *ngx_http_auth_basic_user_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_command_t ngx_http_auth_basic_commands[] = { { ngx_string("auth_basic"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF |NGX_CONF_TAKE1, ngx_http_set_complex_value_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_auth_basic_loc_conf_t, realm), NULL }, { ngx_string("auth_basic_user_file"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF |NGX_CONF_TAKE1, ngx_http_auth_basic_user_file, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_auth_basic_loc_conf_t, user_file), NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_auth_basic_module_ctx = { NULL, /* preconfiguration */ ngx_http_auth_basic_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_auth_basic_create_loc_conf, /* create location configuration */ ngx_http_auth_basic_merge_loc_conf /* merge location configuration */ }; ngx_module_t ngx_http_auth_basic_module = { NGX_MODULE_V1, &ngx_http_auth_basic_module_ctx, /* module context */ ngx_http_auth_basic_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_int_t ngx_http_auth_basic_handler(ngx_http_request_t *r) { off_t offset; ssize_t n; ngx_fd_t fd; ngx_int_t rc; ngx_err_t err; ngx_str_t pwd, realm, user_file; ngx_uint_t i, level, login, left, passwd; ngx_file_t file; ngx_http_auth_basic_ctx_t *ctx; ngx_http_auth_basic_loc_conf_t *alcf; u_char buf[NGX_HTTP_AUTH_BUF_SIZE]; enum { sw_login, sw_passwd, sw_skip } state; alcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_basic_module); if (alcf->realm == NULL || alcf->user_file.value.data == NULL) { return NGX_DECLINED; } if (ngx_http_complex_value(r, alcf->realm, &realm) != NGX_OK) { return NGX_ERROR; } if (realm.len == 3 && ngx_strncmp(realm.data, "off", 3) == 0) { return NGX_DECLINED; } ctx = ngx_http_get_module_ctx(r, ngx_http_auth_basic_module); if (ctx) { return ngx_http_auth_basic_crypt_handler(r, ctx, &ctx->passwd, &realm); } rc = ngx_http_auth_basic_user(r); if (rc == NGX_DECLINED) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no user/password was provided for basic authentication"); return ngx_http_auth_basic_set_realm(r, &realm); } if (rc == NGX_ERROR) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_http_complex_value(r, &alcf->user_file, &user_file) != NGX_OK) { return NGX_ERROR; } fd = ngx_open_file(user_file.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); if (fd == NGX_INVALID_FILE) { err = ngx_errno; if (err == NGX_ENOENT) { level = NGX_LOG_ERR; rc = NGX_HTTP_FORBIDDEN; } else { level = NGX_LOG_CRIT; rc = NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_log_error(level, r->connection->log, err, ngx_open_file_n " \"%s\" failed", user_file.data); return rc; } ngx_memzero(&file, sizeof(ngx_file_t)); file.fd = fd; file.name = user_file; file.log = r->connection->log; state = sw_login; passwd = 0; login = 0; left = 0; offset = 0; for ( ;; ) { i = left; n = ngx_read_file(&file, buf + left, NGX_HTTP_AUTH_BUF_SIZE - left, offset); if (n == NGX_ERROR) { ngx_http_auth_basic_close(&file); return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (n == 0) { break; } for (i = left; i < left + n; i++) { switch (state) { case sw_login: if (login == 0) { if (buf[i] == '#' || buf[i] == CR) { state = sw_skip; break; } if (buf[i] == LF) { break; } } if (buf[i] != r->headers_in.user.data[login]) { state = sw_skip; break; } if (login == r->headers_in.user.len) { state = sw_passwd; passwd = i + 1; } login++; break; case sw_passwd: if (buf[i] == LF || buf[i] == CR || buf[i] == ':') { buf[i] = '\0'; ngx_http_auth_basic_close(&file); pwd.len = i - passwd; pwd.data = &buf[passwd]; return ngx_http_auth_basic_crypt_handler(r, NULL, &pwd, &realm); } break; case sw_skip: if (buf[i] == LF) { state = sw_login; login = 0; } break; } } if (state == sw_passwd) { left = left + n - passwd; ngx_memmove(buf, &buf[passwd], left); passwd = 0; } else { left = 0; } offset += n; } ngx_http_auth_basic_close(&file); if (state == sw_passwd) { pwd.len = i - passwd; pwd.data = ngx_pnalloc(r->pool, pwd.len + 1); if (pwd.data == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_cpystrn(pwd.data, &buf[passwd], pwd.len + 1); return ngx_http_auth_basic_crypt_handler(r, NULL, &pwd, &realm); } ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "user \"%V\" was not found in \"%V\"", &r->headers_in.user, &user_file); return ngx_http_auth_basic_set_realm(r, &realm); } static ngx_int_t ngx_http_auth_basic_crypt_handler(ngx_http_request_t *r, ngx_http_auth_basic_ctx_t *ctx, ngx_str_t *passwd, ngx_str_t *realm) { ngx_int_t rc; u_char *encrypted; rc = ngx_crypt(r->pool, r->headers_in.passwd.data, passwd->data, &encrypted); ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "rc: %d user: \"%V\" salt: \"%s\"", rc, &r->headers_in.user, passwd->data); if (rc == NGX_OK) { if (ngx_strcmp(encrypted, passwd->data) == 0) { return NGX_OK; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "encrypted: \"%s\"", encrypted); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "user \"%V\": password mismatch", &r->headers_in.user); return ngx_http_auth_basic_set_realm(r, realm); } if (rc == NGX_ERROR) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } /* rc == NGX_AGAIN */ if (ctx == NULL) { ctx = ngx_palloc(r->pool, sizeof(ngx_http_auth_basic_ctx_t)); if (ctx == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_auth_basic_module); ctx->passwd.len = passwd->len; passwd->len++; ctx->passwd.data = ngx_pstrdup(r->pool, passwd); if (ctx->passwd.data == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } } /* TODO: add mutex event */ return rc; } static ngx_int_t ngx_http_auth_basic_set_realm(ngx_http_request_t *r, ngx_str_t *realm) { size_t len; u_char *basic, *p; r->headers_out.www_authenticate = ngx_list_push(&r->headers_out.headers); if (r->headers_out.www_authenticate == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } len = sizeof("Basic realm=\"\"") - 1 + realm->len; basic = ngx_pnalloc(r->pool, len); if (basic == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } p = ngx_cpymem(basic, "Basic realm=\"", sizeof("Basic realm=\"") - 1); p = ngx_cpymem(p, realm->data, realm->len); *p = '"'; r->headers_out.www_authenticate->hash = 1; ngx_str_set(&r->headers_out.www_authenticate->key, "WWW-Authenticate"); r->headers_out.www_authenticate->value.data = basic; r->headers_out.www_authenticate->value.len = len; return NGX_HTTP_UNAUTHORIZED; } static void ngx_http_auth_basic_close(ngx_file_t *file) { if (ngx_close_file(file->fd) == NGX_FILE_ERROR) { ngx_log_error(NGX_LOG_ALERT, file->log, ngx_errno, ngx_close_file_n " \"%s\" failed", file->name.data); } } static void * ngx_http_auth_basic_create_loc_conf(ngx_conf_t *cf) { ngx_http_auth_basic_loc_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_auth_basic_loc_conf_t)); if (conf == NULL) { return NULL; } return conf; } static char * ngx_http_auth_basic_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_auth_basic_loc_conf_t *prev = parent; ngx_http_auth_basic_loc_conf_t *conf = child; if (conf->realm == NULL) { conf->realm = prev->realm; } if (conf->user_file.value.data == NULL) { conf->user_file = prev->user_file; } return NGX_CONF_OK; } static ngx_int_t ngx_http_auth_basic_init(ngx_conf_t *cf) { ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } *h = ngx_http_auth_basic_handler; return NGX_OK; } static char * ngx_http_auth_basic_user_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_auth_basic_loc_conf_t *alcf = conf; ngx_str_t *value; ngx_http_compile_complex_value_t ccv; if (alcf->user_file.value.data) { return "is duplicate"; } value = cf->args->elts; ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = &alcf->user_file; ccv.zero = 1; ccv.conf_prefix = 1; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } nginx-1.4.6/src/http/modules/ngx_http_autoindex_module.c000644 001751 001751 00000047754 12305336445 024723 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; time_t mtime; off_t size; } ngx_http_autoindex_entry_t; typedef struct { ngx_flag_t enable; ngx_flag_t localtime; ngx_flag_t exact_size; } ngx_http_autoindex_loc_conf_t; #define NGX_HTTP_AUTOINDEX_PREALLOCATE 50 #define NGX_HTTP_AUTOINDEX_NAME_LEN 50 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_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_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 u_char title[] = "" CRLF "Index of " ; static u_char header[] = "" CRLF "" CRLF "

Index of " ; static u_char tail[] = "" CRLF "" CRLF ; static ngx_int_t ngx_http_autoindex_handler(ngx_http_request_t *r) { u_char *last, *filename, scale; off_t length; size_t len, char_len, escape_html, allocated, root; ngx_tm_t tm; ngx_err_t err; ngx_buf_t *b; ngx_int_t rc, size; ngx_str_t path; ngx_dir_t dir; ngx_uint_t i, level, utf8; ngx_pool_t *pool; ngx_time_t *tp; ngx_chain_t out; ngx_array_t entries; ngx_http_autoindex_entry_t *entry; ngx_http_autoindex_loc_conf_t *alcf; static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; if (r->uri.data[r->uri.len - 1] != '/') { return NGX_DECLINED; } if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) { return NGX_DECLINED; } alcf = ngx_http_get_module_loc_conf(r, ngx_http_autoindex_module); if (!alcf->enable) { return NGX_DECLINED; } /* NGX_DIR_MASK_LEN is lesser than NGX_HTTP_AUTOINDEX_PREALLOCATE */ last = ngx_http_map_uri_to_path(r, &path, &root, NGX_HTTP_AUTOINDEX_PREALLOCATE); if (last == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } allocated = path.len; path.len = last - path.data; if (path.len > 1) { path.len--; } path.data[path.len] = '\0'; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http autoindex: \"%s\"", path.data); 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; r->headers_out.content_type_len = sizeof("text/html") - 1; ngx_str_set(&r->headers_out.content_type, "text/html"); 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] = '/'; 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; } 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->escape = 2 * ngx_escape_uri(NULL, ngx_de_name(&dir), len, NGX_ESCAPE_URI_COMPONENT); entry->escape_html = ngx_escape_html(NULL, entry->name.data, entry->name.len); if (utf8) { entry->utf_len = ngx_utf8_length(entry->name.data, entry->name.len); } else { entry->utf_len = len; } entry->dir = ngx_de_is_dir(&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 " \"%s\" failed", &path); } 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++) { len += sizeof("") - 1 + entry[i].name.len - entry[i].utf_len + entry[i].escape_html + NGX_HTTP_AUTOINDEX_NAME_LEN + sizeof(">") - 2 + sizeof("") - 1 + sizeof(" 28-Sep-1970 12:00 ") - 1 + 20 /* the file size */ + 2; } b = ngx_create_temp_buf(r->pool, len); if (b == NULL) { return NGX_ERROR; } if (entries.nelts > 1) { ngx_qsort(entry, (size_t) entries.nelts, sizeof(ngx_http_autoindex_entry_t), ngx_http_autoindex_cmp_entries); } 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);

    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;
    }

    /* TODO: free temporary pool */

    b->last = ngx_cpymem(b->last, "

", sizeof("

") - 1); b->last = ngx_cpymem(b->last, tail, sizeof(tail) - 1); 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 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->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_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.4.6/src/http/modules/ngx_http_browser_module.c000644 001751 001751 00000047114 12305336445 024374 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_str_t name; ngx_http_get_variable_pt handler; uintptr_t data; } ngx_http_browser_variable_t; typedef struct { ngx_array_t *modern_browsers; ngx_array_t *ancient_browsers; ngx_http_variable_value_t *modern_browser_value; ngx_http_variable_value_t *ancient_browser_value; unsigned modern_unlisted_browsers:1; unsigned netscape4:1; } ngx_http_browser_conf_t; static ngx_int_t ngx_http_msie_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_browser_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_uint_t ngx_http_browser(ngx_http_request_t *r, ngx_http_browser_conf_t *cf); static ngx_int_t ngx_http_browser_add_variable(ngx_conf_t *cf); static void *ngx_http_browser_create_conf(ngx_conf_t *cf); static char *ngx_http_browser_merge_conf(ngx_conf_t *cf, void *parent, void *child); static int ngx_libc_cdecl ngx_http_modern_browser_sort(const void *one, const void *two); static char *ngx_http_modern_browser(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_ancient_browser(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_modern_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_ancient_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_command_t ngx_http_browser_commands[] = { { ngx_string("modern_browser"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12, ngx_http_modern_browser, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("ancient_browser"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_ancient_browser, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("modern_browser_value"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_modern_browser_value, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("ancient_browser_value"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_ancient_browser_value, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_browser_module_ctx = { ngx_http_browser_add_variable, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_browser_create_conf, /* create location configuration */ ngx_http_browser_merge_conf /* merge location configuration */ }; ngx_module_t ngx_http_browser_module = { NGX_MODULE_V1, &ngx_http_browser_module_ctx, /* module context */ ngx_http_browser_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_modern_browser_mask_t ngx_http_modern_browser_masks[] = { /* Opera must be the first browser to check */ /* * "Opera/7.50 (X11; FreeBSD i386; U) [en]" * "Mozilla/5.0 (X11; FreeBSD i386; U) Opera 7.50 [en]" * "Mozilla/4.0 (compatible; MSIE 6.0; X11; FreeBSD i386) Opera 7.50 [en]" * "Opera/8.0 (Windows NT 5.1; U; ru)" * "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; en) Opera 8.0" * "Opera/9.01 (X11; FreeBSD 6 i386; U; en)" */ { "opera", 0, sizeof("Opera ") - 1, "Opera"}, /* "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)" */ { "msie", sizeof("Mozilla/4.0 (compatible; ") - 1, sizeof("MSIE ") - 1, "MSIE "}, /* * "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.0.0) Gecko/20020610" * "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU; rv:1.5) Gecko/20031006" * "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU; rv:1.6) Gecko/20040206 * Firefox/0.8" * "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU; rv:1.7.8) * Gecko/20050511 Firefox/1.0.4" * "Mozilla/5.0 (X11; U; FreeBSD i386; en-US; rv:1.8.0.5) Gecko/20060729 * Firefox/1.5.0.5" */ { "gecko", sizeof("Mozilla/5.0 (") - 1, sizeof("rv:") - 1, "rv:"}, /* * "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ru-ru) AppleWebKit/125.2 * (KHTML, like Gecko) Safari/125.7" * "Mozilla/5.0 (SymbianOS/9.1; U; en-us) AppleWebKit/413 * (KHTML, like Gecko) Safari/413" * "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418 * (KHTML, like Gecko) Safari/417.9.3" * "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ru-ru) AppleWebKit/418.8 * (KHTML, like Gecko) Safari/419.3" */ { "safari", sizeof("Mozilla/5.0 (") - 1, sizeof("Safari/") - 1, "Safari/"}, /* * "Mozilla/5.0 (compatible; Konqueror/3.1; Linux)" * "Mozilla/5.0 (compatible; Konqueror/3.4; Linux) KHTML/3.4.2 (like Gecko)" * "Mozilla/5.0 (compatible; Konqueror/3.5; FreeBSD) KHTML/3.5.1 * (like Gecko)" */ { "konqueror", sizeof("Mozilla/5.0 (compatible; ") - 1, sizeof("Konqueror/") - 1, "Konqueror/"}, { "", 0, 0, "" } }; static ngx_http_browser_variable_t ngx_http_browsers[] = { { ngx_string("msie"), ngx_http_msie_variable, 0 }, { ngx_string("modern_browser"), ngx_http_browser_variable, NGX_HTTP_MODERN_BROWSER }, { ngx_string("ancient_browser"), ngx_http_browser_variable, NGX_HTTP_ANCIENT_BROWSER }, { ngx_null_string, NULL, 0 } }; static ngx_int_t ngx_http_browser_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_uint_t rc; ngx_http_browser_conf_t *cf; cf = ngx_http_get_module_loc_conf(r, ngx_http_browser_module); rc = ngx_http_browser(r, cf); if (data == NGX_HTTP_MODERN_BROWSER && rc == NGX_HTTP_MODERN_BROWSER) { *v = *cf->modern_browser_value; return NGX_OK; } if (data == NGX_HTTP_ANCIENT_BROWSER && rc == NGX_HTTP_ANCIENT_BROWSER) { *v = *cf->ancient_browser_value; return NGX_OK; } *v = ngx_http_variable_null_value; return NGX_OK; } static ngx_uint_t ngx_http_browser(ngx_http_request_t *r, ngx_http_browser_conf_t *cf) { size_t len; u_char *name, *ua, *last, c; ngx_str_t *ancient; ngx_uint_t i, version, ver, scale; ngx_http_modern_browser_t *modern; if (r->headers_in.user_agent == NULL) { if (cf->modern_unlisted_browsers) { return NGX_HTTP_MODERN_BROWSER; } return NGX_HTTP_ANCIENT_BROWSER; } ua = r->headers_in.user_agent->value.data; len = r->headers_in.user_agent->value.len; last = ua + len; if (cf->modern_browsers) { modern = cf->modern_browsers->elts; for (i = 0; i < cf->modern_browsers->nelts; i++) { name = ua + modern[i].skip; if (name >= last) { continue; } name = (u_char *) ngx_strstr(name, modern[i].name); if (name == NULL) { continue; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "browser: \"%s\"", name); name += modern[i].add; if (name >= last) { continue; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "version: \"%ui\" \"%s\"", modern[i].version, name); version = 0; ver = 0; scale = 1000000; while (name < last) { c = *name++; if (c >= '0' && c <= '9') { ver = ver * 10 + (c - '0'); continue; } if (c == '.') { version += ver * scale; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "version: \"%ui\" \"%ui\"", modern[i].version, version); if (version > modern[i].version) { return NGX_HTTP_MODERN_BROWSER; } ver = 0; scale /= 100; continue; } break; } version += ver * scale; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "version: \"%ui\" \"%ui\"", modern[i].version, version); if (version >= modern[i].version) { return NGX_HTTP_MODERN_BROWSER; } return NGX_HTTP_ANCIENT_BROWSER; } if (!cf->modern_unlisted_browsers) { return NGX_HTTP_ANCIENT_BROWSER; } } if (cf->netscape4) { if (len > sizeof("Mozilla/4.72 ") - 1 && ngx_strncmp(ua, "Mozilla/", sizeof("Mozilla/") - 1) == 0 && ua[8] > '0' && ua[8] < '5') { return NGX_HTTP_ANCIENT_BROWSER; } } if (cf->ancient_browsers) { ancient = cf->ancient_browsers->elts; for (i = 0; i < cf->ancient_browsers->nelts; i++) { if (len >= ancient[i].len && ngx_strstr(ua, ancient[i].data) != NULL) { return NGX_HTTP_ANCIENT_BROWSER; } } } if (cf->modern_unlisted_browsers) { return NGX_HTTP_MODERN_BROWSER; } return NGX_HTTP_ANCIENT_BROWSER; } static ngx_int_t ngx_http_msie_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { if (r->headers_in.msie) { *v = ngx_http_variable_true_value; return NGX_OK; } *v = ngx_http_variable_null_value; return NGX_OK; } static ngx_int_t ngx_http_browser_add_variable(ngx_conf_t *cf) { ngx_http_browser_variable_t *var; ngx_http_variable_t *v; for (var = ngx_http_browsers; var->name.len; var++) { v = ngx_http_add_variable(cf, &var->name, NGX_HTTP_VAR_CHANGEABLE); if (v == NULL) { return NGX_ERROR; } v->get_handler = var->handler; v->data = var->data; } return NGX_OK; } static void * ngx_http_browser_create_conf(ngx_conf_t *cf) { ngx_http_browser_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_browser_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->modern_browsers = NULL; * conf->ancient_browsers = NULL; * conf->modern_browser_value = NULL; * conf->ancient_browser_value = NULL; * * conf->modern_unlisted_browsers = 0; * conf->netscape4 = 0; */ return conf; } static char * ngx_http_browser_merge_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_browser_conf_t *prev = parent; ngx_http_browser_conf_t *conf = child; ngx_uint_t i, n; ngx_http_modern_browser_t *browsers, *opera; /* * At the merge the skip field is used to store the browser slot, * it will be used in sorting and then will overwritten * with a real skip value. The zero value means Opera. */ if (conf->modern_browsers == NULL && conf->modern_unlisted_browsers == 0) { conf->modern_browsers = prev->modern_browsers; conf->modern_unlisted_browsers = prev->modern_unlisted_browsers; } else if (conf->modern_browsers != NULL) { browsers = conf->modern_browsers->elts; for (i = 0; i < conf->modern_browsers->nelts; i++) { if (browsers[i].skip == 0) { goto found; } } /* * Opera may contain MSIE string, so if Opera was not enumerated * as modern browsers, then add it and set a unreachable version */ opera = ngx_array_push(conf->modern_browsers); if (opera == NULL) { return NGX_CONF_ERROR; } opera->skip = 0; opera->version = 4001000000U; browsers = conf->modern_browsers->elts; found: ngx_qsort(browsers, (size_t) conf->modern_browsers->nelts, sizeof(ngx_http_modern_browser_t), ngx_http_modern_browser_sort); for (i = 0; i < conf->modern_browsers->nelts; i++) { n = browsers[i].skip; browsers[i].skip = ngx_http_modern_browser_masks[n].skip; browsers[i].add = ngx_http_modern_browser_masks[n].add; (void) ngx_cpystrn(browsers[i].name, ngx_http_modern_browser_masks[n].name, 12); } } if (conf->ancient_browsers == NULL && conf->netscape4 == 0) { conf->ancient_browsers = prev->ancient_browsers; conf->netscape4 = prev->netscape4; } if (conf->modern_browser_value == NULL) { conf->modern_browser_value = prev->modern_browser_value; } if (conf->modern_browser_value == NULL) { conf->modern_browser_value = &ngx_http_variable_true_value; } if (conf->ancient_browser_value == NULL) { conf->ancient_browser_value = prev->ancient_browser_value; } if (conf->ancient_browser_value == NULL) { conf->ancient_browser_value = &ngx_http_variable_true_value; } return NGX_CONF_OK; } static int ngx_libc_cdecl ngx_http_modern_browser_sort(const void *one, const void *two) { ngx_http_modern_browser_t *first = (ngx_http_modern_browser_t *) one; ngx_http_modern_browser_t *second = (ngx_http_modern_browser_t *) two; return (first->skip - second->skip); } static char * ngx_http_modern_browser(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_browser_conf_t *bcf = conf; u_char c; ngx_str_t *value; ngx_uint_t i, n, version, ver, scale; ngx_http_modern_browser_t *browser; ngx_http_modern_browser_mask_t *mask; value = cf->args->elts; if (cf->args->nelts == 2) { if (ngx_strcmp(value[1].data, "unlisted") == 0) { bcf->modern_unlisted_browsers = 1; return NGX_CONF_OK; } return NGX_CONF_ERROR; } if (bcf->modern_browsers == NULL) { bcf->modern_browsers = ngx_array_create(cf->pool, 5, sizeof(ngx_http_modern_browser_t)); if (bcf->modern_browsers == NULL) { return NGX_CONF_ERROR; } } browser = ngx_array_push(bcf->modern_browsers); if (browser == NULL) { return NGX_CONF_ERROR; } mask = ngx_http_modern_browser_masks; for (n = 0; mask[n].browser[0] != '\0'; n++) { if (ngx_strcasecmp(mask[n].browser, value[1].data) == 0) { goto found; } } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unknown browser name \"%V\"", &value[1]); return NGX_CONF_ERROR; found: /* * at this stage the skip field is used to store the browser slot, * it will be used in sorting in merge stage and then will overwritten * with a real value */ browser->skip = n; version = 0; ver = 0; scale = 1000000; for (i = 0; i < value[2].len; i++) { c = value[2].data[i]; if (c >= '0' && c <= '9') { ver = ver * 10 + (c - '0'); continue; } if (c == '.') { version += ver * scale; ver = 0; scale /= 100; continue; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid browser version \"%V\"", &value[2]); return NGX_CONF_ERROR; } version += ver * scale; browser->version = version; return NGX_CONF_OK; } static char * ngx_http_ancient_browser(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_browser_conf_t *bcf = conf; ngx_str_t *value, *browser; ngx_uint_t i; value = cf->args->elts; for (i = 1; i < cf->args->nelts; i++) { if (ngx_strcmp(value[i].data, "netscape4") == 0) { bcf->netscape4 = 1; continue; } if (bcf->ancient_browsers == NULL) { bcf->ancient_browsers = ngx_array_create(cf->pool, 4, sizeof(ngx_str_t)); if (bcf->ancient_browsers == NULL) { return NGX_CONF_ERROR; } } browser = ngx_array_push(bcf->ancient_browsers); if (browser == NULL) { return NGX_CONF_ERROR; } *browser = value[i]; } return NGX_CONF_OK; } static char * ngx_http_modern_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_browser_conf_t *bcf = conf; ngx_str_t *value; bcf->modern_browser_value = ngx_palloc(cf->pool, sizeof(ngx_http_variable_value_t)); if (bcf->modern_browser_value == NULL) { return NGX_CONF_ERROR; } value = cf->args->elts; bcf->modern_browser_value->len = value[1].len; bcf->modern_browser_value->valid = 1; bcf->modern_browser_value->no_cacheable = 0; bcf->modern_browser_value->not_found = 0; bcf->modern_browser_value->data = value[1].data; return NGX_CONF_OK; } static char * ngx_http_ancient_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_browser_conf_t *bcf = conf; ngx_str_t *value; bcf->ancient_browser_value = ngx_palloc(cf->pool, sizeof(ngx_http_variable_value_t)); if (bcf->ancient_browser_value == NULL) { return NGX_CONF_ERROR; } value = cf->args->elts; bcf->ancient_browser_value->len = value[1].len; bcf->ancient_browser_value->valid = 1; bcf->ancient_browser_value->no_cacheable = 0; bcf->ancient_browser_value->not_found = 0; bcf->ancient_browser_value->data = value[1].data; return NGX_CONF_OK; } nginx-1.4.6/src/http/modules/ngx_http_not_modified_filter_module.c000644 001751 001751 00000014502 12305336445 026711 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); 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->headers_out.last_modified_time == -1) { 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)) { 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)) { 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; iums = ngx_http_parse_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:%d lm:%d", 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; 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_http_parse_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:%d lm:%d", 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) { 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); start = list->data; end = list->data + list->len; while (start < end) { 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.4.6/src/http/modules/ngx_http_chunked_filter_module.c000644 001751 001751 00000014550 12305336445 025675 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_http_module_t ngx_http_chunked_filter_module_ctx = { NULL, /* preconfiguration */ ngx_http_chunked_filter_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; ngx_module_t ngx_http_chunked_filter_module = { NGX_MODULE_V1, &ngx_http_chunked_filter_module_ctx, /* module context */ NULL, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter; static ngx_int_t ngx_http_chunked_header_filter(ngx_http_request_t *r) { ngx_http_core_loc_conf_t *clcf; ngx_http_chunked_filter_ctx_t *ctx; if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED || r->headers_out.status == NGX_HTTP_NO_CONTENT || r->headers_out.status < NGX_HTTP_OK || r != r->main || (r->method & NGX_HTTP_HEAD)) { return ngx_http_next_header_filter(r); } if (r->headers_out.content_length_n == -1) { if (r->http_version < NGX_HTTP_VERSION_11) { r->keepalive = 0; } else { clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (clcf->chunked_transfer_encoding) { r->chunked = 1; ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_chunked_filter_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_chunked_filter_module); } else { r->keepalive = 0; } } } return ngx_http_next_header_filter(r); } static ngx_int_t ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { u_char *chunk; off_t size; ngx_int_t rc; ngx_buf_t *b; ngx_chain_t *out, *cl, *tl, **ll; ngx_http_chunked_filter_ctx_t *ctx; if (in == NULL || !r->chunked || r->header_only) { return ngx_http_next_body_filter(r, in); } ctx = ngx_http_get_module_ctx(r, ngx_http_chunked_filter_module); out = NULL; ll = &out; size = 0; cl = in; for ( ;; ) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http chunk: %d", ngx_buf_size(cl->buf)); size += ngx_buf_size(cl->buf); if (cl->buf->flush || cl->buf->sync || ngx_buf_in_memory(cl->buf) || cl->buf->in_file) { tl = ngx_alloc_chain_link(r->pool); if (tl == NULL) { return NGX_ERROR; } tl->buf = cl->buf; *ll = tl; ll = &tl->next; } if (cl->next == NULL) { break; } cl = cl->next; } if (size) { tl = ngx_chain_get_free_buf(r->pool, &ctx->free); if (tl == NULL) { return NGX_ERROR; } b = tl->buf; chunk = b->start; if (chunk == NULL) { /* the "0000000000000000" is 64-bit hexadecimal string */ chunk = ngx_palloc(r->pool, sizeof("0000000000000000" CRLF) - 1); if (chunk == NULL) { return NGX_ERROR; } b->start = chunk; b->end = chunk + sizeof("0000000000000000" CRLF) - 1; } b->tag = (ngx_buf_tag_t) &ngx_http_chunked_filter_module; b->memory = 0; b->temporary = 1; b->pos = chunk; b->last = ngx_sprintf(chunk, "%xO" CRLF, size); tl->next = out; out = tl; } if (cl->buf->last_buf) { tl = ngx_chain_get_free_buf(r->pool, &ctx->free); if (tl == NULL) { return NGX_ERROR; } b = tl->buf; b->tag = (ngx_buf_tag_t) &ngx_http_chunked_filter_module; b->temporary = 0; b->memory = 1; b->last_buf = 1; b->pos = (u_char *) CRLF "0" CRLF CRLF; b->last = b->pos + 7; cl->buf->last_buf = 0; *ll = tl; if (size == 0) { b->pos += 2; } } else if (size > 0) { tl = ngx_chain_get_free_buf(r->pool, &ctx->free); if (tl == NULL) { return NGX_ERROR; } b = tl->buf; b->tag = (ngx_buf_tag_t) &ngx_http_chunked_filter_module; b->temporary = 0; b->memory = 1; b->pos = (u_char *) CRLF; b->last = b->pos + 2; *ll = tl; } else { *ll = NULL; } rc = ngx_http_next_body_filter(r, out); ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, (ngx_buf_tag_t) &ngx_http_chunked_filter_module); return rc; } static ngx_int_t ngx_http_chunked_filter_init(ngx_conf_t *cf) { ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_chunked_header_filter; ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_chunked_body_filter; return NGX_OK; } nginx-1.4.6/src/http/modules/ngx_http_dav_module.c000644 001751 001751 00000075100 12305336445 023457 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ #include #include #include #define NGX_HTTP_DAV_COPY_BLOCK 65536 #define NGX_HTTP_DAV_OFF 2 #define NGX_HTTP_DAV_NO_DEPTH -3 #define NGX_HTTP_DAV_INVALID_DEPTH -2 #define NGX_HTTP_DAV_INFINITY_DEPTH -1 typedef struct { ngx_uint_t methods; ngx_uint_t access; ngx_uint_t min_delete_depth; ngx_flag_t create_full_put_path; } ngx_http_dav_loc_conf_t; typedef struct { ngx_str_t path; size_t len; } ngx_http_dav_copy_ctx_t; static ngx_int_t ngx_http_dav_handler(ngx_http_request_t *r); static void ngx_http_dav_put_handler(ngx_http_request_t *r); static ngx_int_t ngx_http_dav_delete_handler(ngx_http_request_t *r); static ngx_int_t ngx_http_dav_delete_path(ngx_http_request_t *r, ngx_str_t *path, ngx_uint_t dir); static ngx_int_t ngx_http_dav_delete_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path); static ngx_int_t ngx_http_dav_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path); static ngx_int_t ngx_http_dav_noop(ngx_tree_ctx_t *ctx, ngx_str_t *path); static ngx_int_t ngx_http_dav_mkcol_handler(ngx_http_request_t *r, ngx_http_dav_loc_conf_t *dlcf); static ngx_int_t ngx_http_dav_copy_move_handler(ngx_http_request_t *r); static ngx_int_t ngx_http_dav_copy_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path); static ngx_int_t ngx_http_dav_copy_dir_time(ngx_tree_ctx_t *ctx, ngx_str_t *path); static ngx_int_t ngx_http_dav_copy_tree_file(ngx_tree_ctx_t *ctx, ngx_str_t *path); static ngx_int_t ngx_http_dav_depth(ngx_http_request_t *r, ngx_int_t dflt); static ngx_int_t ngx_http_dav_error(ngx_log_t *log, ngx_err_t err, ngx_int_t not_found, char *failed, u_char *path); static ngx_int_t ngx_http_dav_location(ngx_http_request_t *r, u_char *path); static void *ngx_http_dav_create_loc_conf(ngx_conf_t *cf); static char *ngx_http_dav_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); static ngx_int_t ngx_http_dav_init(ngx_conf_t *cf); static ngx_conf_bitmask_t ngx_http_dav_methods_mask[] = { { ngx_string("off"), NGX_HTTP_DAV_OFF }, { ngx_string("put"), NGX_HTTP_PUT }, { ngx_string("delete"), NGX_HTTP_DELETE }, { ngx_string("mkcol"), NGX_HTTP_MKCOL }, { ngx_string("copy"), NGX_HTTP_COPY }, { ngx_string("move"), NGX_HTTP_MOVE }, { ngx_null_string, 0 } }; static ngx_command_t ngx_http_dav_commands[] = { { ngx_string("dav_methods"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_dav_loc_conf_t, methods), &ngx_http_dav_methods_mask }, { ngx_string("create_full_put_path"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_dav_loc_conf_t, create_full_put_path), NULL }, { ngx_string("min_delete_depth"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_dav_loc_conf_t, min_delete_depth), NULL }, { ngx_string("dav_access"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123, ngx_conf_set_access_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_dav_loc_conf_t, access), NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_dav_module_ctx = { NULL, /* preconfiguration */ ngx_http_dav_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_dav_create_loc_conf, /* create location configuration */ ngx_http_dav_merge_loc_conf /* merge location configuration */ }; ngx_module_t ngx_http_dav_module = { NGX_MODULE_V1, &ngx_http_dav_module_ctx, /* module context */ ngx_http_dav_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_int_t ngx_http_dav_handler(ngx_http_request_t *r) { ngx_int_t rc; ngx_http_dav_loc_conf_t *dlcf; dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module); if (!(r->method & dlcf->methods)) { return NGX_DECLINED; } switch (r->method) { case NGX_HTTP_PUT: if (r->uri.data[r->uri.len - 1] == '/') { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "cannot PUT to a collection"); return NGX_HTTP_CONFLICT; } r->request_body_in_file_only = 1; r->request_body_in_persistent_file = 1; r->request_body_in_clean_file = 1; r->request_body_file_group_access = 1; r->request_body_file_log_level = 0; rc = ngx_http_read_client_request_body(r, ngx_http_dav_put_handler); if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { return rc; } return NGX_DONE; case NGX_HTTP_DELETE: return ngx_http_dav_delete_handler(r); case NGX_HTTP_MKCOL: return ngx_http_dav_mkcol_handler(r, dlcf); case NGX_HTTP_COPY: return ngx_http_dav_copy_move_handler(r); case NGX_HTTP_MOVE: return ngx_http_dav_copy_move_handler(r); } return NGX_DECLINED; } static void ngx_http_dav_put_handler(ngx_http_request_t *r) { size_t root; time_t date; ngx_str_t *temp, path; ngx_uint_t status; ngx_file_info_t fi; ngx_ext_rename_file_t ext; ngx_http_dav_loc_conf_t *dlcf; if (r->request_body == NULL || r->request_body->temp_file == NULL) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } ngx_http_map_uri_to_path(r, &path, &root, 0); 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_http_parse_time(r->headers_in.date->value.data, r->headers_in.date->value.len); if (date != NGX_ERROR) { ext.time = date; ext.fd = r->request_body->temp_file->file.fd; } } if (ngx_ext_rename_file(temp, &path, &ext) != NGX_OK) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (status == NGX_HTTP_CREATED) { if (ngx_http_dav_location(r, path.data) != NGX_OK) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } r->headers_out.content_length_n = 0; } r->headers_out.status = status; r->header_only = 1; ngx_http_finalize_request(r, ngx_http_send_header(r)); return; } static ngx_int_t ngx_http_dav_delete_handler(ngx_http_request_t *r) { size_t root; ngx_err_t err; ngx_int_t rc, depth; ngx_uint_t i, d, dir; ngx_str_t path; ngx_file_info_t fi; ngx_http_dav_loc_conf_t *dlcf; if (r->headers_in.content_length_n > 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "DELETE with body is unsupported"); return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE; } dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module); if (dlcf->min_delete_depth) { d = 0; for (i = 0; i < r->uri.len; /* void */) { if (r->uri.data[i++] == '/') { if (++d >= dlcf->min_delete_depth && i < r->uri.len) { goto ok; } } } ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "insufficient URI depth:%i to DELETE", d); return NGX_HTTP_CONFLICT; } ok: ngx_http_map_uri_to_path(r, &path, &root, 0); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http delete filename: \"%s\"", path.data); if (ngx_link_info(path.data, &fi) == NGX_FILE_ERROR) { err = ngx_errno; rc = (err == NGX_ENOTDIR) ? NGX_HTTP_CONFLICT : NGX_HTTP_NOT_FOUND; return ngx_http_dav_error(r->connection->log, err, rc, ngx_link_info_n, path.data); } if (ngx_is_dir(&fi)) { if (r->uri.data[r->uri.len - 1] != '/') { ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_EISDIR, "DELETE \"%s\" failed", path.data); return NGX_HTTP_CONFLICT; } depth = ngx_http_dav_depth(r, NGX_HTTP_DAV_INFINITY_DEPTH); if (depth != NGX_HTTP_DAV_INFINITY_DEPTH) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "\"Depth\" header must be infinity"); return NGX_HTTP_BAD_REQUEST; } path.len -= 2; /* omit "/\0" */ dir = 1; } else { /* * we do not need to test (r->uri.data[r->uri.len - 1] == '/') * because ngx_link_info("/file/") returned NGX_ENOTDIR above */ depth = ngx_http_dav_depth(r, 0); if (depth != 0 && depth != NGX_HTTP_DAV_INFINITY_DEPTH) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "\"Depth\" header must be 0 or infinity"); return NGX_HTTP_BAD_REQUEST; } dir = 0; } rc = ngx_http_dav_delete_path(r, &path, dir); if (rc == NGX_OK) { return NGX_HTTP_NO_CONTENT; } return rc; } static ngx_int_t ngx_http_dav_delete_path(ngx_http_request_t *r, ngx_str_t *path, ngx_uint_t dir) { char *failed; ngx_tree_ctx_t tree; if (dir) { tree.init_handler = NULL; tree.file_handler = ngx_http_dav_delete_file; tree.pre_tree_handler = ngx_http_dav_noop; tree.post_tree_handler = ngx_http_dav_delete_dir; tree.spec_handler = ngx_http_dav_delete_file; tree.data = NULL; tree.alloc = 0; tree.log = r->connection->log; /* TODO: 207 */ if (ngx_walk_tree(&tree, path) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_delete_dir(path->data) != NGX_FILE_ERROR) { return NGX_OK; } failed = ngx_delete_dir_n; } else { if (ngx_delete_file(path->data) != NGX_FILE_ERROR) { return NGX_OK; } failed = ngx_delete_file_n; } return ngx_http_dav_error(r->connection->log, ngx_errno, NGX_HTTP_NOT_FOUND, failed, path->data); } static ngx_int_t ngx_http_dav_delete_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, "http delete dir: \"%s\"", path->data); if (ngx_delete_dir(path->data) == NGX_FILE_ERROR) { /* TODO: add to 207 */ (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_delete_dir_n, path->data); } return NGX_OK; } static ngx_int_t ngx_http_dav_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, "http delete file: \"%s\"", path->data); if (ngx_delete_file(path->data) == NGX_FILE_ERROR) { /* TODO: add to 207 */ (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_delete_file_n, path->data); } return NGX_OK; } static ngx_int_t ngx_http_dav_noop(ngx_tree_ctx_t *ctx, ngx_str_t *path) { return NGX_OK; } static ngx_int_t ngx_http_dav_mkcol_handler(ngx_http_request_t *r, ngx_http_dav_loc_conf_t *dlcf) { u_char *p; size_t root; ngx_str_t path; if (r->headers_in.content_length_n > 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "MKCOL with body is unsupported"); return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE; } if (r->uri.data[r->uri.len - 1] != '/') { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "MKCOL can create a collection only"); return NGX_HTTP_CONFLICT; } p = ngx_http_map_uri_to_path(r, &path, &root, 0); *(p - 1) = '\0'; r->uri.len--; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http mkcol path: \"%s\"", path.data); if (ngx_create_dir(path.data, ngx_dir_access(dlcf->access)) != NGX_FILE_ERROR) { if (ngx_http_dav_location(r, path.data) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } return NGX_HTTP_CREATED; } return ngx_http_dav_error(r->connection->log, ngx_errno, NGX_HTTP_CONFLICT, ngx_create_dir_n, path.data); } static ngx_int_t ngx_http_dav_copy_move_handler(ngx_http_request_t *r) { u_char *p, *host, *last, ch; size_t len, root; ngx_err_t err; ngx_int_t rc, depth; ngx_uint_t overwrite, slash, dir, flags; ngx_str_t path, uri, duri, args; ngx_tree_ctx_t tree; ngx_copy_file_t cf; ngx_file_info_t fi; ngx_table_elt_t *dest, *over; ngx_ext_rename_file_t ext; ngx_http_dav_copy_ctx_t copy; ngx_http_dav_loc_conf_t *dlcf; if (r->headers_in.content_length_n > 0) { return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE; } dest = r->headers_in.destination; if (dest == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "client sent no \"Destination\" header"); return NGX_HTTP_BAD_REQUEST; } p = dest->value.data; /* there is always '\0' even after empty header value */ if (p[0] == '/') { last = p + dest->value.len; goto destination_done; } len = r->headers_in.server.len; if (len == 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "client sent no \"Host\" header"); return NGX_HTTP_BAD_REQUEST; } #if (NGX_HTTP_SSL) if (r->connection->ssl) { if (ngx_strncmp(dest->value.data, "https://", sizeof("https://") - 1) != 0) { goto invalid_destination; } host = dest->value.data + sizeof("https://") - 1; } else #endif { if (ngx_strncmp(dest->value.data, "http://", sizeof("http://") - 1) != 0) { goto invalid_destination; } host = dest->value.data + sizeof("http://") - 1; } if (ngx_strncmp(host, r->headers_in.server.data, len) != 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "\"Destination\" URI \"%V\" is handled by " "different repository than the source URI", &dest->value); return NGX_HTTP_BAD_REQUEST; } last = dest->value.data + dest->value.len; for (p = host + len; p < last; p++) { if (*p == '/') { goto destination_done; } } invalid_destination: ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "client sent invalid \"Destination\" header: \"%V\"", &dest->value); return NGX_HTTP_BAD_REQUEST; destination_done: duri.len = last - p; duri.data = p; flags = 0; 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: ngx_http_map_uri_to_path(r, &path, &root, 0); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http copy from: \"%s\"", path.data); uri = r->uri; r->uri = duri; ngx_http_map_uri_to_path(r, ©.path, &root, 0); r->uri = uri; copy.path.len--; /* omit "\0" */ if (copy.path.data[copy.path.len - 1] == '/') { slash = 1; copy.path.len--; copy.path.data[copy.path.len] = '\0'; } else { slash = 0; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http copy to: \"%s\"", copy.path.data); if (ngx_link_info(copy.path.data, &fi) == NGX_FILE_ERROR) { err = ngx_errno; if (err != NGX_ENOENT) { return ngx_http_dav_error(r->connection->log, err, NGX_HTTP_NOT_FOUND, ngx_link_info_n, copy.path.data); } /* destination does not exist */ overwrite = 0; dir = 0; } else { /* destination exists */ if (ngx_is_dir(&fi) && !slash) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "\"%V\" could not be %Ved to collection \"%V\"", &r->uri, &r->method_name, &dest->value); return NGX_HTTP_CONFLICT; } if (!overwrite) { ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_EEXIST, "\"%s\" could not be created", copy.path.data); return NGX_HTTP_PRECONDITION_FAILED; } dir = ngx_is_dir(&fi); } if (ngx_link_info(path.data, &fi) == NGX_FILE_ERROR) { return ngx_http_dav_error(r->connection->log, ngx_errno, NGX_HTTP_NOT_FOUND, ngx_link_info_n, path.data); } if (ngx_is_dir(&fi)) { if (r->uri.data[r->uri.len - 1] != '/') { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "\"%V\" is collection", &r->uri); return NGX_HTTP_BAD_REQUEST; } if (overwrite) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http delete: \"%s\"", copy.path.data); rc = ngx_http_dav_delete_path(r, ©.path, dir); if (rc != NGX_OK) { return rc; } } } if (ngx_is_dir(&fi)) { path.len -= 2; /* omit "/\0" */ if (r->method == NGX_HTTP_MOVE) { if (ngx_rename_file(path.data, copy.path.data) != NGX_FILE_ERROR) { return NGX_HTTP_CREATED; } } if (ngx_create_dir(copy.path.data, ngx_file_access(&fi)) == NGX_FILE_ERROR) { return ngx_http_dav_error(r->connection->log, ngx_errno, NGX_HTTP_NOT_FOUND, ngx_create_dir_n, copy.path.data); } copy.len = path.len; tree.init_handler = NULL; tree.file_handler = ngx_http_dav_copy_tree_file; tree.pre_tree_handler = ngx_http_dav_copy_dir; tree.post_tree_handler = ngx_http_dav_copy_dir_time; tree.spec_handler = ngx_http_dav_noop; tree.data = © tree.alloc = 0; tree.log = r->connection->log; if (ngx_walk_tree(&tree, &path) == NGX_OK) { if (r->method == NGX_HTTP_MOVE) { rc = ngx_http_dav_delete_path(r, &path, 1); if (rc != NGX_OK) { return rc; } } return NGX_HTTP_CREATED; } } else { if (r->method == NGX_HTTP_MOVE) { dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module); ext.access = 0; ext.path_access = dlcf->access; ext.time = -1; ext.create_path = 1; ext.delete_file = 0; ext.log = r->connection->log; if (ngx_ext_rename_file(&path, ©.path, &ext) == NGX_OK) { return NGX_HTTP_NO_CONTENT; } return NGX_HTTP_INTERNAL_SERVER_ERROR; } dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module); cf.size = ngx_file_size(&fi); cf.buf_size = 0; cf.access = dlcf->access; cf.time = ngx_file_mtime(&fi); cf.log = r->connection->log; if (ngx_copy_file(path.data, copy.path.data, &cf) == NGX_OK) { return NGX_HTTP_NO_CONTENT; } } return NGX_HTTP_INTERNAL_SERVER_ERROR; } static ngx_int_t ngx_http_dav_copy_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path) { u_char *p, *dir; size_t len; ngx_http_dav_copy_ctx_t *copy; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, "http copy dir: \"%s\"", path->data); copy = ctx->data; len = copy->path.len + path->len; dir = ngx_alloc(len + 1, ctx->log); if (dir == NULL) { return NGX_ABORT; } p = ngx_cpymem(dir, copy->path.data, copy->path.len); (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, "http copy dir to: \"%s\"", dir); if (ngx_create_dir(dir, ngx_dir_access(ctx->access)) == NGX_FILE_ERROR) { (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_create_dir_n, dir); } ngx_free(dir); return NGX_OK; } static ngx_int_t ngx_http_dav_copy_dir_time(ngx_tree_ctx_t *ctx, ngx_str_t *path) { u_char *p, *dir; size_t len; ngx_http_dav_copy_ctx_t *copy; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, "http copy dir time: \"%s\"", path->data); copy = ctx->data; len = copy->path.len + path->len; dir = ngx_alloc(len + 1, ctx->log); if (dir == NULL) { return NGX_ABORT; } p = ngx_cpymem(dir, copy->path.data, copy->path.len); (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, "http copy dir time to: \"%s\"", dir); #if (NGX_WIN32) { ngx_fd_t fd; fd = ngx_open_file(dir, NGX_FILE_RDWR, NGX_FILE_OPEN, 0); if (fd == NGX_INVALID_FILE) { (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_open_file_n, dir); goto failed; } if (ngx_set_file_time(NULL, fd, ctx->mtime) != NGX_OK) { ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno, ngx_set_file_time_n " \"%s\" failed", dir); } if (ngx_close_file(fd) == NGX_FILE_ERROR) { ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno, ngx_close_file_n " \"%s\" failed", dir); } } failed: #else if (ngx_set_file_time(dir, 0, ctx->mtime) != NGX_OK) { ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno, ngx_set_file_time_n " \"%s\" failed", dir); } #endif ngx_free(dir); return NGX_OK; } static ngx_int_t ngx_http_dav_copy_tree_file(ngx_tree_ctx_t *ctx, ngx_str_t *path) { u_char *p, *file; size_t len; ngx_copy_file_t cf; ngx_http_dav_copy_ctx_t *copy; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, "http copy file: \"%s\"", path->data); copy = ctx->data; len = copy->path.len + path->len; file = ngx_alloc(len + 1, ctx->log); if (file == NULL) { return NGX_ABORT; } p = ngx_cpymem(file, copy->path.data, copy->path.len); (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, "http copy file to: \"%s\"", file); cf.size = ctx->size; cf.buf_size = 0; cf.access = ctx->access; cf.time = ctx->mtime; cf.log = ctx->log; (void) ngx_copy_file(path->data, file, &cf); ngx_free(file); return NGX_OK; } static ngx_int_t ngx_http_dav_depth(ngx_http_request_t *r, ngx_int_t dflt) { ngx_table_elt_t *depth; depth = r->headers_in.depth; if (depth == NULL) { return dflt; } if (depth->value.len == 1) { if (depth->value.data[0] == '0') { return 0; } if (depth->value.data[0] == '1') { return 1; } } else { if (depth->value.len == sizeof("infinity") - 1 && ngx_strcmp(depth->value.data, "infinity") == 0) { return NGX_HTTP_DAV_INFINITY_DEPTH; } } ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "client sent invalid \"Depth\" header: \"%V\"", &depth->value); return NGX_HTTP_DAV_INVALID_DEPTH; } static ngx_int_t ngx_http_dav_error(ngx_log_t *log, ngx_err_t err, ngx_int_t not_found, char *failed, u_char *path) { ngx_int_t rc; ngx_uint_t level; if (err == NGX_ENOENT || err == NGX_ENOTDIR || err == NGX_ENAMETOOLONG) { level = NGX_LOG_ERR; rc = not_found; } else if (err == NGX_EACCES || err == NGX_EPERM) { level = NGX_LOG_ERR; rc = NGX_HTTP_FORBIDDEN; } else if (err == NGX_EEXIST) { level = NGX_LOG_ERR; rc = NGX_HTTP_NOT_ALLOWED; } else if (err == NGX_ENOSPC) { level = NGX_LOG_CRIT; rc = NGX_HTTP_INSUFFICIENT_STORAGE; } else { level = NGX_LOG_CRIT; rc = NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_log_error(level, log, err, "%s \"%s\" failed", failed, path); return rc; } static ngx_int_t ngx_http_dav_location(ngx_http_request_t *r, u_char *path) { u_char *location; ngx_http_core_loc_conf_t *clcf; r->headers_out.location = ngx_palloc(r->pool, sizeof(ngx_table_elt_t)); if (r->headers_out.location == NULL) { return NGX_ERROR; } clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (!clcf->alias && clcf->root_lengths == NULL) { location = path + clcf->root.len; } else { location = ngx_pnalloc(r->pool, r->uri.len); if (location == NULL) { return NGX_ERROR; } ngx_memcpy(location, r->uri.data, r->uri.len); } /* * we do not need to set the r->headers_out.location->hash and * r->headers_out.location->key fields */ r->headers_out.location->value.len = r->uri.len; r->headers_out.location->value.data = location; return NGX_OK; } static void * ngx_http_dav_create_loc_conf(ngx_conf_t *cf) { ngx_http_dav_loc_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_dav_loc_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->methods = 0; */ conf->min_delete_depth = NGX_CONF_UNSET_UINT; conf->access = NGX_CONF_UNSET_UINT; conf->create_full_put_path = NGX_CONF_UNSET; return conf; } static char * ngx_http_dav_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_dav_loc_conf_t *prev = parent; ngx_http_dav_loc_conf_t *conf = child; ngx_conf_merge_bitmask_value(conf->methods, prev->methods, (NGX_CONF_BITMASK_SET|NGX_HTTP_DAV_OFF)); ngx_conf_merge_uint_value(conf->min_delete_depth, prev->min_delete_depth, 0); ngx_conf_merge_uint_value(conf->access, prev->access, 0600); ngx_conf_merge_value(conf->create_full_put_path, prev->create_full_put_path, 0); return NGX_CONF_OK; } static ngx_int_t ngx_http_dav_init(ngx_conf_t *cf) { ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } *h = ngx_http_dav_handler; return NGX_OK; } nginx-1.4.6/src/http/modules/ngx_http_degradation_module.c000644 001751 001751 00000014140 12305336445 025163 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.4.6/src/http/modules/ngx_http_empty_gif_module.c000644 001751 001751 00000012640 12305336445 024670 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.4.6/src/http/modules/ngx_http_fastcgi_module.c000644 001751 001751 00000256664 12305336445 024345 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ #include #include #include typedef struct { ngx_http_upstream_conf_t upstream; ngx_str_t index; ngx_array_t *flushes; ngx_array_t *params_len; ngx_array_t *params; ngx_array_t *params_source; ngx_array_t *catch_stderr; ngx_array_t *fastcgi_lengths; ngx_array_t *fastcgi_values; ngx_hash_t headers_hash; ngx_uint_t header_params; 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; unsigned fastcgi_stdout:1; unsigned large_stderr: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_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_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_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_merge_params(ngx_conf_t *cf, ngx_http_fastcgi_loc_conf_t *conf, ngx_http_fastcgi_loc_conf_t *prev); 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("http_500"), NGX_HTTP_UPSTREAM_FT_HTTP_500 }, { ngx_string("http_503"), NGX_HTTP_UPSTREAM_FT_HTTP_503 }, { ngx_string("http_404"), NGX_HTTP_UPSTREAM_FT_HTTP_404 }, { ngx_string("updating"), NGX_HTTP_UPSTREAM_FT_UPDATING }, { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF }, { ngx_null_string, 0 } }; ngx_module_t ngx_http_fastcgi_module; static ngx_command_t ngx_http_fastcgi_commands[] = { { ngx_string("fastcgi_pass"), NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, ngx_http_fastcgi_pass, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("fastcgi_index"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, index), NULL }, { ngx_string("fastcgi_split_path_info"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_fastcgi_split_path_info, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("fastcgi_store"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_fastcgi_store, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("fastcgi_store_access"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123, ngx_conf_set_access_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.store_access), NULL }, { ngx_string("fastcgi_ignore_client_abort"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.ignore_client_abort), NULL }, { ngx_string("fastcgi_bind"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_upstream_bind_set_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.local), NULL }, { ngx_string("fastcgi_connect_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.connect_timeout), NULL }, { ngx_string("fastcgi_send_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.send_timeout), NULL }, { ngx_string("fastcgi_send_lowat"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.send_lowat), &ngx_http_fastcgi_lowat_post }, { ngx_string("fastcgi_buffer_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.buffer_size), NULL }, { ngx_string("fastcgi_pass_request_headers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.pass_request_headers), NULL }, { ngx_string("fastcgi_pass_request_body"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.pass_request_body), NULL }, { ngx_string("fastcgi_intercept_errors"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.intercept_errors), NULL }, { ngx_string("fastcgi_read_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.read_timeout), NULL }, { ngx_string("fastcgi_buffers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, ngx_conf_set_bufs_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.bufs), NULL }, { ngx_string("fastcgi_busy_buffers_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.busy_buffers_size_conf), NULL }, #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, 0, 0, &ngx_http_fastcgi_module }, { ngx_string("fastcgi_cache_bypass"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_set_predicate_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_bypass), NULL }, { ngx_string("fastcgi_no_cache"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_set_predicate_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.no_cache), NULL }, { ngx_string("fastcgi_cache_valid"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_file_cache_valid_set_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_valid), NULL }, { ngx_string("fastcgi_cache_min_uses"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_min_uses), NULL }, { ngx_string("fastcgi_cache_use_stale"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_use_stale), &ngx_http_fastcgi_next_upstream_masks }, { ngx_string("fastcgi_cache_methods"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_methods), &ngx_http_upstream_cache_method_mask }, { ngx_string("fastcgi_cache_lock"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_lock), NULL }, { ngx_string("fastcgi_cache_lock_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_lock_timeout), NULL }, #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_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 */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_fastcgi_create_loc_conf, /* create location configuration */ ngx_http_fastcgi_merge_loc_conf /* merge location configuration */ }; ngx_module_t ngx_http_fastcgi_module = { NGX_MODULE_V1, &ngx_http_fastcgi_module_ctx, /* module context */ ngx_http_fastcgi_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_fastcgi_request_start_t ngx_http_fastcgi_request_start = { { 1, /* version */ NGX_HTTP_FASTCGI_BEGIN_REQUEST, /* type */ 0, /* request_id_hi */ 1, /* request_id_lo */ 0, /* content_length_hi */ sizeof(ngx_http_fastcgi_begin_request_t), /* content_length_lo */ 0, /* padding_length */ 0 }, /* reserved */ { 0, /* role_hi */ NGX_HTTP_FASTCGI_RESPONDER, /* role_lo */ 0, /* NGX_HTTP_FASTCGI_KEEP_CONN */ /* flags */ { 0, 0, 0, 0, 0 } }, /* reserved[5] */ { 1, /* version */ NGX_HTTP_FASTCGI_PARAMS, /* type */ 0, /* request_id_hi */ 1 }, /* request_id_lo */ }; static ngx_http_variable_t ngx_http_fastcgi_vars[] = { { ngx_string("fastcgi_script_name"), NULL, ngx_http_fastcgi_script_name_variable, 0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 }, { ngx_string("fastcgi_path_info"), NULL, ngx_http_fastcgi_path_info_variable, 0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 }, { ngx_null_string, NULL, NULL, 0, 0, 0 } }; static ngx_str_t ngx_http_fastcgi_hide_headers[] = { ngx_string("Status"), ngx_string("X-Accel-Expires"), ngx_string("X-Accel-Redirect"), ngx_string("X-Accel-Limit-Rate"), ngx_string("X-Accel-Buffering"), ngx_string("X-Accel-Charset"), ngx_null_string }; #if (NGX_HTTP_CACHE) static ngx_keyval_t ngx_http_fastcgi_cache_headers[] = { { ngx_string("HTTP_IF_MODIFIED_SINCE"), ngx_string("") }, { ngx_string("HTTP_IF_UNMODIFIED_SINCE"), ngx_string("") }, { ngx_string("HTTP_IF_NONE_MATCH"), ngx_string("") }, { 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 (r->subrequest_in_memory) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "ngx_http_fastcgi_module does not support " "subrequest in memory"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } 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) 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 = 1; 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; rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init); if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { return rc; } return NGX_DONE; } static ngx_int_t ngx_http_fastcgi_eval(ngx_http_request_t *r, ngx_http_fastcgi_loc_conf_t *flcf) { ngx_url_t url; ngx_http_upstream_t *u; ngx_memzero(&url, sizeof(ngx_url_t)); if (ngx_http_script_run(r, &url.url, flcf->fastcgi_lengths->elts, 0, flcf->fastcgi_values->elts) == NULL) { return NGX_ERROR; } url.no_resolve = 1; if (ngx_parse_url(r->pool, &url) != NGX_OK) { if (url.err) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "%s in upstream \"%V\"", url.err, &url.url); } return NGX_ERROR; } u = r->upstream; u->resolved = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t)); if (u->resolved == NULL) { return NGX_ERROR; } if (url.addrs && url.addrs[0].sockaddr) { u->resolved->sockaddr = url.addrs[0].sockaddr; u->resolved->socklen = url.addrs[0].socklen; u->resolved->naddrs = 1; u->resolved->host = url.addrs[0].name; } else { u->resolved->host = url.host; u->resolved->port = url.port; u->resolved->no_port = url.no_port; } return NGX_OK; } #if (NGX_HTTP_CACHE) static ngx_int_t ngx_http_fastcgi_create_key(ngx_http_request_t *r) { ngx_str_t *key; ngx_http_fastcgi_loc_conf_t *flcf; key = ngx_array_push(&r->cache->keys); if (key == NULL) { return NGX_ERROR; } flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module); if (ngx_http_complex_value(r, &flcf->cache_key, key) != NGX_OK) { return NGX_ERROR; } return NGX_OK; } #endif static ngx_int_t ngx_http_fastcgi_create_request(ngx_http_request_t *r) { off_t file_pos; u_char ch, *pos, *lowcase_key; size_t size, len, key_len, val_len, padding, allocated; ngx_uint_t i, n, next, hash, skip_empty, header_params; ngx_buf_t *b; ngx_chain_t *cl, *body; ngx_list_part_t *part; ngx_table_elt_t *header, **ignored; ngx_http_script_code_pt code; ngx_http_script_engine_t e, le; ngx_http_fastcgi_header_t *h; ngx_http_fastcgi_loc_conf_t *flcf; ngx_http_script_len_code_pt lcode; len = 0; header_params = 0; ignored = NULL; flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module); if (flcf->params_len) { ngx_memzero(&le, sizeof(ngx_http_script_engine_t)); ngx_http_script_flush_no_cacheable_variables(r, flcf->flushes); le.flushed = 1; le.ip = flcf->params_len->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 (flcf->header_params) { n = 0; part = &r->headers_in.headers.part; while (part) { n += part->nelts; part = part->next; } ignored = ngx_palloc(r->pool, n * sizeof(void *)); if (ignored == NULL) { return NGX_ERROR; } } part = &r->headers_in.headers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (flcf->header_params) { 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(&flcf->headers_hash, hash, lowcase_key, n)) { ignored[header_params++] = &header[i]; continue; } n += sizeof("HTTP_") - 1; } else { n = sizeof("HTTP_") - 1 + header[i].key.len; } len += ((n > 127) ? 4 : 1) + ((header[i].value.len > 127) ? 4 : 1) + n + header[i].value.len; } } if (len > 65535) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "fastcgi request record is too big: %uz", len); return NGX_ERROR; } padding = 8 - len % 8; padding = (padding == 8) ? 0 : padding; size = sizeof(ngx_http_fastcgi_header_t) + sizeof(ngx_http_fastcgi_begin_request_t) + sizeof(ngx_http_fastcgi_header_t) /* NGX_HTTP_FASTCGI_PARAMS */ + len + padding + sizeof(ngx_http_fastcgi_header_t) /* NGX_HTTP_FASTCGI_PARAMS */ + sizeof(ngx_http_fastcgi_header_t); /* NGX_HTTP_FASTCGI_STDIN */ b = ngx_create_temp_buf(r->pool, size); if (b == NULL) { return NGX_ERROR; } cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_ERROR; } cl->buf = b; ngx_http_fastcgi_request_start.br.flags = flcf->keep_conn ? NGX_HTTP_FASTCGI_KEEP_CONN : 0; ngx_memcpy(b->pos, &ngx_http_fastcgi_request_start, sizeof(ngx_http_fastcgi_request_start_t)); h = (ngx_http_fastcgi_header_t *) (b->pos + sizeof(ngx_http_fastcgi_header_t) + sizeof(ngx_http_fastcgi_begin_request_t)); h->content_length_hi = (u_char) ((len >> 8) & 0xff); h->content_length_lo = (u_char) (len & 0xff); h->padding_length = (u_char) padding; h->reserved = 0; b->last = b->pos + sizeof(ngx_http_fastcgi_header_t) + sizeof(ngx_http_fastcgi_begin_request_t) + sizeof(ngx_http_fastcgi_header_t); if (flcf->params_len) { ngx_memzero(&e, sizeof(ngx_http_script_engine_t)); e.ip = flcf->params->elts; e.pos = b->last; e.request = r; e.flushed = 1; le.ip = flcf->params_len->elts; while (*(uintptr_t *) le.ip) { lcode = *(ngx_http_script_len_code_pt *) le.ip; key_len = (u_char) lcode(&le); lcode = *(ngx_http_script_len_code_pt *) le.ip; skip_empty = lcode(&le); for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) { lcode = *(ngx_http_script_len_code_pt *) le.ip; } le.ip += sizeof(uintptr_t); if (skip_empty && val_len == 0) { e.skip = 1; while (*(uintptr_t *) e.ip) { code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); } e.ip += sizeof(uintptr_t); e.skip = 0; continue; } *e.pos++ = (u_char) key_len; if (val_len > 127) { *e.pos++ = (u_char) (((val_len >> 24) & 0x7f) | 0x80); *e.pos++ = (u_char) ((val_len >> 16) & 0xff); *e.pos++ = (u_char) ((val_len >> 8) & 0xff); *e.pos++ = (u_char) (val_len & 0xff); } else { *e.pos++ = (u_char) val_len; } while (*(uintptr_t *) e.ip) { code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); } e.ip += sizeof(uintptr_t); ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "fastcgi param: \"%*s: %*s\"", key_len, e.pos - (key_len + val_len), val_len, e.pos - val_len); } b->last = e.pos; } if (flcf->upstream.pass_request_headers) { part = &r->headers_in.headers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } for (n = 0; n < header_params; n++) { if (&header[i] == ignored[n]) { goto next; } } key_len = sizeof("HTTP_") - 1 + header[i].key.len; if (key_len > 127) { *b->last++ = (u_char) (((key_len >> 24) & 0x7f) | 0x80); *b->last++ = (u_char) ((key_len >> 16) & 0xff); *b->last++ = (u_char) ((key_len >> 8) & 0xff); *b->last++ = (u_char) (key_len & 0xff); } else { *b->last++ = (u_char) key_len; } val_len = header[i].value.len; if (val_len > 127) { *b->last++ = (u_char) (((val_len >> 24) & 0x7f) | 0x80); *b->last++ = (u_char) ((val_len >> 16) & 0xff); *b->last++ = (u_char) ((val_len >> 8) & 0xff); *b->last++ = (u_char) (val_len & 0xff); } else { *b->last++ = (u_char) val_len; } b->last = ngx_cpymem(b->last, "HTTP_", sizeof("HTTP_") - 1); for (n = 0; n < header[i].key.len; n++) { ch = header[i].key.data[n]; if (ch >= 'a' && ch <= 'z') { ch &= ~0x20; } else if (ch == '-') { ch = '_'; } *b->last++ = ch; } b->last = ngx_copy(b->last, header[i].value.data, val_len); ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "fastcgi param: \"%*s: %*s\"", key_len, b->last - (key_len + val_len), val_len, b->last - val_len); next: continue; } } if (padding) { ngx_memzero(b->last, padding); b->last += padding; } h = (ngx_http_fastcgi_header_t *) b->last; b->last += sizeof(ngx_http_fastcgi_header_t); h->version = 1; h->type = NGX_HTTP_FASTCGI_PARAMS; h->request_id_hi = 0; h->request_id_lo = 1; h->content_length_hi = 0; h->content_length_lo = 0; h->padding_length = 0; h->reserved = 0; h = (ngx_http_fastcgi_header_t *) b->last; b->last += sizeof(ngx_http_fastcgi_header_t); if (flcf->upstream.pass_request_body) { body = r->upstream->request_bufs; r->upstream->request_bufs = cl; #if (NGX_SUPPRESS_WARN) file_pos = 0; pos = NULL; #endif while (body) { 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; 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->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; } h = (ngx_http_fastcgi_header_t *) b->last; b->last += sizeof(ngx_http_fastcgi_header_t); 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 { r->upstream->request_bufs = cl; } 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; r->state = 0; return NGX_OK; } 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: %d", 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) { u_char *start; 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: %d", rc); if (rc == NGX_AGAIN) { break; } if (rc == NGX_OK) { /* a header line has been parsed successfully */ h = ngx_list_push(&u->headers_in.headers); if (h == NULL) { return NGX_ERROR; } if (f->split_parts && f->split_parts->nelts) { part = f->split_parts->elts; size = u->buffer.pos - part_start; for (i = 0; i < f->split_parts->nelts; i++) { size += part[i].end - part[i].start; } p = ngx_pnalloc(r->pool, size); if (p == NULL) { return NGX_ERROR; } buf.pos = p; for (i = 0; i < f->split_parts->nelts; i++) { p = ngx_cpymem(p, part[i].start, part[i].end - part[i].start); } p = ngx_cpymem(p, part_start, u->buffer.pos - part_start); buf.last = p; f->split_parts->nelts = 0; rc = ngx_http_parse_header_line(r, &buf, 1); h->key.len = r->header_name_end - r->header_name_start; h->key.data = r->header_name_start; h->key.data[h->key.len] = '\0'; h->value.len = r->header_end - r->header_start; h->value.data = r->header_start; h->value.data[h->value.len] = '\0'; h->lowcase_key = ngx_pnalloc(r->pool, h->key.len); if (h->lowcase_key == NULL) { return NGX_ERROR; } } else { h->key.len = r->header_name_end - r->header_name_start; h->value.len = r->header_end - r->header_start; h->key.data = ngx_pnalloc(r->pool, h->key.len + 1 + h->value.len + 1 + h->key.len); if (h->key.data == NULL) { return NGX_ERROR; } h->value.data = h->key.data + h->key.len + 1; h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1; ngx_memcpy(h->key.data, r->header_name_start, h->key.len); h->key.data[h->key.len] = '\0'; ngx_memcpy(h->value.data, r->header_start, h->value.len); h->value.data[h->value.len] = '\0'; } h->hash = r->header_hash; if (h->key.len == r->lowcase_index) { ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len); } else { ngx_strlow(h->lowcase_key, h->key.data, h->key.len); } hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len); if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { return NGX_ERROR; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http fastcgi header: \"%V: %V\"", &h->key, &h->value); if (u->buffer.pos < u->buffer.last) { continue; } /* the end of the FastCGI record */ break; } if (rc == NGX_HTTP_PARSE_HEADER_DONE) { /* a whole header has been parsed successfully */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http fastcgi header done"); if (u->headers_in.status) { status_line = &u->headers_in.status->value; status = ngx_atoi(status_line->data, 3); if (status == NGX_ERROR) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid status \"%V\"", status_line); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } u->headers_in.status_n = status; u->headers_in.status_line = *status_line; } else if (u->headers_in.location) { u->headers_in.status_n = 302; ngx_str_set(&u->headers_in.status_line, "302 Moved Temporarily"); } else { u->headers_in.status_n = 200; ngx_str_set(&u->headers_in.status_line, "200 OK"); } if (u->state) { u->state->status = u->headers_in.status_n; } break; } /* there was error while a header line parsing */ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid header"); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } if (last) { u->buffer.last = last; } f->length -= u->buffer.pos - start; if (f->length == 0) { f->state = ngx_http_fastcgi_st_padding; } if (rc == NGX_HTTP_PARSE_HEADER_DONE) { return NGX_OK; } if (rc == NGX_OK) { continue; } /* rc == NGX_AGAIN */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "upstream split a header line in FastCGI records"); if (f->split_parts == NULL) { f->split_parts = ngx_array_create(r->pool, 1, sizeof(ngx_http_fastcgi_split_part_t)); if (f->split_parts == NULL) { return NGX_ERROR; } } part = ngx_array_push(f->split_parts); if (part == NULL) { return NGX_ERROR; } part->start = part_start; part->end = part_end; if (u->buffer.pos < u->buffer.last) { continue; } return NGX_AGAIN; } } static ngx_int_t ngx_http_fastcgi_input_filter_init(void *data) { ngx_http_request_t *r = data; ngx_http_fastcgi_loc_conf_t *flcf; flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module); r->upstream->pipe->length = flcf->keep_conn ? (off_t) sizeof(ngx_http_fastcgi_header_t) : -1; return NGX_OK; } static ngx_int_t ngx_http_fastcgi_input_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) { u_char *m, *msg; ngx_int_t rc; ngx_buf_t *b, **prev; ngx_chain_t *cl; ngx_http_request_t *r; ngx_http_fastcgi_ctx_t *f; ngx_http_fastcgi_loc_conf_t *flcf; if (buf->pos == buf->last) { return NGX_OK; } r = p->input_ctx; f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module); flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module); b = NULL; prev = &buf->shadow; f->pos = buf->pos; f->last = buf->last; for ( ;; ) { if (f->state < ngx_http_fastcgi_st_data) { rc = ngx_http_fastcgi_process_record(r, f); if (rc == NGX_AGAIN) { break; } if (rc == NGX_ERROR) { return NGX_ERROR; } if (f->type == NGX_HTTP_FASTCGI_STDOUT && f->length == 0) { f->state = ngx_http_fastcgi_st_padding; if (!flcf->keep_conn) { p->upstream_done = 1; } ngx_log_debug0(NGX_LOG_DEBUG_HTTP, p->log, 0, "http fastcgi closed stdout"); continue; } if (f->type == NGX_HTTP_FASTCGI_END_REQUEST) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, p->log, 0, "http fastcgi sent end request"); if (!flcf->keep_conn) { p->upstream_done = 1; break; } continue; } } if (f->state == ngx_http_fastcgi_st_padding) { if (f->type == NGX_HTTP_FASTCGI_END_REQUEST) { if (f->pos + f->padding < f->last) { p->upstream_done = 1; break; } if (f->pos + f->padding == f->last) { p->upstream_done = 1; r->upstream->keepalive = 1; break; } f->padding -= f->last - f->pos; break; } if (f->pos + f->padding < f->last) { f->state = ngx_http_fastcgi_st_version; f->pos += f->padding; continue; } if (f->pos + f->padding == f->last) { f->state = ngx_http_fastcgi_st_version; break; } f->padding -= f->last - f->pos; break; } /* f->state == ngx_http_fastcgi_st_data */ if (f->type == NGX_HTTP_FASTCGI_STDERR) { if (f->length) { if (f->pos == f->last) { break; } msg = f->pos; if (f->pos + f->length <= f->last) { f->pos += f->length; f->length = 0; f->state = ngx_http_fastcgi_st_padding; } else { f->length -= f->last - f->pos; f->pos = f->last; } for (m = f->pos - 1; msg < m; m--) { if (*m != LF && *m != CR && *m != '.' && *m != ' ') { break; } } ngx_log_error(NGX_LOG_ERR, p->log, 0, "FastCGI sent in stderr: \"%*s\"", m + 1 - msg, msg); } else { f->state = ngx_http_fastcgi_st_padding; } continue; } if (f->type == NGX_HTTP_FASTCGI_END_REQUEST) { if (f->pos + f->length <= f->last) { f->state = ngx_http_fastcgi_st_padding; f->pos += f->length; continue; } f->length -= f->last - f->pos; break; } /* f->type == NGX_HTTP_FASTCGI_STDOUT */ if (f->pos == f->last) { break; } if (p->free) { cl = p->free; b = cl->buf; p->free = cl->next; ngx_free_chain(p->pool, cl); } else { b = ngx_alloc_buf(p->pool); if (b == NULL) { return NGX_ERROR; } } 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; cl = ngx_alloc_chain_link(p->pool); if (cl == NULL) { return NGX_ERROR; } cl->buf = b; cl->next = NULL; if (p->in) { *p->last_in = cl; } else { p->in = cl; } p->last_in = &cl->next; /* STUB */ b->num = buf->num; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, p->log, 0, "input buf #%d %p", b->num, b->pos); if (f->pos + f->length <= f->last) { f->state = ngx_http_fastcgi_st_padding; f->pos += f->length; b->last = f->pos; continue; } f->length -= f->last - f->pos; b->last = f->last; break; } if (flcf->keep_conn) { /* set p->length, minimal amount of data we want to see */ if (f->state < ngx_http_fastcgi_st_data) { p->length = 1; } else if (f->state == ngx_http_fastcgi_st_padding) { p->length = f->padding; } else { /* ngx_http_fastcgi_st_data */ p->length = f->length; } } if (b) { b->shadow = buf; b->last_shadow = 1; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, p->log, 0, "input buf %p %z", b->pos, b->last - b->pos); return NGX_OK; } /* there is no data record in the buf, add it to free chain */ if (ngx_event_pipe_add_free_buf(p, buf) != NGX_OK) { return NGX_ERROR; } return NGX_OK; } static ngx_int_t ngx_http_fastcgi_process_record(ngx_http_request_t *r, ngx_http_fastcgi_ctx_t *f) { u_char ch, *p; ngx_http_fastcgi_state_e state; state = f->state; for (p = f->pos; p < f->last; p++) { ch = *p; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http fastcgi record byte: %02Xd", ch); switch (state) { case ngx_http_fastcgi_st_version: if (ch != 1) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent unsupported FastCGI " "protocol version: %d", ch); return NGX_ERROR; } state = ngx_http_fastcgi_st_type; break; case ngx_http_fastcgi_st_type: switch (ch) { case NGX_HTTP_FASTCGI_STDOUT: case NGX_HTTP_FASTCGI_STDERR: case NGX_HTTP_FASTCGI_END_REQUEST: f->type = (ngx_uint_t) ch; break; default: ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid FastCGI " "record type: %d", ch); return NGX_ERROR; } state = ngx_http_fastcgi_st_request_id_hi; break; /* we support the single request per connection */ case ngx_http_fastcgi_st_request_id_hi: if (ch != 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent unexpected FastCGI " "request id high byte: %d", ch); return NGX_ERROR; } state = ngx_http_fastcgi_st_request_id_lo; break; case ngx_http_fastcgi_st_request_id_lo: if (ch != 1) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent unexpected FastCGI " "request id low byte: %d", ch); return NGX_ERROR; } state = ngx_http_fastcgi_st_content_length_hi; break; case ngx_http_fastcgi_st_content_length_hi: f->length = ch << 8; state = ngx_http_fastcgi_st_content_length_lo; break; case ngx_http_fastcgi_st_content_length_lo: f->length |= (size_t) ch; state = ngx_http_fastcgi_st_padding_length; break; case ngx_http_fastcgi_st_padding_length: f->padding = (size_t) ch; state = ngx_http_fastcgi_st_reserved; break; case ngx_http_fastcgi_st_reserved: state = ngx_http_fastcgi_st_data; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http fastcgi record length: %z", f->length); f->pos = p + 1; f->state = state; return NGX_OK; /* suppress warning */ case ngx_http_fastcgi_st_data: case ngx_http_fastcgi_st_padding: break; } } f->state = state; return NGX_AGAIN; } static void ngx_http_fastcgi_abort_request(ngx_http_request_t *r) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "abort http fastcgi request"); return; } static void ngx_http_fastcgi_finalize_request(ngx_http_request_t *r, ngx_int_t rc) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "finalize http fastcgi request"); return; } static ngx_int_t ngx_http_fastcgi_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var, *v; for (v = ngx_http_fastcgi_vars; v->name.len; v++) { var = ngx_http_add_variable(cf, &v->name, v->flags); if (var == NULL) { return NGX_ERROR; } var->get_handler = v->get_handler; var->data = v->data; } return NGX_OK; } static void * ngx_http_fastcgi_create_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_use_stale = 0; * conf->upstream.cache_methods = 0; * conf->upstream.temp_path = NULL; * conf->upstream.hide_headers_hash = { NULL, 0 }; * conf->upstream.uri = { 0, NULL }; * conf->upstream.location = NULL; * conf->upstream.store_lengths = NULL; * conf->upstream.store_values = NULL; * * conf->index.len = { 0, NULL }; */ conf->upstream.store = NGX_CONF_UNSET; conf->upstream.store_access = NGX_CONF_UNSET_UINT; conf->upstream.buffering = NGX_CONF_UNSET; conf->upstream.ignore_client_abort = NGX_CONF_UNSET; conf->upstream.local = NGX_CONF_UNSET_PTR; conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.send_lowat = NGX_CONF_UNSET_SIZE; conf->upstream.buffer_size = 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_PTR; conf->upstream.cache_min_uses = NGX_CONF_UNSET_UINT; conf->upstream.cache_bypass = NGX_CONF_UNSET_PTR; conf->upstream.no_cache = NGX_CONF_UNSET_PTR; conf->upstream.cache_valid = NGX_CONF_UNSET_PTR; conf->upstream.cache_lock = NGX_CONF_UNSET; conf->upstream.cache_lock_timeout = NGX_CONF_UNSET_MSEC; #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->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_hash_init_t hash; ngx_http_core_loc_conf_t *clcf; if (conf->upstream.store != 0) { ngx_conf_merge_value(conf->upstream.store, prev->upstream.store, 0); if (conf->upstream.store_lengths == NULL) { 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_value(conf->upstream.buffering, prev->upstream.buffering, 1); ngx_conf_merge_value(conf->upstream.ignore_client_abort, prev->upstream.ignore_client_abort, 0); ngx_conf_merge_ptr_value(conf->upstream.local, prev->upstream.local, NULL); ngx_conf_merge_msec_value(conf->upstream.connect_timeout, prev->upstream.connect_timeout, 60000); ngx_conf_merge_msec_value(conf->upstream.send_timeout, prev->upstream.send_timeout, 60000); ngx_conf_merge_msec_value(conf->upstream.read_timeout, prev->upstream.read_timeout, 60000); ngx_conf_merge_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_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) ngx_conf_merge_ptr_value(conf->upstream.cache, prev->upstream.cache, NULL); if (conf->upstream.cache && conf->upstream.cache->data == NULL) { ngx_shm_zone_t *shm_zone; shm_zone = conf->upstream.cache; ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"fastcgi_cache\" zone \"%V\" is unknown", &shm_zone->shm.name); return NGX_CONF_ERROR; } ngx_conf_merge_uint_value(conf->upstream.cache_min_uses, prev->upstream.cache_min_uses, 1); ngx_conf_merge_bitmask_value(conf->upstream.cache_use_stale, prev->upstream.cache_use_stale, (NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_OFF)); if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_OFF) { conf->upstream.cache_use_stale = NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_OFF; } if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_ERROR) { conf->upstream.cache_use_stale |= NGX_HTTP_UPSTREAM_FT_NOLIVE; } if (conf->upstream.cache_methods == 0) { conf->upstream.cache_methods = prev->upstream.cache_methods; } conf->upstream.cache_methods |= NGX_HTTP_GET|NGX_HTTP_HEAD; ngx_conf_merge_ptr_value(conf->upstream.cache_bypass, prev->upstream.cache_bypass, NULL); ngx_conf_merge_ptr_value(conf->upstream.no_cache, prev->upstream.no_cache, NULL); if (conf->upstream.no_cache && conf->upstream.cache_bypass == NULL) { ngx_log_error(NGX_LOG_WARN, cf->log, 0, "\"fastcgi_no_cache\" functionality has been changed in 0.8.46, " "now it should be used together with \"fastcgi_cache_bypass\""); } 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); #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; } if (conf->upstream.upstream == NULL) { conf->upstream.upstream = prev->upstream.upstream; } if (conf->fastcgi_lengths == NULL) { conf->fastcgi_lengths = prev->fastcgi_lengths; conf->fastcgi_values = prev->fastcgi_values; } if (conf->upstream.upstream || conf->fastcgi_lengths) { clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); if (clcf->handler == NULL && clcf->lmt_excpt) { 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 (ngx_http_fastcgi_merge_params(cf, conf, prev) != NGX_OK) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } static ngx_int_t ngx_http_fastcgi_merge_params(ngx_conf_t *cf, ngx_http_fastcgi_loc_conf_t *conf, ngx_http_fastcgi_loc_conf_t *prev) { u_char *p; size_t size; uintptr_t *code; ngx_uint_t i, nsrc; ngx_array_t headers_names; #if (NGX_HTTP_CACHE) ngx_array_t params_merged; #endif ngx_hash_key_t *hk; ngx_hash_init_t hash; ngx_http_upstream_param_t *src; ngx_http_script_compile_t sc; ngx_http_script_copy_code_t *copy; if (conf->params_source == NULL) { conf->params_source = prev->params_source; if (prev->headers_hash.buckets #if (NGX_HTTP_CACHE) && ((conf->upstream.cache == NULL) == (prev->upstream.cache == NULL)) #endif ) { conf->flushes = prev->flushes; conf->params_len = prev->params_len; conf->params = prev->params; conf->headers_hash = prev->headers_hash; conf->header_params = prev->header_params; return NGX_OK; } } if (conf->params_source == NULL #if (NGX_HTTP_CACHE) && (conf->upstream.cache == NULL) #endif ) { conf->headers_hash.buckets = (void *) 1; return NGX_OK; } conf->params_len = ngx_array_create(cf->pool, 64, 1); if (conf->params_len == NULL) { return NGX_ERROR; } conf->params = ngx_array_create(cf->pool, 512, 1); if (conf->params == 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 (NGX_HTTP_CACHE) if (conf->upstream.cache) { ngx_keyval_t *h; ngx_http_upstream_param_t *s; 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 = ngx_http_fastcgi_cache_headers; 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 = 0; next: h++; } src = params_merged.elts; nsrc = params_merged.nelts; } #endif 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(conf->params_len, sizeof(ngx_http_script_copy_code_t)); if (copy == NULL) { return NGX_ERROR; } copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code; copy->len = src[i].key.len; copy = ngx_array_push_n(conf->params_len, sizeof(ngx_http_script_copy_code_t)); if (copy == NULL) { return NGX_ERROR; } copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code; copy->len = src[i].skip_empty; size = (sizeof(ngx_http_script_copy_code_t) + src[i].key.len + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1); copy = ngx_array_push_n(conf->params, 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 = &conf->flushes; sc.lengths = &conf->params_len; sc.values = &conf->params; if (ngx_http_script_compile(&sc) != NGX_OK) { return NGX_ERROR; } code = ngx_array_push_n(conf->params_len, sizeof(uintptr_t)); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) NULL; code = ngx_array_push_n(conf->params, sizeof(uintptr_t)); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) NULL; } code = ngx_array_push_n(conf->params_len, sizeof(uintptr_t)); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) NULL; conf->header_params = headers_names.nelts; hash.hash = &conf->headers_hash; hash.key = ngx_hash_key_lc; hash.max_size = 512; hash.bucket_size = 64; hash.name = "fastcgi_params_hash"; hash.pool = cf->pool; hash.temp_pool = NULL; return ngx_hash_init(&hash, headers_names.elts, headers_names.nelts); } static ngx_int_t ngx_http_fastcgi_script_name_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; ngx_http_fastcgi_ctx_t *f; ngx_http_fastcgi_loc_conf_t *flcf; flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module); f = ngx_http_fastcgi_split(r, flcf); if (f == NULL) { return NGX_ERROR; } if (f->script_name.len == 0 || f->script_name.data[f->script_name.len - 1] != '/') { v->len = f->script_name.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = f->script_name.data; return NGX_OK; } v->len = f->script_name.len + flcf->index.len; v->data = ngx_pnalloc(r->pool, v->len); if (v->data == NULL) { return NGX_ERROR; } p = ngx_copy(v->data, f->script_name.data, f->script_name.len); ngx_memcpy(p, flcf->index.data, flcf->index.len); return NGX_OK; } static ngx_int_t ngx_http_fastcgi_path_info_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_http_fastcgi_ctx_t *f; ngx_http_fastcgi_loc_conf_t *flcf; flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module); f = ngx_http_fastcgi_split(r, flcf); if (f == NULL) { return NGX_ERROR; } v->len = f->path_info.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = f->path_info.data; return NGX_OK; } static ngx_http_fastcgi_ctx_t * ngx_http_fastcgi_split(ngx_http_request_t *r, ngx_http_fastcgi_loc_conf_t *flcf) { ngx_http_fastcgi_ctx_t *f; #if (NGX_PCRE) ngx_int_t n; int captures[(1 + 2) * 3]; f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module); if (f == NULL) { f = ngx_pcalloc(r->pool, sizeof(ngx_http_fastcgi_ctx_t)); if (f == NULL) { return NULL; } ngx_http_set_ctx(r, f, ngx_http_fastcgi_module); } if (f->script_name.len) { return f; } if (flcf->split_regex == NULL) { f->script_name = r->uri; return f; } n = ngx_regex_exec(flcf->split_regex, &r->uri, captures, (1 + 2) * 3); if (n >= 0) { /* match */ f->script_name.len = captures[3] - captures[2]; f->script_name.data = r->uri.data + captures[2]; f->path_info.len = captures[5] - captures[4]; f->path_info.data = r->uri.data + captures[4]; return f; } if (n == NGX_REGEX_NO_MATCHED) { f->script_name = r->uri; return f; } ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, ngx_regex_exec_n " failed: %i on \"%V\" using \"%V\"", n, &r->uri, &flcf->split_name); return NULL; #else f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module); if (f == NULL) { f = ngx_pcalloc(r->pool, sizeof(ngx_http_fastcgi_ctx_t)); if (f == NULL) { return NULL; } ngx_http_set_ctx(r, f, ngx_http_fastcgi_module); } f->script_name = r->uri; return f; #endif } static char * ngx_http_fastcgi_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_fastcgi_loc_conf_t *flcf = conf; ngx_url_t u; ngx_str_t *value, *url; ngx_uint_t n; ngx_http_core_loc_conf_t *clcf; ngx_http_script_compile_t sc; if (flcf->upstream.upstream || flcf->fastcgi_lengths) { return "is duplicate"; } clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); clcf->handler = ngx_http_fastcgi_handler; if (clcf->name.data[clcf->name.len - 1] == '/') { clcf->auto_redirect = 1; } value = cf->args->elts; url = &value[1]; n = ngx_http_script_variables_count(url); if (n) { ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); sc.cf = cf; sc.source = url; sc.lengths = &flcf->fastcgi_lengths; sc.values = &flcf->fastcgi_values; sc.variables = n; sc.complete_lengths = 1; sc.complete_values = 1; if (ngx_http_script_compile(&sc) != NGX_OK) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } ngx_memzero(&u, sizeof(ngx_url_t)); u.url = value[1]; u.no_resolve = 1; flcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0); if (flcf->upstream.upstream == NULL) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } static char * ngx_http_fastcgi_split_path_info(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { #if (NGX_PCRE) ngx_http_fastcgi_loc_conf_t *flcf = conf; ngx_str_t *value; ngx_regex_compile_t rc; u_char errstr[NGX_MAX_CONF_ERRSTR]; value = cf->args->elts; flcf->split_name = value[1]; ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); rc.pattern = value[1]; rc.pool = cf->pool; rc.err.len = NGX_MAX_CONF_ERRSTR; rc.err.data = errstr; if (ngx_regex_compile(&rc) != NGX_OK) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err); return NGX_CONF_ERROR; } if (rc.captures != 2) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "pattern \"%V\" must have 2 captures", &value[1]); return NGX_CONF_ERROR; } flcf->split_regex = rc.regex; return NGX_CONF_OK; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"%V\" requires PCRE library", &cmd->name); return NGX_CONF_ERROR; #endif } static char * ngx_http_fastcgi_store(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_fastcgi_loc_conf_t *flcf = conf; ngx_str_t *value; ngx_http_script_compile_t sc; if (flcf->upstream.store != NGX_CONF_UNSET || flcf->upstream.store_lengths) { 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 != NGX_CONF_UNSET_PTR && flcf->upstream.cache != NULL) { return "is incompatible with \"fastcgi_cache\""; } #endif if (ngx_strcmp(value[1].data, "on") == 0) { flcf->upstream.store = 1; 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; value = cf->args->elts; if (flcf->upstream.cache != NGX_CONF_UNSET_PTR) { return "is duplicate"; } if (ngx_strcmp(value[1].data, "off") == 0) { flcf->upstream.cache = NULL; return NGX_CONF_OK; } if (flcf->upstream.store > 0 || flcf->upstream.store_lengths) { return "is incompatible with \"fastcgi_store\""; } flcf->upstream.cache = ngx_shared_memory_add(cf, &value[1], 0, &ngx_http_fastcgi_module); if (flcf->upstream.cache == 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.4.6/src/http/modules/ngx_http_flv_module.c000644 001751 001751 00000014777 12305336445 023511 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) { if (ngx_close_file(of.fd) == NGX_FILE_ERROR) { ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, ngx_close_file_n " \"%s\" failed", path.data); } return NGX_DECLINED; } r->root_tested = !r->error_page; start = 0; len = of.size; i = 1; if (r->args.len) { if (ngx_http_arg(r, (u_char *) "start", 5, &value) == NGX_OK) { start = ngx_atoof(value.data, value.len); if (start == NGX_ERROR || start >= len) { start = 0; } if (start) { len = sizeof(ngx_flv_header) - 1 + len - start; i = 0; } } } log->action = "sending flv to client"; r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = len; r->headers_out.last_modified_time = of.mtime; if (ngx_http_set_etag(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_http_set_content_type(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (i == 0) { b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); if (b == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } b->pos = ngx_flv_header; b->last = ngx_flv_header + sizeof(ngx_flv_header) - 1; b->memory = 1; out[0].buf = b; out[0].next = &out[1]; } b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); if (b == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t)); if (b->file == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } r->allow_ranges = 1; rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { return rc; } b->file_pos = start; b->file_last = of.size; b->in_file = b->file_last ? 1: 0; b->last_buf = (r == r->main) ? 1 : 0; b->last_in_chain = 1; b->file->fd = of.fd; b->file->name = path; b->file->log = log; b->file->directio = of.is_directio; out[1].buf = b; out[1].next = NULL; return ngx_http_output_filter(r, &out[i]); } static char * ngx_http_flv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_core_loc_conf_t *clcf; clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); clcf->handler = ngx_http_flv_handler; return NGX_CONF_OK; } nginx-1.4.6/src/http/modules/ngx_http_geo_module.c000644 001751 001751 00000124577 12305336445 023474 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 default: /* AF_INET */ sin = (struct sockaddr_in *) addr.sockaddr; inaddr = ntohl(sin->sin_addr.s_addr); vv = (ngx_http_variable_value_t *) ngx_radix32tree_find(ctx->u.trees.tree, inaddr); break; } done: *v = *vv; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http geo: %v", v); return NGX_OK; } static ngx_int_t ngx_http_geo_range_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_http_geo_ctx_t *ctx = (ngx_http_geo_ctx_t *) data; in_addr_t inaddr; ngx_addr_t addr; ngx_uint_t n; struct sockaddr_in *sin; ngx_http_geo_range_t *range; #if (NGX_HAVE_INET6) u_char *p; struct in6_addr *inaddr6; #endif *v = *ctx->u.high.default_value; if (ngx_http_geo_addr(r, ctx, &addr) == NGX_OK) { switch (addr.sockaddr->sa_family) { #if (NGX_HAVE_INET6) case AF_INET6: inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr; if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { p = inaddr6->s6_addr; inaddr = p[12] << 24; inaddr += p[13] << 16; inaddr += p[14] << 8; inaddr += p[15]; } else { inaddr = INADDR_NONE; } break; #endif default: /* AF_INET */ sin = (struct sockaddr_in *) addr.sockaddr; inaddr = ntohl(sin->sin_addr.s_addr); break; } } else { inaddr = INADDR_NONE; } if (ctx->u.high.low) { range = ctx->u.high.low[inaddr >> 16]; if (range) { n = inaddr & 0xffff; do { if (n >= (ngx_uint_t) range->start && n <= (ngx_uint_t) range->end) { *v = *range->value; break; } } while ((++range)->value); } } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http geo: %v", v); return NGX_OK; } static ngx_int_t ngx_http_geo_addr(ngx_http_request_t *r, ngx_http_geo_ctx_t *ctx, ngx_addr_t *addr) { ngx_array_t *xfwd; if (ngx_http_geo_real_addr(r, ctx, addr) != NGX_OK) { return NGX_ERROR; } xfwd = &r->headers_in.x_forwarded_for; if (xfwd->nelts > 0 && ctx->proxies != NULL) { (void) ngx_http_get_forwarded_addr(r, addr, xfwd, NULL, ctx->proxies, ctx->proxy_recursive); } return NGX_OK; } static ngx_int_t ngx_http_geo_real_addr(ngx_http_request_t *r, ngx_http_geo_ctx_t *ctx, ngx_addr_t *addr) { ngx_http_variable_value_t *v; if (ctx->index == -1) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http geo started: %V", &r->connection->addr_text); addr->sockaddr = r->connection->sockaddr; addr->socklen = r->connection->socklen; /* addr->name = r->connection->addr_text; */ return NGX_OK; } v = ngx_http_get_flushed_variable(r, ctx->index); if (v == NULL || v->not_found) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http geo not found"); return NGX_ERROR; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http geo started: %v", v); if (ngx_parse_addr(r->pool, addr, v->data, v->len) == NGX_OK) { return NGX_OK; } return NGX_ERROR; } static char * ngx_http_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { char *rv; size_t len; ngx_str_t *value, name; ngx_uint_t i; ngx_conf_t save; ngx_pool_t *pool; ngx_array_t *a; ngx_http_variable_t *var; ngx_http_geo_ctx_t *geo; ngx_http_geo_conf_ctx_t ctx; #if (NGX_HAVE_INET6) static struct in6_addr zero; #endif value = cf->args->elts; geo = ngx_palloc(cf->pool, sizeof(ngx_http_geo_ctx_t)); if (geo == NULL) { return NGX_CONF_ERROR; } name = value[1]; if (name.data[0] != '$') { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid variable name \"%V\"", &name); return NGX_CONF_ERROR; } name.len--; name.data++; if (cf->args->nelts == 3) { geo->index = ngx_http_get_variable_index(cf, &name); if (geo->index == NGX_ERROR) { return NGX_CONF_ERROR; } name = value[2]; if (name.data[0] != '$') { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid variable name \"%V\"", &name); return NGX_CONF_ERROR; } name.len--; name.data++; } else { geo->index = -1; } var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE); if (var == NULL) { return NGX_CONF_ERROR; } pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log); if (pool == NULL) { return NGX_CONF_ERROR; } ngx_memzero(&ctx, sizeof(ngx_http_geo_conf_ctx_t)); ctx.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log); if (ctx.temp_pool == NULL) { return NGX_CONF_ERROR; } ngx_rbtree_init(&ctx.rbtree, &ctx.sentinel, ngx_str_rbtree_insert_value); ctx.pool = cf->pool; ctx.data_size = sizeof(ngx_http_geo_header_t) + sizeof(ngx_http_variable_value_t) + 0x10000 * sizeof(ngx_http_geo_range_t *); ctx.allow_binary_include = 1; save = *cf; cf->pool = pool; cf->ctx = &ctx; cf->handler = ngx_http_geo; cf->handler_conf = conf; rv = ngx_conf_parse(cf, NULL); *cf = save; geo->proxies = ctx.proxies; geo->proxy_recursive = ctx.proxy_recursive; if (ctx.ranges) { if (ctx.high.low && !ctx.binary_include) { for (i = 0; i < 0x10000; i++) { a = (ngx_array_t *) ctx.high.low[i]; if (a == NULL || a->nelts == 0) { continue; } len = a->nelts * sizeof(ngx_http_geo_range_t); ctx.high.low[i] = ngx_palloc(cf->pool, len + sizeof(void *)); if (ctx.high.low[i] == NULL) { return NGX_CONF_ERROR; } ngx_memcpy(ctx.high.low[i], a->elts, len); ctx.high.low[i][a->nelts].value = NULL; ctx.data_size += len + sizeof(void *); } if (ctx.allow_binary_include && !ctx.outside_entries && ctx.entries > 100000 && ctx.includes == 1) { ngx_http_geo_create_binary_base(&ctx); } } if (ctx.high.default_value == NULL) { ctx.high.default_value = &ngx_http_variable_null_value; } geo->u.high = ctx.high; var->get_handler = ngx_http_geo_range_variable; var->data = (uintptr_t) geo; ngx_destroy_pool(ctx.temp_pool); ngx_destroy_pool(pool); } else { if (ctx.tree == NULL) { ctx.tree = ngx_radix_tree_create(cf->pool, -1); if (ctx.tree == NULL) { return NGX_CONF_ERROR; } } geo->u.trees.tree = ctx.tree; #if (NGX_HAVE_INET6) if (ctx.tree6 == NULL) { ctx.tree6 = ngx_radix_tree_create(cf->pool, -1); if (ctx.tree6 == NULL) { return NGX_CONF_ERROR; } } geo->u.trees.tree6 = ctx.tree6; #endif var->get_handler = ngx_http_geo_cidr_variable; var->data = (uintptr_t) geo; ngx_destroy_pool(ctx.temp_pool); ngx_destroy_pool(pool); if (ngx_radix32tree_insert(ctx.tree, 0, 0, (uintptr_t) &ngx_http_variable_null_value) == NGX_ERROR) { return NGX_CONF_ERROR; } /* NGX_BUSY is okay (default was set explicitly) */ #if (NGX_HAVE_INET6) if (ngx_radix128tree_insert(ctx.tree6, zero.s6_addr, zero.s6_addr, (uintptr_t) &ngx_http_variable_null_value) == NGX_ERROR) { return NGX_CONF_ERROR; } #endif } return rv; } static char * ngx_http_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) { char *rv; ngx_str_t *value; ngx_cidr_t cidr; ngx_http_geo_conf_ctx_t *ctx; ctx = cf->ctx; value = cf->args->elts; if (cf->args->nelts == 1) { if (ngx_strcmp(value[0].data, "ranges") == 0) { if (ctx->tree #if (NGX_HAVE_INET6) || ctx->tree6 #endif ) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the \"ranges\" directive must be " "the first directive inside \"geo\" block"); goto failed; } ctx->ranges = 1; rv = NGX_CONF_OK; goto done; } else if (ngx_strcmp(value[0].data, "proxy_recursive") == 0) { ctx->proxy_recursive = 1; rv = NGX_CONF_OK; goto done; } } if (cf->args->nelts != 2) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid number of the geo parameters"); goto failed; } if (ngx_strcmp(value[0].data, "include") == 0) { rv = ngx_http_geo_include(cf, ctx, &value[1]); goto done; } else if (ngx_strcmp(value[0].data, "proxy") == 0) { if (ngx_http_geo_cidr_value(cf, &value[1], &cidr) != NGX_OK) { goto failed; } rv = ngx_http_geo_add_proxy(cf, ctx, &cidr); goto done; } if (ctx->ranges) { rv = ngx_http_geo_range(cf, ctx, value); } else { rv = ngx_http_geo_cidr(cf, ctx, value); } done: ngx_reset_pool(cf->pool); return rv; failed: ngx_reset_pool(cf->pool); return NGX_CONF_ERROR; } static char * ngx_http_geo_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, ngx_str_t *value) { u_char *p, *last; in_addr_t start, end; ngx_str_t *net; ngx_uint_t del; if (ngx_strcmp(value[0].data, "default") == 0) { if (ctx->high.default_value) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "duplicate default geo range value: \"%V\", old value: \"%v\"", &value[1], ctx->high.default_value); } ctx->high.default_value = ngx_http_geo_value(cf, ctx, &value[1]); if (ctx->high.default_value == NULL) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } if (ctx->binary_include) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "binary geo range base \"%s\" cannot be mixed with usual entries", ctx->include_name.data); return NGX_CONF_ERROR; } if (ctx->high.low == NULL) { ctx->high.low = ngx_pcalloc(ctx->pool, 0x10000 * sizeof(ngx_http_geo_range_t *)); if (ctx->high.low == NULL) { return NGX_CONF_ERROR; } } ctx->entries++; ctx->outside_entries = 1; if (ngx_strcmp(value[0].data, "delete") == 0) { net = &value[1]; del = 1; } else { net = &value[0]; del = 0; } last = net->data + net->len; p = ngx_strlchr(net->data, last, '-'); if (p == NULL) { goto invalid; } start = ngx_inet_addr(net->data, p - net->data); if (start == INADDR_NONE) { goto invalid; } start = ntohl(start); p++; end = ngx_inet_addr(p, last - p); if (end == INADDR_NONE) { goto invalid; } end = ntohl(end); if (start > end) { goto invalid; } if (del) { if (ngx_http_geo_delete_range(cf, ctx, start, end)) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "no address range \"%V\" to delete", net); } return NGX_CONF_OK; } ctx->value = ngx_http_geo_value(cf, ctx, &value[1]); if (ctx->value == NULL) { return NGX_CONF_ERROR; } ctx->net = net; return ngx_http_geo_add_range(cf, ctx, start, end); invalid: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid range \"%V\"", net); return NGX_CONF_ERROR; } /* the add procedure is optimized to add a growing up sequence */ static char * ngx_http_geo_add_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end) { in_addr_t n; ngx_uint_t h, i, s, e; ngx_array_t *a; ngx_http_geo_range_t *range; for (n = start; n <= end; n = (n + 0x10000) & 0xffff0000) { h = n >> 16; if (n == start) { s = n & 0xffff; } else { s = 0; } if ((n | 0xffff) > end) { e = end & 0xffff; } else { e = 0xffff; } a = (ngx_array_t *) ctx->high.low[h]; if (a == NULL) { a = ngx_array_create(ctx->temp_pool, 64, sizeof(ngx_http_geo_range_t)); if (a == NULL) { return NGX_CONF_ERROR; } ctx->high.low[h] = (ngx_http_geo_range_t *) a; } i = a->nelts; range = a->elts; while (i) { i--; if (e < (ngx_uint_t) range[i].start) { continue; } if (s > (ngx_uint_t) range[i].end) { /* add after the range */ range = ngx_array_push(a); if (range == NULL) { return NGX_CONF_ERROR; } range = a->elts; ngx_memmove(&range[i + 2], &range[i + 1], (a->nelts - 2 - i) * sizeof(ngx_http_geo_range_t)); range[i + 1].start = (u_short) s; range[i + 1].end = (u_short) e; range[i + 1].value = ctx->value; goto next; } if (s == (ngx_uint_t) range[i].start && e == (ngx_uint_t) range[i].end) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "duplicate range \"%V\", value: \"%v\", old value: \"%v\"", ctx->net, ctx->value, range[i].value); range[i].value = ctx->value; goto next; } if (s > (ngx_uint_t) range[i].start && e < (ngx_uint_t) range[i].end) { /* split the range and insert the new one */ range = ngx_array_push(a); if (range == NULL) { return NGX_CONF_ERROR; } range = ngx_array_push(a); if (range == NULL) { return NGX_CONF_ERROR; } range = a->elts; ngx_memmove(&range[i + 3], &range[i + 1], (a->nelts - 3 - i) * sizeof(ngx_http_geo_range_t)); range[i + 2].start = (u_short) (e + 1); range[i + 2].end = range[i].end; range[i + 2].value = range[i].value; range[i + 1].start = (u_short) s; range[i + 1].end = (u_short) e; range[i + 1].value = ctx->value; range[i].end = (u_short) (s - 1); goto next; } if (s == (ngx_uint_t) range[i].start && e < (ngx_uint_t) range[i].end) { /* shift the range start and insert the new range */ range = ngx_array_push(a); if (range == NULL) { return NGX_CONF_ERROR; } range = a->elts; ngx_memmove(&range[i + 1], &range[i], (a->nelts - 1 - i) * sizeof(ngx_http_geo_range_t)); range[i + 1].start = (u_short) (e + 1); range[i].start = (u_short) s; range[i].end = (u_short) e; range[i].value = ctx->value; goto next; } if (s > (ngx_uint_t) range[i].start && e == (ngx_uint_t) range[i].end) { /* shift the range end and insert the new range */ range = ngx_array_push(a); if (range == NULL) { return NGX_CONF_ERROR; } range = a->elts; ngx_memmove(&range[i + 2], &range[i + 1], (a->nelts - 2 - i) * sizeof(ngx_http_geo_range_t)); range[i + 1].start = (u_short) s; range[i + 1].end = (u_short) e; range[i + 1].value = ctx->value; range[i].end = (u_short) (s - 1); goto next; } s = (ngx_uint_t) range[i].start; e = (ngx_uint_t) range[i].end; ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "range \"%V\" overlaps \"%d.%d.%d.%d-%d.%d.%d.%d\"", ctx->net, h >> 8, h & 0xff, s >> 8, s & 0xff, h >> 8, h & 0xff, e >> 8, e & 0xff); return NGX_CONF_ERROR; } /* add the first range */ range = ngx_array_push(a); if (range == NULL) { return NGX_CONF_ERROR; } range->start = (u_short) s; range->end = (u_short) e; range->value = ctx->value; next: continue; } return NGX_CONF_OK; } static ngx_uint_t ngx_http_geo_delete_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end) { in_addr_t n; ngx_uint_t h, i, s, e, warn; ngx_array_t *a; ngx_http_geo_range_t *range; warn = 0; for (n = start; n <= end; n += 0x10000) { h = n >> 16; if (n == start) { s = n & 0xffff; } else { s = 0; } if ((n | 0xffff) > end) { e = end & 0xffff; } else { e = 0xffff; } a = (ngx_array_t *) ctx->high.low[h]; if (a == NULL) { warn = 1; continue; } range = a->elts; for (i = 0; i < a->nelts; i++) { if (s == (ngx_uint_t) range[i].start && e == (ngx_uint_t) range[i].end) { ngx_memmove(&range[i], &range[i + 1], (a->nelts - 1 - i) * sizeof(ngx_http_geo_range_t)); a->nelts--; break; } if (s != (ngx_uint_t) range[i].start && e != (ngx_uint_t) range[i].end) { continue; } warn = 1; } } return warn; } static char * ngx_http_geo_cidr(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, ngx_str_t *value) { char *rv; ngx_int_t rc, del; ngx_str_t *net; ngx_cidr_t cidr; if (ctx->tree == NULL) { ctx->tree = ngx_radix_tree_create(ctx->pool, -1); if (ctx->tree == NULL) { return NGX_CONF_ERROR; } } #if (NGX_HAVE_INET6) if (ctx->tree6 == NULL) { ctx->tree6 = ngx_radix_tree_create(ctx->pool, -1); if (ctx->tree6 == NULL) { return NGX_CONF_ERROR; } } #endif if (ngx_strcmp(value[0].data, "default") == 0) { cidr.family = AF_INET; cidr.u.in.addr = 0; cidr.u.in.mask = 0; rv = ngx_http_geo_cidr_add(cf, ctx, &cidr, &value[1], &value[0]); if (rv != NGX_CONF_OK) { return rv; } #if (NGX_HAVE_INET6) cidr.family = AF_INET6; ngx_memzero(&cidr.u.in6, sizeof(ngx_in6_cidr_t)); rv = ngx_http_geo_cidr_add(cf, ctx, &cidr, &value[1], &value[0]); if (rv != NGX_CONF_OK) { return rv; } #endif return NGX_CONF_OK; } if (ngx_strcmp(value[0].data, "delete") == 0) { net = &value[1]; del = 1; } else { net = &value[0]; del = 0; } if (ngx_http_geo_cidr_value(cf, net, &cidr) != NGX_OK) { return NGX_CONF_ERROR; } if (cidr.family == AF_INET) { cidr.u.in.addr = ntohl(cidr.u.in.addr); cidr.u.in.mask = ntohl(cidr.u.in.mask); } if (del) { switch (cidr.family) { #if (NGX_HAVE_INET6) case AF_INET6: rc = ngx_radix128tree_delete(ctx->tree6, cidr.u.in6.addr.s6_addr, cidr.u.in6.mask.s6_addr); break; #endif default: /* AF_INET */ rc = ngx_radix32tree_delete(ctx->tree, cidr.u.in.addr, cidr.u.in.mask); break; } if (rc != NGX_OK) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "no network \"%V\" to delete", net); } return NGX_CONF_OK; } return ngx_http_geo_cidr_add(cf, ctx, &cidr, &value[1], net); } static char * ngx_http_geo_cidr_add(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, ngx_cidr_t *cidr, ngx_str_t *value, ngx_str_t *net) { ngx_int_t rc; ngx_http_variable_value_t *val, *old; val = ngx_http_geo_value(cf, ctx, value); if (val == NULL) { return NGX_CONF_ERROR; } switch (cidr->family) { #if (NGX_HAVE_INET6) case AF_INET6: rc = ngx_radix128tree_insert(ctx->tree6, cidr->u.in6.addr.s6_addr, cidr->u.in6.mask.s6_addr, (uintptr_t) val); if (rc == NGX_OK) { return NGX_CONF_OK; } if (rc == NGX_ERROR) { return NGX_CONF_ERROR; } /* rc == NGX_BUSY */ old = (ngx_http_variable_value_t *) ngx_radix128tree_find(ctx->tree6, cidr->u.in6.addr.s6_addr); ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "duplicate network \"%V\", value: \"%v\", old value: \"%v\"", net, val, old); rc = ngx_radix128tree_delete(ctx->tree6, cidr->u.in6.addr.s6_addr, cidr->u.in6.mask.s6_addr); if (rc == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree"); return NGX_CONF_ERROR; } rc = ngx_radix128tree_insert(ctx->tree6, cidr->u.in6.addr.s6_addr, cidr->u.in6.mask.s6_addr, (uintptr_t) val); break; #endif default: /* AF_INET */ rc = ngx_radix32tree_insert(ctx->tree, cidr->u.in.addr, cidr->u.in.mask, (uintptr_t) val); if (rc == NGX_OK) { return NGX_CONF_OK; } if (rc == NGX_ERROR) { return NGX_CONF_ERROR; } /* rc == NGX_BUSY */ old = (ngx_http_variable_value_t *) ngx_radix32tree_find(ctx->tree, cidr->u.in.addr); ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "duplicate network \"%V\", value: \"%v\", old value: \"%v\"", net, val, old); rc = ngx_radix32tree_delete(ctx->tree, cidr->u.in.addr, cidr->u.in.mask); if (rc == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree"); return NGX_CONF_ERROR; } rc = ngx_radix32tree_insert(ctx->tree, cidr->u.in.addr, cidr->u.in.mask, (uintptr_t) val); break; } if (rc == NGX_OK) { return NGX_CONF_OK; } return NGX_CONF_ERROR; } static ngx_http_variable_value_t * ngx_http_geo_value(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, ngx_str_t *value) { uint32_t hash; ngx_http_variable_value_t *val; ngx_http_geo_variable_value_node_t *gvvn; hash = ngx_crc32_long(value->data, value->len); gvvn = (ngx_http_geo_variable_value_node_t *) ngx_str_rbtree_lookup(&ctx->rbtree, value, hash); if (gvvn) { return gvvn->value; } val = ngx_palloc(ctx->pool, sizeof(ngx_http_variable_value_t)); if (val == NULL) { return NULL; } val->len = value->len; val->data = ngx_pstrdup(ctx->pool, value); if (val->data == NULL) { return NULL; } val->valid = 1; val->no_cacheable = 0; val->not_found = 0; gvvn = ngx_palloc(ctx->temp_pool, sizeof(ngx_http_geo_variable_value_node_t)); if (gvvn == NULL) { return NULL; } gvvn->sn.node.key = hash; gvvn->sn.str.len = val->len; gvvn->sn.str.data = val->data; gvvn->value = val; gvvn->offset = 0; ngx_rbtree_insert(&ctx->rbtree, &gvvn->sn.node); ctx->data_size += ngx_align(sizeof(ngx_http_variable_value_t) + value->len, sizeof(void *)); return val; } static char * ngx_http_geo_add_proxy(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, ngx_cidr_t *cidr) { ngx_cidr_t *c; if (ctx->proxies == NULL) { ctx->proxies = ngx_array_create(ctx->pool, 4, sizeof(ngx_cidr_t)); if (ctx->proxies == NULL) { return NGX_CONF_ERROR; } } c = ngx_array_push(ctx->proxies); if (c == NULL) { return NGX_CONF_ERROR; } *c = *cidr; return NGX_CONF_OK; } static ngx_int_t ngx_http_geo_cidr_value(ngx_conf_t *cf, ngx_str_t *net, ngx_cidr_t *cidr) { ngx_int_t rc; if (ngx_strcmp(net->data, "255.255.255.255") == 0) { cidr->family = AF_INET; cidr->u.in.addr = 0xffffffff; cidr->u.in.mask = 0xffffffff; return NGX_OK; } rc = ngx_ptocidr(net, cidr); if (rc == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid network \"%V\"", net); return NGX_ERROR; } if (rc == NGX_DONE) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "low address bits of %V are meaningless", net); } return NGX_OK; } static char * ngx_http_geo_include(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, ngx_str_t *name) { char *rv; ngx_str_t file; file.len = name->len + 4; file.data = ngx_pnalloc(ctx->temp_pool, name->len + 5); if (file.data == NULL) { return NGX_CONF_ERROR; } ngx_sprintf(file.data, "%V.bin%Z", name); if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK) { return NGX_CONF_ERROR; } if (ctx->ranges) { ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data); switch (ngx_http_geo_include_binary_base(cf, ctx, &file)) { case NGX_OK: return NGX_CONF_OK; case NGX_ERROR: return NGX_CONF_ERROR; default: break; } } file.len -= 4; file.data[file.len] = '\0'; ctx->include_name = file; if (ctx->outside_entries) { ctx->allow_binary_include = 0; } ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data); rv = ngx_conf_parse(cf, &file); ctx->includes++; ctx->outside_entries = 0; return rv; } static ngx_int_t ngx_http_geo_include_binary_base(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, ngx_str_t *name) { u_char *base, ch; time_t mtime; size_t size, len; ssize_t n; uint32_t crc32; ngx_err_t err; ngx_int_t rc; ngx_uint_t i; ngx_file_t file; ngx_file_info_t fi; ngx_http_geo_range_t *range, **ranges; ngx_http_geo_header_t *header; ngx_http_variable_value_t *vv; ngx_memzero(&file, sizeof(ngx_file_t)); file.name = *name; file.log = cf->log; file.fd = ngx_open_file(name->data, NGX_FILE_RDONLY, 0, 0); if (file.fd == NGX_INVALID_FILE) { err = ngx_errno; if (err != NGX_ENOENT) { ngx_conf_log_error(NGX_LOG_CRIT, cf, err, ngx_open_file_n " \"%s\" failed", name->data); } return NGX_DECLINED; } if (ctx->outside_entries) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "binary geo range base \"%s\" cannot be mixed with usual entries", name->data); rc = NGX_ERROR; goto done; } if (ctx->binary_include) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "second binary geo range base \"%s\" cannot be mixed with \"%s\"", name->data, ctx->include_name.data); rc = NGX_ERROR; goto done; } if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) { ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, ngx_fd_info_n " \"%s\" failed", name->data); goto failed; } size = (size_t) ngx_file_size(&fi); mtime = ngx_file_mtime(&fi); ch = name->data[name->len - 4]; name->data[name->len - 4] = '\0'; if (ngx_file_info(name->data, &fi) == NGX_FILE_ERROR) { ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, ngx_file_info_n " \"%s\" failed", name->data); goto failed; } name->data[name->len - 4] = ch; if (mtime < ngx_file_mtime(&fi)) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "stale binary geo range base \"%s\"", name->data); goto failed; } base = ngx_palloc(ctx->pool, size); if (base == NULL) { goto failed; } n = ngx_read_file(&file, base, size, 0); if (n == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, ngx_read_file_n " \"%s\" failed", name->data); goto failed; } if ((size_t) n != size) { ngx_conf_log_error(NGX_LOG_CRIT, cf, 0, ngx_read_file_n " \"%s\" returned only %z bytes instead of %z", name->data, n, size); goto failed; } header = (ngx_http_geo_header_t *) base; if (size < 16 || ngx_memcmp(&ngx_http_geo_header, header, 12) != 0) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "incompatible binary geo range base \"%s\"", name->data); goto failed; } ngx_crc32_init(crc32); vv = (ngx_http_variable_value_t *) (base + sizeof(ngx_http_geo_header_t)); while(vv->data) { len = ngx_align(sizeof(ngx_http_variable_value_t) + vv->len, sizeof(void *)); ngx_crc32_update(&crc32, (u_char *) vv, len); vv->data += (size_t) base; vv = (ngx_http_variable_value_t *) ((u_char *) vv + len); } ngx_crc32_update(&crc32, (u_char *) vv, sizeof(ngx_http_variable_value_t)); vv++; ranges = (ngx_http_geo_range_t **) vv; for (i = 0; i < 0x10000; i++) { ngx_crc32_update(&crc32, (u_char *) &ranges[i], sizeof(void *)); if (ranges[i]) { ranges[i] = (ngx_http_geo_range_t *) ((u_char *) ranges[i] + (size_t) base); } } range = (ngx_http_geo_range_t *) &ranges[0x10000]; while ((u_char *) range < base + size) { while (range->value) { ngx_crc32_update(&crc32, (u_char *) range, sizeof(ngx_http_geo_range_t)); range->value = (ngx_http_variable_value_t *) ((u_char *) range->value + (size_t) base); range++; } ngx_crc32_update(&crc32, (u_char *) range, sizeof(void *)); range = (ngx_http_geo_range_t *) ((u_char *) range + sizeof(void *)); } ngx_crc32_final(crc32); if (crc32 != header->crc32) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "CRC32 mismatch in binary geo range base \"%s\"", name->data); goto failed; } ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0, "using binary geo range base \"%s\"", name->data); ctx->include_name = *name; ctx->binary_include = 1; ctx->high.low = ranges; rc = NGX_OK; goto done; failed: rc = NGX_DECLINED; done: if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, ngx_close_file_n " \"%s\" failed", name->data); } return rc; } static void ngx_http_geo_create_binary_base(ngx_http_geo_conf_ctx_t *ctx) { u_char *p; uint32_t hash; ngx_str_t s; ngx_uint_t i; ngx_file_mapping_t fm; ngx_http_geo_range_t *r, *range, **ranges; ngx_http_geo_header_t *header; ngx_http_geo_variable_value_node_t *gvvn; fm.name = ngx_pnalloc(ctx->temp_pool, ctx->include_name.len + 5); if (fm.name == NULL) { return; } ngx_sprintf(fm.name, "%V.bin%Z", &ctx->include_name); fm.size = ctx->data_size; fm.log = ctx->pool->log; ngx_log_error(NGX_LOG_NOTICE, fm.log, 0, "creating binary geo range base \"%s\"", fm.name); if (ngx_create_file_mapping(&fm) != NGX_OK) { return; } p = ngx_cpymem(fm.addr, &ngx_http_geo_header, sizeof(ngx_http_geo_header_t)); p = ngx_http_geo_copy_values(fm.addr, p, ctx->rbtree.root, ctx->rbtree.sentinel); p += sizeof(ngx_http_variable_value_t); ranges = (ngx_http_geo_range_t **) p; p += 0x10000 * sizeof(ngx_http_geo_range_t *); for (i = 0; i < 0x10000; i++) { r = ctx->high.low[i]; if (r == NULL) { continue; } range = (ngx_http_geo_range_t *) p; ranges[i] = (ngx_http_geo_range_t *) (p - (u_char *) fm.addr); do { s.len = r->value->len; s.data = r->value->data; hash = ngx_crc32_long(s.data, s.len); gvvn = (ngx_http_geo_variable_value_node_t *) ngx_str_rbtree_lookup(&ctx->rbtree, &s, hash); range->value = (ngx_http_variable_value_t *) gvvn->offset; range->start = r->start; range->end = r->end; range++; } while ((++r)->value); range->value = NULL; p = (u_char *) range + sizeof(void *); } header = fm.addr; header->crc32 = ngx_crc32_long((u_char *) fm.addr + sizeof(ngx_http_geo_header_t), fm.size - sizeof(ngx_http_geo_header_t)); ngx_close_file_mapping(&fm); } static u_char * ngx_http_geo_copy_values(u_char *base, u_char *p, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) { ngx_http_variable_value_t *vv; ngx_http_geo_variable_value_node_t *gvvn; if (node == sentinel) { return p; } gvvn = (ngx_http_geo_variable_value_node_t *) node; gvvn->offset = p - base; vv = (ngx_http_variable_value_t *) p; *vv = *gvvn->value; p += sizeof(ngx_http_variable_value_t); vv->data = (u_char *) (p - base); p = ngx_cpymem(p, gvvn->sn.str.data, gvvn->sn.str.len); p = ngx_align_ptr(p, sizeof(void *)); p = ngx_http_geo_copy_values(base, p, node->left, sentinel); return ngx_http_geo_copy_values(base, p, node->right, sentinel); } nginx-1.4.6/src/http/modules/ngx_http_geoip_module.c000644 001751 001751 00000053364 12305336445 024020 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_null_string, NULL, NULL, 0, 0, 0 } }; static u_long ngx_http_geoip_addr(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf) { ngx_addr_t addr; ngx_array_t *xfwd; struct sockaddr_in *sin; addr.sockaddr = r->connection->sockaddr; addr.socklen = r->connection->socklen; /* addr.name = r->connection->addr_text; */ xfwd = &r->headers_in.x_forwarded_for; if (xfwd->nelts > 0 && gcf->proxies != NULL) { (void) ngx_http_get_forwarded_addr(r, &addr, xfwd, NULL, gcf->proxies, gcf->proxy_recursive); } #if (NGX_HAVE_INET6) if (addr.sockaddr->sa_family == AF_INET6) { u_char *p; in_addr_t inaddr; struct in6_addr *inaddr6; inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr; if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { p = inaddr6->s6_addr; inaddr = p[12] << 24; inaddr += p[13] << 16; inaddr += p[14] << 8; inaddr += p[15]; return inaddr; } } #endif if (addr.sockaddr->sa_family != AF_INET) { return INADDR_NONE; } sin = (struct sockaddr_in *) addr.sockaddr; return ntohl(sin->sin_addr.s_addr); } #if (NGX_HAVE_GEOIP_V6) static geoipv6_t ngx_http_geoip_addr_v6(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf) { ngx_addr_t addr; ngx_array_t *xfwd; in_addr_t addr4; struct in6_addr addr6; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; addr.sockaddr = r->connection->sockaddr; addr.socklen = r->connection->socklen; /* addr.name = r->connection->addr_text; */ xfwd = &r->headers_in.x_forwarded_for; if (xfwd->nelts > 0 && gcf->proxies != NULL) { (void) ngx_http_get_forwarded_addr(r, &addr, xfwd, NULL, gcf->proxies, gcf->proxy_recursive); } switch (addr.sockaddr->sa_family) { case AF_INET: /* Produce IPv4-mapped IPv6 address. */ sin = (struct sockaddr_in *) addr.sockaddr; addr4 = ntohl(sin->sin_addr.s_addr); ngx_memzero(&addr6, sizeof(struct in6_addr)); addr6.s6_addr[10] = 0xff; addr6.s6_addr[11] = 0xff; addr6.s6_addr[12] = addr4 >> 24; addr6.s6_addr[13] = addr4 >> 16; addr6.s6_addr[14] = addr4 >> 8; addr6.s6_addr[15] = addr4; return addr6; case AF_INET6: sin6 = (struct sockaddr_in6 *) addr.sockaddr; return sin6->sin6_addr; default: return in6addr_any; } } #endif static ngx_int_t ngx_http_geoip_country_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_http_geoip_variable_handler_pt handler = ngx_http_geoip_country_functions[data]; #if (NGX_HAVE_GEOIP_V6) ngx_http_geoip_variable_handler_v6_pt handler_v6 = ngx_http_geoip_country_v6_functions[data]; #endif const char *val; ngx_http_geoip_conf_t *gcf; gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module); if (gcf->country == NULL) { goto not_found; } #if (NGX_HAVE_GEOIP_V6) val = gcf->country_v6 ? handler_v6(gcf->country, ngx_http_geoip_addr_v6(r, gcf)) : handler(gcf->country, ngx_http_geoip_addr(r, gcf)); #else val = handler(gcf->country, ngx_http_geoip_addr(r, gcf)); #endif if (val == NULL) { goto not_found; } v->len = ngx_strlen(val); v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = (u_char *) val; return NGX_OK; not_found: v->not_found = 1; return NGX_OK; } static ngx_int_t ngx_http_geoip_org_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { size_t len; char *val; ngx_http_geoip_conf_t *gcf; gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module); if (gcf->org == NULL) { goto not_found; } #if (NGX_HAVE_GEOIP_V6) val = gcf->org_v6 ? GeoIP_name_by_ipnum_v6(gcf->org, ngx_http_geoip_addr_v6(r, gcf)) : GeoIP_name_by_ipnum(gcf->org, ngx_http_geoip_addr(r, gcf)); #else val = GeoIP_name_by_ipnum(gcf->org, ngx_http_geoip_addr(r, gcf)); #endif if (val == NULL) { goto not_found; } len = ngx_strlen(val); v->data = ngx_pnalloc(r->pool, len); if (v->data == NULL) { ngx_free(val); return NGX_ERROR; } ngx_memcpy(v->data, val, len); v->len = len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; ngx_free(val); return NGX_OK; not_found: v->not_found = 1; return NGX_OK; } static ngx_int_t ngx_http_geoip_city_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { char *val; size_t len; GeoIPRecord *gr; gr = ngx_http_geoip_get_city_record(r); if (gr == NULL) { goto not_found; } val = *(char **) ((char *) gr + data); if (val == NULL) { goto no_value; } len = ngx_strlen(val); v->data = ngx_pnalloc(r->pool, len); if (v->data == NULL) { GeoIPRecord_delete(gr); return NGX_ERROR; } ngx_memcpy(v->data, val, len); v->len = len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; GeoIPRecord_delete(gr); return NGX_OK; no_value: GeoIPRecord_delete(gr); not_found: v->not_found = 1; return NGX_OK; } static ngx_int_t ngx_http_geoip_region_name_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { size_t len; const char *val; GeoIPRecord *gr; gr = ngx_http_geoip_get_city_record(r); if (gr == NULL) { goto not_found; } val = GeoIP_region_name_by_code(gr->country_code, gr->region); GeoIPRecord_delete(gr); if (val == NULL) { goto not_found; } len = ngx_strlen(val); v->data = ngx_pnalloc(r->pool, len); if (v->data == NULL) { return NGX_ERROR; } ngx_memcpy(v->data, val, len); v->len = len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; return NGX_OK; not_found: v->not_found = 1; return NGX_OK; } static ngx_int_t ngx_http_geoip_city_float_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { float val; GeoIPRecord *gr; gr = ngx_http_geoip_get_city_record(r); if (gr == NULL) { v->not_found = 1; return NGX_OK; } v->data = ngx_pnalloc(r->pool, NGX_INT64_LEN + 5); if (v->data == NULL) { GeoIPRecord_delete(gr); return NGX_ERROR; } val = *(float *) ((char *) gr + data); v->len = ngx_sprintf(v->data, "%.4f", val) - v->data; 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; 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.4.6/src/http/modules/ngx_http_gunzip_filter_module.c000644 001751 001751 00000040536 12305336445 025573 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_clear_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_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; } 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) { 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; 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_ALERT, 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 (rc == Z_STREAM_END && ctx->flush == Z_FINISH && ctx->zstream.avail_in == 0) { 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.4.6/src/http/modules/ngx_http_gzip_filter_module.c000644 001751 001751 00000074662 12305336445 025237 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 gzheader:1; unsigned buffering:1; size_t zin; size_t zout; uint32_t crc32; z_stream zstream; ngx_http_request_t *request; } ngx_http_gzip_ctx_t; #if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED) struct gztrailer { uint32_t crc32; uint32_t zlen; }; #else /* NGX_HAVE_BIG_ENDIAN || !NGX_HAVE_NONALIGNED */ struct gztrailer { u_char crc32[4]; u_char zlen[4]; }; #endif static void ngx_http_gzip_filter_memory(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx); static ngx_int_t ngx_http_gzip_filter_buffer(ngx_http_gzip_ctx_t *ctx, ngx_chain_t *in); static ngx_int_t ngx_http_gzip_filter_deflate_start(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx); static ngx_int_t ngx_http_gzip_filter_gzheader(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx); static ngx_int_t ngx_http_gzip_filter_add_data(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx); static ngx_int_t ngx_http_gzip_filter_get_buf(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx); static ngx_int_t ngx_http_gzip_filter_deflate(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx); static ngx_int_t ngx_http_gzip_filter_deflate_end(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx); static void *ngx_http_gzip_filter_alloc(void *opaque, u_int items, u_int size); static void ngx_http_gzip_filter_free(void *opaque, void *address); static void ngx_http_gzip_filter_free_copy_buf(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx); static ngx_int_t ngx_http_gzip_add_variables(ngx_conf_t *cf); static ngx_int_t ngx_http_gzip_ratio_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_gzip_filter_init(ngx_conf_t *cf); static void *ngx_http_gzip_create_conf(ngx_conf_t *cf); static char *ngx_http_gzip_merge_conf(ngx_conf_t *cf, void *parent, void *child); static char *ngx_http_gzip_window(ngx_conf_t *cf, void *post, void *data); static char *ngx_http_gzip_hash(ngx_conf_t *cf, void *post, void *data); static ngx_conf_num_bounds_t ngx_http_gzip_comp_level_bounds = { ngx_conf_check_num_bounds, 1, 9 }; static ngx_conf_post_handler_pt ngx_http_gzip_window_p = ngx_http_gzip_window; static ngx_conf_post_handler_pt ngx_http_gzip_hash_p = ngx_http_gzip_hash; static ngx_command_t ngx_http_gzip_filter_commands[] = { { ngx_string("gzip"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF |NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_gzip_conf_t, enable), NULL }, { ngx_string("gzip_buffers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, ngx_conf_set_bufs_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_gzip_conf_t, bufs), NULL }, { ngx_string("gzip_types"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_types_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_gzip_conf_t, types_keys), &ngx_http_html_default_types[0] }, { ngx_string("gzip_comp_level"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_gzip_conf_t, level), &ngx_http_gzip_comp_level_bounds }, { ngx_string("gzip_window"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_gzip_conf_t, wbits), &ngx_http_gzip_window_p }, { ngx_string("gzip_hash"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_gzip_conf_t, memlevel), &ngx_http_gzip_hash_p }, { ngx_string("postpone_gzipping"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_gzip_conf_t, postpone_gzipping), NULL }, { ngx_string("gzip_no_buffer"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_gzip_conf_t, no_buffer), NULL }, { ngx_string("gzip_min_length"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_gzip_conf_t, min_length), NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_gzip_filter_module_ctx = { ngx_http_gzip_add_variables, /* preconfiguration */ ngx_http_gzip_filter_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_gzip_create_conf, /* create location configuration */ ngx_http_gzip_merge_conf /* merge location configuration */ }; ngx_module_t ngx_http_gzip_filter_module = { NGX_MODULE_V1, &ngx_http_gzip_filter_module_ctx, /* module context */ ngx_http_gzip_filter_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_str_t ngx_http_gzip_ratio = ngx_string("gzip_ratio"); static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter; static ngx_int_t ngx_http_gzip_header_filter(ngx_http_request_t *r) { ngx_table_elt_t *h; ngx_http_gzip_ctx_t *ctx; ngx_http_gzip_conf_t *conf; conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module); if (!conf->enable || (r->headers_out.status != NGX_HTTP_OK && r->headers_out.status != NGX_HTTP_FORBIDDEN && r->headers_out.status != NGX_HTTP_NOT_FOUND) || (r->headers_out.content_encoding && r->headers_out.content_encoding->value.len) || (r->headers_out.content_length_n != -1 && r->headers_out.content_length_n < conf->min_length) || ngx_http_test_content_type(r, &conf->types) == NULL || r->header_only) { return ngx_http_next_header_filter(r); } r->gzip_vary = 1; #if (NGX_HTTP_DEGRADATION) { ngx_http_core_loc_conf_t *clcf; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (clcf->gzip_disable_degradation && ngx_http_degraded(r)) { return ngx_http_next_header_filter(r); } } #endif if (!r->gzip_tested) { if (ngx_http_gzip_ok(r) != NGX_OK) { return ngx_http_next_header_filter(r); } } else if (!r->gzip_ok) { return ngx_http_next_header_filter(r); } ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_gzip_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_gzip_filter_module); ctx->request = r; ctx->buffering = (conf->postpone_gzipping != 0); ngx_http_gzip_filter_memory(r, ctx); h = ngx_list_push(&r->headers_out.headers); if (h == NULL) { return NGX_ERROR; } h->hash = 1; ngx_str_set(&h->key, "Content-Encoding"); ngx_str_set(&h->value, "gzip"); r->headers_out.content_encoding = h; r->main_filter_need_in_memory = 1; ngx_http_clear_content_length(r); ngx_http_clear_accept_ranges(r); ngx_http_clear_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_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; } } 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; } 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) { ngx_http_gzip_filter_free_copy_buf(r, ctx); return ctx->busy ? NGX_AGAIN : NGX_OK; } if (!ctx->gzheader) { if (ngx_http_gzip_filter_gzheader(r, ctx) != NGX_OK) { goto failed; } } rc = ngx_http_next_body_filter(r, ctx->out); if (rc == NGX_ERROR) { goto failed; } ngx_http_gzip_filter_free_copy_buf(r, ctx); ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &ctx->out, (ngx_buf_tag_t) &ngx_http_gzip_filter_module); ctx->last_out = &ctx->out; ctx->nomem = 0; if (ctx->done) { return rc; } } /* unreachable */ failed: ctx->done = 1; if (ctx->preallocated) { deflateEnd(&ctx->zstream); ngx_pfree(r->pool, ctx->preallocated); } ngx_http_gzip_filter_free_copy_buf(r, ctx); return NGX_ERROR; } static void ngx_http_gzip_filter_memory(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) { int wbits, memlevel; ngx_http_gzip_conf_t *conf; conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module); wbits = conf->wbits; memlevel = conf->memlevel; if (r->headers_out.content_length_n > 0) { /* the actual zlib window size is smaller by 262 bytes */ while (r->headers_out.content_length_n < ((1 << (wbits - 1)) - 262)) { wbits--; memlevel--; } if (memlevel < 1) { memlevel = 1; } } ctx->wbits = wbits; ctx->memlevel = memlevel; /* * We preallocate a memory for zlib in one buffer (200K-400K), this * decreases a number of malloc() and free() calls and also probably * decreases a number of syscalls (sbrk()/mmap() and so on). * Besides we free the memory as soon as a gzipping will complete * and do not wait while a whole response will be sent to a client. * * 8K is for zlib deflate_state, it takes * *) 5816 bytes on i386 and sparc64 (32-bit mode) * *) 5920 bytes on amd64 and sparc64 */ ctx->allocated = 8192 + (1 << (wbits + 2)) + (1 << (memlevel + 9)); } static ngx_int_t ngx_http_gzip_filter_buffer(ngx_http_gzip_ctx_t *ctx, ngx_chain_t *in) { size_t size, buffered; ngx_buf_t *b, *buf; ngx_chain_t *cl, **ll; ngx_http_request_t *r; ngx_http_gzip_conf_t *conf; r = ctx->request; r->connection->buffered |= NGX_HTTP_GZIP_BUFFERED; buffered = 0; ll = &ctx->in; for (cl = ctx->in; cl; cl = cl->next) { buffered += cl->buf->last - cl->buf->pos; ll = &cl->next; } conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module); while (in) { cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_ERROR; } b = in->buf; size = b->last - b->pos; buffered += size; if (b->flush || b->last_buf || buffered > conf->postpone_gzipping) { ctx->buffering = 0; } if (ctx->buffering && size) { buf = ngx_create_temp_buf(r->pool, size); if (buf == NULL) { return NGX_ERROR; } buf->last = ngx_cpymem(buf->pos, b->pos, size); b->pos = b->last; buf->last_buf = b->last_buf; buf->tag = (ngx_buf_tag_t) &ngx_http_gzip_filter_module; cl->buf = buf; } else { cl->buf = b; } *ll = cl; ll = &cl->next; in = in->next; } *ll = NULL; return ctx->buffering ? NGX_OK : NGX_DONE; } static ngx_int_t ngx_http_gzip_filter_deflate_start(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) { int rc; ngx_http_gzip_conf_t *conf; conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module); ctx->preallocated = ngx_palloc(r->pool, ctx->allocated); if (ctx->preallocated == NULL) { return NGX_ERROR; } ctx->free_mem = ctx->preallocated; ctx->zstream.zalloc = ngx_http_gzip_filter_alloc; ctx->zstream.zfree = ngx_http_gzip_filter_free; ctx->zstream.opaque = ctx; rc = deflateInit2(&ctx->zstream, (int) conf->level, Z_DEFLATED, - ctx->wbits, ctx->memlevel, Z_DEFAULT_STRATEGY); if (rc != Z_OK) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "deflateInit2() failed: %d", rc); return NGX_ERROR; } r->connection->buffered |= NGX_HTTP_GZIP_BUFFERED; ctx->last_out = &ctx->out; ctx->crc32 = crc32(0L, Z_NULL, 0); ctx->flush = Z_NO_FLUSH; return NGX_OK; } static ngx_int_t ngx_http_gzip_filter_gzheader(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) { ngx_buf_t *b; ngx_chain_t *cl; static u_char gzheader[10] = { 0x1f, 0x8b, Z_DEFLATED, 0, 0, 0, 0, 0, 0, 3 }; b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); if (b == NULL) { return NGX_ERROR; } b->memory = 1; b->pos = gzheader; b->last = b->pos + 10; cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_ERROR; } cl->buf = b; cl->next = ctx->out; ctx->out = cl; ctx->gzheader = 1; return NGX_OK; } static ngx_int_t ngx_http_gzip_filter_add_data(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) { if (ctx->zstream.avail_in || ctx->flush != Z_NO_FLUSH || ctx->redo) { return NGX_OK; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "gzip in: %p", ctx->in); if (ctx->in == NULL) { return NGX_DECLINED; } if (ctx->copy_buf) { /* * to avoid CPU cache trashing we do not free() just quit buf, * but postpone free()ing after zlib compressing and data output */ ctx->copy_buf->next = ctx->copied; ctx->copied = ctx->copy_buf; ctx->copy_buf = NULL; } ctx->in_buf = ctx->in->buf; if (ctx->in_buf->tag == (ngx_buf_tag_t) &ngx_http_gzip_filter_module) { ctx->copy_buf = ctx->in; } ctx->in = ctx->in->next; ctx->zstream.next_in = ctx->in_buf->pos; ctx->zstream.avail_in = ctx->in_buf->last - ctx->in_buf->pos; ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "gzip in_buf:%p ni:%p ai:%ud", ctx->in_buf, ctx->zstream.next_in, ctx->zstream.avail_in); if (ctx->in_buf->last_buf) { ctx->flush = Z_FINISH; } else if (ctx->in_buf->flush) { ctx->flush = Z_SYNC_FLUSH; } if (ctx->zstream.avail_in) { ctx->crc32 = crc32(ctx->crc32, ctx->zstream.next_in, ctx->zstream.avail_in); } else if (ctx->flush == Z_NO_FLUSH) { return NGX_AGAIN; } return NGX_OK; } static ngx_int_t ngx_http_gzip_filter_get_buf(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) { ngx_http_gzip_conf_t *conf; if (ctx->zstream.avail_out) { return NGX_OK; } conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module); if (ctx->free) { ctx->out_buf = ctx->free->buf; ctx->free = ctx->free->next; } else if (ctx->bufs < conf->bufs.num) { ctx->out_buf = ngx_create_temp_buf(r->pool, conf->bufs.size); if (ctx->out_buf == NULL) { return NGX_ERROR; } ctx->out_buf->tag = (ngx_buf_tag_t) &ngx_http_gzip_filter_module; ctx->out_buf->recycled = 1; ctx->bufs++; } else { ctx->nomem = 1; return NGX_DECLINED; } ctx->zstream.next_out = ctx->out_buf->pos; ctx->zstream.avail_out = conf->bufs.size; return NGX_OK; } static ngx_int_t ngx_http_gzip_filter_deflate(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) { int rc; ngx_buf_t *b; ngx_chain_t *cl; ngx_http_gzip_conf_t *conf; ngx_log_debug6(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "deflate in: ni:%p no:%p ai:%ud ao:%ud fl:%d redo:%d", ctx->zstream.next_in, ctx->zstream.next_out, ctx->zstream.avail_in, ctx->zstream.avail_out, ctx->flush, ctx->redo); rc = deflate(&ctx->zstream, ctx->flush); if (rc != Z_OK && rc != Z_STREAM_END && rc != Z_BUF_ERROR) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "deflate() failed: %d, %d", ctx->flush, rc); return NGX_ERROR; } ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "deflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d", ctx->zstream.next_in, ctx->zstream.next_out, ctx->zstream.avail_in, ctx->zstream.avail_out, rc); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "gzip in_buf:%p pos:%p", ctx->in_buf, ctx->in_buf->pos); if (ctx->zstream.next_in) { ctx->in_buf->pos = ctx->zstream.next_in; if (ctx->zstream.avail_in == 0) { ctx->zstream.next_in = NULL; } } ctx->out_buf->last = ctx->zstream.next_out; if (ctx->zstream.avail_out == 0) { /* zlib wants to output some more gzipped data */ cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_ERROR; } cl->buf = ctx->out_buf; cl->next = NULL; *ctx->last_out = cl; ctx->last_out = &cl->next; ctx->redo = 1; return NGX_AGAIN; } ctx->redo = 0; if (ctx->flush == Z_SYNC_FLUSH) { ctx->flush = Z_NO_FLUSH; cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_ERROR; } b = ctx->out_buf; if (ngx_buf_size(b) == 0) { b = ngx_calloc_buf(ctx->request->pool); if (b == NULL) { return NGX_ERROR; } } else { ctx->zstream.avail_out = 0; } b->flush = 1; cl->buf = b; cl->next = NULL; *ctx->last_out = cl; ctx->last_out = &cl->next; return NGX_OK; } if (rc == Z_STREAM_END) { if (ngx_http_gzip_filter_deflate_end(r, ctx) != NGX_OK) { return NGX_ERROR; } return NGX_OK; } conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module); if (conf->no_buffer && ctx->in == NULL) { cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_ERROR; } cl->buf = ctx->out_buf; cl->next = NULL; *ctx->last_out = cl; ctx->last_out = &cl->next; return NGX_OK; } return NGX_AGAIN; } static ngx_int_t ngx_http_gzip_filter_deflate_end(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) { int rc; ngx_buf_t *b; ngx_chain_t *cl; struct gztrailer *trailer; ctx->zin = ctx->zstream.total_in; ctx->zout = 10 + ctx->zstream.total_out + 8; rc = deflateEnd(&ctx->zstream); if (rc != Z_OK) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "deflateEnd() failed: %d", rc); return NGX_ERROR; } ngx_pfree(r->pool, ctx->preallocated); cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_ERROR; } cl->buf = ctx->out_buf; cl->next = NULL; *ctx->last_out = cl; ctx->last_out = &cl->next; if (ctx->zstream.avail_out >= 8) { trailer = (struct gztrailer *) ctx->out_buf->last; ctx->out_buf->last += 8; ctx->out_buf->last_buf = 1; } else { b = ngx_create_temp_buf(r->pool, 8); if (b == NULL) { return NGX_ERROR; } b->last_buf = 1; cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_ERROR; } cl->buf = b; cl->next = NULL; *ctx->last_out = cl; ctx->last_out = &cl->next; trailer = (struct gztrailer *) b->pos; b->last += 8; } #if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED) trailer->crc32 = ctx->crc32; trailer->zlen = ctx->zin; #else trailer->crc32[0] = (u_char) (ctx->crc32 & 0xff); trailer->crc32[1] = (u_char) ((ctx->crc32 >> 8) & 0xff); trailer->crc32[2] = (u_char) ((ctx->crc32 >> 16) & 0xff); trailer->crc32[3] = (u_char) ((ctx->crc32 >> 24) & 0xff); trailer->zlen[0] = (u_char) (ctx->zin & 0xff); trailer->zlen[1] = (u_char) ((ctx->zin >> 8) & 0xff); trailer->zlen[2] = (u_char) ((ctx->zin >> 16) & 0xff); trailer->zlen[3] = (u_char) ((ctx->zin >> 24) & 0xff); #endif ctx->zstream.avail_in = 0; ctx->zstream.avail_out = 0; ctx->done = 1; r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED; return NGX_OK; } static void * ngx_http_gzip_filter_alloc(void *opaque, u_int items, u_int size) { ngx_http_gzip_ctx_t *ctx = opaque; void *p; ngx_uint_t alloc; alloc = items * size; if (alloc % 512 != 0 && alloc < 8192) { /* * The zlib deflate_state allocation, it takes about 6K, * we allocate 8K. Other allocations are divisible by 512. */ alloc = 8192; } if (alloc <= ctx->allocated) { p = ctx->free_mem; ctx->free_mem += alloc; ctx->allocated -= alloc; ngx_log_debug4(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0, "gzip alloc: n:%ud s:%ud a:%ud p:%p", items, size, alloc, p); return p; } ngx_log_error(NGX_LOG_ALERT, ctx->request->connection->log, 0, "gzip filter failed to use preallocated memory: %ud of %ud", items * size, ctx->allocated); p = ngx_palloc(ctx->request->pool, items * size); return p; } static void ngx_http_gzip_filter_free(void *opaque, void *address) { #if 0 ngx_http_gzip_ctx_t *ctx = opaque; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0, "gzip free: %p", address); #endif } static void ngx_http_gzip_filter_free_copy_buf(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) { ngx_chain_t *cl; for (cl = ctx->copied; cl; cl = cl->next) { ngx_pfree(r->pool, cl->buf->start); } ctx->copied = NULL; } static ngx_int_t ngx_http_gzip_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var; var = ngx_http_add_variable(cf, &ngx_http_gzip_ratio, NGX_HTTP_VAR_NOHASH); if (var == NULL) { return NGX_ERROR; } var->get_handler = ngx_http_gzip_ratio_variable; return NGX_OK; } static ngx_int_t ngx_http_gzip_ratio_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_uint_t zint, zfrac; ngx_http_gzip_ctx_t *ctx; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; ctx = ngx_http_get_module_ctx(r, ngx_http_gzip_filter_module); if (ctx == NULL || ctx->zout == 0) { v->not_found = 1; return NGX_OK; } v->data = ngx_pnalloc(r->pool, NGX_INT32_LEN + 3); if (v->data == NULL) { return NGX_ERROR; } zint = (ngx_uint_t) (ctx->zin / ctx->zout); zfrac = (ngx_uint_t) ((ctx->zin * 100 / ctx->zout) % 100); if ((ctx->zin * 1000 / ctx->zout) % 10 > 4) { /* the rounding, e.g., 2.125 to 2.13 */ zfrac++; if (zfrac > 99) { zint++; zfrac = 0; } } v->len = ngx_sprintf(v->data, "%ui.%02ui", zint, zfrac) - v->data; return NGX_OK; } static void * ngx_http_gzip_create_conf(ngx_conf_t *cf) { ngx_http_gzip_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_gzip_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->bufs.num = 0; * conf->types = { NULL }; * conf->types_keys = NULL; */ conf->enable = NGX_CONF_UNSET; conf->no_buffer = NGX_CONF_UNSET; conf->postpone_gzipping = NGX_CONF_UNSET_SIZE; conf->level = NGX_CONF_UNSET; conf->wbits = NGX_CONF_UNSET_SIZE; conf->memlevel = NGX_CONF_UNSET_SIZE; conf->min_length = NGX_CONF_UNSET; return conf; } static char * ngx_http_gzip_merge_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_gzip_conf_t *prev = parent; ngx_http_gzip_conf_t *conf = child; ngx_conf_merge_value(conf->enable, prev->enable, 0); ngx_conf_merge_value(conf->no_buffer, prev->no_buffer, 0); ngx_conf_merge_bufs_value(conf->bufs, prev->bufs, (128 * 1024) / ngx_pagesize, ngx_pagesize); ngx_conf_merge_size_value(conf->postpone_gzipping, prev->postpone_gzipping, 0); ngx_conf_merge_value(conf->level, prev->level, 1); ngx_conf_merge_size_value(conf->wbits, prev->wbits, MAX_WBITS); ngx_conf_merge_size_value(conf->memlevel, prev->memlevel, MAX_MEM_LEVEL - 1); ngx_conf_merge_value(conf->min_length, prev->min_length, 20); if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types, &prev->types_keys, &prev->types, ngx_http_html_default_types) != NGX_OK) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } static ngx_int_t ngx_http_gzip_filter_init(ngx_conf_t *cf) { ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_gzip_header_filter; ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_gzip_body_filter; return NGX_OK; } static char * ngx_http_gzip_window(ngx_conf_t *cf, void *post, void *data) { size_t *np = data; size_t wbits, wsize; wbits = 15; for (wsize = 32 * 1024; wsize > 256; wsize >>= 1) { if (wsize == *np) { *np = wbits; return NGX_CONF_OK; } wbits--; } return "must be 512, 1k, 2k, 4k, 8k, 16k, or 32k"; } static char * ngx_http_gzip_hash(ngx_conf_t *cf, void *post, void *data) { size_t *np = data; size_t memlevel, hsize; memlevel = 9; for (hsize = 128 * 1024; hsize > 256; hsize >>= 1) { if (hsize == *np) { *np = memlevel; return NGX_CONF_OK; } memlevel--; } return "must be 512, 1k, 2k, 4k, 8k, 16k, 32k, 64k, or 128k"; } nginx-1.4.6/src/http/modules/ngx_http_gzip_static_module.c000644 001751 001751 00000020454 12305336445 025227 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_FLAG, ngx_conf_set_enum_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_gzip_static_conf_t, enable), &ngx_http_gzip_static }, ngx_null_command }; ngx_http_module_t ngx_http_gzip_static_module_ctx = { NULL, /* preconfiguration */ ngx_http_gzip_static_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_gzip_static_create_conf, /* create location configuration */ ngx_http_gzip_static_merge_conf /* merge location configuration */ }; ngx_module_t ngx_http_gzip_static_module = { NGX_MODULE_V1, &ngx_http_gzip_static_module_ctx, /* module context */ ngx_http_gzip_static_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_int_t ngx_http_gzip_static_handler(ngx_http_request_t *r) { u_char *p; size_t root; ngx_str_t path; ngx_int_t rc; ngx_uint_t level; ngx_log_t *log; ngx_buf_t *b; ngx_chain_t out; ngx_table_elt_t *h; ngx_open_file_info_t of; ngx_http_core_loc_conf_t *clcf; ngx_http_gzip_static_conf_t *gzcf; if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) { return NGX_DECLINED; } if (r->uri.data[r->uri.len - 1] == '/') { return NGX_DECLINED; } gzcf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_static_module); if (gzcf->enable == NGX_HTTP_GZIP_STATIC_OFF) { return NGX_DECLINED; } if (gzcf->enable == NGX_HTTP_GZIP_STATIC_ON) { rc = ngx_http_gzip_ok(r); } else { /* always */ rc = NGX_OK; } clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (!clcf->gzip_vary && rc != NGX_OK) { return NGX_DECLINED; } log = r->connection->log; p = ngx_http_map_uri_to_path(r, &path, &root, sizeof(".gz") - 1); if (p == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } *p++ = '.'; *p++ = 'g'; *p++ = 'z'; *p = '\0'; path.len = p - path.data; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http filename: \"%s\"", path.data); ngx_memzero(&of, sizeof(ngx_open_file_info_t)); of.read_ahead = clcf->read_ahead; of.directio = clcf->directio; of.valid = clcf->open_file_cache_valid; of.min_uses = clcf->open_file_cache_min_uses; of.errors = clcf->open_file_cache_errors; of.events = clcf->open_file_cache_events; if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool) != NGX_OK) { switch (of.err) { case 0: return NGX_HTTP_INTERNAL_SERVER_ERROR; case NGX_ENOENT: case NGX_ENOTDIR: case NGX_ENAMETOOLONG: return NGX_DECLINED; case NGX_EACCES: #if (NGX_HAVE_OPENAT) case NGX_EMLINK: case NGX_ELOOP: #endif level = NGX_LOG_ERR; break; default: level = NGX_LOG_CRIT; break; } ngx_log_error(level, log, of.err, "%s \"%s\" failed", of.failed, path.data); return NGX_DECLINED; } if (gzcf->enable == NGX_HTTP_GZIP_STATIC_ON) { r->gzip_vary = 1; if (rc != NGX_OK) { return NGX_DECLINED; } } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http static fd: %d", of.fd); if (of.is_dir) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http dir"); return NGX_DECLINED; } #if !(NGX_WIN32) /* the not regular files are probably Unix specific */ if (!of.is_file) { ngx_log_error(NGX_LOG_CRIT, log, 0, "\"%s\" is not a regular file", path.data); return NGX_HTTP_NOT_FOUND; } #endif r->root_tested = !r->error_page; rc = ngx_http_discard_request_body(r); if (rc != NGX_OK) { return rc; } log->action = "sending response to client"; r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = of.size; r->headers_out.last_modified_time = of.mtime; if (ngx_http_set_etag(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_http_set_content_type(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } h = ngx_list_push(&r->headers_out.headers); if (h == NULL) { return NGX_ERROR; } h->hash = 1; ngx_str_set(&h->key, "Content-Encoding"); ngx_str_set(&h->value, "gzip"); r->headers_out.content_encoding = h; r->ignore_content_encoding = 1; /* we need to allocate all before the header would be sent */ b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); if (b == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t)); if (b->file == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { return rc; } b->file_pos = 0; b->file_last = of.size; b->in_file = b->file_last ? 1 : 0; b->last_buf = (r == r->main) ? 1 : 0; b->last_in_chain = 1; b->file->fd = of.fd; b->file->name = path; b->file->log = log; b->file->directio = of.is_directio; out.buf = b; out.next = NULL; return ngx_http_output_filter(r, &out); } static void * ngx_http_gzip_static_create_conf(ngx_conf_t *cf) { ngx_http_gzip_static_conf_t *conf; conf = ngx_palloc(cf->pool, sizeof(ngx_http_gzip_static_conf_t)); if (conf == NULL) { return NULL; } conf->enable = NGX_CONF_UNSET_UINT; return conf; } static char * ngx_http_gzip_static_merge_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_gzip_static_conf_t *prev = parent; ngx_http_gzip_static_conf_t *conf = child; ngx_conf_merge_uint_value(conf->enable, prev->enable, NGX_HTTP_GZIP_STATIC_OFF); return NGX_CONF_OK; } static ngx_int_t ngx_http_gzip_static_init(ngx_conf_t *cf) { ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } *h = ngx_http_gzip_static_handler; return NGX_OK; } nginx-1.4.6/src/http/modules/ngx_http_headers_filter_module.c000644 001751 001751 00000037326 12305336445 025675 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; }; 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_array_t *headers; } ngx_http_headers_conf_t; static ngx_int_t ngx_http_set_expires(ngx_http_request_t *r, ngx_http_headers_conf_t *conf); static ngx_int_t ngx_http_add_cache_control(ngx_http_request_t *r, ngx_http_header_val_t *hv, ngx_str_t *value); static ngx_int_t ngx_http_add_header(ngx_http_request_t *r, ngx_http_header_val_t *hv, ngx_str_t *value); static ngx_int_t ngx_http_set_last_modified(ngx_http_request_t *r, ngx_http_header_val_t *hv, ngx_str_t *value); static ngx_int_t ngx_http_set_response_header(ngx_http_request_t *r, ngx_http_header_val_t *hv, ngx_str_t *value); static void *ngx_http_headers_create_conf(ngx_conf_t *cf); static char *ngx_http_headers_merge_conf(ngx_conf_t *cf, void *parent, void *child); static ngx_int_t ngx_http_headers_filter_init(ngx_conf_t *cf); static char *ngx_http_headers_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_headers_add(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_http_set_header_t ngx_http_set_headers[] = { { ngx_string("Cache-Control"), 0, ngx_http_add_cache_control }, { ngx_string("Last-Modified"), offsetof(ngx_http_headers_out_t, last_modified), ngx_http_set_last_modified }, { ngx_string("ETag"), offsetof(ngx_http_headers_out_t, etag), ngx_http_set_response_header }, { ngx_null_string, 0, NULL } }; static ngx_command_t ngx_http_headers_filter_commands[] = { { ngx_string("expires"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF |NGX_CONF_TAKE12, ngx_http_headers_expires, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL}, { ngx_string("add_header"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF |NGX_CONF_TAKE2, ngx_http_headers_add, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL}, ngx_null_command }; static ngx_http_module_t ngx_http_headers_filter_module_ctx = { NULL, /* preconfiguration */ ngx_http_headers_filter_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_headers_create_conf, /* create location configuration */ ngx_http_headers_merge_conf /* merge location configuration */ }; ngx_module_t ngx_http_headers_filter_module = { NGX_MODULE_V1, &ngx_http_headers_filter_module_ctx, /* module context */ ngx_http_headers_filter_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_int_t ngx_http_headers_filter(ngx_http_request_t *r) { ngx_str_t value; ngx_uint_t i; ngx_http_header_val_t *h; ngx_http_headers_conf_t *conf; conf = ngx_http_get_module_loc_conf(r, ngx_http_headers_filter_module); if ((conf->expires == NGX_HTTP_EXPIRES_OFF && conf->headers == NULL) || r != r->main || (r->headers_out.status != NGX_HTTP_OK && r->headers_out.status != NGX_HTTP_CREATED && r->headers_out.status != NGX_HTTP_NO_CONTENT && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT && r->headers_out.status != NGX_HTTP_MOVED_PERMANENTLY && r->headers_out.status != NGX_HTTP_MOVED_TEMPORARILY && r->headers_out.status != NGX_HTTP_SEE_OTHER && r->headers_out.status != NGX_HTTP_NOT_MODIFIED && r->headers_out.status != NGX_HTTP_TEMPORARY_REDIRECT)) { return ngx_http_next_header_filter(r); } if (conf->expires != NGX_HTTP_EXPIRES_OFF) { 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 (ngx_http_complex_value(r, &h[i].value, &value) != NGX_OK) { return NGX_ERROR; } if (h[i].handler(r, &h[i], &value) != NGX_OK) { return NGX_ERROR; } } } return ngx_http_next_header_filter(r); } static ngx_int_t ngx_http_set_expires(ngx_http_request_t *r, ngx_http_headers_conf_t *conf) { size_t len; time_t now, expires_time, max_age; ngx_uint_t i; ngx_table_elt_t *expires, *cc, **ccp; expires = r->headers_out.expires; if (expires == NULL) { expires = ngx_list_push(&r->headers_out.headers); if (expires == NULL) { return NGX_ERROR; } r->headers_out.expires = expires; expires->hash = 1; ngx_str_set(&expires->key, "Expires"); } len = sizeof("Mon, 28 Sep 1970 06:00:00 GMT"); expires->value.len = len - 1; ccp = r->headers_out.cache_control.elts; if (ccp == NULL) { if (ngx_array_init(&r->headers_out.cache_control, r->pool, 1, sizeof(ngx_table_elt_t *)) != NGX_OK) { return NGX_ERROR; } ccp = ngx_array_push(&r->headers_out.cache_control); if (ccp == NULL) { return NGX_ERROR; } cc = ngx_list_push(&r->headers_out.headers); if (cc == NULL) { return NGX_ERROR; } cc->hash = 1; ngx_str_set(&cc->key, "Cache-Control"); *ccp = cc; } else { for (i = 1; i < r->headers_out.cache_control.nelts; i++) { ccp[i]->hash = 0; } cc = ccp[0]; } if (conf->expires == NGX_HTTP_EXPIRES_EPOCH) { expires->value.data = (u_char *) "Thu, 01 Jan 1970 00:00:01 GMT"; ngx_str_set(&cc->value, "no-cache"); return NGX_OK; } if (conf->expires == NGX_HTTP_EXPIRES_MAX) { expires->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; } expires->value.data = ngx_pnalloc(r->pool, len); if (expires->value.data == NULL) { return NGX_ERROR; } if (conf->expires_time == 0 && conf->expires != NGX_HTTP_EXPIRES_DAILY) { ngx_memcpy(expires->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 (conf->expires == NGX_HTTP_EXPIRES_DAILY) { expires_time = ngx_next_time(conf->expires_time); max_age = expires_time - now; } else if (conf->expires == NGX_HTTP_EXPIRES_ACCESS || r->headers_out.last_modified_time == -1) { expires_time = now + conf->expires_time; max_age = conf->expires_time; } else { expires_time = r->headers_out.last_modified_time + conf->expires_time; max_age = expires_time - now; } ngx_http_time(expires->value.data, expires_time); if (conf->expires_time < 0 || max_age < 0) { ngx_str_set(&cc->value, "no-cache"); return NGX_OK; } cc->value.data = ngx_pnalloc(r->pool, sizeof("max-age=") + NGX_TIME_T_LEN + 1); if (cc->value.data == NULL) { return NGX_ERROR; } cc->value.len = ngx_sprintf(cc->value.data, "max-age=%T", max_age) - cc->value.data; return NGX_OK; } static ngx_int_t ngx_http_add_header(ngx_http_request_t *r, ngx_http_header_val_t *hv, ngx_str_t *value) { ngx_table_elt_t *h; if (value->len) { h = ngx_list_push(&r->headers_out.headers); if (h == NULL) { return NGX_ERROR; } h->hash = 1; h->key = hv->key; h->value = *value; } return NGX_OK; } static ngx_int_t ngx_http_add_cache_control(ngx_http_request_t *r, ngx_http_header_val_t *hv, ngx_str_t *value) { ngx_table_elt_t *cc, **ccp; ccp = r->headers_out.cache_control.elts; if (ccp == NULL) { if (ngx_array_init(&r->headers_out.cache_control, r->pool, 1, sizeof(ngx_table_elt_t *)) != NGX_OK) { return NGX_ERROR; } } ccp = ngx_array_push(&r->headers_out.cache_control); if (ccp == NULL) { return NGX_ERROR; } cc = ngx_list_push(&r->headers_out.headers); if (cc == NULL) { return NGX_ERROR; } cc->hash = 1; ngx_str_set(&cc->key, "Cache-Control"); cc->value = *value; *ccp = cc; return NGX_OK; } static ngx_int_t ngx_http_set_last_modified(ngx_http_request_t *r, ngx_http_header_val_t *hv, ngx_str_t *value) { if (ngx_http_set_response_header(r, hv, value) != NGX_OK) { return NGX_ERROR; } r->headers_out.last_modified_time = (value->len) ? ngx_http_parse_time(value->data, value->len) : -1; return NGX_OK; } static ngx_int_t ngx_http_set_response_header(ngx_http_request_t *r, ngx_http_header_val_t *hv, ngx_str_t *value) { ngx_table_elt_t *h, **old; old = (ngx_table_elt_t **) ((char *) &r->headers_out + hv->offset); if (value->len == 0) { if (*old) { (*old)->hash = 0; *old = NULL; } return NGX_OK; } if (*old) { h = *old; } else { h = ngx_list_push(&r->headers_out.headers); if (h == NULL) { return NGX_ERROR; } *old = h; } h->hash = 1; h->key = hv->key; h->value = *value; return NGX_OK; } static void * ngx_http_headers_create_conf(ngx_conf_t *cf) { ngx_http_headers_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_headers_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->headers = NULL; * conf->expires_time = 0; */ conf->expires = 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; if (conf->expires == NGX_HTTP_EXPIRES_UNSET) { conf->expires = NGX_HTTP_EXPIRES_OFF; } } if (conf->headers == NULL) { conf->headers = prev->headers; } return NGX_CONF_OK; } static ngx_int_t ngx_http_headers_filter_init(ngx_conf_t *cf) { ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_headers_filter; return NGX_OK; } static char * ngx_http_headers_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_headers_conf_t *hcf = conf; ngx_uint_t minus, n; ngx_str_t *value; if (hcf->expires != NGX_HTTP_EXPIRES_UNSET) { return "is duplicate"; } value = cf->args->elts; if (cf->args->nelts == 2) { if (ngx_strcmp(value[1].data, "epoch") == 0) { hcf->expires = NGX_HTTP_EXPIRES_EPOCH; return NGX_CONF_OK; } if (ngx_strcmp(value[1].data, "max") == 0) { hcf->expires = NGX_HTTP_EXPIRES_MAX; return NGX_CONF_OK; } if (ngx_strcmp(value[1].data, "off") == 0) { hcf->expires = NGX_HTTP_EXPIRES_OFF; return NGX_CONF_OK; } 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; } if (value[n].data[0] == '@') { value[n].data++; value[n].len--; minus = 0; if (hcf->expires == NGX_HTTP_EXPIRES_MODIFIED) { return "daily time cannot be used with \"modified\" parameter"; } hcf->expires = NGX_HTTP_EXPIRES_DAILY; } else if (value[n].data[0] == '+') { value[n].data++; value[n].len--; minus = 0; } else if (value[n].data[0] == '-') { value[n].data++; value[n].len--; minus = 1; } else { minus = 0; } hcf->expires_time = ngx_parse_time(&value[n], 1); if (hcf->expires_time == (time_t) NGX_ERROR) { return "invalid value"; } if (hcf->expires == NGX_HTTP_EXPIRES_DAILY && hcf->expires_time > 24 * 60 * 60) { return "daily time value must be less than 24 hours"; } if (minus) { hcf->expires_time = - hcf->expires_time; } return NGX_CONF_OK; } static char * ngx_http_headers_add(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_headers_conf_t *hcf = conf; ngx_str_t *value; ngx_uint_t i; ngx_http_header_val_t *hv; ngx_http_set_header_t *set; ngx_http_compile_complex_value_t ccv; value = cf->args->elts; if (hcf->headers == NULL) { hcf->headers = ngx_array_create(cf->pool, 1, sizeof(ngx_http_header_val_t)); if (hcf->headers == NULL) { return NGX_CONF_ERROR; } } hv = ngx_array_push(hcf->headers); if (hv == NULL) { return NGX_CONF_ERROR; } hv->key = value[1]; hv->handler = ngx_http_add_header; hv->offset = 0; 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)); return NGX_CONF_OK; } 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; } return NGX_CONF_OK; } nginx-1.4.6/src/http/modules/ngx_http_image_filter_module.c000644 001751 001751 00000112615 12305336445 025337 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_BUFFERED 0x08 typedef struct { ngx_uint_t filter; ngx_uint_t width; ngx_uint_t height; ngx_uint_t angle; ngx_uint_t jpeg_quality; ngx_uint_t sharpen; ngx_flag_t transparency; ngx_flag_t interlace; ngx_http_complex_value_t *wcv; ngx_http_complex_value_t *hcv; ngx_http_complex_value_t *acv; ngx_http_complex_value_t *jqcv; ngx_http_complex_value_t *shcv; size_t buffer_size; } ngx_http_image_filter_conf_t; typedef struct { u_char *image; u_char *last; size_t length; ngx_uint_t width; ngx_uint_t height; ngx_uint_t max_width; ngx_uint_t max_height; ngx_uint_t angle; ngx_uint_t phase; ngx_uint_t type; ngx_uint_t force; } ngx_http_image_filter_ctx_t; static ngx_int_t ngx_http_image_send(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx, ngx_chain_t *in); static ngx_uint_t ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in); static ngx_int_t ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in); static ngx_buf_t *ngx_http_image_process(ngx_http_request_t *r); static ngx_buf_t *ngx_http_image_json(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx); static ngx_buf_t *ngx_http_image_asis(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx); static void ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b); static ngx_int_t ngx_http_image_size(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx); static ngx_buf_t *ngx_http_image_resize(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx); static gdImagePtr ngx_http_image_source(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx); static gdImagePtr ngx_http_image_new(ngx_http_request_t *r, int w, int h, int colors); static u_char *ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type, gdImagePtr img, int *size); static void ngx_http_image_cleanup(void *data); static ngx_uint_t ngx_http_image_filter_get_value(ngx_http_request_t *r, ngx_http_complex_value_t *cv, ngx_uint_t v); static ngx_uint_t ngx_http_image_filter_value(ngx_str_t *value); static void *ngx_http_image_filter_create_conf(ngx_conf_t *cf); static char *ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child); static char *ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_int_t ngx_http_image_filter_init(ngx_conf_t *cf); static ngx_command_t ngx_http_image_filter_commands[] = { { ngx_string("image_filter"), NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123, ngx_http_image_filter, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("image_filter_jpeg_quality"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_image_filter_jpeg_quality, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("image_filter_sharpen"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_image_filter_sharpen, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("image_filter_transparency"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_image_filter_conf_t, transparency), NULL }, { ngx_string("image_filter_interlace"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_image_filter_conf_t, interlace), NULL }, { ngx_string("image_filter_buffer"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_image_filter_conf_t, buffer_size), NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_image_filter_module_ctx = { NULL, /* preconfiguration */ ngx_http_image_filter_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_image_filter_create_conf, /* create location configuration */ ngx_http_image_filter_merge_conf /* merge location configuration */ }; ngx_module_t ngx_http_image_filter_module = { NGX_MODULE_V1, &ngx_http_image_filter_module_ctx, /* module context */ ngx_http_image_filter_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter; static ngx_str_t ngx_http_image_types[] = { ngx_string("image/jpeg"), ngx_string("image/gif"), ngx_string("image/png") }; static ngx_int_t ngx_http_image_header_filter(ngx_http_request_t *r) { off_t len; ngx_http_image_filter_ctx_t *ctx; ngx_http_image_filter_conf_t *conf; if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) { return ngx_http_next_header_filter(r); } ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module); if (ctx) { ngx_http_set_ctx(r, NULL, ngx_http_image_filter_module); return ngx_http_next_header_filter(r); } conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); if (conf->filter == NGX_HTTP_IMAGE_OFF) { return ngx_http_next_header_filter(r); } if (r->headers_out.content_type.len >= sizeof("multipart/x-mixed-replace") - 1 && ngx_strncasecmp(r->headers_out.content_type.data, (u_char *) "multipart/x-mixed-replace", sizeof("multipart/x-mixed-replace") - 1) == 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "image filter: multipart/x-mixed-replace response"); return NGX_ERROR; } ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_image_filter_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_image_filter_module); len = r->headers_out.content_length_n; if (len != -1 && len > (off_t) conf->buffer_size) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "image filter: too big response: %O", len); return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE; } if (len == -1) { ctx->length = conf->buffer_size; } else { ctx->length = (size_t) len; } if (r->headers_out.refresh) { r->headers_out.refresh->hash = 0; } r->main_filter_need_in_memory = 1; r->allow_ranges = 0; return NGX_OK; } static ngx_int_t ngx_http_image_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_int_t rc; ngx_str_t *ct; ngx_chain_t out; ngx_http_image_filter_ctx_t *ctx; ngx_http_image_filter_conf_t *conf; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image filter"); if (in == NULL) { return ngx_http_next_body_filter(r, in); } ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module); if (ctx == NULL) { return ngx_http_next_body_filter(r, in); } switch (ctx->phase) { case NGX_HTTP_IMAGE_START: ctx->type = ngx_http_image_test(r, in); conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); if (ctx->type == NGX_HTTP_IMAGE_NONE) { if (conf->filter == NGX_HTTP_IMAGE_SIZE) { out.buf = ngx_http_image_json(r, NULL); if (out.buf) { out.next = NULL; ctx->phase = NGX_HTTP_IMAGE_DONE; return ngx_http_image_send(r, ctx, &out); } } return ngx_http_filter_finalize_request(r, &ngx_http_image_filter_module, NGX_HTTP_UNSUPPORTED_MEDIA_TYPE); } /* override content type */ ct = &ngx_http_image_types[ctx->type - 1]; r->headers_out.content_type_len = ct->len; r->headers_out.content_type = *ct; r->headers_out.content_type_lowcase = NULL; if (conf->filter == NGX_HTTP_IMAGE_TEST) { ctx->phase = NGX_HTTP_IMAGE_PASS; return ngx_http_image_send(r, ctx, in); } ctx->phase = NGX_HTTP_IMAGE_READ; /* fall through */ case NGX_HTTP_IMAGE_READ: rc = ngx_http_image_read(r, in); if (rc == NGX_AGAIN) { return NGX_OK; } if (rc == NGX_ERROR) { return ngx_http_filter_finalize_request(r, &ngx_http_image_filter_module, NGX_HTTP_UNSUPPORTED_MEDIA_TYPE); } /* fall through */ case NGX_HTTP_IMAGE_PROCESS: out.buf = ngx_http_image_process(r); if (out.buf == NULL) { return ngx_http_filter_finalize_request(r, &ngx_http_image_filter_module, NGX_HTTP_UNSUPPORTED_MEDIA_TYPE); } out.next = NULL; ctx->phase = NGX_HTTP_IMAGE_PASS; return ngx_http_image_send(r, ctx, &out); case NGX_HTTP_IMAGE_PASS: return ngx_http_next_body_filter(r, in); default: /* NGX_HTTP_IMAGE_DONE */ rc = ngx_http_next_body_filter(r, NULL); /* NGX_ERROR resets any pending data */ return (rc == NGX_OK) ? NGX_ERROR : rc; } } static ngx_int_t ngx_http_image_send(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx, ngx_chain_t *in) { ngx_int_t rc; rc = ngx_http_next_header_filter(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { return NGX_ERROR; } rc = ngx_http_next_body_filter(r, in); if (ctx->phase == NGX_HTTP_IMAGE_DONE) { /* NGX_ERROR resets any pending data */ return (rc == NGX_OK) ? NGX_ERROR : rc; } return rc; } static ngx_uint_t ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in) { u_char *p; p = in->buf->pos; if (in->buf->last - p < 16) { return NGX_HTTP_IMAGE_NONE; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image filter: \"%c%c\"", p[0], p[1]); if (p[0] == 0xff && p[1] == 0xd8) { /* JPEG */ return NGX_HTTP_IMAGE_JPEG; } else if (p[0] == 'G' && p[1] == 'I' && p[2] == 'F' && p[3] == '8' && p[5] == 'a') { if (p[4] == '9' || p[4] == '7') { /* GIF */ return NGX_HTTP_IMAGE_GIF; } } else if (p[0] == 0x89 && p[1] == 'P' && p[2] == 'N' && p[3] == 'G' && p[4] == 0x0d && p[5] == 0x0a && p[6] == 0x1a && p[7] == 0x0a) { /* PNG */ return NGX_HTTP_IMAGE_PNG; } return NGX_HTTP_IMAGE_NONE; } static ngx_int_t ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in) { u_char *p; size_t size, rest; ngx_buf_t *b; ngx_chain_t *cl; ngx_http_image_filter_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module); if (ctx->image == NULL) { ctx->image = ngx_palloc(r->pool, ctx->length); if (ctx->image == NULL) { return NGX_ERROR; } ctx->last = ctx->image; } p = ctx->last; for (cl = in; cl; cl = cl->next) { b = cl->buf; size = b->last - b->pos; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image buf: %uz", size); rest = ctx->image + ctx->length - p; size = (rest < size) ? rest : size; p = ngx_cpymem(p, b->pos, size); b->pos += size; if (b->last_buf) { ctx->last = p; return NGX_OK; } } ctx->last = p; r->connection->buffered |= NGX_HTTP_IMAGE_BUFFERED; return NGX_AGAIN; } static ngx_buf_t * ngx_http_image_process(ngx_http_request_t *r) { ngx_int_t rc; ngx_http_image_filter_ctx_t *ctx; ngx_http_image_filter_conf_t *conf; r->connection->buffered &= ~NGX_HTTP_IMAGE_BUFFERED; ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module); rc = ngx_http_image_size(r, ctx); conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); if (conf->filter == NGX_HTTP_IMAGE_SIZE) { return ngx_http_image_json(r, rc == NGX_OK ? ctx : NULL); } ctx->angle = ngx_http_image_filter_get_value(r, conf->acv, conf->angle); if (conf->filter == NGX_HTTP_IMAGE_ROTATE) { if (ctx->angle != 90 && ctx->angle != 180 && ctx->angle != 270) { return NULL; } return ngx_http_image_resize(r, ctx); } ctx->max_width = ngx_http_image_filter_get_value(r, conf->wcv, conf->width); if (ctx->max_width == 0) { return NULL; } ctx->max_height = ngx_http_image_filter_get_value(r, conf->hcv, conf->height); if (ctx->max_height == 0) { return NULL; } if (rc == NGX_OK && ctx->width <= ctx->max_width && ctx->height <= ctx->max_height && ctx->angle == 0 && !ctx->force) { return ngx_http_image_asis(r, ctx); } return ngx_http_image_resize(r, ctx); } static ngx_buf_t * ngx_http_image_json(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) { size_t len; ngx_buf_t *b; b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); if (b == NULL) { return NULL; } b->memory = 1; b->last_buf = 1; ngx_http_clean_header(r); r->headers_out.status = NGX_HTTP_OK; ngx_str_set(&r->headers_out.content_type, "text/plain"); r->headers_out.content_type_lowcase = NULL; if (ctx == NULL) { b->pos = (u_char *) "{}" CRLF; b->last = b->pos + sizeof("{}" CRLF) - 1; ngx_http_image_length(r, b); return b; } len = sizeof("{ \"img\" : " "{ \"width\": , \"height\": , \"type\": \"jpeg\" } }" CRLF) - 1 + 2 * NGX_SIZE_T_LEN; b->pos = ngx_pnalloc(r->pool, len); if (b->pos == NULL) { return NULL; } b->last = ngx_sprintf(b->pos, "{ \"img\" : " "{ \"width\": %uz," " \"height\": %uz," " \"type\": \"%s\" } }" CRLF, ctx->width, ctx->height, ngx_http_image_types[ctx->type - 1].data + 6); ngx_http_image_length(r, b); return b; } static ngx_buf_t * ngx_http_image_asis(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) { ngx_buf_t *b; b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); if (b == NULL) { return NULL; } b->pos = ctx->image; b->last = ctx->last; b->memory = 1; b->last_buf = 1; ngx_http_image_length(r, b); return b; } static void ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b) { r->headers_out.content_length_n = b->last - b->pos; if (r->headers_out.content_length) { r->headers_out.content_length->hash = 0; } r->headers_out.content_length = NULL; } static ngx_int_t ngx_http_image_size(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) { u_char *p, *last; size_t len, app; ngx_uint_t width, height; p = ctx->image; switch (ctx->type) { case NGX_HTTP_IMAGE_JPEG: p += 2; last = ctx->image + ctx->length - 10; width = 0; height = 0; app = 0; while (p < last) { if (p[0] == 0xff && p[1] != 0xff) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "JPEG: %02xd %02xd", p[0], p[1]); p++; if ((*p == 0xc0 || *p == 0xc1 || *p == 0xc2 || *p == 0xc3 || *p == 0xc9 || *p == 0xca || *p == 0xcb) && (width == 0 || height == 0)) { width = p[6] * 256 + p[7]; height = p[4] * 256 + p[5]; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "JPEG: %02xd %02xd", p[1], p[2]); len = p[1] * 256 + p[2]; if (*p >= 0xe1 && *p <= 0xef) { /* application data, e.g., EXIF, Adobe XMP, etc. */ app += len; } p += len; continue; } p++; } if (width == 0 || height == 0) { return NGX_DECLINED; } if (ctx->length / 20 < app) { /* force conversion if application data consume more than 5% */ ctx->force = 1; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "app data size: %uz", app); } break; case NGX_HTTP_IMAGE_GIF: if (ctx->length < 10) { return NGX_DECLINED; } width = p[7] * 256 + p[6]; height = p[9] * 256 + p[8]; break; case NGX_HTTP_IMAGE_PNG: if (ctx->length < 24) { return NGX_DECLINED; } width = p[18] * 256 + p[19]; height = p[22] * 256 + p[23]; break; default: return NGX_DECLINED; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image size: %d x %d", width, height); ctx->width = width; ctx->height = height; return NGX_OK; } static ngx_buf_t * ngx_http_image_resize(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) { int sx, sy, dx, dy, ox, oy, ax, ay, size, colors, palette, transparent, sharpen, red, green, blue, t; u_char *out; ngx_buf_t *b; ngx_uint_t resize; gdImagePtr src, dst; ngx_pool_cleanup_t *cln; ngx_http_image_filter_conf_t *conf; src = ngx_http_image_source(r, ctx); if (src == NULL) { return NULL; } sx = gdImageSX(src); sy = gdImageSY(src); conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); if (!ctx->force && ctx->angle == 0 && (ngx_uint_t) sx <= ctx->max_width && (ngx_uint_t) sy <= ctx->max_height) { gdImageDestroy(src); return ngx_http_image_asis(r, ctx); } colors = gdImageColorsTotal(src); if (colors && conf->transparency) { transparent = gdImageGetTransparent(src); if (transparent != -1) { palette = colors; red = gdImageRed(src, transparent); green = gdImageGreen(src, transparent); blue = gdImageBlue(src, transparent); goto transparent; } } palette = 0; transparent = -1; red = 0; green = 0; blue = 0; transparent: gdImageColorTransparent(src, -1); dx = sx; dy = sy; if (conf->filter == NGX_HTTP_IMAGE_RESIZE) { if ((ngx_uint_t) dx > ctx->max_width) { dy = dy * ctx->max_width / dx; dy = dy ? dy : 1; dx = ctx->max_width; } if ((ngx_uint_t) dy > ctx->max_height) { dx = dx * ctx->max_height / dy; dx = dx ? dx : 1; dy = ctx->max_height; } resize = 1; } else if (conf->filter == NGX_HTTP_IMAGE_ROTATE) { resize = 0; } else { /* NGX_HTTP_IMAGE_CROP */ resize = 0; if ((double) dx / dy < (double) ctx->max_width / ctx->max_height) { if ((ngx_uint_t) dx > ctx->max_width) { dy = dy * ctx->max_width / dx; dy = dy ? dy : 1; dx = ctx->max_width; resize = 1; } } else { if ((ngx_uint_t) dy > ctx->max_height) { dx = dx * ctx->max_height / dy; dx = dx ? dx : 1; dy = ctx->max_height; resize = 1; } } } if (resize) { dst = ngx_http_image_new(r, dx, dy, palette); if (dst == NULL) { gdImageDestroy(src); return NULL; } if (colors == 0) { gdImageSaveAlpha(dst, 1); gdImageAlphaBlending(dst, 0); } gdImageCopyResampled(dst, src, 0, 0, 0, 0, dx, dy, sx, sy); if (colors) { gdImageTrueColorToPalette(dst, 1, 256); } gdImageDestroy(src); } else { dst = src; } if (ctx->angle) { src = dst; ax = (dx % 2 == 0) ? 1 : 0; ay = (dy % 2 == 0) ? 1 : 0; switch (ctx->angle) { case 90: case 270: dst = ngx_http_image_new(r, dy, dx, palette); if (dst == NULL) { gdImageDestroy(src); return NULL; } if (ctx->angle == 90) { ox = dy / 2 + ay; oy = dx / 2 - ax; } else { ox = dy / 2 - ay; oy = dx / 2 + ax; } gdImageCopyRotated(dst, src, ox, oy, 0, 0, dx + ax, dy + ay, ctx->angle); gdImageDestroy(src); t = dx; dx = dy; dy = t; break; case 180: dst = ngx_http_image_new(r, dx, dy, palette); if (dst == NULL) { gdImageDestroy(src); return NULL; } gdImageCopyRotated(dst, src, dx / 2 - ax, dy / 2 - ay, 0, 0, dx + ax, dy + ay, ctx->angle); gdImageDestroy(src); break; } } if (conf->filter == NGX_HTTP_IMAGE_CROP) { src = dst; if ((ngx_uint_t) dx > ctx->max_width) { ox = dx - ctx->max_width; } else { ox = 0; } if ((ngx_uint_t) dy > ctx->max_height) { oy = dy - ctx->max_height; } else { oy = 0; } if (ox || oy) { dst = ngx_http_image_new(r, dx - ox, dy - oy, colors); if (dst == NULL) { gdImageDestroy(src); return NULL; } ox /= 2; oy /= 2; ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image crop: %d x %d @ %d x %d", dx, dy, ox, oy); if (colors == 0) { gdImageSaveAlpha(dst, 1); gdImageAlphaBlending(dst, 0); } gdImageCopy(dst, src, 0, 0, ox, oy, dx - ox, dy - oy); if (colors) { gdImageTrueColorToPalette(dst, 1, 256); } gdImageDestroy(src); } } if (transparent != -1 && colors) { gdImageColorTransparent(dst, gdImageColorExact(dst, red, green, blue)); } sharpen = ngx_http_image_filter_get_value(r, conf->shcv, conf->sharpen); if (sharpen > 0) { gdImageSharpen(dst, sharpen); } gdImageInterlace(dst, (int) conf->interlace); out = ngx_http_image_out(r, ctx->type, dst, &size); ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image: %d x %d %d", sx, sy, colors); gdImageDestroy(dst); ngx_pfree(r->pool, ctx->image); if (out == NULL) { return NULL; } cln = ngx_pool_cleanup_add(r->pool, 0); if (cln == NULL) { gdFree(out); return NULL; } b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); if (b == NULL) { gdFree(out); return NULL; } cln->handler = ngx_http_image_cleanup; cln->data = out; b->pos = out; b->last = out + size; b->memory = 1; b->last_buf = 1; ngx_http_image_length(r, b); return b; } static gdImagePtr ngx_http_image_source(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) { char *failed; gdImagePtr img; img = NULL; switch (ctx->type) { case NGX_HTTP_IMAGE_JPEG: img = gdImageCreateFromJpegPtr(ctx->length, ctx->image); failed = "gdImageCreateFromJpegPtr() failed"; break; case NGX_HTTP_IMAGE_GIF: img = gdImageCreateFromGifPtr(ctx->length, ctx->image); failed = "gdImageCreateFromGifPtr() failed"; break; case NGX_HTTP_IMAGE_PNG: img = gdImageCreateFromPngPtr(ctx->length, ctx->image); failed = "gdImageCreateFromPngPtr() failed"; break; default: failed = "unknown image type"; break; } if (img == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed); } return img; } static gdImagePtr ngx_http_image_new(ngx_http_request_t *r, int w, int h, int colors) { gdImagePtr img; if (colors == 0) { img = gdImageCreateTrueColor(w, h); if (img == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "gdImageCreateTrueColor() failed"); return NULL; } } else { img = gdImageCreate(w, h); if (img == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "gdImageCreate() failed"); return NULL; } } return img; } static u_char * ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type, gdImagePtr img, int *size) { char *failed; u_char *out; ngx_int_t jq; ngx_http_image_filter_conf_t *conf; out = NULL; switch (type) { case NGX_HTTP_IMAGE_JPEG: conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); jq = ngx_http_image_filter_get_value(r, conf->jqcv, conf->jpeg_quality); if (jq <= 0) { return NULL; } out = gdImageJpegPtr(img, size, jq); failed = "gdImageJpegPtr() failed"; break; case NGX_HTTP_IMAGE_GIF: out = gdImageGifPtr(img, size); failed = "gdImageGifPtr() failed"; break; case NGX_HTTP_IMAGE_PNG: out = gdImagePngPtr(img, size); failed = "gdImagePngPtr() failed"; break; default: failed = "unknown image type"; break; } if (out == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed); } return out; } static void ngx_http_image_cleanup(void *data) { gdFree(data); } static ngx_uint_t ngx_http_image_filter_get_value(ngx_http_request_t *r, ngx_http_complex_value_t *cv, ngx_uint_t v) { ngx_str_t val; if (cv == NULL) { return v; } if (ngx_http_complex_value(r, cv, &val) != NGX_OK) { return 0; } return ngx_http_image_filter_value(&val); } static ngx_uint_t ngx_http_image_filter_value(ngx_str_t *value) { ngx_int_t n; if (value->len == 1 && value->data[0] == '-') { return (ngx_uint_t) -1; } n = ngx_atoi(value->data, value->len); if (n > 0) { return (ngx_uint_t) n; } return 0; } static void * ngx_http_image_filter_create_conf(ngx_conf_t *cf) { ngx_http_image_filter_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_image_filter_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->width = 0; * conf->height = 0; * conf->angle = 0; * conf->wcv = NULL; * conf->hcv = NULL; * conf->acv = NULL; * conf->jqcv = NULL; * conf->shcv = NULL; */ conf->filter = NGX_CONF_UNSET_UINT; conf->jpeg_quality = NGX_CONF_UNSET_UINT; conf->sharpen = NGX_CONF_UNSET_UINT; conf->transparency = NGX_CONF_UNSET; conf->interlace = NGX_CONF_UNSET; conf->buffer_size = NGX_CONF_UNSET_SIZE; return conf; } static char * ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_image_filter_conf_t *prev = parent; ngx_http_image_filter_conf_t *conf = child; if (conf->filter == NGX_CONF_UNSET_UINT) { if (prev->filter == NGX_CONF_UNSET_UINT) { conf->filter = NGX_HTTP_IMAGE_OFF; } else { conf->filter = prev->filter; conf->width = prev->width; conf->height = prev->height; conf->angle = prev->angle; conf->wcv = prev->wcv; conf->hcv = prev->hcv; conf->acv = prev->acv; } } if (conf->jpeg_quality == NGX_CONF_UNSET_UINT) { /* 75 is libjpeg default quality */ ngx_conf_merge_uint_value(conf->jpeg_quality, prev->jpeg_quality, 75); if (conf->jqcv == NULL) { conf->jqcv = prev->jqcv; } } if (conf->sharpen == NGX_CONF_UNSET_UINT) { ngx_conf_merge_uint_value(conf->sharpen, prev->sharpen, 0); if (conf->shcv == NULL) { conf->shcv = prev->shcv; } } ngx_conf_merge_value(conf->transparency, prev->transparency, 1); ngx_conf_merge_value(conf->interlace, prev->interlace, 0); ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, 1 * 1024 * 1024); return NGX_CONF_OK; } static char * ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_image_filter_conf_t *imcf = conf; ngx_str_t *value; ngx_int_t n; ngx_uint_t i; ngx_http_complex_value_t cv; ngx_http_compile_complex_value_t ccv; value = cf->args->elts; i = 1; if (cf->args->nelts == 2) { if (ngx_strcmp(value[i].data, "off") == 0) { imcf->filter = NGX_HTTP_IMAGE_OFF; } else if (ngx_strcmp(value[i].data, "test") == 0) { imcf->filter = NGX_HTTP_IMAGE_TEST; } else if (ngx_strcmp(value[i].data, "size") == 0) { imcf->filter = NGX_HTTP_IMAGE_SIZE; } else { goto failed; } return NGX_CONF_OK; } else if (cf->args->nelts == 3) { if (ngx_strcmp(value[i].data, "rotate") == 0) { if (imcf->filter != NGX_HTTP_IMAGE_RESIZE && imcf->filter != NGX_HTTP_IMAGE_CROP) { imcf->filter = NGX_HTTP_IMAGE_ROTATE; } ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[++i]; ccv.complex_value = &cv; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } if (cv.lengths == NULL) { n = ngx_http_image_filter_value(&value[i]); if (n != 90 && n != 180 && n != 270) { goto failed; } imcf->angle = (ngx_uint_t) n; } else { imcf->acv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); if (imcf->acv == NULL) { return NGX_CONF_ERROR; } *imcf->acv = cv; } return NGX_CONF_OK; } else { goto failed; } } if (ngx_strcmp(value[i].data, "resize") == 0) { imcf->filter = NGX_HTTP_IMAGE_RESIZE; } else if (ngx_strcmp(value[i].data, "crop") == 0) { imcf->filter = NGX_HTTP_IMAGE_CROP; } else { goto failed; } ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[++i]; ccv.complex_value = &cv; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } if (cv.lengths == NULL) { n = ngx_http_image_filter_value(&value[i]); if (n == 0) { goto failed; } imcf->width = (ngx_uint_t) n; } else { imcf->wcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); if (imcf->wcv == NULL) { return NGX_CONF_ERROR; } *imcf->wcv = cv; } ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[++i]; ccv.complex_value = &cv; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } if (cv.lengths == NULL) { n = ngx_http_image_filter_value(&value[i]); if (n == 0) { goto failed; } imcf->height = (ngx_uint_t) n; } else { imcf->hcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); if (imcf->hcv == NULL) { return NGX_CONF_ERROR; } *imcf->hcv = cv; } return NGX_CONF_OK; failed: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[i]); return NGX_CONF_ERROR; } static char * ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_image_filter_conf_t *imcf = conf; ngx_str_t *value; ngx_int_t n; ngx_http_complex_value_t cv; ngx_http_compile_complex_value_t ccv; value = cf->args->elts; ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = &cv; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } if (cv.lengths == NULL) { n = ngx_http_image_filter_value(&value[1]); if (n <= 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid value \"%V\"", &value[1]); return NGX_CONF_ERROR; } imcf->jpeg_quality = (ngx_uint_t) n; } else { imcf->jqcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); if (imcf->jqcv == NULL) { return NGX_CONF_ERROR; } *imcf->jqcv = cv; } return NGX_CONF_OK; } static char * ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_image_filter_conf_t *imcf = conf; ngx_str_t *value; ngx_int_t n; ngx_http_complex_value_t cv; ngx_http_compile_complex_value_t ccv; value = cf->args->elts; ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = &cv; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } if (cv.lengths == NULL) { n = ngx_http_image_filter_value(&value[1]); if (n < 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid value \"%V\"", &value[1]); return NGX_CONF_ERROR; } imcf->sharpen = (ngx_uint_t) n; } else { imcf->shcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); if (imcf->shcv == NULL) { return NGX_CONF_ERROR; } *imcf->shcv = cv; } return NGX_CONF_OK; } static ngx_int_t ngx_http_image_filter_init(ngx_conf_t *cf) { ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_image_header_filter; ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_image_body_filter; return NGX_OK; } nginx-1.4.6/src/http/modules/ngx_http_index_module.c000644 001751 001751 00000035247 12305336445 024024 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_ERROR; } allocated = path.data + path.len - name; } if (index[i].values == NULL) { /* index[i].name.len includes the terminating '\0' */ ngx_memcpy(name, index[i].name.data, index[i].name.len); path.len = (name + index[i].name.len - 1) - path.data; } else { e.ip = index[i].values->elts; e.pos = name; while (*(uintptr_t *) e.ip) { code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); } if (*name == '/') { uri.len = len - 1; uri.data = name; return ngx_http_internal_redirect(r, &uri, &r->args); } path.len = e.pos - path.data; *e.pos = '\0'; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "open index \"%V\"", &path); ngx_memzero(&of, sizeof(ngx_open_file_info_t)); of.read_ahead = clcf->read_ahead; of.directio = clcf->directio; of.valid = clcf->open_file_cache_valid; of.min_uses = clcf->open_file_cache_min_uses; of.test_only = 1; of.errors = clcf->open_file_cache_errors; of.events = clcf->open_file_cache_events; if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool) != NGX_OK) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, of.err, "%s \"%s\" failed", of.failed, path.data); if (of.err == 0) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } #if (NGX_HAVE_OPENAT) if (of.err == NGX_EMLINK || of.err == NGX_ELOOP) { return NGX_HTTP_FORBIDDEN; } #endif if (of.err == NGX_ENOTDIR || of.err == NGX_ENAMETOOLONG || of.err == NGX_EACCES) { return ngx_http_index_error(r, clcf, path.data, of.err); } if (!dir_tested) { rc = ngx_http_index_test_dir(r, clcf, path.data, name - 1); if (rc != NGX_OK) { return rc; } dir_tested = 1; } if (of.err == NGX_ENOENT) { continue; } ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err, "%s \"%s\" failed", of.failed, path.data); return NGX_HTTP_INTERNAL_SERVER_ERROR; } uri.len = r->uri.len + len - 1; if (!clcf->alias) { uri.data = path.data + root; } else { uri.data = ngx_pnalloc(r->pool, uri.len); if (uri.data == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } p = ngx_copy(uri.data, r->uri.data, r->uri.len); ngx_memcpy(p, name, len - 1); } return ngx_http_internal_redirect(r, &uri, &r->args); } return NGX_DECLINED; } static ngx_int_t ngx_http_index_test_dir(ngx_http_request_t *r, ngx_http_core_loc_conf_t *clcf, u_char *path, u_char *last) { u_char c; ngx_str_t dir; ngx_open_file_info_t of; c = *last; if (c != '/' || path == last) { /* "alias" without trailing slash */ c = *(++last); } *last = '\0'; dir.len = last - path; dir.data = path; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http index check dir: \"%V\"", &dir); ngx_memzero(&of, sizeof(ngx_open_file_info_t)); of.test_dir = 1; of.test_only = 1; of.valid = clcf->open_file_cache_valid; of.errors = clcf->open_file_cache_errors; if (ngx_http_set_disable_symlinks(r, clcf, &dir, &of) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_open_cached_file(clcf->open_file_cache, &dir, &of, r->pool) != NGX_OK) { if (of.err) { #if (NGX_HAVE_OPENAT) if (of.err == NGX_EMLINK || of.err == NGX_ELOOP) { return NGX_HTTP_FORBIDDEN; } #endif if (of.err == NGX_ENOENT) { *last = c; return ngx_http_index_error(r, clcf, dir.data, NGX_ENOENT); } if (of.err == NGX_EACCES) { *last = c; /* * ngx_http_index_test_dir() is called after the first index * file testing has returned an error distinct from NGX_EACCES. * This means that directory searching is allowed. */ return NGX_OK; } ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err, "%s \"%s\" failed", of.failed, dir.data); } return NGX_HTTP_INTERNAL_SERVER_ERROR; } *last = c; if (of.is_dir) { return NGX_OK; } ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "\"%s\" is not a directory", dir.data); return NGX_HTTP_INTERNAL_SERVER_ERROR; } static ngx_int_t ngx_http_index_error(ngx_http_request_t *r, ngx_http_core_loc_conf_t *clcf, u_char *file, ngx_err_t err) { if (err == NGX_EACCES) { ngx_log_error(NGX_LOG_ERR, r->connection->log, err, "\"%s\" is forbidden", file); return NGX_HTTP_FORBIDDEN; } if (clcf->log_not_found) { ngx_log_error(NGX_LOG_ERR, r->connection->log, err, "\"%s\" is not found", file); } return NGX_HTTP_NOT_FOUND; } static void * ngx_http_index_create_loc_conf(ngx_conf_t *cf) { ngx_http_index_loc_conf_t *conf; conf = ngx_palloc(cf->pool, sizeof(ngx_http_index_loc_conf_t)); if (conf == NULL) { return NULL; } conf->indices = NULL; conf->max_index_len = 0; return conf; } static char * ngx_http_index_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_index_loc_conf_t *prev = parent; ngx_http_index_loc_conf_t *conf = child; ngx_http_index_t *index; if (conf->indices == NULL) { conf->indices = prev->indices; conf->max_index_len = prev->max_index_len; } if (conf->indices == NULL) { conf->indices = ngx_array_create(cf->pool, 1, sizeof(ngx_http_index_t)); if (conf->indices == NULL) { return NGX_CONF_ERROR; } index = ngx_array_push(conf->indices); if (index == NULL) { return NGX_CONF_ERROR; } index->name.len = sizeof(NGX_HTTP_DEFAULT_INDEX); index->name.data = (u_char *) NGX_HTTP_DEFAULT_INDEX; index->lengths = NULL; index->values = NULL; conf->max_index_len = sizeof(NGX_HTTP_DEFAULT_INDEX); return NGX_CONF_OK; } return NGX_CONF_OK; } static ngx_int_t ngx_http_index_init(ngx_conf_t *cf) { ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } *h = ngx_http_index_handler; return NGX_OK; } /* TODO: warn about duplicate indices */ static char * ngx_http_index_set_index(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_index_loc_conf_t *ilcf = conf; ngx_str_t *value; ngx_uint_t i, n; ngx_http_index_t *index; ngx_http_script_compile_t sc; if (ilcf->indices == NULL) { ilcf->indices = ngx_array_create(cf->pool, 2, sizeof(ngx_http_index_t)); if (ilcf->indices == NULL) { return NGX_CONF_ERROR; } } value = cf->args->elts; for (i = 1; i < cf->args->nelts; i++) { if (value[i].data[0] == '/' && i != cf->args->nelts - 1) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "only the last index in \"index\" directive " "should be absolute"); } if (value[i].len == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "index \"%V\" in \"index\" directive is invalid", &value[1]); return NGX_CONF_ERROR; } index = ngx_array_push(ilcf->indices); if (index == NULL) { return NGX_CONF_ERROR; } index->name.len = value[i].len; index->name.data = value[i].data; index->lengths = NULL; index->values = NULL; n = ngx_http_script_variables_count(&value[i]); if (n == 0) { if (ilcf->max_index_len < index->name.len) { ilcf->max_index_len = index->name.len; } if (index->name.data[0] == '/') { continue; } /* include the terminating '\0' to the length to use ngx_memcpy() */ index->name.len++; continue; } ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); sc.cf = cf; sc.source = &value[i]; sc.lengths = &index->lengths; sc.values = &index->values; sc.variables = n; sc.complete_lengths = 1; sc.complete_values = 1; if (ngx_http_script_compile(&sc) != NGX_OK) { return NGX_CONF_ERROR; } } return NGX_CONF_OK; } nginx-1.4.6/src/http/modules/ngx_http_limit_conn_module.c000644 001751 001751 00000047540 12305336445 025047 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ #include #include #include typedef struct { u_char color; u_char len; u_short conn; u_char data[1]; } ngx_http_limit_conn_node_t; typedef struct { ngx_shm_zone_t *shm_zone; ngx_rbtree_node_t *node; } ngx_http_limit_conn_cleanup_t; typedef struct { ngx_rbtree_t *rbtree; ngx_int_t index; ngx_str_t var; } ngx_http_limit_conn_ctx_t; typedef struct { ngx_shm_zone_t *shm_zone; ngx_uint_t conn; } ngx_http_limit_conn_limit_t; typedef struct { ngx_array_t limits; ngx_uint_t log_level; ngx_uint_t status_code; } ngx_http_limit_conn_conf_t; static ngx_rbtree_node_t *ngx_http_limit_conn_lookup(ngx_rbtree_t *rbtree, ngx_http_variable_value_t *vv, uint32_t hash); static void ngx_http_limit_conn_cleanup(void *data); static ngx_inline void ngx_http_limit_conn_cleanup_all(ngx_pool_t *pool); static void *ngx_http_limit_conn_create_conf(ngx_conf_t *cf); static char *ngx_http_limit_conn_merge_conf(ngx_conf_t *cf, void *parent, void *child); static char *ngx_http_limit_conn_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_limit_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_limit_conn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_int_t ngx_http_limit_conn_init(ngx_conf_t *cf); static ngx_conf_deprecated_t ngx_conf_deprecated_limit_zone = { ngx_conf_deprecated, "limit_zone", "limit_conn_zone" }; 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_zone"), NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE3, ngx_http_limit_zone, 0, 0, NULL }, { ngx_string("limit_conn"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, ngx_http_limit_conn, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("limit_conn_log_level"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_enum_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_limit_conn_conf_t, log_level), &ngx_http_limit_conn_log_levels }, { ngx_string("limit_conn_status"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_limit_conn_conf_t, status_code), &ngx_http_limit_conn_status_bounds }, ngx_null_command }; static ngx_http_module_t ngx_http_limit_conn_module_ctx = { NULL, /* preconfiguration */ ngx_http_limit_conn_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_limit_conn_create_conf, /* create location configuration */ ngx_http_limit_conn_merge_conf /* merge location configuration */ }; ngx_module_t ngx_http_limit_conn_module = { NGX_MODULE_V1, &ngx_http_limit_conn_module_ctx, /* module context */ ngx_http_limit_conn_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_int_t ngx_http_limit_conn_handler(ngx_http_request_t *r) { size_t len, n; uint32_t hash; ngx_uint_t i; ngx_slab_pool_t *shpool; ngx_rbtree_node_t *node; ngx_pool_cleanup_t *cln; ngx_http_variable_value_t *vv; ngx_http_limit_conn_ctx_t *ctx; ngx_http_limit_conn_node_t *lc; ngx_http_limit_conn_conf_t *lccf; ngx_http_limit_conn_limit_t *limits; ngx_http_limit_conn_cleanup_t *lccln; if (r->main->limit_conn_set) { return NGX_DECLINED; } lccf = ngx_http_get_module_loc_conf(r, ngx_http_limit_conn_module); limits = lccf->limits.elts; for (i = 0; i < lccf->limits.nelts; i++) { ctx = limits[i].shm_zone->data; vv = ngx_http_get_indexed_variable(r, ctx->index); if (vv == NULL || vv->not_found) { continue; } len = vv->len; if (len == 0) { continue; } if (len > 255) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the value of the \"%V\" variable " "is more than 255 bytes: \"%v\"", &ctx->var, vv); continue; } r->main->limit_conn_set = 1; hash = ngx_crc32_short(vv->data, len); shpool = (ngx_slab_pool_t *) limits[i].shm_zone->shm.addr; ngx_shmtx_lock(&shpool->mutex); node = ngx_http_limit_conn_lookup(ctx->rbtree, vv, hash); if (node == NULL) { n = offsetof(ngx_rbtree_node_t, color) + offsetof(ngx_http_limit_conn_node_t, data) + len; node = ngx_slab_alloc_locked(shpool, n); if (node == NULL) { ngx_shmtx_unlock(&shpool->mutex); ngx_http_limit_conn_cleanup_all(r->pool); return lccf->status_code; } lc = (ngx_http_limit_conn_node_t *) &node->color; node->key = hash; lc->len = (u_char) len; lc->conn = 1; ngx_memcpy(lc->data, vv->data, len); ngx_rbtree_insert(ctx->rbtree, node); } else { lc = (ngx_http_limit_conn_node_t *) &node->color; if ((ngx_uint_t) lc->conn >= limits[i].conn) { ngx_shmtx_unlock(&shpool->mutex); ngx_log_error(lccf->log_level, r->connection->log, 0, "limiting connections by zone \"%V\"", &limits[i].shm_zone->shm.name); ngx_http_limit_conn_cleanup_all(r->pool); return lccf->status_code; } lc->conn++; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "limit conn: %08XD %d", node->key, lc->conn); ngx_shmtx_unlock(&shpool->mutex); cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_http_limit_conn_cleanup_t)); if (cln == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } cln->handler = ngx_http_limit_conn_cleanup; lccln = cln->data; lccln->shm_zone = limits[i].shm_zone; lccln->node = node; } return NGX_DECLINED; } static void ngx_http_limit_conn_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) { ngx_rbtree_node_t **p; ngx_http_limit_conn_node_t *lcn, *lcnt; for ( ;; ) { if (node->key < temp->key) { p = &temp->left; } else if (node->key > temp->key) { p = &temp->right; } else { /* node->key == temp->key */ lcn = (ngx_http_limit_conn_node_t *) &node->color; lcnt = (ngx_http_limit_conn_node_t *) &temp->color; p = (ngx_memn2cmp(lcn->data, lcnt->data, lcn->len, lcnt->len) < 0) ? &temp->left : &temp->right; } if (*p == sentinel) { break; } temp = *p; } *p = node; node->parent = temp; node->left = sentinel; node->right = sentinel; ngx_rbt_red(node); } static ngx_rbtree_node_t * ngx_http_limit_conn_lookup(ngx_rbtree_t *rbtree, ngx_http_variable_value_t *vv, 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(vv->data, lcn->data, (size_t) vv->len, (size_t) lcn->len); if (rc == 0) { return node; } node = (rc < 0) ? node->left : node->right; } return NULL; } static void ngx_http_limit_conn_cleanup(void *data) { ngx_http_limit_conn_cleanup_t *lccln = data; ngx_slab_pool_t *shpool; ngx_rbtree_node_t *node; ngx_http_limit_conn_ctx_t *ctx; ngx_http_limit_conn_node_t *lc; ctx = lccln->shm_zone->data; shpool = (ngx_slab_pool_t *) lccln->shm_zone->shm.addr; node = lccln->node; lc = (ngx_http_limit_conn_node_t *) &node->color; ngx_shmtx_lock(&shpool->mutex); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, lccln->shm_zone->shm.log, 0, "limit conn cleanup: %08XD %d", node->key, lc->conn); lc->conn--; if (lc->conn == 0) { ngx_rbtree_delete(ctx->rbtree, node); ngx_slab_free_locked(shpool, node); } ngx_shmtx_unlock(&shpool->mutex); } static ngx_inline void ngx_http_limit_conn_cleanup_all(ngx_pool_t *pool) { ngx_pool_cleanup_t *cln; cln = pool->cleanup; while (cln && cln->handler == ngx_http_limit_conn_cleanup) { ngx_http_limit_conn_cleanup(cln->data); cln = cln->next; } pool->cleanup = cln; } static ngx_int_t ngx_http_limit_conn_init_zone(ngx_shm_zone_t *shm_zone, void *data) { ngx_http_limit_conn_ctx_t *octx = data; size_t len; ngx_slab_pool_t *shpool; ngx_rbtree_node_t *sentinel; ngx_http_limit_conn_ctx_t *ctx; ctx = shm_zone->data; if (octx) { if (ngx_strcmp(ctx->var.data, octx->var.data) != 0) { ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0, "limit_conn_zone \"%V\" uses the \"%V\" variable " "while previously it used the \"%V\" variable", &shm_zone->shm.name, &ctx->var, &octx->var); return NGX_ERROR; } ctx->rbtree = octx->rbtree; return NGX_OK; } shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; if (shm_zone->shm.exists) { ctx->rbtree = shpool->data; return NGX_OK; } ctx->rbtree = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_t)); if (ctx->rbtree == NULL) { return NGX_ERROR; } shpool->data = ctx->rbtree; sentinel = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_node_t)); if (sentinel == NULL) { return NGX_ERROR; } ngx_rbtree_init(ctx->rbtree, sentinel, ngx_http_limit_conn_rbtree_insert_value); len = sizeof(" in limit_conn_zone \"\"") + shm_zone->shm.name.len; shpool->log_ctx = ngx_slab_alloc(shpool, len); if (shpool->log_ctx == NULL) { return NGX_ERROR; } ngx_sprintf(shpool->log_ctx, " in limit_conn_zone \"%V\"%Z", &shm_zone->shm.name); return NGX_OK; } static void * ngx_http_limit_conn_create_conf(ngx_conf_t *cf) { ngx_http_limit_conn_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_conn_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->limits.elts = NULL; */ conf->log_level = NGX_CONF_UNSET_UINT; conf->status_code = NGX_CONF_UNSET_UINT; return conf; } static char * ngx_http_limit_conn_merge_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_limit_conn_conf_t *prev = parent; ngx_http_limit_conn_conf_t *conf = child; if (conf->limits.elts == NULL) { conf->limits = prev->limits; } ngx_conf_merge_uint_value(conf->log_level, prev->log_level, NGX_LOG_ERR); ngx_conf_merge_uint_value(conf->status_code, prev->status_code, NGX_HTTP_SERVICE_UNAVAILABLE); return NGX_CONF_OK; } static char * ngx_http_limit_conn_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { u_char *p; ssize_t size; ngx_str_t *value, name, s; ngx_uint_t i; ngx_shm_zone_t *shm_zone; ngx_http_limit_conn_ctx_t *ctx; value = cf->args->elts; ctx = NULL; size = 0; name.len = 0; for (i = 1; 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 (value[i].data[0] == '$') { value[i].len--; value[i].data++; ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_conn_ctx_t)); if (ctx == NULL) { return NGX_CONF_ERROR; } ctx->index = ngx_http_get_variable_index(cf, &value[i]); if (ctx->index == NGX_ERROR) { return NGX_CONF_ERROR; } ctx->var = value[i]; 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; } if (ctx == NULL) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "no variable is defined for %V \"%V\"", &cmd->name, &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 variable \"%V\"", &cmd->name, &name, &ctx->var); 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_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ssize_t n; ngx_str_t *value; ngx_shm_zone_t *shm_zone; ngx_http_limit_conn_ctx_t *ctx; ngx_conf_deprecated(cf, &ngx_conf_deprecated_limit_zone, NULL); value = cf->args->elts; if (value[2].data[0] != '$') { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid variable name \"%V\"", &value[2]); return NGX_CONF_ERROR; } value[2].len--; value[2].data++; ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_conn_ctx_t)); if (ctx == NULL) { return NGX_CONF_ERROR; } ctx->index = ngx_http_get_variable_index(cf, &value[2]); if (ctx->index == NGX_ERROR) { return NGX_CONF_ERROR; } ctx->var = value[2]; n = ngx_parse_size(&value[3]); if (n == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid size of limit_zone \"%V\"", &value[3]); return NGX_CONF_ERROR; } if (n < (ngx_int_t) (8 * ngx_pagesize)) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "limit_zone \"%V\" is too small", &value[1]); return NGX_CONF_ERROR; } shm_zone = ngx_shared_memory_add(cf, &value[1], n, &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, "limit_zone \"%V\" is already bound to variable \"%V\"", &value[1], &ctx->var); return NGX_CONF_ERROR; } shm_zone->init = ngx_http_limit_conn_init_zone; shm_zone->data = ctx; return NGX_CONF_OK; } static char * ngx_http_limit_conn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_shm_zone_t *shm_zone; ngx_http_limit_conn_conf_t *lccf = conf; ngx_http_limit_conn_limit_t *limit, *limits; ngx_str_t *value; ngx_int_t n; ngx_uint_t i; value = cf->args->elts; shm_zone = ngx_shared_memory_add(cf, &value[1], 0, &ngx_http_limit_conn_module); if (shm_zone == NULL) { return NGX_CONF_ERROR; } limits = lccf->limits.elts; if (limits == NULL) { if (ngx_array_init(&lccf->limits, cf->pool, 1, sizeof(ngx_http_limit_conn_limit_t)) != NGX_OK) { return NGX_CONF_ERROR; } } for (i = 0; i < lccf->limits.nelts; i++) { if (shm_zone == limits[i].shm_zone) { return "is duplicate"; } } n = ngx_atoi(value[2].data, value[2].len); if (n <= 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid number of connections \"%V\"", &value[2]); return NGX_CONF_ERROR; } if (n > 65535) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "connection limit must be less 65536"); return NGX_CONF_ERROR; } limit = ngx_array_push(&lccf->limits); if (limit == NULL) { return NGX_CONF_ERROR; } limit->conn = n; limit->shm_zone = shm_zone; return NGX_CONF_OK; } static ngx_int_t ngx_http_limit_conn_init(ngx_conf_t *cf) { ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } *h = ngx_http_limit_conn_handler; return NGX_OK; } nginx-1.4.6/src/http/modules/ngx_http_limit_req_module.c000644 001751 001751 00000061644 12305336445 024702 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ #include #include #include typedef struct { u_char color; u_char dummy; u_short len; ngx_queue_t queue; ngx_msec_t last; /* integer value, 1 corresponds to 0.001 r/s */ ngx_uint_t excess; ngx_uint_t count; u_char data[1]; } ngx_http_limit_req_node_t; typedef struct { ngx_rbtree_t rbtree; ngx_rbtree_node_t sentinel; ngx_queue_t queue; } ngx_http_limit_req_shctx_t; typedef struct { ngx_http_limit_req_shctx_t *sh; ngx_slab_pool_t *shpool; /* integer value, 1 corresponds to 0.001 r/s */ ngx_uint_t rate; ngx_int_t index; ngx_str_t var; ngx_http_limit_req_node_t *node; } ngx_http_limit_req_ctx_t; typedef struct { ngx_shm_zone_t *shm_zone; /* integer value, 1 corresponds to 0.001 r/s */ ngx_uint_t burst; ngx_uint_t nodelay; /* unsigned nodelay:1 */ } ngx_http_limit_req_limit_t; typedef struct { ngx_array_t limits; ngx_uint_t limit_log_level; ngx_uint_t delay_log_level; ngx_uint_t status_code; } ngx_http_limit_req_conf_t; static void ngx_http_limit_req_delay(ngx_http_request_t *r); static ngx_int_t ngx_http_limit_req_lookup(ngx_http_limit_req_limit_t *limit, ngx_uint_t hash, u_char *data, size_t len, ngx_uint_t *ep, ngx_uint_t account); static ngx_msec_t ngx_http_limit_req_account(ngx_http_limit_req_limit_t *limits, ngx_uint_t n, ngx_uint_t *ep, ngx_http_limit_req_limit_t **limit); static void ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, ngx_uint_t n); static void *ngx_http_limit_req_create_conf(ngx_conf_t *cf); static char *ngx_http_limit_req_merge_conf(ngx_conf_t *cf, void *parent, void *child); static char *ngx_http_limit_req_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_limit_req(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_int_t ngx_http_limit_req_init(ngx_conf_t *cf); static ngx_conf_enum_t ngx_http_limit_req_log_levels[] = { { ngx_string("info"), NGX_LOG_INFO }, { ngx_string("notice"), NGX_LOG_NOTICE }, { ngx_string("warn"), NGX_LOG_WARN }, { ngx_string("error"), NGX_LOG_ERR }, { ngx_null_string, 0 } }; static ngx_conf_num_bounds_t ngx_http_limit_req_status_bounds = { ngx_conf_check_num_bounds, 400, 599 }; static ngx_command_t ngx_http_limit_req_commands[] = { { ngx_string("limit_req_zone"), NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE3, ngx_http_limit_req_zone, 0, 0, NULL }, { ngx_string("limit_req"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123, ngx_http_limit_req, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("limit_req_log_level"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_enum_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_limit_req_conf_t, limit_log_level), &ngx_http_limit_req_log_levels }, { ngx_string("limit_req_status"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_limit_req_conf_t, status_code), &ngx_http_limit_req_status_bounds }, ngx_null_command }; static ngx_http_module_t ngx_http_limit_req_module_ctx = { NULL, /* preconfiguration */ ngx_http_limit_req_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_limit_req_create_conf, /* create location configuration */ ngx_http_limit_req_merge_conf /* merge location configuration */ }; ngx_module_t ngx_http_limit_req_module = { NGX_MODULE_V1, &ngx_http_limit_req_module_ctx, /* module context */ ngx_http_limit_req_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_int_t ngx_http_limit_req_handler(ngx_http_request_t *r) { size_t len; uint32_t hash; ngx_int_t rc; ngx_uint_t n, excess; ngx_msec_t delay; ngx_http_variable_value_t *vv; ngx_http_limit_req_ctx_t *ctx; ngx_http_limit_req_conf_t *lrcf; ngx_http_limit_req_limit_t *limit, *limits; if (r->main->limit_req_set) { return NGX_DECLINED; } lrcf = ngx_http_get_module_loc_conf(r, ngx_http_limit_req_module); limits = lrcf->limits.elts; excess = 0; rc = NGX_DECLINED; #if (NGX_SUPPRESS_WARN) limit = NULL; #endif for (n = 0; n < lrcf->limits.nelts; n++) { limit = &limits[n]; ctx = limit->shm_zone->data; vv = ngx_http_get_indexed_variable(r, ctx->index); if (vv == NULL || vv->not_found) { continue; } len = vv->len; if (len == 0) { continue; } if (len > 65535) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the value of the \"%V\" variable " "is more than 65535 bytes: \"%v\"", &ctx->var, vv); continue; } hash = ngx_crc32_short(vv->data, len); ngx_shmtx_lock(&ctx->shpool->mutex); rc = ngx_http_limit_req_lookup(limit, hash, vv->data, len, &excess, (n == lrcf->limits.nelts - 1)); ngx_shmtx_unlock(&ctx->shpool->mutex); ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "limit_req[%ui]: %i %ui.%03ui", n, rc, excess / 1000, excess % 1000); if (rc != NGX_AGAIN) { break; } } if (rc == NGX_DECLINED) { return NGX_DECLINED; } r->main->limit_req_set = 1; if (rc == NGX_BUSY || rc == NGX_ERROR) { if (rc == NGX_BUSY) { ngx_log_error(lrcf->limit_log_level, r->connection->log, 0, "limiting requests, excess: %ui.%03ui by zone \"%V\"", excess / 1000, excess % 1000, &limit->shm_zone->shm.name); } while (n--) { ctx = limits[n].shm_zone->data; if (ctx->node == NULL) { continue; } ngx_shmtx_lock(&ctx->shpool->mutex); ctx->node->count--; ngx_shmtx_unlock(&ctx->shpool->mutex); ctx->node = NULL; } return lrcf->status_code; } /* rc == NGX_AGAIN || rc == NGX_OK */ if (rc == NGX_AGAIN) { excess = 0; } delay = ngx_http_limit_req_account(limits, n, &excess, &limit); if (!delay) { return NGX_DECLINED; } ngx_log_error(lrcf->delay_log_level, r->connection->log, 0, "delaying request, excess: %ui.%03ui, by zone \"%V\"", excess / 1000, excess % 1000, &limit->shm_zone->shm.name); if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } r->read_event_handler = ngx_http_test_reading; r->write_event_handler = ngx_http_limit_req_delay; ngx_add_timer(r->connection->write, delay); return NGX_AGAIN; } static void ngx_http_limit_req_delay(ngx_http_request_t *r) { ngx_event_t *wev; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "limit_req delay"); wev = r->connection->write; if (!wev->timedout) { if (ngx_handle_write_event(wev, 0) != NGX_OK) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); } return; } wev->timedout = 0; if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } r->read_event_handler = ngx_http_block_reading; r->write_event_handler = ngx_http_core_run_phases; ngx_http_core_run_phases(r); } static void ngx_http_limit_req_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) { ngx_rbtree_node_t **p; ngx_http_limit_req_node_t *lrn, *lrnt; for ( ;; ) { if (node->key < temp->key) { p = &temp->left; } else if (node->key > temp->key) { p = &temp->right; } else { /* node->key == temp->key */ lrn = (ngx_http_limit_req_node_t *) &node->color; lrnt = (ngx_http_limit_req_node_t *) &temp->color; p = (ngx_memn2cmp(lrn->data, lrnt->data, lrn->len, lrnt->len) < 0) ? &temp->left : &temp->right; } if (*p == sentinel) { break; } temp = *p; } *p = node; node->parent = temp; node->left = sentinel; node->right = sentinel; ngx_rbt_red(node); } static ngx_int_t ngx_http_limit_req_lookup(ngx_http_limit_req_limit_t *limit, ngx_uint_t hash, u_char *data, size_t len, ngx_uint_t *ep, ngx_uint_t account) { size_t size; ngx_int_t rc, excess; ngx_time_t *tp; ngx_msec_t now; ngx_msec_int_t ms; ngx_rbtree_node_t *node, *sentinel; ngx_http_limit_req_ctx_t *ctx; ngx_http_limit_req_node_t *lr; tp = ngx_timeofday(); now = (ngx_msec_t) (tp->sec * 1000 + tp->msec); ctx = limit->shm_zone->data; node = ctx->sh->rbtree.root; sentinel = ctx->sh->rbtree.sentinel; while (node != sentinel) { if (hash < node->key) { node = node->left; continue; } if (hash > node->key) { node = node->right; continue; } /* hash == node->key */ lr = (ngx_http_limit_req_node_t *) &node->color; rc = ngx_memn2cmp(data, lr->data, len, (size_t) lr->len); if (rc == 0) { ngx_queue_remove(&lr->queue); ngx_queue_insert_head(&ctx->sh->queue, &lr->queue); ms = (ngx_msec_int_t) (now - lr->last); excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000; if (excess < 0) { excess = 0; } *ep = excess; if ((ngx_uint_t) excess > limit->burst) { return NGX_BUSY; } if (account) { lr->excess = excess; lr->last = now; return NGX_OK; } lr->count++; ctx->node = lr; return NGX_AGAIN; } node = (rc < 0) ? node->left : node->right; } *ep = 0; size = offsetof(ngx_rbtree_node_t, color) + offsetof(ngx_http_limit_req_node_t, data) + 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) { return NGX_ERROR; } } node->key = hash; lr = (ngx_http_limit_req_node_t *) &node->color; lr->len = (u_char) len; lr->excess = 0; ngx_memcpy(lr->data, data, len); ngx_rbtree_insert(&ctx->sh->rbtree, node); ngx_queue_insert_head(&ctx->sh->queue, &lr->queue); if (account) { lr->last = now; lr->count = 0; return NGX_OK; } lr->last = 0; lr->count = 1; ctx->node = lr; return NGX_AGAIN; } static ngx_msec_t ngx_http_limit_req_account(ngx_http_limit_req_limit_t *limits, ngx_uint_t n, ngx_uint_t *ep, ngx_http_limit_req_limit_t **limit) { ngx_int_t excess; ngx_time_t *tp; ngx_msec_t now, delay, max_delay; ngx_msec_int_t ms; ngx_http_limit_req_ctx_t *ctx; ngx_http_limit_req_node_t *lr; excess = *ep; if (excess == 0 || (*limit)->nodelay) { max_delay = 0; } else { ctx = (*limit)->shm_zone->data; max_delay = excess * 1000 / ctx->rate; } while (n--) { ctx = limits[n].shm_zone->data; lr = ctx->node; if (lr == NULL) { continue; } ngx_shmtx_lock(&ctx->shpool->mutex); tp = ngx_timeofday(); now = (ngx_msec_t) (tp->sec * 1000 + tp->msec); ms = (ngx_msec_int_t) (now - lr->last); excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000; if (excess < 0) { excess = 0; } lr->last = now; lr->excess = excess; lr->count--; ngx_shmtx_unlock(&ctx->shpool->mutex); ctx->node = NULL; if (limits[n].nodelay) { continue; } delay = excess * 1000 / ctx->rate; if (delay > max_delay) { max_delay = delay; *ep = excess; *limit = &limits[n]; } } return max_delay; } static void ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, ngx_uint_t n) { ngx_int_t excess; ngx_time_t *tp; ngx_msec_t now; ngx_queue_t *q; ngx_msec_int_t ms; ngx_rbtree_node_t *node; ngx_http_limit_req_node_t *lr; tp = ngx_timeofday(); now = (ngx_msec_t) (tp->sec * 1000 + tp->msec); /* * n == 1 deletes one or two zero rate entries * n == 0 deletes oldest entry by force * and one or two zero rate entries */ while (n < 3) { if (ngx_queue_empty(&ctx->sh->queue)) { return; } q = ngx_queue_last(&ctx->sh->queue); lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue); if (lr->count) { /* * There is not much sense in looking further, * because we bump nodes on the lookup stage. */ return; } if (n++ != 0) { ms = (ngx_msec_int_t) (now - lr->last); ms = ngx_abs(ms); if (ms < 60000) { return; } excess = lr->excess - ctx->rate * ms / 1000; if (excess > 0) { return; } } ngx_queue_remove(q); node = (ngx_rbtree_node_t *) ((u_char *) lr - offsetof(ngx_rbtree_node_t, color)); ngx_rbtree_delete(&ctx->sh->rbtree, node); ngx_slab_free_locked(ctx->shpool, node); } } static ngx_int_t ngx_http_limit_req_init_zone(ngx_shm_zone_t *shm_zone, void *data) { ngx_http_limit_req_ctx_t *octx = data; size_t len; ngx_http_limit_req_ctx_t *ctx; ctx = shm_zone->data; if (octx) { if (ngx_strcmp(ctx->var.data, octx->var.data) != 0) { ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0, "limit_req \"%V\" uses the \"%V\" variable " "while previously it used the \"%V\" variable", &shm_zone->shm.name, &ctx->var, &octx->var); 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); return NGX_OK; } static void * ngx_http_limit_req_create_conf(ngx_conf_t *cf) { ngx_http_limit_req_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_req_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->limits.elts = NULL; */ conf->limit_log_level = NGX_CONF_UNSET_UINT; conf->status_code = NGX_CONF_UNSET_UINT; return conf; } static char * ngx_http_limit_req_merge_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_limit_req_conf_t *prev = parent; ngx_http_limit_req_conf_t *conf = child; if (conf->limits.elts == NULL) { conf->limits = prev->limits; } ngx_conf_merge_uint_value(conf->limit_log_level, prev->limit_log_level, NGX_LOG_ERR); conf->delay_log_level = (conf->limit_log_level == NGX_LOG_INFO) ? NGX_LOG_INFO : conf->limit_log_level + 1; ngx_conf_merge_uint_value(conf->status_code, prev->status_code, NGX_HTTP_SERVICE_UNAVAILABLE); return NGX_CONF_OK; } static char * ngx_http_limit_req_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { u_char *p; size_t len; ssize_t size; ngx_str_t *value, name, s; ngx_int_t rate, scale; ngx_uint_t i; ngx_shm_zone_t *shm_zone; ngx_http_limit_req_ctx_t *ctx; value = cf->args->elts; ctx = NULL; size = 0; rate = 1; scale = 1; name.len = 0; for (i = 1; 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; } if (value[i].data[0] == '$') { value[i].len--; value[i].data++; ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_req_ctx_t)); if (ctx == NULL) { return NGX_CONF_ERROR; } ctx->index = ngx_http_get_variable_index(cf, &value[i]); if (ctx->index == NGX_ERROR) { return NGX_CONF_ERROR; } ctx->var = value[i]; 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; } if (ctx == NULL) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "no variable is defined for %V \"%V\"", &cmd->name, &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 variable \"%V\"", &cmd->name, &name, &ctx->var); return NGX_CONF_ERROR; } shm_zone->init = ngx_http_limit_req_init_zone; shm_zone->data = ctx; return NGX_CONF_OK; } static char * ngx_http_limit_req(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_limit_req_conf_t *lrcf = conf; ngx_int_t burst; ngx_str_t *value, s; ngx_uint_t i, nodelay; ngx_shm_zone_t *shm_zone; ngx_http_limit_req_limit_t *limit, *limits; value = cf->args->elts; shm_zone = NULL; burst = 0; nodelay = 0; for (i = 1; i < cf->args->nelts; i++) { if (ngx_strncmp(value[i].data, "zone=", 5) == 0) { s.len = value[i].len - 5; s.data = value[i].data + 5; shm_zone = ngx_shared_memory_add(cf, &s, 0, &ngx_http_limit_req_module); if (shm_zone == NULL) { return NGX_CONF_ERROR; } continue; } if (ngx_strncmp(value[i].data, "burst=", 6) == 0) { burst = ngx_atoi(value[i].data + 6, value[i].len - 6); if (burst <= 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid burst rate \"%V\"", &value[i]); return NGX_CONF_ERROR; } continue; } if (ngx_strncmp(value[i].data, "nodelay", 7) == 0) { nodelay = 1; continue; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[i]); return NGX_CONF_ERROR; } if (shm_zone == NULL) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"%V\" must have \"zone\" parameter", &cmd->name); return NGX_CONF_ERROR; } if (shm_zone->data == NULL) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unknown limit_req_zone \"%V\"", &shm_zone->shm.name); return NGX_CONF_ERROR; } limits = lrcf->limits.elts; if (limits == NULL) { if (ngx_array_init(&lrcf->limits, cf->pool, 1, sizeof(ngx_http_limit_req_limit_t)) != NGX_OK) { return NGX_CONF_ERROR; } } for (i = 0; i < lrcf->limits.nelts; i++) { if (shm_zone == limits[i].shm_zone) { return "is duplicate"; } } limit = ngx_array_push(&lrcf->limits); if (limit == NULL) { return NGX_CONF_ERROR; } limit->shm_zone = shm_zone; limit->burst = burst * 1000; limit->nodelay = nodelay; return NGX_CONF_OK; } static ngx_int_t ngx_http_limit_req_init(ngx_conf_t *cf) { ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } *h = ngx_http_limit_req_handler; return NGX_OK; } nginx-1.4.6/src/http/modules/ngx_http_log_module.c000644 001751 001751 00000125611 12305336445 023471 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_http_log_fmt_t *format; } ngx_http_log_t; typedef struct { ngx_array_t *logs; /* array of ngx_http_log_t */ ngx_open_file_cache_t *open_file_cache; time_t open_file_cache_valid; ngx_uint_t open_file_cache_min_uses; ngx_uint_t off; /* unsigned off:1 */ } ngx_http_log_loc_conf_t; typedef struct { ngx_str_t name; size_t len; ngx_http_log_op_run_pt run; } ngx_http_log_var_t; static void ngx_http_log_write(ngx_http_request_t *r, ngx_http_log_t *log, u_char *buf, size_t len); static ssize_t ngx_http_log_script_write(ngx_http_request_t *r, ngx_http_log_script_t *script, u_char **name, u_char *buf, size_t len); #if (NGX_ZLIB) static ssize_t ngx_http_log_gzip(ngx_fd_t fd, u_char *buf, size_t len, ngx_int_t level, ngx_log_t *log); static void *ngx_http_log_gzip_alloc(void *opaque, u_int items, u_int size); static void ngx_http_log_gzip_free(void *opaque, void *address); #endif static void ngx_http_log_flush(ngx_open_file_t *file, ngx_log_t *log); static void ngx_http_log_flush_handler(ngx_event_t *ev); static u_char *ngx_http_log_pipe(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static u_char *ngx_http_log_time(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static u_char *ngx_http_log_iso8601(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static u_char *ngx_http_log_msec(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static u_char *ngx_http_log_request_time(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static u_char *ngx_http_log_status(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static u_char *ngx_http_log_bytes_sent(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static u_char *ngx_http_log_body_bytes_sent(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static u_char *ngx_http_log_request_length(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static ngx_int_t ngx_http_log_variable_compile(ngx_conf_t *cf, ngx_http_log_op_t *op, ngx_str_t *value); static size_t ngx_http_log_variable_getlen(ngx_http_request_t *r, uintptr_t data); static u_char *ngx_http_log_variable(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); static uintptr_t ngx_http_log_escape(u_char *dst, u_char *src, size_t size); static void *ngx_http_log_create_main_conf(ngx_conf_t *cf); static void *ngx_http_log_create_loc_conf(ngx_conf_t *cf); static char *ngx_http_log_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); static char *ngx_http_log_set_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_log_set_format(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_log_compile_format(ngx_conf_t *cf, ngx_array_t *flushes, ngx_array_t *ops, ngx_array_t *args, ngx_uint_t s); static char *ngx_http_log_open_file_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_int_t ngx_http_log_init(ngx_conf_t *cf); static ngx_command_t ngx_http_log_commands[] = { { ngx_string("log_format"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_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; 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 (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; } } 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); } } line = ngx_pnalloc(r->pool, len); if (line == NULL) { return NGX_ERROR; } p = line; for (i = 0; i < log[l].format->ops->nelts; i++) { p = op[i].run(r, p, &op[i]); } ngx_linefeed(p); ngx_http_log_write(r, &log[l], line, p - line); } return NGX_OK; } static void ngx_http_log_write(ngx_http_request_t *r, ngx_http_log_t *log, u_char *buf, size_t len) { u_char *name; time_t now; ssize_t n; ngx_err_t err; #if (NGX_ZLIB) ngx_http_log_buf_t *buffer; #endif if (log->script == NULL) { name = log->file->name.data; #if (NGX_ZLIB) buffer = log->file->data; if (buffer && buffer->gzip) { n = ngx_http_log_gzip(log->file->fd, buf, len, buffer->gzip, r->connection->log); } else { n = ngx_write_fd(log->file->fd, buf, len); } #else n = ngx_write_fd(log->file->fd, buf, len); #endif } else { name = NULL; n = ngx_http_log_script_write(r, log->script, &name, buf, len); } if (n == (ssize_t) len) { return; } now = ngx_time(); if (n == -1) { err = ngx_errno; if (err == NGX_ENOSPC) { log->disk_full_time = now; } if (now - log->error_log_time > 59) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, err, ngx_write_fd_n " to \"%s\" failed", name); log->error_log_time = now; } return; } if (now - log->error_log_time > 59) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, ngx_write_fd_n " to \"%s\" was incomplete: %z of %uz", name, n, len); log->error_log_time = now; } } static ssize_t ngx_http_log_script_write(ngx_http_request_t *r, ngx_http_log_script_t *script, u_char **name, u_char *buf, size_t len) { size_t root; ssize_t n; ngx_str_t log, path; ngx_open_file_info_t of; ngx_http_log_loc_conf_t *llcf; ngx_http_core_loc_conf_t *clcf; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (!r->root_tested) { /* test root directory existence */ if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) { /* simulate successful logging */ return len; } path.data[root] = '\0'; ngx_memzero(&of, sizeof(ngx_open_file_info_t)); of.valid = clcf->open_file_cache_valid; of.min_uses = clcf->open_file_cache_min_uses; of.test_dir = 1; of.test_only = 1; of.errors = clcf->open_file_cache_errors; of.events = clcf->open_file_cache_events; if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) { /* simulate successful logging */ return len; } if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool) != NGX_OK) { if (of.err == 0) { /* simulate successful logging */ return len; } ngx_log_error(NGX_LOG_ERR, r->connection->log, of.err, "testing \"%s\" existence failed", path.data); /* simulate successful logging */ return len; } if (!of.is_dir) { ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_ENOTDIR, "testing \"%s\" existence failed", path.data); /* simulate successful logging */ return len; } } if (ngx_http_script_run(r, &log, script->lengths->elts, 1, script->values->elts) == NULL) { /* simulate successful logging */ return len; } log.data[log.len - 1] = '\0'; *name = log.data; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http log \"%s\"", log.data); llcf = ngx_http_get_module_loc_conf(r, ngx_http_log_module); ngx_memzero(&of, sizeof(ngx_open_file_info_t)); of.log = 1; of.valid = llcf->open_file_cache_valid; of.min_uses = llcf->open_file_cache_min_uses; of.directio = NGX_OPEN_FILE_DIRECTIO_OFF; if (ngx_http_set_disable_symlinks(r, clcf, &log, &of) != NGX_OK) { /* simulate successful logging */ return len; } if (ngx_open_cached_file(llcf->open_file_cache, &log, &of, r->pool) != NGX_OK) { ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno, "%s \"%s\" failed", of.failed, log.data); /* simulate successful logging */ return len; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http log #%d", of.fd); n = ngx_write_fd(of.fd, buf, len); return n; } #if (NGX_ZLIB) static ssize_t ngx_http_log_gzip(ngx_fd_t fd, u_char *buf, size_t len, ngx_int_t level, ngx_log_t *log) { int rc, wbits, memlevel; u_char *out; size_t size; ssize_t n; z_stream zstream; ngx_err_t err; ngx_pool_t *pool; wbits = MAX_WBITS; memlevel = MAX_MEM_LEVEL - 1; while ((ssize_t) len < ((1 << (wbits - 1)) - 262)) { wbits--; memlevel--; } /* * This is a formula from deflateBound() for conservative upper bound of * compressed data plus 18 bytes of gzip wrapper. */ size = len + ((len + 7) >> 3) + ((len + 63) >> 6) + 5 + 18; ngx_memzero(&zstream, sizeof(z_stream)); pool = ngx_create_pool(256, log); if (pool == NULL) { /* simulate successful logging */ return len; } pool->log = log; zstream.zalloc = ngx_http_log_gzip_alloc; zstream.zfree = ngx_http_log_gzip_free; zstream.opaque = pool; out = ngx_pnalloc(pool, size); if (out == NULL) { goto done; } zstream.next_in = buf; zstream.avail_in = len; zstream.next_out = out; zstream.avail_out = size; rc = deflateInit2(&zstream, (int) level, Z_DEFLATED, wbits + 16, memlevel, Z_DEFAULT_STRATEGY); if (rc != Z_OK) { ngx_log_error(NGX_LOG_ALERT, log, 0, "deflateInit2() failed: %d", rc); goto done; } ngx_log_debug4(NGX_LOG_DEBUG_HTTP, log, 0, "deflate in: ni:%p no:%p ai:%ud ao:%ud", zstream.next_in, zstream.next_out, zstream.avail_in, zstream.avail_out); rc = deflate(&zstream, Z_FINISH); if (rc != Z_STREAM_END) { ngx_log_error(NGX_LOG_ALERT, log, 0, "deflate(Z_FINISH) failed: %d", rc); goto done; } ngx_log_debug5(NGX_LOG_DEBUG_HTTP, log, 0, "deflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d", zstream.next_in, zstream.next_out, zstream.avail_in, zstream.avail_out, rc); size -= zstream.avail_out; rc = deflateEnd(&zstream); if (rc != Z_OK) { ngx_log_error(NGX_LOG_ALERT, log, 0, "deflateEnd() failed: %d", rc); goto done; } n = ngx_write_fd(fd, out, size); if (n != (ssize_t) size) { err = (n == -1) ? ngx_errno : 0; ngx_destroy_pool(pool); ngx_set_errno(err); return -1; } done: ngx_destroy_pool(pool); /* simulate successful logging */ return len; } static void * ngx_http_log_gzip_alloc(void *opaque, u_int items, u_int size) { ngx_pool_t *pool = opaque; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pool->log, 0, "gzip alloc: n:%ud s:%ud", items, size); return ngx_palloc(pool, items * size); } static void ngx_http_log_gzip_free(void *opaque, void *address) { #if 0 ngx_pool_t *pool = opaque; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pool->log, 0, "gzip free: %p", address); #endif } #endif static void ngx_http_log_flush(ngx_open_file_t *file, ngx_log_t *log) { size_t len; ssize_t n; ngx_http_log_buf_t *buffer; buffer = file->data; len = buffer->pos - buffer->start; if (len == 0) { return; } #if (NGX_ZLIB) if (buffer->gzip) { n = ngx_http_log_gzip(file->fd, buffer->start, len, buffer->gzip, log); } else { n = ngx_write_fd(file->fd, buffer->start, len); } #else n = ngx_write_fd(file->fd, buffer->start, len); #endif if (n == -1) { ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, ngx_write_fd_n " to \"%s\" failed", file->name.data); } else if ((size_t) n != len) { ngx_log_error(NGX_LOG_ALERT, log, 0, ngx_write_fd_n " to \"%s\" was incomplete: %z of %uz", file->name.data, n, len); } buffer->pos = buffer->start; if (buffer->event && buffer->event->timer_set) { ngx_del_timer(buffer->event); } } static void ngx_http_log_flush_handler(ngx_event_t *ev) { ngx_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_int_t index; index = ngx_http_get_variable_index(cf, value); if (index == NGX_ERROR) { return NGX_ERROR; } op->len = 0; op->getlen = ngx_http_log_variable_getlen; op->run = ngx_http_log_variable; op->data = index; return NGX_OK; } static size_t ngx_http_log_variable_getlen(ngx_http_request_t *r, uintptr_t data) { uintptr_t len; ngx_http_variable_value_t *value; value = ngx_http_get_indexed_variable(r, data); if (value == NULL || value->not_found) { return 1; } len = ngx_http_log_escape(NULL, value->data, value->len); value->escape = len ? 1 : 0; return value->len + len * 3; } static u_char * ngx_http_log_variable(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) { ngx_http_variable_value_t *value; value = ngx_http_get_indexed_variable(r, op->data); if (value == NULL || value->not_found) { *buf = '-'; return buf + 1; } if (value->escape == 0) { return ngx_cpymem(buf, value->data, value->len); } else { return (u_char *) ngx_http_log_escape(buf, value->data, value->len); } } static uintptr_t ngx_http_log_escape(u_char *dst, u_char *src, size_t size) { ngx_uint_t n; static u_char hex[] = "0123456789ABCDEF"; static uint32_t escape[] = { 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ 0x00000004, /* 0000 0000 0000 0000 0000 0000 0000 0100 */ /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ 0x10000000, /* 0001 0000 0000 0000 0000 0000 0000 0000 */ /* ~}| {zyx wvut srqp onml kjih gfed cba` */ 0x80000000, /* 1000 0000 0000 0000 0000 0000 0000 0000 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ }; if (dst == NULL) { /* find the number of the characters to be escaped */ n = 0; while (size) { if (escape[*src >> 5] & (1 << (*src & 0x1f))) { n++; } src++; size--; } return (uintptr_t) n; } while (size) { if (escape[*src >> 5] & (1 << (*src & 0x1f))) { *dst++ = '\\'; *dst++ = 'x'; *dst++ = hex[*src >> 4]; *dst++ = hex[*src & 0xf]; src++; } else { *dst++ = *src++; } size--; } return (uintptr_t) dst; } static void * ngx_http_log_create_main_conf(ngx_conf_t *cf) { ngx_http_log_main_conf_t *conf; ngx_http_log_fmt_t *fmt; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_log_main_conf_t)); if (conf == NULL) { return NULL; } if (ngx_array_init(&conf->formats, cf->pool, 4, sizeof(ngx_http_log_fmt_t)) != NGX_OK) { return NULL; } fmt = ngx_array_push(&conf->formats); if (fmt == NULL) { return NULL; } ngx_str_set(&fmt->name, "combined"); fmt->flushes = NULL; fmt->ops = ngx_array_create(cf->pool, 16, sizeof(ngx_http_log_op_t)); if (fmt->ops == NULL) { return NULL; } return conf; } static void * ngx_http_log_create_loc_conf(ngx_conf_t *cf) { ngx_http_log_loc_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_log_loc_conf_t)); if (conf == NULL) { return NULL; } conf->open_file_cache = NGX_CONF_UNSET_PTR; return conf; } static char * ngx_http_log_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_log_loc_conf_t *prev = parent; ngx_http_log_loc_conf_t *conf = child; ngx_http_log_t *log; ngx_http_log_fmt_t *fmt; ngx_http_log_main_conf_t *lmcf; if (conf->open_file_cache == NGX_CONF_UNSET_PTR) { conf->open_file_cache = prev->open_file_cache; conf->open_file_cache_valid = prev->open_file_cache_valid; conf->open_file_cache_min_uses = prev->open_file_cache_min_uses; if (conf->open_file_cache == NGX_CONF_UNSET_PTR) { conf->open_file_cache = NULL; } } if (conf->logs || conf->off) { return NGX_CONF_OK; } conf->logs = prev->logs; conf->off = prev->off; if (conf->logs || conf->off) { return NGX_CONF_OK; } conf->logs = ngx_array_create(cf->pool, 2, sizeof(ngx_http_log_t)); if (conf->logs == NULL) { return NGX_CONF_ERROR; } log = ngx_array_push(conf->logs); if (log == NULL) { return NGX_CONF_ERROR; } log->file = ngx_conf_open_file(cf->cycle, &ngx_http_access_log); if (log->file == NULL) { return NGX_CONF_ERROR; } log->script = NULL; log->disk_full_time = 0; log->error_log_time = 0; 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_http_log_buf_t *buffer; ngx_http_log_fmt_t *fmt; ngx_http_log_main_conf_t *lmcf; ngx_http_script_compile_t sc; 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)); 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; } } 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 } 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->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->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; if (cf->cmd_type != NGX_HTTP_MAIN_CONF) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "the \"log_format\" directive may be used " "only on \"http\" level"); } value = cf->args->elts; fmt = lmcf->formats.elts; for (i = 0; i < lmcf->formats.nelts; i++) { if (fmt[i].name.len == value[1].len && ngx_strcmp(fmt[i].name.data, value[1].data) == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "duplicate \"log_format\" name \"%V\"", &value[1]); return NGX_CONF_ERROR; } } fmt = ngx_array_push(&lmcf->formats); if (fmt == NULL) { return NGX_CONF_ERROR; } fmt->name = value[1]; fmt->flushes = ngx_array_create(cf->pool, 4, sizeof(ngx_int_t)); if (fmt->flushes == NULL) { return NGX_CONF_ERROR; } fmt->ops = ngx_array_create(cf->pool, 16, sizeof(ngx_http_log_op_t)); if (fmt->ops == NULL) { return NGX_CONF_ERROR; } return ngx_http_log_compile_format(cf, fmt->flushes, fmt->ops, cf->args, 2); } static char * ngx_http_log_compile_format(ngx_conf_t *cf, ngx_array_t *flushes, ngx_array_t *ops, ngx_array_t *args, ngx_uint_t s) { u_char *data, *p, ch; size_t i, len; ngx_str_t *value, var; ngx_int_t *flush; ngx_uint_t bracket; ngx_http_log_op_t *op; ngx_http_log_var_t *v; value = args->elts; for ( /* void */ ; s < args->nelts; s++) { i = 0; while (i < value[s].len) { op = ngx_array_push(ops); if (op == NULL) { return NGX_CONF_ERROR; } data = &value[s].data[i]; if (value[s].data[i] == '$') { if (++i == value[s].len) { goto invalid; } if (value[s].data[i] == '{') { bracket = 1; if (++i == value[s].len) { goto invalid; } var.data = &value[s].data[i]; } else { bracket = 0; var.data = &value[s].data[i]; } for (var.len = 0; i < value[s].len; i++, var.len++) { ch = value[s].data[i]; if (ch == '}' && bracket) { i++; bracket = 0; break; } if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_') { continue; } break; } if (bracket) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the closing bracket in \"%V\" " "variable is missing", &var); return NGX_CONF_ERROR; } if (var.len == 0) { goto invalid; } for (v = ngx_http_log_vars; v->name.len; v++) { if (v->name.len == var.len && ngx_strncmp(v->name.data, var.data, var.len) == 0) { op->len = v->len; op->getlen = NULL; op->run = v->run; op->data = 0; goto found; } } if (ngx_http_log_variable_compile(cf, op, &var) != NGX_OK) { return NGX_CONF_ERROR; } if (flushes) { flush = ngx_array_push(flushes); if (flush == NULL) { return NGX_CONF_ERROR; } *flush = op->data; /* variable index */ } found: continue; } i++; while (i < value[s].len && value[s].data[i] != '$') { i++; } len = &value[s].data[i] - data; if (len) { op->len = len; op->getlen = NULL; if (len <= sizeof(uintptr_t)) { op->run = ngx_http_log_copy_short; op->data = 0; while (len--) { op->data <<= 8; op->data |= data[len]; } } else { op->run = ngx_http_log_copy_long; p = ngx_pnalloc(cf->pool, len); if (p == NULL) { return NGX_CONF_ERROR; } ngx_memcpy(p, data, len); op->data = (uintptr_t) p; } } } } return NGX_CONF_OK; invalid: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%s\"", data); return NGX_CONF_ERROR; } static char * ngx_http_log_open_file_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_log_loc_conf_t *llcf = conf; time_t inactive, valid; ngx_str_t *value, s; ngx_int_t max, min_uses; ngx_uint_t i; if (llcf->open_file_cache != NGX_CONF_UNSET_PTR) { return "is duplicate"; } value = cf->args->elts; max = 0; inactive = 10; valid = 60; min_uses = 1; for (i = 1; i < cf->args->nelts; i++) { if (ngx_strncmp(value[i].data, "max=", 4) == 0) { max = ngx_atoi(value[i].data + 4, value[i].len - 4); if (max == NGX_ERROR) { goto failed; } continue; } if (ngx_strncmp(value[i].data, "inactive=", 9) == 0) { s.len = value[i].len - 9; s.data = value[i].data + 9; inactive = ngx_parse_time(&s, 1); if (inactive == (time_t) NGX_ERROR) { goto failed; } continue; } if (ngx_strncmp(value[i].data, "min_uses=", 9) == 0) { min_uses = ngx_atoi(value[i].data + 9, value[i].len - 9); if (min_uses == NGX_ERROR) { goto failed; } continue; } if (ngx_strncmp(value[i].data, "valid=", 6) == 0) { s.len = value[i].len - 6; s.data = value[i].data + 6; valid = ngx_parse_time(&s, 1); if (valid == (time_t) NGX_ERROR) { goto failed; } continue; } if (ngx_strcmp(value[i].data, "off") == 0) { llcf->open_file_cache = NULL; continue; } failed: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid \"open_log_file_cache\" parameter \"%V\"", &value[i]); return NGX_CONF_ERROR; } if (llcf->open_file_cache == NULL) { return NGX_CONF_OK; } if (max == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"open_log_file_cache\" must have \"max\" parameter"); return NGX_CONF_ERROR; } llcf->open_file_cache = ngx_open_file_cache_init(cf->pool, max, inactive); if (llcf->open_file_cache) { llcf->open_file_cache_valid = valid; llcf->open_file_cache_min_uses = min_uses; return NGX_CONF_OK; } return NGX_CONF_ERROR; } static ngx_int_t ngx_http_log_init(ngx_conf_t *cf) { ngx_str_t *value; ngx_array_t a; ngx_http_handler_pt *h; ngx_http_log_fmt_t *fmt; ngx_http_log_main_conf_t *lmcf; ngx_http_core_main_conf_t *cmcf; lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_log_module); if (lmcf->combined_used) { if (ngx_array_init(&a, cf->pool, 1, sizeof(ngx_str_t)) != NGX_OK) { return NGX_ERROR; } value = ngx_array_push(&a); if (value == NULL) { return NGX_ERROR; } *value = ngx_http_combined_fmt; fmt = lmcf->formats.elts; if (ngx_http_log_compile_format(cf, NULL, fmt->ops, &a, 0) != NGX_CONF_OK) { return NGX_ERROR; } } cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h = ngx_array_push(&cmcf->phases[NGX_HTTP_LOG_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } *h = ngx_http_log_handler; return NGX_OK; } nginx-1.4.6/src/http/modules/ngx_http_map_module.c000644 001751 001751 00000034315 12305336445 023465 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; ngx_array_t var_values; #if (NGX_PCRE) ngx_array_t regexes; #endif ngx_http_variable_value_t *default_value; ngx_conf_t *cf; ngx_uint_t hostnames; /* unsigned hostnames:1 */ } ngx_http_map_conf_ctx_t; typedef struct { ngx_http_map_t map; ngx_http_complex_value_t value; ngx_http_variable_value_t *default_value; ngx_uint_t hostnames; /* unsigned hostnames:1 */ } ngx_http_map_ctx_t; static int ngx_libc_cdecl ngx_http_map_cmp_dns_wildcards(const void *one, const void *two); static void *ngx_http_map_create_conf(ngx_conf_t *cf); static char *ngx_http_map_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf); static ngx_command_t ngx_http_map_commands[] = { { ngx_string("map"), NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE2, ngx_http_map_block, NGX_HTTP_MAIN_CONF_OFFSET, 0, NULL }, { ngx_string("map_hash_max_size"), NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_MAIN_CONF_OFFSET, offsetof(ngx_http_map_conf_t, hash_max_size), NULL }, { ngx_string("map_hash_bucket_size"), NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_MAIN_CONF_OFFSET, offsetof(ngx_http_map_conf_t, hash_bucket_size), NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_map_module_ctx = { NULL, /* preconfiguration */ NULL, /* postconfiguration */ ngx_http_map_create_conf, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; ngx_module_t ngx_http_map_module = { NGX_MODULE_V1, &ngx_http_map_module_ctx, /* module context */ ngx_http_map_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_int_t ngx_http_map_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_http_map_ctx_t *map = (ngx_http_map_ctx_t *) data; ngx_str_t val; ngx_http_variable_value_t *value; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http map started"); if (ngx_http_complex_value(r, &map->value, &val) != NGX_OK) { return NGX_ERROR; } if (map->hostnames && val.len > 0 && val.data[val.len - 1] == '.') { val.len--; } value = ngx_http_map_find(r, &map->map, &val); if (value == NULL) { value = map->default_value; } if (!value->valid) { value = ngx_http_get_flushed_variable(r, (ngx_uint_t) value->data); if (value == NULL || value->not_found) { value = &ngx_http_variable_null_value; } } *v = *value; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http map: \"%v\" \"%v\"", &val, v); return NGX_OK; } static void * ngx_http_map_create_conf(ngx_conf_t *cf) { ngx_http_map_conf_t *mcf; mcf = ngx_palloc(cf->pool, sizeof(ngx_http_map_conf_t)); if (mcf == NULL) { return NULL; } mcf->hash_max_size = NGX_CONF_UNSET_UINT; mcf->hash_bucket_size = NGX_CONF_UNSET_UINT; return mcf; } static char * ngx_http_map_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_map_conf_t *mcf = conf; char *rv; ngx_str_t *value, name; ngx_conf_t save; ngx_pool_t *pool; ngx_hash_init_t hash; ngx_http_map_ctx_t *map; ngx_http_variable_t *var; ngx_http_map_conf_ctx_t ctx; ngx_http_compile_complex_value_t ccv; if (mcf->hash_max_size == NGX_CONF_UNSET_UINT) { mcf->hash_max_size = 2048; } if (mcf->hash_bucket_size == NGX_CONF_UNSET_UINT) { mcf->hash_bucket_size = ngx_cacheline_size; } else { mcf->hash_bucket_size = ngx_align(mcf->hash_bucket_size, ngx_cacheline_size); } map = ngx_pcalloc(cf->pool, sizeof(ngx_http_map_ctx_t)); if (map == NULL) { return NGX_CONF_ERROR; } value = cf->args->elts; ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = &map->value; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } name = value[2]; if (name.data[0] != '$') { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid variable name \"%V\"", &name); return NGX_CONF_ERROR; } name.len--; name.data++; var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE); if (var == NULL) { return NGX_CONF_ERROR; } var->get_handler = ngx_http_map_variable; var->data = (uintptr_t) map; pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log); if (pool == NULL) { return NGX_CONF_ERROR; } ctx.keys.pool = cf->pool; ctx.keys.temp_pool = pool; if (ngx_hash_keys_array_init(&ctx.keys, NGX_HASH_LARGE) != NGX_OK) { ngx_destroy_pool(pool); return NGX_CONF_ERROR; } ctx.values_hash = ngx_pcalloc(pool, sizeof(ngx_array_t) * ctx.keys.hsize); if (ctx.values_hash == NULL) { ngx_destroy_pool(pool); return NGX_CONF_ERROR; } if (ngx_array_init(&ctx.var_values, cf->pool, 2, sizeof(ngx_http_variable_value_t)) != NGX_OK) { ngx_destroy_pool(pool); return NGX_CONF_ERROR; } #if (NGX_PCRE) if (ngx_array_init(&ctx.regexes, cf->pool, 2, sizeof(ngx_http_map_regex_t)) != NGX_OK) { ngx_destroy_pool(pool); return NGX_CONF_ERROR; } #endif ctx.default_value = NULL; ctx.cf = &save; ctx.hostnames = 0; save = *cf; cf->pool = pool; cf->ctx = &ctx; cf->handler = ngx_http_map; cf->handler_conf = conf; rv = ngx_conf_parse(cf, NULL); *cf = save; if (rv != NGX_CONF_OK) { ngx_destroy_pool(pool); return rv; } map->default_value = ctx.default_value ? ctx.default_value: &ngx_http_variable_null_value; map->hostnames = ctx.hostnames; hash.key = ngx_hash_key_lc; hash.max_size = mcf->hash_max_size; hash.bucket_size = mcf->hash_bucket_size; hash.name = "map_hash"; hash.pool = cf->pool; if (ctx.keys.keys.nelts) { hash.hash = &map->map.hash.hash; hash.temp_pool = NULL; if (ngx_hash_init(&hash, ctx.keys.keys.elts, ctx.keys.keys.nelts) != NGX_OK) { ngx_destroy_pool(pool); return NGX_CONF_ERROR; } } if (ctx.keys.dns_wc_head.nelts) { ngx_qsort(ctx.keys.dns_wc_head.elts, (size_t) ctx.keys.dns_wc_head.nelts, sizeof(ngx_hash_key_t), ngx_http_map_cmp_dns_wildcards); hash.hash = NULL; hash.temp_pool = pool; if (ngx_hash_wildcard_init(&hash, ctx.keys.dns_wc_head.elts, ctx.keys.dns_wc_head.nelts) != NGX_OK) { ngx_destroy_pool(pool); return NGX_CONF_ERROR; } map->map.hash.wc_head = (ngx_hash_wildcard_t *) hash.hash; } if (ctx.keys.dns_wc_tail.nelts) { ngx_qsort(ctx.keys.dns_wc_tail.elts, (size_t) ctx.keys.dns_wc_tail.nelts, sizeof(ngx_hash_key_t), ngx_http_map_cmp_dns_wildcards); hash.hash = NULL; hash.temp_pool = pool; if (ngx_hash_wildcard_init(&hash, ctx.keys.dns_wc_tail.elts, ctx.keys.dns_wc_tail.nelts) != NGX_OK) { ngx_destroy_pool(pool); return NGX_CONF_ERROR; } map->map.hash.wc_tail = (ngx_hash_wildcard_t *) hash.hash; } #if (NGX_PCRE) if (ctx.regexes.nelts) { map->map.regex = ctx.regexes.elts; map->map.nregex = ctx.regexes.nelts; } #endif ngx_destroy_pool(pool); return rv; } static int ngx_libc_cdecl ngx_http_map_cmp_dns_wildcards(const void *one, const void *two) { ngx_hash_key_t *first, *second; first = (ngx_hash_key_t *) one; second = (ngx_hash_key_t *) two; return ngx_dns_strcmp(first->key.data, second->key.data); } static char * ngx_http_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) { ngx_int_t rc, index; ngx_str_t *value, name; ngx_uint_t i, key; ngx_http_map_conf_ctx_t *ctx; ngx_http_variable_value_t *var, **vp; ctx = cf->ctx; value = cf->args->elts; if (cf->args->nelts == 1 && ngx_strcmp(value[0].data, "hostnames") == 0) { ctx->hostnames = 1; return NGX_CONF_OK; } else if (cf->args->nelts != 2) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid number of the map parameters"); return NGX_CONF_ERROR; } if (ngx_strcmp(value[0].data, "include") == 0) { return ngx_conf_include(cf, dummy, conf); } if (value[1].data[0] == '$') { name = value[1]; name.len--; name.data++; index = ngx_http_get_variable_index(ctx->cf, &name); if (index == NGX_ERROR) { return NGX_CONF_ERROR; } var = ctx->var_values.elts; for (i = 0; i < ctx->var_values.nelts; i++) { if (index == (ngx_int_t) var[i].data) { var = &var[i]; goto found; } } var = ngx_array_push(&ctx->var_values); if (var == NULL) { return NGX_CONF_ERROR; } var->valid = 0; var->no_cacheable = 0; var->not_found = 0; var->len = 0; var->data = (u_char *) index; goto found; } key = 0; for (i = 0; i < value[1].len; i++) { key = ngx_hash(key, value[1].data[i]); } key %= ctx->keys.hsize; vp = ctx->values_hash[key].elts; if (vp) { for (i = 0; i < ctx->values_hash[key].nelts; i++) { if (value[1].len != (size_t) vp[i]->len) { continue; } if (ngx_strncmp(value[1].data, vp[i]->data, value[1].len) == 0) { var = vp[i]; goto found; } } } else { if (ngx_array_init(&ctx->values_hash[key], cf->pool, 4, sizeof(ngx_http_variable_value_t *)) != NGX_OK) { return NGX_CONF_ERROR; } } var = ngx_palloc(ctx->keys.pool, sizeof(ngx_http_variable_value_t)); if (var == NULL) { return NGX_CONF_ERROR; } var->len = value[1].len; var->data = ngx_pstrdup(ctx->keys.pool, &value[1]); if (var->data == NULL) { return NGX_CONF_ERROR; } var->valid = 1; var->no_cacheable = 0; var->not_found = 0; vp = ngx_array_push(&ctx->values_hash[key]); if (vp == NULL) { return NGX_CONF_ERROR; } *vp = var; found: if (ngx_strcmp(value[0].data, "default") == 0) { if (ctx->default_value) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "duplicate default map parameter"); return NGX_CONF_ERROR; } ctx->default_value = var; return NGX_CONF_OK; } #if (NGX_PCRE) if (value[0].len && value[0].data[0] == '~') { ngx_regex_compile_t rc; ngx_http_map_regex_t *regex; u_char errstr[NGX_MAX_CONF_ERRSTR]; regex = ngx_array_push(&ctx->regexes); if (regex == NULL) { return NGX_CONF_ERROR; } value[0].len--; value[0].data++; ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); if (value[0].data[0] == '*') { value[0].len--; value[0].data++; rc.options = NGX_REGEX_CASELESS; } rc.pattern = value[0]; rc.err.len = NGX_MAX_CONF_ERRSTR; rc.err.data = errstr; regex->regex = ngx_http_regex_compile(ctx->cf, &rc); if (regex->regex == NULL) { return NGX_CONF_ERROR; } regex->value = var; return NGX_CONF_OK; } #endif if (value[0].len && value[0].data[0] == '\\') { value[0].len--; value[0].data++; } rc = ngx_hash_add_key(&ctx->keys, &value[0], var, (ctx->hostnames) ? NGX_HASH_WILDCARD_KEY : 0); if (rc == NGX_OK) { return NGX_CONF_OK; } if (rc == NGX_DECLINED) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid hostname or wildcard \"%V\"", &value[0]); } if (rc == NGX_BUSY) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "conflicting parameter \"%V\"", &value[0]); } return NGX_CONF_ERROR; } nginx-1.4.6/src/http/modules/ngx_http_memcached_module.c000644 001751 001751 00000046116 12305336445 024620 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_TAKE1, ngx_http_upstream_bind_set_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_memcached_loc_conf_t, upstream.local), NULL }, { ngx_string("memcached_connect_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_memcached_loc_conf_t, upstream.connect_timeout), NULL }, { ngx_string("memcached_send_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_memcached_loc_conf_t, upstream.send_timeout), NULL }, { ngx_string("memcached_buffer_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_memcached_loc_conf_t, upstream.buffer_size), NULL }, { ngx_string("memcached_read_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_memcached_loc_conf_t, upstream.read_timeout), NULL }, { ngx_string("memcached_next_upstream"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_memcached_loc_conf_t, upstream.next_upstream), &ngx_http_memcached_next_upstream_masks }, { ngx_string("memcached_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->rest = NGX_HTTP_MEMCACHED_END; 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: *p = '\0'; line.len = p - u->buffer.pos - 1; line.data = u->buffer.pos; 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->key.len = sizeof("Content-Encoding") - 1; h->key.data = (u_char *) "Content-Encoding"; h->value.len = sizeof("gzip") - 1; h->value.data = (u_char *) "gzip"; r->headers_out.content_encoding = h; } length: start = p; while (*p && *p++ != CR) { /* void */ } u->headers_in.content_length_n = ngx_atoof(start, p - start - 1); if (u->headers_in.content_length_n == -1) { 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 + 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.status_n = 404; u->state->status = 404; 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; u->length += NGX_HTTP_MEMCACHED_END; return NGX_OK; } static ngx_int_t ngx_http_memcached_filter(void *data, ssize_t bytes) { ngx_http_memcached_ctx_t *ctx = data; u_char *last; ngx_buf_t *b; ngx_chain_t *cl, **ll; ngx_http_upstream_t *u; u = ctx->request->upstream; b = &u->buffer; if (u->length == (ssize_t) ctx->rest) { if (ngx_strncmp(b->last, ngx_http_memcached_end + NGX_HTTP_MEMCACHED_END - ctx->rest, bytes) != 0) { ngx_log_error(NGX_LOG_ERR, ctx->request->connection->log, 0, "memcached sent invalid trailer"); u->length = 0; ctx->rest = 0; return NGX_OK; } u->length -= bytes; ctx->rest -= bytes; if (u->length == 0) { u->keepalive = 1; } return NGX_OK; } for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) { ll = &cl->next; } cl = ngx_chain_get_free_buf(ctx->request->pool, &u->free_bufs); if (cl == NULL) { return NGX_ERROR; } cl->buf->flush = 1; cl->buf->memory = 1; *ll = cl; last = b->last; cl->buf->pos = last; b->last += bytes; cl->buf->last = b->last; cl->buf->tag = u->output.tag; ngx_log_debug4(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0, "memcached filter bytes:%z size:%z length:%z 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 += u->length - NGX_HTTP_MEMCACHED_END; if (ngx_strncmp(last, ngx_http_memcached_end, b->last - last) != 0) { ngx_log_error(NGX_LOG_ERR, ctx->request->connection->log, 0, "memcached sent invalid trailer"); b->last = last; cl->buf->last = last; u->length = 0; ctx->rest = 0; return NGX_OK; } ctx->rest -= b->last - last; b->last = last; cl->buf->last = last; u->length = ctx->rest; if (u->length == 0) { u->keepalive = 1; } return NGX_OK; } static void ngx_http_memcached_abort_request(ngx_http_request_t *r) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "abort http memcached request"); return; } static void ngx_http_memcached_finalize_request(ngx_http_request_t *r, ngx_int_t rc) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "finalize http memcached request"); return; } static void * ngx_http_memcached_create_loc_conf(ngx_conf_t *cf) { ngx_http_memcached_loc_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_memcached_loc_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->upstream.bufs.num = 0; * conf->upstream.next_upstream = 0; * conf->upstream.temp_path = NULL; * conf->upstream.uri = { 0, NULL }; * conf->upstream.location = NULL; */ conf->upstream.local = NGX_CONF_UNSET_PTR; conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.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->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_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_size_value(conf->upstream.buffer_size, prev->upstream.buffer_size, (size_t) ngx_pagesize); ngx_conf_merge_bitmask_value(conf->upstream.next_upstream, prev->upstream.next_upstream, (NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_ERROR |NGX_HTTP_UPSTREAM_FT_TIMEOUT)); if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) { conf->upstream.next_upstream = NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_OFF; } if (conf->upstream.upstream == NULL) { conf->upstream.upstream = prev->upstream.upstream; } if (conf->index == NGX_CONF_UNSET) { conf->index = prev->index; } ngx_conf_merge_uint_value(conf->gzip_flag, prev->gzip_flag, 0); return NGX_CONF_OK; } static char * ngx_http_memcached_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_memcached_loc_conf_t *mlcf = conf; ngx_str_t *value; ngx_url_t u; ngx_http_core_loc_conf_t *clcf; if (mlcf->upstream.upstream) { return "is duplicate"; } value = cf->args->elts; ngx_memzero(&u, sizeof(ngx_url_t)); u.url = value[1]; u.no_resolve = 1; mlcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0); if (mlcf->upstream.upstream == NULL) { return NGX_CONF_ERROR; } clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); clcf->handler = ngx_http_memcached_handler; if (clcf->name.data[clcf->name.len - 1] == '/') { clcf->auto_redirect = 1; } mlcf->index = ngx_http_get_variable_index(cf, &ngx_http_memcached_key); if (mlcf->index == NGX_ERROR) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } nginx-1.4.6/src/http/modules/ngx_http_mp4_module.c000644 001751 001751 00000260521 12305336445 023410 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_MDIA_ATOM 2 #define NGX_HTTP_MP4_MDHD_ATOM 3 #define NGX_HTTP_MP4_HDLR_ATOM 4 #define NGX_HTTP_MP4_MINF_ATOM 5 #define NGX_HTTP_MP4_VMHD_ATOM 6 #define NGX_HTTP_MP4_SMHD_ATOM 7 #define NGX_HTTP_MP4_DINF_ATOM 8 #define NGX_HTTP_MP4_STBL_ATOM 9 #define NGX_HTTP_MP4_STSD_ATOM 10 #define NGX_HTTP_MP4_STTS_ATOM 11 #define NGX_HTTP_MP4_STTS_DATA 12 #define NGX_HTTP_MP4_STSS_ATOM 13 #define NGX_HTTP_MP4_STSS_DATA 14 #define NGX_HTTP_MP4_CTTS_ATOM 15 #define NGX_HTTP_MP4_CTTS_DATA 16 #define NGX_HTTP_MP4_STSC_ATOM 17 #define NGX_HTTP_MP4_STSC_CHUNK 18 #define NGX_HTTP_MP4_STSC_DATA 19 #define NGX_HTTP_MP4_STSZ_ATOM 20 #define NGX_HTTP_MP4_STSZ_DATA 21 #define NGX_HTTP_MP4_STCO_ATOM 22 #define NGX_HTTP_MP4_STCO_DATA 23 #define NGX_HTTP_MP4_CO64_ATOM 24 #define NGX_HTTP_MP4_CO64_DATA 25 #define NGX_HTTP_MP4_LAST_ATOM NGX_HTTP_MP4_CO64_DATA typedef struct { size_t buffer_size; size_t max_buffer_size; } ngx_http_mp4_conf_t; typedef struct { u_char chunk[4]; u_char samples[4]; u_char id[4]; } ngx_mp4_stsc_entry_t; typedef struct { uint32_t timescale; uint32_t time_to_sample_entries; uint32_t sample_to_chunk_entries; uint32_t sync_samples_entries; uint32_t composition_offset_entries; uint32_t sample_sizes_entries; uint32_t chunks; ngx_uint_t start_sample; ngx_uint_t start_chunk; ngx_uint_t chunk_samples; uint64_t chunk_samples_size; off_t start_offset; size_t tkhd_size; size_t mdhd_size; size_t hdlr_size; size_t vmhd_size; size_t smhd_size; size_t dinf_size; size_t size; ngx_chain_t out[NGX_HTTP_MP4_LAST_ATOM + 1]; ngx_buf_t trak_atom_buf; ngx_buf_t tkhd_atom_buf; ngx_buf_t mdia_atom_buf; ngx_buf_t mdhd_atom_buf; ngx_buf_t hdlr_atom_buf; ngx_buf_t minf_atom_buf; ngx_buf_t vmhd_atom_buf; ngx_buf_t smhd_atom_buf; ngx_buf_t dinf_atom_buf; ngx_buf_t stbl_atom_buf; ngx_buf_t stsd_atom_buf; ngx_buf_t stts_atom_buf; ngx_buf_t stts_data_buf; ngx_buf_t stss_atom_buf; ngx_buf_t stss_data_buf; ngx_buf_t ctts_atom_buf; ngx_buf_t ctts_data_buf; ngx_buf_t stsc_atom_buf; ngx_buf_t stsc_chunk_buf; ngx_buf_t stsc_data_buf; ngx_buf_t stsz_atom_buf; ngx_buf_t stsz_data_buf; ngx_buf_t stco_atom_buf; ngx_buf_t stco_data_buf; ngx_buf_t co64_atom_buf; ngx_buf_t co64_data_buf; ngx_mp4_stsc_entry_t stsc_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; uint32_t timescale; ngx_http_request_t *request; ngx_array_t trak; ngx_http_mp4_trak_t traks[2]; size_t ftyp_size; size_t moov_size; ngx_chain_t *out; ngx_chain_t ftyp_atom; ngx_chain_t moov_atom; ngx_chain_t mvhd_atom; ngx_chain_t mdat_atom; ngx_chain_t mdat_data; ngx_buf_t ftyp_atom_buf; ngx_buf_t moov_atom_buf; ngx_buf_t mvhd_atom_buf; ngx_buf_t mdat_atom_buf; ngx_buf_t mdat_data_buf; u_char moov_atom_header[8]; u_char mdat_atom_header[16]; } ngx_http_mp4_file_t; typedef struct { char *name; ngx_int_t (*handler)(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); } ngx_http_mp4_atom_handler_t; #define ngx_mp4_atom_header(mp4) (mp4->buffer_pos - 8) #define ngx_mp4_atom_data(mp4) mp4->buffer_pos #define ngx_mp4_atom_data_size(t) (uint64_t) (sizeof(t) - 8) #define ngx_mp4_atom_next(mp4, n) mp4->buffer_pos += n; mp4->offset += n #define ngx_mp4_set_atom_name(p, n1, n2, n3, n4) \ ((u_char *) (p))[4] = n1; \ ((u_char *) (p))[5] = n2; \ ((u_char *) (p))[6] = n3; \ ((u_char *) (p))[7] = n4 #define ngx_mp4_get_32value(p) \ ( ((uint32_t) ((u_char *) (p))[0] << 24) \ + ( ((u_char *) (p))[1] << 16) \ + ( ((u_char *) (p))[2] << 8) \ + ( ((u_char *) (p))[3]) ) #define ngx_mp4_set_32value(p, n) \ ((u_char *) (p))[0] = (u_char) ((n) >> 24); \ ((u_char *) (p))[1] = (u_char) ((n) >> 16); \ ((u_char *) (p))[2] = (u_char) ((n) >> 8); \ ((u_char *) (p))[3] = (u_char) (n) #define ngx_mp4_get_64value(p) \ ( ((uint64_t) ((u_char *) (p))[0] << 56) \ + ((uint64_t) ((u_char *) (p))[1] << 48) \ + ((uint64_t) ((u_char *) (p))[2] << 40) \ + ((uint64_t) ((u_char *) (p))[3] << 32) \ + ((uint64_t) ((u_char *) (p))[4] << 24) \ + ( ((u_char *) (p))[5] << 16) \ + ( ((u_char *) (p))[6] << 8) \ + ( ((u_char *) (p))[7]) ) #define ngx_mp4_set_64value(p, n) \ ((u_char *) (p))[0] = (u_char) ((uint64_t) (n) >> 56); \ ((u_char *) (p))[1] = (u_char) ((uint64_t) (n) >> 48); \ ((u_char *) (p))[2] = (u_char) ((uint64_t) (n) >> 40); \ ((u_char *) (p))[3] = (u_char) ((uint64_t) (n) >> 32); \ ((u_char *) (p))[4] = (u_char) ( (n) >> 24); \ ((u_char *) (p))[5] = (u_char) ( (n) >> 16); \ ((u_char *) (p))[6] = (u_char) ( (n) >> 8); \ ((u_char *) (p))[7] = (u_char) (n) #define ngx_mp4_last_trak(mp4) \ &((ngx_http_mp4_trak_t *) mp4->trak.elts)[mp4->trak.nelts - 1] static ngx_int_t ngx_http_mp4_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); static ngx_int_t ngx_http_mp4_read_mvhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_read_trak_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static void ngx_http_mp4_update_trak_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak); static ngx_int_t ngx_http_mp4_read_cmov_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_read_tkhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_read_mdia_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static void ngx_http_mp4_update_mdia_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak); static ngx_int_t ngx_http_mp4_read_mdhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_read_hdlr_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_read_minf_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static void ngx_http_mp4_update_minf_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak); static ngx_int_t ngx_http_mp4_read_dinf_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_read_vmhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_read_smhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_read_stbl_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static void ngx_http_mp4_update_stbl_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak); static ngx_int_t ngx_http_mp4_read_stsd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_read_stts_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_update_stts_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak); static ngx_int_t ngx_http_mp4_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 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 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_read_stsz_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_update_stsz_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak); static ngx_int_t ngx_http_mp4_read_stco_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_update_stco_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak); static void ngx_http_mp4_adjust_stco_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak, int32_t adjustment); static ngx_int_t ngx_http_mp4_read_co64_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_update_co64_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak); static void ngx_http_mp4_adjust_co64_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak, off_t adjustment); static char *ngx_http_mp4(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static void *ngx_http_mp4_create_conf(ngx_conf_t *cf); static char *ngx_http_mp4_merge_conf(ngx_conf_t *cf, void *parent, void *child); static ngx_command_t ngx_http_mp4_commands[] = { { ngx_string("mp4"), NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, ngx_http_mp4, 0, 0, NULL }, { ngx_string("mp4_buffer_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_mp4_conf_t, buffer_size), NULL }, { ngx_string("mp4_max_buffer_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_mp4_conf_t, max_buffer_size), NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_mp4_module_ctx = { NULL, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_mp4_create_conf, /* create location configuration */ ngx_http_mp4_merge_conf /* merge location configuration */ }; ngx_module_t ngx_http_mp4_module = { NGX_MODULE_V1, &ngx_http_mp4_module_ctx, /* module context */ ngx_http_mp4_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_mp4_atom_handler_t ngx_http_mp4_atoms[] = { { "ftyp", ngx_http_mp4_read_ftyp_atom }, { "moov", ngx_http_mp4_read_moov_atom }, { "mdat", ngx_http_mp4_read_mdat_atom }, { NULL, NULL } }; static ngx_http_mp4_atom_handler_t ngx_http_mp4_moov_atoms[] = { { "mvhd", ngx_http_mp4_read_mvhd_atom }, { "trak", ngx_http_mp4_read_trak_atom }, { "cmov", ngx_http_mp4_read_cmov_atom }, { NULL, NULL } }; static ngx_http_mp4_atom_handler_t ngx_http_mp4_trak_atoms[] = { { "tkhd", ngx_http_mp4_read_tkhd_atom }, { "mdia", ngx_http_mp4_read_mdia_atom }, { NULL, NULL } }; static ngx_http_mp4_atom_handler_t ngx_http_mp4_mdia_atoms[] = { { "mdhd", ngx_http_mp4_read_mdhd_atom }, { "hdlr", ngx_http_mp4_read_hdlr_atom }, { "minf", ngx_http_mp4_read_minf_atom }, { NULL, NULL } }; static ngx_http_mp4_atom_handler_t ngx_http_mp4_minf_atoms[] = { { "vmhd", ngx_http_mp4_read_vmhd_atom }, { "smhd", ngx_http_mp4_read_smhd_atom }, { "dinf", ngx_http_mp4_read_dinf_atom }, { "stbl", ngx_http_mp4_read_stbl_atom }, { NULL, NULL } }; static ngx_http_mp4_atom_handler_t ngx_http_mp4_stbl_atoms[] = { { "stsd", ngx_http_mp4_read_stsd_atom }, { "stts", ngx_http_mp4_read_stts_atom }, { "stss", ngx_http_mp4_read_stss_atom }, { "ctts", ngx_http_mp4_read_ctts_atom }, { "stsc", ngx_http_mp4_read_stsc_atom }, { "stsz", ngx_http_mp4_read_stsz_atom }, { "stco", ngx_http_mp4_read_stco_atom }, { "co64", ngx_http_mp4_read_co64_atom }, { NULL, NULL } }; static ngx_int_t ngx_http_mp4_handler(ngx_http_request_t *r) { u_char *last; size_t root; ngx_int_t rc, start; ngx_uint_t level; ngx_str_t path, value; ngx_log_t *log; ngx_buf_t *b; ngx_chain_t out; ngx_http_mp4_file_t *mp4; ngx_open_file_info_t of; ngx_http_core_loc_conf_t *clcf; if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) { return NGX_HTTP_NOT_ALLOWED; } if (r->uri.data[r->uri.len - 1] == '/') { return NGX_DECLINED; } rc = ngx_http_discard_request_body(r); if (rc != NGX_OK) { return rc; } last = ngx_http_map_uri_to_path(r, &path, &root, 0); if (last == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } log = r->connection->log; path.len = last - path.data; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http mp4 filename: \"%V\"", &path); clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); ngx_memzero(&of, sizeof(ngx_open_file_info_t)); of.read_ahead = clcf->read_ahead; of.directio = NGX_MAX_OFF_T_VALUE; of.valid = clcf->open_file_cache_valid; of.min_uses = clcf->open_file_cache_min_uses; of.errors = clcf->open_file_cache_errors; of.events = clcf->open_file_cache_events; if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool) != NGX_OK) { switch (of.err) { case 0: return NGX_HTTP_INTERNAL_SERVER_ERROR; case NGX_ENOENT: case NGX_ENOTDIR: case NGX_ENAMETOOLONG: level = NGX_LOG_ERR; rc = NGX_HTTP_NOT_FOUND; break; case NGX_EACCES: #if (NGX_HAVE_OPENAT) case NGX_EMLINK: case NGX_ELOOP: #endif level = NGX_LOG_ERR; rc = NGX_HTTP_FORBIDDEN; break; default: level = NGX_LOG_CRIT; rc = NGX_HTTP_INTERNAL_SERVER_ERROR; break; } if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) { ngx_log_error(level, log, of.err, "%s \"%s\" failed", of.failed, path.data); } return rc; } if (!of.is_file) { if (ngx_close_file(of.fd) == NGX_FILE_ERROR) { ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, ngx_close_file_n " \"%s\" failed", path.data); } return NGX_DECLINED; } r->root_tested = !r->error_page; r->allow_ranges = 1; start = -1; r->headers_out.content_length_n = of.size; mp4 = NULL; b = NULL; if (r->args.len) { if (ngx_http_arg(r, (u_char *) "start", 5, &value) == NGX_OK) { /* * A Flash player may send start value with a lot of digits * after dot so strtod() is used instead of atofp(). NaNs and * infinities become negative numbers after (int) conversion. */ ngx_set_errno(0); start = (int) (strtod((char *) value.data, NULL) * 1000); if (ngx_errno == 0 && start >= 0) { r->allow_ranges = 0; 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->request = r; switch (ngx_http_mp4_process(mp4)) { case NGX_DECLINED: if (mp4->buffer) { ngx_pfree(r->pool, mp4->buffer); } ngx_pfree(r->pool, mp4); mp4 = NULL; break; case NGX_OK: r->headers_out.content_length_n = mp4->content_length; break; default: /* NGX_ERROR */ if (mp4->buffer) { ngx_pfree(r->pool, mp4->buffer); } ngx_pfree(r->pool, mp4); return NGX_HTTP_INTERNAL_SERVER_ERROR; } } } } log->action = "sending mp4 to client"; if (clcf->directio <= of.size) { /* * DIRECTIO is set on transfer only * to allow kernel to cache "moov" atom */ if (ngx_directio_on(of.fd) == NGX_FILE_ERROR) { ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, ngx_directio_on_n " \"%s\" failed", path.data); } of.is_directio = 1; if (mp4) { mp4->file.directio = 1; } } r->headers_out.status = NGX_HTTP_OK; r->headers_out.last_modified_time = of.mtime; if (ngx_http_set_etag(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_http_set_content_type(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (mp4 == NULL) { b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); if (b == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t)); if (b->file == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } } rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { return rc; } if (mp4) { return ngx_http_output_filter(r, mp4->out); } b->file_pos = 0; b->file_last = of.size; b->in_file = b->file_last ? 1 : 0; b->last_buf = (r == r->main) ? 1 : 0; b->last_in_chain = 1; b->file->fd = of.fd; b->file->name = path; b->file->log = log; b->file->directio = of.is_directio; out.buf = b; out.next = NULL; return ngx_http_output_filter(r, &out); } static ngx_int_t ngx_http_mp4_process(ngx_http_mp4_file_t *mp4) { off_t start_offset, 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_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 start:%ui", mp4->start); 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; trak = mp4->trak.elts; for (i = 0; i < mp4->trak.nelts; i++) { if (ngx_http_mp4_update_stts_atom(mp4, &trak[i]) != NGX_OK) { return NGX_ERROR; } if (ngx_http_mp4_update_stss_atom(mp4, &trak[i]) != NGX_OK) { return NGX_ERROR; } ngx_http_mp4_update_ctts_atom(mp4, &trak[i]); if (ngx_http_mp4_update_stsc_atom(mp4, &trak[i]) != NGX_OK) { return NGX_ERROR; } if (ngx_http_mp4_update_stsz_atom(mp4, &trak[i]) != NGX_OK) { return NGX_ERROR; } if (trak[i].out[NGX_HTTP_MP4_CO64_DATA].buf) { if (ngx_http_mp4_update_co64_atom(mp4, &trak[i]) != NGX_OK) { return NGX_ERROR; } } else { if (ngx_http_mp4_update_stco_atom(mp4, &trak[i]) != NGX_OK) { return NGX_ERROR; } } ngx_http_mp4_update_stbl_atom(mp4, &trak[i]); ngx_http_mp4_update_minf_atom(mp4, &trak[i]); trak[i].size += trak[i].mdhd_size; trak[i].size += trak[i].hdlr_size; ngx_http_mp4_update_mdia_atom(mp4, &trak[i]); trak[i].size += trak[i].tkhd_size; ngx_http_mp4_update_trak_atom(mp4, &trak[i]); mp4->moov_size += trak[i].size; if (start_offset > trak[i].start_offset) { start_offset = trak[i].start_offset; } *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; } } } 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) - start_offset; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 adjustment:%O", adjustment); for (i = 0; i < mp4->trak.nelts; i++) { if (trak[i].out[NGX_HTTP_MP4_CO64_DATA].buf) { ngx_http_mp4_adjust_co64_atom(mp4, &trak[i], adjustment); } else { ngx_http_mp4_adjust_stco_atom(mp4, &trak[i], (int32_t) adjustment); } } return NGX_OK; } typedef struct { u_char size[4]; u_char name[4]; } ngx_mp4_atom_header_t; typedef struct { u_char size[4]; u_char name[4]; u_char size64[8]; } ngx_mp4_atom_header64_t; static ngx_int_t ngx_http_mp4_read_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_atom_handler_t *atom, uint64_t atom_data_size) { off_t end; size_t atom_header_size; u_char *atom_header, *atom_name; uint64_t atom_size; ngx_int_t rc; ngx_uint_t n; end = mp4->offset + atom_data_size; while (mp4->offset < end) { if (ngx_http_mp4_read(mp4, sizeof(uint32_t)) != NGX_OK) { return NGX_ERROR; } atom_header = mp4->buffer_pos; atom_size = ngx_mp4_get_32value(atom_header); atom_header_size = sizeof(ngx_mp4_atom_header_t); if (atom_size == 0) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 atom end"); return NGX_OK; } if (atom_size < sizeof(ngx_mp4_atom_header_t)) { if (atom_size == 1) { if (ngx_http_mp4_read(mp4, sizeof(ngx_mp4_atom_header64_t)) != NGX_OK) { return NGX_ERROR; } /* 64-bit atom size */ atom_header = mp4->buffer_pos; atom_size = ngx_mp4_get_64value(atom_header + 8); atom_header_size = sizeof(ngx_mp4_atom_header64_t); } else { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 atom is too small:%uL", mp4->file.name.data, atom_size); return NGX_ERROR; } } if (ngx_http_mp4_read(mp4, sizeof(ngx_mp4_atom_header_t)) != NGX_OK) { return NGX_ERROR; } atom_header = mp4->buffer_pos; atom_name = atom_header + sizeof(uint32_t); ngx_log_debug4(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 atom: %*s @%O:%uL", 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) + atom_data_size > mp4->buffer_end) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 ftyp atom is too large:%uL", mp4->file.name.data, atom_data_size); return NGX_ERROR; } atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; ftyp_atom = ngx_palloc(mp4->request->pool, atom_size); if (ftyp_atom == NULL) { return NGX_ERROR; } ngx_mp4_set_32value(ftyp_atom, atom_size); ngx_mp4_set_atom_name(ftyp_atom, 'f', 't', 'y', 'p'); /* * only moov atom content is guaranteed to be in mp4->buffer * during sending response, so ftyp atom content should be copied */ ngx_memcpy(ftyp_atom + sizeof(ngx_mp4_atom_header_t), ngx_mp4_atom_data(mp4), (size_t) atom_data_size); atom = &mp4->ftyp_atom_buf; atom->temporary = 1; atom->pos = ftyp_atom; atom->last = ftyp_atom + atom_size; mp4->ftyp_atom.buf = atom; mp4->ftyp_size = atom_size; mp4->content_length = atom_size; ngx_mp4_atom_next(mp4, atom_data_size); return NGX_OK; } /* * Small excess buffer to process atoms after moov atom, mp4->buffer_start * will be set to this buffer part after moov atom processing. */ #define NGX_HTTP_MP4_MOOV_BUFFER_EXCESS (4 * 1024) static ngx_int_t ngx_http_mp4_read_moov_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { ngx_int_t rc; ngx_uint_t no_mdat; ngx_buf_t *atom; ngx_http_mp4_conf_t *conf; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 moov atom"); no_mdat = (mp4->mdat_atom.buf == NULL); if (no_mdat && mp4->start == 0) { /* * send original file if moov atom resides before * mdat atom and client requests integral file */ return NGX_DECLINED; } conf = ngx_http_get_module_loc_conf(mp4->request, ngx_http_mp4_module); if (atom_data_size > mp4->buffer_size) { if (atom_data_size > conf->max_buffer_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 moov atom is too large:%uL, " "you may want to increase mp4_max_buffer_size", mp4->file.name.data, atom_data_size); return NGX_ERROR; } ngx_pfree(mp4->request->pool, mp4->buffer); mp4->buffer = NULL; mp4->buffer_pos = NULL; mp4->buffer_end = NULL; mp4->buffer_size = (size_t) atom_data_size + NGX_HTTP_MP4_MOOV_BUFFER_EXCESS * no_mdat; } if (ngx_http_mp4_read(mp4, (size_t) atom_data_size) != NGX_OK) { return NGX_ERROR; } mp4->trak.elts = &mp4->traks; mp4->trak.size = sizeof(ngx_http_mp4_trak_t); mp4->trak.nalloc = 2; mp4->trak.pool = mp4->request->pool; atom = &mp4->moov_atom_buf; atom->temporary = 1; atom->pos = mp4->moov_atom_header; atom->last = mp4->moov_atom_header + 8; mp4->moov_atom.buf = &mp4->moov_atom_buf; rc = ngx_http_mp4_read_atom(mp4, ngx_http_mp4_moov_atoms, atom_data_size); ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 moov atom done"); if (no_mdat) { mp4->buffer_start = mp4->buffer_pos; mp4->buffer_size = NGX_HTTP_MP4_MOOV_BUFFER_EXCESS; if (mp4->buffer_start + mp4->buffer_size > mp4->buffer_end) { mp4->buffer = NULL; mp4->buffer_pos = NULL; mp4->buffer_end = NULL; } } else { /* skip atoms after moov atom */ mp4->offset = mp4->end; } return rc; } static ngx_int_t ngx_http_mp4_read_mdat_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { ngx_buf_t *data; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 mdat atom"); data = &mp4->mdat_data_buf; data->file = &mp4->file; data->in_file = 1; data->last_buf = 1; data->last_in_chain = 1; data->file_last = mp4->offset + atom_data_size; mp4->mdat_atom.buf = &mp4->mdat_atom_buf; mp4->mdat_atom.next = &mp4->mdat_data; mp4->mdat_data.buf = data; if (mp4->trak.nelts) { /* skip atoms after mdat atom */ mp4->offset = mp4->end; } else { ngx_mp4_atom_next(mp4, atom_data_size); } return NGX_OK; } static size_t ngx_http_mp4_update_mdat_atom(ngx_http_mp4_file_t *mp4, off_t start_offset) { off_t atom_data_size; u_char *atom_header; uint32_t atom_header_size; uint64_t atom_size; ngx_buf_t *atom; atom_data_size = mp4->mdat_data.buf->file_last - start_offset; mp4->mdat_data.buf->file_pos = start_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 > 0xffffffff) { atom_size = 1; atom_header_size = sizeof(ngx_mp4_atom_header64_t); ngx_mp4_set_64value(atom_header + sizeof(ngx_mp4_atom_header_t), sizeof(ngx_mp4_atom_header64_t) + atom_data_size); } else { atom_size = sizeof(ngx_mp4_atom_header_t) + atom_data_size; atom_header_size = sizeof(ngx_mp4_atom_header_t); } mp4->content_length += atom_header_size + atom_data_size; ngx_mp4_set_32value(atom_header, atom_size); ngx_mp4_set_atom_name(atom_header, 'm', 'd', 'a', 't'); atom = &mp4->mdat_atom_buf; atom->temporary = 1; atom->pos = atom_header; atom->last = atom_header + atom_header_size; return atom_header_size; } typedef struct { u_char size[4]; u_char name[4]; u_char version[1]; u_char flags[3]; u_char creation_time[4]; u_char modification_time[4]; u_char timescale[4]; u_char duration[4]; u_char rate[4]; u_char volume[2]; u_char reserved[10]; u_char matrix[36]; u_char preview_time[4]; u_char preview_duration[4]; u_char poster_time[4]; u_char selection_time[4]; u_char selection_duration[4]; u_char current_time[4]; u_char next_track_id[4]; } ngx_mp4_mvhd_atom_t; typedef struct { u_char size[4]; u_char name[4]; u_char version[1]; u_char flags[3]; u_char creation_time[8]; u_char modification_time[8]; u_char timescale[4]; u_char duration[8]; u_char rate[4]; u_char volume[2]; u_char reserved[10]; u_char matrix[36]; u_char preview_time[4]; u_char preview_duration[4]; u_char poster_time[4]; u_char selection_time[4]; u_char selection_duration[4]; u_char current_time[4]; u_char next_track_id[4]; } ngx_mp4_mvhd64_atom_t; static ngx_int_t ngx_http_mp4_read_mvhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { u_char *atom_header; size_t atom_size; uint32_t timescale; uint64_t duration; ngx_buf_t *atom; ngx_mp4_mvhd_atom_t *mvhd_atom; ngx_mp4_mvhd64_atom_t *mvhd64_atom; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 mvhd atom"); atom_header = ngx_mp4_atom_header(mp4); mvhd_atom = (ngx_mp4_mvhd_atom_t *) atom_header; mvhd64_atom = (ngx_mp4_mvhd64_atom_t *) atom_header; ngx_mp4_set_atom_name(atom_header, 'm', 'v', 'h', 'd'); if (ngx_mp4_atom_data_size(ngx_mp4_mvhd_atom_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 mvhd atom too small", mp4->file.name.data); return NGX_ERROR; } if (mvhd_atom->version[0] == 0) { /* version 0: 32-bit duration */ timescale = ngx_mp4_get_32value(mvhd_atom->timescale); duration = ngx_mp4_get_32value(mvhd_atom->duration); } else { /* version 1: 64-bit duration */ if (ngx_mp4_atom_data_size(ngx_mp4_mvhd64_atom_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 mvhd atom too small", mp4->file.name.data); return NGX_ERROR; } timescale = ngx_mp4_get_32value(mvhd64_atom->timescale); duration = ngx_mp4_get_64value(mvhd64_atom->duration); } mp4->timescale = timescale; ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mvhd timescale:%uD, duration:%uL, time:%.3fs", timescale, duration, (double) duration / timescale); duration -= (uint64_t) mp4->start * timescale / 1000; 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 + 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 reverved3[2]; u_char matrix[36]; u_char width[4]; u_char heigth[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 reverved3[2]; u_char matrix[36]; u_char width[4]; u_char heigth[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; 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); duration -= (uint64_t) mp4->start * mp4->timescale / 1000; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "tkhd new duration:%uL, time:%.3fs", duration, (double) duration / mp4->timescale); atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; trak = ngx_mp4_last_trak(mp4); trak->tkhd_size = atom_size; ngx_mp4_set_32value(tkhd_atom->size, atom_size); if (tkhd_atom->version[0] == 0) { ngx_mp4_set_32value(tkhd_atom->duration, duration); } else { ngx_mp4_set_64value(tkhd64_atom->duration, duration); } atom = &trak->tkhd_atom_buf; atom->temporary = 1; atom->pos = atom_header; atom->last = atom_header + atom_size; trak->out[NGX_HTTP_MP4_TKHD_ATOM].buf = atom; ngx_mp4_atom_next(mp4, atom_data_size); return NGX_OK; } static ngx_int_t ngx_http_mp4_read_mdia_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { u_char *atom_header; ngx_buf_t *atom; ngx_http_mp4_trak_t *trak; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "process mdia atom"); atom_header = ngx_mp4_atom_header(mp4); ngx_mp4_set_atom_name(atom_header, 'm', 'd', 'i', 'a'); trak = ngx_mp4_last_trak(mp4); atom = &trak->mdia_atom_buf; atom->temporary = 1; atom->pos = atom_header; atom->last = atom_header + sizeof(ngx_mp4_atom_header_t); trak->out[NGX_HTTP_MP4_MDIA_ATOM].buf = atom; return ngx_http_mp4_read_atom(mp4, ngx_http_mp4_mdia_atoms, atom_data_size); } static void ngx_http_mp4_update_mdia_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak) { ngx_buf_t *atom; trak->size += sizeof(ngx_mp4_atom_header_t); atom = &trak->mdia_atom_buf; ngx_mp4_set_32value(atom->pos, trak->size); } typedef struct { u_char size[4]; u_char name[4]; u_char version[1]; u_char flags[3]; u_char creation_time[4]; u_char modification_time[4]; u_char timescale[4]; u_char duration[4]; u_char language[2]; u_char quality[2]; } ngx_mp4_mdhd_atom_t; typedef struct { u_char size[4]; u_char name[4]; u_char version[1]; u_char flags[3]; u_char creation_time[8]; u_char modification_time[8]; u_char timescale[4]; u_char duration[8]; u_char language[2]; u_char quality[2]; } ngx_mp4_mdhd64_atom_t; static ngx_int_t ngx_http_mp4_read_mdhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { u_char *atom_header; size_t atom_size; uint32_t timescale; uint64_t duration; 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); duration -= (uint64_t) mp4->start * timescale / 1000; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mdhd new duration:%uL, time:%.3fs", duration, (double) duration / timescale); atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; trak = ngx_mp4_last_trak(mp4); trak->mdhd_size = atom_size; trak->timescale = timescale; ngx_mp4_set_32value(mdhd_atom->size, atom_size); if (mdhd_atom->version[0] == 0) { ngx_mp4_set_32value(mdhd_atom->duration, duration); } else { ngx_mp4_set_64value(mdhd64_atom->duration, duration); } atom = &trak->mdhd_atom_buf; atom->temporary = 1; atom->pos = atom_header; atom->last = atom_header + atom_size; trak->out[NGX_HTTP_MP4_MDHD_ATOM].buf = atom; ngx_mp4_atom_next(mp4, atom_data_size); return NGX_OK; } static ngx_int_t ngx_http_mp4_read_hdlr_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { u_char *atom_header; size_t atom_size; ngx_buf_t *atom; ngx_http_mp4_trak_t *trak; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 hdlr atom"); atom_header = ngx_mp4_atom_header(mp4); atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; ngx_mp4_set_32value(atom_header, atom_size); ngx_mp4_set_atom_name(atom_header, 'h', 'd', 'l', 'r'); trak = ngx_mp4_last_trak(mp4); atom = &trak->hdlr_atom_buf; atom->temporary = 1; atom->pos = atom_header; atom->last = atom_header + atom_size; trak->hdlr_size = atom_size; trak->out[NGX_HTTP_MP4_HDLR_ATOM].buf = atom; ngx_mp4_atom_next(mp4, atom_data_size); return NGX_OK; } static ngx_int_t ngx_http_mp4_read_minf_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { u_char *atom_header; ngx_buf_t *atom; ngx_http_mp4_trak_t *trak; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "process minf atom"); atom_header = ngx_mp4_atom_header(mp4); ngx_mp4_set_atom_name(atom_header, 'm', 'i', 'n', 'f'); trak = ngx_mp4_last_trak(mp4); atom = &trak->minf_atom_buf; atom->temporary = 1; atom->pos = atom_header; atom->last = atom_header + sizeof(ngx_mp4_atom_header_t); trak->out[NGX_HTTP_MP4_MINF_ATOM].buf = atom; return ngx_http_mp4_read_atom(mp4, ngx_http_mp4_minf_atoms, atom_data_size); } static void ngx_http_mp4_update_minf_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak) { ngx_buf_t *atom; trak->size += sizeof(ngx_mp4_atom_header_t) + trak->vmhd_size + trak->smhd_size + trak->dinf_size; atom = &trak->minf_atom_buf; ngx_mp4_set_32value(atom->pos, trak->size); } static ngx_int_t ngx_http_mp4_read_vmhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { u_char *atom_header; size_t atom_size; ngx_buf_t *atom; ngx_http_mp4_trak_t *trak; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 vmhd atom"); atom_header = ngx_mp4_atom_header(mp4); atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; ngx_mp4_set_32value(atom_header, atom_size); ngx_mp4_set_atom_name(atom_header, 'v', 'm', 'h', 'd'); trak = ngx_mp4_last_trak(mp4); atom = &trak->vmhd_atom_buf; atom->temporary = 1; atom->pos = atom_header; atom->last = atom_header + atom_size; trak->vmhd_size += atom_size; trak->out[NGX_HTTP_MP4_VMHD_ATOM].buf = atom; ngx_mp4_atom_next(mp4, atom_data_size); return NGX_OK; } static ngx_int_t ngx_http_mp4_read_smhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { u_char *atom_header; size_t atom_size; ngx_buf_t *atom; ngx_http_mp4_trak_t *trak; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 smhd atom"); atom_header = ngx_mp4_atom_header(mp4); atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; ngx_mp4_set_32value(atom_header, atom_size); ngx_mp4_set_atom_name(atom_header, 's', 'm', 'h', 'd'); trak = ngx_mp4_last_trak(mp4); atom = &trak->smhd_atom_buf; atom->temporary = 1; atom->pos = atom_header; atom->last = atom_header + atom_size; trak->vmhd_size += atom_size; trak->out[NGX_HTTP_MP4_SMHD_ATOM].buf = atom; ngx_mp4_atom_next(mp4, atom_data_size); return NGX_OK; } static ngx_int_t ngx_http_mp4_read_dinf_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { u_char *atom_header; size_t atom_size; ngx_buf_t *atom; ngx_http_mp4_trak_t *trak; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 dinf atom"); atom_header = ngx_mp4_atom_header(mp4); atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; ngx_mp4_set_32value(atom_header, atom_size); ngx_mp4_set_atom_name(atom_header, 'd', 'i', 'n', 'f'); trak = ngx_mp4_last_trak(mp4); atom = &trak->dinf_atom_buf; atom->temporary = 1; atom->pos = atom_header; atom->last = atom_header + atom_size; trak->dinf_size += atom_size; trak->out[NGX_HTTP_MP4_DINF_ATOM].buf = atom; ngx_mp4_atom_next(mp4, atom_data_size); return NGX_OK; } static ngx_int_t ngx_http_mp4_read_stbl_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { u_char *atom_header; ngx_buf_t *atom; ngx_http_mp4_trak_t *trak; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "process stbl atom"); atom_header = ngx_mp4_atom_header(mp4); ngx_mp4_set_atom_name(atom_header, 's', 't', 'b', 'l'); trak = ngx_mp4_last_trak(mp4); atom = &trak->stbl_atom_buf; atom->temporary = 1; atom->pos = atom_header; atom->last = atom_header + sizeof(ngx_mp4_atom_header_t); trak->out[NGX_HTTP_MP4_STBL_ATOM].buf = atom; return ngx_http_mp4_read_atom(mp4, ngx_http_mp4_stbl_atoms, atom_data_size); } static void ngx_http_mp4_update_stbl_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak) { ngx_buf_t *atom; trak->size += sizeof(ngx_mp4_atom_header_t); atom = &trak->stbl_atom_buf; ngx_mp4_set_32value(atom->pos, trak->size); } typedef struct { u_char size[4]; u_char name[4]; u_char version[1]; u_char flags[3]; u_char entries[4]; u_char media_size[4]; u_char media_name[4]; } ngx_mp4_stsd_atom_t; static ngx_int_t ngx_http_mp4_read_stsd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { u_char *atom_header, *atom_table; size_t atom_size; ngx_buf_t *atom; ngx_mp4_stsd_atom_t *stsd_atom; ngx_http_mp4_trak_t *trak; /* sample description atom */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stsd atom"); atom_header = ngx_mp4_atom_header(mp4); stsd_atom = (ngx_mp4_stsd_atom_t *) atom_header; atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; atom_table = atom_header + atom_size; ngx_mp4_set_32value(stsd_atom->size, atom_size); ngx_mp4_set_atom_name(stsd_atom, 's', 't', 's', 'd'); if (ngx_mp4_atom_data_size(ngx_mp4_stsd_atom_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 stsd atom too small", mp4->file.name.data); return NGX_ERROR; } ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "stsd entries:%uD, media:%*s", ngx_mp4_get_32value(stsd_atom->entries), 4, stsd_atom->media_name); trak = ngx_mp4_last_trak(mp4); atom = &trak->stsd_atom_buf; atom->temporary = 1; atom->pos = atom_header; atom->last = atom_table; trak->out[NGX_HTTP_MP4_STSD_ATOM].buf = atom; trak->size += atom_size; ngx_mp4_atom_next(mp4, atom_data_size); return NGX_OK; } typedef struct { u_char size[4]; u_char name[4]; u_char version[1]; u_char flags[3]; u_char entries[4]; } ngx_mp4_stts_atom_t; typedef struct { u_char count[4]; u_char duration[4]; } ngx_mp4_stts_entry_t; static ngx_int_t ngx_http_mp4_read_stts_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { u_char *atom_header, *atom_table, *atom_end; uint32_t entries; ngx_buf_t *atom, *data; ngx_mp4_stts_atom_t *stts_atom; ngx_http_mp4_trak_t *trak; /* time-to-sample atom */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stts atom"); atom_header = ngx_mp4_atom_header(mp4); stts_atom = (ngx_mp4_stts_atom_t *) atom_header; ngx_mp4_set_atom_name(stts_atom, 's', 't', 't', 's'); if (ngx_mp4_atom_data_size(ngx_mp4_stts_atom_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 stts atom too small", mp4->file.name.data); return NGX_ERROR; } entries = ngx_mp4_get_32value(stts_atom->entries); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 time-to-sample entries:%uD", entries); if (ngx_mp4_atom_data_size(ngx_mp4_stts_atom_t) + entries * sizeof(ngx_mp4_stts_entry_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 stts atom too small", mp4->file.name.data); return NGX_ERROR; } atom_table = atom_header + sizeof(ngx_mp4_stts_atom_t); atom_end = atom_table + entries * sizeof(ngx_mp4_stts_entry_t); trak = ngx_mp4_last_trak(mp4); trak->time_to_sample_entries = entries; atom = &trak->stts_atom_buf; atom->temporary = 1; atom->pos = atom_header; atom->last = atom_table; data = &trak->stts_data_buf; data->temporary = 1; data->pos = atom_table; data->last = atom_end; trak->out[NGX_HTTP_MP4_STTS_ATOM].buf = atom; trak->out[NGX_HTTP_MP4_STTS_DATA].buf = data; ngx_mp4_atom_next(mp4, atom_data_size); return NGX_OK; } static ngx_int_t ngx_http_mp4_update_stts_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak) { size_t atom_size; uint32_t entries, count, duration; uint64_t start_time; ngx_buf_t *atom, *data; ngx_uint_t start_sample; ngx_mp4_stts_atom_t *stts_atom; ngx_mp4_stts_entry_t *entry, *end; /* * 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; } entries = trak->time_to_sample_entries; start_time = (uint64_t) mp4->start * trak->timescale / 1000; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "time-to-sample start_time:%uL", start_time); 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_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "count:%uD, duration:%uD", count, duration); if (start_time < (uint64_t) count * duration) { start_sample += (ngx_uint_t) (start_time / duration); count -= (uint32_t) (start_time / duration); ngx_mp4_set_32value(entry->count, count); goto found; } start_sample += count; start_time -= count * duration; entries--; entry++; } 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; found: ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "start_sample:%ui, new count:%uD", start_sample, count); trak->start_sample = start_sample; data->pos = (u_char *) entry; 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, 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_http_mp4_stss_atom_t; static ngx_int_t ngx_http_mp4_read_stss_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { u_char *atom_header, *atom_table, *atom_end; uint32_t entries; ngx_buf_t *atom, *data; ngx_http_mp4_trak_t *trak; ngx_http_mp4_stss_atom_t *stss_atom; /* sync samples atom */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stss atom"); atom_header = ngx_mp4_atom_header(mp4); stss_atom = (ngx_http_mp4_stss_atom_t *) atom_header; ngx_mp4_set_atom_name(stss_atom, 's', 't', 's', 's'); if (ngx_mp4_atom_data_size(ngx_http_mp4_stss_atom_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 stss atom too small", mp4->file.name.data); return NGX_ERROR; } entries = ngx_mp4_get_32value(stss_atom->entries); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "sync sample entries:%uD", entries); trak = ngx_mp4_last_trak(mp4); trak->sync_samples_entries = entries; atom_table = atom_header + sizeof(ngx_http_mp4_stss_atom_t); atom = &trak->stss_atom_buf; atom->temporary = 1; atom->pos = atom_header; atom->last = atom_table; if (ngx_mp4_atom_data_size(ngx_http_mp4_stss_atom_t) + entries * sizeof(uint32_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 stss atom too small", mp4->file.name.data); return NGX_ERROR; } atom_end = atom_table + entries * sizeof(uint32_t); data = &trak->stss_data_buf; data->temporary = 1; data->pos = atom_table; data->last = atom_end; trak->out[NGX_HTTP_MP4_STSS_ATOM].buf = atom; trak->out[NGX_HTTP_MP4_STSS_DATA].buf = data; ngx_mp4_atom_next(mp4, atom_data_size); return NGX_OK; } static ngx_int_t ngx_http_mp4_update_stss_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak) { size_t atom_size; uint32_t entries, 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; } /* sync samples starts from 1 */ start_sample = trak->start_sample + 1; 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_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "start:%uD, sync:%uD", start_sample, sample); if (sample >= start_sample) { goto found; } entries--; entry++; } ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "start sample is out of mp4 stss atom in \"%s\"", mp4->file.name.data); return NGX_ERROR; found: data->pos = (u_char *) entry; start_sample = trak->start_sample; while (entry < end) { sample = ngx_mp4_get_32value(entry); sample -= start_sample; ngx_mp4_set_32value(entry, sample); entry++; } 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, 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_ctts_atom_t; typedef struct { u_char count[4]; u_char offset[4]; } ngx_mp4_ctts_entry_t; static ngx_int_t ngx_http_mp4_read_ctts_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { u_char *atom_header, *atom_table, *atom_end; uint32_t entries; ngx_buf_t *atom, *data; ngx_mp4_ctts_atom_t *ctts_atom; ngx_http_mp4_trak_t *trak; /* composition offsets atom */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 ctts atom"); atom_header = ngx_mp4_atom_header(mp4); ctts_atom = (ngx_mp4_ctts_atom_t *) atom_header; ngx_mp4_set_atom_name(ctts_atom, 'c', 't', 't', 's'); if (ngx_mp4_atom_data_size(ngx_mp4_ctts_atom_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 ctts atom too small", mp4->file.name.data); return NGX_ERROR; } entries = ngx_mp4_get_32value(ctts_atom->entries); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "composition offset entries:%uD", entries); trak = ngx_mp4_last_trak(mp4); trak->composition_offset_entries = entries; atom_table = atom_header + sizeof(ngx_mp4_ctts_atom_t); atom = &trak->ctts_atom_buf; atom->temporary = 1; atom->pos = atom_header; atom->last = atom_table; if (ngx_mp4_atom_data_size(ngx_mp4_ctts_atom_t) + entries * sizeof(ngx_mp4_ctts_entry_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 ctts atom too small", mp4->file.name.data); return NGX_ERROR; } atom_end = atom_table + entries * sizeof(ngx_mp4_ctts_entry_t); data = &trak->ctts_data_buf; data->temporary = 1; data->pos = atom_table; data->last = atom_end; trak->out[NGX_HTTP_MP4_CTTS_ATOM].buf = atom; trak->out[NGX_HTTP_MP4_CTTS_DATA].buf = data; ngx_mp4_atom_next(mp4, atom_data_size); return NGX_OK; } static void ngx_http_mp4_update_ctts_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak) { size_t atom_size; uint32_t entries, count, start_sample; ngx_buf_t *atom, *data; ngx_mp4_ctts_atom_t *ctts_atom; ngx_mp4_ctts_entry_t *entry, *end; /* * 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; } /* sync samples starts from 1 */ start_sample = trak->start_sample + 1; 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, "start:%uD, count:%uD, offset:%uD", start_sample, count, ngx_mp4_get_32value(entry->offset)); if (start_sample <= count) { count -= (start_sample - 1); ngx_mp4_set_32value(entry->count, count); goto found; } start_sample -= count; entries--; entry++; } trak->out[NGX_HTTP_MP4_CTTS_ATOM].buf = NULL; trak->out[NGX_HTTP_MP4_CTTS_DATA].buf = NULL; return; found: data->pos = (u_char *) entry; 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, entries); return; } typedef struct { u_char size[4]; u_char name[4]; u_char version[1]; u_char flags[3]; u_char entries[4]; } ngx_mp4_stsc_atom_t; static ngx_int_t ngx_http_mp4_read_stsc_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { u_char *atom_header, *atom_table, *atom_end; uint32_t entries; ngx_buf_t *atom, *data; ngx_mp4_stsc_atom_t *stsc_atom; ngx_http_mp4_trak_t *trak; /* sample-to-chunk atom */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stsc atom"); atom_header = ngx_mp4_atom_header(mp4); stsc_atom = (ngx_mp4_stsc_atom_t *) atom_header; ngx_mp4_set_atom_name(stsc_atom, 's', 't', 's', 'c'); if (ngx_mp4_atom_data_size(ngx_mp4_stsc_atom_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 stsc atom too small", mp4->file.name.data); return NGX_ERROR; } entries = ngx_mp4_get_32value(stsc_atom->entries); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "sample-to-chunk entries:%uD", entries); if (ngx_mp4_atom_data_size(ngx_mp4_stsc_atom_t) + entries * sizeof(ngx_mp4_stsc_entry_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 stsc atom too small", mp4->file.name.data); return NGX_ERROR; } atom_table = atom_header + sizeof(ngx_mp4_stsc_atom_t); atom_end = atom_table + entries * sizeof(ngx_mp4_stsc_entry_t); trak = ngx_mp4_last_trak(mp4); trak->sample_to_chunk_entries = entries; atom = &trak->stsc_atom_buf; atom->temporary = 1; atom->pos = atom_header; atom->last = atom_table; data = &trak->stsc_data_buf; data->temporary = 1; data->pos = atom_table; data->last = atom_end; trak->out[NGX_HTTP_MP4_STSC_ATOM].buf = atom; trak->out[NGX_HTTP_MP4_STSC_DATA].buf = data; ngx_mp4_atom_next(mp4, atom_data_size); return NGX_OK; } static ngx_int_t ngx_http_mp4_update_stsc_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak) { size_t atom_size; uint32_t start_sample, entries, chunk, samples, id, next_chunk, n; ngx_buf_t *atom, *data, *buf; ngx_mp4_stsc_atom_t *stsc_atom; ngx_mp4_stsc_entry_t *entry, *first, *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; } start_sample = (uint32_t) trak->start_sample; entries = trak->sample_to_chunk_entries - 1; 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); entry++; while (entry < end) { next_chunk = ngx_mp4_get_32value(entry->chunk); ngx_log_debug5(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "start_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; chunk = next_chunk; samples = ngx_mp4_get_32value(entry->samples); id = ngx_mp4_get_32value(entry->id); entries--; entry++; } next_chunk = trak->chunks; ngx_log_debug4(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "start_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, "start time is out mp4 stsc chunks in \"%s\"", 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; } trak->start_chunk = chunk - 1; trak->start_chunk += start_sample / samples; trak->chunk_samples = start_sample % samples; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "start chunk:%ui, samples:%uD", trak->start_chunk, trak->chunk_samples); data->pos = (u_char *) entry; atom_size = sizeof(ngx_mp4_stsc_atom_t) + (data->last - data->pos); ngx_mp4_set_32value(entry->chunk, 1); if (trak->chunk_samples && next_chunk - trak->start_chunk == 2) { /* last chunk in the entry */ ngx_mp4_set_32value(entry->samples, samples - trak->chunk_samples); } else if (trak->chunk_samples) { first = &trak->stsc_chunk_entry; ngx_mp4_set_32value(first->chunk, 1); ngx_mp4_set_32value(first->samples, samples - trak->chunk_samples); ngx_mp4_set_32value(first->id, id); buf = &trak->stsc_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_CHUNK].buf = buf; ngx_mp4_set_32value(entry->chunk, 2); entries++; atom_size += sizeof(ngx_mp4_stsc_entry_t); } while (++entry < end) { chunk = ngx_mp4_get_32value(entry->chunk); chunk -= trak->start_chunk; ngx_mp4_set_32value(entry->chunk, chunk); } 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, entries); return NGX_OK; } typedef struct { u_char size[4]; u_char name[4]; u_char version[1]; u_char flags[3]; u_char uniform_size[4]; u_char entries[4]; } ngx_mp4_stsz_atom_t; static ngx_int_t ngx_http_mp4_read_stsz_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { u_char *atom_header, *atom_table, *atom_end; size_t atom_size; uint32_t entries, size; ngx_buf_t *atom, *data; ngx_mp4_stsz_atom_t *stsz_atom; ngx_http_mp4_trak_t *trak; /* sample sizes atom */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stsz atom"); atom_header = ngx_mp4_atom_header(mp4); stsz_atom = (ngx_mp4_stsz_atom_t *) atom_header; ngx_mp4_set_atom_name(stsz_atom, 's', 't', 's', 'z'); if (ngx_mp4_atom_data_size(ngx_mp4_stsz_atom_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 stsz atom too small", mp4->file.name.data); return NGX_ERROR; } size = ngx_mp4_get_32value(stsz_atom->uniform_size); entries = ngx_mp4_get_32value(stsz_atom->entries); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "sample uniform size:%uD, entries:%uD", size, entries); trak = ngx_mp4_last_trak(mp4); trak->sample_sizes_entries = entries; atom_table = atom_header + sizeof(ngx_mp4_stsz_atom_t); atom = &trak->stsz_atom_buf; atom->temporary = 1; atom->pos = atom_header; atom->last = atom_table; trak->out[NGX_HTTP_MP4_STSZ_ATOM].buf = atom; if (size == 0) { if (ngx_mp4_atom_data_size(ngx_mp4_stsz_atom_t) + entries * sizeof(uint32_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 stsz atom too small", mp4->file.name.data); return NGX_ERROR; } atom_end = atom_table + entries * sizeof(uint32_t); data = &trak->stsz_data_buf; data->temporary = 1; data->pos = atom_table; data->last = atom_end; trak->out[NGX_HTTP_MP4_STSZ_DATA].buf = data; } else { /* if size != 0 then all samples are the same size */ /* TODO : chunk samples */ atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; ngx_mp4_set_32value(atom_header, atom_size); trak->size += atom_size; } ngx_mp4_atom_next(mp4, atom_data_size); return NGX_OK; } static ngx_int_t ngx_http_mp4_update_stsz_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak) { size_t atom_size; uint32_t *pos, *end; 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) { if (trak->start_sample > trak->sample_sizes_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; } data->pos += trak->start_sample * sizeof(uint32_t); end = (uint32_t *) data->pos; for (pos = end - trak->chunk_samples; pos < end; pos++) { trak->chunk_samples_size += ngx_mp4_get_32value(pos); } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "chunk samples sizes:%uL", trak->chunk_samples_size); atom_size = sizeof(ngx_mp4_stsz_atom_t) + (data->last - data->pos); trak->size += atom_size; atom = trak->out[NGX_HTTP_MP4_STSZ_ATOM].buf; stsz_atom = (ngx_mp4_stsz_atom_t *) atom->pos; ngx_mp4_set_32value(stsz_atom->size, atom_size); ngx_mp4_set_32value(stsz_atom->entries, trak->sample_sizes_entries - trak->start_sample); } return NGX_OK; } typedef struct { u_char size[4]; u_char name[4]; u_char version[1]; u_char flags[3]; u_char entries[4]; } ngx_mp4_stco_atom_t; static ngx_int_t ngx_http_mp4_read_stco_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { u_char *atom_header, *atom_table, *atom_end; uint32_t entries; ngx_buf_t *atom, *data; ngx_mp4_stco_atom_t *stco_atom; ngx_http_mp4_trak_t *trak; /* chunk offsets atom */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stco atom"); atom_header = ngx_mp4_atom_header(mp4); stco_atom = (ngx_mp4_stco_atom_t *) atom_header; ngx_mp4_set_atom_name(stco_atom, 's', 't', 'c', 'o'); if (ngx_mp4_atom_data_size(ngx_mp4_stco_atom_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 stco atom too small", mp4->file.name.data); return NGX_ERROR; } entries = ngx_mp4_get_32value(stco_atom->entries); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "chunks:%uD", entries); if (ngx_mp4_atom_data_size(ngx_mp4_stco_atom_t) + entries * sizeof(uint32_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 stco atom too small", mp4->file.name.data); return NGX_ERROR; } atom_table = atom_header + sizeof(ngx_mp4_stco_atom_t); atom_end = atom_table + entries * sizeof(uint32_t); trak = ngx_mp4_last_trak(mp4); trak->chunks = entries; atom = &trak->stco_atom_buf; atom->temporary = 1; atom->pos = atom_header; atom->last = atom_table; data = &trak->stco_data_buf; data->temporary = 1; data->pos = atom_table; data->last = atom_end; trak->out[NGX_HTTP_MP4_STCO_ATOM].buf = atom; trak->out[NGX_HTTP_MP4_STCO_DATA].buf = data; ngx_mp4_atom_next(mp4, atom_data_size); return NGX_OK; } static ngx_int_t ngx_http_mp4_update_stco_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak) { size_t atom_size; 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); atom_size = sizeof(ngx_mp4_stco_atom_t) + (data->last - data->pos); trak->size += atom_size; trak->start_offset = ngx_mp4_get_32value(data->pos); trak->start_offset += trak->chunk_samples_size; ngx_mp4_set_32value(data->pos, trak->start_offset); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "start chunk offset:%uD", trak->start_offset); 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, trak->chunks - trak->start_chunk); return NGX_OK; } static void ngx_http_mp4_adjust_stco_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak, int32_t adjustment) { uint32_t offset, *entry, *end; ngx_buf_t *data; /* * moov.trak.mdia.minf.stbl.stco adjustment requires * minimal start offset of all traks and new moov atom size */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stco atom adjustment"); data = trak->out[NGX_HTTP_MP4_STCO_DATA].buf; entry = (uint32_t *) data->pos; end = (uint32_t *) data->last; while (entry < end) { offset = ngx_mp4_get_32value(entry); offset += adjustment; ngx_mp4_set_32value(entry, offset); entry++; } } typedef struct { u_char size[4]; u_char name[4]; u_char version[1]; u_char flags[3]; u_char entries[4]; } ngx_mp4_co64_atom_t; static ngx_int_t ngx_http_mp4_read_co64_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { u_char *atom_header, *atom_table, *atom_end; uint32_t entries; ngx_buf_t *atom, *data; ngx_mp4_co64_atom_t *co64_atom; ngx_http_mp4_trak_t *trak; /* chunk offsets atom */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 co64 atom"); atom_header = ngx_mp4_atom_header(mp4); co64_atom = (ngx_mp4_co64_atom_t *) atom_header; ngx_mp4_set_atom_name(co64_atom, 'c', 'o', '6', '4'); if (ngx_mp4_atom_data_size(ngx_mp4_co64_atom_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 co64 atom too small", mp4->file.name.data); return NGX_ERROR; } entries = ngx_mp4_get_32value(co64_atom->entries); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "chunks:%uD", entries); if (ngx_mp4_atom_data_size(ngx_mp4_co64_atom_t) + entries * sizeof(uint64_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 co64 atom too small", mp4->file.name.data); return NGX_ERROR; } atom_table = atom_header + sizeof(ngx_mp4_co64_atom_t); atom_end = atom_table + entries * sizeof(uint64_t); trak = ngx_mp4_last_trak(mp4); trak->chunks = entries; atom = &trak->co64_atom_buf; atom->temporary = 1; atom->pos = atom_header; atom->last = atom_table; data = &trak->co64_data_buf; data->temporary = 1; data->pos = atom_table; data->last = atom_end; trak->out[NGX_HTTP_MP4_CO64_ATOM].buf = atom; trak->out[NGX_HTTP_MP4_CO64_DATA].buf = data; ngx_mp4_atom_next(mp4, atom_data_size); return NGX_OK; } static ngx_int_t ngx_http_mp4_update_co64_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak) { size_t atom_size; 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); atom_size = sizeof(ngx_mp4_co64_atom_t) + (data->last - data->pos); trak->size += atom_size; trak->start_offset = ngx_mp4_get_64value(data->pos); trak->start_offset += trak->chunk_samples_size; ngx_mp4_set_64value(data->pos, trak->start_offset); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "start chunk offset:%uL", trak->start_offset); 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, trak->chunks - trak->start_chunk); return NGX_OK; } static void ngx_http_mp4_adjust_co64_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak, off_t adjustment) { uint64_t offset, *entry, *end; ngx_buf_t *data; /* * moov.trak.mdia.minf.stbl.co64 adjustment requires * minimal start offset of all traks and new moov atom size */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 co64 atom adjustment"); data = trak->out[NGX_HTTP_MP4_CO64_DATA].buf; entry = (uint64_t *) data->pos; end = (uint64_t *) data->last; while (entry < end) { offset = ngx_mp4_get_64value(entry); offset += adjustment; ngx_mp4_set_64value(entry, offset); entry++; } } static char * ngx_http_mp4(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_core_loc_conf_t *clcf; clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); clcf->handler = ngx_http_mp4_handler; return NGX_CONF_OK; } static void * ngx_http_mp4_create_conf(ngx_conf_t *cf) { ngx_http_mp4_conf_t *conf; conf = ngx_palloc(cf->pool, sizeof(ngx_http_mp4_conf_t)); if (conf == NULL) { return NULL; } conf->buffer_size = NGX_CONF_UNSET_SIZE; conf->max_buffer_size = NGX_CONF_UNSET_SIZE; return conf; } static char * ngx_http_mp4_merge_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_mp4_conf_t *prev = parent; ngx_http_mp4_conf_t *conf = child; ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, 512 * 1024); ngx_conf_merge_size_value(conf->max_buffer_size, prev->max_buffer_size, 10 * 1024 * 1024); return NGX_CONF_OK; } nginx-1.4.6/src/http/modules/ngx_http_random_index_module.c000644 001751 001751 00000021434 12305336445 025355 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 = NGX_DIR_MASK_LEN; #else len = NGX_HTTP_RANDOM_INDEX_PREALLOCATE; #endif last = ngx_http_map_uri_to_path(r, &path, &root, len); if (last == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } allocated = path.len; path.len = last - path.data - 1; path.data[path.len] = '\0'; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http random index: \"%s\"", path.data); if (ngx_open_dir(&path, &dir) == NGX_ERROR) { err = ngx_errno; if (err == NGX_ENOENT || err == NGX_ENOTDIR || err == NGX_ENAMETOOLONG) { level = NGX_LOG_ERR; rc = NGX_HTTP_NOT_FOUND; } else if (err == NGX_EACCES) { level = NGX_LOG_ERR; rc = NGX_HTTP_FORBIDDEN; } else { level = NGX_LOG_CRIT; rc = NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_log_error(level, r->connection->log, err, ngx_open_dir_n " \"%s\" failed", path.data); return rc; } if (ngx_array_init(&names, r->pool, 32, sizeof(ngx_str_t)) != NGX_OK) { return ngx_http_random_index_error(r, &dir, &path); } filename = path.data; filename[path.len] = '/'; for ( ;; ) { ngx_set_errno(0); if (ngx_read_dir(&dir) == NGX_ERROR) { err = ngx_errno; if (err != NGX_ENOMOREFILES) { ngx_log_error(NGX_LOG_CRIT, r->connection->log, err, ngx_read_dir_n " \"%V\" failed", &path); return ngx_http_random_index_error(r, &dir, &path); } break; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http random index file: \"%s\"", ngx_de_name(&dir)); if (ngx_de_name(&dir)[0] == '.') { continue; } len = ngx_de_namelen(&dir); if (dir.type == 0 || ngx_de_is_link(&dir)) { /* 1 byte for '/' and 1 byte for terminating '\0' */ if (path.len + 1 + len + 1 > allocated) { allocated = path.len + 1 + len + 1 + NGX_HTTP_RANDOM_INDEX_PREALLOCATE; filename = ngx_pnalloc(r->pool, allocated); if (filename == NULL) { return ngx_http_random_index_error(r, &dir, &path); } last = ngx_cpystrn(filename, path.data, path.len + 1); *last++ = '/'; } ngx_cpystrn(last, ngx_de_name(&dir), len + 1); if (ngx_de_info(filename, &dir) == NGX_FILE_ERROR) { err = ngx_errno; if (err != NGX_ENOENT) { ngx_log_error(NGX_LOG_CRIT, r->connection->log, err, ngx_de_info_n " \"%s\" failed", filename); return ngx_http_random_index_error(r, &dir, &path); } if (ngx_de_link_info(filename, &dir) == NGX_FILE_ERROR) { ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno, ngx_de_link_info_n " \"%s\" failed", filename); return ngx_http_random_index_error(r, &dir, &path); } } } if (!ngx_de_is_file(&dir)) { continue; } name = ngx_array_push(&names); if (name == NULL) { return ngx_http_random_index_error(r, &dir, &path); } name->len = len; name->data = ngx_pnalloc(r->pool, len); if (name->data == NULL) { return ngx_http_random_index_error(r, &dir, &path); } ngx_memcpy(name->data, ngx_de_name(&dir), len); } if (ngx_close_dir(&dir) == NGX_ERROR) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno, ngx_close_dir_n " \"%s\" 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.4.6/src/http/modules/ngx_http_proxy_module.c000644 001751 001751 00000320076 12305336445 024073 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ #include #include #include typedef struct ngx_http_proxy_rewrite_s ngx_http_proxy_rewrite_t; typedef ngx_int_t (*ngx_http_proxy_rewrite_pt)(ngx_http_request_t *r, ngx_table_elt_t *h, size_t prefix, size_t len, ngx_http_proxy_rewrite_t *pr); struct ngx_http_proxy_rewrite_s { ngx_http_proxy_rewrite_pt handler; union { ngx_http_complex_value_t complex; #if (NGX_PCRE) ngx_http_regex_t *regex; #endif } pattern; ngx_http_complex_value_t replacement; }; typedef struct { ngx_str_t key_start; ngx_str_t schema; ngx_str_t host_header; ngx_str_t port; ngx_str_t uri; } ngx_http_proxy_vars_t; typedef struct { ngx_http_upstream_conf_t upstream; ngx_array_t *flushes; ngx_array_t *body_set_len; ngx_array_t *body_set; ngx_array_t *headers_set_len; ngx_array_t *headers_set; ngx_hash_t headers_set_hash; ngx_array_t *headers_source; ngx_array_t *proxy_lengths; ngx_array_t *proxy_values; ngx_array_t *redirects; ngx_array_t *cookie_domains; ngx_array_t *cookie_paths; ngx_str_t body_source; ngx_str_t method; ngx_str_t location; ngx_str_t url; #if (NGX_HTTP_CACHE) ngx_http_complex_value_t cache_key; #endif ngx_http_proxy_vars_t vars; ngx_flag_t redirect; ngx_uint_t http_version; ngx_uint_t headers_hash_max_size; ngx_uint_t headers_hash_bucket_size; } 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_uint_t head; /* unsigned head: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_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_rewrite_redirect(ngx_http_request_t *r, ngx_table_elt_t *h, size_t prefix); static ngx_int_t ngx_http_proxy_rewrite_cookie(ngx_http_request_t *r, ngx_table_elt_t *h); static ngx_int_t ngx_http_proxy_rewrite_cookie_value(ngx_http_request_t *r, ngx_table_elt_t *h, u_char *value, ngx_array_t *rewrites); static ngx_int_t ngx_http_proxy_rewrite(ngx_http_request_t *r, ngx_table_elt_t *h, size_t prefix, size_t len, ngx_str_t *replacement); static ngx_int_t ngx_http_proxy_add_variables(ngx_conf_t *cf); static void *ngx_http_proxy_create_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_merge_headers(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *conf, ngx_http_proxy_loc_conf_t *prev); static char *ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_proxy_redirect(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_proxy_cookie_domain(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_proxy_cookie_path(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_proxy_store(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); #if (NGX_HTTP_CACHE) static char *ngx_http_proxy_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_proxy_cache_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); #endif static char *ngx_http_proxy_lowat_check(ngx_conf_t *cf, void *post, void *data); static ngx_int_t ngx_http_proxy_rewrite_regex(ngx_conf_t *cf, ngx_http_proxy_rewrite_t *pr, ngx_str_t *regex, ngx_uint_t caseless); #if (NGX_HTTP_SSL) static ngx_int_t ngx_http_proxy_set_ssl(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *plcf); #endif static void ngx_http_proxy_set_vars(ngx_url_t *u, ngx_http_proxy_vars_t *v); static ngx_conf_post_t ngx_http_proxy_lowat_post = { ngx_http_proxy_lowat_check }; static ngx_conf_bitmask_t ngx_http_proxy_next_upstream_masks[] = { { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR }, { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT }, { ngx_string("invalid_header"), NGX_HTTP_UPSTREAM_FT_INVALID_HEADER }, { ngx_string("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_404"), NGX_HTTP_UPSTREAM_FT_HTTP_404 }, { ngx_string("updating"), NGX_HTTP_UPSTREAM_FT_UPDATING }, { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF }, { ngx_null_string, 0 } }; static ngx_conf_enum_t ngx_http_proxy_http_version[] = { { ngx_string("1.0"), NGX_HTTP_VERSION_10 }, { ngx_string("1.1"), NGX_HTTP_VERSION_11 }, { ngx_null_string, 0 } }; ngx_module_t ngx_http_proxy_module; static ngx_command_t ngx_http_proxy_commands[] = { { ngx_string("proxy_pass"), NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1, ngx_http_proxy_pass, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("proxy_redirect"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12, ngx_http_proxy_redirect, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("proxy_cookie_domain"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12, ngx_http_proxy_cookie_domain, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("proxy_cookie_path"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12, ngx_http_proxy_cookie_path, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("proxy_store"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_proxy_store, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("proxy_store_access"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123, ngx_conf_set_access_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.store_access), NULL }, { ngx_string("proxy_buffering"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.buffering), NULL }, { ngx_string("proxy_ignore_client_abort"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.ignore_client_abort), NULL }, { ngx_string("proxy_bind"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_upstream_bind_set_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.local), NULL }, { ngx_string("proxy_connect_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.connect_timeout), NULL }, { ngx_string("proxy_send_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.send_timeout), NULL }, { ngx_string("proxy_send_lowat"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.send_lowat), &ngx_http_proxy_lowat_post }, { ngx_string("proxy_intercept_errors"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.intercept_errors), NULL }, { ngx_string("proxy_set_header"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, ngx_conf_set_keyval_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, headers_source), NULL }, { ngx_string("proxy_headers_hash_max_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, headers_hash_max_size), NULL }, { ngx_string("proxy_headers_hash_bucket_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, headers_hash_bucket_size), NULL }, { ngx_string("proxy_set_body"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, body_source), NULL }, { ngx_string("proxy_method"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, method), NULL }, { ngx_string("proxy_pass_request_headers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.pass_request_headers), NULL }, { ngx_string("proxy_pass_request_body"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.pass_request_body), NULL }, { ngx_string("proxy_buffer_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.buffer_size), NULL }, { ngx_string("proxy_read_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.read_timeout), NULL }, { ngx_string("proxy_buffers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, ngx_conf_set_bufs_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.bufs), NULL }, { ngx_string("proxy_busy_buffers_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.busy_buffers_size_conf), NULL }, #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, 0, 0, &ngx_http_proxy_module }, { ngx_string("proxy_cache_bypass"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_set_predicate_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_bypass), NULL }, { ngx_string("proxy_no_cache"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_set_predicate_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.no_cache), NULL }, { ngx_string("proxy_cache_valid"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_file_cache_valid_set_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_valid), NULL }, { ngx_string("proxy_cache_min_uses"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_min_uses), NULL }, { ngx_string("proxy_cache_use_stale"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_use_stale), &ngx_http_proxy_next_upstream_masks }, { ngx_string("proxy_cache_methods"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_methods), &ngx_http_upstream_cache_method_mask }, { ngx_string("proxy_cache_lock"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_lock), NULL }, { ngx_string("proxy_cache_lock_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_lock_timeout), NULL }, #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_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 }, #endif ngx_null_command }; static ngx_http_module_t ngx_http_proxy_module_ctx = { ngx_http_proxy_add_variables, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* 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("") }, { 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("") }, { ngx_string("Keep-Alive"), ngx_string("") }, { ngx_string("Expect"), ngx_string("") }, { ngx_string("Upgrade"), ngx_string("") }, { ngx_string("If-Modified-Since"), ngx_string("") }, { ngx_string("If-Unmodified-Since"), ngx_string("") }, { ngx_string("If-None-Match"), ngx_string("") }, { 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_null_string, NULL, NULL, 0, 0, 0 } }; static ngx_path_init_t ngx_http_proxy_temp_path = { ngx_string(NGX_HTTP_PROXY_TEMP_PATH), { 1, 2, 0 } }; static ngx_int_t ngx_http_proxy_handler(ngx_http_request_t *r) { ngx_int_t rc; ngx_http_upstream_t *u; ngx_http_proxy_ctx_t *ctx; ngx_http_proxy_loc_conf_t *plcf; if (ngx_http_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_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_proxy_module); plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); u = r->upstream; if (plcf->proxy_lengths == NULL) { ctx->vars = plcf->vars; u->schema = plcf->vars.schema; #if (NGX_HTTP_SSL) u->ssl = (plcf->upstream.ssl != NULL); #endif } else { if (ngx_http_proxy_eval(r, ctx, plcf) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } } u->output.tag = (ngx_buf_tag_t) &ngx_http_proxy_module; u->conf = &plcf->upstream; #if (NGX_HTTP_CACHE) u->create_key = ngx_http_proxy_create_key; #endif u->create_request = ngx_http_proxy_create_request; u->reinit_request = ngx_http_proxy_reinit_request; u->process_header = ngx_http_proxy_process_status_line; u->abort_request = ngx_http_proxy_abort_request; u->finalize_request = ngx_http_proxy_finalize_request; r->state = 0; if (plcf->redirects) { u->rewrite_redirect = ngx_http_proxy_rewrite_redirect; } if (plcf->cookie_domains || plcf->cookie_paths) { u->rewrite_cookie = ngx_http_proxy_rewrite_cookie; } u->buffering = plcf->upstream.buffering; u->pipe = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t)); if (u->pipe == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } u->pipe->input_filter = ngx_http_proxy_copy_filter; u->pipe->input_ctx = r; u->input_filter_init = ngx_http_proxy_input_filter_init; u->input_filter = ngx_http_proxy_non_buffered_copy_filter; u->input_filter_ctx = r; u->accel = 1; rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init); if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { return rc; } return NGX_DONE; } static ngx_int_t ngx_http_proxy_eval(ngx_http_request_t *r, ngx_http_proxy_ctx_t *ctx, ngx_http_proxy_loc_conf_t *plcf) { u_char *p; size_t add; u_short port; ngx_str_t proxy; ngx_url_t url; ngx_http_upstream_t *u; if (ngx_http_script_run(r, &proxy, plcf->proxy_lengths->elts, 0, plcf->proxy_values->elts) == NULL) { return NGX_ERROR; } if (proxy.len > 7 && ngx_strncasecmp(proxy.data, (u_char *) "http://", 7) == 0) { add = 7; port = 80; #if (NGX_HTTP_SSL) } else if (proxy.len > 8 && ngx_strncasecmp(proxy.data, (u_char *) "https://", 8) == 0) { add = 8; port = 443; r->upstream->ssl = 1; #endif } else { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid URL prefix in \"%V\"", &proxy); return NGX_ERROR; } u = r->upstream; u->schema.len = add; u->schema.data = proxy.data; ngx_memzero(&url, sizeof(ngx_url_t)); url.url.len = proxy.len - add; url.url.data = proxy.data + add; url.default_port = port; url.uri_part = 1; url.no_resolve = 1; if (ngx_parse_url(r->pool, &url) != NGX_OK) { if (url.err) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "%s in upstream \"%V\"", url.err, &url.url); } return NGX_ERROR; } if (url.uri.len) { if (url.uri.data[0] == '?') { p = ngx_pnalloc(r->pool, url.uri.len + 1); if (p == NULL) { return NGX_ERROR; } *p++ = '/'; ngx_memcpy(p, url.uri.data, url.uri.len); url.uri.len++; url.uri.data = p - 1; } } ctx->vars.key_start = u->schema; ngx_http_proxy_set_vars(&url, &ctx->vars); u->resolved = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t)); if (u->resolved == NULL) { return NGX_ERROR; } if (url.addrs && url.addrs[0].sockaddr) { u->resolved->sockaddr = url.addrs[0].sockaddr; u->resolved->socklen = url.addrs[0].socklen; u->resolved->naddrs = 1; u->resolved->host = url.addrs[0].name; } else { u->resolved->host = url.host; u->resolved->port = (in_port_t) (url.no_port ? port : url.port); u->resolved->no_port = url.no_port; } return NGX_OK; } #if (NGX_HTTP_CACHE) static ngx_int_t ngx_http_proxy_create_key(ngx_http_request_t *r) { size_t len, loc_len; u_char *p; uintptr_t escape; ngx_str_t *key; ngx_http_upstream_t *u; ngx_http_proxy_ctx_t *ctx; ngx_http_proxy_loc_conf_t *plcf; u = r->upstream; plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); key = ngx_array_push(&r->cache->keys); if (key == NULL) { return NGX_ERROR; } if (plcf->cache_key.value.data) { if (ngx_http_complex_value(r, &plcf->cache_key, key) != NGX_OK) { return NGX_ERROR; } return NGX_OK; } *key = ctx->vars.key_start; key = ngx_array_push(&r->cache->keys); if (key == NULL) { return NGX_ERROR; } if (plcf->proxy_lengths && ctx->vars.uri.len) { *key = ctx->vars.uri; u->uri = ctx->vars.uri; return NGX_OK; } else if (ctx->vars.uri.len == 0 && r->valid_unparsed_uri && r == r->main) { *key = r->unparsed_uri; u->uri = r->unparsed_uri; return NGX_OK; } loc_len = (r->valid_location && ctx->vars.uri.len) ? plcf->location.len : 0; if (r->quoted_uri || r->internal) { escape = 2 * ngx_escape_uri(NULL, r->uri.data + loc_len, r->uri.len - loc_len, NGX_ESCAPE_URI); } else { escape = 0; } len = ctx->vars.uri.len + r->uri.len - loc_len + escape + sizeof("?") - 1 + r->args.len; p = ngx_pnalloc(r->pool, len); if (p == NULL) { return NGX_ERROR; } key->data = p; if (r->valid_location) { p = ngx_copy(p, ctx->vars.uri.data, ctx->vars.uri.len); } if (escape) { ngx_escape_uri(p, r->uri.data + loc_len, r->uri.len - loc_len, NGX_ESCAPE_URI); p += r->uri.len - loc_len + escape; } else { p = ngx_copy(p, r->uri.data + loc_len, r->uri.len - loc_len); } if (r->args.len > 0) { *p++ = '?'; p = ngx_copy(p, r->args.data, r->args.len); } key->len = p - key->data; u->uri = *key; return NGX_OK; } #endif static ngx_int_t ngx_http_proxy_create_request(ngx_http_request_t *r) { size_t len, uri_len, loc_len, body_len; uintptr_t escape; ngx_buf_t *b; ngx_str_t method; ngx_uint_t i, unparsed_uri; ngx_chain_t *cl, *body; ngx_list_part_t *part; ngx_table_elt_t *header; ngx_http_upstream_t *u; ngx_http_proxy_ctx_t *ctx; ngx_http_script_code_pt code; ngx_http_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 (u->method.len) { /* HEAD was changed to GET to cache response */ method = u->method; method.len++; } else if (plcf->method.len) { method = plcf->method; } else { method = r->method_name; method.len++; } ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); if (method.len == 5 && ngx_strncasecmp(method.data, (u_char *) "HEAD ", 5) == 0) { ctx->head = 1; } len = method.len + sizeof(ngx_http_proxy_version) - 1 + sizeof(CRLF) - 1; escape = 0; loc_len = 0; unparsed_uri = 0; if (plcf->proxy_lengths && ctx->vars.uri.len) { uri_len = ctx->vars.uri.len; } else if (ctx->vars.uri.len == 0 && r->valid_unparsed_uri && r == r->main) { unparsed_uri = 1; uri_len = r->unparsed_uri.len; } else { loc_len = (r->valid_location && ctx->vars.uri.len) ? plcf->location.len : 0; if (r->quoted_uri || r->space_in_uri || r->internal) { escape = 2 * ngx_escape_uri(NULL, r->uri.data + loc_len, r->uri.len - loc_len, NGX_ESCAPE_URI); } uri_len = ctx->vars.uri.len + r->uri.len - loc_len + escape + sizeof("?") - 1 + r->args.len; } if (uri_len == 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "zero length URI to proxy"); return NGX_ERROR; } len += uri_len; ngx_http_script_flush_no_cacheable_variables(r, plcf->flushes); if (plcf->body_set_len) { le.ip = plcf->body_set_len->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 { ctx->internal_body_length = r->headers_in.content_length_n; } le.ip = plcf->headers_set_len->elts; le.request = r; le.flushed = 1; while (*(uintptr_t *) le.ip) { while (*(uintptr_t *) le.ip) { lcode = *(ngx_http_script_len_code_pt *) le.ip; len += lcode(&le); } le.ip += sizeof(uintptr_t); } if (plcf->upstream.pass_request_headers) { part = &r->headers_in.headers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (ngx_hash_find(&plcf->headers_set_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); 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 = plcf->headers_set->elts; e.pos = b->last; e.request = r; e.flushed = 1; le.ip = plcf->headers_set_len->elts; while (*(uintptr_t *) le.ip) { lcode = *(ngx_http_script_len_code_pt *) le.ip; /* skip the header line name length */ (void) lcode(&le); if (*(ngx_http_script_len_code_pt *) le.ip) { for (len = 0; *(uintptr_t *) le.ip; len += lcode(&le)) { lcode = *(ngx_http_script_len_code_pt *) le.ip; } e.skip = (len == sizeof(CRLF) - 1) ? 1 : 0; } else { e.skip = 0; } le.ip += sizeof(uintptr_t); while (*(uintptr_t *) e.ip) { code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); } e.ip += sizeof(uintptr_t); } b->last = e.pos; if (plcf->upstream.pass_request_headers) { part = &r->headers_in.headers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (ngx_hash_find(&plcf->headers_set_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_set) { e.ip = plcf->body_set->elts; e.pos = b->last; 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 (plcf->body_set == 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_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 = ctx->status.code; } u->headers_in.status_n = ctx->status.code; len = ctx->status.end - ctx->status.start; u->headers_in.status_line.len = len; u->headers_in.status_line.data = ngx_pnalloc(r->pool, len); if (u->headers_in.status_line.data == NULL) { return NGX_ERROR; } ngx_memcpy(u->headers_in.status_line.data, ctx->status.start, len); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http proxy status %ui \"%V\"", u->headers_in.status_n, &u->headers_in.status_line); if (ctx->status.http_version < NGX_HTTP_VERSION_11) { u->headers_in.connection_close = 1; } u->process_header = ngx_http_proxy_process_header; return ngx_http_proxy_process_header(r); } static ngx_int_t ngx_http_proxy_process_header(ngx_http_request_t *r) { ngx_int_t rc; ngx_table_elt_t *h; ngx_http_upstream_t *u; ngx_http_proxy_ctx_t *ctx; ngx_http_upstream_header_t *hh; ngx_http_upstream_main_conf_t *umcf; umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module); for ( ;; ) { rc = ngx_http_parse_header_line(r, &r->upstream->buffer, 1); if (rc == NGX_OK) { /* a header line has been parsed successfully */ h = ngx_list_push(&r->upstream->headers_in.headers); if (h == NULL) { return NGX_ERROR; } h->hash = r->header_hash; h->key.len = r->header_name_end - r->header_name_start; h->value.len = r->header_end - r->header_start; h->key.data = ngx_pnalloc(r->pool, h->key.len + 1 + h->value.len + 1 + h->key.len); if (h->key.data == NULL) { return NGX_ERROR; } h->value.data = h->key.data + h->key.len + 1; h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1; ngx_memcpy(h->key.data, r->header_name_start, h->key.len); h->key.data[h->key.len] = '\0'; ngx_memcpy(h->value.data, r->header_start, h->value.len); h->value.data[h->value.len] = '\0'; if (h->key.len == r->lowcase_index) { ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len); } else { ngx_strlow(h->lowcase_key, h->key.data, h->key.len); } hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len); if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { return NGX_ERROR; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http proxy header: \"%V: %V\"", &h->key, &h->value); continue; } if (rc == NGX_HTTP_PARSE_HEADER_DONE) { /* a whole header has been parsed successfully */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http proxy header done"); /* * if no "Server" and "Date" in header line, * then add the special empty headers */ if (r->upstream->headers_in.server == NULL) { h = ngx_list_push(&r->upstream->headers_in.headers); if (h == NULL) { return NGX_ERROR; } h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash( ngx_hash('s', 'e'), 'r'), 'v'), 'e'), 'r'); ngx_str_set(&h->key, "Server"); ngx_str_null(&h->value); h->lowcase_key = (u_char *) "server"; } if (r->upstream->headers_in.date == NULL) { h = ngx_list_push(&r->upstream->headers_in.headers); if (h == NULL) { return NGX_ERROR; } h->hash = ngx_hash(ngx_hash(ngx_hash('d', 'a'), 't'), 'e'); ngx_str_set(&h->key, "Date"); ngx_str_null(&h->value); h->lowcase_key = (u_char *) "date"; } /* clear content length if response is chunked */ u = r->upstream; if (u->headers_in.chunked) { u->headers_in.content_length_n = -1; } /* * set u->keepalive if response has no body; this allows to keep * connections alive in case of r->header_only or X-Accel-Redirect */ ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); if (u->headers_in.status_n == NGX_HTTP_NO_CONTENT || u->headers_in.status_n == NGX_HTTP_NOT_MODIFIED || ctx->head || (!u->headers_in.chunked && u->headers_in.content_length_n == 0)) { u->keepalive = !u->headers_in.connection_close; } if (u->headers_in.status_n == NGX_HTTP_SWITCHING_PROTOCOLS) { u->keepalive = 0; if (r->headers_in.upgrade) { u->upgrade = 1; } } return NGX_OK; } if (rc == NGX_AGAIN) { return NGX_AGAIN; } /* there was error while a header line parsing */ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid header"); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } } static ngx_int_t ngx_http_proxy_input_filter_init(void *data) { ngx_http_request_t *r = data; ngx_http_upstream_t *u; ngx_http_proxy_ctx_t *ctx; u = r->upstream; ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); if (ctx == NULL) { return NGX_ERROR; } ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http proxy filter init s:%d 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->free) { cl = p->free; b = cl->buf; p->free = cl->next; ngx_free_chain(p->pool, cl); } else { b = ngx_alloc_buf(p->pool); if (b == NULL) { return NGX_ERROR; } } 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; cl = ngx_alloc_chain_link(p->pool); if (cl == NULL) { return NGX_ERROR; } cl->buf = b; cl->next = NULL; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0, "input buf #%d", b->num); if (p->in) { *p->last_in = cl; } else { p->in = cl; } p->last_in = &cl->next; if (p->length == -1) { return NGX_OK; } p->length -= b->last - b->pos; if (p->length == 0) { r = p->input_ctx; p->upstream_done = 1; r->upstream->keepalive = !r->upstream->headers_in.connection_close; } else if (p->length < 0) { r = p->input_ctx; p->upstream_done = 1; ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "upstream sent more data than specified in " "\"Content-Length\" header"); } return NGX_OK; } static ngx_int_t ngx_http_proxy_chunked_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) { ngx_int_t rc; ngx_buf_t *b, **prev; ngx_chain_t *cl; ngx_http_request_t *r; ngx_http_proxy_ctx_t *ctx; if (buf->pos == buf->last) { return NGX_OK; } r = p->input_ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); if (ctx == NULL) { return NGX_ERROR; } b = NULL; prev = &buf->shadow; for ( ;; ) { rc = ngx_http_parse_chunked(r, buf, &ctx->chunked); if (rc == NGX_OK) { /* a chunk has been parsed successfully */ if (p->free) { cl = p->free; b = cl->buf; p->free = cl->next; ngx_free_chain(p->pool, cl); } else { b = ngx_alloc_buf(p->pool); if (b == NULL) { return NGX_ERROR; } } 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; cl = ngx_alloc_chain_link(p->pool); if (cl == NULL) { return NGX_ERROR; } cl->buf = b; cl->next = NULL; 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 += ctx->chunked.size; b->last = buf->pos; ctx->chunked.size = 0; continue; } ctx->chunked.size -= buf->last - buf->pos; buf->pos = buf->last; b->last = buf->last; continue; } if (rc == NGX_DONE) { /* a whole response has been parsed successfully */ p->upstream_done = 1; r->upstream->keepalive = !r->upstream->headers_in.connection_close; break; } if (rc == NGX_AGAIN) { /* set p->length, minimal amount of data we want to see */ p->length = ctx->chunked.length; break; } /* invalid response */ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid chunked response"); return NGX_ERROR; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http proxy chunked state %d, length %d", ctx->chunked.state, p->length); if (b) { b->shadow = buf; b->last_shadow = 1; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, p->log, 0, "input buf %p %z", b->pos, b->last - b->pos); return NGX_OK; } /* there is no data record in the buf, add it to free chain */ if (ngx_event_pipe_add_free_buf(p, buf) != NGX_OK) { return NGX_ERROR; } return NGX_OK; } static ngx_int_t ngx_http_proxy_non_buffered_copy_filter(void *data, ssize_t bytes) { ngx_http_request_t *r = data; ngx_buf_t *b; ngx_chain_t *cl, **ll; ngx_http_upstream_t *u; u = r->upstream; for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) { ll = &cl->next; } cl = ngx_chain_get_free_buf(r->pool, &u->free_bufs); if (cl == NULL) { return NGX_ERROR; } *ll = cl; cl->buf->flush = 1; cl->buf->memory = 1; b = &u->buffer; cl->buf->pos = b->last; b->last += bytes; cl->buf->last = b->last; cl->buf->tag = u->output.tag; if (u->length == -1) { return NGX_OK; } u->length -= bytes; if (u->length == 0) { u->keepalive = !u->headers_in.connection_close; } return NGX_OK; } static ngx_int_t ngx_http_proxy_non_buffered_chunked_filter(void *data, ssize_t bytes) { ngx_http_request_t *r = data; ngx_int_t rc; ngx_buf_t *b, *buf; ngx_chain_t *cl, **ll; ngx_http_upstream_t *u; ngx_http_proxy_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); if (ctx == NULL) { return NGX_ERROR; } u = r->upstream; buf = &u->buffer; buf->pos = buf->last; buf->last += bytes; for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) { ll = &cl->next; } for ( ;; ) { rc = ngx_http_parse_chunked(r, buf, &ctx->chunked); if (rc == NGX_OK) { /* a chunk has been parsed successfully */ cl = ngx_chain_get_free_buf(r->pool, &u->free_bufs); if (cl == NULL) { return NGX_ERROR; } *ll = cl; ll = &cl->next; b = cl->buf; b->flush = 1; b->memory = 1; b->pos = buf->pos; b->tag = u->output.tag; if (buf->last - buf->pos >= ctx->chunked.size) { buf->pos += ctx->chunked.size; b->last = buf->pos; ctx->chunked.size = 0; } else { ctx->chunked.size -= buf->last - buf->pos; buf->pos = buf->last; b->last = buf->last; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http proxy out buf %p %z", b->pos, b->last - b->pos); continue; } if (rc == NGX_DONE) { /* a whole response has been parsed successfully */ u->keepalive = !u->headers_in.connection_close; u->length = 0; break; } if (rc == NGX_AGAIN) { break; } /* invalid response */ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid chunked response"); return NGX_ERROR; } /* provide continuous buffer for subrequests in memory */ if (r->subrequest_in_memory) { cl = u->out_bufs; if (cl) { buf->pos = cl->buf->pos; } buf->last = buf->pos; for (cl = u->out_bufs; cl; cl = cl->next) { ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http proxy in memory %p-%p %uz", cl->buf->pos, cl->buf->last, ngx_buf_size(cl->buf)); if (buf->last == cl->buf->pos) { buf->last = cl->buf->last; continue; } buf->last = ngx_movemem(buf->last, cl->buf->pos, cl->buf->last - cl->buf->pos); cl->buf->pos = buf->last - (cl->buf->last - cl->buf->pos); cl->buf->last = buf->last; } } return NGX_OK; } static void ngx_http_proxy_abort_request(ngx_http_request_t *r) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "abort http proxy request"); return; } static void ngx_http_proxy_finalize_request(ngx_http_request_t *r, ngx_int_t rc) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "finalize http proxy request"); return; } static ngx_int_t ngx_http_proxy_host_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_http_proxy_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); if (ctx == NULL) { v->not_found = 1; return NGX_OK; } v->len = ctx->vars.host_header.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = ctx->vars.host_header.data; return NGX_OK; } static ngx_int_t ngx_http_proxy_port_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_http_proxy_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); if (ctx == NULL) { v->not_found = 1; return NGX_OK; } v->len = ctx->vars.port.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = ctx->vars.port.data; return NGX_OK; } static ngx_int_t ngx_http_proxy_add_x_forwarded_for_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { size_t len; u_char *p; ngx_uint_t i, n; ngx_table_elt_t **h; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; n = r->headers_in.x_forwarded_for.nelts; h = r->headers_in.x_forwarded_for.elts; len = 0; for (i = 0; i < n; i++) { len += h[i]->value.len + sizeof(", ") - 1; } if (len == 0) { v->len = r->connection->addr_text.len; v->data = r->connection->addr_text.data; return NGX_OK; } len += r->connection->addr_text.len; p = ngx_pnalloc(r->pool, len); if (p == NULL) { return NGX_ERROR; } v->len = len; v->data = p; for (i = 0; i < n; i++) { p = ngx_copy(p, h[i]->value.data, h[i]->value.len); *p++ = ','; *p++ = ' '; } ngx_memcpy(p, r->connection->addr_text.data, r->connection->addr_text.len); return NGX_OK; } static ngx_int_t ngx_http_proxy_internal_body_length_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_http_proxy_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); if (ctx == NULL || ctx->internal_body_length < 0) { v->not_found = 1; return NGX_OK; } v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = ngx_pnalloc(r->connection->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_rewrite_redirect(ngx_http_request_t *r, ngx_table_elt_t *h, size_t prefix) { size_t len; ngx_int_t rc; ngx_uint_t i; ngx_http_proxy_rewrite_t *pr; ngx_http_proxy_loc_conf_t *plcf; plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); pr = plcf->redirects->elts; if (pr == NULL) { return NGX_DECLINED; } len = h->value.len - prefix; for (i = 0; i < plcf->redirects->nelts; i++) { rc = pr[i].handler(r, h, prefix, len, &pr[i]); if (rc != NGX_DECLINED) { return rc; } } return NGX_DECLINED; } static ngx_int_t ngx_http_proxy_rewrite_cookie(ngx_http_request_t *r, ngx_table_elt_t *h) { size_t prefix; u_char *p; ngx_int_t rc, rv; ngx_http_proxy_loc_conf_t *plcf; p = (u_char *) ngx_strchr(h->value.data, ';'); if (p == NULL) { return NGX_DECLINED; } prefix = p + 1 - h->value.data; rv = NGX_DECLINED; plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); if (plcf->cookie_domains) { p = ngx_strcasestrn(h->value.data + prefix, "domain=", 7 - 1); if (p) { rc = ngx_http_proxy_rewrite_cookie_value(r, h, p + 7, plcf->cookie_domains); if (rc == NGX_ERROR) { return NGX_ERROR; } if (rc != NGX_DECLINED) { rv = rc; } } } if (plcf->cookie_paths) { p = ngx_strcasestrn(h->value.data + prefix, "path=", 5 - 1); if (p) { rc = ngx_http_proxy_rewrite_cookie_value(r, h, p + 5, plcf->cookie_paths); if (rc == NGX_ERROR) { return NGX_ERROR; } if (rc != NGX_DECLINED) { rv = rc; } } } return rv; } static ngx_int_t ngx_http_proxy_rewrite_cookie_value(ngx_http_request_t *r, ngx_table_elt_t *h, u_char *value, ngx_array_t *rewrites) { size_t len, prefix; u_char *p; ngx_int_t rc; ngx_uint_t i; ngx_http_proxy_rewrite_t *pr; prefix = value - h->value.data; p = (u_char *) ngx_strchr(value, ';'); len = p ? (size_t) (p - value) : (h->value.len - prefix); pr = rewrites->elts; for (i = 0; i < rewrites->nelts; i++) { rc = pr[i].handler(r, h, prefix, len, &pr[i]); if (rc != NGX_DECLINED) { return rc; } } return NGX_DECLINED; } static ngx_int_t ngx_http_proxy_rewrite_complex_handler(ngx_http_request_t *r, ngx_table_elt_t *h, size_t prefix, size_t len, ngx_http_proxy_rewrite_t *pr) { ngx_str_t pattern, replacement; if (ngx_http_complex_value(r, &pr->pattern.complex, &pattern) != NGX_OK) { return NGX_ERROR; } if (pattern.len > len || ngx_rstrncmp(h->value.data + prefix, pattern.data, pattern.len) != 0) { return NGX_DECLINED; } if (ngx_http_complex_value(r, &pr->replacement, &replacement) != NGX_OK) { return NGX_ERROR; } return ngx_http_proxy_rewrite(r, h, prefix, pattern.len, &replacement); } #if (NGX_PCRE) static ngx_int_t ngx_http_proxy_rewrite_regex_handler(ngx_http_request_t *r, ngx_table_elt_t *h, size_t prefix, size_t len, ngx_http_proxy_rewrite_t *pr) { ngx_str_t pattern, replacement; pattern.len = len; pattern.data = h->value.data + prefix; if (ngx_http_regex_exec(r, pr->pattern.regex, &pattern) != NGX_OK) { return NGX_DECLINED; } if (ngx_http_complex_value(r, &pr->replacement, &replacement) != NGX_OK) { return NGX_ERROR; } if (prefix == 0 && h->value.len == len) { h->value = replacement; return NGX_OK; } return ngx_http_proxy_rewrite(r, h, prefix, len, &replacement); } #endif static ngx_int_t ngx_http_proxy_rewrite_domain_handler(ngx_http_request_t *r, ngx_table_elt_t *h, size_t prefix, size_t len, ngx_http_proxy_rewrite_t *pr) { u_char *p; ngx_str_t pattern, replacement; if (ngx_http_complex_value(r, &pr->pattern.complex, &pattern) != NGX_OK) { return NGX_ERROR; } p = h->value.data + prefix; if (p[0] == '.') { p++; prefix++; len--; } if (pattern.len != len || ngx_rstrncasecmp(pattern.data, p, len) != 0) { return NGX_DECLINED; } if (ngx_http_complex_value(r, &pr->replacement, &replacement) != NGX_OK) { return NGX_ERROR; } return ngx_http_proxy_rewrite(r, h, prefix, len, &replacement); } static ngx_int_t ngx_http_proxy_rewrite(ngx_http_request_t *r, ngx_table_elt_t *h, size_t prefix, size_t len, ngx_str_t *replacement) { u_char *p, *data; size_t new_len; new_len = replacement->len + h->value.len - len; if (replacement->len > len) { data = ngx_pnalloc(r->pool, new_len); if (data == NULL) { return NGX_ERROR; } p = ngx_copy(data, h->value.data, prefix); p = ngx_copy(p, replacement->data, replacement->len); ngx_memcpy(p, h->value.data + prefix + len, h->value.len - len - prefix); h->value.data = data; } else { p = ngx_copy(h->value.data + prefix, replacement->data, replacement->len); ngx_memmove(p, h->value.data + prefix + len, h->value.len - len - prefix); } h->value.len = new_len; return NGX_OK; } static ngx_int_t ngx_http_proxy_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var, *v; for (v = ngx_http_proxy_vars; v->name.len; v++) { var = ngx_http_add_variable(cf, &v->name, v->flags); if (var == NULL) { return NGX_ERROR; } var->get_handler = v->get_handler; var->data = v->data; } return NGX_OK; } static void * ngx_http_proxy_create_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_use_stale = 0; * conf->upstream.cache_methods = 0; * conf->upstream.temp_path = NULL; * conf->upstream.hide_headers_hash = { NULL, 0 }; * conf->upstream.uri = { 0, NULL }; * conf->upstream.location = NULL; * conf->upstream.store_lengths = NULL; * conf->upstream.store_values = NULL; * * conf->method = { 0, NULL }; * conf->headers_source = NULL; * conf->headers_set_len = NULL; * conf->headers_set = NULL; * conf->headers_set_hash = NULL; * conf->body_set_len = NULL; * conf->body_set = NULL; * conf->body_source = { 0, NULL }; * conf->redirects = NULL; */ conf->upstream.store = NGX_CONF_UNSET; conf->upstream.store_access = NGX_CONF_UNSET_UINT; conf->upstream.buffering = NGX_CONF_UNSET; conf->upstream.ignore_client_abort = NGX_CONF_UNSET; conf->upstream.local = NGX_CONF_UNSET_PTR; conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.send_lowat = NGX_CONF_UNSET_SIZE; conf->upstream.buffer_size = 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_PTR; conf->upstream.cache_min_uses = NGX_CONF_UNSET_UINT; conf->upstream.cache_bypass = NGX_CONF_UNSET_PTR; conf->upstream.no_cache = NGX_CONF_UNSET_PTR; conf->upstream.cache_valid = NGX_CONF_UNSET_PTR; conf->upstream.cache_lock = NGX_CONF_UNSET; conf->upstream.cache_lock_timeout = NGX_CONF_UNSET_MSEC; #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; #endif /* "proxy_cyclic_temp_file" is disabled */ conf->upstream.cyclic_temp_file = 0; conf->redirect = NGX_CONF_UNSET; conf->upstream.change_buffering = 1; conf->cookie_domains = NGX_CONF_UNSET_PTR; conf->cookie_paths = NGX_CONF_UNSET_PTR; conf->http_version = NGX_CONF_UNSET_UINT; conf->headers_hash_max_size = NGX_CONF_UNSET_UINT; conf->headers_hash_bucket_size = NGX_CONF_UNSET_UINT; ngx_str_set(&conf->upstream.module, "proxy"); return conf; } static char * ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_proxy_loc_conf_t *prev = parent; ngx_http_proxy_loc_conf_t *conf = child; u_char *p; size_t size; ngx_hash_init_t hash; ngx_http_core_loc_conf_t *clcf; ngx_http_proxy_rewrite_t *pr; ngx_http_script_compile_t sc; if (conf->upstream.store != 0) { ngx_conf_merge_value(conf->upstream.store, prev->upstream.store, 0); if (conf->upstream.store_lengths == NULL) { 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_value(conf->upstream.buffering, prev->upstream.buffering, 1); ngx_conf_merge_value(conf->upstream.ignore_client_abort, prev->upstream.ignore_client_abort, 0); ngx_conf_merge_ptr_value(conf->upstream.local, prev->upstream.local, NULL); ngx_conf_merge_msec_value(conf->upstream.connect_timeout, prev->upstream.connect_timeout, 60000); ngx_conf_merge_msec_value(conf->upstream.send_timeout, prev->upstream.send_timeout, 60000); ngx_conf_merge_msec_value(conf->upstream.read_timeout, prev->upstream.read_timeout, 60000); ngx_conf_merge_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_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) ngx_conf_merge_ptr_value(conf->upstream.cache, prev->upstream.cache, NULL); if (conf->upstream.cache && conf->upstream.cache->data == NULL) { ngx_shm_zone_t *shm_zone; shm_zone = conf->upstream.cache; ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"proxy_cache\" zone \"%V\" is unknown", &shm_zone->shm.name); return NGX_CONF_ERROR; } ngx_conf_merge_uint_value(conf->upstream.cache_min_uses, prev->upstream.cache_min_uses, 1); ngx_conf_merge_bitmask_value(conf->upstream.cache_use_stale, prev->upstream.cache_use_stale, (NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_OFF)); if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_OFF) { conf->upstream.cache_use_stale = NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_OFF; } if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_ERROR) { conf->upstream.cache_use_stale |= NGX_HTTP_UPSTREAM_FT_NOLIVE; } if (conf->upstream.cache_methods == 0) { conf->upstream.cache_methods = prev->upstream.cache_methods; } conf->upstream.cache_methods |= NGX_HTTP_GET|NGX_HTTP_HEAD; ngx_conf_merge_ptr_value(conf->upstream.cache_bypass, prev->upstream.cache_bypass, NULL); ngx_conf_merge_ptr_value(conf->upstream.no_cache, prev->upstream.no_cache, NULL); if (conf->upstream.no_cache && conf->upstream.cache_bypass == NULL) { ngx_log_error(NGX_LOG_WARN, cf->log, 0, "\"proxy_no_cache\" functionality has been changed in 0.8.46, " "now it should be used together with \"proxy_cache_bypass\""); } 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); #endif ngx_conf_merge_str_value(conf->method, prev->method, ""); if (conf->method.len && conf->method.data[conf->method.len - 1] != ' ') { conf->method.data[conf->method.len] = ' '; conf->method.len++; } ngx_conf_merge_value(conf->upstream.pass_request_headers, prev->upstream.pass_request_headers, 1); ngx_conf_merge_value(conf->upstream.pass_request_body, prev->upstream.pass_request_body, 1); ngx_conf_merge_value(conf->upstream.intercept_errors, prev->upstream.intercept_errors, 0); #if (NGX_HTTP_SSL) ngx_conf_merge_value(conf->upstream.ssl_session_reuse, prev->upstream.ssl_session_reuse, 1); #endif ngx_conf_merge_value(conf->redirect, prev->redirect, 1); if (conf->redirect) { if (conf->redirects == NULL) { conf->redirects = prev->redirects; } if (conf->redirects == NULL && conf->url.data) { conf->redirects = ngx_array_create(cf->pool, 1, sizeof(ngx_http_proxy_rewrite_t)); if (conf->redirects == NULL) { return NGX_CONF_ERROR; } pr = ngx_array_push(conf->redirects); if (pr == NULL) { return NGX_CONF_ERROR; } ngx_memzero(&pr->pattern.complex, sizeof(ngx_http_complex_value_t)); ngx_memzero(&pr->replacement, sizeof(ngx_http_complex_value_t)); pr->handler = ngx_http_proxy_rewrite_complex_handler; if (conf->vars.uri.len) { pr->pattern.complex.value = conf->url; pr->replacement.value = conf->location; } else { pr->pattern.complex.value.len = conf->url.len + sizeof("/") - 1; p = ngx_pnalloc(cf->pool, pr->pattern.complex.value.len); if (p == NULL) { return NGX_CONF_ERROR; } pr->pattern.complex.value.data = p; p = ngx_cpymem(p, conf->url.data, conf->url.len); *p = '/'; ngx_str_set(&pr->replacement.value, "/"); } } } ngx_conf_merge_ptr_value(conf->cookie_domains, prev->cookie_domains, NULL); ngx_conf_merge_ptr_value(conf->cookie_paths, prev->cookie_paths, NULL); #if (NGX_HTTP_SSL) if (conf->upstream.ssl == NULL) { conf->upstream.ssl = prev->upstream.ssl; } #endif 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; } if (conf->upstream.upstream == NULL) { conf->upstream.upstream = prev->upstream.upstream; conf->vars = prev->vars; } if (conf->proxy_lengths == NULL) { conf->proxy_lengths = prev->proxy_lengths; conf->proxy_values = prev->proxy_values; } if (conf->upstream.upstream || conf->proxy_lengths) { clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); if (clcf->handler == NULL && clcf->lmt_excpt) { clcf->handler = ngx_http_proxy_handler; conf->location = prev->location; } } if (conf->body_source.data == NULL) { conf->body_source = prev->body_source; conf->body_set_len = prev->body_set_len; conf->body_set = prev->body_set; } if (conf->body_source.data && conf->body_set_len == NULL) { ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); sc.cf = cf; sc.source = &conf->body_source; sc.flushes = &conf->flushes; sc.lengths = &conf->body_set_len; sc.values = &conf->body_set; sc.complete_lengths = 1; sc.complete_values = 1; if (ngx_http_script_compile(&sc) != NGX_OK) { return NGX_CONF_ERROR; } } if (ngx_http_proxy_merge_headers(cf, conf, prev) != NGX_OK) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } static ngx_int_t ngx_http_proxy_merge_headers(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *conf, ngx_http_proxy_loc_conf_t *prev) { 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 (conf->headers_source == NULL) { conf->flushes = prev->flushes; conf->headers_set_len = prev->headers_set_len; conf->headers_set = prev->headers_set; conf->headers_set_hash = prev->headers_set_hash; conf->headers_source = prev->headers_source; } if (conf->headers_set_hash.buckets #if (NGX_HTTP_CACHE) && ((conf->upstream.cache == NULL) == (prev->upstream.cache == NULL)) #endif ) { return NGX_OK; } if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t)) != NGX_OK) { return NGX_ERROR; } if (ngx_array_init(&headers_merged, cf->temp_pool, 4, sizeof(ngx_keyval_t)) != NGX_OK) { return NGX_ERROR; } if (conf->headers_source == NULL) { conf->headers_source = ngx_array_create(cf->pool, 4, sizeof(ngx_keyval_t)); if (conf->headers_source == NULL) { return NGX_ERROR; } } conf->headers_set_len = ngx_array_create(cf->pool, 64, 1); if (conf->headers_set_len == NULL) { return NGX_ERROR; } conf->headers_set = ngx_array_create(cf->pool, 512, 1); if (conf->headers_set == NULL) { return NGX_ERROR; } #if (NGX_HTTP_CACHE) h = conf->upstream.cache ? ngx_http_proxy_cache_headers: ngx_http_proxy_headers; #else h = ngx_http_proxy_headers; #endif 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]; } while (h->key.len) { src = headers_merged.elts; for (i = 0; i < headers_merged.nelts; i++) { if (ngx_strcasecmp(h->key.data, src[i].key.data) == 0) { goto next; } } s = ngx_array_push(&headers_merged); if (s == NULL) { return NGX_ERROR; } *s = *h; next: h++; } src = headers_merged.elts; for (i = 0; i < headers_merged.nelts; i++) { hk = ngx_array_push(&headers_names); if (hk == NULL) { return NGX_ERROR; } hk->key = src[i].key; hk->key_hash = ngx_hash_key_lc(src[i].key.data, src[i].key.len); hk->value = (void *) 1; if (src[i].value.len == 0) { continue; } if (ngx_http_script_variables_count(&src[i].value) == 0) { copy = ngx_array_push_n(conf->headers_set_len, sizeof(ngx_http_script_copy_code_t)); if (copy == NULL) { return NGX_ERROR; } copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code; copy->len = src[i].key.len + sizeof(": ") - 1 + src[i].value.len + sizeof(CRLF) - 1; size = (sizeof(ngx_http_script_copy_code_t) + src[i].key.len + sizeof(": ") - 1 + src[i].value.len + sizeof(CRLF) - 1 + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1); copy = ngx_array_push_n(conf->headers_set, size); if (copy == NULL) { return NGX_ERROR; } copy->code = ngx_http_script_copy_code; copy->len = src[i].key.len + sizeof(": ") - 1 + src[i].value.len + sizeof(CRLF) - 1; p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t); p = ngx_cpymem(p, src[i].key.data, src[i].key.len); *p++ = ':'; *p++ = ' '; p = ngx_cpymem(p, src[i].value.data, src[i].value.len); *p++ = CR; *p = LF; } else { copy = ngx_array_push_n(conf->headers_set_len, sizeof(ngx_http_script_copy_code_t)); if (copy == NULL) { return NGX_ERROR; } copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code; copy->len = src[i].key.len + sizeof(": ") - 1; size = (sizeof(ngx_http_script_copy_code_t) + src[i].key.len + sizeof(": ") - 1 + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1); copy = ngx_array_push_n(conf->headers_set, size); if (copy == NULL) { return NGX_ERROR; } copy->code = ngx_http_script_copy_code; copy->len = src[i].key.len + sizeof(": ") - 1; p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t); p = ngx_cpymem(p, src[i].key.data, src[i].key.len); *p++ = ':'; *p = ' '; ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); sc.cf = cf; sc.source = &src[i].value; sc.flushes = &conf->flushes; sc.lengths = &conf->headers_set_len; sc.values = &conf->headers_set; if (ngx_http_script_compile(&sc) != NGX_OK) { return NGX_ERROR; } copy = ngx_array_push_n(conf->headers_set_len, sizeof(ngx_http_script_copy_code_t)); if (copy == NULL) { return NGX_ERROR; } copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code; copy->len = sizeof(CRLF) - 1; size = (sizeof(ngx_http_script_copy_code_t) + sizeof(CRLF) - 1 + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1); copy = ngx_array_push_n(conf->headers_set, size); if (copy == NULL) { return NGX_ERROR; } copy->code = ngx_http_script_copy_code; copy->len = sizeof(CRLF) - 1; p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t); *p++ = CR; *p = LF; } code = ngx_array_push_n(conf->headers_set_len, sizeof(uintptr_t)); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) NULL; code = ngx_array_push_n(conf->headers_set, sizeof(uintptr_t)); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) NULL; } code = ngx_array_push_n(conf->headers_set_len, sizeof(uintptr_t)); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) NULL; hash.hash = &conf->headers_set_hash; hash.key = ngx_hash_key_lc; hash.max_size = conf->headers_hash_max_size; hash.bucket_size = conf->headers_hash_bucket_size; hash.name = "proxy_headers_hash"; hash.pool = cf->pool; hash.temp_pool = NULL; return ngx_hash_init(&hash, headers_names.elts, headers_names.nelts); } static char * ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_proxy_loc_conf_t *plcf = conf; size_t add; u_short port; ngx_str_t *value, *url; ngx_url_t u; ngx_uint_t n; ngx_http_core_loc_conf_t *clcf; ngx_http_script_compile_t sc; if (plcf->upstream.upstream || plcf->proxy_lengths) { return "is duplicate"; } clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); clcf->handler = ngx_http_proxy_handler; if (clcf->name.data[clcf->name.len - 1] == '/') { clcf->auto_redirect = 1; } value = cf->args->elts; url = &value[1]; n = ngx_http_script_variables_count(url); if (n) { ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); sc.cf = cf; sc.source = url; sc.lengths = &plcf->proxy_lengths; sc.values = &plcf->proxy_values; sc.variables = n; sc.complete_lengths = 1; sc.complete_values = 1; if (ngx_http_script_compile(&sc) != NGX_OK) { return NGX_CONF_ERROR; } #if (NGX_HTTP_SSL) if (ngx_http_proxy_set_ssl(cf, plcf) != NGX_OK) { return NGX_CONF_ERROR; } #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) if (ngx_http_proxy_set_ssl(cf, plcf) != NGX_OK) { return NGX_CONF_ERROR; } add = 8; port = 443; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "https protocol requires SSL support"); return NGX_CONF_ERROR; #endif } else { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid URL prefix"); return NGX_CONF_ERROR; } ngx_memzero(&u, sizeof(ngx_url_t)); u.url.len = url->len - add; u.url.data = url->data + add; u.default_port = port; u.uri_part = 1; u.no_resolve = 1; plcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0); if (plcf->upstream.upstream == NULL) { return NGX_CONF_ERROR; } plcf->vars.schema.len = add; plcf->vars.schema.data = url->data; plcf->vars.key_start = plcf->vars.schema; ngx_http_proxy_set_vars(&u, &plcf->vars); plcf->location = clcf->name; if (clcf->named #if (NGX_PCRE) || clcf->regex #endif || clcf->noname) { if (plcf->vars.uri.len) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"proxy_pass\" cannot have URI part in " "location given by regular expression, " "or inside named location, " "or inside \"if\" statement, " "or inside \"limit_except\" block"); return NGX_CONF_ERROR; } plcf->location.len = 0; } plcf->url = *url; return NGX_CONF_OK; } static char * ngx_http_proxy_redirect(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_proxy_loc_conf_t *plcf = conf; u_char *p; ngx_str_t *value; ngx_http_proxy_rewrite_t *pr; ngx_http_compile_complex_value_t ccv; if (plcf->redirect == 0) { return NGX_CONF_OK; } plcf->redirect = 1; value = cf->args->elts; if (cf->args->nelts == 2) { if (ngx_strcmp(value[1].data, "off") == 0) { plcf->redirect = 0; plcf->redirects = NULL; return NGX_CONF_OK; } if (ngx_strcmp(value[1].data, "false") == 0) { ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "invalid parameter \"false\", use \"off\" instead"); plcf->redirect = 0; plcf->redirects = NULL; return NGX_CONF_OK; } if (ngx_strcmp(value[1].data, "default") != 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[1]); return NGX_CONF_ERROR; } } if (plcf->redirects == NULL) { plcf->redirects = ngx_array_create(cf->pool, 1, sizeof(ngx_http_proxy_rewrite_t)); if (plcf->redirects == NULL) { return NGX_CONF_ERROR; } } pr = ngx_array_push(plcf->redirects); if (pr == NULL) { return NGX_CONF_ERROR; } if (ngx_strcmp(value[1].data, "default") == 0) { if (plcf->proxy_lengths) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"proxy_redirect default\" cannot be used " "with \"proxy_pass\" directive with variables"); return NGX_CONF_ERROR; } if (plcf->url.data == NULL) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"proxy_redirect default\" should be placed " "after the \"proxy_pass\" directive"); return NGX_CONF_ERROR; } pr->handler = ngx_http_proxy_rewrite_complex_handler; ngx_memzero(&pr->pattern.complex, sizeof(ngx_http_complex_value_t)); ngx_memzero(&pr->replacement, sizeof(ngx_http_complex_value_t)); if (plcf->vars.uri.len) { pr->pattern.complex.value = plcf->url; pr->replacement.value = plcf->location; } else { pr->pattern.complex.value.len = plcf->url.len + sizeof("/") - 1; p = ngx_pnalloc(cf->pool, pr->pattern.complex.value.len); if (p == NULL) { return NGX_CONF_ERROR; } pr->pattern.complex.value.data = p; p = ngx_cpymem(p, plcf->url.data, plcf->url.len); *p = '/'; ngx_str_set(&pr->replacement.value, "/"); } return NGX_CONF_OK; } if (value[1].data[0] == '~') { value[1].len--; value[1].data++; if (value[1].data[0] == '*') { value[1].len--; value[1].data++; if (ngx_http_proxy_rewrite_regex(cf, pr, &value[1], 1) != NGX_OK) { return NGX_CONF_ERROR; } } else { if (ngx_http_proxy_rewrite_regex(cf, pr, &value[1], 0) != NGX_OK) { return NGX_CONF_ERROR; } } } else { ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = &pr->pattern.complex; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } pr->handler = ngx_http_proxy_rewrite_complex_handler; } ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[2]; ccv.complex_value = &pr->replacement; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } static char * ngx_http_proxy_cookie_domain(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_proxy_loc_conf_t *plcf = conf; ngx_str_t *value; ngx_http_proxy_rewrite_t *pr; ngx_http_compile_complex_value_t ccv; if (plcf->cookie_domains == NULL) { return NGX_CONF_OK; } value = cf->args->elts; if (cf->args->nelts == 2) { if (ngx_strcmp(value[1].data, "off") == 0) { plcf->cookie_domains = NULL; return NGX_CONF_OK; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[1]); return NGX_CONF_ERROR; } if (plcf->cookie_domains == NGX_CONF_UNSET_PTR) { plcf->cookie_domains = ngx_array_create(cf->pool, 1, sizeof(ngx_http_proxy_rewrite_t)); if (plcf->cookie_domains == NULL) { return NGX_CONF_ERROR; } } pr = ngx_array_push(plcf->cookie_domains); if (pr == NULL) { return NGX_CONF_ERROR; } if (value[1].data[0] == '~') { value[1].len--; value[1].data++; if (ngx_http_proxy_rewrite_regex(cf, pr, &value[1], 1) != NGX_OK) { return NGX_CONF_ERROR; } } else { if (value[1].data[0] == '.') { value[1].len--; value[1].data++; } ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = &pr->pattern.complex; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } pr->handler = ngx_http_proxy_rewrite_domain_handler; if (value[2].data[0] == '.') { value[2].len--; value[2].data++; } } ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[2]; ccv.complex_value = &pr->replacement; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } static char * ngx_http_proxy_cookie_path(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_proxy_loc_conf_t *plcf = conf; ngx_str_t *value; ngx_http_proxy_rewrite_t *pr; ngx_http_compile_complex_value_t ccv; if (plcf->cookie_paths == NULL) { return NGX_CONF_OK; } value = cf->args->elts; if (cf->args->nelts == 2) { if (ngx_strcmp(value[1].data, "off") == 0) { plcf->cookie_paths = NULL; return NGX_CONF_OK; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[1]); return NGX_CONF_ERROR; } if (plcf->cookie_paths == NGX_CONF_UNSET_PTR) { plcf->cookie_paths = ngx_array_create(cf->pool, 1, sizeof(ngx_http_proxy_rewrite_t)); if (plcf->cookie_paths == NULL) { return NGX_CONF_ERROR; } } pr = ngx_array_push(plcf->cookie_paths); if (pr == NULL) { return NGX_CONF_ERROR; } if (value[1].data[0] == '~') { value[1].len--; value[1].data++; if (value[1].data[0] == '*') { value[1].len--; value[1].data++; if (ngx_http_proxy_rewrite_regex(cf, pr, &value[1], 1) != NGX_OK) { return NGX_CONF_ERROR; } } else { if (ngx_http_proxy_rewrite_regex(cf, pr, &value[1], 0) != NGX_OK) { return NGX_CONF_ERROR; } } } else { ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = &pr->pattern.complex; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } pr->handler = ngx_http_proxy_rewrite_complex_handler; } ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[2]; ccv.complex_value = &pr->replacement; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } static ngx_int_t ngx_http_proxy_rewrite_regex(ngx_conf_t *cf, ngx_http_proxy_rewrite_t *pr, ngx_str_t *regex, ngx_uint_t caseless) { #if (NGX_PCRE) u_char errstr[NGX_MAX_CONF_ERRSTR]; ngx_regex_compile_t rc; ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); rc.pattern = *regex; rc.err.len = NGX_MAX_CONF_ERRSTR; rc.err.data = errstr; if (caseless) { rc.options = NGX_REGEX_CASELESS; } pr->pattern.regex = ngx_http_regex_compile(cf, &rc); if (pr->pattern.regex == NULL) { return NGX_ERROR; } pr->handler = ngx_http_proxy_rewrite_regex_handler; return NGX_OK; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "using regex \"%V\" requires PCRE library", regex); return NGX_ERROR; #endif } static char * ngx_http_proxy_store(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_proxy_loc_conf_t *plcf = conf; ngx_str_t *value; ngx_http_script_compile_t sc; if (plcf->upstream.store != NGX_CONF_UNSET || plcf->upstream.store_lengths) { 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 != NGX_CONF_UNSET_PTR && plcf->upstream.cache != NULL) { return "is incompatible with \"proxy_cache\""; } #endif if (ngx_strcmp(value[1].data, "on") == 0) { plcf->upstream.store = 1; 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; value = cf->args->elts; if (plcf->upstream.cache != NGX_CONF_UNSET_PTR) { return "is duplicate"; } if (ngx_strcmp(value[1].data, "off") == 0) { plcf->upstream.cache = NULL; return NGX_CONF_OK; } if (plcf->upstream.store > 0 || plcf->upstream.store_lengths) { return "is incompatible with \"proxy_store\""; } plcf->upstream.cache = ngx_shared_memory_add(cf, &value[1], 0, &ngx_http_proxy_module); if (plcf->upstream.cache == 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 static char * ngx_http_proxy_lowat_check(ngx_conf_t *cf, void *post, void *data) { #if (NGX_FREEBSD) ssize_t *np = data; if ((u_long) *np >= ngx_freebsd_net_inet_tcp_sendspace) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"proxy_send_lowat\" must be less than %d " "(sysctl net.inet.tcp.sendspace)", ngx_freebsd_net_inet_tcp_sendspace); return NGX_CONF_ERROR; } #elif !(NGX_HAVE_SO_SNDLOWAT) ssize_t *np = data; ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "\"proxy_send_lowat\" is not supported, ignored"); *np = 0; #endif return NGX_CONF_OK; } #if (NGX_HTTP_SSL) static ngx_int_t ngx_http_proxy_set_ssl(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *plcf) { ngx_pool_cleanup_t *cln; plcf->upstream.ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t)); if (plcf->upstream.ssl == NULL) { return NGX_ERROR; } plcf->upstream.ssl->log = cf->log; if (ngx_ssl_create(plcf->upstream.ssl, NGX_SSL_SSLv2|NGX_SSL_SSLv3|NGX_SSL_TLSv1 |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2, NULL) != NGX_OK) { return NGX_ERROR; } cln = ngx_pool_cleanup_add(cf->pool, 0); if (cln == NULL) { return NGX_ERROR; } cln->handler = ngx_ssl_cleanup_ctx; cln->data = plcf->upstream.ssl; 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.4.6/src/http/modules/ngx_http_upstream_ip_hash_module.c000644 001751 001751 00000016041 12305336445 026237 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ #include #include #include typedef struct { /* the round robin data must be first */ ngx_http_upstream_rr_peer_data_t rrp; ngx_uint_t hash; u_char addrlen; u_char *addr; u_char tries; ngx_event_get_peer_pt get_rr_peer; } ngx_http_upstream_ip_hash_peer_data_t; static ngx_int_t ngx_http_upstream_init_ip_hash_peer(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us); static ngx_int_t ngx_http_upstream_get_ip_hash_peer(ngx_peer_connection_t *pc, void *data); static char *ngx_http_upstream_ip_hash(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_command_t ngx_http_upstream_ip_hash_commands[] = { { ngx_string("ip_hash"), NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS, ngx_http_upstream_ip_hash, 0, 0, NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_upstream_ip_hash_module_ctx = { NULL, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; ngx_module_t ngx_http_upstream_ip_hash_module = { NGX_MODULE_V1, &ngx_http_upstream_ip_hash_module_ctx, /* module context */ ngx_http_upstream_ip_hash_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static u_char ngx_http_upstream_ip_hash_pseudo_addr[3]; static ngx_int_t ngx_http_upstream_init_ip_hash(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us) { if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) { return NGX_ERROR; } us->peer.init = ngx_http_upstream_init_ip_hash_peer; return NGX_OK; } static ngx_int_t ngx_http_upstream_init_ip_hash_peer(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us) { struct sockaddr_in *sin; #if (NGX_HAVE_INET6) struct sockaddr_in6 *sin6; #endif ngx_http_upstream_ip_hash_peer_data_t *iphp; iphp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_ip_hash_peer_data_t)); if (iphp == NULL) { return NGX_ERROR; } r->upstream->peer.data = &iphp->rrp; if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK) { return NGX_ERROR; } r->upstream->peer.get = ngx_http_upstream_get_ip_hash_peer; switch (r->connection->sockaddr->sa_family) { case AF_INET: sin = (struct sockaddr_in *) r->connection->sockaddr; iphp->addr = (u_char *) &sin->sin_addr.s_addr; iphp->addrlen = 3; break; #if (NGX_HAVE_INET6) case AF_INET6: sin6 = (struct sockaddr_in6 *) r->connection->sockaddr; iphp->addr = (u_char *) &sin6->sin6_addr.s6_addr; iphp->addrlen = 16; break; #endif default: iphp->addr = ngx_http_upstream_ip_hash_pseudo_addr; iphp->addrlen = 3; } iphp->hash = 89; iphp->tries = 0; iphp->get_rr_peer = ngx_http_upstream_get_round_robin_peer; return NGX_OK; } static ngx_int_t ngx_http_upstream_get_ip_hash_peer(ngx_peer_connection_t *pc, void *data) { ngx_http_upstream_ip_hash_peer_data_t *iphp = data; time_t now; ngx_int_t w; uintptr_t m; ngx_uint_t i, n, p, hash; ngx_http_upstream_rr_peer_t *peer; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, "get ip hash peer, try: %ui", pc->tries); /* TODO: cached */ if (iphp->tries > 20 || iphp->rrp.peers->single) { return iphp->get_rr_peer(pc, &iphp->rrp); } now = ngx_time(); pc->cached = 0; pc->connection = NULL; hash = iphp->hash; for ( ;; ) { for (i = 0; i < iphp->addrlen; i++) { hash = (hash * 113 + iphp->addr[i]) % 6271; } if (!iphp->rrp.peers->weighted) { p = hash % iphp->rrp.peers->number; } else { w = hash % iphp->rrp.peers->total_weight; for (i = 0; i < iphp->rrp.peers->number; i++) { w -= iphp->rrp.peers->peer[i].weight; if (w < 0) { break; } } p = i; } n = p / (8 * sizeof(uintptr_t)); m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t)); if (!(iphp->rrp.tried[n] & m)) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0, "get ip hash peer, hash: %ui %04XA", p, m); peer = &iphp->rrp.peers->peer[p]; /* ngx_lock_mutex(iphp->rrp.peers->mutex); */ if (!peer->down) { if (peer->max_fails == 0 || peer->fails < peer->max_fails) { break; } if (now - peer->checked > peer->fail_timeout) { peer->checked = now; break; } } iphp->rrp.tried[n] |= m; /* ngx_unlock_mutex(iphp->rrp.peers->mutex); */ pc->tries--; } if (++iphp->tries >= 20) { return iphp->get_rr_peer(pc, &iphp->rrp); } } iphp->rrp.current = p; pc->sockaddr = peer->sockaddr; pc->socklen = peer->socklen; pc->name = &peer->name; /* ngx_unlock_mutex(iphp->rrp.peers->mutex); */ iphp->rrp.tried[n] |= m; iphp->hash = hash; return NGX_OK; } static char * ngx_http_upstream_ip_hash(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_upstream_srv_conf_t *uscf; uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module); if (uscf->peer.init_upstream) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "load balancing method redefined"); } uscf->peer.init_upstream = ngx_http_upstream_init_ip_hash; uscf->flags = NGX_HTTP_UPSTREAM_CREATE |NGX_HTTP_UPSTREAM_WEIGHT |NGX_HTTP_UPSTREAM_MAX_FAILS |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT |NGX_HTTP_UPSTREAM_DOWN; return NGX_CONF_OK; } nginx-1.4.6/src/http/modules/ngx_http_range_filter_module.c000644 001751 001751 00000057347 12305336445 025363 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 mutlipart 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_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->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_http_parse_time(if_range->data, if_range->len); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http ir:%d lm:%d", 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; } if (ngx_array_init(&ctx->ranges, r->pool, 1, sizeof(ngx_http_range_t)) != NGX_OK) { return NGX_ERROR; } switch (ngx_http_range_parse(r, ctx, clcf->max_ranges)) { case NGX_OK: ngx_http_set_ctx(r, ctx, ngx_http_range_body_filter_module); r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT; r->headers_out.status_line.len = 0; if (ctx->ranges.nelts == 1) { return ngx_http_range_singlepart_header(r, ctx); } return ngx_http_range_multipart_header(r, ctx); case NGX_HTTP_RANGE_NOT_SATISFIABLE: return ngx_http_range_not_satisfiable(r); case NGX_ERROR: return NGX_ERROR; default: /* NGX_DECLINED */ break; } next_filter: r->headers_out.accept_ranges = ngx_list_push(&r->headers_out.headers); if (r->headers_out.accept_ranges == NULL) { return NGX_ERROR; } r->headers_out.accept_ranges->hash = 1; ngx_str_set(&r->headers_out.accept_ranges->key, "Accept-Ranges"); ngx_str_set(&r->headers_out.accept_ranges->value, "bytes"); return ngx_http_next_header_filter(r); } static ngx_int_t ngx_http_range_parse(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx, ngx_uint_t ranges) { u_char *p; off_t start, end, size, content_length; ngx_uint_t suffix; ngx_http_range_t *range; p = r->headers_in.range->value.data + 6; size = 0; content_length = r->headers_out.content_length_n; 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') { 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') { end = end * 10 + *p++ - '0'; } while (*p == ' ') { p++; } if (*p != ',' && *p != '\0') { return NGX_HTTP_RANGE_NOT_SATISFIABLE; } if (suffix) { start = content_length - end; end = content_length - 1; } if (end >= content_length) { end = content_length; } else { end++; } found: if (start < end) { range = ngx_array_push(&ctx->ranges); if (range == NULL) { return NGX_ERROR; } range->start = start; range->end = end; size += end - start; if (ranges-- == 0) { return NGX_DECLINED; } } if (*p++ != ',') { break; } } if (ctx->ranges.nelts == 0) { return NGX_HTTP_RANGE_NOT_SATISFIABLE; } if (size > content_length) { return NGX_DECLINED; } return NGX_OK; } static ngx_int_t ngx_http_range_singlepart_header(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx) { ngx_table_elt_t *content_range; ngx_http_range_t *range; content_range = ngx_list_push(&r->headers_out.headers); if (content_range == NULL) { return NGX_ERROR; } r->headers_out.content_range = content_range; content_range->hash = 1; ngx_str_set(&content_range->key, "Content-Range"); content_range->value.data = ngx_pnalloc(r->pool, sizeof("bytes -/") - 1 + 3 * NGX_OFF_T_LEN); if (content_range->value.data == NULL) { return NGX_ERROR; } /* "Content-Range: bytes SSSS-EEEE/TTTT" header */ range = ctx->ranges.elts; content_range->value.len = ngx_sprintf(content_range->value.data, "bytes %O-%O/%O", range->start, range->end - 1, r->headers_out.content_length_n) - content_range->value.data; r->headers_out.content_length_n = range->end - range->start; if (r->headers_out.content_length) { r->headers_out.content_length->hash = 0; r->headers_out.content_length = NULL; } return ngx_http_next_header_filter(r); } static ngx_int_t ngx_http_range_multipart_header(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx) { size_t len; ngx_uint_t i; ngx_http_range_t *range; ngx_atomic_uint_t boundary; len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN + sizeof(CRLF "Content-Type: ") - 1 + r->headers_out.content_type.len + sizeof(CRLF "Content-Range: bytes ") - 1; if (r->headers_out.charset.len) { len += sizeof("; charset=") - 1 + r->headers_out.charset.len; } ctx->boundary_header.data = ngx_pnalloc(r->pool, len); if (ctx->boundary_header.data == NULL) { return NGX_ERROR; } boundary = ngx_next_temp_number(0); /* * The boundary header of the range: * CRLF * "--0123456789" CRLF * "Content-Type: image/jpeg" CRLF * "Content-Range: bytes " */ if (r->headers_out.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; r->headers_out.charset.len = 0; } 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; /* the size of the last boundary CRLF "--0123456789--" CRLF */ len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN + sizeof("--" CRLF) - 1; range = ctx->ranges.elts; for (i = 0; i < ctx->ranges.nelts; i++) { /* the size of the range: "SSSS-EEEE/TTTT" CRLF CRLF */ range[i].content_range.data = ngx_pnalloc(r->pool, 3 * NGX_OFF_T_LEN + 2 + 4); if (range[i].content_range.data == NULL) { return NGX_ERROR; } range[i].content_range.len = ngx_sprintf(range[i].content_range.data, "%O-%O/%O" CRLF CRLF, range[i].start, range[i].end - 1, r->headers_out.content_length_n) - range[i].content_range.data; len += ctx->boundary_header.len + range[i].content_range.len + (size_t) (range[i].end - range[i].start); } r->headers_out.content_length_n = len; if (r->headers_out.content_length) { r->headers_out.content_length->hash = 0; r->headers_out.content_length = NULL; } return ngx_http_next_header_filter(r); } static ngx_int_t ngx_http_range_not_satisfiable(ngx_http_request_t *r) { ngx_table_elt_t *content_range; r->headers_out.status = NGX_HTTP_RANGE_NOT_SATISFIABLE; content_range = ngx_list_push(&r->headers_out.headers); if (content_range == NULL) { return NGX_ERROR; } r->headers_out.content_range = content_range; content_range->hash = 1; ngx_str_set(&content_range->key, "Content-Range"); content_range->value.data = ngx_pnalloc(r->pool, sizeof("bytes */") - 1 + NGX_OFF_T_LEN); if (content_range->value.data == NULL) { return NGX_ERROR; } content_range->value.len = ngx_sprintf(content_range->value.data, "bytes */%O", r->headers_out.content_length_n) - content_range->value.data; ngx_http_clear_content_length(r); return NGX_HTTP_RANGE_NOT_SATISFIABLE; } static ngx_int_t ngx_http_range_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_http_range_filter_ctx_t *ctx; if (in == NULL) { return ngx_http_next_body_filter(r, in); } ctx = ngx_http_get_module_ctx(r, ngx_http_range_body_filter_module); if (ctx == NULL) { return ngx_http_next_body_filter(r, in); } if (ctx->ranges.nelts == 1) { return ngx_http_range_singlepart_body(r, ctx, in); } /* * multipart ranges are supported only if whole body is in a single buffer */ if (ngx_buf_special(in->buf)) { return ngx_http_next_body_filter(r, in); } if (ngx_http_range_test_overlapped(r, ctx, in) != NGX_OK) { return NGX_ERROR; } return ngx_http_range_multipart_body(r, ctx, in); } static ngx_int_t ngx_http_range_test_overlapped(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in) { off_t start, last; ngx_buf_t *buf; ngx_uint_t i; ngx_http_range_t *range; if (ctx->offset) { goto overlapped; } buf = in->buf; if (!buf->last_buf) { start = ctx->offset; last = ctx->offset + ngx_buf_size(buf); range = ctx->ranges.elts; for (i = 0; i < ctx->ranges.nelts; i++) { if (start > range[i].start || last < range[i].end) { goto overlapped; } } } ctx->offset = ngx_buf_size(buf); return NGX_OK; overlapped: ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "range in overlapped buffers"); return NGX_ERROR; } static ngx_int_t ngx_http_range_singlepart_body(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in) { off_t start, last; ngx_buf_t *buf; ngx_chain_t *out, *cl, **ll; ngx_http_range_t *range; out = NULL; ll = &out; range = ctx->ranges.elts; for (cl = in; cl; cl = cl->next) { buf = cl->buf; start = ctx->offset; last = ctx->offset + ngx_buf_size(buf); ctx->offset = last; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http range body buf: %O-%O", start, last); if (ngx_buf_special(buf)) { *ll = cl; ll = &cl->next; continue; } if (range->end <= start || range->start >= last) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http range body skip"); if (buf->in_file) { buf->file_pos = buf->file_last; } buf->pos = buf->last; buf->sync = 1; continue; } if (range->start > start) { if (buf->in_file) { buf->file_pos += range->start - start; } if (ngx_buf_in_memory(buf)) { buf->pos += (size_t) (range->start - start); } } if (range->end <= last) { if (buf->in_file) { buf->file_last -= last - range->end; } if (ngx_buf_in_memory(buf)) { buf->last -= (size_t) (last - range->end); } buf->last_buf = 1; *ll = cl; cl->next = NULL; break; } *ll = cl; ll = &cl->next; } if (out == NULL) { return NGX_OK; } return ngx_http_next_body_filter(r, out); } static ngx_int_t ngx_http_range_multipart_body(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in) { ngx_buf_t *b, *buf; ngx_uint_t i; ngx_chain_t *out, *hcl, *rcl, *dcl, **ll; ngx_http_range_t *range; ll = &out; buf = in->buf; range = ctx->ranges.elts; for (i = 0; i < ctx->ranges.nelts; i++) { /* * The boundary header of the range: * CRLF * "--0123456789" CRLF * "Content-Type: image/jpeg" CRLF * "Content-Range: bytes " */ b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } b->memory = 1; b->pos = ctx->boundary_header.data; b->last = ctx->boundary_header.data + ctx->boundary_header.len; hcl = ngx_alloc_chain_link(r->pool); if (hcl == NULL) { return NGX_ERROR; } hcl->buf = b; /* "SSSS-EEEE/TTTT" CRLF CRLF */ b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } b->temporary = 1; b->pos = range[i].content_range.data; b->last = range[i].content_range.data + range[i].content_range.len; rcl = ngx_alloc_chain_link(r->pool); if (rcl == NULL) { return NGX_ERROR; } rcl->buf = b; /* the range data */ b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } b->in_file = buf->in_file; b->temporary = buf->temporary; b->memory = buf->memory; b->mmap = buf->mmap; b->file = buf->file; if (buf->in_file) { b->file_pos = buf->file_pos + range[i].start; b->file_last = buf->file_pos + range[i].end; } if (ngx_buf_in_memory(buf)) { b->pos = buf->pos + (size_t) range[i].start; b->last = buf->pos + (size_t) range[i].end; } dcl = ngx_alloc_chain_link(r->pool); if (dcl == NULL) { return NGX_ERROR; } dcl->buf = b; *ll = hcl; hcl->next = rcl; rcl->next = dcl; ll = &dcl->next; } /* the last boundary CRLF "--0123456789--" CRLF */ b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } b->temporary = 1; b->last_buf = 1; b->pos = ngx_pnalloc(r->pool, sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN + sizeof("--" CRLF) - 1); if (b->pos == NULL) { return NGX_ERROR; } b->last = ngx_cpymem(b->pos, ctx->boundary_header.data, sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN); *b->last++ = '-'; *b->last++ = '-'; *b->last++ = CR; *b->last++ = LF; hcl = ngx_alloc_chain_link(r->pool); if (hcl == NULL) { return NGX_ERROR; } hcl->buf = b; hcl->next = NULL; *ll = hcl; return ngx_http_next_body_filter(r, out); } static ngx_int_t ngx_http_range_header_filter_init(ngx_conf_t *cf) { ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_range_header_filter; return NGX_OK; } static ngx_int_t ngx_http_range_body_filter_init(ngx_conf_t *cf) { ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_range_body_filter; return NGX_OK; } nginx-1.4.6/src/http/modules/ngx_http_realip_module.c000644 001751 001751 00000024310 12305336445 024156 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 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_init(ngx_conf_t *cf); 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 = { NULL, /* 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_int_t ngx_http_realip_handler(ngx_http_request_t *r) { u_char *p; size_t len; ngx_str_t *value; ngx_uint_t i, hash; ngx_addr_t addr; ngx_array_t *xfwd; ngx_list_part_t *part; ngx_table_elt_t *header; ngx_connection_t *c; ngx_http_realip_ctx_t *ctx; ngx_http_realip_loc_conf_t *rlcf; ctx = ngx_http_get_module_ctx(r, ngx_http_realip_module); if (ctx) { return NGX_DECLINED; } rlcf = ngx_http_get_module_loc_conf(r, ngx_http_realip_module); if (rlcf->from == NULL) { return NGX_DECLINED; } switch (rlcf->type) { case NGX_HTTP_REALIP_XREALIP: if (r->headers_in.x_real_ip == NULL) { return NGX_DECLINED; } value = &r->headers_in.x_real_ip->value; xfwd = NULL; break; case NGX_HTTP_REALIP_XFWD: xfwd = &r->headers_in.x_forwarded_for; if (xfwd->elts == NULL) { return NGX_DECLINED; } value = NULL; break; default: /* NGX_HTTP_REALIP_HEADER */ part = &r->headers_in.headers.part; header = part->elts; hash = rlcf->hash; len = rlcf->header.len; p = rlcf->header.data; for (i = 0; /* void */ ; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (hash == header[i].hash && len == header[i].key.len && ngx_strncmp(p, header[i].lowcase_key, len) == 0) { value = &header[i].value; xfwd = NULL; goto found; } } return NGX_DECLINED; } found: c = r->connection; addr.sockaddr = c->sockaddr; addr.socklen = c->socklen; /* addr.name = c->addr_text; */ if (ngx_http_get_forwarded_addr(r, &addr, xfwd, value, rlcf->from, rlcf->recursive) != NGX_DECLINED) { return ngx_http_realip_set_addr(r, &addr); } return NGX_DECLINED; } static ngx_int_t ngx_http_realip_set_addr(ngx_http_request_t *r, ngx_addr_t *addr) { size_t len; u_char *p; u_char text[NGX_SOCKADDR_STRLEN]; ngx_connection_t *c; ngx_pool_cleanup_t *cln; ngx_http_realip_ctx_t *ctx; cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_http_realip_ctx_t)); if (cln == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ctx = cln->data; ngx_http_set_ctx(r, ctx, ngx_http_realip_module); c = r->connection; len = ngx_sock_ntop(addr->sockaddr, text, NGX_SOCKADDR_STRLEN, 0); if (len == 0) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } p = ngx_pnalloc(c->pool, len); if (p == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_memcpy(p, text, len); cln->handler = ngx_http_realip_cleanup; ctx->connection = c; ctx->sockaddr = c->sockaddr; ctx->socklen = c->socklen; ctx->addr_text = c->addr_text; c->sockaddr = addr->sockaddr; c->socklen = addr->socklen; c->addr_text.len = len; c->addr_text.data = p; return NGX_DECLINED; } static void ngx_http_realip_cleanup(void *data) { ngx_http_realip_ctx_t *ctx = data; ngx_connection_t *c; c = ctx->connection; c->sockaddr = ctx->sockaddr; c->socklen = ctx->socklen; c->addr_text = ctx->addr_text; } static char * ngx_http_realip_from(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_realip_loc_conf_t *rlcf = conf; ngx_int_t rc; ngx_str_t *value; ngx_cidr_t *cidr; value = cf->args->elts; if (rlcf->from == NULL) { rlcf->from = ngx_array_create(cf->pool, 2, sizeof(ngx_cidr_t)); if (rlcf->from == NULL) { return NGX_CONF_ERROR; } } cidr = ngx_array_push(rlcf->from); if (cidr == NULL) { return NGX_CONF_ERROR; } #if (NGX_HAVE_UNIX_DOMAIN) if (ngx_strcmp(value[1].data, "unix:") == 0) { cidr->family = AF_UNIX; return NGX_CONF_OK; } #endif rc = ngx_ptocidr(&value[1], cidr); if (rc == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[1]); return NGX_CONF_ERROR; } if (rc == NGX_DONE) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "low address bits of %V are meaningless", &value[1]); } return NGX_CONF_OK; } static char * ngx_http_realip(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_realip_loc_conf_t *rlcf = conf; ngx_str_t *value; value = cf->args->elts; if (ngx_strcmp(value[1].data, "X-Real-IP") == 0) { rlcf->type = NGX_HTTP_REALIP_XREALIP; return NGX_CONF_OK; } if (ngx_strcmp(value[1].data, "X-Forwarded-For") == 0) { rlcf->type = NGX_HTTP_REALIP_XFWD; return NGX_CONF_OK; } 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_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; } nginx-1.4.6/src/http/modules/ngx_http_referer_module.c000644 001751 001751 00000036454 12305336445 024350 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ #include #include #include #define NGX_HTTP_REFERER_NO_URI_PART ((void *) 4) #if !(NGX_PCRE) #define ngx_regex_t void #endif typedef struct { ngx_hash_combined_t hash; #if (NGX_PCRE) ngx_array_t *regex; #endif ngx_flag_t no_referer; ngx_flag_t blocked_referer; ngx_hash_keys_arrays_t *keys; ngx_uint_t referer_hash_max_size; ngx_uint_t referer_hash_bucket_size; } ngx_http_referer_conf_t; static void * ngx_http_referer_create_conf(ngx_conf_t *cf); static char * ngx_http_referer_merge_conf(ngx_conf_t *cf, void *parent, void *child); static char *ngx_http_valid_referers(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_add_referer(ngx_conf_t *cf, ngx_hash_keys_arrays_t *keys, ngx_str_t *value, ngx_str_t *uri); static char *ngx_http_add_regex_referer(ngx_conf_t *cf, ngx_http_referer_conf_t *rlcf, ngx_str_t *name, ngx_regex_t *regex); static int ngx_libc_cdecl ngx_http_cmp_referer_wildcards(const void *one, const void *two); static ngx_command_t ngx_http_referer_commands[] = { { ngx_string("valid_referers"), NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_valid_referers, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("referer_hash_max_size"), NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_referer_conf_t, referer_hash_max_size), NULL }, { ngx_string("referer_hash_bucket_size"), NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_referer_conf_t, referer_hash_bucket_size), NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_referer_module_ctx = { NULL, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_referer_create_conf, /* create location configuration */ ngx_http_referer_merge_conf /* merge location configuration */ }; ngx_module_t ngx_http_referer_module = { NGX_MODULE_V1, &ngx_http_referer_module_ctx, /* module context */ ngx_http_referer_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_int_t ngx_http_referer_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p, *ref, *last; size_t len; ngx_str_t *uri; ngx_uint_t i, key; ngx_http_referer_conf_t *rlcf; u_char buf[256]; 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 #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; goto valid_scheme; } else if (ngx_strncasecmp(ref, (u_char *) "https://", 8) == 0) { ref += 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; } buf[i] = ngx_tolower(*p); key = ngx_hash(key, buf[i++]); if (i == 256) { goto invalid; } } uri = ngx_hash_find_combined(&rlcf->hash, key, buf, p - ref); if (uri) { goto uri; } #if (NGX_PCRE) if (rlcf->regex) { ngx_int_t rc; ngx_str_t referer; referer.len = len - 7; referer.data = ref; rc = ngx_regex_exec_array(rlcf->regex, &referer, r->connection->log); if (rc == NGX_OK) { goto valid; } if (rc == NGX_ERROR) { return rc; } /* NGX_DECLINED */ } #endif invalid: *v = ngx_http_variable_true_value; return NGX_OK; uri: for ( /* void */ ; p < last; p++) { if (*p == '/') { break; } } len = last - p; if (uri == NGX_HTTP_REFERER_NO_URI_PART) { goto valid; } if (len < uri->len || ngx_strncmp(uri->data, p, uri->len) != 0) { goto invalid; } valid: *v = ngx_http_variable_null_value; return NGX_OK; } static void * ngx_http_referer_create_conf(ngx_conf_t *cf) { ngx_http_referer_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_referer_conf_t)); if (conf == NULL) { return NULL; } #if (NGX_PCRE) conf->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_hash_init_t hash; if (conf->keys == NULL) { conf->hash = prev->hash; #if (NGX_PCRE) ngx_conf_merge_ptr_value(conf->regex, prev->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->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); #endif if (conf->no_referer == NGX_CONF_UNSET) { conf->no_referer = 0; } if (conf->blocked_referer == NGX_CONF_UNSET) { conf->blocked_referer = 0; } conf->keys = NULL; return NGX_CONF_OK; } static char * ngx_http_valid_referers(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_referer_conf_t *rlcf = conf; u_char *p; ngx_str_t *value, uri, name; ngx_uint_t i, n; ngx_http_variable_t *var; ngx_http_server_name_t *sn; ngx_http_core_srv_conf_t *cscf; ngx_str_set(&name, "invalid_referer"); var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOHASH); if (var == NULL) { return NGX_CONF_ERROR; } var->get_handler = ngx_http_referer_variable; if (rlcf->keys == NULL) { rlcf->keys = ngx_pcalloc(cf->temp_pool, sizeof(ngx_hash_keys_arrays_t)); if (rlcf->keys == NULL) { return NGX_CONF_ERROR; } rlcf->keys->pool = cf->pool; rlcf->keys->temp_pool = cf->pool; if (ngx_hash_keys_array_init(rlcf->keys, NGX_HASH_SMALL) != NGX_OK) { return NGX_CONF_ERROR; } } value = cf->args->elts; for (i = 1; i < cf->args->nelts; i++) { if (value[i].len == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid referer \"%V\"", &value[i]); return NGX_CONF_ERROR; } if (ngx_strcmp(value[i].data, "none") == 0) { rlcf->no_referer = 1; continue; } if (ngx_strcmp(value[i].data, "blocked") == 0) { rlcf->blocked_referer = 1; continue; } ngx_str_null(&uri); if (ngx_strcmp(value[i].data, "server_names") == 0) { 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_referer(cf, rlcf, &sn[n].name, sn[n].regex->regex) != NGX_OK) { return NGX_CONF_ERROR; } continue; } #endif if (ngx_http_add_referer(cf, rlcf->keys, &sn[n].name, &uri) != NGX_OK) { return NGX_CONF_ERROR; } } continue; } if (value[i].data[0] == '~') { if (ngx_http_add_regex_referer(cf, rlcf, &value[i], NULL) != NGX_OK) { return NGX_CONF_ERROR; } continue; } 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 char * 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->len == 0) { u = NGX_HTTP_REFERER_NO_URI_PART; } else { u = ngx_palloc(cf->pool, sizeof(ngx_str_t)); if (u == NULL) { return NGX_CONF_ERROR; } *u = *uri; } rc = ngx_hash_add_key(keys, value, u, NGX_HASH_WILDCARD_KEY); if (rc == NGX_OK) { return NGX_CONF_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_CONF_ERROR; } static char * ngx_http_add_regex_referer(ngx_conf_t *cf, ngx_http_referer_conf_t *rlcf, ngx_str_t *name, ngx_regex_t *regex) { #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_CONF_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_CONF_ERROR; } } re = ngx_array_push(rlcf->regex); if (re == NULL) { return NGX_CONF_ERROR; } if (regex) { re->regex = regex; re->name = name->data; return NGX_CONF_OK; } 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_CONF_ERROR; } re->regex = rc.regex; re->name = name->data; return NGX_CONF_OK; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the using of the regex \"%V\" requires PCRE library", name); return NGX_CONF_ERROR; #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.4.6/src/http/modules/ngx_http_rewrite_module.c000644 001751 001751 00000067546 12305336445 024405 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); } if (e->status < NGX_HTTP_BAD_REQUEST) { return e->status; } if (r->err_status == 0) { return e->status; } return r->err_status; } static ngx_int_t ngx_http_rewrite_var(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_http_variable_t *var; ngx_http_core_main_conf_t *cmcf; ngx_http_rewrite_loc_conf_t *rlcf; rlcf = ngx_http_get_module_loc_conf(r, ngx_http_rewrite_module); if (rlcf->uninitialized_variable_warn == 0) { *v = ngx_http_variable_null_value; return NGX_OK; } cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); var = cmcf->variables.elts; /* * the ngx_http_rewrite_module sets variables directly in r->variables, * and they should be handled by ngx_http_get_indexed_variable(), * so the handler is called only if the variable is not initialized */ ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "using uninitialized \"%V\" variable", &var[data].name); *v = ngx_http_variable_null_value; return NGX_OK; } static void * ngx_http_rewrite_create_loc_conf(ngx_conf_t *cf) { ngx_http_rewrite_loc_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_rewrite_loc_conf_t)); if (conf == NULL) { return NULL; } conf->stack_size = NGX_CONF_UNSET_UINT; conf->log = NGX_CONF_UNSET; conf->uninitialized_variable_warn = NGX_CONF_UNSET; return conf; } static char * ngx_http_rewrite_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_rewrite_loc_conf_t *prev = parent; ngx_http_rewrite_loc_conf_t *conf = child; uintptr_t *code; ngx_conf_merge_value(conf->log, prev->log, 0); ngx_conf_merge_value(conf->uninitialized_variable_warn, prev->uninitialized_variable_warn, 1); ngx_conf_merge_uint_value(conf->stack_size, prev->stack_size, 10); if (conf->codes == NULL) { return NGX_CONF_OK; } if (conf->codes == prev->codes) { return NGX_CONF_OK; } code = ngx_array_push_n(conf->codes, sizeof(uintptr_t)); if (code == NULL) { return NGX_CONF_ERROR; } *code = (uintptr_t) NULL; return NGX_CONF_OK; } static ngx_int_t ngx_http_rewrite_init(ngx_conf_t *cf) { ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h = ngx_array_push(&cmcf->phases[NGX_HTTP_SERVER_REWRITE_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } *h = ngx_http_rewrite_handler; h = ngx_array_push(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } *h = ngx_http_rewrite_handler; return NGX_OK; } static char * ngx_http_rewrite(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_rewrite_loc_conf_t *lcf = conf; ngx_str_t *value; ngx_uint_t last; ngx_regex_compile_t rc; ngx_http_script_code_pt *code; ngx_http_script_compile_t sc; ngx_http_script_regex_code_t *regex; ngx_http_script_regex_end_code_t *regex_end; u_char errstr[NGX_MAX_CONF_ERRSTR]; regex = ngx_http_script_start_code(cf->pool, &lcf->codes, sizeof(ngx_http_script_regex_code_t)); if (regex == NULL) { return NGX_CONF_ERROR; } ngx_memzero(regex, sizeof(ngx_http_script_regex_code_t)); value = cf->args->elts; ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); rc.pattern = value[1]; rc.err.len = NGX_MAX_CONF_ERRSTR; rc.err.data = errstr; /* TODO: NGX_REGEX_CASELESS */ regex->regex = ngx_http_regex_compile(cf, &rc); if (regex->regex == NULL) { return NGX_CONF_ERROR; } regex->code = ngx_http_script_regex_start_code; regex->uri = 1; regex->name = value[1]; if (value[2].data[value[2].len - 1] == '?') { /* the last "?" drops the original arguments */ value[2].len--; } else { regex->add_args = 1; } last = 0; if (ngx_strncmp(value[2].data, "http://", sizeof("http://") - 1) == 0 || ngx_strncmp(value[2].data, "https://", sizeof("https://") - 1) == 0 || ngx_strncmp(value[2].data, "$scheme", sizeof("$scheme") - 1) == 0) { regex->status = NGX_HTTP_MOVED_TEMPORARILY; regex->redirect = 1; last = 1; } if (cf->args->nelts == 4) { if (ngx_strcmp(value[3].data, "last") == 0) { last = 1; } else if (ngx_strcmp(value[3].data, "break") == 0) { regex->break_cycle = 1; last = 1; } else if (ngx_strcmp(value[3].data, "redirect") == 0) { regex->status = NGX_HTTP_MOVED_TEMPORARILY; regex->redirect = 1; last = 1; } else if (ngx_strcmp(value[3].data, "permanent") == 0) { regex->status = NGX_HTTP_MOVED_PERMANENTLY; regex->redirect = 1; last = 1; } else { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[3]); return NGX_CONF_ERROR; } } ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); sc.cf = cf; sc.source = &value[2]; sc.lengths = ®ex->lengths; sc.values = &lcf->codes; sc.variables = ngx_http_script_variables_count(&value[2]); sc.main = regex; sc.complete_lengths = 1; sc.compile_args = !regex->redirect; if (ngx_http_script_compile(&sc) != NGX_OK) { return NGX_CONF_ERROR; } regex = sc.main; regex->size = sc.size; regex->args = sc.args; if (sc.variables == 0 && !sc.dup_capture) { regex->lengths = NULL; } regex_end = ngx_http_script_add_code(lcf->codes, sizeof(ngx_http_script_regex_end_code_t), ®ex); if (regex_end == NULL) { return NGX_CONF_ERROR; } regex_end->code = ngx_http_script_regex_end_code; regex_end->uri = regex->uri; regex_end->args = regex->args; regex_end->add_args = regex->add_args; regex_end->redirect = regex->redirect; if (last) { code = ngx_http_script_add_code(lcf->codes, sizeof(uintptr_t), ®ex); if (code == NULL) { return NGX_CONF_ERROR; } *code = NULL; } regex->next = (u_char *) lcf->codes->elts + lcf->codes->nelts - (u_char *) regex; return NGX_CONF_OK; } static char * ngx_http_rewrite_return(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_rewrite_loc_conf_t *lcf = conf; u_char *p; ngx_str_t *value, *v; ngx_http_script_return_code_t *ret; ngx_http_compile_complex_value_t ccv; ret = ngx_http_script_start_code(cf->pool, &lcf->codes, sizeof(ngx_http_script_return_code_t)); if (ret == NULL) { return NGX_CONF_ERROR; } value = cf->args->elts; ngx_memzero(ret, sizeof(ngx_http_script_return_code_t)); ret->code = ngx_http_script_return_code; p = value[1].data; ret->status = ngx_atoi(p, value[1].len); if (ret->status == (uintptr_t) NGX_ERROR) { if (cf->args->nelts == 2 && (ngx_strncmp(p, "http://", sizeof("http://") - 1) == 0 || ngx_strncmp(p, "https://", sizeof("https://") - 1) == 0 || ngx_strncmp(p, "$scheme", sizeof("$scheme") - 1) == 0)) { ret->status = NGX_HTTP_MOVED_TEMPORARILY; v = &value[1]; } else { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid return code \"%V\"", &value[1]); return NGX_CONF_ERROR; } } else { if (ret->status > 999) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid return code \"%V\"", &value[1]); return NGX_CONF_ERROR; } if (cf->args->nelts == 2) { return NGX_CONF_OK; } v = &value[2]; } ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = v; ccv.complex_value = &ret->text; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } static char * ngx_http_rewrite_break(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_rewrite_loc_conf_t *lcf = conf; ngx_http_script_code_pt *code; code = ngx_http_script_start_code(cf->pool, &lcf->codes, sizeof(uintptr_t)); if (code == NULL) { return NGX_CONF_ERROR; } *code = ngx_http_script_break_code; return NGX_CONF_OK; } static char * ngx_http_rewrite_if(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_rewrite_loc_conf_t *lcf = conf; void *mconf; char *rv; u_char *elts; ngx_uint_t i; ngx_conf_t save; ngx_http_module_t *module; ngx_http_conf_ctx_t *ctx, *pctx; ngx_http_core_loc_conf_t *clcf, *pclcf; ngx_http_script_if_code_t *if_code; ngx_http_rewrite_loc_conf_t *nlcf; ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t)); if (ctx == NULL) { return NGX_CONF_ERROR; } pctx = cf->ctx; ctx->main_conf = pctx->main_conf; ctx->srv_conf = pctx->srv_conf; ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); if (ctx->loc_conf == NULL) { return NGX_CONF_ERROR; } for (i = 0; ngx_modules[i]; i++) { if (ngx_modules[i]->type != NGX_HTTP_MODULE) { continue; } module = ngx_modules[i]->ctx; if (module->create_loc_conf) { mconf = module->create_loc_conf(cf); if (mconf == NULL) { return NGX_CONF_ERROR; } ctx->loc_conf[ngx_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 (pclcf->name.len == 0) { if_code->loc_conf = NULL; cf->cmd_type = NGX_HTTP_SIF_CONF; } else { if_code->loc_conf = ctx->loc_conf; cf->cmd_type = NGX_HTTP_LIF_CONF; } rv = ngx_conf_parse(cf, NULL); *cf = save; if (rv != NGX_CONF_OK) { return rv; } if (elts != lcf->codes->elts) { if_code = (ngx_http_script_if_code_t *) ((u_char *) if_code + ((u_char *) lcf->codes->elts - elts)); } if_code->next = (u_char *) lcf->codes->elts + lcf->codes->nelts - (u_char *) if_code; /* the code array belong to parent block */ nlcf->codes = NULL; return NGX_CONF_OK; } static char * ngx_http_rewrite_if_condition(ngx_conf_t *cf, ngx_http_rewrite_loc_conf_t *lcf) { u_char *p; size_t len; ngx_str_t *value; ngx_uint_t cur, last; ngx_regex_compile_t rc; ngx_http_script_code_pt *code; ngx_http_script_file_code_t *fop; ngx_http_script_regex_code_t *regex; u_char errstr[NGX_MAX_CONF_ERRSTR]; value = cf->args->elts; last = cf->args->nelts - 1; if (value[1].len < 1 || value[1].data[0] != '(') { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid condition \"%V\"", &value[1]); return NGX_CONF_ERROR; } if (value[1].len == 1) { cur = 2; } else { cur = 1; value[1].len--; value[1].data++; } if (value[last].len < 1 || value[last].data[value[last].len - 1] != ')') { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid condition \"%V\"", &value[last]); return NGX_CONF_ERROR; } if (value[last].len == 1) { last--; } else { value[last].len--; value[last].data[value[last].len] = '\0'; } len = value[cur].len; p = value[cur].data; if (len > 1 && p[0] == '$') { if (cur != last && cur + 2 != last) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid condition \"%V\"", &value[cur]); return NGX_CONF_ERROR; } if (ngx_http_rewrite_variable(cf, lcf, &value[cur]) != NGX_CONF_OK) { return NGX_CONF_ERROR; } if (cur == last) { return NGX_CONF_OK; } cur++; len = value[cur].len; p = value[cur].data; if (len == 1 && p[0] == '=') { if (ngx_http_rewrite_value(cf, lcf, &value[last]) != NGX_CONF_OK) { return NGX_CONF_ERROR; } code = ngx_http_script_start_code(cf->pool, &lcf->codes, sizeof(uintptr_t)); if (code == NULL) { return NGX_CONF_ERROR; } *code = ngx_http_script_equal_code; return NGX_CONF_OK; } if (len == 2 && p[0] == '!' && p[1] == '=') { if (ngx_http_rewrite_value(cf, lcf, &value[last]) != NGX_CONF_OK) { return NGX_CONF_ERROR; } code = ngx_http_script_start_code(cf->pool, &lcf->codes, sizeof(uintptr_t)); if (code == NULL) { return NGX_CONF_ERROR; } *code = ngx_http_script_not_equal_code; return NGX_CONF_OK; } if ((len == 1 && p[0] == '~') || (len == 2 && p[0] == '~' && p[1] == '*') || (len == 2 && p[0] == '!' && p[1] == '~') || (len == 3 && p[0] == '!' && p[1] == '~' && p[2] == '*')) { regex = ngx_http_script_start_code(cf->pool, &lcf->codes, sizeof(ngx_http_script_regex_code_t)); if (regex == NULL) { return NGX_CONF_ERROR; } ngx_memzero(regex, sizeof(ngx_http_script_regex_code_t)); ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); rc.pattern = value[last]; rc.options = (p[len - 1] == '*') ? NGX_REGEX_CASELESS : 0; rc.err.len = NGX_MAX_CONF_ERRSTR; rc.err.data = errstr; regex->regex = ngx_http_regex_compile(cf, &rc); if (regex->regex == NULL) { return NGX_CONF_ERROR; } regex->code = ngx_http_script_regex_start_code; regex->next = sizeof(ngx_http_script_regex_code_t); regex->test = 1; if (p[0] == '!') { regex->negative_test = 1; } regex->name = value[last]; return NGX_CONF_OK; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"%V\" in condition", &value[cur]); return NGX_CONF_ERROR; } else if ((len == 2 && p[0] == '-') || (len == 3 && p[0] == '!' && p[1] == '-')) { if (cur + 1 != last) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid condition \"%V\"", &value[cur]); return NGX_CONF_ERROR; } value[last].data[value[last].len] = '\0'; value[last].len++; if (ngx_http_rewrite_value(cf, lcf, &value[last]) != NGX_CONF_OK) { return NGX_CONF_ERROR; } fop = ngx_http_script_start_code(cf->pool, &lcf->codes, sizeof(ngx_http_script_file_code_t)); if (fop == NULL) { return NGX_CONF_ERROR; } fop->code = ngx_http_script_file_code; if (p[1] == 'f') { fop->op = ngx_http_script_file_plain; return NGX_CONF_OK; } if (p[1] == 'd') { fop->op = ngx_http_script_file_dir; return NGX_CONF_OK; } if (p[1] == 'e') { fop->op = ngx_http_script_file_exists; return NGX_CONF_OK; } if (p[1] == 'x') { fop->op = ngx_http_script_file_exec; return NGX_CONF_OK; } if (p[0] == '!') { if (p[2] == 'f') { fop->op = ngx_http_script_file_not_plain; return NGX_CONF_OK; } if (p[2] == 'd') { fop->op = ngx_http_script_file_not_dir; return NGX_CONF_OK; } if (p[2] == 'e') { fop->op = ngx_http_script_file_not_exists; return NGX_CONF_OK; } if (p[2] == 'x') { fop->op = ngx_http_script_file_not_exec; return NGX_CONF_OK; } } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid condition \"%V\"", &value[cur]); return NGX_CONF_ERROR; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid condition \"%V\"", &value[cur]); return NGX_CONF_ERROR; } static char * ngx_http_rewrite_variable(ngx_conf_t *cf, ngx_http_rewrite_loc_conf_t *lcf, ngx_str_t *value) { ngx_int_t index; ngx_http_script_var_code_t *var_code; value->len--; value->data++; index = ngx_http_get_variable_index(cf, value); if (index == NGX_ERROR) { return NGX_CONF_ERROR; } var_code = ngx_http_script_start_code(cf->pool, &lcf->codes, sizeof(ngx_http_script_var_code_t)); if (var_code == NULL) { return NGX_CONF_ERROR; } var_code->code = ngx_http_script_var_code; var_code->index = index; return NGX_CONF_OK; } static char * ngx_http_rewrite_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_rewrite_loc_conf_t *lcf = conf; ngx_int_t index; ngx_str_t *value; ngx_http_variable_t *v; ngx_http_script_var_code_t *vcode; ngx_http_script_var_handler_code_t *vhcode; value = cf->args->elts; if (value[1].data[0] != '$') { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid variable name \"%V\"", &value[1]); return NGX_CONF_ERROR; } value[1].len--; value[1].data++; v = ngx_http_add_variable(cf, &value[1], NGX_HTTP_VAR_CHANGEABLE); if (v == NULL) { return NGX_CONF_ERROR; } index = ngx_http_get_variable_index(cf, &value[1]); if (index == NGX_ERROR) { return NGX_CONF_ERROR; } if (v->get_handler == NULL && ngx_strncasecmp(value[1].data, (u_char *) "http_", 5) != 0 && ngx_strncasecmp(value[1].data, (u_char *) "sent_http_", 10) != 0 && ngx_strncasecmp(value[1].data, (u_char *) "upstream_http_", 14) != 0) { 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.4.6/src/http/modules/ngx_http_scgi_module.c000644 001751 001751 00000147317 12305336445 023644 0ustar00mdouninmdounin000000 000000 /* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. * Copyright (C) Manlio Perillo (manlio.perillo@gmail.com) */ #include #include #include typedef struct { ngx_http_upstream_conf_t upstream; ngx_array_t *flushes; ngx_array_t *params_len; ngx_array_t *params; ngx_array_t *params_source; ngx_hash_t headers_hash; ngx_uint_t header_params; ngx_array_t *scgi_lengths; ngx_array_t *scgi_values; #if (NGX_HTTP_CACHE) ngx_http_complex_value_t cache_key; #endif } ngx_http_scgi_loc_conf_t; static ngx_int_t ngx_http_scgi_eval(ngx_http_request_t *r, ngx_http_scgi_loc_conf_t *scf); static ngx_int_t ngx_http_scgi_create_request(ngx_http_request_t *r); static ngx_int_t ngx_http_scgi_reinit_request(ngx_http_request_t *r); static ngx_int_t ngx_http_scgi_process_status_line(ngx_http_request_t *r); static ngx_int_t ngx_http_scgi_process_header(ngx_http_request_t *r); static void ngx_http_scgi_abort_request(ngx_http_request_t *r); static void ngx_http_scgi_finalize_request(ngx_http_request_t *r, ngx_int_t rc); static void *ngx_http_scgi_create_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_merge_params(ngx_conf_t *cf, ngx_http_scgi_loc_conf_t *conf, ngx_http_scgi_loc_conf_t *prev); 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("http_500"), NGX_HTTP_UPSTREAM_FT_HTTP_500 }, { ngx_string("http_503"), NGX_HTTP_UPSTREAM_FT_HTTP_503 }, { ngx_string("http_404"), NGX_HTTP_UPSTREAM_FT_HTTP_404 }, { ngx_string("updating"), NGX_HTTP_UPSTREAM_FT_UPDATING }, { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF }, { ngx_null_string, 0 } }; ngx_module_t ngx_http_scgi_module; static ngx_command_t ngx_http_scgi_commands[] = { { ngx_string("scgi_pass"), NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, ngx_http_scgi_pass, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("scgi_store"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_scgi_store, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("scgi_store_access"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123, ngx_conf_set_access_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.store_access), NULL }, { ngx_string("scgi_buffering"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.buffering), NULL }, { ngx_string("scgi_ignore_client_abort"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.ignore_client_abort), NULL }, { ngx_string("scgi_bind"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_upstream_bind_set_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.local), NULL }, { ngx_string("scgi_connect_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.connect_timeout), NULL }, { ngx_string("scgi_send_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.send_timeout), NULL }, { ngx_string("scgi_buffer_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.buffer_size), NULL }, { ngx_string("scgi_pass_request_headers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.pass_request_headers), NULL }, { ngx_string("scgi_pass_request_body"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.pass_request_body), NULL }, { ngx_string("scgi_intercept_errors"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.intercept_errors), NULL }, { ngx_string("scgi_read_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.read_timeout), NULL }, { ngx_string("scgi_buffers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, ngx_conf_set_bufs_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.bufs), NULL }, { ngx_string("scgi_busy_buffers_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.busy_buffers_size_conf), NULL }, #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, 0, 0, &ngx_http_scgi_module }, { ngx_string("scgi_cache_bypass"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_set_predicate_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_bypass), NULL }, { ngx_string("scgi_no_cache"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_set_predicate_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.no_cache), NULL }, { ngx_string("scgi_cache_valid"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_file_cache_valid_set_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_valid), NULL }, { ngx_string("scgi_cache_min_uses"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_min_uses), NULL }, { ngx_string("scgi_cache_use_stale"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_use_stale), &ngx_http_scgi_next_upstream_masks }, { ngx_string("scgi_cache_methods"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_methods), &ngx_http_upstream_cache_method_mask }, { ngx_string("scgi_cache_lock"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_lock), NULL }, { ngx_string("scgi_cache_lock_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_lock_timeout), NULL }, #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_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 */ NULL, /* 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("") }, { ngx_string("HTTP_IF_UNMODIFIED_SINCE"), ngx_string("") }, { ngx_string("HTTP_IF_NONE_MATCH"), ngx_string("") }, { 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 (r->subrequest_in_memory) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "ngx_http_scgi_module does not support " "subrequests in memory"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } 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) 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; rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init); if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { return rc; } return NGX_DONE; } static ngx_int_t ngx_http_scgi_eval(ngx_http_request_t *r, ngx_http_scgi_loc_conf_t * scf) { ngx_url_t url; ngx_http_upstream_t *u; ngx_memzero(&url, sizeof(ngx_url_t)); if (ngx_http_script_run(r, &url.url, scf->scgi_lengths->elts, 0, scf->scgi_values->elts) == NULL) { return NGX_ERROR; } url.no_resolve = 1; if (ngx_parse_url(r->pool, &url) != NGX_OK) { if (url.err) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "%s in upstream \"%V\"", url.err, &url.url); } return NGX_ERROR; } u = r->upstream; u->resolved = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t)); if (u->resolved == NULL) { return NGX_ERROR; } if (url.addrs && url.addrs[0].sockaddr) { u->resolved->sockaddr = url.addrs[0].sockaddr; u->resolved->socklen = url.addrs[0].socklen; u->resolved->naddrs = 1; u->resolved->host = url.addrs[0].name; } else { u->resolved->host = url.host; u->resolved->port = url.port; u->resolved->no_port = url.no_port; } return NGX_OK; } #if (NGX_HTTP_CACHE) static ngx_int_t ngx_http_scgi_create_key(ngx_http_request_t *r) { ngx_str_t *key; ngx_http_scgi_loc_conf_t *scf; key = ngx_array_push(&r->cache->keys); if (key == NULL) { return NGX_ERROR; } scf = ngx_http_get_module_loc_conf(r, ngx_http_scgi_module); if (ngx_http_complex_value(r, &scf->cache_key, key) != NGX_OK) { return NGX_ERROR; } return NGX_OK; } #endif static ngx_int_t ngx_http_scgi_create_request(ngx_http_request_t *r) { off_t content_length_n; u_char ch, *key, *val, *lowcase_key; size_t len, key_len, val_len, allocated; ngx_buf_t *b; ngx_str_t content_length; ngx_uint_t i, n, hash, skip_empty, header_params; ngx_chain_t *cl, *body; ngx_list_part_t *part; ngx_table_elt_t *header, **ignored; ngx_http_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 (scf->params_len) { ngx_memzero(&le, sizeof(ngx_http_script_engine_t)); ngx_http_script_flush_no_cacheable_variables(r, scf->flushes); le.flushed = 1; le.ip = scf->params_len->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 (scf->header_params) { n = 0; part = &r->headers_in.headers.part; while (part) { n += part->nelts; part = part->next; } ignored = ngx_palloc(r->pool, n * sizeof(void *)); if (ignored == NULL) { return NGX_ERROR; } } part = &r->headers_in.headers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (scf->header_params) { 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(&scf->headers_hash, hash, lowcase_key, n)) { ignored[header_params++] = &header[i]; continue; } } len += sizeof("HTTP_") - 1 + header[i].key.len + 1 + header[i].value.len + 1; } } /* netstring: "length:" + packet + "," */ b = ngx_create_temp_buf(r->pool, NGX_SIZE_T_LEN + 1 + len + 1); if (b == NULL) { return NGX_ERROR; } cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_ERROR; } cl->buf = b; b->last = ngx_sprintf(b->last, "%ui:CONTENT_LENGTH%Z%V%Z", len, &content_length); if (scf->params_len) { ngx_memzero(&e, sizeof(ngx_http_script_engine_t)); e.ip = scf->params->elts; e.pos = b->last; e.request = r; e.flushed = 1; le.ip = scf->params_len->elts; while (*(uintptr_t *) le.ip) { lcode = *(ngx_http_script_len_code_pt *) le.ip; lcode(&le); /* key length */ lcode = *(ngx_http_script_len_code_pt *) le.ip; skip_empty = lcode(&le); for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) { lcode = *(ngx_http_script_len_code_pt *) le.ip; } le.ip += sizeof(uintptr_t); if (skip_empty && val_len == 0) { e.skip = 1; while (*(uintptr_t *) e.ip) { code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); } e.ip += sizeof(uintptr_t); e.skip = 0; continue; } #if (NGX_DEBUG) key = e.pos; #endif code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) & e); #if (NGX_DEBUG) val = e.pos; #endif while (*(uintptr_t *) e.ip) { code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); } *e.pos++ = '\0'; e.ip += sizeof(uintptr_t); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "scgi param: \"%s: %s\"", key, val); } b->last = e.pos; } if (scf->upstream.pass_request_headers) { part = &r->headers_in.headers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } for (n = 0; n < header_params; n++) { if (&header[i] == ignored[n]) { goto next; } } key = b->last; b->last = ngx_cpymem(key, "HTTP_", sizeof("HTTP_") - 1); for (n = 0; n < header[i].key.len; n++) { ch = header[i].key.data[n]; if (ch >= 'a' && ch <= 'z') { ch &= ~0x20; } else if (ch == '-') { ch = '_'; } *b->last++ = ch; } *b->last++ = (u_char) 0; val = b->last; b->last = ngx_copy(val, header[i].value.data, header[i].value.len); *b->last++ = (u_char) 0; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "scgi param: \"%s: %s\"", key, val); next: continue; } } *b->last++ = (u_char) ','; if (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 = status->code; } u->headers_in.status_n = status->code; len = status->end - status->start; u->headers_in.status_line.len = len; u->headers_in.status_line.data = ngx_pnalloc(r->pool, len); if (u->headers_in.status_line.data == NULL) { return NGX_ERROR; } ngx_memcpy(u->headers_in.status_line.data, status->start, len); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http scgi status %ui \"%V\"", u->headers_in.status_n, &u->headers_in.status_line); u->process_header = ngx_http_scgi_process_header; return ngx_http_scgi_process_header(r); } static ngx_int_t ngx_http_scgi_process_header(ngx_http_request_t *r) { ngx_str_t *status_line; ngx_int_t rc, status; ngx_table_elt_t *h; ngx_http_upstream_t *u; ngx_http_upstream_header_t *hh; ngx_http_upstream_main_conf_t *umcf; umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module); for ( ;; ) { rc = ngx_http_parse_header_line(r, &r->upstream->buffer, 1); if (rc == NGX_OK) { /* a header line has been parsed successfully */ h = ngx_list_push(&r->upstream->headers_in.headers); if (h == NULL) { return NGX_ERROR; } h->hash = r->header_hash; h->key.len = r->header_name_end - r->header_name_start; h->value.len = r->header_end - r->header_start; h->key.data = ngx_pnalloc(r->pool, h->key.len + 1 + h->value.len + 1 + h->key.len); if (h->key.data == NULL) { return NGX_ERROR; } h->value.data = h->key.data + h->key.len + 1; h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1; ngx_memcpy(h->key.data, r->header_name_start, h->key.len); h->key.data[h->key.len] = '\0'; ngx_memcpy(h->value.data, r->header_start, h->value.len); h->value.data[h->value.len] = '\0'; if (h->key.len == r->lowcase_index) { ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len); } else { ngx_strlow(h->lowcase_key, h->key.data, h->key.len); } hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len); if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { return NGX_ERROR; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http scgi header: \"%V: %V\"", &h->key, &h->value); continue; } if (rc == NGX_HTTP_PARSE_HEADER_DONE) { /* a whole header has been parsed successfully */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http scgi header done"); u = r->upstream; if (u->headers_in.status_n) { goto done; } if (u->headers_in.status) { status_line = &u->headers_in.status->value; status = ngx_atoi(status_line->data, 3); if (status == NGX_ERROR) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid status \"%V\"", status_line); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } u->headers_in.status_n = status; u->headers_in.status_line = *status_line; } else if (u->headers_in.location) { u->headers_in.status_n = 302; ngx_str_set(&u->headers_in.status_line, "302 Moved Temporarily"); } else { u->headers_in.status_n = 200; ngx_str_set(&u->headers_in.status_line, "200 OK"); } if (u->state) { u->state->status = u->headers_in.status_n; } done: if (u->headers_in.status_n == NGX_HTTP_SWITCHING_PROTOCOLS && r->headers_in.upgrade) { u->upgrade = 1; } return NGX_OK; } if (rc == NGX_AGAIN) { return NGX_AGAIN; } /* there was error while a header line parsing */ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid header"); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } } static void ngx_http_scgi_abort_request(ngx_http_request_t *r) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "abort http scgi request"); return; } static void ngx_http_scgi_finalize_request(ngx_http_request_t *r, ngx_int_t rc) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "finalize http scgi request"); return; } static void * ngx_http_scgi_create_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.buffering = NGX_CONF_UNSET; conf->upstream.ignore_client_abort = NGX_CONF_UNSET; conf->upstream.local = NGX_CONF_UNSET_PTR; conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC; conf->upstream.send_lowat = NGX_CONF_UNSET_SIZE; conf->upstream.buffer_size = 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_PTR; conf->upstream.cache_min_uses = NGX_CONF_UNSET_UINT; conf->upstream.cache_bypass = NGX_CONF_UNSET_PTR; conf->upstream.no_cache = NGX_CONF_UNSET_PTR; conf->upstream.cache_valid = NGX_CONF_UNSET_PTR; conf->upstream.cache_lock = NGX_CONF_UNSET; conf->upstream.cache_lock_timeout = NGX_CONF_UNSET_MSEC; #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_hash_init_t hash; ngx_http_core_loc_conf_t *clcf; if (conf->upstream.store != 0) { ngx_conf_merge_value(conf->upstream.store, prev->upstream.store, 0); if (conf->upstream.store_lengths == NULL) { 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_value(conf->upstream.buffering, prev->upstream.buffering, 1); ngx_conf_merge_value(conf->upstream.ignore_client_abort, prev->upstream.ignore_client_abort, 0); ngx_conf_merge_ptr_value(conf->upstream.local, prev->upstream.local, NULL); ngx_conf_merge_msec_value(conf->upstream.connect_timeout, prev->upstream.connect_timeout, 60000); ngx_conf_merge_msec_value(conf->upstream.send_timeout, prev->upstream.send_timeout, 60000); ngx_conf_merge_msec_value(conf->upstream.read_timeout, prev->upstream.read_timeout, 60000); ngx_conf_merge_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_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) ngx_conf_merge_ptr_value(conf->upstream.cache, prev->upstream.cache, NULL); if (conf->upstream.cache && conf->upstream.cache->data == NULL) { ngx_shm_zone_t *shm_zone; shm_zone = conf->upstream.cache; ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"scgi_cache\" zone \"%V\" is unknown", &shm_zone->shm.name); return NGX_CONF_ERROR; } ngx_conf_merge_uint_value(conf->upstream.cache_min_uses, prev->upstream.cache_min_uses, 1); ngx_conf_merge_bitmask_value(conf->upstream.cache_use_stale, prev->upstream.cache_use_stale, (NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_OFF)); if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_OFF) { conf->upstream.cache_use_stale = NGX_CONF_BITMASK_SET |NGX_HTTP_UPSTREAM_FT_OFF; } if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_ERROR) { conf->upstream.cache_use_stale |= NGX_HTTP_UPSTREAM_FT_NOLIVE; } if (conf->upstream.cache_methods == 0) { conf->upstream.cache_methods = prev->upstream.cache_methods; } conf->upstream.cache_methods |= NGX_HTTP_GET|NGX_HTTP_HEAD; ngx_conf_merge_ptr_value(conf->upstream.cache_bypass, prev->upstream.cache_bypass, NULL); ngx_conf_merge_ptr_value(conf->upstream.no_cache, prev->upstream.no_cache, NULL); ngx_conf_merge_ptr_value(conf->upstream.cache_valid, prev->upstream.cache_valid, NULL); if (conf->cache_key.value.data == NULL) { conf->cache_key = prev->cache_key; } 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); #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; } if (conf->upstream.upstream == NULL) { conf->upstream.upstream = prev->upstream.upstream; } if (conf->scgi_lengths == NULL) { conf->scgi_lengths = prev->scgi_lengths; conf->scgi_values = prev->scgi_values; } if (conf->upstream.upstream || conf->scgi_lengths) { clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); if (clcf->handler == NULL && clcf->lmt_excpt) { clcf->handler = ngx_http_scgi_handler; } } if (ngx_http_scgi_merge_params(cf, conf, prev) != NGX_OK) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } static ngx_int_t ngx_http_scgi_merge_params(ngx_conf_t *cf, ngx_http_scgi_loc_conf_t *conf, ngx_http_scgi_loc_conf_t *prev) { u_char *p; size_t size; uintptr_t *code; ngx_uint_t i, nsrc; ngx_array_t headers_names; #if (NGX_HTTP_CACHE) ngx_array_t params_merged; #endif ngx_hash_key_t *hk; ngx_hash_init_t hash; ngx_http_upstream_param_t *src; ngx_http_script_compile_t sc; ngx_http_script_copy_code_t *copy; if (conf->params_source == NULL) { conf->params_source = prev->params_source; if (prev->headers_hash.buckets #if (NGX_HTTP_CACHE) && ((conf->upstream.cache == NULL) == (prev->upstream.cache == NULL)) #endif ) { conf->flushes = prev->flushes; conf->params_len = prev->params_len; conf->params = prev->params; conf->headers_hash = prev->headers_hash; conf->header_params = prev->header_params; return NGX_OK; } } if (conf->params_source == NULL #if (NGX_HTTP_CACHE) && (conf->upstream.cache == NULL) #endif ) { conf->headers_hash.buckets = (void *) 1; return NGX_OK; } conf->params_len = ngx_array_create(cf->pool, 64, 1); if (conf->params_len == NULL) { return NGX_ERROR; } conf->params = ngx_array_create(cf->pool, 512, 1); if (conf->params == 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 (NGX_HTTP_CACHE) if (conf->upstream.cache) { ngx_keyval_t *h; ngx_http_upstream_param_t *s; 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 = ngx_http_scgi_cache_headers; 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 = 0; next: h++; } src = params_merged.elts; nsrc = params_merged.nelts; } #endif 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(conf->params_len, sizeof(ngx_http_script_copy_code_t)); if (copy == NULL) { return NGX_ERROR; } copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code; copy->len = src[i].key.len + 1; copy = ngx_array_push_n(conf->params_len, sizeof(ngx_http_script_copy_code_t)); if (copy == NULL) { return NGX_ERROR; } copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code; copy->len = src[i].skip_empty; size = (sizeof(ngx_http_script_copy_code_t) + src[i].key.len + 1 + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1); copy = ngx_array_push_n(conf->params, 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 = &conf->flushes; sc.lengths = &conf->params_len; sc.values = &conf->params; if (ngx_http_script_compile(&sc) != NGX_OK) { return NGX_ERROR; } code = ngx_array_push_n(conf->params_len, sizeof(uintptr_t)); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) NULL; code = ngx_array_push_n(conf->params, sizeof(uintptr_t)); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) NULL; } code = ngx_array_push_n(conf->params_len, sizeof(uintptr_t)); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) NULL; code = ngx_array_push_n(conf->params, sizeof(uintptr_t)); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) NULL; conf->header_params = headers_names.nelts; hash.hash = &conf->headers_hash; hash.key = ngx_hash_key_lc; hash.max_size = 512; hash.bucket_size = 64; hash.name = "scgi_params_hash"; hash.pool = cf->pool; hash.temp_pool = NULL; return ngx_hash_init(&hash, headers_names.elts, headers_names.nelts); } static char * ngx_http_scgi_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_scgi_loc_conf_t *scf = conf; ngx_url_t u; ngx_str_t *value, *url; ngx_uint_t n; ngx_http_core_loc_conf_t *clcf; ngx_http_script_compile_t sc; if (scf->upstream.upstream || scf->scgi_lengths) { return "is duplicate"; } clcf = ngx_http_conf_get_module_loc_conf (cf, ngx_http_core_module); clcf->handler = ngx_http_scgi_handler; value = cf->args->elts; url = &value[1]; n = ngx_http_script_variables_count(url); if (n) { ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); sc.cf = cf; sc.source = url; sc.lengths = &scf->scgi_lengths; sc.values = &scf->scgi_values; sc.variables = n; sc.complete_lengths = 1; sc.complete_values = 1; if (ngx_http_script_compile(&sc) != NGX_OK) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } ngx_memzero(&u, sizeof(ngx_url_t)); u.url = value[1]; u.no_resolve = 1; scf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0); if (scf->upstream.upstream == NULL) { return NGX_CONF_ERROR; } if (clcf->name.data[clcf->name.len - 1] == '/') { clcf->auto_redirect = 1; } return NGX_CONF_OK; } static char * ngx_http_scgi_store(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_scgi_loc_conf_t *scf = conf; ngx_str_t *value; ngx_http_script_compile_t sc; if (scf->upstream.store != NGX_CONF_UNSET || scf->upstream.store_lengths) { 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 != NGX_CONF_UNSET_PTR && scf->upstream.cache != NULL) { return "is incompatible with \"scgi_cache\""; } #endif if (ngx_strcmp(value[1].data, "on") == 0) { scf->upstream.store = 1; 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; value = cf->args->elts; if (scf->upstream.cache != NGX_CONF_UNSET_PTR) { return "is duplicate"; } if (ngx_strcmp(value[1].data, "off") == 0) { scf->upstream.cache = NULL; return NGX_CONF_OK; } if (scf->upstream.store > 0 || scf->upstream.store_lengths) { return "is incompatible with \"scgi_store\""; } scf->upstream.cache = ngx_shared_memory_add(cf, &value[1], 0, &ngx_http_scgi_module); if (scf->upstream.cache == 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.4.6/src/http/modules/ngx_http_secure_link_module.c000644 001751 001751 00000021645 12305336445 025215 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[16], md5_buf[16]; conf = ngx_http_get_module_loc_conf(r, ngx_http_secure_link_module); if (conf->secret.data) { return ngx_http_secure_link_old_variable(r, conf, v, data); } if (conf->variable == NULL || conf->md5 == NULL) { goto not_found; } if (ngx_http_complex_value(r, conf->variable, &val) != NGX_OK) { return NGX_ERROR; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "secure link: \"%V\"", &val); last = val.data + val.len; p = ngx_strlchr(val.data, last, ','); expires = 0; if (p) { val.len = p++ - val.data; expires = ngx_atotm(p, last - p); if (expires <= 0) { goto not_found; } ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_secure_link_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_secure_link_module); ctx->expires.len = last - p; ctx->expires.data = p; } if (val.len > 24) { goto not_found; } hash.len = 16; hash.data = hash_buf; if (ngx_decode_base64url(&hash, &val) != NGX_OK) { goto not_found; } if (hash.len != 16) { goto not_found; } if (ngx_http_complex_value(r, conf->md5, &val) != NGX_OK) { return NGX_ERROR; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "secure link md5: \"%V\"", &val); ngx_md5_init(&md5); ngx_md5_update(&md5, val.data, val.len); ngx_md5_final(md5_buf, &md5); if (ngx_memcmp(hash_buf, md5_buf, 16) != 0) { goto not_found; } v->data = (u_char *) ((expires && expires < ngx_time()) ? "0" : "1"); v->len = 1; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; return NGX_OK; not_found: v->not_found = 1; return NGX_OK; } static ngx_int_t ngx_http_secure_link_old_variable(ngx_http_request_t *r, ngx_http_secure_link_conf_t *conf, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p, *start, *end, *last; size_t len; ngx_int_t n; ngx_uint_t i; ngx_md5_t md5; u_char hash[16]; p = &r->unparsed_uri.data[1]; last = r->unparsed_uri.data + r->unparsed_uri.len; while (p < last) { if (*p++ == '/') { start = p; goto md5_start; } } goto not_found; md5_start: while (p < last) { if (*p++ == '/') { end = p - 1; goto url_start; } } goto not_found; url_start: len = last - p; if (end - start != 32 || len == 0) { goto not_found; } ngx_md5_init(&md5); ngx_md5_update(&md5, p, len); ngx_md5_update(&md5, conf->secret.data, conf->secret.len); ngx_md5_final(hash, &md5); for (i = 0; i < 16; i++) { n = ngx_hextoi(&start[2 * i], 2); if (n == NGX_ERROR || n != hash[i]) { goto not_found; } } v->len = len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; not_found: v->not_found = 1; return NGX_OK; } static ngx_int_t ngx_http_secure_link_expires_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_http_secure_link_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_secure_link_module); if (ctx) { v->len = ctx->expires.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = ctx->expires.data; } else { v->not_found = 1; } return NGX_OK; } static void * ngx_http_secure_link_create_conf(ngx_conf_t *cf) { ngx_http_secure_link_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_secure_link_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->variable = NULL; * conf->md5 = NULL; * conf->secret = { 0, NULL }; */ return conf; } static char * ngx_http_secure_link_merge_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_secure_link_conf_t *prev = parent; ngx_http_secure_link_conf_t *conf = child; if (conf->secret.data) { if (conf->variable || conf->md5) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"secure_link_secret\" cannot be mixed with " "\"secure_link\" and \"secure_link_md5\""); return NGX_CONF_ERROR; } return NGX_CONF_OK; } if (conf->variable == NULL) { conf->variable = prev->variable; } if (conf->md5 == NULL) { conf->md5 = prev->md5; } if (conf->variable == NULL && conf->md5 == NULL) { conf->secret = prev->secret; } return NGX_CONF_OK; } static ngx_int_t ngx_http_secure_link_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var; var = ngx_http_add_variable(cf, &ngx_http_secure_link_name, 0); if (var == NULL) { return NGX_ERROR; } var->get_handler = ngx_http_secure_link_variable; var = ngx_http_add_variable(cf, &ngx_http_secure_link_expires_name, 0); if (var == NULL) { return NGX_ERROR; } var->get_handler = ngx_http_secure_link_expires_variable; return NGX_OK; } nginx-1.4.6/src/http/modules/ngx_http_split_clients_module.c000644 001751 001751 00000014727 12305336445 025571 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.4.6/src/http/modules/ngx_http_ssi_filter_module.c000644 001751 001751 00000233025 12305336445 025052 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_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_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[] = "